@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 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 { spec: specPath, name = 'openapi-mcp-server', version = '0.1.0' } = options;
21
- // Load and parse OpenAPI spec
22
- const spec = await loadOpenAPISpec(specPath);
23
- const baseUrl = extractBaseUrl(spec);
24
- const authConfig = extractAuthConfig(spec);
25
- // Set up auth manager
26
- const authManager = new AuthManager(authConfig);
27
- configureAuth(authManager, authConfig, options);
28
- // Parse operations into tool definitions and create executable tools
29
- const toolDefs = parseOperations(spec);
30
- const tools = createExecutableTools(toolDefs, baseUrl, authManager);
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((tool) => ({
37
- name: tool.name,
38
- description: tool.description,
39
- inputSchema: zodToJsonSchema(tool.parameters, { target: 'jsonSchema7' }),
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 tool = tools.find((t) => t.name === toolName);
47
- if (!tool) {
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 result = await tool.execute(args ?? {});
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, authConfig, options) {
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.1.1",
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.1.2"
42
+ "@spec2tools/core": "0.2.0"
41
43
  },
42
44
  "devDependencies": {
43
45
  "@types/node": "^22.0.0",