MCP客户经理(Go)
mcpmgr 是围绕 modelcontextprotocol/go-sdk 客户。它使多个MCP传输(stdio或HTTP)保持活动状态,扇出 事件,并展示人体工程学助手,用于列出或调用工具、提示、, 以及Go应用程序中的资源。同伴 mcpgateway 包构建 在...之上 mcpmgr 通过单个Streamable公开每个托管服务器 HTTP端点。
安装
go get github.com/vikashloomba/mcp-client-manager-go/pkg/mcpmgr
go get github.com/vikashloomba/mcp-client-manager-go/pkg/mcp-gateway使用预先注册的服务器进行初始化
package main
import (
"context"
"time"
"github.com/vikashloomba/mcp-client-manager-go/pkg/mcpmgr"
)
func main() {
manager := mcpmgr.NewManager(map[string]mcpmgr.ServerConfig{
"stdio-example": &mcpmgr.StdioServerConfig{
BaseServerConfig: mcpmgr.BaseServerConfig{Timeout: 30 * time.Second},
Command: "npx",
Args: []string{"@modelcontextprotocol/server-everything"},
},
"streamable-example": &mcpmgr.HTTPServerConfig{
BaseServerConfig: mcpmgr.BaseServerConfig{Timeout: 30 * time.Second},
Endpoint: "https://gitmcp.io/modelcontextprotocol/go-sdk",
},
}, &mcpmgr.ManagerOptions{DefaultClientName: "my-app", AutoConnect: true})
ctx := context.Background()
// AutoConnect will dial the transports in the background; ensure you close
// them before exiting.
defer manager.DisconnectAllServers(ctx)
}初始化后添加服务器
ctx := context.Background()
config := &mcpmgr.HTTPServerConfig{
BaseServerConfig: mcpmgr.BaseServerConfig{Timeout: 45 * time.Second},
Endpoint: "https://gitmcp.io/modelcontextprotocol/go-sdk",
}
if _, err := manager.ConnectToServer(ctx, "docs-server", config); err != nil {
panic(err)
}列出和调用工具
tools, err := manager.ListTools(ctx, "streamable-example", nil)
if err != nil {
panic(err)
}
for _, tool := range tools.Tools {
println("Tool:", tool.Name)
}
result, err := manager.ExecuteTool(ctx, "streamable-example", "fetch_url_content", map[string]any{
"url": "https://example.com",
})
if err != nil {
panic(err)
}
println("Result:", result.Content)阅读提示和资源
prompts, err := manager.ListPrompts(ctx, "stdio-example", nil)
if err != nil {
panic(err)
}
for _, prompt := range prompts.Prompts {
println("Prompt:", prompt.Name)
}
resources, err := manager.ListResources(ctx, "stdio-example", nil)
if err != nil {
panic(err)
}
for _, resource := range resources.Resources {
println("Resource:", resource.URI)
}
details, err := manager.ReadResource(ctx, "stdio-example", &mcp.ReadResourceParams{URI: resources.Resources[0].URI})
if err != nil {
panic(err)
}
println("First resource size:", len(details.Resource.Data))运行可流式MCP网关
这 mcpgateway 软件包重新导出由管理的每个工具、提示和资源 mcpmgr 通过单个Streamable HTTP端点,因此仅限下游客户端 必须连接一次。
package main
import (
"context"
"log"
"time"
mcpgateway "github.com/vikashloomba/mcp-client-manager-go/pkg/mcp-gateway"
"github.com/vikashloomba/mcp-client-manager-go/pkg/mcpmgr"
)
func main() {
manager := mcpmgr.NewManager(map[string]mcpmgr.ServerConfig{
"stdio-example": &mcpmgr.StdioServerConfig{
BaseServerConfig: mcpmgr.BaseServerConfig{Timeout: 30 * time.Second},
Command: "npx",
Args: []string{"@modelcontextprotocol/server-everything"},
},
}, &mcpmgr.ManagerOptions{DefaultClientName: "gateway-example", AutoConnect: true})
gateway, err := mcpgateway.NewGateway(manager, &mcpgateway.Options{Addr: ":8787", Path: "/mcp"})
if err != nil {
log.Fatalf("gateway init failed: %v", err)
}
ctx := context.Background()
defer manager.DisconnectAllServers(ctx)
log.Println("Serving MCP gateway on http://localhost:8787/mcp")
if err := gateway.ListenAndServe(ctx); err != nil {
log.Fatalf("gateway stopped: %v", err)
}
}检查 cmd/gateway-example 对于可运行的示例和下的包文档 pkg/mcp-gateway 对于命名空间策略等定制选项, 通知挂钩、进度扇出和启发桥接。
检查并序列化服务器配置
GetServerSummaries() 返回一段摘要,其中 Config 是一个 ServerConfig 接口由实现 *StdioServerConfig 或 *HTTPServerConfig。为了避免在每个呼叫站点进行类型切换,请使用辅助程序 防护装置和窄缝器:
summaries := manager.GetServerSummaries()
for _, s := range summaries {
switch mcpmgr.TransportOf(s.Config) {
case mcpmgr.TransportStdio:
if cfg, ok := mcpmgr.AsStdio(s.Config); ok {
// Use cfg.Command, cfg.Args, cfg.Env, etc.
}
case mcpmgr.TransportHTTP:
if cfg, ok := mcpmgr.AsHTTP(s.Config); ok {
// Use cfg.Endpoint, cfg.MaxRetries, cfg.PreferSSE, etc.
}
}
}注: BaseServerConfig 包含功能字段(例如。, OnError, RPCLogger) 哪个 encoding/json 不能元帅。当生成返回的API时 将摘要转换为JSON,构造一个JSON安全的DTO,而不是封送配置 直接。例如:
type serverSummaryDTO struct {
ID string `json:"id"`
Status mcpmgr.ConnectionStatus `json:"status"`
Config map[string]any `json:"config"`
}
func buildSummaryDTOs(m *mcpmgr.Manager) ([]serverSummaryDTO, error) {
sums := m.GetServerSummaries()
out := make([]serverSummaryDTO, 0, len(sums))
for _, s := range sums {
dto := serverSummaryDTO{ID: s.ID, Status: s.Status, Config: map[string]any{}}
switch mcpmgr.TransportOf(s.Config) {
case mcpmgr.TransportStdio:
if c, ok := mcpmgr.AsStdio(s.Config); ok {
dto.Config = map[string]any{
"type": "stdio",
"command": c.Command,
"args": c.Args,
"env": c.Env,
"timeoutSeconds": int(c.BaseServerConfig.Timeout / time.Second),
"version": c.BaseServerConfig.Version,
}
}
case mcpmgr.TransportHTTP:
if c, ok := mcpmgr.AsHTTP(s.Config); ok {
dto.Config = map[string]any{
"type": "http",
"endpoint": c.Endpoint,
"maxRetries": c.MaxRetries,
"sessionId": c.SessionID,
"preferSse": c.PreferSSE,
"timeoutSeconds": int(c.BaseServerConfig.Timeout / time.Second),
"version": c.BaseServerConfig.Version,
}
}
}
out = append(out, dto)
}
return out, nil
}添加自定义HTTP路由
如果您想在MCP网关旁边托管额外的端点(用于健康 检查、指标等),呼叫 ServeMux() 获取底层多路复用器 在启动服务器之前注册路由:
gateway, _ := mcpgateway.NewGateway(manager, &mcpgateway.Options{Addr: ":8787", Path: "/mcp"})
// Add custom routes on the same server.
mux := gateway.ServeMux()
mux.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(200) })
// Start the gateway's server.
_ = gateway.ListenAndServe(context.Background())或者,您可以运行自己的 http.Server 并重用网关的 处理程序和状态:
srv := &http.Server{Addr: ":8787"}
// If Handler is nil, ListenAndServeServer will install gateway.Handler().
_ = gateway.ListenAndServeServer(context.Background(), srv)当通过启用承载令牌保护时 Options.TokenVerifier,只有 可流化MCP端点由该中间件保护。其他路线您 寄存器不会自动打包;根据需要应用自己的身份验证。
UI根镜像
一些MCP服务器根据客户端的UI限制对文件资源的访问 “根”。网关可以将UI的根集镜像到每个下游服务器。 当你的UI学习到它的有效根时,使用新的助手:
// Replace all roots at once (diffed and propagated to all downstream servers):
gateway.SetUIRoots([]*mcp.Root{{URI: "file:///workspace", Name: "Workspace"}})
// Or incrementally add/remove roots:
gateway.AddUIRoots(&mcp.Root{URI: "file:///tmp", Name: "Temp"})
gateway.RemoveUIRoots("file:///tmp")当通过连接新服务器时 gateway.AttachServer,当前缓存 根被推送到该服务器的客户端,因此它立即反映了UI集。
干净地删除服务器
网关现在暴露 DetachServer 和 RemoveServer 助手:
// Detach removes a server's tools/prompts/resources from the aggregated view.
_ = gateway.DetachServer(ctx, serverID)
// Remove detaches and then calls manager.RemoveServer to close and delete it.
_ = gateway.RemoveServer(ctx, serverID)另外, mcpmgr.Manager 发出网关订阅的删除事件 给;打电话 manager.RemoveServer(ctx, id) 自动修剪服务器的 网关的功能,这样它们就不会再出现在客户端面前。
进度通知
两者 mcpmgr 网关保护 _meta.progressToken 价值观和前进 notifications/progress 端到端,即使上游服务器发出浮点数 令牌或客户端省略令牌(网关会自动为每个请求生成一个令牌)。 下游消费者只需在连接时注册处理程序:
client := mcp.NewClient(
&mcp.Implementation{Name: "ui", Version: "1.0.0"},
&mcp.ClientOptions{
ProgressNotificationHandler: func(_ context.Context, req *mcp.ProgressNotificationClientRequest) {
if req == nil || req.Params == nil {
return
}
log.Printf("progress %s %.0f/%.0f", req.Params.Message, req.Params.Progress, req.Params.Total)
},
},
)
session, err := client.Connect(ctx, transport, nil)如果您需要观察Go服务中的上游进度(用于指标或 UI继电器),设置 ManagerOptions.DefaultClientOptions.ProgressNotificationHandler 或打电话 manager.AddNotificationHandler(serverID, mcpmgr.NotificationSchemaProgress, ...).
可选OAuth 2.0保护
mcpgateway 在转发任何MCP流量之前,可能需要承载令牌。到 启用此功能,填充 mcpgateway.Options.TokenVerifier 用你的代币 检查逻辑并提供匹配 TokenOptions (auth.RequireBearerTokenOptions).当两者都存在时,网关会封装 可流式处理程序 auth.RequireBearerToken,自动托管 /.well-known/oauth-protected-resource,回答正确 WWW-Authenticate: Bearer resource_metadata=… header,并在上启用CORS 元数据端点。集 Options.AuthorizationServer 因此,元数据响应 链接到您的发行人,并覆盖 Options.ResourceURL 如果公共URL 客户端将使用不匹配 http://localhost .
样品在 cmd/gateway-example 将此行为与环境切换 变量,这样您就可以在不更改代码的情况下选择加入:
| 变量 | 描述 | 示例值 |
|---|---|---|
AUTHORIZATION_SERVER_URL | 发出网关令牌的OAuth 2.0/OIDC授权服务器的基本URL。 | https://example-server.modelcontextprotocol.io/ |
OAUTH_RESOURCE_METADATA_URL | 客户端发现网关资源元数据的完全限定URL。这通常应该是您部署的网关 /.well-known/oauth-protected-resource. | https://example-server.modelcontextprotocol.io/.well-known/oauth-protected-resource |
mcpgateway.Options.ResourceURL 控制 "resource" 返回的值 元数据端点。它默认为 http://localhost ,匹配 进程内监听器,因此在公共网关URL存在时明确设置它 在代理后面或使用与本地侦听器不同的TLS/主机名。
如果设置了这两个变量,则该示例将附加您的验证器并保护 可流化端点;如果其中任何一个为空,则网关仍对本地用户开放 发展。最小启动顺序如下:
export AUTHORIZATION_SERVER_URL="https://example-server.modelcontextprotocol.io/"
export OAUTH_RESOURCE_METADATA_URL="https://example-server.modelcontextprotocol.io/.well-known/oauth-protected-resource"
go run ./cmd/gateway-example样品内部 TokenVerifier,用真正的JWT替换占位符逻辑 对您的授权服务器进行检查或令牌自检调用。
MCP管理器用户界面(弃权3)
这 apps/mcp-manager-ui/ 目录包含一个Wails 3桌面项目,该项目练习管理器和网关API。使用 wails3 dev 用于实时开发或 wails3 build 生成可分发的二进制文件。
UI模块导入以下包 github.com/vikashloomba/mcp-client-manager-go/pkg/mcpmgr。因为存储库在中定义了一个工作区 go.work:
use (
.
./apps/mcp-manager-ui
)Go在此签出中将这些导入解析到本地源,而不是获取已发布的模块。这使UI与后端保持相同的提交状态——不需要替换指令——因此您可以迭代 pkg/mcpmgr, pkg/mcp-gateway,同时具有腰部前端。
回应启发请求
manager.SetElicitationCallback(func(ctx context.Context, event *mcpmgr.ElicitationEvent) (*mcp.ElicitResult, error) {
println("Elicitation from", event.ServerID, "message:", event.Message)
// Option A: respond immediately.
return &mcp.ElicitResult{Content: []any{"acknowledged"}}, nil
})
// Option B: defer the response and use RespondToElicitation later.
manager.SetElicitationCallback(func(ctx context.Context, event *mcpmgr.ElicitationEvent) (*mcp.ElicitResult, error) {
go func(id string) {
manager.RespondToElicitation(id, &mcp.ElicitResult{Content: []any{"done"}})
}(event.RequestID)
return nil, nil // pending response will be delivered asynchronously.
})下一步
- 通过注册通知处理程序
OnToolListChanged,OnResourceUpdated,
或 AddNotificationHandler 对服务器事件做出反应。
- 提供自定义
RPCLogger通过ManagerOptions发出JSON-RPC流量
您的日志记录或可观察性管道。
发布
推到 main 分支通过以下方式自动剪切释放 .github/workflows/release.yml:
- 工作流程比较
HEAD到最近v*.*.*标记并碰撞
版本(默认为补丁)。包含 #minor 或 #major 在提交消息中 要求更大的颠簸; BREAKING CHANGE 也迫使一个重大的释放。
- 测试成功后,跨平台
manager-example构建二进制文件
作为发布资产上传。然后,工作流创建标签,发布 GitHub发布了一个更新日志,并触发了pkg.go.dev。
