细粒度权限控制终极指南:Neon MCP Server 的 Grant Context 与工具过滤机制源码解读

【免费下载链接】mcp-server-neon

MCP server for interacting with Neon Management API and databases

项目地址: https://gitcode.com/gh_mirrors/mc/mcp-server-neon
点击查看 免费下载

Neon MCP Server 是连接自然语言与 Neon 云数据库的桥梁。而 Grant Context 与工具过滤机制正是它的权限引擎:通过几个 URL 查询参数,即可把 AI 智能体可用的一百多个数据库工具,精确裁剪到只读、单项目、指定角色甚至只剩几个文档工具——这就是细粒度权限控制的完整实现。

Grant Context 是什么:权限上下文的核心定义

Grant Context 是一个轻量的"权限凭证"结构体,记录了本次连接被允许做什么。它的完整定义只有四个字段:

export type GrantContext = {
  projectId: string | null;   // 锁定单个项目,null 表示全部项目
  scopes: ScopeCategory[] | null; // 允许的工具分类,null 表示全部
  unknownCategories?: string[]; // 无法识别的分类(用于提示用户)
  roleName?: string;          // 固定 SQL 执行所用的 Postgres 角色
};

(源码:grant-context.ts)

两个 null 语义是理解整个机制的钥匙:

  • scopes: null 表示不限制,全部 13 个工具分类都开放;
  • projectId: null 表示不限定项目,可访问账号下所有项目。

13 个工具分类(scope categories)覆盖了 Neon 的主要能力域:

projects、branches、endpoints、snapshots、schema、querying、neon_auth、data_api、observability、docs、functions、storage、realtime

完整定义见 grant-context.ts,所有工具与其分类的对应关系可以查阅 hosted-tools-catalog.md。

💡 当没有任何配置时,系统回退到全量放行的默认值:

export const DEFAULT_GRANT: GrantContext = {
  projectId: null,
  scopes: null,
};

(grant-context.ts)

通过 URL 查询参数配置 Grant Context

Grant Context 的入口是 MCP 服务器 URL 上的 4 个查询参数,新手只需记住一张表:

参数作用示例
readonly启用只读模式(true/false)?readonly=true
category限定工具分类,可重复或逗号分隔?category=querying&category=schema
projectId将全部操作限定到单个项目?projectId=proj-123
roleName所有 SQL 固定以该 Postgres 角色执行?roleName=app_reader

参数解析统一由 resolveGrantFromSearchParams 完成,同时支持 ?category=a&category=b 和 ?category=a,b 两种写法(grant-context.ts)。两个容易踩坑的细节:

  • 无效分类不报错而是被静默过滤,并记入 unknownCategories,最终会以一条提示告知用户哪些值被忽略、合法值有哪些;
  • roleName 的校验则是反向的——固定角色是一种"收窄",所以空值、重复或非法字符会直接抛出 InvalidRoleNameError 返回 400,绝不悄悄降级为"不固定"(grant-context.ts)。

roleName 的完整规则(合法字符、LOGIN 要求、OAuth 与 API key 两种认证下的行为差异)在 README.md 中有权威说明。

🔐 值得注意的是:category=docs 且不带 projectId 是一个特殊组合,会触发无需登录的匿名文档端点,由 isDocsOnlyRequest 判定(grant-context.ts)。

工具过滤三层管线:从 100+ 工具到最小可用集

拿到 Grant Context 后,核心的过滤逻辑集中在 grant-filter.ts 中,由 filterToolsForGrant 编排三层管线:

let filtered = applyScopeCategoryFilter(tools, grant.scopes);
filtered = applyProjectScopeFilter(filtered, grant);
if (grant.roleName !== undefined) {
  filtered = filtered.filter(
    (tool) => !PINNED_ROLE_WITHHELD_TOOLS.has(tool.name),
  );
}

(grant-filter.ts)

第一层:按 category 过滤

scopes 为 null 时全量放行;为指定列表时,工具的 scope 字段必须命中列表;空数组则只剩三个"永远可用"工具:

export const ALWAYS_AVAILABLE_TOOLS: ReadonlySet<string> = new Set([
  'search',
  'fetch',
  'send_feedback',
]);

这三个是智能体导航与用户反馈工具,任何收窄配置下都保留,避免用户被完全锁死(grant-filter.ts)。

第二层:按 projectId 过滤

一旦 grant 锁定了项目,只有 projectScoped: true 的工具才能存活——list_projects、create_project 这类跨项目管理工具会整体消失,project_id 参数则被服务端自动注入:配置了项目的连接直接省略它;显式传了但与授权项目不匹配则抛错,杜绝越权(grant-filter.ts)。

第三层:只读过滤

getFilteredTools 在此之上再叠一层:readonly 生效时,只保留 readOnlySafe: true 的工具。create_branch、delete_project、run_sql_transaction、get_connection_string 等写入类工具全部被剔除;而 run_sql 保留,但每条查询都包裹在 READ ONLY 事务中执行。

(grant-filter.ts)

固定 Postgres 角色的额外保护

roleName 的作用不仅是"换个身份跑 SQL"——它决定了 Postgres 授权(GRANT)本身成为第二道闸门。为了配合这一限制,源码主动隐藏了 get_connection_string 工具:

export const PINNED_ROLE_WITHHELD_TOOLS: ReadonlySet<string> = new Set([
  'get_connection_string',
]);

原因写得很直白:该工具存在就是为了把角色凭证交出去,若暴露它,调用方就能绕过服务器的只读事务、以固定角色身份在服务器之外执行任意 SQL(grant-filter.ts)。

两种授权页面:可编辑模式与固定授权

📄 OAuth 授权页面有两种形态,与 URL 是否带参数直接对应:

  • 默认 URL(可编辑模式):授权页提供"全部项目 / 单个项目"单选、Postgres 角色输入框、13 个工具分类勾选(实时显示"13/13 selected · 126 tools available")以及 Read only / Read and write 权限选择;
  • 参数化 URL(固定模式):页面只展示 URL 中已定好的项目、分类与权限供确认,并提示"To change this access, update the connection URL and authorize again."。

Neon MCP Server 参数化 URL 的只读授权确认页面,展示固定 Grant Context 下的项目与工具分类

Neon MCP Server 默认 URL 的可编辑授权页面,可自选项目范围、Postgres 角色与工具分类

Neon MCP Server 授权页面中固定 Postgres 角色为 app_reader 并选择读写权限的效果

授权页面的工具清单预览同样走 grant 过滤逻辑(consent-tools.ts),所以用户看到的"126 tools available"与实际生效的工具集完全一致。

⚠️ 安全设计要点:token 签发后,修改 URL 不会扩大或改变已有 token 的权限——想变更授权必须重新授权。在 route.ts 中,OAuth token 的 grant 只从令牌存储读取(resolveGrantFromToken),若存储的 grant 无法解析,认证直接失败,而不是回退到 URL 参数或 API key 路径(route.ts)。

无认证预览:用 list-tools 端点验证你的配置

改完 URL 参数后,不必连接智能体就能验证工具可见性——/api/list-tools 是一个无需认证的无状态预览端点:

curl "https://mcp.neon.tech/api/list-tools?readonly=true&category=querying"

响应包含 grant(实际生效的权限上下文)、readOnly、notices(服务端级别的安全提示,如只读模式声明)、warnings(边界情况警告,例如"未设置任何有效分类,仅剩 search/fetch/send_feedback")以及完整的 tools 数组(route.ts)。

设计亮点小结

  • 默认全开,显式收窄:null 即"全部",每个参数都在做减法,符合最小惊讶原则;
  • 收窄失败必须显式:非法 roleName 直接 400、token grant 损坏直接拒绝认证,"宁可报错也不悄悄放宽";
  • 权限提示集中下发:只读声明、破坏性工具警告等 Notices 作为顶层字段一次性返回,而不是复制进每个工具描述,为智能体省下了大量 token(route.ts);
  • 过滤逻辑单点维护:MCP 协议路径、list-tools 预览端点、OAuth 授权页面三处共用同一套 grant 过滤代码,权限语义天然一致。

延伸阅读:关键模块路径

【免费下载链接】mcp-server-neon

MCP server for interacting with Neon Management API and databases

项目地址: https://gitcode.com/gh_mirrors/mc/mcp-server-neon
点击查看 免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值