api-mcp
by grj1997
README.md
# api-mcp
一个本地 stdio MCP Server:使用项目 token 或账号 Cookie 只读查询 YApi 项目、分类和接口文档,并通过“先预览、后应用”按分类生成确定性的 TypeScript/JavaScript 请求函数与类型。
运行时不会修改 YApi;它只会在明确执行 `api_apply_generation` 后写入本地受管文件。每个业务项目在自身根目录保存 YApi 配置和凭据,因此只需在 Codex 全局注册一次 MCP 服务。
## 环境要求
- Node.js 20 或更高版本
- 可访问目标 YApi 实例
- YApi 项目 token,或账号登录态中的 `_yapi_token` 与 `_yapi_uid` Cookie
## 本地使用(Codex)
```bash
cd E:\GitHub\api-mcp
pnpm install
pnpm build
```
在 `~/.codex/config.toml` 中只添加一次下面的全局配置。不要设置 `cwd`、`--config`、环境变量 token 或 `env_vars`:
```toml
[mcp_servers.api-mcp]
command = "node"
args = ['E:\GitHub\api-mcp\dist\cli.js', '--workspace-mode']
startup_timeout_sec = 20
tool_timeout_sec = 120
default_tools_approval_mode = "writes"
```
然后在每个业务项目根目录创建 `api-mcp.config.mjs` 或 `api-mcp.config.json`。从示例复制:
```bash
copy E:\GitHub\api-mcp\api-mcp.config.example.mjs api-mcp.config.mjs
```
每次源码变更后都执行 `pnpm build`,重启 Codex,并用 `/mcp` 确认服务已连接。
使用前检查:
- 只在 `~/.codex/config.toml` 配置一条带 `--workspace-mode`、不含 `cwd` 的 stdio 服务。
- 每个业务项目根目录只放一个 `api-mcp.config.mjs` 或 `.json`。
- 认证可使用 `projects[].token`,或服务器级 Cookie;不支持 `tokenEnv` 或 `env_vars`。
- 实际生成目录固定为 `<output.dir>/<project.id>`。
- 把真实的 `api-mcp.config.*` 加入业务仓库的 `.gitignore`。
- 先执行 preview,再执行 apply;计划有效期为十分钟。
服务只在 stdout 写 MCP 协议消息,诊断信息写 stderr,并对 token、Authorization、Cookie 等敏感值脱敏。
### 固定配置模式(可选)
如果只想将服务绑定到一个固定项目,可直接在终端运行:
```bash
node E:\GitHub\api-mcp\dist\cli.js --config /absolute/path/api-mcp.config.json
```
## 配置
在 `--workspace-mode` 下,服务仅在工具调用给出的业务项目根目录查找以下文件之一:`api-mcp.config.js`、`.mjs`、`.cjs` 或 `.json`。不能缺失,也不能同时存在多个。`.mjs` 和 ESM `.js` 使用 `export default`;`.cjs` 使用 `module.exports`;JSON 不执行代码。
`--workspace-mode` 要求 `workspaceRoot` 保持默认 `"."`(或解析后与调用的项目根目录相同),避免配置将生成目标带出当前项目。固定 `--config` 模式仍可按配置文件解析该字段。所有 `output.dir` 都相对有效 workspace,不能是绝对路径、包含 `..`,或通过符号链接越界。不同项目不能共用输出目录。项目 ID 必须是安全路径段,生成目录自动追加项目 ID。
```ts
type ProjectTokenAuth = {
projects: Array<{
id: string
token: string
categories?: { include?: 'all' | number[]; exclude?: number[] }
}>
auth?: never
discovery?: never
}
type AccountCookieAuth = {
projects?: never
auth: {
type: 'cookie'
cookie: string // 必须包含 _yapi_token 与 _yapi_uid
}
discovery?: {
include?: 'all' | number[] // YApi 数字项目 ID,默认 "all"
exclude?: number[] // 默认 []
}
}
interface YapiMcpConfig {
workspaceRoot?: string // 默认 "."
cacheTtlMs?: number // 默认 60_000
requestTimeoutMs?: number // 默认 10_000
servers: Array<({
id: string
url: string
output: {
dir: string
language?: 'ts' | 'js' // 默认 "ts"
aiManifest?: string | false // 默认 "api-manifest.json"
requestAdapter: { module: string; export?: 'default' | string }
dataKey?: string | string[]
queryArrayFormat?: 'brackets' | 'indices' | 'repeat' | 'comma' | 'json'
barrel?: string | false // TS 默认 "index.ts",JS 默认 "index.js"
}
} & (ProjectTokenAuth | AccountCookieAuth))>
}
```
推荐在业务项目根目录新建 `api-mcp.config.mjs`。下面是 TypeScript 输出的完整配置;将 `token` 替换为对应 YApi 项目的项目 token,并将 adapter 模块路径改为业务项目实际路径:
```js
export default {
workspaceRoot: '.',
servers: [
{
id: 'company-yapi',
url: 'https://yapi.example.com',
output: {
dir: 'src/generated',
language: 'ts',
aiManifest: 'api-manifest.json',
requestAdapter: {
module: '@/utils/request',
export: 'default',
},
dataKey: ['data'],
queryArrayFormat: 'brackets',
barrel: 'index.ts',
},
projects: [
{
id: 'web-api',
token: '你的 YApi 项目 token',
categories: { include: 'all', exclude: [] },
},
],
},
],
}
```
同一个 YApi 服务下的多个项目共用 `output` 与 adapter;每个项目只配置自己的 `id`、`token`、分类筛选。不同 server 可各自配置一套 `output`。`id` 在整个配置文件内必须唯一。
### Cookie 账号发现模式
如果不想逐个维护项目 token,可改为服务器级 Cookie:
```js
export default {
workspaceRoot: '.',
servers: [
{
id: 'company-yapi',
url: 'https://yapi.example.com',
auth: {
type: 'cookie',
cookie: '_yapi_token=替换为登录Cookie; _yapi_uid=替换为用户ID',
},
discovery: {
include: 'all',
exclude: [100, 200],
},
output: {
dir: 'src/generated',
language: 'ts',
aiManifest: 'api-manifest.json',
requestAdapter: { module: '@/utils/request', export: 'default' },
queryArrayFormat: 'brackets',
barrel: 'index.ts',
},
},
],
}
```
Cookie 模式会读取账号可见分组及其项目,并按 `discovery` 过滤。发现出的 MCP 项目标识就是 YApi 数字项目 ID,例如 `291`;输出目录为 `src/generated/project-291`。Cookie 与 `projects` 互斥,不能在同一个 server 中同时配置。
JavaScript 项目只需切换输出语言和 barrel 扩展名:
```js
output: {
dir: 'src/generated',
language: 'js',
aiManifest: 'api-manifest.json',
requestAdapter: { module: '@/utils/request', export: 'default' },
queryArrayFormat: 'brackets',
barrel: 'index.js',
}
```
可以省略 `language`、`aiManifest`、`queryArrayFormat` 和 `barrel`,默认值分别为 `'ts'`、`'api-manifest.json'`、`'brackets'`、TS 的 `'index.ts'` 或 JS 的 `'index.js'`。`barrel: false` 会禁用入口文件,`aiManifest: false` 会禁用清单。barrel 必须使用与语言相同的扩展名,manifest 必须是相对 `.json` 路径。
例如,`output.dir: 'src/generated'` 且项目 `id: 'web-api'` 时,生成位置为 `src/generated/web-api`。一个 server 的 `output` 由其全部 projects 共用,而每个 project 只保留自己的 token 和分类筛选。
token 和 Cookie 都只适合受信任的本地业务仓库;Cookie 代表整个账号权限,风险高于单项目 token。不要将真实配置提交到版本控制。应在 `.gitignore` 中忽略四种 `api-mcp.config.*` 文件名。运行时会对完整 Cookie、token、Authorization 和敏感响应字段脱敏。
`dataKey` 同时影响生成的响应类型和运行时 descriptor。例如 `dataKey: ["data", "items"]` 会让响应类型指向 schema 的 `data.items`,并由 request adapter 在实际响应中解包相同路径。路径无法从 schema 解析时,响应类型回退到 `unknown`,preview 返回警告。
## MCP 工具
- `api_list_projects({ workspaceRoot, checkConnection? })`:列出一个业务项目根目录中的安全项目元数据;可选择检查连接。
- `api_list_categories({ workspaceRoot, project, refresh? })`:列出配置过滤后的分类。
- `api_search_interfaces({ workspaceRoot, project, query?, categoryIds?, methods?, statuses?, limit?, cursor?, refresh? })`:搜索标题、路径、标签、方法和状态;`limit` 为 1–100。
- `api_resolve_url({ workspaceRoot, url, refresh? })`:直接解析已配置项目中的 YApi 分类或接口链接,返回内容及可传给 preview 的 scope。
- `api_get_interface({ workspaceRoot, project, interfaceId, refresh? })`:获取归一化接口详情。
- `api_preview_generation({ workspaceRoot, project, scope, includeDiff? })`:计算目标文件、hash、冲突、警告、过期时间及可选 diff,不写文件。
- `api_apply_generation({ planId })`:重新校验配置、YApi 源数据和目标 hash 后应用计划。
`workspaceRoot` 必须是 Codex 当前任务工作区的绝对路径;apply 根据 preview 创建的计划定位工作区,因此不能也不需要传递 `workspaceRoot`。
### 提问式使用
可以直接对 AI 说:
> 帮我看看 `https://yapi.example.com/project/291/interface/api/cat_4047` 的接口内容。
AI 会调用 `api_resolve_url`。分类 URL(`.../api/cat_<分类ID>`)会返回分类信息、接口摘要及 `{ "kind": "categories", "ids": [分类ID] }`;单接口 URL(`.../api/<接口ID>`)会返回完整请求/响应 schema 及对应的 interfaces scope。随后 AI 应先解释接口内容;只有在你明确确认后,才调用 `api_preview_generation`,并在你确认 preview 后调用 `api_apply_generation`。
URL 的服务地址和 YApi 数字项目 ID 必须匹配某个已配置 token,或属于 Cookie 账号可见且未被 `discovery` 排除的项目。项目不匹配、权限不足、被筛选排除或 URL 格式无效时会返回稳定错误。
`scope` 必须是以下三种形式之一:
```json
{ "kind": "interfaces", "ids": [101, 102] }
{ "kind": "categories", "ids": [10, 11] }
{ "kind": "project" }
```
选择单个接口仍会重新读取并生成它所属分类的完整 `category-<id>.ts`,不会意外覆盖同分类的其他接口。
## 生成与安全写入
默认 TypeScript 模式每个项目会生成:
- `request-types.ts`:受管的 descriptor、adapter 和 `YapiFileData<T>` 类型。
- `category-<id>.ts`:该分类的完整接口类型和请求函数。
- `index.ts`(可配置或禁用):只导出本次生成范围包含的受管文件。
- `api-manifest.json`:供 AI 和业务代码检索的稳定操作清单,可改名或设为 `false` 禁用。
设置 `output.language: 'js'` 时生成 ESM:`request-types.d.ts`、`category-<id>.js`、配套的 `category-<id>.d.ts`、`index.js` 与 `index.d.ts`。JS 不依赖 TypeScript 运行时;类型信息只存在于 `.d.ts`。barrel 的扩展名必须与语言一致。
业务开发时优先读取生成目录的 `api-manifest.json`:它列出每个接口的函数名、导入入口、请求/响应类型、参数部分和 YApi 链接。应导入其中的函数复用,而不是重新手写 HTTP 请求。
文件不包含时间戳;分类、接口与声明顺序稳定。函数名固定以 `api` 开头,并由路径组成:`GET /api/users/{id}` 生成 `apiUsersById`;HTTP method 不参与命名,路径仍冲突时追加 interface ID。
每个生成函数均为箭头函数,接收 descriptor 形式的 `requestData` 及可选 `...args`。基础 descriptor 会保留全部固定字段,随后以 `Object.assign(descriptor, requestData)` 合并调用数据,再将额外参数原样传给 adapter:
```ts
export const apiUsersById = (requestData, ...args) =>
requestAdapter(Object.assign(descriptor, requestData), ...args)
```
TypeScript 会生成对应的 `ApiUsersByIdRequestData` 类型:它保留 `YapiRequestDescriptor` 的字段,并将 `pathParams`、`query`、`headers`、`body` 收窄到接口文档定义的类型。
preview 生成随机 `planId`,默认 10 分钟有效。apply 不接受未经 preview 的写入,并会拒绝:
- 没有 managed marker 或 marker 不属于同项目/来源的目标文件;
- preview 后变化的 YApi 数据、配置或目标文件;
- workspace 外路径及越界符号链接。
不再出现在当前生成范围的旧分类文件只会列入 `staleFiles`,不会自动删除;barrel 也不再导出它们。TS/JS 切换产生的旧受管文件同样只报告为 stale,绝不自动删除。
常见稳定错误码包括 `CONFIG_INVALID`、`CONFIG_NOT_FOUND`、`CONFIG_AMBIGUOUS`、`WORKSPACE_INVALID`、`YAPI_HTTP_ERROR`、`YAPI_API_ERROR`、`YAPI_TIMEOUT`、`PLAN_EXPIRED`、`PLAN_STALE`、`PATH_OUTSIDE_WORKSPACE`、`UNMANAGED_FILE_CONFLICT` 和 `WRITE_PARTIAL`。
## Request adapter
业务侧 adapter 接收以下 descriptor,并返回解包后的响应:
```ts
interface YapiRequestDescriptor<TBody = unknown> {
method: string
path: string
pathParams?: Record<string, unknown>
query?: Record<string, unknown>
headers?: Record<string, unknown>
body?: TBody
requestBodyType: string
responseBodyType: string
dataKey?: string[]
queryArrayFormat: string
metadata: {
projectId: number
categoryId: number
interfaceId: number
title: string
yapiUrl: string
}
extraInfo: {
name: string // 接口名称
creator: string // 创建人
updatedAt: string // 中国标准时间,格式 YYYY-MM-DD HH:mm:ss
remark: string // 接口备注
}
}
type YapiRequestAdapter = <TResponse>(
descriptor: YapiRequestDescriptor,
...args: unknown[]
) => Promise<TResponse>
```
[`examples/request-adapter.ts`](./examples/request-adapter.ts) 提供了基于 `fetch` 的参考实现,包括 path 参数、五种 query 数组格式、JSON、表单、上传以及 `dataKey` 解包。生成器只导入配置指定的 adapter,不执行任何配置回调或外部命令。
## 开发与验证
```bash
pnpm install
pnpm lint
pnpm typecheck
pnpm run typecheck:examples
pnpm test
pnpm build
pnpm run pack:dry-run
```
测试覆盖双认证配置、敏感值脱敏、账号项目发现、YApi 错误/超时/分页/缓存、schema 推断、生成代码编译、preview/apply 冲突与 stale 文件策略,以及真实子进程 stdio 上的全部 MCP 工具。
使用 MCP Inspector 做交互式冒烟测试:
```bash
npx @modelcontextprotocol/inspector node ./dist/cli.js --workspace-mode
```
## 设计参考
- [yapi-to-typescript handbook](https://fjc0k.github.io/yapi-to-typescript/handbook/):多项目配置、类型生成和统一请求适配器设计。
- [YApi OpenAPI](https://github.com/YMFE/yapi/blob/master/docs/openapi-doc.md):只读项目与接口文档端点。
- [MCP TypeScript SDK server guide](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/docs/server.md):stdio Server 与 tools 接口。
## License
MIT