apipost-mcp-server-simple 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/LICENSE +21 -0
- package/README.md +100 -0
- package/dist/apipost-client.js +134 -0
- package/dist/index.js +95 -0
- package/dist/types.js +1 -0
- package/package.json +37 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Joey Chen
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# Apipost MCP Server
|
|
2
|
+
|
|
3
|
+
这是一个基于 [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) 实现的 Apipost 服务端。它允许 AI Agent(如 Claude Desktop、Cursor、Cline、Antigravity 等)直接读取您在 Apipost 中的接口定义,并能够发起 API 请求以获取实时响应。
|
|
4
|
+
|
|
5
|
+
## 🚀 功能特性
|
|
6
|
+
|
|
7
|
+
- **自动同步接口**: 通过 Apipost 的 OpenAPI 3.0 分享链接,自动加载最新的接口定义。
|
|
8
|
+
- **资源浏览**: 提供 `apipost://apis` 资源,方便 AI 了解所有可用的端点、方法及功能描述。
|
|
9
|
+
- **动态执行**: 提供 `run_api_request` 工具,AI 可以根据需求构造参数并直接调用接口,获取真实业务数据。
|
|
10
|
+
- **灵活配置**: 支持通过环境变量自定义 API 基础路径(Base URL),解决接口定义中缺少服务器地址的问题。
|
|
11
|
+
|
|
12
|
+
## ⚙️ 配置说明
|
|
13
|
+
|
|
14
|
+
在使用本服务前,通常需要配置以下环境变量:
|
|
15
|
+
|
|
16
|
+
| 变量名 | 描述 | 示例 |
|
|
17
|
+
| :--- | :--- | :--- |
|
|
18
|
+
| `APIPOST_OPENAPI_URL` | **(必填)** Apipost 项目的 OpenAPI 分享链接 | `https://openapi.apipost.net/swagger/v3/...` |
|
|
19
|
+
| `APIPOST_API_BASE_URL` | **(推荐)** 接口执行的实际后端基础 URL | `https://api.example.com` |
|
|
20
|
+
| `APIPOST_HEADERS` | **(可选)** 全局鉴权 Headers,必须为 JSON 字符串 | `{"Authorization":"Bearer token"}` |
|
|
21
|
+
|
|
22
|
+
## 🛠️ 使用方式
|
|
23
|
+
|
|
24
|
+
### 方式一:直接通过 npx 运行 (推荐)
|
|
25
|
+
|
|
26
|
+
这是最简单的方式,无需下载代码,直接在您的 AI 客户端配置中添加即可。
|
|
27
|
+
|
|
28
|
+
以 **Claude Desktop** 或 **Cursor** 为例,在 MCP 设置中添加如下配置:
|
|
29
|
+
|
|
30
|
+
```json
|
|
31
|
+
{
|
|
32
|
+
"mcpServers": {
|
|
33
|
+
"apipost": {
|
|
34
|
+
"command": "npx",
|
|
35
|
+
"args": [
|
|
36
|
+
"-y",
|
|
37
|
+
"apipost-mcp-server-simple"
|
|
38
|
+
],
|
|
39
|
+
"env": {
|
|
40
|
+
"APIPOST_OPENAPI_URL": "您的 OpenAPI 分享链接",
|
|
41
|
+
"APIPOST_API_BASE_URL": "您的后端服务地址",
|
|
42
|
+
"APIPOST_HEADERS": "{\"Authorization\":\"Bearer your_token\"}"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### 方式二:本地开发运行
|
|
50
|
+
|
|
51
|
+
如果您需要修改代码或在本地调试运行:
|
|
52
|
+
|
|
53
|
+
1. **克隆并安装依赖**:
|
|
54
|
+
```bash
|
|
55
|
+
git clone <your-repo-url>
|
|
56
|
+
cd ApipostMcpServer
|
|
57
|
+
npm install
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
2. **配置本地环境**:
|
|
61
|
+
在项目根目录创建 `.env` 文件,参照上述“配置说明”填写内容。
|
|
62
|
+
|
|
63
|
+
3. **编译并启动**:
|
|
64
|
+
```bash
|
|
65
|
+
npm run build
|
|
66
|
+
npm start
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
4. **客户端配置**:
|
|
70
|
+
在 AI 客户端配置中指向本地路径:
|
|
71
|
+
```json
|
|
72
|
+
"apipost": {
|
|
73
|
+
"command": "node",
|
|
74
|
+
"args": ["/绝对路径/到/项目/dist/index.js"],
|
|
75
|
+
"env": { ... }
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## 🔒 鉴权说明
|
|
80
|
+
|
|
81
|
+
本 Server 支持两种鉴权模式:
|
|
82
|
+
1. **隐式注入 (推荐)**:在环境变量中设置 `APIPOST_HEADERS`,AI 客户端无需感知 Token,服务端自动补全,更安全。
|
|
83
|
+
2. **显式传递**:AI 可以在调用工具时,通过 `headers` 参数手动传递 Token,适用于需要动态切换身份的场景。
|
|
84
|
+
- 注意:显式传递的 Header 优先级高于隐式注入。
|
|
85
|
+
|
|
86
|
+
## 📝 暴露的接口
|
|
87
|
+
|
|
88
|
+
### 资源 (Resources)
|
|
89
|
+
- `apipost://apis`: 返回 JSON 格式的接口列表,包含所有可用接口的 Method、Path 和 Summary。
|
|
90
|
+
|
|
91
|
+
### 工具 (Tools)
|
|
92
|
+
- `run_api_request`:
|
|
93
|
+
- `method`: HTTP 方法 (GET, POST, PUT, DELETE 等)。
|
|
94
|
+
- `path`: 接口路径。
|
|
95
|
+
- `params`: Query 参数对象。
|
|
96
|
+
- `body`: 请求体 JSON。
|
|
97
|
+
- `headers`: 自定义请求头。
|
|
98
|
+
|
|
99
|
+
## 📄 许可证
|
|
100
|
+
MIT
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import fs from 'fs/promises';
|
|
3
|
+
export class ApipostClient {
|
|
4
|
+
openApiUrl;
|
|
5
|
+
openApiFilePath;
|
|
6
|
+
apiBaseUrl;
|
|
7
|
+
globalHeaders = {};
|
|
8
|
+
spec = null;
|
|
9
|
+
constructor(config) {
|
|
10
|
+
this.openApiUrl = config.openApiUrl;
|
|
11
|
+
this.openApiFilePath = config.openApiFilePath;
|
|
12
|
+
this.apiBaseUrl = config.apiBaseUrl;
|
|
13
|
+
// 解析全局 Headers 字符串,例如: "{"Authorization":"Bearer xxx","X-Key":"yyy"}"
|
|
14
|
+
if (config.globalHeaders) {
|
|
15
|
+
try {
|
|
16
|
+
this.globalHeaders = JSON.parse(config.globalHeaders);
|
|
17
|
+
}
|
|
18
|
+
catch (e) {
|
|
19
|
+
console.error('解析 APIPOST_HEADERS 失败,请确保它是正确的 JSON 字符串:', e);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* 加载 OpenAPI 定义
|
|
25
|
+
* 根据配置决定从 URL 下载还是读取本地文件。
|
|
26
|
+
*/
|
|
27
|
+
async loadDefinition() {
|
|
28
|
+
if (this.openApiUrl) {
|
|
29
|
+
try {
|
|
30
|
+
console.error(`正在从 URL 获取 OpenAPI 定义: ${this.openApiUrl}...`);
|
|
31
|
+
const response = await axios.get(this.openApiUrl);
|
|
32
|
+
// 显式断言类型
|
|
33
|
+
this.spec = response.data;
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
throw new Error(`无法从 URL 获取 OpenAPI 定义: ${error?.message || error}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
else if (this.openApiFilePath) {
|
|
40
|
+
try {
|
|
41
|
+
console.error(`正在从文件读取 OpenAPI 定义: ${this.openApiFilePath}...`);
|
|
42
|
+
const content = await fs.readFile(this.openApiFilePath, 'utf-8');
|
|
43
|
+
this.spec = JSON.parse(content);
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
throw new Error(`无法从文件读取 OpenAPI 定义: ${error?.message || error}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
throw new Error('未提供 OpenAPI URL 或文件路径。');
|
|
51
|
+
}
|
|
52
|
+
if (!this.spec) {
|
|
53
|
+
throw new Error('OpenAPI 规范为空。');
|
|
54
|
+
}
|
|
55
|
+
return this.spec;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* 获取 API 列表
|
|
59
|
+
* 提取可用的端点 (Endpoints) 和方法 (Methods)。
|
|
60
|
+
*/
|
|
61
|
+
getApiList() {
|
|
62
|
+
if (!this.spec || !this.spec.paths)
|
|
63
|
+
return [];
|
|
64
|
+
const apis = [];
|
|
65
|
+
for (const [path, pathItem] of Object.entries(this.spec.paths)) {
|
|
66
|
+
const methods = ['get', 'post', 'put', 'delete', 'patch'];
|
|
67
|
+
for (const method of methods) {
|
|
68
|
+
if (pathItem[method]) {
|
|
69
|
+
const operation = pathItem[method];
|
|
70
|
+
apis.push({
|
|
71
|
+
method: method.toUpperCase(),
|
|
72
|
+
path,
|
|
73
|
+
summary: operation.summary,
|
|
74
|
+
description: operation.description,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return apis;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* 确定执行的基础 URL (Base URL)。
|
|
83
|
+
* 优先级:
|
|
84
|
+
* 1. 环境变量 APIPOST_API_BASE_URL
|
|
85
|
+
* 2. OpenAPI 规范中的第一个 servers URL
|
|
86
|
+
* 3. 如果都未找到或无效,抛出错误。
|
|
87
|
+
*/
|
|
88
|
+
getBaseUrl() {
|
|
89
|
+
if (this.apiBaseUrl) {
|
|
90
|
+
return this.apiBaseUrl;
|
|
91
|
+
}
|
|
92
|
+
if (this.spec?.servers && this.spec.servers.length > 0 && this.spec.servers[0].url) {
|
|
93
|
+
return this.spec.servers[0].url;
|
|
94
|
+
}
|
|
95
|
+
throw new Error('未配置 Base URL。请在 .env 中设置 APIPOST_API_BASE_URL 或确保 OpenAPI 规范包含有效的 server URL。');
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* 执行 API 请求
|
|
99
|
+
*/
|
|
100
|
+
async executeApi(method, path, params, body, headers) {
|
|
101
|
+
if (!this.spec) {
|
|
102
|
+
await this.loadDefinition();
|
|
103
|
+
}
|
|
104
|
+
const baseUrl = this.getBaseUrl();
|
|
105
|
+
const url = `${baseUrl}${path}`;
|
|
106
|
+
console.error(`正在执行 API: ${method} ${url}`);
|
|
107
|
+
try {
|
|
108
|
+
const response = await axios({
|
|
109
|
+
method: method,
|
|
110
|
+
url,
|
|
111
|
+
params,
|
|
112
|
+
data: body,
|
|
113
|
+
headers: {
|
|
114
|
+
'Content-Type': 'application/json',
|
|
115
|
+
...this.globalHeaders, // 注入环境变量中的全局 Header
|
|
116
|
+
...headers, // 注入 AI 传递的临时 Header (优先级更高)
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
return response.data;
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
if (axios.isAxiosError(error)) {
|
|
123
|
+
return {
|
|
124
|
+
status: error.response?.status,
|
|
125
|
+
statusText: error.response?.statusText,
|
|
126
|
+
data: error.response?.data,
|
|
127
|
+
headers: error.response?.headers,
|
|
128
|
+
message: error.message
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
throw error;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import { ApipostClient } from './apipost-client.js';
|
|
6
|
+
import dotenv from 'dotenv';
|
|
7
|
+
dotenv.config();
|
|
8
|
+
const server = new McpServer({
|
|
9
|
+
name: 'Apipost MCP Server Simple',
|
|
10
|
+
version: '1.0.0',
|
|
11
|
+
});
|
|
12
|
+
const client = new ApipostClient({
|
|
13
|
+
openApiUrl: process.env.APIPOST_OPENAPI_URL,
|
|
14
|
+
openApiFilePath: process.env.APIPOST_OPENAPI_FILE,
|
|
15
|
+
apiBaseUrl: process.env.APIPOST_API_BASE_URL,
|
|
16
|
+
globalHeaders: process.env.APIPOST_HEADERS
|
|
17
|
+
});
|
|
18
|
+
// 资源: 获取 API 列表
|
|
19
|
+
server.resource("apis", "apipost://apis", async (uri) => {
|
|
20
|
+
try {
|
|
21
|
+
await client.loadDefinition(); // 确保已加载
|
|
22
|
+
const apis = client.getApiList();
|
|
23
|
+
return {
|
|
24
|
+
contents: [{
|
|
25
|
+
uri: uri.href,
|
|
26
|
+
text: JSON.stringify(apis, null, 2),
|
|
27
|
+
mimeType: "application/json"
|
|
28
|
+
}]
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
return {
|
|
33
|
+
contents: [{
|
|
34
|
+
uri: uri.href,
|
|
35
|
+
text: `加载 API 列表失败: ${error.message}`,
|
|
36
|
+
mimeType: "text/plain"
|
|
37
|
+
}]
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
// 工具: 执行 API 请求
|
|
42
|
+
server.tool("run_api_request", "执行 Apipost 中定义的 API 请求。", {
|
|
43
|
+
method: z.string().describe("HTTP 方法 (GET, POST 等)"),
|
|
44
|
+
path: z.string().describe("API 路径 (例如: /users/1)"),
|
|
45
|
+
params: z.record(z.string(), z.any()).optional().describe("查询参数 (Query parameters)"),
|
|
46
|
+
body: z.any().optional().describe("请求体 (JSON)"),
|
|
47
|
+
headers: z.record(z.string(), z.string()).optional().describe("自定义 Header")
|
|
48
|
+
}, async ({ method, path, params, body, headers }) => {
|
|
49
|
+
try {
|
|
50
|
+
// 确保定义已加载
|
|
51
|
+
await client.loadDefinition();
|
|
52
|
+
// 检查路径是否存在于规范中
|
|
53
|
+
const apis = client.getApiList();
|
|
54
|
+
const apiExists = apis.some(api => api.path === path && api.method === method.toUpperCase());
|
|
55
|
+
if (!apiExists) {
|
|
56
|
+
// 宽松检查: 有时路径可能包含变量如 {id},如果用户传递了解析后的路径,精确匹配可能会失败。
|
|
57
|
+
// 目前我们继续执行,但可能需要记录警告。
|
|
58
|
+
// 实际上,axios execution 会处理提供的路径。
|
|
59
|
+
// 对于 MVP,我们跳过严格验证,相信用户/LLM 会选择正确的路径。
|
|
60
|
+
}
|
|
61
|
+
const result = await client.executeApi(method, path, params, body, headers);
|
|
62
|
+
return {
|
|
63
|
+
content: [{
|
|
64
|
+
type: "text",
|
|
65
|
+
text: typeof result === 'string' ? result : JSON.stringify(result, null, 2)
|
|
66
|
+
}]
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
return {
|
|
71
|
+
content: [{
|
|
72
|
+
type: "text",
|
|
73
|
+
text: `执行 API 失败: ${error.message}\n${JSON.stringify(error.response?.data || {}, null, 2)}`
|
|
74
|
+
}],
|
|
75
|
+
isError: true,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
async function main() {
|
|
80
|
+
try {
|
|
81
|
+
// 启动时预加载以验证配置
|
|
82
|
+
await client.loadDefinition();
|
|
83
|
+
console.error(`成功加载 OpenAPI 定义,包含 ${client.getApiList().length} 个 API。`);
|
|
84
|
+
}
|
|
85
|
+
catch (e) {
|
|
86
|
+
console.error(`警告: 启动时加载 OpenAPI 定义失败: ${e.message}`);
|
|
87
|
+
}
|
|
88
|
+
const transport = new StdioServerTransport();
|
|
89
|
+
await server.connect(transport);
|
|
90
|
+
console.error("Apipost MCP Server Simple 正在通过 stdio 运行");
|
|
91
|
+
}
|
|
92
|
+
main().catch((error) => {
|
|
93
|
+
console.error("main() 发生致命错误:", error);
|
|
94
|
+
process.exit(1);
|
|
95
|
+
});
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "apipost-mcp-server-simple",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"main": "dist/index.js",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"build": "tsc",
|
|
7
|
+
"start": "node dist/index.js",
|
|
8
|
+
"dev": "ts-node src/index.ts"
|
|
9
|
+
},
|
|
10
|
+
"type": "module",
|
|
11
|
+
"keywords": [],
|
|
12
|
+
"author": "",
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"description": "Apipost MCP Server for AI Agents",
|
|
15
|
+
"bin": {
|
|
16
|
+
"apipost-mcp-server": "dist/index.js"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist",
|
|
20
|
+
"README.md",
|
|
21
|
+
"LICENSE"
|
|
22
|
+
],
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"access": "public"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@modelcontextprotocol/sdk": "^1.25.2",
|
|
28
|
+
"axios": "^1.13.2",
|
|
29
|
+
"dotenv": "^17.2.3",
|
|
30
|
+
"zod": "^4.3.5"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@types/node": "^25.0.7",
|
|
34
|
+
"ts-node": "^10.9.2",
|
|
35
|
+
"typescript": "^5.9.3"
|
|
36
|
+
}
|
|
37
|
+
}
|