# mcpx **Repository Path**: ariacompute/mcpx ## Basic Information - **Project Name**: mcpx - **Description**: aria mcpx. - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-10-21 - **Last Updated**: 2026-11-11 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README
# mcpx **把任意 HTTP 服务变成 MCP 工具的网关** 用一份声明式配置,把**任意 HTTP 服务**发布成 MCP 工具,同时对外提供 MCP
服务端出口(Streamable HTTP 与 stdio)。名字沿用 nginx 那种短小写风格 ——
"MCP-x"。它和 nginx 是**形态参照**而非依赖:upstream 声明地址、前缀白名单决定
"能去哪儿"、限长与超时决定"能待多久",但协议换成了 MCP、配置换成了 YAML。 [English](./README.md) · **简体中文** · [文档](./requirements.md) [![][release-shield]][release-link] [![][go-shield]][go-link] [![][license-shield]][license-link] [![][lastcommit-shield]][lastcommit-link]
[![][transports-shield]][transports-link] [![][tests-shield]][tests-link] [![][docs-shield]][docs-link]
Table of contents #### TOC **上手** - [✨ 功能](#-功能) - [🚀 快速开始](#-快速开始) - [🔌 接入方式](#-接入方式) **配置与服务** - [🔧 配置](#-配置) - [🧰 示例](#-示例) - [🛳 部署](#-部署) **内部机制** - [🔐 安全模型](#-安全模型) - [📊 错误语义](#-错误语义) - [🔨 开发](#-开发) **信任与治理** - [📚 文档](#-文档) - [📝 许可证](#-许可证) - [🤝 工程约定](#-工程约定)
## ✨ 功能 MCP 网关:用一份声明式配置,把**任意 HTTP 服务**发布成 MCP 工具,同时对外提供 MCP 服务端出口(Streamable HTTP 与 stdio)。名字沿用 nginx 那种短小写风格 —— nginx 是 "engine-x",这里是 "MCP-x"。 ``` MCP 客户端 ──HTTP POST /mcp──┐ ──stdio NDJSON───┤ mcpx ──HTTP──> 你的 HTTP 服务(任意语言/框架) │ ├─ 每个 operation 一个具名工具 │ └─ 每个 upstream 一个受限透传工具 ``` 和 nginx 的关系是**形态参照**而不是依赖:upstream 声明地址、前缀白名单决定"能去哪儿"、限长与超时决定"能待多久",但协议换成了 MCP,配置换成了 YAML。网关不内置 TLS、不缓存、不热重载 —— 这三件事交给前置 nginx 与一次重启。 - 🔌 **两种 MCP 传输** —— 同一份工具集同时以 Streamable HTTP(`POST /mcp`、会话、`Mcp-Session-Id`)与 stdio(按行 JSON-RPC)对外提供,共用一份配置。 - 🧩 **一份配置,两类工具** —— 每个 `operation` 变成一个具名工具(`_`);每个 upstream 另有一个受白名单约束的受限透传工具。 - 🛡 **配置失败即关机** —— 未知字段、非法工具名、透传白名单为空、凭据变量名不合法,一律启动失败,绝不静默放过。 - 🔒 **配置里没有密钥** —— 凭据只以环境变量名出现,运行时才读取;日志与错误消息不打印任何 token,敏感头统一脱敏。 - 🪶 **零框架** —— Go 1.27.3、标准库 `net/http`,唯一第三方依赖是 `gopkg.in/yaml.v3`,单一静态二进制。
[![][back-to-top]](#readme-top)
## 🚀 快速开始 ```bash # 1. 先构建一次:bin/mcpx 是静态二进制,git 版本戳(version / commit / date)通过 # -ldflags 注入,所以 `mcpx version` 与 /healthz 能说清跑的是哪个 revision。 make build # 2. 在自己的副本上改。mcpx.yaml 是仓库里的示例(指向 petstore.example.com), # *.local.yaml 已 gitignore,你的改动(以及没写完的上游)不会进仓库。 cp mcpx.yaml mcpx.local.yaml # 3. 凭据只走环境变量,不写进配置: # MCPX_TOKEN 是客户端访问 /mcp 必须出示的 token —— mcpx.local.yaml 在 # server.auth.token_env 里引用它,为空则进程拒绝启动; # PETSTORE_TOKEN 是那个上游的 bearer token,调用时读取,缺了只会让该次工具调用 # 变成 isError 结果,不会影响网关运行。 export MCPX_TOKEN=... export PETSTORE_TOKEN=... # 4. 先体检:逐个校验 URL、路径模板、工具名冲突、透传白名单与凭据变量名。 # 不启动任何服务、不需要 token 值,通过后会把工具清单打出来。 ./bin/mcpx check -c mcpx.local.yaml # 5. 用 Streamable HTTP 提供 MCP 端点 —— 这是默认命令, # 所以 `./bin/mcpx -c mcpx.local.yaml` 等价。 ./bin/mcpx serve -c mcpx.local.yaml # 6. 或改用 stdio 暴露同一批工具,供“自己拉起进程、在 stdin/stdout 上收发 # 按行 JSON-RPC”的客户端使用。 ./bin/mcpx stdio -c mcpx.local.yaml ``` `check` 通过即代表:URL 合法、路径模板可编译、工具名无冲突、透传前缀非空、凭据变量名合法。任何一项失败都会带着位置报错并拒绝启动。
[![][back-to-top]](#readme-top)
## 🔌 接入方式 ### Streamable HTTP ```bash curl -sS http://127.0.0.1:8848/mcp \ -H "Authorization: Bearer $MCPX_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","clientInfo":{"name":"curl","version":"1"}}}' ``` 响应头 `Mcp-Session-Id` 必须出现在后续每个请求上;`notifications/*` 返回 `202` 无正文;`DELETE /mcp` 终止会话;`GET /mcp` 返回 `405`(本网关不主动推送)。`/healthz` 不需要鉴权,返回版本、工具数与会话数。 MCP 客户端配置片段(例如 Claude Desktop / IDE 插件): ```json { "mcpServers": { "mcpx": { "type": "http", "url": "http://127.0.0.1:8848/mcp", "headers": { "Authorization": "Bearer ${MCPX_TOKEN}" } } } } ``` ### stdio ```json { "mcpServers": { "mcpx": { "command": "/path/to/bin/mcpx", "args": ["stdio", "-c", "/path/to/mcpx.local.yaml"] } } } ``` stdout 是协议通道,所有日志走 stderr;读到 EOF 即正常退出。
[![][back-to-top]](#readme-top)
## 🔧 配置 主配置 YAML,**同结构的 JSON 也接受**(同一套解码路径)。未知字段直接报错,避免把 `allow_prefixs` 这种拼写错误静默解释成"不限制"。 ```yaml server: listen: 127.0.0.1:8848 path: /mcp auth: {scheme: bearer, token_env: MCPX_TOKEN} # none | bearer protocol_versions: ["2025-06-18", "2025-03-26"] allowed_origins: ["https://app.example.com"] # 留空 = 只允许 loopback Origin instructions: "..." # 可选,initialize 时回给客户端 max_request_bytes: 1048576 session_ttl: 30m shutdown_grace: 15s defaults: timeout: 15s max_response_bytes: 4194304 max_body_bytes: 1048576 upstreams: - name: petstore base_url: https://petstore.example.com # 可带路径前缀,如 /api headers: {X-Env: prod} auth: {scheme: bearer, token_env: PETSTORE_TOKEN} timeout: 10s max_response_bytes: 1048576 allow_prefixes: ["/api/v1"] operations: - name: get_pet description: Fetch one pet by id. method: GET path: /api/v1/pets/{id} params: - {name: id, in: path, required: true, schema: {type: string}} - {name: verbose, in: query, schema: {type: boolean}} - {name: X-Trace, in: header} - name: create_pet method: POST path: /api/v1/pets body_param: pet params: - {name: pet, in: body, required: true} passthrough: enabled: true name: petstore_request # 默认 _request methods: [GET, POST] # 默认 [GET] allow_prefixes: ["/api/v1"] # 默认继承 upstream.allow_prefixes ``` ### 字段语义 | 字段 | 说明 | | --- | --- | | `server.auth` | `none` 或 `bearer`。`bearer` 时启动必须能从 `token_env` 读到非空值,否则拒绝启动 | | `server.allowed_origins` | 浏览器 `Origin` 白名单;`"*"` 放行全部;留空只允许 `localhost/127.0.0.1/[::1]` | | `upstream.base_url` | 必须是 `http(s)://host`,不允许内嵌凭据/查询串;配置里的 path 会拼在其路径之后 | | `upstream.headers` | 每个请求都带的静态头;禁止 CR/LF | | `upstream.auth` | `none` / `bearer` / `header`(配 `header_name`);只存环境变量名 | | `operation.path` | 必须 `/` 开头,支持 `{name}` 占位;禁止 `.`/`..`、`?`、`#`、`%`、空白 | | `operation.params[].in` | `path` / `query` / `header` / `body`,缺省 `query` | | `operation.body_param` | 指定哪个参数作为请求体;缺省时自动取唯一的 `in=body` 参数 | | `operation.input_schema` | 覆盖自动派生的 JSON Schema,仅影响展示,不影响实际绑定 | | `passthrough` | 受限通用工具;`allow_prefixes` 为空时启动失败(宁可不可用,也不当开放代理) | 工具名规则:`_`,大小写不敏感唯一;冲突、非法字符都会启动失败。`upstream.name` 必须匹配 `^[a-z][a-z0-9_-]{0,31}$`。 ### 参数如何变成请求 - `path`:按 `{name}` 替换,值经 `url.PathEscape`;值本身是 `.`/`..` 时拒绝。 - `query`:数组值展开为重复参数;数字/布尔按字面量渲染(`2` 而不是 `2.000000`)。 - `header`:参数名就是头名(允许 `X-Trace` 这类连字符写法,大小写不敏感去重);`Authorization`、`Cookie`、`Host`、`Content-Length` 等保留头一律拒绝,避免调用方绕过配置里的凭据。 - `body`:字符串原样发送(`text/plain`),其它类型 JSON 序列化(`application/json`);未声明 `schema.type` 的 body 参数接受任意 JSON 值。 - 未声明的参数、缺失的必填参数、类型不匹配、枚举越界都会返回 `isError: true` 的结果,并且**不会**发往上游。
[![][back-to-top]](#readme-top)
## 🧰 示例 `example/` 是一对可直接运行的 Python 示例,不用装任何依赖:一个对 MCP 一无所知的 notes REST 服务,加两个 MCP 客户端(Streamable HTTP 与 stdio),共用同一份配置。 ```bash # 1. 上游(终端 1):普通 REST 服务,与 MCP 无关。这些路由就是 mcpx 要发布的接口, # GET /healthz 表示它已就绪。 python3 example/python-http/server.py --port 18081 # 2. 网关(终端 2):在 127.0.0.1:18848/mcp 发布五个工具。开的是 bearer 鉴权, # 没有 token 会直接退出;这里除 mcpx 外没人校验该值,随便生成一个即可 # (`openssl rand -hex 24`)。 export MCPX_TOKEN=... ./bin/mcpx serve -c example/mcpx.example.yaml # 3a. Streamable HTTP 客户端(终端 3):带 Authorization: Bearer $MCPX_TOKEN 去 # POST /mcp(--token 默认取该变量),走完一个完整会话。 python3 example/python-http/client.py --url http://127.0.0.1:18848/mcp # 3b. stdio 客户端(终端 3,与 3a 二选一):不连终端 2,自己拉起 `mcpx stdio` # 子进程,一行一条 JSON-RPC;不需要 token —— stdin/stdout 就是传输通道。 python3 example/python-stdio/client.py --mcpx ./bin/mcpx --config example/mcpx.example.yaml ``` 两个客户端都走完整会话(握手、`tools/list`、`tools/call`、两种失败形态、`DELETE` 或 EOF),最后打印 `PASS`。配置在演示什么见 `example/README_cn.md`;连带断言与清理的一条命令版本: ```bash make example-test # 两种传输都跑;EXAMPLE_{UPSTREAM,GATEWAY}_PORT 可改端口 ``` 示例网关保留 `server.auth.scheme: bearer`(与容器同一姿态),启动必须有 `MCPX_TOKEN`;测试目标在变量未设置时会填一个只在本机回环用的占位值。
[![][back-to-top]](#readme-top)
## 🛳 部署 支持两条路径:自己用前置代理跑二进制,或用 Docker Compose 拉起容器栈。 ### `A` Docker Compose ```bash cp .env.example .env # 填 MCPX_TOKEN,例如 openssl rand -hex 24 docker compose up -d --build curl -s http://127.0.0.1:8848/healthz ``` 镜像为两阶段构建(静态二进制跑在 Alpine、非 root uid 10001),容器只读挂载 `deploy/mcpx.docker.yaml`。该配置监听 `0.0.0.0:8848`(容器内的 `127.0.0.1` 从宿主机不可达),并指向 compose 里的 `demo` 服务,所以新栈启动后就有一个可调用工具。换成自己的配置时用 `MCPX_CONFIG_FILE`,务必保留 `0.0.0.0` 监听地址。 端口默认只发布到宿主机 `127.0.0.1`;要改 `MCPX_BIND` 前请先加反向代理。缺少 `MCPX_TOKEN` 时进程打印 `authentication misconfigured` 后退出,但 `docker compose down` 仍可正常清理。 ### `B` 已发布镜像与二进制包 **已发布镜像。** 在 GitHub 上发布 release 会触发 `.github/workflows/release.yml`,把 `ariacompute/mcpx` 推送到 DockerHub:该 release 的 `:` 加最新的 `:latest`,同时覆盖 linux/amd64 与 linux/arm64。release 的版本与 commit 以构建参数注入镜像,所以镜像 tag 与 `/healthz` 报告的 `version@commit` 永远一致。工作流需要两个仓库 secret:`DOCKERHUB_USERNAME`、`DOCKERHUB_TOKEN`。 ```bash docker pull ariacompute/mcpx: # 例如 1.2.3(release tag 去掉 v) docker run --rm -e MCPX_TOKEN=$(openssl rand -hex 24) \ -p 127.0.0.1:8848:8848 ariacompute/mcpx:1.2.3 ``` **二进制包。** 同一个 release 还附带 linux/amd64、linux/arm64、darwin/amd64、darwin/arm64、windows/amd64 的独立压缩包,以及覆盖全部的单个 `SHA256SUMS.txt`: ```bash tar xzf mcpx-1.2.3-linux-amd64.tar.gz # mcpx + LICENSE ./mcpx version # mcpx 1.2.3 () ... ``` macOS 包**未做代码签名与公证**(未配置证书),下载后会被 Gatekeeper 隔离,可 `xattr -d com.apple.quarantine mcpx` 或直接源码构建。解包前先用 `sha256sum -c SHA256SUMS.txt`(macOS 上 `shasum -a 256 -c`)校验。
[![][back-to-top]](#readme-top)
## 🔐 安全模型 - 默认只监听 `127.0.0.1`;对外暴露请前置 nginx 并启用 `server.auth`。 - 路径永远由"配置里的 base 路径 + 配置里的模板"拼出:拒绝绝对 URL、`//host`、`..`、跨主机重定向;透传路径按**段**匹配前缀(`/api/v1` 不会放行 `/api/v1x`)。 - 请求体、响应体、单次调用都有上限与超时;响应被截断时结果里明确标注。 - 凭据只以环境变量名出现在配置中,运行时读取;日志与错误消息不打印 token,敏感头统一脱敏。 - 上游非 2xx 不抛协议错误,而是 `isError: true` + 状态码 + 截断后的正文,让模型自己决定下一步。 ### 明确不做 - 配置热重载(重启即可,避免读到半写文件) - 多租户与配额 - 请求缓存(网关不替上游做一致性决策) - 内置 TLS(交给前置 nginx) - 动态发现上游(工具集在启动时固定,`tools/list` 因此稳定可缓存)
[![][back-to-top]](#readme-top)
## 📊 错误语义 | 场景 | 返回 | | --- | --- | | JSON 解析失败 | `-32700`,HTTP 400 | | `jsonrpc` 不是 `2.0` / 缺 `method` | `-32600` | | 未知方法 | `-32601` | | 未知工具 / 坏游标 / 参数结构错误 | `-32602` | | 上游非 2xx、超时、越权路径、参数校验失败 | `tools/call` 的 `isError: true` 结果 | | 未鉴权 / Origin 拒绝 / 会话缺失 / 会话过期 / 请求过大 | HTTP 401 / 403 / 400 / 404 / 413 |
[![][back-to-top]](#readme-top)
## 🔨 开发 ```bash make build # bin/mcpx(-ldflags 注入 version/commit/date) make test # go test -race ./... make vet # go vet ./... make cover # 全仓覆盖率汇总(coverage.out,已 gitignore) make cover-check # 语句覆盖率不足 100% 时失败 make cover-html # 生成并在浏览器里看 HTML 报告 make check # 用仓库里的示例配置做一次体检,并列出工具 make run # 前台运行 HTTP make stdio # 前台运行 stdio make example-test # example/ 两种传输的端到端测试(见「示例」) ``` 目录: ``` cmd/mcpx CLI:serve / stdio / check / version internal/config 配置解码、默认值、校验 internal/tool 工具抽象、注册表、schema 派生与参数校验 internal/mcp JSON-RPC 信封、协议方法、错误码 internal/upstream HTTP 客户端、路径模板、operation、受限透传、脱敏 internal/gateway 配置 → 工具索引、调用分发 internal/transport httpsrv(会话/鉴权/202/DELETE)、stdio(行循环) tests httptest 端到端 example Python notes 服务 + HTTP/stdio 两种 MCP 客户端 scripts example/ 的一键测试脚本(bash) ```
[![][back-to-top]](#readme-top)
## 📝 许可证 MIT.
[![][back-to-top]](#readme-top)
## 🤝 工程约定 本仓库遵循 Harness Engineering: - [`AGENTS.md`](AGENTS.md):Agent 工程上下文入口与目录索引 - [`requirements.md`](requirements.md):需求规格(功能边界 / 异常 / 验收,人审后编码) - [`task.md`](task.md):实施清单
[![][back-to-top]](#readme-top)
---
Copyright © 2026 [ariacompute][org].
This project is [MIT](./LICENSE) licensed.
[org]: https://github.com/ariacompute [release-shield]: https://img.shields.io/github/v/release/ariacompute/mcpx?style=flat-square&labelColor=black&color=555 [release-link]: https://github.com/ariacompute/mcpx/releases [go-shield]: https://img.shields.io/github/go-mod/go-version/ariacompute/mcpx?style=flat-square&labelColor=black&color=555 [go-link]: ./go.mod [license-shield]: https://img.shields.io/github/license/ariacompute/mcpx?style=flat-square&labelColor=black&color=555 [license-link]: ./LICENSE [lastcommit-shield]: https://img.shields.io/github/last-commit/ariacompute/mcpx?style=flat-square&labelColor=black&color=555 [lastcommit-link]: https://github.com/ariacompute/mcpx/commits [transports-shield]: https://img.shields.io/badge/MCP_transports-HTTP%2Bstdio-555?style=flat-square&labelColor=black [transports-link]: #-接入方式 [tests-shield]: https://img.shields.io/badge/tests-go_test-555?style=flat-square&labelColor=black [tests-link]: ./Makefile [docs-shield]: https://img.shields.io/badge/docs-requirements-555?style=flat-square&labelColor=black [docs-link]: ./requirements.md [back-to-top]: https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square