高级 📋 6 个步骤 第 616 / 617 篇

用 Dapr MCP Server 把状态、事件与分布式锁变成 Agent 可调用的工具:组件注册、stdio/HTTP 双通道与鉴权实操

dapr-mcp-server 是 Dapr 官方的 Go 语言 MCP 服务器,它挡在 Dapr sidecar 前面,把 state、pub/sub、secrets、actor、lock 等构建块动态注册成 Agent 工具。本教程按官方 README 与 docs.dapr.io 入门指南六步落地:安装并校验二进制、写两份内存组件 YAML、用 stdio 接 Claude Desktop、用 streamable HTTP 接 Claude Code 与自建 Python Agent、跑通 get_components 到 save_state/get_state/publish_event 的工具链,最后用 AUTH_MODE 与组件作用域收紧边界。

2026.11.12· 18 分钟阅读· 约 3439 字· 🧱 Dapr / 🔌 MCP 服务器

多智能体系统里最难复用的一层,往往不是提示词,而是「几个 Agent 怎么共享状态、怎么互相通知、怎么抢一把锁」。dapr-mcp-server 把这一层直接变成 MCP 工具:它是一个 Go 写的服务器,挡在 Dapr sidecar 前,按当前加载的组件动态注册工具——部署里没有 pub/sub 组件,就不会出现 publish_event。本教程按官方仓库 README 与 docs.dapr.io 的 getting started 六步实操,从装二进制到收紧鉴权,命令逐条可复现。

🧱 适合谁:已经在用或打算用 Dapr 做微服务、想让多个 Agent 通过共享状态与事件协作的后端工程师;以及想给自建 Agent 加一套标准化工具注册机制的开发者。前置准备:Dapr CLI 且已 dapr init 初始化为 self-hosted 模式、一个 MCP 客户端(Claude Desktop / Cursor / Claude Code 任一)、Go 1.26.6+(仅源码安装需要)、curl。官方许可为 Apache 2.0。主线用内存组件,不需要云账号、不需要模型 API Key。

先弄清:工具是「按组件动态注册」的,不是写死的清单

官方 README 反复强调这一点,理解它能省掉一半排错时间:服务器会跟随 Dapr 对组件的热重载,每次客户端列工具或调用 get_components 时都去问一次 sidecar;某类组件的头一个出现时加进对应工具,最后一个消失时移除,并通知客户端工具列表变了。

组件类型加载后新增的工具注册方式
(无组件也注册)get_components、invoke_service、invoke_actor_methodCore,始终存在
state.*save_state、get_state、get_bulk_state、delete_state、execute_transaction条件注册
pubsub.*publish_event、publish_event_with_metadata条件注册
secretstores.*get_secret、get_bulk_secrets条件注册
lock.*acquire_lock、release_lock条件注册

还有 bindings.* 的 invoke_output_binding、crypto.* 的 encrypt_data / decrypt_data、conversation.* 的 converse_with_llm。每个工具调用都带 OpenTelemetry span、metrics 与可选日志导出。

项目状态:该服务器随 Dapr 1.19 线的「developing-ai / mcp」文档一起发布,官方入门页最近修改于 2026-11-10;社区文章把它标为 alpha 并提醒工具名与配置仍可能变化。动手前请以 docs.dapr.io/developing-ai/mcp/dapr-mcp-server/ 的工具参考页核对当前工具名,别把本教程的清单当成永久契约。

Step 1:装二进制并校验,确认版本

1 下载、对 checksums、放进 PATH

官方提供三条安装路线:Release 二进制、容器镜像、从源码 go install。主线走二进制,因为官方入门页要求先对 checksums.txt 再放行——这一步别省。

# Linux amd64(官方入门页原样;ARM 用 linux-arm64)
curl -LO https://github.com/dapr/dapr-mcp-server/releases/latest/download/dapr-mcp-server-linux-amd64
curl -LO https://github.com/dapr/dapr-mcp-server/releases/latest/download/checksums.txt
sha256sum --ignore-missing -c checksums.txt
chmod +x dapr-mcp-server-linux-amd64
sudo mv dapr-mcp-server-linux-amd64 /usr/local/bin/dapr-mcp-server

# 确认安装
dapr-mcp-server --version

# 另外两条路线(按需二选一)
docker pull ghcr.io/dapr/dapr-mcp-server:latest
go install github.com/dapr/dapr-mcp-server/cmd/dapr-mcp-server@latest

# 前置:初始化 Dapr self-hosted(已做过可跳过)
dapr init

Windows 用 PowerShell 下载 dapr-mcp-server-windows-amd64.exe 到 $Env:LOCALAPPDATAdapr-mcp-server,用 Get-FileHash 与 checksums 里对应行比对,再把该目录加进用户 PATH,重开终端生效(官方入门页给了完整脚本,照抄即可)。

💡 从源码 go install 装出来的二进制执行 --version 会显示 dev,官方说明版本号只在 Release 构建时打入——看到 dev 不代表装坏了。容器镜像以非 root 用户运行,默认监听 --http 0.0.0.0:8080 并自带 HEALTHCHECK。

别跳过校验:这个进程最终会拿到读写状态、发事件、取密钥的能力,供应链上值得多花十秒核对哈希。官方还提供 MCP Registry 条目 io.github.dapr/dapr-mcp-server,支持浏览注册表的客户端可直接按容器装;用注册表安装时同样先确认镜像来源。

Step 2:写两份内存组件,决定「有哪些工具」

2 components 目录里放 statestore 与 pubsub

建一个 components 文件夹,放两个内存组件。官方说明:有这两个组件,服务器会注册三个 Core 工具外加 state 与 pub/sub 两组工具。

mkdir -p components

# components/statestore.yaml
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: statestore
spec:
  type: state.in-memory
  version: v1

# components/pubsub.yaml
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: pubsub
spec:
  type: pubsub.in-memory
  version: v1

想暴露更多能力,就按官方工具参考页补对应类型的组件:bindings.*、secretstores.*、lock.*、crypto.*、conversation.*。组件名(metadata.name)会作为工具入参里的组件名使用,所以命名要稳定、好记。

✅ 这一步就是给 Agent 定工具边界:不加载 secretstores 组件,Agent 连 get_secret 这个工具都看不到。比在提示词里写「不准读密钥」可靠得多,是这套设计里最值得抄的一点。

YAML 与目录名要对:官方仓库 README 的快速上手示例用的是 --resources-path resources,而入门指南用 ./components;两者只是目录名不同,关键是 --resources-path 指到你实际放 YAML 的那个目录。in-memory 组件重启即丢数据,只适合本地验证,生产要换成真实 state / pubsub 后端。

Step 3:stdio 通道接桌面客户端

3 先单独起 sidecar,再让客户端拉起服务器

stdio 模式下 MCP 客户端自己启动服务器进程,所以要把 sidecar 单独跑在固定 gRPC 端口上,并通过环境变量告诉服务器去哪找它。

# 终端 A:单独起 sidecar,固定 gRPC 端口,保持运行
dapr run --app-id dapr-mcp-server --dapr-grpc-port 50001 --resources-path ./components

# 终端 B:把服务器注册给 Claude Code
claude mcp add dapr --env DAPR_GRPC_PORT=50001 -- /usr/local/bin/dapr-mcp-server

# 或写进 claude_desktop_config.json / ~/.cursor/mcp.json
{
  "mcpServers": {
    "dapr": {
      "command": "/usr/local/bin/dapr-mcp-server",
      "env": { "DAPR_GRPC_PORT": "50001" }
    }
  }
}

# 改完桌面客户端配置要重启客户端才生效

官方给的验收话术很直接:让助手「列出我的 Dapr 组件」,它应调用 get_components 并报出 statestore 与 pubsub;接着说「把键 greeting 的值 hello 存进状态库」,再「读回来」。

🔎 官方说明服务器把日志以 JSON 写到 stderr,这样不会和 stdout 上的 MCP 消息混在一起;多数客户端在 MCP 日志视图里能直接看到。配置里务必写完整路径,因为客户端不一定继承你 shell 的 PATH;Windows 的 JSON 里每个反斜杠要写成两个。

端口要一致:终端 A 的 --dapr-grpc-port 50001 与客户端配置里的 DAPR_GRPC_PORT=50001 必须相同,否则服务器连不上 sidecar。stdio 通道没有 HTTP 端口,因此不存在「被网络里别人调用」的问题,适合本机开发。

Step 4:streamable HTTP 通道,接 Claude Code 与自建 Python Agent

4 一条命令同时起 sidecar 与服务器,先探活

HTTP 模式更贴近多 Agent 共享:一个服务器进程,多个客户端连接。官方 README 与入门页给的写法如下,注意 dapr run 会替服务器设好 DAPR_GRPC_PORT。

# 起服务(HTTP 端点为 http://localhost:8080/)
dapr run --app-id dapr-mcp-server --resources-path ./components   -- dapr-mcp-server --http localhost:8080

# 探活:200 且 status healthy 表示能连到 sidecar;503 表示 sidecar 不可达
curl http://localhost:8080/readyz
# PowerShell 等价:Invoke-RestMethod http://localhost:8080/readyz

# Claude Code 注册 HTTP 传输
claude mcp add --transport http dapr http://localhost:8080/

# 任意客户端的 mcpServers 写法
{ "mcpServers": { "dapr": { "url": "http://localhost:8080/" } } }

官方还提供了不依赖任何 Agent 框架的裸 MCP 客户端验收脚本,适合确认「到底是工具问题还是模型问题」:

import asyncio
from mcp import ClientSession
from mcp.client.streamable_http import streamablehttp_client

async def main():
    # 连上官方 HTTP 端点
    async with streamablehttp_client("http://localhost:8080/") as (read, write, _):
        async with ClientSession(read, write) as session:
            await session.initialize()
            tools = await session.list_tools()
            print([tool.name for tool in tools.tools])   # 应含 get_components 与 state/pubsub 工具
            result = await session.call_tool("get_components", {})
            print(result.structuredContent)              # 应列出 statestore 与 pubsub

asyncio.run(main())
🎉 预期效果:readyz 返回 200,Python 脚本打印出的工具名里有 save_state、get_state、publish_event,且 get_components 的结构化结果能看到两个组件名。把这份脚本留在仓库里,后续换组件或升级版本时它就是回归测试。

官方原文的安全边界:「Bind to localhost unless authentication is on」——没设 AUTH_MODE 时,任何能访问该 HTTP 端口的人都能调用全部工具,包括写状态、发事件、批量取密钥。本地用就监听 localhost;要暴露到网络前先按官方 authentication 页开鉴权(OIDC / SPIFFE / Dapr Sentry / hybrid 四选一或组合),或用组件作用域只加载必要组件。浏览器跨源客户端才需要 DAPR_MCP_CORS_ORIGIN,默认不返回任何 CORS 头。

Step 5:跑一轮真实工具链——共享状态与事件

5 get_components 到 save_state 到 publish_event

工具名和调用顺序有讲究:官方要求先调 get_components,因为其余工具需要用到它返回的状态库与 broker 名字。下面是给 Agent 的指令模板与用官方 Python MCP 客户端复现的等价调用。

# 给 Agent 的系统提示词(示例,组件名按 get_components 返回值替换)
先调用 get_components,找出 state store 与 pub/sub 的名字。
把发布清单维护在 state store 的 release-checklist 这个键下。
每次改动后,把新清单发布到 pubsub 的 checklist 主题。
只允许使用读类工具与 save_state,不要调用 delete_state 或 execute_transaction。

# 等价的客户端调用序列(伪代码,按你用的 SDK 调整)
session.call_tool("get_components", {})
session.call_tool("save_state", {"storeName": "statestore",
                                 "key": "release-checklist",
                                 "value": "update the quickstart README"})
session.call_tool("get_state", {"storeName": "statestore", "key": "release-checklist"})
session.call_tool("publish_event", {"pubsubName": "pubsub",
                                    "topic": "checklist", "data": "..."})

官方工具参考页给每个工具标了安全属性:get_state / get_bulk_state 是只读,delete_state 是破坏性但幂等,publish_event 非幂等,get_bulk_secrets 标为高风险。MCP 客户端可以用这些注解区分读写;社区实践则更直接——在客户端侧用 allowed_tools 白名单把破坏性工具整个挡在 Agent 之外。

🤝 多智能体怎么落:让「资料 Agent」把产物写进 save_state,「审核 Agent」用 get_state 读回,改动通过 publish_event 广播给订阅者;需要互斥时用 acquire_lock / release_lock 成对包住临界区。状态与事件都走同一个 sidecar,比各 Agent 自己维护一套 JSON 文件可靠。

别把注解当权限:工具上的安全注解是给客户端看的提示,不是服务端强制的访问控制。官方建议把 delete_state、execute_transaction 这类破坏性工具当作「需要人工确认」,并从最小可用工具集开始。真实凭据请用 get_secret 从 secretstores 取,不要塞进 Agent 提示词。

Step 6:上生产前的收紧清单

6 鉴权、作用域、K8s 探针与会话亲和

官方入门页在 Kubernetes 一节给了一份完整 Deployment:靠 dapr.io/enabled 与 dapr.io/app-id 两个注解让注入器补上 sidecar,三个探针分别指向 /startupz、/livez、/readyz。

# Deployment 关键片段(官方入门页)
annotations:
  dapr.io/enabled: "true"
  dapr.io/app-id: "dapr-mcp-server"
containers:
  - name: dapr-mcp-server
    image: ghcr.io/dapr/dapr-mcp-server:latest
    ports:
      - containerPort: 8080
    startupProbe:
      httpGet: { path: /startupz, port: 8080 }
      failureThreshold: 30
      periodSeconds: 2
    livenessProbe:
      httpGet: { path: /livez, port: 8080 }
    readinessProbe:
      httpGet: { path: /readyz, port: 8080 }

# 相关环境变量与旗标
# AUTH_MODE=oidc | spiffe | dapr-sentry | hybrid
# DAPR_MCP_CORS_ORIGIN=https://app.example.com   # 仅浏览器跨源客户端需要
# --health-check             # 探测本地 /livez 并以 0/1 退出
# --health-check-addr host:port
# --version                  # 打印版本
📌 官方特别提醒:streamable HTTP 的会话保存在服务器内存里。多副本部署时必须在流量入口配会话亲和,让同一客户端始终落到同一个副本,否则会话会丢。另外 Service 一旦建起来,集群里任何 Pod 都能访问,务必同步开启 AUTH_MODE 或用 NetworkPolicy 限制来源。

上生产前的四项必查:① 是否已开 AUTH_MODE,没开就别把端口暴露到 localhost 之外;② 组件作用域是否只加载了 Agent 真正需要的类型(尤其别随手加载 secretstores / conversation);③ 客户端侧是否做了破坏性工具白名单与人工确认;④ 多副本是否配了会话亲和。想继续深入,官方 quickstarts 仓库里同一份 Agent 分别用 LangGraph、Dapr Agents 与 OpenAI Agents SDK 通过 MCP 客户端和 MCPServer 资源跑过一遍,是很好的下一步。

常见问题 FAQ

现象原因与处理
工具列表里没有 save_state没有加载 state.* 组件,或 --resources-path 指错目录
readyz 返回 503官方说明表示 Dapr sidecar 不可达,先确认 dapr init 与 sidecar 进程
stdio 客户端一直连不上DAPR_GRPC_PORT 与 sidecar 的 --dapr-grpc-port 不一致;配置里要用完整路径
--version 显示 dev官方说明从源码 go install 构建不打版本号,属正常现象
桌面客户端看不到日志日志是 JSON 写到 stderr,去客户端的 MCP 日志视图找
浏览器客户端跨域被拦按需设 DAPR_MCP_CORS_ORIGIN,默认不返回 CORS 头
多副本下会话莫名断开streamable HTTP 会话在内存中,需在入口配会话亲和
组件增删后工具没变让客户端重新列工具或再调一次 get_components 触发同步

做到哪一步算成功

四条验收线:① dapr-mcp-server --version 正常输出,二进制哈希与 checksums.txt 对得上;② 两个内存组件加载后 get_components 返回 statestore 与 pubsub,工具列表出现 state 与 pub/sub 组;③ stdio 与 HTTP 两条通道各能跑通一次「列出组件 → 存状态 → 读回 → 发事件」;④ HTTP 端点只绑在 localhost,且已明确记录生产前要开的 AUTH_MODE 与组件作用域。做到这些,再把 in-memory 组件换成真实后端,按官方 quickstarts 接一支多 Agent 队伍。

交付前自查:本教程命令来自官方仓库 README 与 docs.dapr.io 的 getting started(页面最近修改于 2026-11-10);工具名、旗标与鉴权模式请以你部署当天的工具参考页为准。该能力仍偏新,社区文章标注为 alpha;接真实 secretstores、bindings 与 conversation 组件会产生实际外部调用与费用,先在隔离环境验证。

← 返回教程中心