Help us shape the future of Go! Take the 2026 Go Developer Survey

mcp

package
v0.9.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 28, 2026 License: MIT Imports: 25 Imported by: 3

Documentation

Overview

目录层(批次对标 ZCode「连接时一次 tools/list、注册面全量」adapters/src/mcp): 每条连接建立后仅首次取目录走一次线上 ListTools,此后 Tools() 命中内存缓存; 连接被 evict(列举失败/探活死亡/自愈摘除)时目录随连接一并失效,重建连接后 重新列举。第三方 server 工具面在连接生命周期内视为静态——工具集变化需重连 生效,换来每次专家装配/直调不再全量走线(此前 einomcp.GetTools 每次 Tools() 都发一轮 ListTools)。

注解(annotations)随目录同源提取,供并发治理与风险分级消费(对标 ZCode readOnlyHint/idempotentHint/destructiveHint → 风险级 + concurrentSafe 映射)。

envref.go —— ServerConfig 头部值的 ${VAR} 环境引用展开(token 不落配置 文件的纪律件;收编自 argus toolpool,v0.10.39)。未定义变量保留原样并经 onMissing 上报——显式可见优于静默空值。

连接租约面(批次五十三自 bianque 对标 ZCode pool 沉淀):探活 + 闲置回收。

语义裁定——ZCode 原型是「引用计数归零 + 30s 惰性关闭」,本池不可照搬:工具对象 (eino tool)持有连接且生命周期调用方不可见,refcount 无处挂钩。借用模型的等价 租约 = **闲置回收**(ReapIdle/StartIdleReaper)+ **调用期透明重建**(既有 evict + lazy 建连)。在途工具调用经 leaseUse 登记 inFlight,回收据此跳过在用连接 (无须再要求 maxIdle 显著大于最大工具超时)。

请求侧上下文透传(批次五十六 B,对标 ZCode request-context:请求侧 _meta 携带 trace/workspace 上下文,adapters/src/mcp/index.ts:1767-1770): Pool.RequestMeta 在每笔工具调用的 ctx 上取值,经 metaClient 装饰器注入 req.Params.Meta(stdio 与 HTTP 同走 JSON-RPC params,server 侧通用)。 池不感知业务键——metaFn 由装配方提供(bianque 侧从 ctx 取 session/trace/caller)。

errorAsObservation MCP isError 降级器(2026-09-21 k8s 106 审计 P1):eino-ext 把 MCP isError:true 的工具结果当调用 error 上抛,ReAct 直接以 NodeRunError 炸掉整个 agent 步骤——LLM 没有机会看到错误并修正参数(实弹:k8sgpt get-resource 猜错 pod 名 → 专家整步 degraded)。本装饰器把这类错误降级为文本观察回传 LLM(是否修正参数/ 换路径由 LLM 自行决定);传输层等其他错误原样上抛。仅 mcp.Pool.Tools() 出口启用。

Package mcp MCP 工具池:MCP server 连接管理 → eino tool.BaseTool(对齐 eino-ext components/tool/mcp 的能力面,补连接生命周期与白名单池化)。

定位(对应 Argus design §9.2 方向 A):R3/builtin agent 需要 diff 之外的上下文 时,把外部 MCP server 的工具挂进 agent 工具表(工单系统/内部文档/linter 等)。

用法:

pool := mcp.NewPool(
    mcp.ServerConfig{Name: "docs", URL: "http://docs.svc/mcp",
        Headers: map[string]string{"Authorization": "Bearer " + tok}},
    mcp.ServerConfig{Name: "lint", Command: []string{"lint-mcp", "serve"}},
)
defer pool.Close()
res, err := pool.Tools(ctx, []mcp.ToolSpec{{Server: "docs", Allow: []string{"search_docs"}}})
// res.Tools = 装配成功的工具;res.Failed = 被容忍的失败面(server → 原因)

安全模型:stdio 子进程环境走白名单透传(绝不继承密钥,经 procx.ChildEnv 单一纪律);工具白名单按 spec 收敛(未声明的 server/工具不暴露给 agent)。

有线 schema 保真通道(v0.12.5):mcp-go 类型化 Tool 反序列化丢 anyOf—— ToolArgumentsSchema 仅 $defs/type/properties/required/additionalProperties 五类键, outputSchema 的 anyOf/oneOf 联合声明经类型化 tools/list 即残废(inputSchema 同理)。 建连路径本就自建 transport(dialStdio/dialHTTP),句柄入 connState 后 tools/list 走原始 JSON-RPC 自解析,双 schema 无损;transport 未登记(测试注入 InProcess client 等)自动回落类型化通道,outputSchema 置空——宁缺勿假。

工具出参 schema 能力面(v0.12.5,MCP 2025-06-18 outputSchema):目录层经原始 tools/list 无损透传(rawlist.go),invokableTool 以可选接口暴露——eino tool.BaseTool 为封闭接口,schema 进不了 ToolInfo,故走类型断言。断言失败=该 工具无声明(typed 回退/第三方未声明),消费方按「未声明」处理。

Index

Constants

View Source
const DefaultProbeTimeout = 5 * time.Second

DefaultProbeTimeout 探活独立预算(批次五十六 B:ping 是轻量协议方法,不该吃 连接级 30s 预算;对标 ZCode MCP_PING_TIMEOUT_MS=5s)。

View Source
const DefaultRequestTimeout = 10 * time.Minute

DefaultRequestTimeout HTTP 传输单请求兜底缺省。

View Source
const DefaultSelfcheckTTL = 30 * time.Second

DefaultSelfcheckTTL 自检默认目录缓存 TTL。

View Source
const DefaultSelfcheckTimeout = 10 * time.Second

DefaultSelfcheckTimeout 自检默认超时。

View Source
const DefaultTimeout = 30 * time.Second

DefaultTimeout 连接与初始化的缺省超时。

Variables

View Source
var ErrNotConnected = errors.New("mcp: server 无缓存连接(未建连或已被回收)")

ErrNotConnected PingServer 探活时该 server 无缓存连接——懒建连模型下属正常态 (与「有连接但已死」区分,探活无意义不算失败)。

Functions

func CallMeta

func CallMeta(ctx context.Context) map[string]any

CallMeta 从 ctx 取调用元数据;无注入返回 nil(metaClient 对空不注入 _meta)。 返回值可直接作 Pool.RequestMeta:pool.RequestMeta = mcp.CallMeta。

func Classify added in v0.8.2

func Classify(err error) error

Classify 归类并包装(保留原始错误链,errors.Is/As 仍可穿透)。

func ExpandEnvRef

func ExpandEnvRef(v string, onMissing func(name string)) string

ExpandEnvRef 展开字符串中的 ${VAR} 环境引用。已定义取环境值;未定义保留 原样(扫描位置跳过本标记,防死循环)并回调 onMissing。

func ExpandEnvRefs

func ExpandEnvRefs(headers map[string]string, onMissing func(name string)) map[string]string

ExpandEnvRefs 对声明式头部表逐值做 ${VAR} 展开(nil/空表原样返回 nil)。 onMissing 可 nil(未定义变量仍保留原样,只是无告警出口)。

func IsToolResultError

func IsToolResultError(err error) bool

IsToolResultError err 链上是否存在工具业务失败(isError:true)。

func OutputSchemaOf

func OutputSchemaOf(t tool.BaseTool) (json.RawMessage, bool)

OutputSchemaOf 取工具的声明 outputSchema(尽力:装饰器链各自转发;断言失败或 未声明返回 false)。

func UnwrapMCPText

func UnwrapMCPText(raw string) string

UnwrapMCPText 解 MCP 工具返回的信封取内层文本;非信封形态原样返回——快照/ 结果进证据条目时,snippet 配额应留给有效数据而非包装层。 信封两种形态:content[].text(标准工具结果,人类可读文本优先);缺失时 structuredContent(较新 MCP 规范的结构化结果)序列化为 JSON——直接把结构化 对象丢给 LLM 优于丢弃。

func WithCallMeta

func WithCallMeta(ctx context.Context, meta map[string]any) context.Context

WithCallMeta 在 ctx 上注入任务级调用元数据(Runner/任务层调用一次,随 ctx 流入该任务全部 MCP 工具调用)。

func WrapErrorAsObservation

func WrapErrorAsObservation(tools []tool.BaseTool) []tool.BaseTool

WrapErrorAsObservation 出口统一包装(幂等:已包装不重复包;非 InvokableTool 原样透传)。

Types

type Catalog added in v0.8.2

type Catalog struct {
	// Server 所属 server 名(空 = 跨 server 聚合)。
	Server string
	// Tools 工具名清单(已去重、已排序)。
	Tools []string
}

Catalog 工具面快照:某个 server(或一组 server)实际提供的工具清单。

这是「装包≠开洞」「授权面 ⊄ 契约面」这类治理规则的**数据基础**: 只有把「声明要用的」与「实际有的」摆在一起做差集, 才能答出「我以为有的工具其实没有」与「多出来的没声明的工具」两个问题。

func NewCatalog added in v0.8.2

func NewCatalog(server string, tools []string) Catalog

NewCatalog 构造目录(去重 + 排序,保证可比与可比较)。

func (Catalog) Contains added in v0.8.2

func (c Catalog) Contains(name string) bool

Contains 目录是否含某工具。

type CatalogDiff added in v0.8.2

type CatalogDiff struct {
	// Missing 声明了但实际没有(expected - actual)——「我以为有的其实没有」。
	Missing []string
	// Extra 实际有但没声明(actual - expected)——「多出来的、没走授权面」。
	Extra []string
}

CatalogDiff 双向差集结果。

func Diff added in v0.8.2

func Diff(expected, actual Catalog) CatalogDiff

Diff 双向有序差集。

expected = 声明/期望要用的工具面;actual = server 实际提供的工具面。 两个方向都要:**只看一个方向会漏掉一半的治理问题**—— 只看 Missing 漏掉「没声明却存在」的工具(授权面之外的开洞), 只看 Extra 漏掉「声明了却没有」的工具(装包≠开洞)。

结果按字典序(可复现、可比较)。

func DiffNames added in v0.8.2

func DiffNames(expected, actual []string) CatalogDiff

DiffNames 便捷形态:两个工具名清单的差集。

func (CatalogDiff) Equal added in v0.8.2

func (d CatalogDiff) Equal() bool

Equal 双向差集是否都为空。

type ClassifiedError added in v0.8.2

type ClassifiedError struct {
	Kind ErrorKind
	Err  error
}

ClassifiedError 带分类的错误(便于 errors.As 取分类,同时保留原始错误链)。

func (*ClassifiedError) Error added in v0.8.2

func (e *ClassifiedError) Error() string

func (*ClassifiedError) Unwrap added in v0.8.2

func (e *ClassifiedError) Unwrap() error

type ErrorKind added in v0.8.2

type ErrorKind int

ErrorKind MCP 调用失败的稳定分类。

为什么要有枚举:下游此前各自做**字符串匹配**(`strings.Contains(err.Error(), "connection refused")`)判「连不上」还是「工具不存在」,文案一改就失效。 枚举把判定锚在**协议层事实**上(JSON-RPC 错误码 / net.Error / 错误链类型), 不随实现文案漂移。

处置口径(这四类的处置完全不同,不要混):

KindUnreachable —— 连不上。基础设施问题:重试 / 换节点 / 报警。
KindProtocol    —— 协议错。server 实现不合规或版本不匹配:修 server / 升级。
KindToolMissing —— 工具不存在。**配置或契约问题**:server 起来了但没有这个
                   工具(装包≠开洞、工具面未声明)。不要重试——重试一万次
                   它也不会出现。改配置或改契约。
KindInvalidArgs —— 参数错。**调用问题**:工具在、但传参不合法。改调用方。
const (
	// KindUnknown 未归类(兜底)。
	KindUnknown ErrorKind = iota
	// KindUnreachable 连不上(网络/进程层)。
	KindUnreachable
	// KindProtocol 协议层错误(JSON-RPC 错误码非「工具不存在/参数错」)。
	KindProtocol
	// KindToolMissing 工具不存在(契约/配置问题)。
	KindToolMissing
	// KindInvalidArgs 参数错(调用问题)。
	KindInvalidArgs
)

func ClassifyError added in v0.8.2

func ClassifyError(err error) ErrorKind

ClassifyError 把任意错误归到稳定分类。

判定顺序(**先具体后笼统**,避免被笼统分支抢走):

  1. 工具业务失败(isError:true)→ 按 payload 信号细分,否则 KindProtocol
  2. JSON-RPC 哨兵错误(mcp.ErrInvalidParams / ErrMethodNotFound …)—— 这是 mcp-go 把 JSON-RPC 错误码转成 Go error 的**官方路径** (JSONRPCErrorDetails.AsError),按哨兵判比按错误码判更可靠: 它扛得住错误码被包进自定义 error 的情况。
  3. 错误链里有 net.Error / os.ErrNotExist / syscall.ECONNREFUSED 等 → KindUnreachable
  4. ctx 超时/取消 → KindUnreachable(连不上的一种)
  5. 兜底 KindUnknown

func ClassifyRPCCode added in v0.8.2

func ClassifyRPCCode(code int) ErrorKind

ClassifyRPCCode JSON-RPC 错误码 → 分类。

供「已知错误码但还没转成 Go error」的场景用(如自检时读了原始响应)。 走 mcp-go 官方转换路径(JSONRPCErrorDetails.AsError)时不必用本函数—— ClassifyError 已经按哨兵错误判好了。

func KindOf added in v0.8.2

func KindOf(err error) ErrorKind

KindOf 取错误分类(未包装时也能直接判)。

func (ErrorKind) MarshalJSON added in v0.8.2

func (k ErrorKind) MarshalJSON() ([]byte, error)

MarshalJSON 输出可读分类名(不是数字)——JSON 消费方(监控/告警)不该 看到裸 int 枚举值。

func (ErrorKind) Retryable added in v0.8.2

func (k ErrorKind) Retryable() bool

Retryable 该类错误重试是否有意义。

关键:**工具不存在与参数错重试没有意义**——重试不会让 server 长出那个工具, 也不会让非法参数变合法。盲目重试会把「配置问题」伪装成「偶发抖动」。

func (ErrorKind) String added in v0.8.2

func (k ErrorKind) String() string

String 分类名(稳定标识,可进日志/指标标签)。

func (*ErrorKind) UnmarshalJSON added in v0.8.2

func (k *ErrorKind) UnmarshalJSON(data []byte) error

UnmarshalJSON 支持从分类名反解。

type Health added in v0.8.2

type Health struct {
	// State 三态结论。
	State HealthState
	// Servers 参与聚合的 server 名(排序)。
	Servers []string
	// Issues 问题清单(按 server → tool 排序)。
	Issues []HealthIssue
	// Total 参与聚合的 server 数。
	Total int
	// HealthyCount 完全健康的 server 数。
	HealthyCount int
	// DegradedCount 降级的 server 数。
	DegradedCount int
	// DownCount 不可达的 server 数。
	DownCount int
}

Health 健康聚合结果。

判据可观察:**能看出是哪个 server 的哪个工具出的问题**——不是只给一个总体布尔。 Issues 逐条给出 server / tool / 分类 / 原因。

func Aggregate added in v0.8.2

func Aggregate(results []SelfcheckResult) Health

Aggregate 把多个 server 的自检结果聚合成一个健康结论。

## 三态判定依据(明确写死,不留给调用方猜)

down     —— **有 server 连不上**(Reachable==false)。
           这是基础设施故障:server 进程挂了 / 网络不通 / 建连超时。

degraded —— **全部 server 都连得上,但至少一个能力不可用**:
           工具缺失(声明了但目录里没有)或个别工具调用失败。

healthy  —— 全部 server 连得上,且声明的工具全部在目录里、可调。

## 关键歧义:「工具缺失」归 degraded,**不是** down

这是下游踩过的坑:把「工具缺失」当「server 挂了」→ 依赖故障被 放大成平台故障。理由是二者的**处置完全不同**:

  • server 连不上 ⇒ 该 server 的能力**全部**不可用 ⇒ 影响面是整个 server。
  • 工具缺失 ⇒ server **活着**且在正常提供服务,只是没有某个工具 ⇒ 影响面是那一个工具。

若把工具缺失算成 down:一个 server 少了个工具,平台就被判全挂, 告警风暴 + 流量全量切走 —— 而实际上 server 还能服务其他工具。

## 「server 挂了」时,工具状态标 unreachable 而非 missing

server 连不上时**无从知道**它有没有那个工具(没应答), 所以自检层把工具标成 ToolUnreachable(未知)而不是 ToolMissing(确认没有)。 聚合时 unreachable 的工具**不计入** missing 计数—— 否则「server 挂了」会被算成「N 个工具缺失」, 再次把基础设施故障伪装成契约故障。

## 部分 server 挂掉时,整体是 down 还是 degraded?

取**最严重**态:任一 server 不可达 ⇒ 整体 down。 理由:聚合结论回答的是「平台能不能按声明提供服务」—— 声明了却完全拿不到的那个 server,其能力是真缺失的。 但 Issues 里会逐条列出,调用方能看出是「哪个 server 挂了」 而不是笼统一个 down。

func (Health) Degraded added in v0.8.2

func (h Health) Degraded() bool

Degraded 是否降级。

func (Health) Down added in v0.8.2

func (h Health) Down() bool

Down 是否不可用。

func (Health) Healthy added in v0.8.2

func (h Health) Healthy() bool

Healthy 是否完全健康。

func (Health) OK added in v0.8.2

func (h Health) OK() bool

OK 是否可服务(healthy 或 degraded 都还有能力在提供)。

type HealthIssue added in v0.8.2

type HealthIssue struct {
	// Server 出问题的 server 名(空 = 跨 server 汇总项)。
	Server string
	// Tool 出问题的工具名(空 = server 级问题,如连不上)。
	Tool string
	// Kind 错误分类。
	Kind ErrorKind
	// Reason 人类可读原因。
	Reason string
}

HealthIssue 单条问题。

type HealthState added in v0.8.2

type HealthState string

HealthState 健康三态。

const (
	// HealthHealthy 全绿:server 全可达,声明的工具全在且可调。
	HealthHealthy HealthState = "healthy"
	// HealthDegraded 降级:server 活着,但**部分**能力不可用
	//(工具缺失 / 个别工具调用失败)。服务还在提供部分能力。
	HealthDegraded HealthState = "degraded"
	// HealthDown 全挂:server 连不上(基础设施故障)。
	HealthDown HealthState = "down"
)

func (HealthState) MarshalJSON added in v0.8.2

func (s HealthState) MarshalJSON() ([]byte, error)

MarshalJSON 输出可读状态名。

type OAuthClientConfig

type OAuthClientConfig struct {
	ClientID     string // 空=依赖 IdP DCR(handler.RegisterClient 由调用方驱动)
	ClientSecret string
	Scopes       []string
	MetadataURL  string // 空=从 base URL .well-known 发现
	RedirectURI  string // 授权回调落点(无头服务端=固定平台端点)
	TokenStore   client.TokenStore
}

OAuthClientConfig URL 传输的 OAuth2 装配输入(映射 mcp-go client.OAuthConfig; TokenStore 必填——缺省会落内存 store,进程内自愈场景重连后丢授权态)。

type OutputSchemer

type OutputSchemer interface {
	OutputSchema() json.RawMessage
}

OutputSchemer 工具出参 schema 的可选能力接口。

type Pool

type Pool struct {

	// OnError 建连/列举失败回调(nil 安全)。返回错误不中断其余 server——
	// 部分失败容忍:可用的工具照常返回,失败的 server 由调用方经钩子观测。
	OnError func(server string, err error)
	// RequestMeta 每笔工具调用 _meta 注入来源(nil 安全;批次五十六 B,对标 ZCode
	// request-context)。metaFn 在调用方 ctx 上取值,池不感知业务键;经 metaClient
	// 装饰器注入 req.Params.Meta(见 meta.go)。返回 nil/空 = 不注入。
	RequestMeta func(ctx context.Context) map[string]any
	// Elicitation server→client 征集输入处理器(mcp-go v1.1 SEP-2322):工具执行中
	// server 反向请求补充输入(表单/URL 模式)时回调;nil 时该类请求得到
	// "no elicitation handler configured" 错误(调用失败呈现,与不声明能力的服务端
	// 行为一致)。池级单点——建连/自愈 redial/Initialize 能力声明共用。
	ElicitationHandler client.ElicitationHandler
	// contains filtered or unexported fields
}

Pool MCP 连接池:lazy 建连(首次 Tools 时)、连接缓存复用、Close 全量回收。 闲置回收/探活租约面见 lease.go(ReapIdle/StartIdleReaper/PingServer)。

func NewPool

func NewPool(cfgs ...ServerConfig) *Pool

NewPool 创建连接池(建连延后到 Tools 调用时——允许启动期 MCP server 未就绪)。 Name 为空的配置忽略;重名保留先到者(后者忽略)——配置错误尽早收敛为确定性 行为而非静默覆盖。

func (*Pool) Close

func (p *Pool) Close()

Close 关闭全部缓存连接并拒绝后续建连(幂等);停止闲置回收协程。Close 后池不可复用。 锁序:先在 p.mu 内标记 closed 并摘出各 connState 指针,释放后再逐个收连接—— 绝不在持 p.mu 时抢 st.mu(与 dialAndRegister 的写入路径交叉会死锁)。

func (*Pool) Hints

func (p *Pool) Hints(server string) map[string]ToolHints

Hints 目录注解快照(server 名 → 工具名 → 注解)。server 未配置或未列举 → nil。 供装饰层做并发治理与风险分级;返回副本,调用方可自由持有。

func (*Pool) PingServer

func (p *Pool) PingServer(ctx context.Context, name string) error

PingServer 探活指定 server 的缓存连接。死亡连接立即摘除出缓存并关闭,下次 调用透明重建——HTTP server 被停掉不派发断连回调,「无声死亡」只有显式探活 才能暴露(ZCode pool 同款问题与解法)。

探活方法按建连协商的协议代际分流(v0.10.29 审计修复——mcp-go v1.1 起现代 协议 2026-07-28 移除了 ping RPC,Client.Ping 在该代际是 no-op,曾让探活 虚报存活且 touch 给死连接续命、闲置回收永远收不走):

  • legacy(≤2025-11-25):协议 ping;
  • modern(2026-07-28+):改发真实轻量 RPC(tools/list)——比 ping 重, 但这是该代际下唯一能证明连接活着的信号。

返回 ErrNotConnected = 无缓存连接(不算失败);其余错误 = 探活失败(连接已摘除)。

func (*Pool) ReapIdle

func (p *Pool) ReapIdle(maxIdle time.Duration) int

ReapIdle 关闭闲置超过 maxIdle 的缓存连接,返回回收数。闲置时钟 = 最近一次 建连/取工具/探活成功。被回收的 server 下次调用透明重建(evict+lazy 建连)。 在途工具调用(leaseUse 登记)中的连接不回收——原先契约要求 maxIdle 显著大于 最大工具超时,现在由在途计数直接挡下。

func (*Pool) Servers

func (p *Pool) Servers() []string

Servers 返回全部配置的 server 名(字母序,可观测用)。

func (*Pool) StartIdleReaper

func (p *Pool) StartIdleReaper(interval, maxIdle time.Duration)

StartIdleReaper 启动后台闲置回收协程:每 interval 周期执行 ReapIdle(maxIdle)。 Close 停止;重复启动幂等(只起一个)。

func (*Pool) Tool added in v0.9.0

func (p *Pool) Tool(ctx context.Context, server, name string) (tool.BaseTool, error)

Tool 按 server + 工具名取**单个**工具。

「取不到」是**显式错误**,不是静默降级:此前调用方只能写 `res.Tools[0]` 这种**位置假设**——Allow 给多个工具时取到的可能是另一个工具,server 列举 部分失败时同样可能取错,而这两种失效都是**静默**的(调用成功、调的是别的工具)。

name 可写**原始工具名**(server 内)或带 server 的全名("server.tool")—— 与白名单的两种写法一致。

func (*Pool) Tools

func (p *Pool) Tools(ctx context.Context, specs []ToolSpec) (ToolsResult, error)

Tools 按 spec 白名单返回 eino 工具与失败面(跨 server 并行拉取)。 spec.Server 未配置 → 报错(配置错误应显式暴露);server 建连/列举失败 → 容忍:记入 Failed(同时经 OnError 发射),可用工具照常返回——列举失败视为 该连接失效,已从缓存摘除关闭(server 崩溃后下次调用自动重建)。 OAuth 授权必需错误 fail-fast 直通(错误返回,不进 Failed)。

type SelfcheckOptions added in v0.8.2

type SelfcheckOptions struct {
	// Server server 名(结果标识 + 缓存键)。
	Server string
	// Timeout 单次探测的超时预算(默认 10s)。
	Timeout time.Duration
	// TTL 目录缓存有效期(默认 30s)。<= 0 表示不缓存(每次都实拉)。
	//
	// 为什么需要缓存:自检是「高频拉目录」的动作,而 MCP 侧的工具目录
	// 在连接生命周期内视为静态(见 catalog.go)。没有 TTL 时,每次探活
	// 都会打一轮 tools/list —— 部署机的巡检会把 server 压垮。
	TTL time.Duration
	// ProbeTools 是否逐工具做一次**调用**探测(默认 false)。
	//
	// true = 每个工具都真调一次(最彻底,但有副作用:会真的执行工具)。
	// 默认 false = 只探连通性 + 拉目录(无副作用)。
	// 工具「存在但调不通」这种情形,用 tools/call 探针单点确认(见 ProbeTool)。
	ProbeTools bool
	// ProbeArgs 逐工具探测时的参数(nil = 用空对象)。
	ProbeArgs map[string]any
	// Initialize 是否在自检前先发 initialize 握手。
	//
	// 默认 false:调用方通常传**已建连**的 client(Pool 的 client 都已
	// 握手过)。但若传的是裸 client(未 initialize),协议请求会返回
	// "client not initialized"——那时自检会误判成 server 不可达。
	// 自检是「诊断工具」,不该因为握手缺失而给出错误结论,
	// 所以提供这个开关。
	Initialize bool
	// Expected 声明期望的工具面(可选)。
	//
	// 给了它,自检会逐个核:这���工具在不在 server 目录里、在但调不通。
	// 这是「装包≠开洞」这类治理规则的输入——只有把声明面与实际面对照,
	// 才能发现「我以为有的工具其实没有」。
	Expected Catalog
}

SelfcheckOptions 自检参数。

type SelfcheckResult added in v0.8.2

type SelfcheckResult struct {
	// Server server 名。
	Server string
	// Reachable 连通性(server 能否应答协议请求)。
	Reachable bool
	// Tools 逐工具结果(按工具名排序)。
	Tools []ToolCheck
	// Catalog server 实际提供的工具目录(连通时有效)。
	Catalog Catalog
	// Expected 声明期望的工具面(可选,供差集比对)。
	Expected Catalog
	// Diff 期望 vs 实际的差集(Expected 非空时有效)。
	Diff CatalogDiff
	// PingErr ping 探测的错误(**不代表不可达**——server 可能没实现 ping;
	// 真正的连通性判据是列举成功)。留在这里供诊断。
	PingErr error
	// Kind 整体失败的错误分类(Reachable==false 时有效)。
	Kind ErrorKind
	// Err 整体失败的原始错误。
	Err error
	// Cached 本次结果是否来自 TTL 缓存。
	Cached bool
	// At 探测时刻。
	At time.Time
}

SelfcheckResult 单个 server 的自检结果。

func (SelfcheckResult) Failed added in v0.8.2

func (r SelfcheckResult) Failed() []ToolCheck

Failed 失败的工具条目(Status != ToolOK)。

type Selfchecker added in v0.8.2

type Selfchecker struct {
	// contains filtered or unexported fields
}

Selfchecker 带 TTL 缓存的自检器。

用法(部署机上的常驻自检):

sc := NewSelfchecker()
res := sc.Check(ctx, client, SelfcheckOptions{Server: "fs", Expected: exp})

零值不可用——用 NewSelfchecker 构造。

func NewSelfchecker added in v0.8.2

func NewSelfchecker() *Selfchecker

NewSelfchecker 构造自检器。

func (*Selfchecker) Check added in v0.8.2

Check 对单个 server 做自检:拉工具目录 + 探连通性 + 逐工具核状态。

client 非 nil 时用它拉目录与探活;nil 时只做「声明面 vs 已有目录」的核, 不发任何请求(离线比对模式)。

type ServerConfig

type ServerConfig struct {
	// Name 逻辑名:ToolSpec.Server 引用。
	Name string
	// Command stdio 传输:可执行 + 参数(子进程由本包启动与回收)。
	Command []string
	// Env stdio 子进程环境白名单:条目为纯变量名(如 "GITHUB_TOKEN")时按名从
	// 当前进程透传;含 "=" 时按 KEY=VALUE 字面透传。未列出的变量一律不继承
	// (基础集见 procx.ChildEnv),防止本进程密钥泄漏给外部 MCP server。
	Env []string
	// URL HTTP 传输(streamable http)。
	URL string
	// Headers HTTP 请求头(如 Bearer 鉴权)。
	Headers map[string]string
	// Timeout 连接 + Initialize 超时(默认 30s)。仅作用于建连期(dial +
	// Initialize 的 ctx 预算),不约束后续请求——HTTP 传输的每请求兜底见
	// RequestTimeout。
	Timeout time.Duration
	// RequestTimeout HTTP 传输的单请求兜底上限(默认 10 分钟)。mcp-go 的
	// WithHTTPTimeout 直接设 httpClient.Timeout、作用于该连接的每一次请求
	// (含 tools/call)——此前误用连接级 Timeout(30s)会让任何长工具调用
	// 在 HTTP 传输下永远失败(v0.10.29 审计修复);工具调用的正常时长控制
	// 归调用方 ctx,这里只是挂死兜底。stdio 不受影响。
	RequestTimeout time.Duration
	// ProbeTimeout 探活(PingServer)独立预算(默认 5s;租约面 lease.go——
	// 探活是轻量协议 ping,不该吃连接级 30s 预算)。
	ProbeTimeout time.Duration
	// OAuth URL 模式 OAuth2 客户端配置(批次五十六 C,对标 ZCode oauth 全栈;
	// 仅 URL 传输生效)。非 nil 时 dial 走 NewOAuthStreamableHttpClient——401 触发
	// client.OAuthAuthorizationRequiredError(errors.As 可穿透池的 %w 包装),
	// 调用方经 client.GetOAuthHandler 取 handler 驱动授权码+PKCE 流程;
	// TokenStore 由调用方提供(进程内缓存失效后重 dial 仍能取回已授权 token)。
	OAuth *OAuthClientConfig
}

ServerConfig 单个 MCP server 连接声明。 stdio(Command 非空)与 HTTP(URL 非空)二选一;都空视为无效配置(跳过并告警)。

type ToolCheck added in v0.8.2

type ToolCheck struct {
	// Tool 工具名。
	Tool string
	// Status 状态。
	Status ToolStatus
	// Kind 失败时的错误分类(Status==ToolOK 时为 KindUnknown)。
	Kind ErrorKind
	// Reason 人类可读的失败原因(Status==ToolOK 时为空)。
	Reason string
	// Err 原始错误(供 errors.Is/As 穿透)。
	Err error
}

ToolCheck 单个工具的自检结果。

type ToolHints

type ToolHints struct {
	ReadOnly    bool
	Idempotent  bool
	Destructive bool
}

ToolHints 工具注解归一(mcp.ToolAnnotation 子集,nil 视为规范缺省)。

func (ToolHints) ConcurrentSafe

func (h ToolHints) ConcurrentSafe() bool

ConcurrentSafe 读读可并行(readOnly+idempotent,对标 ZCode concurrentSafe); 缺省注解按非安全处理——保守。

func (ToolHints) RiskLevel

func (h ToolHints) RiskLevel() string

RiskLevel 风险级(对标 ZCode:readOnly→low、destructive→high、其余 medium)。

type ToolResultError

type ToolResultError struct {
	// contains filtered or unexported fields
}

ToolResultError MCP 工具执行了但业务失败(CallToolResult.IsError=true)。 v0.11 起导出:包外消费方(经 agentkit/mcp 组装工具表的调用方)可 errors.As 穿透 %w 包装直达本类型取 Payload,无须对 Error() 文案做文本匹配——多错误 聚合文本的 marker 分类不可靠。Error() 文案是历史识别锚点(observe 兜底与 既有测试消费),改文案须同步;新代码优先 errors.As / IsToolResultError。

func (*ToolResultError) Error

func (e *ToolResultError) Error() string

func (*ToolResultError) Payload

func (e *ToolResultError) Payload() []byte

Payload 原始 CallToolResult JSON(observe 层剥 content[].text 用)。

type ToolSpec

type ToolSpec struct {
	// Server ServerConfig.Name。
	Server string
	// Allow 工具名白名单。
	//
	// ⚠️ **破坏式变更**(v0.9.0):**空 = 不授予任何工具**(缺省拒绝)。
	// 此前空 = 该 server 的**全部**工具——而这个默认值在配置里**不可见**:
	// 一份 `allow: []` 看起来像「没限制」,实际是把该 server 的全部工具
	// (含删除/执行类破坏性工具)都授予了模型,最小权限的责任整个落到了下游
	// (下游只能靠事后门禁兜——那时配置已生效、模型已拿到工具)。
	//
	// 要「全给」必须**显式**写:AllowAll: true,或 Allow 里含 "*"。
	Allow []string
	// AllowAll 显式授予该 server 的全部工具(缺省拒绝的对立面,必须写在配置里)。
	//
	// 与 Allow 里的 "*" 等价;同时存在时以本字段为准(Allow 被忽略)。
	AllowAll bool
}

ToolSpec 工具白名单:某 server 的哪些工具暴露给 agent。

type ToolStatus added in v0.8.2

type ToolStatus string

ToolStatus 单个工具的自检状态。

const (
	// ToolOK 工具在目录里且连通。
	ToolOK ToolStatus = "ok"
	// ToolMissing 工具在声明面里但 server 目录里没有(契约/配置问题)。
	ToolMissing ToolStatus = "missing"
	// ToolUnreachable server 连不上,工具状态未知(**不是** missing)。
	ToolUnreachable ToolStatus = "unreachable"
	// ToolFailed 工具存在但调用失败。
	ToolFailed ToolStatus = "failed"
)

type ToolsResult

type ToolsResult struct {
	// Tools 装配成功的工具(跨 server 并行拉取,按 specs 序稳定)。
	//
	// ⚠️ 这是**跨 server 的扁平列表**:拿到的工具没有「它来自哪个 server」的
	// 结构化归属(工具名虽带 server 命名空间,但那是模型侧的名字,不是归属面)。
	// 要按 server 取用走 ByServer;要按 server+名字取单个走 Pool.Tool。
	Tools []tool.BaseTool
	// ByServer server → 该 server 装配出的工具(按目录序)。
	//
	// 它是「工具归属」的结构化面——不让调用方靠位置假设(res.Tools[0])去猜
	// 某个工具来自哪个 server。空 = 没有工具。
	ByServer map[string][]tool.BaseTool
	// Failed server → 被容忍的失败原因(建连/列举失败、白名单无一命中)。
	// 失败连接已摘除,下次调用透明重建。空 = 全部成功。OnError 仍发射同源
	// 通知(遥测钩子),失败面的程序化消费一律走这里。
	Failed map[string]error
}

ToolsResult Tools 的结果面(v0.10.38 破坏式:此前返回 []BaseTool,部分失败只 经 OnError 侧面观测——调用方在返回值层面无法区分「失败被容忍」与「真的没 工具」,只能把失败当空表静默降级)。

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL