谈谈 API 接口自描述与权限配置系统

2026年05月16日0 次阅读0 人喜欢
API权限系统MCPTypeScript

起因

说实话,之前项目的 API 权限管理挺乱的。接口描述散落在 api-registry.ts 里,每次新增接口都得去那个文件手动注册,漏了就出问题。MCP 工具的开关也是硬编码的,想动态调整根本没戏。

最近花时间搞了一套 API 接口自描述系统,把这些问题一并解决了。

方案设计

核心思路很简单:每个 route.ts 文件自己声明自己

typescript 复制代码
// src/app/api/post/create/route.ts
import type { ApiDescriptor } from '@/types/api-descriptor';
import { POST_CREATE } from '@/constants/permissions';

export const descriptor: ApiDescriptor = {
  code: 'post_create',
  name: '创建文章',
  module: 'post',
  method: 'POST',
  permissionCode: POST_CREATE,
  inputSchema: {
    type: 'object',
    properties: {
      title: { type: 'string', description: '文章标题' },
      content: { type: 'string', description: '文章内容' },
    },
    required: ['title', 'content'],
  },
};

export async function POST(request: NextRequest) {
  // 原有逻辑不变
}

一个文件有多个接口?用 xxxDescriptor 命名就行:

typescript 复制代码
export const getDescriptor: ApiDescriptor = { /* GET */ };
export const updateDescriptor: ApiDescriptor = { /* PUT */ };
export const deleteDescriptor: ApiDescriptor = { /* DELETE */ };

扫描脚本

写了个脚本 pnpm sync:api-registry,扫描所有 route.ts 文件,自动同步到数据库。

一开始想用字符串解析的方式提取 descriptor,结果发现根本没法处理导入的权限码常量。后来改成动态 import,直接读取模块导出,代码从 238 行缩减到 155 行,还省了一堆重复定义。

typescript 复制代码
// 直接 import 模块,读取导出
const module = await import(filePath);
if (module.descriptor?.code) {
  results.push(module.descriptor);
}

后台配置

在现有的角色管理页面加了 MCP 工具配置区域。现在可以:

  • 给角色分配接口权限
  • 设置数据权限范围(全部/仅自己)
  • 动态开关哪些接口暴露给 MCP

不用改代码,后台勾选就行。

数据库变更

tb_api_registry 表加了两个字段:

  • auto_discovered:标记是自动扫描还是手动添加
  • synced_at:最后同步时间

效果

现在新增接口只需要:

  1. 在 route.ts 里写好 descriptor
  2. 跑一下 pnpm sync:api-registry
  3. 后台勾选权限和 MCP 配置

不用再去 api-registry.ts 手动注册了,也不会漏掉。


这套系统其实不复杂,但解决了实际痛点。有时候就是这样,一个小改动能让维护成本降不少。

加载评论中...