Skip to main content
Glama
yourco-mcp

yourco-mcp

yourco-mcp — 面向 AI Registry 的 MCP SDK

构建一个 MCP 服务器,其工具元数据存放在 AI Registry 中,并在运行时热重载。你只需编写处理函数;其余一切——描述、schema、受众、按工具划分的 scope、暴露方式——都由管理员在 registry UI 中管理,并在毫秒级时间内到达正在运行的服务器,无需重新部署。

Registry (control plane)          Your server (data plane, this SDK)
  admins edit metadata   ──push──▶  in-memory manifest ──▶ answers MCP calls
  UI / RBAC / versions              your handlers      ──▶ your business logic

你的服务器永远不会因 registry 而阻塞:所有 MCP 流量都从内存中提供,即使 registry 宕机,你的服务器也能继续运行(参见「韧性」)。

安装

pip install "yourco-mcp[server,redis] @ git+https://github.com/amitmohapatra/mcp-sdk.git"

在生产环境固定一个 tag(...mcp-sdk.git@v0.1.0)。可选依赖:server 附带 uvicorn 以支持 server.run();redis 启用 Redis pub/sub(生产环境推荐——没有它时 SDK 会自动回退到 registry 的 SSE 流)。

Related MCP server: dynamic-mcp

快速开始——完整集成

import os
from yourco_mcp import ProductServer

server = ProductServer(
    registry_url="https://registry.yourco.com",
    product_key="billing",                    # your product's key in the registry
    api_key=os.environ["REGISTRY_API_KEY"],   # issued in the UI: Manage -> SDK API keys
)

@server.tool("get_invoice")                   # bound by NAME — metadata comes from the registry
async def get_invoice(ctx, invoice_id: str, max_results: int = 100):
    return {"invoice_id": invoice_id, "max_results": max_results}

if __name__ == "__main__":
    server.run(port=8080)                     # stateless MCP over HTTP at POST /mcp

注意缺失了什么:没有描述、没有 JSON schema、没有 Redis 配置、没有认证样板代码。registry 拥有元数据;你的代码拥有行为。如果 registry 列出了某个你没有对应处理函数的工具,它会从 tools/list 中被排除并给出警告(故障安全,绝不故障崩溃)。

认证——你拥有身份,SDK 负责执行

每个产品为自己的工具处理认证。 SDK 永远不会看到你的密码、密钥或令牌格式——你只需实现一个方法:传入 headers,返回 user。

from yourco_mcp import ProductServer, AuthProvider, AuthUser

class MyProductAuth(AuthProvider):
    async def authenticate(self, headers) -> AuthUser | None:
        token = headers.get("authorization", "").removeprefix("Bearer ")
        claims = my_jwt_verify(token)          # YOUR auth: your JWT lib, your OAuth
        if not claims:                         # introspection, your session store
            return None
        return AuthUser(id=claims["sub"], scopes=claims.get("scopes", []))

server = ProductServer(..., auth=MyProductAuth())

普通的 async def fn(headers) -> AuthUser | None 也可以。

框架的职责止于接口。 authenticate 内部发生什么完全是你自己的业务逻辑——Firebase、Auth0、Keycloak、你自己的 JWT 签发器、会话表、mTLS、LDAP,任何方案都可以。SDK 从不导入、捆绑或偏向任何身份系统;它只消费你返回的 AuthUser。下面的示例恰好使用 Firebase,纯粹是为了说明:

import asyncio
import firebase_admin
from firebase_admin import auth as fb_auth
from yourco_mcp import AuthProvider, AuthUser

firebase_admin.initialize_app()                      # your service account creds

class FirebaseAuth(AuthProvider):
    async def authenticate(self, headers) -> AuthUser | None:
        token = headers.get("authorization", "").removeprefix("Bearer ").strip()
        try:                                          # verify_id_token is blocking:
            decoded = await asyncio.to_thread(fb_auth.verify_id_token, token)
        except Exception:
            return None
        roles = await my_db.fetch_roles(decoded["uid"])       # YOUR roles table
        return AuthUser(id=decoded["uid"],
                        scopes=[f"role:{r}" for r in roles],   # roles become scopes
                        claims=decoded)

然后在装饰器上按工具要求角色:

@server.tool("refund_payment", scopes=["role:finance-admin"])
async def refund_payment(ctx, payment_id: str, amount: float): ...

代码声明的 scopes 与 registry 设置的 required_scopes 以并集方式强制执行——任何一方都可以收紧工具,但任何一方都不能放宽另一方。内置提供者:ApiKeyAuthProvider({key: {...}})、StaticTokenProvider({token: {...}}) 和 NoAuth()——这是对真正开放服务器的显式退出选项(任何东西都不会意外开放)。

一句话概括契约: 发现始终公开;执行的一切都是你产品可插拔的选择。

  • tools/list(以及 initialize/ping)永远不需要认证——这是一个不变量,而非默认值。网关和目录(例如 Bifrost)可以在零凭据的情况下枚举每个产品的工具。匿名调用者看到的是默认受众的视图。

  • 认证是可插拔的:你的 AuthProvider——或者对完全开放的服务器使用 NoAuth()(这是一个显式选择,绝非意外)。

  • 授权是可插拔的:你的 scopes 由你的认证系统签发,按工具与 registry 设置的 required_scopes 进行校验,再加上你的 @server.authorize 钩子来处理 scopes 无法表达的任何情况。

  • 按工具的执行认证由你决定:

@server.tool("ping", public=True)          # executes without auth
async def ping(ctx): ...

@server.tool("refund_payment")             # gated (the default)
async def refund(ctx, payment_id: str): ...

安全规则:如果管理员在 registry 中为某个工具附加了 required_scopes,那么即使代码将其标记为公开,仍然需要认证——运行时收紧始终优先;代码侧的退出选项永远不能覆盖它。

一旦你的验证器就位,SDK 就会强制执行——你无需编写以下任何内容:

层级

行为

你的配置方式…

默认策略

tools/list 开放;tools/call 需要已认证用户(否则返回 -32001)

从不(或替换为 policy=AllGatedPolicy())

受众授权

仅当用户的 scopes 包含 audience:internal 时才认可 x-tool-audience: internal;其他人被静默降级到默认受众

通过你的认证系统签发的 scopes

按工具 scopes

registry 中带有 required_scopes: ["payments:write"] 的工具会拒绝没有该 scope 的调用者(-32003)——管理员可在运行时收紧,无需重新部署

在 registry UI 中

业务规则

scope 检查之后的任意代码检查

@server.authorize 钩子

@server.authorize
async def gate(user, tool, args) -> bool:
    return not (tool == "refund_payment" and args["amount"] > 10_000
                and "payments:admin" not in user.scopes)

公司 scope 约定(全组织统一一次):

  • audience:<key> — 授予某个受众(例如内部代理使用 audience:internal)

  • <domain>:<action> — 管理员在 registry 中设置的按工具要求(例如 payments:write、invoices:read)

受众、隐藏参数、固定值

管理员可以按受众(例如 external 与 internal)以不同方式暴露同一个工具:不同的描述、额外的仅内部参数,或者对某个受众隐藏的参数,改为向你的处理函数发送固定值——调用者永远无法看到或覆盖它。你的处理函数只需用默认值声明该参数;SDK 会根据调用者的受众 schema 校验参数、剥离未知参数,并在你的代码运行之前注入固定值。

@server.tool("charge_card")
async def charge_card(ctx, card_id: str, amount: float, currency: str = "USD"):
    # external callers can't even see `currency` — the SDK always passes the
    # admin-fixed value; internal callers control it. ctx.audience tells you which.
    ...

ctx 为你提供 ctx.user(即 AuthUser)、ctx.audience 和 ctx.tool。

实时更新——registry 的保存如何到达你的服务器

  1. 管理员在 registry 中保存 → 一个事务递增产品的序列号,并发布携带已解析视图的事件。

  2. 你的服务器(自启动起即订阅——如果你的产品配置了 Redis 则使用 Redis,否则使用 registry 的 SSE 流;manifest 会告诉 SDK 使用哪种方式)接收该事件。

  3. 序列检查:按顺序的下一个 → 作为原子 manifest 交换应用;过期 → 忽略;有缺口 → 完全重新获取并协调。收敛是有保证的。

  4. 下一次 tools/list/tools/call 将提供新的元数据。典型延迟:个位数毫秒(Redis)到几百毫秒(SSE)。

韧性

  • Registry 宕机 → 你的服务器继续从内存提供服务,包括最新应用的更新。registry 是控制平面,绝不是运行时依赖。

  • Registry 宕机时的冷启动 → 从 SDK 自动维护的最近一次良好快照提供服务(缓存在 ~/.cache/yourco-mcp/ 下;如果你的运行时需要,可以用 YOURCO_MCP_CACHE_DIR 覆盖该位置)。

  • Pub/sub 中断 → 自动回退到廉价的带指数退避的条件轮询(ETag/304),同时持续尝试重新订阅——更新持续流动,只是慢几秒。

  • 错误/格式损坏的更新 → 记录日志、忽略、重新同步;完好的 manifest 永远不会被损坏的 manifest 替换。

  • 无状态 HTTP → 在任何负载均衡器后面运行 N 个副本;无需粘性会话。

新产品团队检查清单

  1. 请 registry 管理员将你的产品接入并给你一个 API key。

  2. pip install(见上文),在部署环境中设置 REGISTRY_API_KEY。

  3. 为你产品拥有的工具编写处理函数(名称必须与 registry 匹配)。

  4. 将你现有的认证接入一个 AuthProvider.authenticate 方法。

  5. 决定你的哪些令牌携带 audience:* scopes(内部代理等)。

  6. server.run() — 用 curl localhost:8080/healthz 和 MCP tools/list 验证。在 registry UI 中编辑一个描述,然后观察它实时变化。

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    A flexible, extensible framework for building MCP servers with API key authentication, user management, and dynamic tool sharing.
    7 npm
    11
    MIT
  • A
    license
    B
    quality
    C
    maintenance
    Dynamic MCP server for Node.js enabling runtime tool creation, management, and execution in isolated sandboxes (Docker or Node).
    8
    8 npm
    1
    MIT
  • F
    license
    Not graded
    quality
    D
    maintenance
    A universal MCP server for registering internal, external, and OpenAPI-based APIs as MCP tools. It exposes them to MCP clients via Streamable HTTP and provides admin portal, RBAC/session auth, credential injection, and audit logging.
    -
  • A
    license
    Not graded
    quality
    B
    maintenance
    Provides a reusable MCP server foundation with explicit tool registration, operation-mode separation, scope-based permissions, structured results, and built-in health/status tools.
    MIT