swaggertools-mcp 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +3 -0
- package/README.md +98 -0
- package/README.zh-CN.md +98 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +243 -0
- package/dist/openapi-service.d.ts +62 -0
- package/dist/openapi-service.js +578 -0
- package/dist/tool-types.d.ts +143 -0
- package/dist/tool-types.js +1 -0
- package/docs/swagger-mcp-design.md +243 -0
- package/package.json +29 -0
- package/src/index.ts +290 -0
- package/src/openapi-service.ts +719 -0
- package/src/tool-types.ts +178 -0
- package/tsconfig.json +17 -0
package/.env.example
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# swagger-mcp
|
|
2
|
+
|
|
3
|
+
English | [中文](./README.zh-CN.md)
|
|
4
|
+
|
|
5
|
+
MCP server for loading and querying OpenAPI/Swagger documents.
|
|
6
|
+
It supports fuzzy operation matching when users provide incomplete paths or keywords.
|
|
7
|
+
|
|
8
|
+
## Features
|
|
9
|
+
|
|
10
|
+
- Load remote OpenAPI 3.x docs (JSON/YAML)
|
|
11
|
+
- In-memory document caching with TTL
|
|
12
|
+
- List/filter operations by method/tag/keyword
|
|
13
|
+
- Fuzzy resolve operations from partial paths
|
|
14
|
+
- Inspect full request/response details
|
|
15
|
+
- Inspect `components.schemas` and where they are used
|
|
16
|
+
|
|
17
|
+
## Tools
|
|
18
|
+
|
|
19
|
+
- `openapi.load`
|
|
20
|
+
- `openapi.list_operations`
|
|
21
|
+
- `openapi.resolve_operation`
|
|
22
|
+
- `openapi.get_operation`
|
|
23
|
+
- `openapi.get_schema`
|
|
24
|
+
|
|
25
|
+
## Requirements
|
|
26
|
+
|
|
27
|
+
- Node.js 18+
|
|
28
|
+
- pnpm 10+
|
|
29
|
+
|
|
30
|
+
## Quick Start
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pnpm install
|
|
34
|
+
pnpm dev
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Build and run:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pnpm build
|
|
41
|
+
pnpm start
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Codex MCP Config Example
|
|
45
|
+
|
|
46
|
+
```json
|
|
47
|
+
{
|
|
48
|
+
"mcpServers": {
|
|
49
|
+
"swagger-mcp": {
|
|
50
|
+
"command": "node",
|
|
51
|
+
"args": ["D:\\workspace\\demo\\mcp\\swagger-mcp\\dist\\index.js"],
|
|
52
|
+
"cwd": "D:\\workspace\\demo\\mcp\\swagger-mcp"
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
If you are actively developing this repo, you can also run with `pnpm dev`.
|
|
59
|
+
|
|
60
|
+
## Environment Variables
|
|
61
|
+
|
|
62
|
+
- `OPENAPI_DEFAULT_URL` or `OPENAPI_URL`
|
|
63
|
+
Default OpenAPI URL used when `openapi.load` is called without `url`.
|
|
64
|
+
- `OPENAPI_DOC_TTL_MS` (default: `600000`)
|
|
65
|
+
Cache TTL in milliseconds.
|
|
66
|
+
- `ALLOW_PRIVATE_NETWORK` (default: `false`)
|
|
67
|
+
Set to `true` if your OpenAPI endpoint is in private/internal network.
|
|
68
|
+
- `OPENAPI_ENV_FILE`
|
|
69
|
+
Optional explicit `.env` file path (highest priority).
|
|
70
|
+
- `OPENAPI_ENV_DIR`
|
|
71
|
+
Optional directory that contains `.env`.
|
|
72
|
+
|
|
73
|
+
Env file load order:
|
|
74
|
+
|
|
75
|
+
1. `OPENAPI_ENV_FILE`
|
|
76
|
+
2. `OPENAPI_ENV_DIR/.env`
|
|
77
|
+
3. `process.cwd()/.env`
|
|
78
|
+
4. `INIT_CWD/.env` (if runtime provides it)
|
|
79
|
+
|
|
80
|
+
## `docId: "default"` Behavior
|
|
81
|
+
|
|
82
|
+
- If a document has already been loaded, `default` maps to the latest loaded `docId`.
|
|
83
|
+
- If no document is loaded yet, server auto-loads from `OPENAPI_DEFAULT_URL` / `OPENAPI_URL`.
|
|
84
|
+
|
|
85
|
+
## Example `.env`
|
|
86
|
+
|
|
87
|
+
```env
|
|
88
|
+
OPENAPI_DEFAULT_URL=http://dev.manage.zw.uav.sczlcq.com/v3/api-docs
|
|
89
|
+
ALLOW_PRIVATE_NETWORK=true
|
|
90
|
+
# OPENAPI_DOC_TTL_MS=600000
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Troubleshooting
|
|
94
|
+
|
|
95
|
+
- Error: `Document not found: default`
|
|
96
|
+
- Ensure `.env` is readable from the server runtime working directory.
|
|
97
|
+
- Ensure `OPENAPI_DEFAULT_URL` or `OPENAPI_URL` is set.
|
|
98
|
+
- Restart MCP server after changing `.env`.
|
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# swagger-mcp
|
|
2
|
+
|
|
3
|
+
[English](./README.md) | 中文
|
|
4
|
+
|
|
5
|
+
用于加载和查询 OpenAPI/Swagger 文档的 MCP 服务。
|
|
6
|
+
支持在用户只提供部分路径或关键词时进行接口模糊匹配。
|
|
7
|
+
|
|
8
|
+
## 功能特性
|
|
9
|
+
|
|
10
|
+
- 加载远程 OpenAPI 3.x 文档(JSON/YAML)
|
|
11
|
+
- 基于内存的文档缓存(TTL)
|
|
12
|
+
- 按 method/tag/关键词筛选接口
|
|
13
|
+
- 通过不完整路径进行接口模糊解析
|
|
14
|
+
- 查看接口完整请求/响应结构
|
|
15
|
+
- 查看 `components.schemas` 以及被哪些接口引用
|
|
16
|
+
|
|
17
|
+
## 工具列表
|
|
18
|
+
|
|
19
|
+
- `openapi.load`
|
|
20
|
+
- `openapi.list_operations`
|
|
21
|
+
- `openapi.resolve_operation`
|
|
22
|
+
- `openapi.get_operation`
|
|
23
|
+
- `openapi.get_schema`
|
|
24
|
+
|
|
25
|
+
## 环境要求
|
|
26
|
+
|
|
27
|
+
- Node.js 18+
|
|
28
|
+
- pnpm 10+
|
|
29
|
+
|
|
30
|
+
## 快速开始
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pnpm install
|
|
34
|
+
pnpm dev
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
构建并运行:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pnpm build
|
|
41
|
+
pnpm start
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Codex MCP 配置示例
|
|
45
|
+
|
|
46
|
+
```json
|
|
47
|
+
{
|
|
48
|
+
"mcpServers": {
|
|
49
|
+
"swagger-mcp": {
|
|
50
|
+
"command": "node",
|
|
51
|
+
"args": ["D:\\workspace\\demo\\mcp\\swagger-mcp\\dist\\index.js"],
|
|
52
|
+
"cwd": "D:\\workspace\\demo\\mcp\\swagger-mcp"
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
如果你在本仓库内联调,也可以用 `pnpm dev` 启动。
|
|
59
|
+
|
|
60
|
+
## 环境变量
|
|
61
|
+
|
|
62
|
+
- `OPENAPI_DEFAULT_URL` 或 `OPENAPI_URL`
|
|
63
|
+
当 `openapi.load` 未传 `url` 时,使用该默认地址。
|
|
64
|
+
- `OPENAPI_DOC_TTL_MS`(默认:`600000`)
|
|
65
|
+
文档缓存时长(毫秒)。
|
|
66
|
+
- `ALLOW_PRIVATE_NETWORK`(默认:`false`)
|
|
67
|
+
如果 OpenAPI 地址是内网地址,需设为 `true`。
|
|
68
|
+
- `OPENAPI_ENV_FILE`
|
|
69
|
+
可选,显式指定 `.env` 文件路径(最高优先级)。
|
|
70
|
+
- `OPENAPI_ENV_DIR`
|
|
71
|
+
可选,指定包含 `.env` 的目录。
|
|
72
|
+
|
|
73
|
+
`.env` 加载顺序:
|
|
74
|
+
|
|
75
|
+
1. `OPENAPI_ENV_FILE`
|
|
76
|
+
2. `OPENAPI_ENV_DIR/.env`
|
|
77
|
+
3. `process.cwd()/.env`
|
|
78
|
+
4. `INIT_CWD/.env`(如果运行时提供)
|
|
79
|
+
|
|
80
|
+
## `docId: "default"` 行为
|
|
81
|
+
|
|
82
|
+
- 如果服务中已加载过文档,`default` 会映射到最近一次加载的 `docId`。
|
|
83
|
+
- 如果尚未加载文档,服务会尝试使用 `OPENAPI_DEFAULT_URL` / `OPENAPI_URL` 自动加载。
|
|
84
|
+
|
|
85
|
+
## `.env` 示例
|
|
86
|
+
|
|
87
|
+
```env
|
|
88
|
+
OPENAPI_DEFAULT_URL=http://dev.manage.zw.uav.sczlcq.com/v3/api-docs
|
|
89
|
+
ALLOW_PRIVATE_NETWORK=true
|
|
90
|
+
# OPENAPI_DOC_TTL_MS=600000
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## 常见问题
|
|
94
|
+
|
|
95
|
+
- 错误:`Document not found: default`
|
|
96
|
+
- 确认服务运行目录可读取到 `.env`
|
|
97
|
+
- 确认已设置 `OPENAPI_DEFAULT_URL` 或 `OPENAPI_URL`
|
|
98
|
+
- 修改 `.env` 后重启 MCP 服务
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { config as dotenvConfig } from 'dotenv';
|
|
5
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
6
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
7
|
+
import * as z from 'zod/v4';
|
|
8
|
+
import { OpenApiRegistry } from './openapi-service.js';
|
|
9
|
+
const loadedEnvFiles = loadEnvFiles();
|
|
10
|
+
const server = new McpServer({
|
|
11
|
+
name: 'swagger-mcp',
|
|
12
|
+
version: '1.0.0',
|
|
13
|
+
});
|
|
14
|
+
const registry = new OpenApiRegistry();
|
|
15
|
+
server.registerTool('openapi.load', {
|
|
16
|
+
description: 'Load and index a remote OpenAPI document URL.',
|
|
17
|
+
inputSchema: {
|
|
18
|
+
url: z.string().url().optional().describe('OpenAPI JSON/YAML URL. If omitted, read from .env'),
|
|
19
|
+
headers: z.record(z.string(), z.string()).optional().describe('Optional HTTP headers'),
|
|
20
|
+
forceRefresh: z.boolean().optional().describe('Bypass cache and reload'),
|
|
21
|
+
},
|
|
22
|
+
}, async ({ url, headers, forceRefresh }) => {
|
|
23
|
+
try {
|
|
24
|
+
const finalUrl = url ?? getDefaultOpenApiUrl();
|
|
25
|
+
if (!finalUrl) {
|
|
26
|
+
return failure({
|
|
27
|
+
code: 'VALIDATION_ERROR',
|
|
28
|
+
message: `Missing OpenAPI URL. Provide url input or set OPENAPI_DEFAULT_URL/OPENAPI_URL in .env (searched: ${loadedEnvFiles.join(', ') || 'none'})`,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
const result = await registry.load(finalUrl, headers, forceRefresh ?? false);
|
|
32
|
+
return success({
|
|
33
|
+
docId: result.docId,
|
|
34
|
+
title: result.title,
|
|
35
|
+
version: result.version,
|
|
36
|
+
servers: result.servers,
|
|
37
|
+
stats: {
|
|
38
|
+
operationCount: result.operationCount,
|
|
39
|
+
schemaCount: result.schemaCount,
|
|
40
|
+
},
|
|
41
|
+
}, {
|
|
42
|
+
docId: result.docId,
|
|
43
|
+
cached: result.cached,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
return failure(mapError(error));
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
server.registerTool('openapi.list_operations', {
|
|
51
|
+
description: 'List operations in a loaded OpenAPI document with basic filters.',
|
|
52
|
+
inputSchema: {
|
|
53
|
+
docId: z.string(),
|
|
54
|
+
tag: z.string().optional(),
|
|
55
|
+
method: z.enum(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS']).optional(),
|
|
56
|
+
keyword: z.string().optional(),
|
|
57
|
+
page: z.number().int().positive().optional(),
|
|
58
|
+
pageSize: z.number().int().positive().max(200).optional(),
|
|
59
|
+
},
|
|
60
|
+
}, async ({ docId, tag, method, keyword, page, pageSize }) => {
|
|
61
|
+
try {
|
|
62
|
+
const resolvedDocId = await resolveDocId(docId);
|
|
63
|
+
const result = registry.listOperations({ docId: resolvedDocId, tag, method, keyword, page, pageSize });
|
|
64
|
+
return success(result, { docId: resolvedDocId, requestedDocId: docId });
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
return failure(mapError(error), { docId });
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
server.registerTool('openapi.resolve_operation', {
|
|
71
|
+
description: 'Resolve incomplete path/keyword into operation candidates.',
|
|
72
|
+
inputSchema: {
|
|
73
|
+
docId: z.string(),
|
|
74
|
+
query: z.string().min(1),
|
|
75
|
+
method: z.enum(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS']).optional(),
|
|
76
|
+
tag: z.string().optional(),
|
|
77
|
+
topK: z.number().int().min(1).max(20).optional(),
|
|
78
|
+
},
|
|
79
|
+
}, async ({ docId, query, method, tag, topK }) => {
|
|
80
|
+
try {
|
|
81
|
+
const resolvedDocId = await resolveDocId(docId);
|
|
82
|
+
const result = registry.resolveOperation(resolvedDocId, query, { method, tag, topK });
|
|
83
|
+
return success(result, { docId: resolvedDocId, requestedDocId: docId });
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
return failure(mapError(error), { docId });
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
server.registerTool('openapi.get_operation', {
|
|
90
|
+
description: 'Get operation details by exact or fuzzy mode, including parameters and responses.',
|
|
91
|
+
inputSchema: {
|
|
92
|
+
docId: z.string(),
|
|
93
|
+
operationId: z.string().optional(),
|
|
94
|
+
method: z.enum(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS']).optional(),
|
|
95
|
+
path: z.string().optional(),
|
|
96
|
+
matchMode: z.enum(['exact', 'fuzzy']).optional(),
|
|
97
|
+
query: z.string().optional(),
|
|
98
|
+
topK: z.number().int().min(1).max(20).optional(),
|
|
99
|
+
minScore: z.number().min(0).max(1).optional(),
|
|
100
|
+
resolveRefs: z.boolean().optional(),
|
|
101
|
+
},
|
|
102
|
+
}, async (args) => {
|
|
103
|
+
try {
|
|
104
|
+
const resolvedDocId = await resolveDocId(args.docId);
|
|
105
|
+
const result = registry.getOperation({ ...args, docId: resolvedDocId });
|
|
106
|
+
const matchMode = args.matchMode ?? 'exact';
|
|
107
|
+
if (!result.operation) {
|
|
108
|
+
return failure({
|
|
109
|
+
code: 'OPERATION_NOT_FOUND',
|
|
110
|
+
message: 'No operation matched the input',
|
|
111
|
+
details: { candidates: result.candidates ?? [] },
|
|
112
|
+
}, { docId: resolvedDocId, requestedDocId: args.docId, matchMode });
|
|
113
|
+
}
|
|
114
|
+
return success({
|
|
115
|
+
matchMode,
|
|
116
|
+
matched: result.matched,
|
|
117
|
+
candidates: result.candidates,
|
|
118
|
+
operation: result.operation,
|
|
119
|
+
}, { docId: resolvedDocId, requestedDocId: args.docId, matchMode });
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
return failure(mapError(error), { docId: args.docId });
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
server.registerTool('openapi.get_schema', {
|
|
126
|
+
description: 'Get a schema from components.schemas by schemaName.',
|
|
127
|
+
inputSchema: {
|
|
128
|
+
docId: z.string(),
|
|
129
|
+
schemaName: z.string().min(1),
|
|
130
|
+
resolveRefs: z.boolean().optional(),
|
|
131
|
+
},
|
|
132
|
+
}, async ({ docId, schemaName, resolveRefs }) => {
|
|
133
|
+
try {
|
|
134
|
+
const resolvedDocId = await resolveDocId(docId);
|
|
135
|
+
const result = registry.getSchema(resolvedDocId, schemaName, resolveRefs ?? true);
|
|
136
|
+
return success(result, { docId: resolvedDocId, requestedDocId: docId });
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
return failure(mapError(error), { docId, schemaName });
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
async function main() {
|
|
143
|
+
const transport = new StdioServerTransport();
|
|
144
|
+
await server.connect(transport);
|
|
145
|
+
console.error('swagger-mcp server running on stdio');
|
|
146
|
+
}
|
|
147
|
+
main().catch((error) => {
|
|
148
|
+
console.error('swagger-mcp fatal error:', error);
|
|
149
|
+
process.exit(1);
|
|
150
|
+
});
|
|
151
|
+
async function resolveDocId(docId) {
|
|
152
|
+
if (docId !== 'default') {
|
|
153
|
+
return docId;
|
|
154
|
+
}
|
|
155
|
+
const latest = registry.getLatestDocId();
|
|
156
|
+
if (latest) {
|
|
157
|
+
return latest;
|
|
158
|
+
}
|
|
159
|
+
const defaultUrl = getDefaultOpenApiUrl();
|
|
160
|
+
if (!defaultUrl) {
|
|
161
|
+
throw new Error(`Document not found: default. Set OPENAPI_DEFAULT_URL/OPENAPI_URL in AI workspace .env or pass explicit docId from openapi.load`);
|
|
162
|
+
}
|
|
163
|
+
const loaded = await registry.load(defaultUrl);
|
|
164
|
+
return loaded.docId;
|
|
165
|
+
}
|
|
166
|
+
function getDefaultOpenApiUrl() {
|
|
167
|
+
const raw = process.env.OPENAPI_DEFAULT_URL ?? process.env.OPENAPI_URL;
|
|
168
|
+
const normalized = raw?.trim();
|
|
169
|
+
return normalized ? normalized : undefined;
|
|
170
|
+
}
|
|
171
|
+
function loadEnvFiles() {
|
|
172
|
+
const found = [];
|
|
173
|
+
const candidates = new Set();
|
|
174
|
+
const explicitFile = process.env.OPENAPI_ENV_FILE?.trim();
|
|
175
|
+
const explicitDir = process.env.OPENAPI_ENV_DIR?.trim();
|
|
176
|
+
if (explicitFile) {
|
|
177
|
+
candidates.add(path.resolve(explicitFile));
|
|
178
|
+
}
|
|
179
|
+
if (explicitDir) {
|
|
180
|
+
candidates.add(path.resolve(explicitDir, '.env'));
|
|
181
|
+
}
|
|
182
|
+
candidates.add(path.resolve(process.cwd(), '.env'));
|
|
183
|
+
const initCwd = process.env.INIT_CWD?.trim();
|
|
184
|
+
if (initCwd) {
|
|
185
|
+
candidates.add(path.resolve(initCwd, '.env'));
|
|
186
|
+
}
|
|
187
|
+
for (const envFile of candidates) {
|
|
188
|
+
if (!existsSync(envFile))
|
|
189
|
+
continue;
|
|
190
|
+
dotenvConfig({ path: envFile, override: false });
|
|
191
|
+
found.push(envFile);
|
|
192
|
+
}
|
|
193
|
+
return found;
|
|
194
|
+
}
|
|
195
|
+
function success(data, meta = {}) {
|
|
196
|
+
const envelope = { ok: true, data, meta, error: null };
|
|
197
|
+
return {
|
|
198
|
+
content: [{ type: 'text', text: safeJson(envelope) }],
|
|
199
|
+
structuredContent: envelope,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
function failure(error, meta = {}) {
|
|
203
|
+
const envelope = { ok: false, data: null, meta, error };
|
|
204
|
+
return {
|
|
205
|
+
content: [{ type: 'text', text: safeJson(envelope) }],
|
|
206
|
+
structuredContent: envelope,
|
|
207
|
+
isError: true,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
function mapError(raw) {
|
|
211
|
+
const message = raw instanceof Error ? raw.message : String(raw);
|
|
212
|
+
if (message.includes('Document not found')) {
|
|
213
|
+
return { code: 'DOC_NOT_FOUND', message };
|
|
214
|
+
}
|
|
215
|
+
if (message.includes('Schema not found')) {
|
|
216
|
+
return { code: 'SCHEMA_NOT_FOUND', message };
|
|
217
|
+
}
|
|
218
|
+
if (message.includes('Operation not found') || message.includes('exact mode requires') || message.includes('query is required')) {
|
|
219
|
+
return { code: 'OPERATION_NOT_FOUND', message };
|
|
220
|
+
}
|
|
221
|
+
if (message.includes('Only http/https') || message.includes('Invalid URL') || message.includes('blocked')) {
|
|
222
|
+
return { code: 'VALIDATION_ERROR', message };
|
|
223
|
+
}
|
|
224
|
+
if (message.toLowerCase().includes('parse')) {
|
|
225
|
+
return { code: 'OPENAPI_PARSE_ERROR', message };
|
|
226
|
+
}
|
|
227
|
+
if (message.toLowerCase().includes('fetch') || message.toLowerCase().includes('network') || message.toLowerCase().includes('timeout')) {
|
|
228
|
+
return { code: 'OPENAPI_FETCH_ERROR', message };
|
|
229
|
+
}
|
|
230
|
+
return { code: 'VALIDATION_ERROR', message };
|
|
231
|
+
}
|
|
232
|
+
function safeJson(value) {
|
|
233
|
+
const seen = new WeakSet();
|
|
234
|
+
return JSON.stringify(value, (_, currentValue) => {
|
|
235
|
+
if (currentValue && typeof currentValue === 'object') {
|
|
236
|
+
if (seen.has(currentValue)) {
|
|
237
|
+
return '[Circular]';
|
|
238
|
+
}
|
|
239
|
+
seen.add(currentValue);
|
|
240
|
+
}
|
|
241
|
+
return currentValue;
|
|
242
|
+
}, 2);
|
|
243
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { HttpMethod, OperationCandidate, OperationDetail, OperationSummary, ToolGetOperationInput } from './tool-types.js';
|
|
2
|
+
type JsonMap = Record<string, unknown>;
|
|
3
|
+
interface ResolveOptions {
|
|
4
|
+
method?: HttpMethod;
|
|
5
|
+
tag?: string;
|
|
6
|
+
topK?: number;
|
|
7
|
+
minScore?: number;
|
|
8
|
+
}
|
|
9
|
+
export interface LoadResult {
|
|
10
|
+
docId: string;
|
|
11
|
+
title?: string;
|
|
12
|
+
version?: string;
|
|
13
|
+
servers: string[];
|
|
14
|
+
operationCount: number;
|
|
15
|
+
schemaCount: number;
|
|
16
|
+
cached: boolean;
|
|
17
|
+
}
|
|
18
|
+
export interface ResolveResult {
|
|
19
|
+
bestMatch?: OperationCandidate;
|
|
20
|
+
candidates: OperationCandidate[];
|
|
21
|
+
}
|
|
22
|
+
export interface GetOperationResolved {
|
|
23
|
+
matched?: OperationCandidate;
|
|
24
|
+
operation?: OperationDetail;
|
|
25
|
+
candidates?: OperationCandidate[];
|
|
26
|
+
}
|
|
27
|
+
export interface GetSchemaResolved {
|
|
28
|
+
schemaName: string;
|
|
29
|
+
schema: JsonMap;
|
|
30
|
+
usedByOperations: Array<Pick<OperationSummary, 'operationId' | 'method' | 'path'>>;
|
|
31
|
+
}
|
|
32
|
+
export declare class OpenApiRegistry {
|
|
33
|
+
private readonly docs;
|
|
34
|
+
private readonly keyToDocId;
|
|
35
|
+
private readonly ttlMs;
|
|
36
|
+
private latestDocId?;
|
|
37
|
+
constructor(ttlMs?: number);
|
|
38
|
+
load(url: string, headers?: Record<string, string>, forceRefresh?: boolean): Promise<LoadResult>;
|
|
39
|
+
listOperations(input: {
|
|
40
|
+
docId: string;
|
|
41
|
+
tag?: string;
|
|
42
|
+
method?: HttpMethod;
|
|
43
|
+
keyword?: string;
|
|
44
|
+
page?: number;
|
|
45
|
+
pageSize?: number;
|
|
46
|
+
}): {
|
|
47
|
+
items: OperationSummary[];
|
|
48
|
+
page: number;
|
|
49
|
+
pageSize: number;
|
|
50
|
+
total: number;
|
|
51
|
+
};
|
|
52
|
+
resolveOperation(docId: string, query: string, options?: ResolveOptions): ResolveResult;
|
|
53
|
+
getOperation(input: ToolGetOperationInput): GetOperationResolved;
|
|
54
|
+
getSchema(docId: string, schemaName: string, resolveRefs?: boolean): GetSchemaResolved;
|
|
55
|
+
getLatestDocId(): string | undefined;
|
|
56
|
+
private getDoc;
|
|
57
|
+
private toLoadResult;
|
|
58
|
+
private cleanupExpired;
|
|
59
|
+
private assertSupportedUrl;
|
|
60
|
+
private requireOperationByMethodPath;
|
|
61
|
+
}
|
|
62
|
+
export {};
|