mcp-server-proposal - #1541
Conversation
Signed-off-by: Oxidaner <18622412361@163.com>
|
|
@Oxidaner 请把proposal放到issue下面。另外proposal似乎只考虑了nacos,但其实admin已经对nacos/zookeeper这类注册中心进行了屏蔽,已经有统一的resource抽象,可以在这个方面纠正code agent。spec其他地方没有大问题。 |
|
另外,mcp的认证可以再细化一下 |
|
Dubbo Admin 业务 MCP Server MVP 设计方案 1. 摘要本方案为 Dubbo Admin 增加业务 MCP 能力。管理员从 Admin 统一 Resource catalog 中选择已经发现的 Dubbo 方法,为 MCP Server 和 MCP Tool 补充业务说明,然后发布配置。MCP Client 可以通过 Dubbo Admin 查看这些 Tools 并发起调用。 本功能不会自动把所有 Dubbo 方法暴露给 Agent。Provider metadata 提供结构契约,管理员提供业务语义和开放范围。 一个 MCP Tool 绑定一个确定的 Dubbo 方法签名和调用协议。一个 MCP Server 可以组合多个 Dubbo Service 中的方法,但这些方法必须来自同一个 discovery。 2. 背景与业务场景2.1 普通 Consumer 与泛化 Consumer 的区别普通 Java Consumer 会依赖 API JAR。JAR 中的接口已经为业务代码提供了方法名、参数类型、参数顺序,以及开发者在源码中赋予它们的含义。Provider metadata 不是用来替普通 Consumer 自动填写业务参数的。 泛化 Consumer 不需要 API JAR,它通过以下通用契约调用 Dubbo: Object $invoke(String method, String[] parameterTypes, Object[] args)方法名和有序参数类型仍然不可缺少。它们用于定位重载方法,并告诉 Provider 应该把通用的 Map、List 和标量值还原成哪些真实参数类型。 泛化调用解决的是编译期接口依赖问题,不会自动告诉 Agent 一个方法的业务作用,也不会自动解释每个参数的业务含义。 2.2 Provider metadata 是结构契约,不是语义契约Apache Dubbo Java 的 因此,现有 Provider metadata 可以告诉 Admin 某个方法是: 它不能可靠地告诉 Agent 第一个参数表示 本方案把调用契约分为两层:
这和手工维护另一份完整 API 定义不同。用户不需要重写 DTO 结构、RPC 序列化、Provider 发现、路由、负载均衡或泛化对象还原规则。用户只负责选择开放范围,并补齐 metadata 本身不具备的业务语义。 2.3 适用场景当 MCP Client 需要调用一组经过筛选的内部 Dubbo 能力,而又不适合为每个服务嵌入 API JAR 或生成专用 Consumer 时,可以使用本功能。例如,平台团队可以把现有服务中的少量操作开放给编码 Agent、客服 Agent 或内部自动化工具。 本功能不用于替代普通应用之间的强类型 Dubbo 调用。 3. 目标与非目标3.1 MVP 目标MVP 完成以下闭环:
3.2 MVP 非目标MVP 不包含:
这些是明确的 MVP 边界,不是未定义行为。 4. 源码基线与现有能力本方案基于以下源码版本核查:
源码核查时,官方 MCP Go SDK 关键的外部源码入口包括 Dubbo Java 的 4.1 Admin 已屏蔽注册中心差异MCP 业务层不以 Nacos、ZooKeeper 或其原生数据模型作为输入。Admin 的 discovery factory、ListWatcher、Informer 和 subscriber 已经把不同注册中心的数据归一为 Resource: 源码中的两条现有路径证明了该边界:
MCP 实现必须遵守同一边界:
因此 MCP 的支持条件只有一个:该 Admin discovery 能产出本方案依赖的统一 metadata/instance Resources。MCP 不维护 Provider SDK 或 Dubbo client 的注册中心兼容矩阵,也不据此缩小自身支持范围。 当前 MCP 支持矩阵直接跟随 Admin 已注册的 production discovery types:
这里不再单独限定 Nacos 2.x 或 ZooKeeper 3.x 的服务端小版本。具体服务端版本兼容性属于各 discovery adapter 的既有支持范围;只要 adapter 成功产出相同 Resource contract,MCP 行为就应一致。未来新增 production discovery type 时,MCP 不增加类型分支,只增加一组公共 Resource contract tests。 活跃服务索引Service definition 是持久化契约,Provider 下线后仍可能存在,不能用它判断实例是否存活。 MVP 的公共判定规则仍是:声明导出该接口的 Provider 应用中,是否存在至少一个可用 每个 Service identity 加协议保留三态: MVP 不做实例级 revision 与接口 definition 的精确关联。因此应用仍在线但新版本已移除某接口时,旧 definition 可能让状态暂时保持
4.2 现有方法解析能力
当前合并类型定义的 helper 不能直接用于证明所有 Provider definition 一致,因为它遇到同名类型时会保留第一份定义。MCP 发布前必须分别规范化每份匹配的 Provider definition,再比较规范化结果。 4.3 现有泛化调用能力
业务 MCP 不能复用这段编排控制流。非幂等 RPC 可能已经执行成功,只是 Admin 在接收响应时发生了超时或连接错误。此时继续尝试另一个目标会造成重复业务效果。 可以复用或抽取精确方法查找、基础类型参数解码、结果转换为 JSON 兼容值等纯 helper。Provider 选择和重试控制必须为业务 MCP 单独实现。 4.4 现有 MCP endpoint
它的 现有 4.5 现有存储边界现有 本功能为 ResourceStore 注册 MCP 资源类型,并在 ResourceStore 之上增加职责单一的 MCP repository 或 manager。它不修改 Governor 的资源范围。 5. 领域模型5.1 术语
5.2 Binding identity每个 Tool binding 保存精确的 Operation identity,以及 identity 之外的调用协议: source:
mesh: production-registry
serviceName: com.example.OrderService
group: trade
version: 1.0.0
methodName: createOrder
parameterTypes:
- java.lang.String
- com.example.OrderRequest
protocol: dubbo有序参数类型列表是 identity 的组成部分。仅保存方法名无法区分重载方法。 顶层参数 alias 不属于 Dubbo metadata,也不参与 Operation identity。它通过不可编辑的参数位置绑定到 RPC 参数: type MCPParameterBinding struct {
Position int `json:"position"`
Alias string `json:"alias"`
Description string `json:"description,omitempty"`
}
Draft 和 Published snapshot 都保存完整的
选择另一个 Operation identity,或者原 Operation 的有序参数类型发生变化时,Admin 必须重新生成 parameter bindings,不能按 alias、类型或“相同下标”自动迁移旧配置。多个参数可以具有相同类型,按类型匹配无法判断业务语义。 存在一个 metadata 无法解决的硬边界:如果 Provider 保持完全相同的有序参数类型,却改变了两个位置的业务含义,例如
协议必须存进 binding,不能在调用时动态跟随 Provider 当前广播的内容。同一个 Tool 的两次调用如果走了不同协议,其超时传递方式(§8.6)和错误信息形状(§9.2)都不一样,会让线上问题无法归因。持久化之后,审计日志(§12)也能记录本次调用实际使用的协议。
MCP Tool 名称由用户定义,在同一个 MCP Server 内必须唯一,不要求与 Dubbo 方法名相同。 一个 MCP Server 内的所有 binding 必须使用同一个 5.3 MCPServerResource本功能新增 建议的逻辑结构如下: metadata:
name: orders-mcp
mesh: production-registry
resourceVersion: "42"
spec:
draft:
displayName: Order Operations
description: Create and query orders for internal support workflows.
requiredOAuthScopes:
- orders:read
tools: []
published:
revision: 7
publishedAt: "2026-08-26T10:00:00Z"
displayName: Order Operations
description: Create and query orders for internal support workflows.
requiredOAuthScopes:
- orders:read
tools: []每个已发布 Tool 快照保存:
Server 级 5.4 MCPCredentialResourceCredential 与 Published revision 分开保存,防止配置发布恢复旧凭证或撤销已经完成的 revoke。 metadata:
name: credential-id
mesh: production-registry
spec:
serverId: orders-mcp
name: production-agent
secretHash: sha256:...
status: active
expiresAt: "2026-11-24T10:00:00Z"
createdAt: "2026-08-26T10:00:00Z"一个 MCP Server 可以有多个命名 Credential。所有 Credential 权限相同,只能访问该 Server 的全部 Tools,不能访问 Admin Console API 或其他 MCP Server。 删除 MCP Server 时,必须级联删除该 Server 的全部 Credential,避免留下无法通过任何 UI 路径查看或撤销的孤儿凭证。 Token 包含非敏感的 credential ID 和随机 secret: Admin 只在创建时返回一次明文 Token。Admin 保存高熵随机 secret 的 SHA-256 hash,并使用常量时间比较。secret 至少 256 bit,来自密码学安全随机源。 Credential 的权威数据源是 ResourceStore。因此业务 MCP 要求使用数据库 store:memory store 是进程内的,多副本部署下在一个副本上完成的 revoke 对其他副本不可见,被撤销的凭证仍然可用。这是安全约束,不是性能取舍。 Agent 不需要在对话中记住或重复 Token。MCP Client 负责保存凭证,并在每次 HTTP 请求中自动添加 5.5 双认证模型业务 MCP endpoint 同时接受两类 Bearer token:
两类 token 使用同一个 HTTP 入口和同一个 Admin 只充当 OAuth Resource Server,不实现登录页、Authorization Endpoint、Token Endpoint、refresh token 或 Client Registration。OAuth Client 与外部 Authorization Server 完成 Authorization Code + PKCE 或 Client Credentials 流程,Admin 只验证取得的 access token。 OAuth 部署配置属于 Admin 实例级配置,而不是每个 MCPServerResource 重复保存: mcp:
oauth:
issuer: https://idp.example.com
audience: https://admin.example.com/business-mcp
每个 MCP Server 保存 两种认证统一产出内部身份: type MCPPrincipal struct {
Kind string // credential | oauth
Subject string // credential ID, OAuth sub or client ID
ServerID string // credential 模式必填
Scopes []string // OAuth token scopes;credential 模式填充该 Server 的 effective scopes
TokenExpires time.Time
}后续审计和授权只依赖 6. 控制面6.1 草稿编辑创建或编辑 Server 时只修改 用户从 Admin 已发现的 Service catalog 中选择方法。Admin 保存精确签名,而不是只保存 interface 或方法名。 Service catalog 必须显示每个候选方法的 用户还需要为每个方法选择调用协议。Admin 根据当前导出情况给出默认值(两种都可用时默认 Admin 按顺序展示每个 RPC 参数类型,这些结构字段不可修改。由于 Java metadata 没有可靠的参数名,Admin 默认显示 例如:
Position 和 RPC type 不可修改。运行时按保存的 Position 把别名映射回有序参数。 这里的“不可修改”由后端保证:Console 请求只接受 MVP 不提供嵌套 DTO 字段编辑器。嵌套对象继续使用 metadata 中的属性名和类型。如需说明特殊字段含义,可以写在 Tool description 中。 6.2 草稿校验草稿校验是只读操作,不发布配置,也不会自动发送业务 RPC。 至少校验以下内容:
以上均为 error,校验不通过不能发布。 以下为 warning,不阻塞发布:
存活状态不作为发布门禁,原因有三。第一,运行时对这种情况已有干净的处理:§8.3 会把 Admin 在 warning 详情中必须保留 校验结果分别返回 errors 和 warnings,不修改当前生效版本。 6.3 原子发布Publish 会基于当前 metadata 重新执行完整校验,生成不可变的 Published snapshot,递增 Server revision,并通过一次 CAS 替换 如果校验或 CAS 失败,旧的 Published snapshot 继续生效。MCP Client 不会看到部分更新的 Tool 列表。 所有 Draft 更新和 Publish 请求都必须携带期望的 ResourceStore 6.4 契约 fingerprintAdmin 为每个 binding 生成两个确定性的 fingerprint。
Hash 前对 Map key 和 object property 排序。用户填写的别名、描述、annotations 和 timeout 不属于 Provider 结构契约,不进入任何 fingerprint。binding 的调用协议同样不进入——它是传输选择,不是结构契约。 Provider 存活状态也不进入 fingerprint。它变化频繁,不会改变已经发布的 Tool 契约。 为什么只有入参参与 fail closed已发布的对外契约只有 因此入参结构的变化会真实地让已发布契约失效:§7.3 对每个 POJO 设置了 返回值结构的变化则不会让任何已发布的东西失效。Provider 给响应 DTO 增加一个字段(Dubbo/Hessian 生态中最常见的向后兼容演进方式),多出来的字段会原样透传给 Client,不会破坏任何东西。如果把它也纳入 fail closed,等于把最常见的兼容变更配置成了最高级别的故障。 返回值 fingerprint 变化时,Admin 在管理页对该 Tool 显示"Provider 返回结构已变更,建议重新发布",不影响 校验时机与失败范围在 这与 §8.3 对 Admin 不会静默更新已发布 Schema。Provider 发生入参 breaking change 时,应发布新的 group 或 version,再由管理员发布新的 binding。 6.5 Provider metadata 发布前置条件Admin 可以发现 Published fingerprint 与当前 当前 Dubbo Java 源码没有把 metadata 发布成功作为 Provider 注册屏障:
因此,生产使用需要额外的 Provider 发布约束:新版本的 service definition 必须成功写入并经过验证,然后新 Provider 实例才能进入流量。只配置 这个约束可以通过 Provider 侧改造实现,也可以由部署系统在 metadata 验证通过前保持新实例不可用。它不属于 Admin 运行时,但它是强契约一致性的前置条件。 7. 从 metadata 生成 MCP inputSchema7.1 顶层参数模型Tool input 固定为 JSON object,其 properties 与方法的有序参数一一对应。 {
"type": "object",
"properties": {
"tenantId": {
"type": "string",
"description": "订单所属租户"
},
"request": {
"$ref": "#/$defs/com.example.OrderRequest",
"description": "创建订单请求"
}
},
"required": ["tenantId", "request"],
"additionalProperties": false
}方法的每一个顶层参数都进入 Binding 单独保存参数 Position,不能依赖 JSON object 的遍历顺序决定 RPC 参数顺序。 7.2 类型映射MVP 把闭合的 Java metadata 类型映射为以下 JSON 表达:
Admin proto 中的
命名类型使用本地 Java primitive 不接受 以下契约在 MVP 中直接校验失败,不生成含义不确定的 Schema:
dubbo-go Provider 的发布侧类型映射dubbo-go Provider 也可以接入这套设计,但必须以 Java 类型词汇发布 service definition,不能发布 Go 原生类型名。这是对发布方的规范要求,Admin 侧不做 Go 类型识别,也不按 这条要求不是 Admin 的偏好,而是 dubbo-go 运行时自身的既定契约。 func shouldUnwrapPackedVariadicArg(variadicType string, variadicSliceType reflect.Type) bool {
if slices.Contains(javaTypeNamesForType(variadicSliceType), variadicType) {
return true
}
...
}
发布侧映射规则:
变参方法的 采用 Java 词汇后, 字段名不做映射property 名必须保持 Go 侧的 wire name( 类型名和字段名的性质不同:类型名除变参尾项外是描述性的,而 property 名是 wire 级的,Generalizer 的 由此产生的后果记录在 §16:同一个接口如果同时存在 Java 和 Go Provider,两者的 property 名不同( 7.3 严格拒绝未知字段未知字段必须被拒绝,不能先删除再调用:
这条规则需要递归执行。只校验顶层 required 的 validator 不满足要求。 生成的
|
| 情况 | 必须做的处理 |
|---|---|
long / java.lang.Long |
十进制 string 转 int64,转换失败或溢出则拒绝 |
byte / short / int 及其 wrapper |
JSON integer 的范围检查,越界则拒绝 |
char / java.lang.Character |
校验字符串长度为 1 |
float / double |
校验为有限值 |
关键在于这些标量不只出现在顶层参数。一个 long 可以嵌在 POJO 的属性里、嵌在 List 的元素里、嵌在 Map<String, T> 的 value 里,也可以嵌在上述任意组合的多层嵌套中。
因此 Admin 必须按已发布的 inputSchema 递归遍历整棵参数树完成解码,而不是只处理顶层的几个参数。这一步的工作量明显大于"若干技术转换",实施计划中应作为独立事项排期(见 §15)。
递归遍历的路径与 §7.3 的递归校验一致,两者可以合并为一次遍历:先校验后解码,或在同一次下降中完成。
解码失败一律在 RPC dispatch 之前拒绝,返回 §9.2 的 not-executed。
8. MCP 运行时与 RPC 调用
8.1 Endpoint 与无状态行为
每个已发布 Server 暴露一个业务 endpoint:
POST /mcp/{serverId}
现有运维 endpoint 保持不变:
POST /api/mcp
业务 endpoint 刻意不与 /api/mcp 共享路径前缀。原因见 §8.2:现有鉴权中间件按精确路径匹配运维 endpoint,任何共前缀的方案都需要改动那段代码,而改动它的主要风险是让运维 endpoint 的静态 API Key 校验被意外绕过。
业务 endpoint 使用官方 MCP Go SDK Streamable HTTP handler:
mcp.StreamableHTTPOptions{
Stateless: true,
JSONResponse: true,
}MVP 不使用 GET/SSE Session、服务端主动请求、Session state 或 revision pinning。
每个 HTTP 请求都读取当前 MCPServerResource,并使用当前 Published snapshot。
缓存需要分两层,因为一次 tools/list 的结果不再只由已发布配置决定:
- 按
serverId + published revision缓存:编译后的inputSchema和 SDK Server object。这些只随 publish 变化。 - 按 Provider metadata 的
resourceVersion缓存:inputFingerprint的计算结果。它随 Provider 发版变化,与 published revision 无关。
不能只用 serverId + published revision 做 key,否则 §6.4 要求的 fingerprint 重算会被缓存掉,契约漂移将检测不到。反过来,如果每个请求都对每个 binding 重新读取并规范化 metadata,在数据库 store 下,一次 tools/list 会退化成 N 次索引查询加 N 次规范化。
LiveServiceIndex 的状态是进程内的,直接读取即可,不需要额外缓存。
选择任何缓存之前必须先读取权威资源。
如果 Agent 执行 tools/list 后发生了 publish,后续 tools/call 使用新的 Published revision。Tool 已删除或参数已经不兼容时,Admin 返回配置已更新并要求重新获取 Tool 列表的错误。
由于 MVP 是无状态的,服务端无法主动发送 notifications/tools/list_changed。Agent 手中的 Tool 列表可能已经过期,它只能通过下一次 tools/list 或一次失败的 tools/call 发现这一点。这是 §8.3 动态列表的已知代价。
8.2 鉴权
管理 API 继续使用 Admin 现有登录和授权系统。业务 MCP endpoint 同时支持 §5.4 的 Admin API Key 和 §5.5 的 OAuth access token。
请求必须包含:
Authorization: Bearer <api-key-or-oauth-access-token>鉴权发生在 MCP request handler 之前。统一 verifier 按 token namespace 分派:
mcp_token 查询 Credential,校验 active、expiration、secret hash 和 path 中的{serverId},成功后为 SDKTokenInfo填充该 Server 的 required scopes;- 其他 token 交给 OAuth verifier,通过 issuer 的 OIDC discovery 获取并缓存 JWKS,校验 JWT signature、issuer、audience、expiration/not-before,并提取 subject/client ID 与 scopes;
- 两者统一生成
MCPPrincipal;认证失败返回 401,OAuth token 缺少 Server required scopes 返回 403,不暴露 MCP Tool 信息。
业务 MCP 复用固定 MCP Go SDK v1.4.0 的 auth.RequireBearerToken 做 Bearer header 解析、统一 expiration/scopes 检查和 TokenInfo context 注入,并复用 auth.ProtectedResourceMetadataHandler 提供 RFC 9728 metadata。该版本默认要求每个 TokenInfo.Expiration 非零,且没有允许永久 token 的配置项,因此 API Key 的 expiresAt 改为必填;不新增自定义中间件绕过依赖默认行为。OAuth 第一版固定支持 JWT access token + OIDC discovery/JWKS,不实现 opaque-token introspection。JWT signature、claims 校验和 JWKS 缓存必须复用成熟 OIDC verifier;选定并固定具体依赖版本后,按该版本公开默认值配置,不自行编写密码学或重复声明默认项。
SDK 的 RequireBearerTokenOptions.Scopes 和 ResourceMetadataURL 是静态 handler 选项。业务 runtime 已按 serverId + published revision 缓存 handler,因此每个 Server 的缓存 handler 使用自己的 required scopes 和 metadata URL;不为每个请求复制 OAuth 配置。
OAuth 模式必须公开:
/.well-known/oauth-protected-resource/<business-mcp-path>
资源文档的 resource 是对应 business MCP endpoint,authorization_servers 来自实例级 issuer,scopes_supported 来自该 Server 的 required scopes。401/403 的 WWW-Authenticate 指向同一个 metadata URL。access token 不能通过 query string 传递,也不能原样转发给下游 Dubbo Provider。
与现有中间件的关系
现有 authMiddleware 是全局注册的(r.Use(c.authMiddleware())),它按精确路径相等判断是否为 MCP 请求:
isMCPRequest := requestPath == "/api/mcp" || (c.mcpPath != "" && requestPath == c.mcpPath)任何不等于运维 endpoint 的路径都会落到会话认证分支,检查 session 中的 user。因此业务 MCP endpoint 如果沿用该中间件,API Key 和 OAuth Bearer 请求都会在到达 MCP handler 之前被判为未登录并返回 401。
业务 MCP 挂在独立的 gin RouterGroup 上,使用统一 Bearer 中间件,不复用也不修改 authMiddleware。这样 §13 要求的"现有 Console browser authentication 保持不变"是结构上保证的,而不是靠 review 保证的。
不采用"把精确匹配改成前缀匹配"的方案:/api/mcp 与 /api/mcp/... 共前缀,一旦匹配条件写宽,运维 endpoint 的静态 API Key 校验就会被绕过,而这正是最不应该出现回归的地方。
8.3 tools/list
tools/list 返回当前 Published snapshot 中通过运行时过滤的 Tools。MVP 不设置 Tool 数量硬限制,也不分页。
逐 Tool 过滤
Admin 对每个 binding 独立判定,判定结果只影响该 Tool:
| 条件 | 该 Tool 是否出现在 tools/list |
|---|---|
definition 缺失、存在冲突,或 inputFingerprint 不一致 |
否 |
LiveServiceIndex 为 INACTIVE,且持续时间超过宽限期 |
否 |
LiveServiceIndex 为 INACTIVE,但仍在宽限期内 |
是 |
LiveServiceIndex 为 UNKNOWN |
是 |
outputFingerprint 不一致 |
是(仅在管理页告警) |
任何单个 binding 的问题都不会导致整个请求失败。返回一份少了几个 Tool 的列表,优于返回一个错误——后者会让 Agent 的能力集合整体归零,包括那些完全健康的 Tool。
UNKNOWN 必须保持显示
这是本节的安全底线。INACTIVE 表示"已经同步完成,确认没有实例";UNKNOWN 表示"不知道"。只有前者可以隐藏。
如果 UNKNOWN 也隐藏,那么一次注册中心不可用、或者 Admin 刚重启尚未完成初始同步,都会让所有 Agent 的全部能力瞬间消失。故障范围会从"Admin 看不到状态"放大成"所有依赖 MCP 的自动化全部停摆"。
宽限期
只有一到两个实例的应用做滚动重启时,中间存在一段实例数为零的窗口。此时 discovery Resources 已完成同步,状态是确定的 INACTIVE,不是 UNKNOWN。
如果立即隐藏,每次例行发版都会让 Agent 的工具短暂消失又出现。正在执行多步任务的 Agent 会认为该能力不存在,转而走完全不同的路径。
因此 INACTIVE 必须连续持续超过宽限期才触发隐藏,默认 60 秒,可配置。索引需要记录每个 identity 进入 INACTIVE 的时刻;状态回到 ACTIVE 时该计时清零。
返回内容
每个 Tool 包含:
- 用户定义的 Tool name;
- 用户定义的 Tool description;
- 生成的
inputSchema; - 用户实际配置时才返回的
readOnlyHint、destructiveHint和idempotentHint。
MCP Server description 通过 initialize 响应中的 instructions 字段暴露给 Client。
8.4 tools/call
一次 tools/call 按以下顺序执行:
- 校验机器凭证。
- 读取当前 Published snapshot。
- 按 Tool name 查找 binding。
- 重新计算并比较
inputFingerprint。 - 使用保存的 inputSchema 递归校验 arguments。
- 递归解码标量并恢复有序 Dubbo 参数(§7.4)。
- 计算调用 context 和可选 timeout。
- 按 binding 记录的协议执行一次
GenericService.Invoke。 - 只为 JSON 编码需要,把返回的 Go value 转换为等价的 Dubbo 业务结果。
- 返回 MCP result 并记录 audit event。
整个链路不会重试 RPC。
第 4 步失败时返回契约已变更的错误。存活状态不在调用路径上判定:即使某个 Tool 因 INACTIVE 已经从 tools/list 隐藏,针对它的 tools/call 仍然照常进入上述流程,最终由 Dubbo directory 给出结果——没有可用 Provider 时返回 not-executed。
这样处理的原因是无状态模式下服务端无法推送列表变更,Agent 手中的列表必然可能过期。返回"该能力暂时不可用"比返回"该能力不存在"更有用:前者 Agent 会稍后重试或如实告知用户,后者可能让它永久放弃这条路径。同时,最终可用性本来就以本次调用使用的 Dubbo directory 为准,而不是以索引的缓存状态为准。
8.5 使用正常 Dubbo 路由与负载均衡
MCP runtime 是一个正常 Dubbo Consumer,固定 application name 为:
dubbo-admin-mcp
由于一个 MCP Server 绑定单个 discovery(§5.2),长生命周期 generic client manager 为每个用到的 discovery 创建一个 dubbo-go client。Generic service reference 按以下 key 复用:
discoveryId + serviceName + group + version + protocol
protocol 必须进入复用 key:同一个服务的 dubbo reference 和 tri reference 是两个不同的对象,不能互相复用。
Reference 使用:
client.WithRegistry(...)
client.WithProtocolDubbo() // 或 client.WithProtocol(constant.TriProtocol),按 binding 记录的协议选择
client.WithGroup(group)
client.WithVersion(version)
client.WithClusterFailFast()
client.WithRetries(0)以上均为 ReferenceOption。协议按 binding 持久化的取值动态选择,不是写死的。
创建 client 时,从 mesh 对应的统一 discovery 配置取得 registry、config center 和 metadata report URL。这些 URL 已携带具体协议(例如 nacos://、zookeeper://);generic client manager 把它们交给 dubbo-go 的标准 registry/config APIs,不在 MCP 层按 discovery type 分支或重新解释原生地址。应用级服务发现模式下仍需完整配置 metadata report/config center,只配置 registry address 不足以建立完整链路。
Reference 不使用 client.WithURL,也不显式配置 load balancer。
这是有意保留的边界。当前固定 dubbo-go 版本默认 cluster 是 failover,默认 retries 是 2,因此必须显式配置 fail-fast 和 zero retries。默认 load balancer 已经是 random,而且 Dubbo governance 可以改变最终生效的路由策略,因此 Admin 不应重新声明 load balancer 并覆盖正常 Dubbo 策略。
序列化不需要配置。client.NewGenericService 内部对两种协议都强制 WithIDL(NONIDL)、WithGeneric() 和 WithSerialization(Hessian2Serialization)。因此 §4.3 提到的现有调试链路"尝试多个序列化目标"的做法在业务 MCP 上不存在。
所选 discovery 对应的 Dubbo registry 提供 Provider directory。dubbo-go 继续执行自己的 directory、router、governance 和最终生效的 load balancer,选择一个 Provider 并调用一次。Admin 不计算调用顺序,也不实现本地 round-robin。
调用时不根据 LiveServiceIndex 中缓存的地址直连 Provider。即使索引刚刚显示 ACTIVE,实例也可能在 RPC 前下线;反过来,索引显示 INACTIVE 后也可能马上有新实例注册。最终可用性以本次调用使用的 Dubbo directory 为准,没有可用 Provider 时返回 not-executed Tool error。
fail-fast cluster 的源码流程是列出有效 invokers,获取最终生效的 load balancer,通过 DoSelect 选出一个 invoker,然后调用该 invoker 一次。这就是生产 MCP 调用要求的行为。
这一保证与协议无关。failfastClusterInvoker.Invoke 只依赖 Directory.List、GetLoadBalance 和 DoSelect,不涉及任何协议特定分支,cluster 层位于 protocol 层之上。因此 dubbo 和 tri 两条路径的"最多一次 RPC"语义完全一致。
Client 和 reference 必须是长生命周期对象。每次 tools/call 都创建新的 registry client 会反复创建订阅和连接。其生命周期跟随 Console component,并接入 dubbo-go graceful shutdown 流程。
8.6 Tool 可选超时
Timeout 是每个 Tool 的可选配置:
- 开关关闭:Admin 不增加 timeout override,使用最终生效的 Dubbo Consumer、method、governance 和 library 默认配置。在当前固定版本的 programmatic client 中,如果没有其他覆盖,Consumer 默认值是 3 秒。
- 开关开启:用户必须填写正数 duration。Admin 写入内部 Dubbo timeout attachment,并创建 call context deadline。如果 MCP Client request context 的 deadline 更短,以更短的 deadline 为准。
Agent 不能覆盖 timeout,也不能传入任意 Dubbo attachments。
两种协议的 timeout 传递不同
dubbo:invoker 读取 timeout attachment,并把它回写为 attachment 发送给 Provider,因此 Java Provider 端也能感知本次调用的超时预算。tri:invoker 把 timeout 放进 context value 传给底层 triple 实现,该处在 dubbo-go 源码中标注为临时方案(Todo(finalt) Temporarily solve the problem that the timeout time is not valid)。
因此在 tri 协议上,call context deadline 是主要保障而不是兜底。实现必须无条件创建 context deadline,不能依赖 attachment 生效。
RPC dispatch 后发生 timeout 或连接断开时,Provider 可能已经执行了操作,只是 Admin 没有收到结果。Admin 返回 unknown-outcome,并且不重试。
8.7 绑定的 discovery 缺失时的行为
一个 MCP Server 绑定单个 discovery(§5.2)。如果该 discovery 从 Admin 配置中被移除,metadata watcher 会停止,ServiceProviderMetadataResource 随之消失,LiveServiceIndex 也取不到任何数据。
这种情况必须单独定义,否则 §8.3 的两条规则会给出互相矛盾的结果:存活状态变成 UNKNOWN,按规则应当保持显示;而 definition 缺失,按 §6.4 应当逐 Tool fail closed。最终表现将取决于实现的判定顺序,而不是设计决定的行为。
MVP 的处理分两层:
前置防护。 删除 discovery 前校验它是否仍被任何 MCP Server 引用。存在引用时拒绝删除,并在错误中列出引用它的 Server。这是正常路径上唯一应该发生的结果。
运行时兜底。 配置仍可能绕过 Console 被直接修改,因此 endpoint 必须有确定行为:Server 绑定的 discovery 不存在时,/mcp/{serverId} 的所有请求——包括 tools/list——一律返回错误,而不是返回一份正在缩水的 Tool 列表。管理页把该 Server 标记为配置错误。
这里与 §8.3 的逐 Tool 降级采取不同策略是有意的。逐 Tool 降级针对的是单个 Provider 的运行时波动,此时 Server 本身仍然是健康且配置正确的。而 discovery 缺失是配置错误,影响该 Server 的全部 binding。返回一份逐渐变空的列表会让 Agent 误以为这些能力被有意收回;整体报错才能让问题立刻暴露给运维。
9. 返回值与错误
9.1 成功返回
Admin 保留 Dubbo 泛化调用产生的业务结果。MVP 没有用户自定义的 output schema、alias、wrapper、字段删除或字段重命名。
协议边界仍然需要必要的技术转换。Hessian map、list、struct 和 scalar 必须先变成 JSON 兼容的 Go value,MCP SDK 才能编码。这个过程不能改变业务结构。
如果结果是 JSON object,Admin 同时返回:
- 保存该 object 的
structuredContent; - 保存同一份 JSON 文本的 text content,兼容不读取 structured content 的 Client。
如果结果是 scalar、array 或 null,Admin 返回对应 JSON text。MCP structured content 要求根节点是 object。如果额外包装成 {"result": ...},会违反原始返回结构要求。
MVP 不声明 outputSchema。SDK 只在设置了 outputSchema 时才校验 structuredContent,因此不声明是可行的;但部分 Client 会因为没有 outputSchema 而忽略 structuredContent。同时返回 text content 的设计正是为了覆盖这种 Client,实现时应对目标 Client 实测确认。
long 的表示是不对称的
§7.2 把入参中的 long 映射为十进制 string,用于防止 JSON Client 丢失精度(Java long 上界约 9.2e18,而 JavaScript 的安全整数上界约 9e15,19 位的 ID 必然溢出)。
返回方向没有对应处理。Java 返回的 long 会以 JSON number 编码输出,同一个精度问题原样存在。
保留这个不对称是有意的取舍。两个方向的后果不对等:入参丢精度会写错数据(例如给错误的租户创建订单),返回值丢精度只影响读取和展示。修复返回方向需要按返回类型闭包遍历整个结果树并改写标量,这与本节不做输出转换、§3.2 不做 output schema 的边界直接冲突。
由此产生一个 MVP 无法修复的往返缺口:Agent 从某个 Tool 拿到一个 19 位 ID,再把它作为参数传给下一个 Tool 时,精度在第一步就已经丢失,第二步传出去的是错误的值。该缺口记录在 §16。
9.2 错误返回
鉴权失败在 MCP 处理前返回 HTTP error。格式错误的 MCP request 继续返回 protocol error。参数校验、契约、Provider 可用性、timeout 和调用失败作为 isError: true 的 Tool error 返回。
MVP 返回 Admin 能取得的全部错误信息。可取得的内容按协议不同:
| 字段 | dubbo |
tri |
|---|---|---|
| exception type / message | ✅ | ✅(status code + message) |
| cause chain | ✅ | ❌ |
| stack trace | ✅ | ❌ |
| Dubbo error code | ✅ | 部分(视 Provider 是否在 trailer 中携带) |
| selected Provider address | 能取得时返回 | 能取得时返回 |
| outcome classification | ✅ | ✅ |
dubbo 协议通过 hessian 反序列化 Java 异常对象,因此能拿到完整的 cause chain 和 stack trace。tri 协议回传的是 connect/gRPC 风格的 status 加 message,结构上不携带 Java 异常的这些细节。
不为了对齐两种协议而砍掉 dubbo 侧的信息。本节已经明确选择返回全部可得信息并接受泄露风险,主动降级到两者的交集只会在不减少风险的前提下削弱可诊断性。协议差异在文档中写明即可。
三种 outcome 分类在两种协议上都可判定,这一层是统一的。
Outcome 分为:
| Outcome | 含义 |
|---|---|
not-executed |
RPC dispatch 前已经拒绝,例如参数非法、标量解码失败、契约缺失或没有可用 Provider |
failed |
收到了确定的 Provider 或 Dubbo error |
unknown-outcome |
可能已经 dispatch,但 timeout、cancel 或连接中断导致结果不确定 |
原始异常可能泄露内部类名、地址和调用栈。MVP 接受这个风险,并在 release notes 中明确说明。
10. 存储一致性与多副本
10.1 Resource 注册
ResourceStore 会为初始化前已经注册的 Resource schema 创建 store。因此,MCPServerResource 和 MCPCredentialResource 必须在 package initialization 阶段注册 schema 和查询所需的 indexes。
建议至少提供:
- MCP Server 的 mesh + name index;
- Credential 的 server ID index;
- Credential ID index,业务 endpoint 需要按 token 中的 credential ID 直接定位凭证。
10.2 CAS 能力
当前 ResourceStore 没有 expected-version atomic update。Gorm store 会在 update transaction 之前读取旧 row,随后执行 unconditional update,因此 handler 先 read 再 update 不能保证多副本安全。
ResourceModel 当前也没有独立的版本列,只有 JSON data、created_at 和 updated_at。实现 CAS 时只为 MCPServerResource 和 MCPCredentialResource 两张表增加 version 列,并把它作为这两类资源 ObjectMeta.resourceVersion 的存储来源。新资源从版本 1 开始;每次 Add、Update、CAS 或 Delete 都必须在同一个 mutation lock 或 database transaction 中处理版本,不能只修改 JSON 内的字符串。
不给所有资源表加版本列。CAS 只有 MCP 需要,而 RPCInstance 这类资源在每次注册中心推送时都会发生 Add / Update / Delete,为其增加版本簿记是没有收益的热路径开销。此外 ObjectMeta.resourceVersion 目前在整个仓库中没有任何读取方,一旦对所有资源生效,所有资源的 JSON payload 都会改变,迁移面显著扩大。
增加范围有限的可选 store capability:
type ConditionalResourceStore interface {
CompareAndSwap(obj model.Resource, expectedVersion string) error
CompareAndDelete(obj model.Resource, expectedVersion string) error
}MCP repository 对可变资源强制要求这个 capability,其余资源的 store 行为不发生任何变化。
- Memory store 在同一个 write lock 内比较版本并更新资源。
- Gorm store 在同一个 database transaction 中读取当前 row,比较
version,生成version + 1的资源 JSON,再通过包含旧version条件的 update 写入 data、indexes 和新版本。条件 update 影响 0 行时返回 conflict。 - 版本不一致时返回 typed conflict error,并映射为 HTTP 409。
ResourceStore 是权威数据源。进程内 compiled schema、MCP Server object 和 Dubbo reference 都只是派生缓存。
10.3 多副本前提
业务 MCP 要求使用数据库 store(MySQL 或 PostgreSQL)。
memory store 的数据完全在进程内。多副本部署下,在一个副本上完成的 credential revoke 对其他副本不可见,被撤销的凭证在其他副本上仍然可用;publish 的 CAS 也只在单个进程内互斥,无法阻止两个副本同时发布出互相覆盖的版本。前者是安全问题。
memory store 仅适用于单副本的开发和测试环境。启动时如果检测到 MCP 功能已启用而 store 为 memory 且副本数大于一,应记录明确的警告。
11. Console 管理 API
建议增加以下 Console endpoints:
| Method | Path | 用途 |
|---|---|---|
| GET | /api/v1/mcp/servers |
查询已配置的 MCP Servers |
| POST | /api/v1/mcp/servers |
创建包含 editable draft 的 Server |
| GET | /api/v1/mcp/servers/{serverId} |
查询 Draft、Published snapshot、revision、warnings 和不含 secret 的 Credentials |
| PUT | /api/v1/mcp/servers/{serverId}/draft |
使用期望 resourceVersion 替换 Draft |
| POST | /api/v1/mcp/servers/{serverId}/validate |
校验当前 Draft,不发布 |
| POST | /api/v1/mcp/servers/{serverId}/publish |
校验并原子发布 Draft |
| DELETE | /api/v1/mcp/servers/{serverId} |
使用期望 resourceVersion 删除 Server,并级联删除其全部 Credential |
| POST | /api/v1/mcp/servers/{serverId}/credentials |
创建命名 Credential,并只返回一次明文 Token |
| GET | /api/v1/mcp/servers/{serverId}/credentials |
查询 Credential metadata |
| DELETE | /api/v1/mcp/servers/{serverId}/credentials/{credentialId} |
revoke Credential |
方法选择尽量复用现有 Service 和 Method detail APIs。如果现有 response model 无法为 Draft editor 提供完整的精确签名,可以增加一个返回规范化 operation candidate 的小型 endpoint。
基础管理 UI 应提供 Server description、discovery 选择、OAuth required scopes、精确方法选择(含 LiveServiceIndex 状态显示和调用协议选择)、Tool name 和 description、顶层参数 alias 和 description、可选 annotations、timeout switch、validation result(errors 与 warnings 分开展示)、publish action,以及 API Key Credential 创建和 revoke。OAuth issuer/audience 是部署级只读状态,不允许普通 Server 编辑者覆盖。已发布 Tool 列表需要显示当前是否因 INACTIVE 被隐藏,以及 outputFingerprint 是否已漂移。MVP 不提供嵌套 DTO editor。
12. 审计日志
每次业务 tools/call 写入一条 structured audit event,包含:
- timestamp 和 request ID;
- Server ID 和 Published revision;
- principal kind(credential / oauth)和 subject;Credential 模式额外记录 Credential ID/name,OAuth 模式记录 issuer 与 client ID(可取得时);
- Tool name;
- Dubbo Service identity 和精确方法签名;
- 本次调用使用的协议;
- 能够取得时的 selected Provider address;
- latency;
- outcome 和 status;
- error class。
Audit event 不记录 request arguments、result values、plaintext token 或 token hash。
12.1 控制面审计
除调用审计外,以下控制面操作同样必须写入 structured audit event:
| 事件 | 记录内容 |
|---|---|
| publish | 操作者、Server ID、新旧 revision、本次快照中的 Tool 数量与名称列表、发布时的 warnings |
| Credential 创建 | 操作者、Server ID、Credential ID 和 name、过期时间 |
| Credential revoke | 操作者、Server ID、Credential ID 和 name |
| Server 删除 | 操作者、Server ID、级联删除的 Credential ID 列表 |
本功能的核心价值是受控地把内部能力暴露给 Agent,因此"谁在什么时候开放了什么、给谁发了凭证、什么时候撤销"比"谁调用了什么"更接近安全审计的关注点。只审计 tools/call 会留下无法回溯的权限变更历史。
同样不记录 plaintext token 或 token hash。
MVP 通过现有 structured application logging path 写入这些字段。专用的可查询 audit store 留到后续版本。
13. 模块边界
实现应保持以下职责边界:
- 各 registry-specific discovery adapter 继续负责导入统一的
ServiceProviderMetadataResource、RPCInstanceResource和派生 Resources;MCP 不依赖原始注册中心对象。 LiveServiceIndex负责把注册中心运行状态归约为三态存活视图,只对外提供判定结果,不做实例选择。- Console service layer 负责 Draft validation、publish、Credential 和业务 MCP handler。
- ResourceStore 负责持久化配置和 optimistic concurrency。
- dubbo-go 负责 registry subscription、routing、load balancing、serialization 和 Provider selection。
- Provider generic filter 负责 POJO realization。
- Agent 或 MCP Client 负责人工确认策略。
不需要新增 runtime component type。pkg/mcp 虽然在 pkg/core/bootstrap/init.go 中被 blank import 从而注册了自己的 component,但 bootstrap.go 只按固定的具名列表启动 EventBus、ResourceStore、ResourceDiscovery、ResourceEngine、ResourceManager、Console 和 RuleGovernor,MCP component 从不会被启动——现有 /api/mcp 路由实际由 Console component 注册。业务 MCP managers 同样应随 Console component 创建和停止。
以下现有行为必须保持不变:
/api/mcp运维 Tools;/api/v1/service/generic/invoke调试调用语义;- 现有 Console browser authentication;
- 现有 discovery 和 governance resources。
14. 测试与验收
14.1 单元测试
Metadata 和 Schema 测试至少覆盖:
- 按精确有序参数类型解析重载方法;
- Java 参数名缺失时生成默认
argN; - alias 到 Position 的参数恢复;
- primitive、enum、array、collection、map、POJO 和 recursive reference;
- 依据
Type.items元数区分 collection 与 map; - 含泛型参数的类型名生成合法且稳定的
$defskey,且不与其他类型碰撞; $defskey 的转义按白名单实现:Java 泛型名的<>,与 Go import path 的/都被同一条规则覆盖,不存在只处理其中一类的实现;- 同一接口的 Java 与 Go definition 因 property 名不同(
id/iD)被判定为契约冲突,错误信息点明命名差异; - 拒绝 unresolved type 和 open type;
- 拒绝顶层和嵌套 POJO unknown fields;
- 允许
Map<String, T>dynamic keys,并校验 value; - 顶层参数 required 和嵌套字段 optional;
- 嵌套在 POJO、List 和 Map value 中的
long被正确解码为 int64,溢出被拒绝; - 嵌套的
byte/short/int范围检查和char长度检查在 dispatch 前拒绝非法值; inputFingerprint和outputFingerprint都不受 map iteration order 影响;- 仅返回类型变化时
inputFingerprint不变、outputFingerprint变化; - 入参类型变化时
inputFingerprint变化; - metadata mismatch 和 conflicting definition 使该 Tool fail closed,其余 Tool 不受影响。
Storage 和 Credential 测试至少覆盖:
- Draft 修改不影响 active snapshot;
- validation 失败时保留旧 Published snapshot;
- 一次 atomic publish 更新完整 revision;
- memory 和 Gorm store 都会拒绝过期
resourceVersion; - 非 MCP 资源的 store 行为与增加 CAS 之前完全一致;
- 一个 Server 存在多个 Credentials;
- plaintext token 只返回一次,hash verification、expiration 和 revoke 正常;
- Credential 不能跨 Server 使用;
- 删除 Server 会级联删除其全部 Credential。
Authentication 测试至少覆盖:
- 同一个 business MCP endpoint 分别接受有效 API Key 和有效 OAuth access token;
mcp_token 只进入 Credential verifier,其他 token 只进入 OAuth verifier,不允许失败后回退到另一种 verifier;- API Key 缺少 expiration、已过期、已 revoke、secret 错误或 serverId 不匹配时返回 401/403;
- OAuth JWT 的 issuer、audience、signature、expiration/not-before 任一无效时返回 401;JWKS key rotation 后能按 verifier 的标准刷新机制恢复;
- OAuth token 缺少 Server required scopes 时返回 403;
- RFC 9728 resource metadata 的 resource、authorization server 和 scopes 与 Server endpoint 一致,401/403 challenge 指向正确 metadata URL;
- 两种认证都产生统一
MCPPrincipal并进入审计字段,日志不记录原始 token; - OAuth access token 不会作为 Dubbo attachment 传给 Provider;
- 未配置 OAuth issuer 时 API Key 正常工作,OAuth token 明确失败且 metadata endpoint 不宣称 OAuth 可用。
14.2 Runtime 测试
Runtime 测试必须证明:
- 使用等价的 Nacos/ZooKeeper fixture 生成统一 Resources 后,catalog、contract resolver、LiveServiceIndex 和 binding 结果一致;
- MCP package 不 import registry-specific discovery package 或 Nacos/ZooKeeper SDK;
- 每个请求读取当前 Published revision;
- list 和 call 之间发生 publish 时,必要情况下返回 relist error;
- 单个 binding 的
inputFingerprint漂移只让该 Tool 从tools/list消失,同 Server 其余 Tool 正常返回; outputFingerprint漂移不影响tools/list和tools/call,只产生管理页告警;LiveServiceIndex能区分ACTIVE、INACTIVE和UNKNOWN;- 状态为
UNKNOWN时(初始同步未完成或 discovery 故障)Tool 保持在tools/list中; - 状态为
INACTIVE但未超过宽限期时 Tool 保持在tools/list中; - 状态为
INACTIVE且超过宽限期后 Tool 从tools/list消失,恢复ACTIVE后重新出现且计时被重置; - 接口级注册模式下的 binding 能被正确判定为
ACTIVE; - discovery adapter 判定为不可用的原生实例不会生成“可用”的公共 instance Resource,也不会让接口判定为
ACTIVE;MCP 测试不重复断言某个注册中心的私有健康字段; - 已被隐藏的 Tool 若仍被
tools/call调用,返回not-executed而非 tool-not-found; - Server 绑定的 discovery 不存在时,
tools/list和tools/call整体返回错误,而不是返回一份逐渐变空的列表; - 删除仍被 MCP Server 引用的 discovery 会被拒绝,错误中列出引用者;
- reference 没有配置 Provider direct URL;
- Dubbo directory 和 router 的结果进入最终生效的 load balancer;
- fail-fast 加
retries=0只产生一次 RPC attempt,dubbo和tri各验证一次; - Admin 没有本地 round-robin 或 fallback;
- timeout 和不确定的 transport failure 返回
unknown-outcome,并且不重试,dubbo和tri各验证一次; tri上仅依靠 context deadline 也能正确超时;- object、array、scalar 和 null 保持原始业务结构;
dubbo的 error response 包含 cause chain 和 stack trace,tri的 error response 包含 status code 和 message;- 三种 outcome 分类在两种协议上都能正确判定;
- audit logs 不包含 arguments、results 和 secrets;
- publish、credential 创建与 revoke 都产生控制面 audit event。
14.3 端到端验收
端到端环境包含:
- 一套 Nacos discovery 和一套 ZooKeeper discovery,分别能产出等价的统一 metadata/instance Resources;
- 一个 Apache Dubbo Java Provider,暴露重载方法和嵌套 POJO,并同时导出
dubbo和tri两种协议; - 一个只导出
tri的 Java Provider,用于验证协议选择; - 使用当前固定 dubbo-go Consumer、并配置数据库 store 的 Dubbo Admin;
- 分别使用已保存 API Key 和外部 Authorization Server access token 的 MCP Client。
验收流程如下:
- 分别通过 Nacos 与 ZooKeeper discovery 在 Admin 中发现等价的 Java service definition,后续 catalog/publish/call 使用同一套 MCP 代码路径。
- 等待
LiveServiceIndex把该 Operation 标记为ACTIVE,确认 UI 上能区分三种状态。 - 创建 Draft,选择精确 Operation,填写语义描述和 aliases,确认协议默认选中
dubbo且可切换到tri。 - Validate 并 Publish。
- 创建两个 API Key Credentials,分别调用,revoke 其中一个,验证只有被 revoke 的 Credential 失败。
- 使用 Authorization Code + PKCE 或 Client Credentials 从外部 Authorization Server 取得 access token,验证同一 endpoint 能调用;移除 required scope 后验证返回 403。
- 验证
tools/list返回 Published schema。 - 验证合法参数通过正常 Dubbo routing 调用一个 Provider,
dubbo和tri各验证一次。 - 下线全部 Provider:验证宽限期内 Tool 仍在
tools/list;超过宽限期后 Tool 从tools/list消失;此时仍然发起tools/call,验证返回not-executed。重新上线后验证 Tool 回到列表。 - 模拟 discovery 不可用,验证状态为
UNKNOWN,已发布 Tool 仍在tools/list,发布新 binding 时给出 warning 但不阻塞。 - 把应用升级为不再导出目标接口的版本,但保留其他接口以使实例继续在线,同时让底层 metadata center 保留该接口的旧 definition。验证 Tool 不会被隐藏(MVP 已知缺口,见 §16),调用返回
not-executed。 - 验证未知嵌套字段在 RPC dispatch 前被拒绝。
- 验证嵌套在 POJO 与 List 中的 19 位
long能以 string 正确传入并被 Provider 收到完整精度。 - 修改 Provider 的入参 DTO,验证该 Tool 从
tools/list消失、同 Server 其余 Tool 正常,直到重新 Publish。 - 修改 Provider 的返回值 DTO,验证
tools/list和tools/call均不受影响,仅管理页出现告警。 - 尝试为只导出
tri的服务选择dubbo协议,验证发布被拒绝并给出明确原因。 - 制造响应 timeout,验证没有第二个 Provider 收到调用,
dubbo和tri各验证一次。 - 启动两个 Admin 副本共用同一个数据库:在副本 A 上 revoke 一个 Credential,验证该凭证在副本 B 上立即失效;两个副本用同一个过期
resourceVersion并发 publish,验证只有一个成功。
15. 建议实施顺序
- 增加 MCP Resource schema、indexes、CAS storage(仅 MCP 两张表)和 repository tests。
- 抽取精确 Operation resolver,并实现
inputFingerprint与outputFingerprint的规范化计算。 - 实现 metadata-to-schema 转换和 recursive validation,含
$defskey 生成。 - 实现递归标量解码(
longstring→int64、整数范围检查、char长度检查,覆盖 POJO / collection / map value 的任意嵌套)。这一步的工作量明显大于其名称暗示的规模,应独立排期,不要并入第 3 步。 - 增加 Resource 驱动的
LiveServiceIndex(MVP 便宜版):索引本身只订阅公共 resource-change events;若某个 registry 的接口级注册或健康信息尚未归一,则在对应 discovery adapter/subscriber 补齐公共 Resource。不含实例 revision resolver。此步与第 1–4 步无依赖,可并行。 - 增加 Draft、Validate、Publish 和 Credential Console APIs。
- 增加官方 MCP SDK handler、独立 RouterGroup、统一 Bearer middleware、API Key verifier、OAuth verifier 和 RFC 9728 metadata endpoint。
- 增加基于 registry 的 generic client manager,按 binding 协议动态选择
dubbo或tri,显式配置 fail-fast 和 zero retries。 - 实现
tools/list的逐 Tool 过滤(fingerprint 漂移与INACTIVE隐藏)和两层缓存。 - 增加 result、error mapping(按协议分别处理)和 structured audit logs(含控制面事件)。
- 增加基础管理 UI。
- 执行 MCP conformance、Java Provider integration(两种协议)和 multi-replica storage tests。
这个边界充分使用 Dubbo 已经上报的数据,只增加缺失的语义配置,同时保留 Dubbo 原有的运行时职责。



mcp-server-proposal of
Fixes: #1491