MCP服务越接越乱?ContextForge一把管起来! 原创MCP协议应用场景

 

把两个 MCP 服务放进一个入口,客户端还能分别调用各自的工具:这是 ContextForge 最吸引我的地方。服务越接越多,每个客户端都维护一串地址,改地址和排查连接就容易变得繁琐。

这里用 ContextForge v1.1.0 组织一个本机例子:分别登记时间服务和文本服务,选出两个工具,合成一个虚拟服务,再从同一个客户端调用。示例依据官方文档组合,调用结果是预期,尚未实跑。

入口合在一起,上游仍各自工作

项目维护者在 README 里把 ContextForge 定位为开源注册中心与代理。它登记上游暴露的工具,再通过虚拟服务把选好的工具提供给客户端;真正执行取时间、处理文本的仍是各自的上游服务。

请求链路并不复杂:MCP 客户端 → ContextForge 虚拟服务 → 时间服务或文本服务 → 对应的工具结果。客户端只需连接虚拟服务的地址,网关再把调用转发给相应上游。

这个变化的价值,首先在于把接入配置收到了一个地方。假设团队有几个不同的 Agent 客户端,时间服务换了地址时,可以集中调整网关里的上游登记,再验证原来的虚拟入口;不用先去每个客户端寻找旧地址。

我更看重工具清单和虚拟服务之间的区别。前者回答“网关发现了什么”,后者回答“这个入口准备提供什么”。先把可用工具登记进来,再按客户端的任务挑选,工具越多,这层区分越有用。

比如开发辅助客户端需要读代码和查时间,状态看板只需要查看服务状态,就可以分别组织工具集合,而不必为每个客户端复制一套上游。集合仍需结合网关认证、资源可见范围和上游权限使用,选中工具本身不等于完成权限治理。

先把两个上游分别登记

示例面向 macOS 或 Linux 的 Bash,需要 Python 3.12、curl 和带 npx 的 Node.js,并使用本机的 4444、8003、8004 端口。先创建目录和虚拟环境,安装指定版本的网关:

mkdir contextforge-demo cd contextforge-demo python3.12 -m venv .venv source .venv/bin/activate python -m pip install mcp-contextforge-gateway==1.1.0

从项目 v1.1.0 发布页源码压缩包,把隐藏文件 .env.example 复制到这个目录并改名为 .env,保留示例管理员邮箱。生成密钥后启动网关;下面的网络设置只用于本机演示:

python -m mcpgateway.scripts.init_secrets python -m mcpgateway.scripts.init_secrets --patch-env .env SSRF_ALLOW_LOCALHOST=true SSRF_ALLOW_PRIVATE_NETWORKS=falseSSRF_DNS_FAIL_CLOSED=true mcpgateway --host 127.0.0.1 --port 4444

保持网关终端运行。这里的 4444 是客户端和管理请求的入口,接下来两个上游会分别监听 8003 和 8004。换到容器或其他机器部署时,本机地址的含义也会变化,网络和权限需要重新配置。

接着在同一目录新建 demo_upstreams.py。代码根据启动参数注册工具:clock 提供当前 UTC 时间,text 把输入转为大写;两个进程各自只运行其中一个工具。

from datetime import datetime, timezone import sys from mcp.server.mcpserver import MCPServer kind = sys.argv[1] = MCPServer(kind) if kind == "clock": @.tool() def current_time() -> str: """Return the current UTC time.""" return datetime.now(timezone.utc).isoformat() else: @.tool() def uppercase_text(text: str) -> str: """Convert text to uppercase.""" returntext.upper() app.run(transport="sse", host="127.0.0.1", port=8003 if kind == "clock" else 8004)

新开一个终端,进入同一目录并激活虚拟环境,运行下面两行后保持终端开启。它们分别在后台启动时间服务和文本服务,让网关能访问到两个独立的 SSE 上游:

python demo_upstreams.py clock & python demo_upstreams.py text &

再打开一个终端,在同一目录激活虚拟环境。从 .env 读取管理员身份和签名密钥,生成有效期为 24 小时的令牌;后面的管理请求和客户端连接都会用到它。

exportJWT_SECRET_KEY="$(python -c from dotenv import dotenv_values; print(dotenv_values(".env")["JWT_SECRET_KEY"]))" export ADMIN_EMAIL="$(python -c from dotenv import dotenv_values; print(dotenv_values(".env")["PLATFORM_ADMIN_EMAIL"]))" export TOKEN="$(python -m mcpgateway.utils.create_jwt_token --username "$ADMIN_EMAIL" --exp 1440 --secret "$JWT_SECRET_KEY")"

现在分别登记两个正在运行的服务。每个请求指向一个上游地址,网关会从对应服务发现工具;这一步登记的是服务,并不是替客户端选好工具。

curl -sS -X POST http://127.0.0.1:4444/gateways \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: lication/json" \ -d {"name":"clock_demo","url":"http://127.0.0.1:8003/sse"} curl -sS -X POST http://127.0.0.1:4444/gateways \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: lication/json" \ -d {"name":"text_demo","url":"http://127.0.0.1:8004/sse"}

两次登记看似只是把地址搬了个地方,后续管理的视角却变了:客户端面向虚拟入口,网关维护上游和工具的对应关系。增加服务时,先在这里完成发现,再决定哪些入口需要使用它。

从清单里选工具,拼成虚拟服务

先读取网关的工具清单,预期能找到 current_time 和 uppercase_text 对应的条目,显示名称可能带上游前缀。记下各自的 id:它们是工具 ID,不是刚才登记服务时使用的名称。

curl -sS -H "Authorization: Bearer $TOKEN" http://127.0.0.1:4444/tools?include_pagination=false

下面两行会依次提示输入时间工具 ID 和文本工具 ID;随后创建名为 daily_tools 的虚拟服务,把这两项写进 associated_tools。创建成功后,另行记下返回的虚拟服务 id,稍后要用它组成客户端地址。

read -r -p 时间工具 ID: CLOCK_TOOL_IDread -r -p 文本工具 ID: TEXT_TOOL_ID curl -sS -X POST http://127.0.0.1:4444/servers \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: lication/json" \ -d "{\"server\":{\"name\":\"daily_tools\",\"description\":\"two tools\",\"associated_tools\":[\"$CLOCK_TOOL_ID\",\"$TEXT_TOOL_ID\"]}}"

这一步把来自两个服务的工具放进同一份清单,而不需要改写工具实现。工具 ID 指向被选中的能力,虚拟服务 ID 指向组合后的入口;这两类 ID 用在不同位置,不能互换。

示例里每个上游只有一个工具,重点是看清跨服务的组合过程。换成包含更多工具的上游,也可以按需要挑选,给不同客户端准备不同的集合;上游新增工具后,还要确认对应虚拟服务是否需要纳入它。

把取时间和转大写作为样例,还有个好处:两边的返回值容易辨认,也不依赖真实业务数据。链路跑通后再接团队已有服务,遇到问题时更容易区分接入配置和业务逻辑。

这比让所有客户端直接面对完整清单更容易理解:先知道一个客户端需要做什么,再给它一组相关工具。项目还提供认证、限流和可观测能力,实际生效范围仍取决于网关和上游的配置。

用一个入口验证两个工具

回到保存 TOKEN 的终端,输入刚才返回的虚拟服务 id,打印客户端入口和令牌,再启动 MCP Inspector:

read -r -p 虚拟服务 ID: SERVER_ID printf http://127.0.0.1:4444/servers/%s/mcp\n "$SERVER_ID" printf %s\n "$TOKEN" npx -y @modelcontextprotocol/inspector

在 Inspector 中选择 Streamable HTTP,URL 填刚打印的入口。请求头名称填 Authorization,值填 Bearer、一个空格和令牌;若界面提供单独的 Bearer Token 输入框,则只填令牌本身。

连接后刷新工具列表,分别调用时间工具和文本工具。时间工具预期返回当前 UTC 时间;文本工具的 text 参数填 hello mcp,预期返回 HELLO MCP。显示名称以客户端列出的实际名称为准。

这里值得分两步看:能列出两个工具,说明客户端拿到了虚拟服务提供的清单;两个调用都返回预期结果,才能进一步说明请求分别到达了时间和文本上游。仅仅连上网关,还不足以证明后端服务可用。

这个例子还展示了两侧传输方式可以不同:上游使用 SSE,客户端连接虚拟服务时使用 Streamable HTTP。客户端不需要分别配置两个上游的传输地址,但网关与上游之间仍要保持可访问。

如果连接失败,先核对令牌;如果工具缺失或调用失败,再检查工具 ID 和对应上游进程。令牌与.env 包含敏感内容,不要放进公开仓库。

已经有多个 MCP 服务的团队,可以先按这个例子建一个测试入口,用两个实际调用确认链路,再把上游地址和工具集合换成自己的配置。

#GitHub #MCP #AI开发

为您推荐

MCP服务越接越乱?ContextForge一把管起来! 原创MCP协议应用场景

MCP服务越接越乱?ContextForge一把管起来! 原创MCP协议应用场景

把两个 MCP 服务放进一个入口,客户端还能分别调用各自的工具:这是 ContextForge 最吸引我的地方。服务越接越...

2026-11-07 栏目:科技派

当前非电脑浏览器正常宽度,请使用移动设备访问本站!