Skip to content

mcp-server-proposal - #1541

Open
Oxidaner wants to merge 1 commit into
apache:aifrom
Oxidaner:mcp-server
Open

mcp-server-proposal#1541
Oxidaner wants to merge 1 commit into
apache:aifrom
Oxidaner:mcp-server

Conversation

@Oxidaner

Copy link
Copy Markdown

mcp-server-proposal of

Fixes: #1491

Signed-off-by: Oxidaner <18622412361@163.com>
@sonarqubecloud

Copy link
Copy Markdown

@robocanic

Copy link
Copy Markdown
Contributor

@Oxidaner 请把proposal放到issue下面。另外proposal似乎只考虑了nacos,但其实admin已经对nacos/zookeeper这类注册中心进行了屏蔽,已经有统一的resource抽象,可以在这个方面纠正code agent。spec其他地方没有大问题。

@robocanic

Copy link
Copy Markdown
Contributor

另外,mcp的认证可以再细化一下

@Oxidaner

Copy link
Copy Markdown
Author

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 提供结构契约,管理员提供业务语义和开放范围。

Provider 向配置的 discovery 上报 metadata
  -> registry-specific discovery adapter 生成统一 Resource
  -> Admin 从 ResourceStore 发现精确的方法签名和类型定义
  -> 用户选择方法并填写描述、参数别名
  -> Admin 校验草稿并原子发布
  -> MCP Client 获取已发布的 Tools
  -> Agent 根据 inputSchema 生成 JSON 参数
  -> Admin 校验参数并恢复 RPC 参数顺序
  -> dubbo-go 通过正常的注册中心链路执行一次泛化调用
  -> Admin 返回 Dubbo 的原始业务结果

一个 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 的 MethodDefinition 包含 nameparameterTypesreturnType。其中已经废弃的 parameters 字段是 TypeDefinition 列表,不是源码参数名列表。ServiceDefinitionBuilder 读取的是 Java 反射参数类型,没有调用 Parameter.getName()

因此,现有 Provider metadata 可以告诉 Admin 某个方法是:

createOrder(java.lang.String, com.example.OrderRequest)

它不能可靠地告诉 Agent 第一个参数表示 tenantId,也不能解释创建订单会产生什么业务效果。

本方案把调用契约分为两层:

层次 来源 职责
结构契约 Provider metadata Service 标识、方法名、有序参数类型、返回类型和关联类型定义
语义契约 Admin 用户配置 Server 描述、Tool 名称、Tool 描述、顶层参数别名和说明、调用协议,以及可选 Tool annotations

这和手工维护另一份完整 API 定义不同。用户不需要重写 DTO 结构、RPC 序列化、Provider 发现、路由、负载均衡或泛化对象还原规则。用户只负责选择开放范围,并补齐 metadata 本身不具备的业务语义。

2.3 适用场景

当 MCP Client 需要调用一组经过筛选的内部 Dubbo 能力,而又不适合为每个服务嵌入 API JAR 或生成专用 Consumer 时,可以使用本功能。例如,平台团队可以把现有服务中的少量操作开放给编码 Agent、客服 Agent 或内部自动化工具。

本功能不用于替代普通应用之间的强类型 Dubbo 调用。

3. 目标与非目标

3.1 MVP 目标

MVP 完成以下闭环:

  1. 从 Admin 统一 ServiceProviderMetadataResource 读取 Dubbo Provider service definition,不感知注册中心类型。
  2. 允许用户创建一个或多个 MCP Server,并选择精确的 Dubbo 方法签名作为 Tools。一个 MCP Server 绑定一个 discovery。
  3. 要求用户为 MCP Server 和每个 Tool 填写描述。
  4. 允许用户为每个顶层方法参数设置别名和说明。
  5. 根据 Provider metadata 和用户语义配置生成严格的 MCP inputSchema
  6. 支持草稿校验,并一次性原子发布完整的生效版本。
  7. 通过无状态 Streamable HTTP 暴露每个已发布的 MCP Server。
  8. 同时支持 Admin 管理的 API Key 机器凭证和标准 OAuth 2.1/OIDC access token。
  9. 通过正常 Dubbo 注册中心、路由和负载均衡链路调用 Provider,支持 dubbotri 两种协议的泛化调用。
  10. 保证一次 tools/call 最多发起一次 RPC。
  11. 不做用户自定义的输出转换,直接返回 Dubbo 的业务结果。
  12. 确认 Provider 已经下线时,把对应 Tool 从 tools/list 中隐藏。

3.2 MVP 非目标

MVP 不包含:

  • 自动暴露所有 Provider 方法;
  • 在 MCP 层实现人工确认或审批状态机;
  • 编排多个 Tool 的执行顺序;
  • Triple IDL 或流式 RPC;
  • 一个 MCP Server 跨多个 discovery 组合方法;
  • 通过实例 dubbo.metadata.revision 精确校验接口导出状态;
  • Admin 自己选择 Provider、实现本地轮询或通过直连地址故障转移;
  • RPC 重试、协议回退或实例回退;
  • 嵌套 DTO 字段别名、字段说明或自定义约束;
  • 输出 Schema、输出别名或业务结果映射;
  • 已发布版本历史或按 MCP Session 固定版本;
  • Tool 级机器凭证权限;
  • 限流、并发限制或 Tool 数量硬限制;
  • MCP tools/list 分页;
  • Provider metadata 变化后的自动契约迁移;
  • Provider 异常信息脱敏。

这些是明确的 MVP 边界,不是未定义行为。

4. 源码基线与现有能力

本方案基于以下源码版本核查:

项目 核查版本 与本方案直接相关的结论
dubbo-admin 2df3e07 Nacos/ZooKeeper discovery 已归一生成 Provider/instance Resources;Console 已按统一 Resource 解析重载方法并进行调试型泛化调用;ResourceStore 没有 CAS 接口;现有 MCP Server 是静态实现
Apache Dubbo Java d0bf5c36d0 FullServiceDefinition 包含方法和类型结构,但不包含源码参数名;泛化调用依赖方法名、参数类型名和有序参数值
dubbo-go 35ea886421f9 当前固定版本支持基于注册中心的 GenericService、fail-fast cluster 和显式 retries;默认 cluster 是 failover,默认 retries 是 2,默认 Consumer 请求超时是 3 秒,默认负载均衡是 random;dubbotri 两种协议都支持泛化调用;cluster 层与协议无关,fail-fastretries=0 的单次调用语义对两种协议一致;NewGenericService 对两种协议都强制 Hessian2Serialization;Triple 的 timeout 传递在 triple_invoker.go 中标注为临时方案
MCP Go SDK v1.4.0 这是兼容仓库 Go 1.24 基线并完整支持 MCP 2025-11-25 的官方 SDK 版本;低层动态 Tool handler 仍需要调用方自行校验参数

源码核查时,官方 MCP Go SDK v1.7.0 已要求 Go 1.25。Admin toolchain 升级和 MCP SDK 升级应作为独立依赖变更处理,本方案不隐式升级仓库的 Go 版本。

关键的外部源码入口包括 Dubbo Java 的 MethodDefinitionServiceDefinitionBuilderMetadataUtilsAbstractMetadataReport,以及当前固定 dubbo-go 版本的 client/options.gofailfast/cluster_invoker.go

4.1 Admin 已屏蔽注册中心差异

MCP 业务层不以 Nacos、ZooKeeper 或其原生数据模型作为输入。Admin 的 discovery factory、ListWatcher、Informer 和 subscriber 已经把不同注册中心的数据归一为 Resource:

Nacos / ZooKeeper / future registry
  -> registry-specific ListWatcher and subscriber
  -> ServiceProviderMetadataResource / ServiceResource
  -> RPCInstanceResource / InstanceResource
  -> ResourceStore + indexes + resource-change events
  -> MCP catalog / contract resolver / LiveServiceIndex

源码中的两条现有路径证明了该边界:

  • Nacos factory 直接把接口 definition 转换为 ServiceProviderMetadataResource
  • ZooKeeper factory 读取 /dubbo/metadataZKMetadataResource,再由 ZKMetadataEventSubscriber 转换为相同的 ServiceProviderMetadataResource
  • 两种 discovery 都把应用实例转换为 RPCInstanceResource,并经公共 subscriber 派生 InstanceResource
  • Console 的方法列表/detail 已经只按 ServiceProviderMetadataKind + mesh + serviceKey 查询,不感知原始注册中心。

MCP 实现必须遵守同一边界:

  1. Service catalog 和 contract resolver 只通过 ResourceManager/ResourceStore 查询 ServiceProviderMetadataResourceServiceResource 及其公共 indexes;
  2. 活跃状态只消费 RPCInstanceResource/InstanceResource 和统一 resource-change events;
  3. mesh 始终是 discovery ID,不携带 nacoszookeeper 等类型语义;
  4. MCP package 不允许 import Nacos/ZooKeeper SDK、解析 Config Service dataId、ZooKeeper node path 或原始实例 payload;
  5. 如果某个 discovery adapter 没有把健康、协议或接口级注册信息完整归一为公共 Resource,应在 discovery adapter/subscriber 层补齐,而不是在 MCP 中增加 registry-specific 分支。

因此 MCP 的支持条件只有一个:该 Admin discovery 能产出本方案依赖的统一 metadata/instance Resources。MCP 不维护 Provider SDK 或 Dubbo client 的注册中心兼容矩阵,也不据此缩小自身支持范围。

当前 MCP 支持矩阵直接跟随 Admin 已注册的 production discovery types:

Admin discovery.Type MCP 范围 判定依据
nacos2 支持 Admin 能归一生成所需 Resources
zookeeper 支持 Admin 能归一生成所需 Resources
mock 仅开发和测试 Admin 配置已明确标记为 dev/test

这里不再单独限定 Nacos 2.x 或 ZooKeeper 3.x 的服务端小版本。具体服务端版本兼容性属于各 discovery adapter 的既有支持范围;只要 adapter 成功产出相同 Resource contract,MCP 行为就应一致。未来新增 production discovery type 时,MCP 不增加类型分支,只增加一组公共 Resource contract tests。

活跃服务索引

Service definition 是持久化契约,Provider 下线后仍可能存在,不能用它判断实例是否存活。LiveServiceIndex 从统一 Resource 和 discovery 同步状态计算活跃服务视图,内部索引项为:

mesh + providerApplication + serviceName + group + version + protocol

MVP 的公共判定规则仍是:声明导出该接口的 Provider 应用中,是否存在至少一个可用 RPCInstanceResource/InstanceResource,且公共 endpoint 数据包含 binding 所需协议。

每个 Service identity 加协议保留三态:ACTIVEINACTIVEUNKNOWNUNKNOWN 表示初始同步尚未完成或 discovery 不可用,不能折叠为确认无实例的 INACTIVE

MVP 不做实例级 revision 与接口 definition 的精确关联。因此应用仍在线但新版本已移除某接口时,旧 definition 可能让状态暂时保持 ACTIVE;最终调用由 dubbo-go directory 判定无 Provider并返回 not-executed。后续若补 revision resolver,也应扩展统一 Resource/Resolver contract,不能让 MCP 直接读取某种注册中心。

LiveServiceIndex 只用于发布 warning 和 tools/list 过滤,不参与 Provider 选择;正式调用仍由 dubbo-go directory、router 和 load balancer 选择 Provider。

4.2 现有方法解析能力

pkg/console/service/service.go 已经可以按 mesh 和 service key 查询 Provider metadata,按方法名和签名去重,并通过精确签名定位重载方法。MCP resolver 应抽取并复用这些规则。

当前合并类型定义的 helper 不能直接用于证明所有 Provider definition 一致,因为它遇到同名类型时会保留第一份定义。MCP 发布前必须分别规范化每份匹配的 Provider definition,再比较规范化结果。

4.3 现有泛化调用能力

pkg/console/service/service_generic_invoke.go 已经通过 dubbo-go GenericService 发起调用。现有 Console API 面向人工调试,调用者需要选择实例,Admin 还可能尝试多个协议或序列化目标。

业务 MCP 不能复用这段编排控制流。非幂等 RPC 可能已经执行成功,只是 Admin 在接收响应时发生了超时或连接错误。此时继续尝试另一个目标会造成重复业务效果。

可以复用或抽取精确方法查找、基础类型参数解码、结果转换为 JSON 兼容值等纯 helper。Provider 选择和重试控制必须为业务 MCP 单独实现。

4.4 现有 MCP endpoint

pkg/mcp/server.go 是面向静态运维 Tools 的 MCP Server。它维护内存 Tool map,并且只做浅层 required 校验。

它的 InputSchema 类型是扁平的,只有 TypeProperties map[string]PropertyDefRequired 三个字段,不支持 $defs$ref 或任何嵌套 schema 组合;其 Tool 的 property 名都是手写常量,从不从类型名派生。因此业务 MCP 的 schema builder(含 §7.3 的 $defs key 生成)没有任何可复用的现成实现,是全新代码。这也意味着不存在一个已按 Java 特有字符写死的历史 sanitizer 需要改造——白名单规则从第一版就应当直接写对。

现有 /api/mcp endpoint 及其鉴权行为保持不变。业务 MCP 使用独立 endpoint 和官方 Go SDK,避免把现有运维 MCP 改造成动态多租户运行时。

4.5 现有存储边界

现有 ResourceManager 只允许写入治理规则资源。MCP 配置不是治理规则,不应为了复用写接口而扩大 Governor 的资源边界。

本功能为 ResourceStore 注册 MCP 资源类型,并在 ResourceStore 之上增加职责单一的 MCP repository 或 manager。它不修改 Governor 的资源范围。

5. 领域模型

5.1 术语

术语 含义
Mesh Admin discovery 的 ID,即一套注册中心加配置中心
Service identity mesh + serviceName + group + version
Operation identity Service identity 加 methodName + ordered parameterTypes
MCP Server 用户定义的一组 Operation 和 Server 描述,绑定单个 mesh
MCP Tool 面向 Agent 的名称和描述,绑定一个 Operation identity 和一个调用协议
Draft 尚未对 MCP Client 生效的可编辑配置
Published revision 当前唯一对 MCP Client 生效的完整配置
Machine credential Admin 签发的 API Key,授权访问一个 MCP Server 的所有 Tools
OAuth principal 外部 Authorization Server 签发 access token 所代表的用户或客户端身份

providerconsumer 仍然是 Dubbo 运行时角色。MCP Server、MCP Tool、Draft 和 Credential 是 Admin 配置概念,不是新的 Dubbo 身份。

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"`
}

RPCType 不在 MCPParameterBinding 中重复保存。它的唯一来源是同一个 Tool contract snapshot 的 ParameterTypes[Position]。创建 binding 时,Admin 对 parameterTypes 的每个下标 i 生成 {Position: i, Alias: "arg" + i};用户只能修改 Alias 和 Description,不能修改、删除或重新排序 Position。这样不会出现 alias mapping 中的 RPC type 与 Operation identity 漂移。

Draft 和 Published snapshot 都保存完整的 []MCPParameterBinding。发布时必须重新从当前 metadata 解析精确 Operation,并验证:

  • mapping 数量等于 ParameterTypes 数量;
  • Position 恰好覆盖 0..N-1,没有重复、空洞或越界;
  • Alias 非空且在 Tool 内唯一;
  • 当前 metadata 的有序 ParameterTypes 与 binding 的 Operation identity 一致。

选择另一个 Operation identity,或者原 Operation 的有序参数类型发生变化时,Admin 必须重新生成 parameter bindings,不能按 alias、类型或“相同下标”自动迁移旧配置。多个参数可以具有相同类型,按类型匹配无法判断业务语义。

存在一个 metadata 无法解决的硬边界:如果 Provider 保持完全相同的有序参数类型,却改变了两个位置的业务含义,例如 transfer(String, String)(fromAccount, toAccount) 改成 (toAccount, fromAccount),method signature 和 fingerprint 都不会变化,Admin 无法自动发现。MVP 必须把“同一 method + signature 的每个 Position 业务语义不可改变”作为 Provider 兼容性规则;需要改变时发布新 method、group 或 version,并重新配置 aliases。

protocol 取值为 dubbotri,在创建 binding 时确定并持久化。Admin 默认自动选择:两种协议都可用时选 dubbo,只有一种时选那一种;用户可以覆盖。

协议必须存进 binding,不能在调用时动态跟随 Provider 当前广播的内容。同一个 Tool 的两次调用如果走了不同协议,其超时传递方式(§8.6)和错误信息形状(§9.2)都不一样,会让线上问题无法归因。持久化之后,审计日志(§12)也能记录本次调用实际使用的协议。

protocol 是 binding 的属性,不进入 §6.4 的契约 fingerprint——它是传输选择,不是结构契约。但它进入 §8.5 的 reference 复用 key 和 §4.1 的存活判断。

MCP Tool 名称由用户定义,在同一个 MCP Server 内必须唯一,不要求与 Dubbo 方法名相同。

一个 MCP Server 内的所有 binding 必须使用同一个 mesh。跨 discovery 组合方法不在 MVP 范围内。

5.3 MCPServerResource

本功能新增 MCPServerResource。它与现有资源一样是 mesh 作用域的——mesh 即该 Server 绑定的 discovery ID。这样可以直接复用现有的 ResourceKey 构造方式和 ByMeshIndex 过滤,不需要向资源模型引入"集群级"这个当前并不存在的概念(ResourceModel.Meshnot null,所有查询路径都以 mesh 为维度)。

建议的逻辑结构如下:

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 快照保存:

  • Tool 名称和描述;
  • 可选的 readOnlyHintdestructiveHintidempotentHint
  • 精确的 Operation identity,含调用协议;
  • 有序的顶层参数别名和说明;
  • 生成的 inputSchema
  • 规范化的 inputFingerprintoutputFingerprint(见 §6.4);
  • 可选的超时开关和超时值。

Server 级 requiredOAuthScopes 与 Tools 一起保存在 Draft/Published snapshot。Draft 和 Published snapshot 保存在同一个资源中,因此一次 publish 可以通过一次 CAS 替换完整生效版本。MVP 不保存已发布版本历史。

5.4 MCPCredentialResource

Credential 与 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:

mcp_<credential-id>.<random-secret>

Admin 只在创建时返回一次明文 Token。Admin 保存高熵随机 secret 的 SHA-256 hash,并使用常量时间比较。secret 至少 256 bit,来自密码学安全随机源。expiresAt 必填且必须晚于当前时间,每个 Credential 可以单独 revoke。固定 MCP Go SDK v1.4.0 的 Bearer middleware 默认拒绝没有 expiration 的 token,因此不再提供“永不过期”分支。

Credential 的权威数据源是 ResourceStore。因此业务 MCP 要求使用数据库 store:memory store 是进程内的,多副本部署下在一个副本上完成的 revoke 对其他副本不可见,被撤销的凭证仍然可用。这是安全约束,不是性能取舍。

Agent 不需要在对话中记住或重复 Token。MCP Client 负责保存凭证,并在每次 HTTP 请求中自动添加 Authorization header。

5.5 双认证模型

业务 MCP endpoint 同时接受两类 Bearer token:

类型 签发方 识别方式 主要身份
API Key Dubbo Admin mcp_ 前缀,随后按 credential ID 查询并校验 secret hash Credential ID
OAuth JWT access token 外部 Authorization Server / OIDC Provider 不带 mcp_ 前缀,交给 OIDC/JWKS token verifier token 的 subject/client ID

两类 token 使用同一个 HTTP 入口和同一个 Authorization: Bearer header,不为认证方式复制 MCP endpoint。API Key 使用 Admin 独占的 mcp_ token namespace;其他 token 必须按 JWT 格式进入 OIDC verifier,不在两个 verifier 之间回退。

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

issuer 为空表示当前部署没有启用 OAuth;API Key 仍可使用。配置了 issuer 时,Admin 必须通过标准 Authorization Server/OIDC discovery 获取公开元数据,不重复配置 discovery 已经提供的 authorization endpoint、token endpoint 或 JWKS URI。audience 必填并由 verifier 严格校验,不能只验证 JWT 签名和过期时间。

每个 MCP Server 保存 requiredOAuthScopes,例如 orders:readorders:write。OAuth JWT 必须同时满足 issuer、audience、signature、expiration/not-before 和该 Server 的全部 required scopes。API Key 已经通过 serverId 绑定到单个 Server,不要求外部 OAuth scopes,也不能跨 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
}

后续审计和授权只依赖 MCPPrincipal,业务 handler 不再判断原始 token 类型。人工确认仍由 Agent 层决定,不因为新增 OAuth 而移入 MCP Server。

6. 控制面

6.1 草稿编辑

创建或编辑 Server 时只修改 spec.draft。MCP Client 继续读取上一个 spec.published snapshot。

用户从 Admin 已发现的 Service catalog 中选择方法。Admin 保存精确签名,而不是只保存 interface 或方法名。

Service catalog 必须显示每个候选方法的 LiveServiceIndex 状态。ServiceProviderMetadataResource 对应的是持久化 definition,一个早已下线的服务,其定义仍可能保留在底层 metadata center。如果不显示存活状态,管理员面对的是一份混杂着大量僵尸接口的列表,无法判断哪些还可以选择。三种状态在 UI 上必须可区分,尤其不能把 UNKNOWN(discovery 故障)显示成 INACTIVE(Provider 下线)。

用户还需要为每个方法选择调用协议。Admin 根据当前导出情况给出默认值(两种都可用时默认 dubbo),用户可以改。只导出了一种协议时,另一种在 UI 上置灰并说明原因。

Admin 按顺序展示每个 RPC 参数类型,这些结构字段不可修改。由于 Java metadata 没有可靠的参数名,Admin 默认显示 arg0arg1 等名称。用户可以修改参数别名,并填写参数说明。

例如:

Position RPC type 默认名称 用户别名 说明
0 java.lang.String arg0 tenantId 订单所属租户
1 com.example.OrderRequest arg1 request 创建订单请求

Position 和 RPC type 不可修改。运行时按保存的 Position 把别名映射回有序参数。

这里的“不可修改”由后端保证:Console 请求只接受 {position, alias, description},展示用 RPC type 始终由 ParameterTypes[position] 派生。不能信任前端回传的 RPC type,也不能用 argN 字符串反向解析 Position。

MVP 不提供嵌套 DTO 字段编辑器。嵌套对象继续使用 metadata 中的属性名和类型。如需说明特殊字段含义,可以写在 Tool description 中。

6.2 草稿校验

草稿校验是只读操作,不发布配置,也不会自动发送业务 RPC。

至少校验以下内容:

  1. Server description 非空。
  2. Tool 名称符合 MCP 命名规则,并且在 Server 内唯一。
  3. 每个 Tool description 非空。
  4. 每个顶层参数别名非空,并且在 Tool 内唯一。
  5. 引用的 discovery 存在,并且能够产出 MCP 所需的统一 metadata/instance Resources。
  6. Provider metadata 中仍然存在精确的方法签名。
  7. 同一个 Service identity 的所有匹配 definition 可以解析为唯一一致的结构契约。
  8. 所有输入类型都可以转换为闭合的 JSON Schema。
  9. 生成的 Schema 可以成功编译。
  10. 超时开关启用时,超时值为正数。
  11. 所有 binding 的 mesh 相同,且等于 Server 自身的 mesh。
  12. 每个 binding 声明的协议(dubbotri)在 Provider metadata 与注册中心中存在对应导出。

以上均为 error,校验不通过不能发布。

以下为 warning,不阻塞发布:

  • binding 对应的 LiveServiceIndex 状态不是 ACTIVE

存活状态不作为发布门禁,原因有三。第一,运行时对这种情况已有干净的处理:§8.3 会把 INACTIVE 的 Tool 从 tools/list 隐藏,§9.2 对仍然发起的调用返回分类正确的 not-executed。第二,规则 6 已经拦掉了真正的误配置(方法名写错、签名不符、接口已删),存活校验在它之上多拦的只有"定义还在但此刻没人在跑"这一种。第三,初始同步尚未完成时状态就是 UNKNOWN,如果它是 error,Admin 每次重启后到全量同步完成之前将无法发布任何 binding。

Admin 在 warning 详情中必须保留 INACTIVEUNKNOWN 的区别,避免把 discovery 故障显示成 Provider 下线。

校验结果分别返回 errors 和 warnings,不修改当前生效版本。

6.3 原子发布

Publish 会基于当前 metadata 重新执行完整校验,生成不可变的 Published snapshot,递增 Server revision,并通过一次 CAS 替换 spec.published

如果校验或 CAS 失败,旧的 Published snapshot 继续生效。MCP Client 不会看到部分更新的 Tool 列表。

所有 Draft 更新和 Publish 请求都必须携带期望的 ResourceStore resourceVersion。版本落后的写入返回 conflict,调用者需要重新读取后再操作。

6.4 契约 fingerprint

Admin 为每个 binding 生成两个确定性的 fingerprint。

inputFingerprint,参与 fail closed:

  • Service name、group 和 version;
  • 方法名和有序参数类型;
  • 从入参可达的所有类型定义;
  • 上述闭包内的 collection item type、enum value 和 object property。

outputFingerprint,只用于告警:

  • 返回类型;
  • 从返回类型可达的所有类型定义。

Hash 前对 Map key 和 object property 排序。用户填写的别名、描述、annotations 和 timeout 不属于 Provider 结构契约,不进入任何 fingerprint。binding 的调用协议同样不进入——它是传输选择,不是结构契约。

Provider 存活状态也不进入 fingerprint。它变化频繁,不会改变已经发布的 Tool 契约。

为什么只有入参参与 fail closed

已发布的对外契约只有 inputSchema。MVP 不生成 output schema(§3.2),也不做输出转换(§9.1),Provider 返回什么就原样透传什么。

因此入参结构的变化会真实地让已发布契约失效:§7.3 对每个 POJO 设置了 additionalProperties: false,Provider 新增一个入参字段后,Agent 在结构上就无法再传这个字段。继续调用的结果要么是 Provider 业务校验失败,要么更糟——字段缺失被当成默认值静默处理。这种情况必须 fail closed。

返回值结构的变化则不会让任何已发布的东西失效。Provider 给响应 DTO 增加一个字段(Dubbo/Hessian 生态中最常见的向后兼容演进方式),多出来的字段会原样透传给 Client,不会破坏任何东西。如果把它也纳入 fail closed,等于把最常见的兼容变更配置成了最高级别的故障。

返回值 fingerprint 变化时,Admin 在管理页对该 Tool 显示"Provider 返回结构已变更,建议重新发布",不影响 tools/listtools/call

校验时机与失败范围

tools/listtools/call 时,Admin 重新读取当前 ServiceProviderMetadataResource 并计算 inputFingerprint。definition 缺失、存在冲突或 inputFingerprint 不一致时,该 Tool fail closed——它不出现在 tools/list 中,对它的 tools/call 返回错误。其余 Tool 不受影响。

这与 §8.3 对 INACTIVE 的处理是同一套降级模型:单个 binding 的问题只影响单个 Tool,不会让整个 Server 的能力集合归零。

Admin 不会静默更新已发布 Schema。Provider 发生入参 breaking change 时,应发布新的 group 或 version,再由管理员发布新的 binding。

6.5 Provider metadata 发布前置条件

Admin 可以发现 Published fingerprint 与当前 ServiceProviderMetadataResource 不一致,但无法发现新 Provider binary 正在使用仍然保持旧 fingerprint 的陈旧 metadata。

当前 Dubbo Java 源码没有把 metadata 发布成功作为 Provider 注册屏障:

  1. ServiceConfig.exportRemote 先导出并注册服务,再调用 MetadataUtils.publishServiceDefinition
  2. AbstractMetadataReport 默认异步上报。
  3. metadata 写入失败后会记录日志并进入后台重试,不会让已经导出的服务自动停止提供流量。

因此,生产使用需要额外的 Provider 发布约束:新版本的 service definition 必须成功写入并经过验证,然后新 Provider 实例才能进入流量。只配置 sync-report=true 仍然不够,因为源码调用顺序依旧是在 export 之后才发布 metadata。

这个约束可以通过 Provider 侧改造实现,也可以由部署系统在 metadata 验证通过前保持新实例不可用。它不属于 Admin 运行时,但它是强契约一致性的前置条件。

7. 从 metadata 生成 MCP inputSchema

7.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
}

方法的每一个顶层参数都进入 required。这里的 required 表示属性必须出现,nullability 由类型规则单独决定。

Binding 单独保存参数 Position,不能依赖 JSON object 的遍历顺序决定 RPC 参数顺序。

7.2 类型映射

MVP 把闭合的 Java metadata 类型映射为以下 JSON 表达:

Dubbo metadata type JSON 表达
booleanjava.lang.Boolean boolean
byteshortint 及其 wrapper 带对应范围的 integer
longjava.lang.Long 十进制 string,避免 JSON Client 丢失整数精度
floatdouble 及其 wrapper number
charjava.lang.Character 长度为 1 的 string
java.lang.String string
enum enum values 的 string
array 或参数化 collection 带 item schema 的 array
POJO 使用 metadata property names 的 object
Map<String, T> 使用 additionalProperties value schema 的 object

Admin proto 中的 Type 用同一个 items 字段表达 collection 和 map,两者靠元数区分:collection 的 items 只有一个元素(item type),map 的 items 有两个元素(key type 和 value type)。实现必须依据元数判别,不能只看类型名字符串。

long 映射为 string 的代价在于解码:参数在送入泛化调用之前必须转回 int64。这个转换是递归的,见 §7.4。

命名类型使用本地 $defs$ref。只有在本地引用图可以安全编译时才接受递归类型。

Java primitive 不接受 null。Provider metadata 没有 nullability annotation,因此引用类型可以接受 null。metadata 同样没有字段 required 信息,因此 POJO 内部字段默认都是可选项。Provider 侧业务校验仍然可以拒绝缺失或为 null 的字段。

以下契约在 MVP 中直接校验失败,不生成含义不确定的 Schema:

  • 没有 item definition 的 raw collection 或 raw map;
  • key 不是 string 的 Map;
  • 找不到定义的类型引用;
  • 同名但定义不同的重复类型;
  • Object、wildcard、未绑定 type variable 等开放类型;
  • 尚未约定稳定 JSON 表达的 Date、Time 和任意精度数字等特殊类型。

dubbo-go Provider 的发布侧类型映射

dubbo-go Provider 也可以接入这套设计,但必须以 Java 类型词汇发布 service definition,不能发布 Go 原生类型名。这是对发布方的规范要求,Admin 侧不做 Go 类型识别,也不按 release 前缀分派不同的映射器。

这条要求不是 Admin 的偏好,而是 dubbo-go 运行时自身的既定契约。filter/generic/service_filter.go 中,非变参方法的 realize 只依赖 Go 反射得到的 argsTypetypes 参数确实未被使用;但变参方法会走 realizeVariadicArg,由 types 的最后一个元素决定是否把打包的尾参展开成 slice,而该判断的实现是:

func shouldUnwrapPackedVariadicArg(variadicType string, variadicSliceType reflect.Type) bool {
	if slices.Contains(javaTypeNamesForType(variadicSliceType), variadicType) {
		return true
	}
	...
}

javaTypeNamesForType 的候选值来自 hessian.GetJavaName() 和 JVM array descriptor(形如 [Ljava.lang.String;)。因此发布 Go 词汇会让变参方法的尾参匹配失败,打包的 slice 被当成单个变参值处理——静默错误,不产生任何报错

发布侧映射规则:

Go Java 说明
bool boolean
int8 / int16 / int32 / int64 byte / short / int / long
uint8 short 0–255 无损
uint16 int 无损
uint32 long 无损
uint / uint64 —— 无无损落点,发布时拒绝
float32 / float64 float / double
string java.lang.String
[]T java.util.List<T> items 一个元素
map[K]V java.util.Map<K,V> items 两个元素,顺序为 key、value
*T T nullability 由本节的引用类型规则表达,Java 类型名同样无法表达指针

uintuint64 在发布时直接拒绝,与本节的拒绝清单一致。放开它们需要同时定义任意精度数字的 JSON 表达和解码规则,为一个在 DTO 中罕见的类型开这个口子不划算。

变参方法的 parameterTypes 尾项必须是 Java 类型名或 JVM array descriptor 之一,否则变参 realize 会出错。

采用 Java 词汇后,int64 自然落到 long,因此本节的十进制 string 规则自动覆盖 Go 侧 19 位 ID 的精度问题,无需额外处理。

字段名不做映射

property 名必须保持 Go 侧的 wire name(ID 字段的 wire name 是 iD,来自 LowerFirstRune),不能一并"Java 化"。

类型名和字段名的性质不同:类型名除变参尾项外是描述性的,而 property 名是 wire 级的,Generalizer 的 Realize 依赖它做字段匹配,改动会直接破坏调用。

由此产生的后果记录在 §16:同一个接口如果同时存在 Java 和 Go Provider,两者的 property 名不同(idiD),§6.2 规则 7 会判定结构契约不一致,binding 无法发布。

7.3 严格拒绝未知字段

未知字段必须被拒绝,不能先删除再调用:

  • 顶层 arguments object 设置 additionalProperties: false
  • $defs 中的每个 POJO object 设置 additionalProperties: false
  • Map<String, T> 允许动态 key,但每个 value 都必须符合 T

这条规则需要递归执行。只校验顶层 required 的 validator 不满足要求。

生成的 inputSchema 使用 JSON Schema Draft 2020-12。官方 SDK 的低层动态 Server.AddTool handler 不会校验 raw arguments。业务 MCP 应直接复用 MCP Go SDK v1.4.0 已经固定的 github.com/google/jsonschema-go v0.4.2,只编译 Admin 生成的内存 Schema,不允许加载外部网络 $ref

$defs key 的构造

不能直接用类型名作为 $defs key。$ref 的取值是一个 URI 引用,而类型名中普遍存在 URI fragment 不接受的字符:Java 参数化类型形如 java.util.List<com.example.Item>,带 <>,;Go 类型名含 import path,带 /。不同 validator 对这类字符的宽容度不一致,直接拼接会带来无法预期的编译或解析失败。

转换规则必须按白名单定义,而不是枚举某种语言特有的字符:把类型名中所有不属于 [A-Za-z0-9_.-] 的字符一律转义。按白名单实现时,Java 的 <>, 和 Go 的 / 会被同一条规则覆盖,不需要为任何语言写特例;反过来,如果实现成"替换 <>, 这三个字符"的黑名单,Go import path 里的 / 就会漏网。

Admin 在 binding 内保存类型名到 key 的映射。转换必须是确定性的且无碰撞——同一份 metadata 每次生成的 key 必须一致,否则 §6.4 的 fingerprint 会出现伪变化。

7.4 恢复有序参数

参数通过 Schema 校验后,Admin 按保存的 Position 组装泛化调用参数:

GenericService.Invoke(
    ctx,
    "createOrder",
    []string{
        "java.lang.String",
        "com.example.OrderRequest",
    },
    []hessian.Object{
        arguments["tenantId"],
        arguments["request"],
    },
)

具体恢复算法是:先创建长度为 len(ParameterTypes) 的 args slice,再遍历 ParameterBindings,读取 arguments[binding.Alias],按 ParameterTypes[binding.Position] 做递归边界解码,最后写入 args[binding.Position]。任何 alias 缺失、Position 非法或重复写入都在 dispatch 前失败。实现绝不能遍历 JSON object/map 的顺序来构造 args。

这与 dubbo-go 当前实现逐位置对齐:definition builder 按 MethodType.ArgsType() 顺序发布 parameterTypesgenericServiceFilter 又把收到的 args[i] 还原为 argsType[i]。alias 从不传给 Provider。

例如 MCP 请求为:

{
  "request": {"productId": "P100"},
  "tenantId": "tenant-a"
}

即使 JSON 属性顺序与 RPC 参数顺序相反,保存的 mapping 仍得到:

Position 0 / tenantId -> args[0] -> java.lang.String
Position 1 / request  -> args[1] -> com.example.OrderRequest

Admin 不负责实例化 Java POJO,真实类型还原由 Provider 的 generic filter 完成。但在交给 generic filter 之前,Admin 必须完成协议边界所需的标量解码。

标量解码必须递归

§7.2 的类型映射在 JSON 表达和 Java 标量之间引入了几处不等价,这些都必须在 dispatch 之前修正:

情况 必须做的处理
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 分派:

  1. mcp_ token 查询 Credential,校验 active、expiration、secret hash 和 path 中的 {serverId},成功后为 SDK TokenInfo 填充该 Server 的 required scopes;
  2. 其他 token 交给 OAuth verifier,通过 issuer 的 OIDC discovery 获取并缓存 JWKS,校验 JWT signature、issuer、audience、expiration/not-before,并提取 subject/client ID 与 scopes;
  3. 两者统一生成 MCPPrincipal;认证失败返回 401,OAuth token 缺少 Server required scopes 返回 403,不暴露 MCP Tool 信息。

业务 MCP 复用固定 MCP Go SDK v1.4.0auth.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.ScopesResourceMetadataURL 是静态 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 不一致
LiveServiceIndexINACTIVE,且持续时间超过宽限期
LiveServiceIndexINACTIVE,但仍在宽限期内
LiveServiceIndexUNKNOWN
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
  • 用户实际配置时才返回的 readOnlyHintdestructiveHintidempotentHint

MCP Server description 通过 initialize 响应中的 instructions 字段暴露给 Client。

8.4 tools/call

一次 tools/call 按以下顺序执行:

  1. 校验机器凭证。
  2. 读取当前 Published snapshot。
  3. 按 Tool name 查找 binding。
  4. 重新计算并比较 inputFingerprint
  5. 使用保存的 inputSchema 递归校验 arguments。
  6. 递归解码标量并恢复有序 Dubbo 参数(§7.4)。
  7. 计算调用 context 和可选 timeout。
  8. 按 binding 记录的协议执行一次 GenericService.Invoke
  9. 只为 JSON 编码需要,把返回的 Go value 转换为等价的 Dubbo 业务结果。
  10. 返回 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.ListGetLoadBalanceDoSelect,不涉及任何协议特定分支,cluster 层位于 protocol 层之上。因此 dubbotri 两条路径的"最多一次 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。因此,MCPServerResourceMCPCredentialResource 必须在 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 datacreated_atupdated_at。实现 CAS 时只为 MCPServerResourceMCPCredentialResource 两张表增加 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 继续负责导入统一的 ServiceProviderMetadataResourceRPCInstanceResource 和派生 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;
  • 含泛型参数的类型名生成合法且稳定的 $defs key,且不与其他类型碰撞;
  • $defs key 的转义按白名单实现: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 前拒绝非法值;
  • inputFingerprintoutputFingerprint 都不受 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 漂移只让该 Tooltools/list 消失,同 Server 其余 Tool 正常返回;
  • outputFingerprint 漂移不影响 tools/listtools/call,只产生管理页告警;
  • LiveServiceIndex 能区分 ACTIVEINACTIVEUNKNOWN
  • 状态为 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/listtools/call 整体返回错误,而不是返回一份逐渐变空的列表;
  • 删除仍被 MCP Server 引用的 discovery 会被拒绝,错误中列出引用者;
  • reference 没有配置 Provider direct URL;
  • Dubbo directory 和 router 的结果进入最终生效的 load balancer;
  • fail-fast 加 retries=0 只产生一次 RPC attempt,dubbotri 各验证一次;
  • Admin 没有本地 round-robin 或 fallback;
  • timeout 和不确定的 transport failure 返回 unknown-outcome,并且不重试,dubbotri 各验证一次;
  • 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,并同时导出 dubbotri 两种协议
  • 一个只导出 tri 的 Java Provider,用于验证协议选择;
  • 使用当前固定 dubbo-go Consumer、并配置数据库 store 的 Dubbo Admin;
  • 分别使用已保存 API Key 和外部 Authorization Server access token 的 MCP Client。

验收流程如下:

  1. 分别通过 Nacos 与 ZooKeeper discovery 在 Admin 中发现等价的 Java service definition,后续 catalog/publish/call 使用同一套 MCP 代码路径。
  2. 等待 LiveServiceIndex 把该 Operation 标记为 ACTIVE,确认 UI 上能区分三种状态。
  3. 创建 Draft,选择精确 Operation,填写语义描述和 aliases,确认协议默认选中 dubbo 且可切换到 tri
  4. Validate 并 Publish。
  5. 创建两个 API Key Credentials,分别调用,revoke 其中一个,验证只有被 revoke 的 Credential 失败。
  6. 使用 Authorization Code + PKCE 或 Client Credentials 从外部 Authorization Server 取得 access token,验证同一 endpoint 能调用;移除 required scope 后验证返回 403。
  7. 验证 tools/list 返回 Published schema。
  8. 验证合法参数通过正常 Dubbo routing 调用一个 Provider,dubbotri 各验证一次。
  9. 下线全部 Provider:验证宽限期内 Tool 仍在 tools/list;超过宽限期后 Tool 从 tools/list 消失;此时仍然发起 tools/call,验证返回 not-executed。重新上线后验证 Tool 回到列表。
  10. 模拟 discovery 不可用,验证状态为 UNKNOWN已发布 Tool 仍在 tools/list,发布新 binding 时给出 warning 但不阻塞。
  11. 把应用升级为不再导出目标接口的版本,但保留其他接口以使实例继续在线,同时让底层 metadata center 保留该接口的旧 definition。验证 Tool 不会被隐藏(MVP 已知缺口,见 §16),调用返回 not-executed
  12. 验证未知嵌套字段在 RPC dispatch 前被拒绝。
  13. 验证嵌套在 POJO 与 List 中的 19 位 long 能以 string 正确传入并被 Provider 收到完整精度。
  14. 修改 Provider 的入参 DTO,验证该 Tool 从 tools/list 消失、同 Server 其余 Tool 正常,直到重新 Publish。
  15. 修改 Provider 的返回值 DTO,验证 tools/listtools/call 均不受影响,仅管理页出现告警。
  16. 尝试为只导出 tri 的服务选择 dubbo 协议,验证发布被拒绝并给出明确原因。
  17. 制造响应 timeout,验证没有第二个 Provider 收到调用,dubbotri 各验证一次。
  18. 启动两个 Admin 副本共用同一个数据库:在副本 A 上 revoke 一个 Credential,验证该凭证在副本 B 上立即失效;两个副本用同一个过期 resourceVersion 并发 publish,验证只有一个成功。

15. 建议实施顺序

  1. 增加 MCP Resource schema、indexes、CAS storage(仅 MCP 两张表)和 repository tests。
  2. 抽取精确 Operation resolver,并实现 inputFingerprintoutputFingerprint 的规范化计算。
  3. 实现 metadata-to-schema 转换和 recursive validation,含 $defs key 生成。
  4. 实现递归标量解码long string→int64、整数范围检查、char 长度检查,覆盖 POJO / collection / map value 的任意嵌套)。这一步的工作量明显大于其名称暗示的规模,应独立排期,不要并入第 3 步。
  5. 增加 Resource 驱动的 LiveServiceIndex(MVP 便宜版):索引本身只订阅公共 resource-change events;若某个 registry 的接口级注册或健康信息尚未归一,则在对应 discovery adapter/subscriber 补齐公共 Resource。不含实例 revision resolver。此步与第 1–4 步无依赖,可并行。
  6. 增加 Draft、Validate、Publish 和 Credential Console APIs。
  7. 增加官方 MCP SDK handler、独立 RouterGroup、统一 Bearer middleware、API Key verifier、OAuth verifier 和 RFC 9728 metadata endpoint。
  8. 增加基于 registry 的 generic client manager,按 binding 协议动态选择 dubbotri,显式配置 fail-fast 和 zero retries。
  9. 实现 tools/list 的逐 Tool 过滤(fingerprint 漂移与 INACTIVE 隐藏)和两层缓存。
  10. 增加 result、error mapping(按协议分别处理)和 structured audit logs(含控制面事件)。
  11. 增加基础管理 UI。
  12. 执行 MCP conformance、Java Provider integration(两种协议)和 multi-replica storage tests。
    这个边界充分使用 Dubbo 已经上报的数据,只增加缺失的语义配置,同时保留 Dubbo 原有的运行时职责。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants