@spec2tools/stdio-mcp 0.1.1 → 0.2.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/README.md +49 -16
- package/dist/index.js +23 -2
- package/dist/server.d.ts +6 -0
- package/dist/server.js +69 -22
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -12,6 +12,22 @@ pnpm add @spec2tools/stdio-mcp
|
|
|
12
12
|
|
|
13
13
|
## Quick Start
|
|
14
14
|
|
|
15
|
+
### With Claude Code
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# Add as an MCP server
|
|
19
|
+
claude mcp add stdio my-api \
|
|
20
|
+
-- npx @spec2tools/stdio-mcp ./path/to/openapi.yaml
|
|
21
|
+
|
|
22
|
+
# With authentication
|
|
23
|
+
claude mcp add --env API_KEY=your-api-key my-api \
|
|
24
|
+
-- npx @spec2tools/stdio-mcp ./openapi.yaml
|
|
25
|
+
|
|
26
|
+
# With code mode (2 tools: search + execute)
|
|
27
|
+
claude mcp add my-api \
|
|
28
|
+
-- npx @spec2tools/stdio-mcp ./openapi.yaml --code-mode
|
|
29
|
+
```
|
|
30
|
+
|
|
15
31
|
### With Claude Desktop
|
|
16
32
|
|
|
17
33
|
Add to your `claude_desktop_config.json`:
|
|
@@ -40,22 +56,6 @@ For authenticated APIs:
|
|
|
40
56
|
}
|
|
41
57
|
```
|
|
42
58
|
|
|
43
|
-
Or using environment variables:
|
|
44
|
-
|
|
45
|
-
```json
|
|
46
|
-
{
|
|
47
|
-
"mcpServers": {
|
|
48
|
-
"my-api": {
|
|
49
|
-
"command": "npx",
|
|
50
|
-
"args": ["@spec2tools/stdio-mcp", "./openapi.yaml"],
|
|
51
|
-
"env": {
|
|
52
|
-
"API_KEY": "your-api-key"
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
```
|
|
58
|
-
|
|
59
59
|
### CLI Usage
|
|
60
60
|
|
|
61
61
|
```bash
|
|
@@ -72,6 +72,36 @@ spec2tools-mcp ./api.yaml --api-key your-api-key
|
|
|
72
72
|
API_KEY=your-api-key spec2tools-mcp ./api.yaml
|
|
73
73
|
```
|
|
74
74
|
|
|
75
|
+
### Remote MCP Server
|
|
76
|
+
|
|
77
|
+
Proxy an existing remote MCP server through stdio. This lets you connect any HTTP or SSE-based MCP server to clients that only support stdio transport (like Claude Desktop), and optionally apply code mode to reduce token usage.
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
# Proxy via Streamable HTTP
|
|
81
|
+
spec2tools-mcp --http https://example.com/mcp
|
|
82
|
+
|
|
83
|
+
# Proxy via SSE
|
|
84
|
+
spec2tools-mcp --sse https://example.com/sse
|
|
85
|
+
|
|
86
|
+
# Proxy with code mode
|
|
87
|
+
spec2tools-mcp --sse https://example.com/sse --code-mode
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
With Claude Code:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
claude mcp add my-remote-api \
|
|
94
|
+
-- npx @spec2tools/stdio-mcp --sse https://example.com/sse --code-mode
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Code Mode
|
|
98
|
+
|
|
99
|
+
Code mode collapses all API endpoints into 2 tools (`search` + `execute`), reducing token usage by ~99.9%. The model discovers endpoints via keyword search and calls them by writing Python code in a sandboxed interpreter.
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
spec2tools-mcp ./openapi.yaml --code-mode
|
|
103
|
+
```
|
|
104
|
+
|
|
75
105
|
### Programmatic Usage
|
|
76
106
|
|
|
77
107
|
```typescript
|
|
@@ -93,6 +123,9 @@ await startMcpServer({
|
|
|
93
123
|
| `--name <name>` | Server name for MCP (default: `openapi-mcp-server`) |
|
|
94
124
|
| `--version <ver>` | Server version for MCP (default: `1.0.0`) |
|
|
95
125
|
| `--api-key <key>` | API key or bearer token for authentication |
|
|
126
|
+
| `--http <url>` | Connect to a remote MCP server (Streamable HTTP) |
|
|
127
|
+
| `--sse <url>` | Connect to a remote MCP server (SSE transport) |
|
|
128
|
+
| `--code-mode` | Use code mode (2 tools: search + execute) |
|
|
96
129
|
| `-h, --help` | Show help message |
|
|
97
130
|
|
|
98
131
|
## Environment Variables
|
package/dist/index.js
CHANGED
|
@@ -17,8 +17,8 @@ async function main() {
|
|
|
17
17
|
process.exit(args.includes('--help') || args.includes('-h') ? 0 : 1);
|
|
18
18
|
}
|
|
19
19
|
const options = parseArgs(args);
|
|
20
|
-
if (!options.spec) {
|
|
21
|
-
console.error('Error: OpenAPI spec path is required');
|
|
20
|
+
if (!options.spec && !options.httpUrl && !options.sseUrl) {
|
|
21
|
+
console.error('Error: OpenAPI spec path or remote MCP URL (--http / --sse) is required');
|
|
22
22
|
printUsage();
|
|
23
23
|
process.exit(1);
|
|
24
24
|
}
|
|
@@ -43,6 +43,15 @@ function parseArgs(args) {
|
|
|
43
43
|
else if (arg === '--api-key' && args[i + 1]) {
|
|
44
44
|
options.apiKey = args[++i];
|
|
45
45
|
}
|
|
46
|
+
else if (arg === '--code-mode') {
|
|
47
|
+
options.codeMode = true;
|
|
48
|
+
}
|
|
49
|
+
else if (arg === '--http' && args[i + 1]) {
|
|
50
|
+
options.httpUrl = args[++i];
|
|
51
|
+
}
|
|
52
|
+
else if (arg === '--sse' && args[i + 1]) {
|
|
53
|
+
options.sseUrl = args[++i];
|
|
54
|
+
}
|
|
46
55
|
else if (!arg.startsWith('-')) {
|
|
47
56
|
options.spec = arg;
|
|
48
57
|
}
|
|
@@ -55,14 +64,19 @@ function printUsage() {
|
|
|
55
64
|
|
|
56
65
|
Usage:
|
|
57
66
|
spec2tools-mcp <spec-path> [options]
|
|
67
|
+
spec2tools-mcp --http <url> [options]
|
|
68
|
+
spec2tools-mcp --sse <url> [options]
|
|
58
69
|
|
|
59
70
|
Arguments:
|
|
60
71
|
spec-path Path or URL to OpenAPI specification (JSON or YAML)
|
|
61
72
|
|
|
62
73
|
Options:
|
|
74
|
+
--http <url> Connect to a remote MCP server (Streamable HTTP)
|
|
75
|
+
--sse <url> Connect to a remote MCP server (SSE transport)
|
|
63
76
|
--name <name> Server name for MCP (default: openapi-mcp-server)
|
|
64
77
|
--version <ver> Server version for MCP (default: 1.0.0)
|
|
65
78
|
--api-key <key> API key or token for authentication
|
|
79
|
+
--code-mode Use code mode (2 tools: search + execute)
|
|
66
80
|
-h, --help Show this help message
|
|
67
81
|
|
|
68
82
|
Environment Variables:
|
|
@@ -78,6 +92,13 @@ Examples:
|
|
|
78
92
|
# Start with authentication
|
|
79
93
|
API_KEY=xxx spec2tools-mcp ./api.yaml
|
|
80
94
|
|
|
95
|
+
# Proxy a remote MCP server via stdio
|
|
96
|
+
spec2tools-mcp --http https://example.com/mcp
|
|
97
|
+
spec2tools-mcp --sse https://example.com/sse
|
|
98
|
+
|
|
99
|
+
# Proxy with code mode (collapse all tools into search + execute)
|
|
100
|
+
spec2tools-mcp --sse https://example.com/sse --code-mode
|
|
101
|
+
|
|
81
102
|
# Configure in Claude Desktop (claude_desktop_config.json):
|
|
82
103
|
{
|
|
83
104
|
"mcpServers": {
|
package/dist/server.d.ts
CHANGED
|
@@ -7,6 +7,12 @@ export interface McpServerOptions {
|
|
|
7
7
|
version?: string;
|
|
8
8
|
/** API key or token for authentication */
|
|
9
9
|
apiKey?: string;
|
|
10
|
+
/** Use code mode (2 tools: search + execute) */
|
|
11
|
+
codeMode?: boolean;
|
|
12
|
+
/** URL of a remote MCP server (Streamable HTTP transport) */
|
|
13
|
+
httpUrl?: string;
|
|
14
|
+
/** URL of a remote MCP server (SSE transport) */
|
|
15
|
+
sseUrl?: string;
|
|
10
16
|
}
|
|
11
17
|
/**
|
|
12
18
|
* Create and start an MCP server that exposes OpenAPI operations as tools.
|
package/dist/server.js
CHANGED
|
@@ -2,7 +2,8 @@ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
|
2
2
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
3
|
import { ListToolsRequestSchema, CallToolRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
4
4
|
import { zodToJsonSchema } from 'zod-to-json-schema';
|
|
5
|
-
import { loadOpenAPISpec, extractBaseUrl, extractAuthConfig, parseOperations, AuthManager, createExecutableTools, } from '@spec2tools/core';
|
|
5
|
+
import { loadOpenAPISpec, extractBaseUrl, extractAuthConfig, parseOperations, AuthManager, createExecutableTools, toAISDKTools, toCodeModeTools, } from '@spec2tools/core';
|
|
6
|
+
import { createMCPClient } from '@ai-sdk/mcp';
|
|
6
7
|
/**
|
|
7
8
|
* Create and start an MCP server that exposes OpenAPI operations as tools.
|
|
8
9
|
*
|
|
@@ -17,41 +18,80 @@ import { loadOpenAPISpec, extractBaseUrl, extractAuthConfig, parseOperations, Au
|
|
|
17
18
|
* ```
|
|
18
19
|
*/
|
|
19
20
|
export async function startMcpServer(options) {
|
|
20
|
-
const {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
21
|
+
const { name = 'openapi-mcp-server', version = '0.1.0' } = options;
|
|
22
|
+
let tools;
|
|
23
|
+
let mcpClient;
|
|
24
|
+
if (options.httpUrl || options.sseUrl) {
|
|
25
|
+
// Remote MCP proxy mode: connect to an existing MCP server
|
|
26
|
+
const transport = options.httpUrl
|
|
27
|
+
? { type: 'http', url: options.httpUrl, headers: { Authorization: `Bearer ${options.apiKey || ''}` } }
|
|
28
|
+
: { type: 'sse', url: options.sseUrl, headers: { Authorization: `Bearer ${options.apiKey || ''}` } };
|
|
29
|
+
mcpClient = await createMCPClient({ transport });
|
|
30
|
+
tools = await mcpClient.tools();
|
|
31
|
+
if (options.codeMode) {
|
|
32
|
+
tools = toCodeModeTools(tools);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
// OpenAPI spec mode: load spec and create tools
|
|
37
|
+
const spec = await loadOpenAPISpec(options.spec);
|
|
38
|
+
const baseUrl = extractBaseUrl(spec);
|
|
39
|
+
const authConfig = extractAuthConfig(spec);
|
|
40
|
+
const authManager = new AuthManager(authConfig);
|
|
41
|
+
configureAuth(authManager, authConfig, options);
|
|
42
|
+
const toolDefs = parseOperations(spec);
|
|
43
|
+
const coreTools = createExecutableTools(toolDefs, baseUrl, authManager);
|
|
44
|
+
tools = toAISDKTools(coreTools);
|
|
45
|
+
if (options.codeMode) {
|
|
46
|
+
tools = toCodeModeTools(tools);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
31
49
|
// Create MCP server
|
|
32
50
|
const server = new Server({ name, version }, { capabilities: { tools: { listChanged: false } } });
|
|
33
51
|
// Register tool listing handler
|
|
34
52
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
35
53
|
return {
|
|
36
|
-
tools: tools.map((
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
54
|
+
tools: Object.entries(tools).map(([toolName, t]) => {
|
|
55
|
+
const description = 'description' in t ? t.description || '' : '';
|
|
56
|
+
const schema = ('inputSchema' in t ? t.inputSchema : undefined) ??
|
|
57
|
+
('parameters' in t ? t.parameters : undefined);
|
|
58
|
+
let inputSchema = { type: 'object', properties: {} };
|
|
59
|
+
if (schema && typeof schema === 'object') {
|
|
60
|
+
const schemaObj = schema;
|
|
61
|
+
if ('jsonSchema' in schemaObj && schemaObj.jsonSchema) {
|
|
62
|
+
inputSchema = schemaObj.jsonSchema;
|
|
63
|
+
}
|
|
64
|
+
else if ('_def' in schemaObj) {
|
|
65
|
+
inputSchema = zodToJsonSchema(schema, { target: 'jsonSchema7' });
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
name: toolName,
|
|
70
|
+
description,
|
|
71
|
+
inputSchema,
|
|
72
|
+
};
|
|
73
|
+
}),
|
|
41
74
|
};
|
|
42
75
|
});
|
|
43
76
|
// Register tool execution handler
|
|
44
77
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
45
78
|
const { name: toolName, arguments: args } = request.params;
|
|
46
|
-
const
|
|
47
|
-
if (!
|
|
79
|
+
const toolEntry = tools[toolName];
|
|
80
|
+
if (!toolEntry) {
|
|
48
81
|
return {
|
|
49
82
|
content: [{ type: 'text', text: `Error: Unknown tool "${toolName}"` }],
|
|
50
83
|
isError: true,
|
|
51
84
|
};
|
|
52
85
|
}
|
|
53
86
|
try {
|
|
54
|
-
const
|
|
87
|
+
const execute = 'execute' in toolEntry ? toolEntry.execute : undefined;
|
|
88
|
+
if (!execute) {
|
|
89
|
+
return {
|
|
90
|
+
content: [{ type: 'text', text: `Error: Tool "${toolName}" has no execute function` }],
|
|
91
|
+
isError: true,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
const result = await execute(args ?? {}, {});
|
|
55
95
|
const resultText = typeof result === 'string' ? result : JSON.stringify(result, null, 2);
|
|
56
96
|
return {
|
|
57
97
|
content: [{ type: 'text', text: resultText }],
|
|
@@ -61,7 +101,6 @@ export async function startMcpServer(options) {
|
|
|
61
101
|
let errorMessage;
|
|
62
102
|
if (error instanceof Error) {
|
|
63
103
|
errorMessage = error.message;
|
|
64
|
-
// Include stack trace for debugging if available
|
|
65
104
|
if (error.stack) {
|
|
66
105
|
errorMessage += `\n\nStack trace:\n${error.stack}`;
|
|
67
106
|
}
|
|
@@ -75,6 +114,14 @@ export async function startMcpServer(options) {
|
|
|
75
114
|
};
|
|
76
115
|
}
|
|
77
116
|
});
|
|
117
|
+
// Clean up remote MCP client on process exit
|
|
118
|
+
if (mcpClient) {
|
|
119
|
+
const cleanup = async () => {
|
|
120
|
+
await mcpClient.close();
|
|
121
|
+
};
|
|
122
|
+
process.on('SIGINT', () => { cleanup().then(() => process.exit(0)); });
|
|
123
|
+
process.on('SIGTERM', () => { cleanup().then(() => process.exit(0)); });
|
|
124
|
+
}
|
|
78
125
|
// Start stdio transport
|
|
79
126
|
const transport = new StdioServerTransport();
|
|
80
127
|
await server.connect(transport);
|
|
@@ -82,7 +129,7 @@ export async function startMcpServer(options) {
|
|
|
82
129
|
/**
|
|
83
130
|
* Configure authentication from options or environment variables
|
|
84
131
|
*/
|
|
85
|
-
function configureAuth(authManager,
|
|
132
|
+
function configureAuth(authManager, _authConfig, options) {
|
|
86
133
|
// Check for explicit option first, then fall back to environment variable
|
|
87
134
|
const apiKey = options.apiKey || process.env.API_KEY;
|
|
88
135
|
// Set the API key if provided, regardless of auth type
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spec2tools/stdio-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "MCP server that exposes OpenAPI endpoints as tools via stdio transport",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -34,10 +34,12 @@
|
|
|
34
34
|
"directory": "packages/stdio-mcp"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
+
"@ai-sdk/mcp": "^1.0.21",
|
|
37
38
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
39
|
+
"ai": "^6.0.68",
|
|
38
40
|
"zod": "^3.24.0",
|
|
39
41
|
"zod-to-json-schema": "^3.24.0",
|
|
40
|
-
"@spec2tools/core": "0.
|
|
42
|
+
"@spec2tools/core": "0.2.0"
|
|
41
43
|
},
|
|
42
44
|
"devDependencies": {
|
|
43
45
|
"@types/node": "^22.0.0",
|