多智能体系统里最难复用的一层,往往不是提示词,而是「几个 Agent 怎么共享状态、怎么互相通知、怎么抢一把锁」。dapr-mcp-server 把这一层直接变成 MCP 工具:它是一个 Go 写的服务器,挡在 Dapr sidecar 前,按当前加载的组件动态注册工具——部署里没有 pub/sub 组件,就不会出现 publish_event。本教程按官方仓库 README 与 docs.dapr.io 的 getting started 六步实操,从装二进制到收紧鉴权,命令逐条可复现。
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_method | Core,始终存在 |
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:装二进制并校验,确认版本
官方提供三条安装路线: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:写两份内存组件,决定「有哪些工具」
建一个 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)会作为工具入参里的组件名使用,所以命名要稳定、好记。
YAML 与目录名要对:官方仓库 README 的快速上手示例用的是 --resources-path resources,而入门指南用 ./components;两者只是目录名不同,关键是 --resources-path 指到你实际放 YAML 的那个目录。in-memory 组件重启即丢数据,只适合本地验证,生产要换成真实 state / pubsub 后端。
Step 3:stdio 通道接桌面客户端
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 存进状态库」,再「读回来」。
端口要一致:终端 A 的 --dapr-grpc-port 50001 与客户端配置里的 DAPR_GRPC_PORT=50001 必须相同,否则服务器连不上 sidecar。stdio 通道没有 HTTP 端口,因此不存在「被网络里别人调用」的问题,适合本机开发。
Step 4:streamable HTTP 通道,接 Claude Code 与自建 Python Agent
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:跑一轮真实工具链——共享状态与事件
工具名和调用顺序有讲究:官方要求先调 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 之外。
save_state,「审核 Agent」用 get_state 读回,改动通过 publish_event 广播给订阅者;需要互斥时用 acquire_lock / release_lock 成对包住临界区。状态与事件都走同一个 sidecar,比各 Agent 自己维护一套 JSON 文件可靠。别把注解当权限:工具上的安全注解是给客户端看的提示,不是服务端强制的访问控制。官方建议把 delete_state、execute_transaction 这类破坏性工具当作「需要人工确认」,并从最小可用工具集开始。真实凭据请用 get_secret 从 secretstores 取,不要塞进 Agent 提示词。
Step 6:上生产前的收紧清单
官方入门页在 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 # 打印版本
上生产前的四项必查:① 是否已开 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 组件会产生实际外部调用与费用,先在隔离环境验证。