@ivotoby/openapi-mcp-server 1.0.0 → 1.1.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 +116 -39
- package/dist/bundle.js +618 -25
- package/package.json +2 -7
package/README.md
CHANGED
|
@@ -2,14 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
A Model Context Protocol (MCP) server that exposes OpenAPI endpoints as MCP resources. This server allows Large Language Models to discover and interact with REST APIs defined by OpenAPI specifications through the MCP protocol.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Overview
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
This MCP server supports two transport methods:
|
|
8
|
+
|
|
9
|
+
1. **Stdio Transport** (default): For direct integration with AI systems like Claude Desktop that manage MCP connections through standard input/output.
|
|
10
|
+
2. **Streamable HTTP Transport**: For connecting to the server over HTTP, allowing web clients and other HTTP-capable systems to use the MCP protocol.
|
|
11
|
+
|
|
12
|
+
## Quick Start for Users
|
|
13
|
+
|
|
14
|
+
### Option 1: Using with Claude Desktop (Stdio Transport)
|
|
15
|
+
|
|
16
|
+
No need to clone this repository. Simply configure Claude Desktop to use this MCP server:
|
|
8
17
|
|
|
9
18
|
1. Locate or create your Claude Desktop configuration file:
|
|
19
|
+
|
|
10
20
|
- On macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
11
21
|
|
|
12
|
-
2. Add the following configuration
|
|
22
|
+
2. Add the following configuration:
|
|
13
23
|
|
|
14
24
|
```json
|
|
15
25
|
{
|
|
@@ -32,27 +42,75 @@ You do not need to clone this repository to use this MCP server. You can simply
|
|
|
32
42
|
- `OPENAPI_SPEC_PATH`: URL or path to your OpenAPI specification
|
|
33
43
|
- `API_HEADERS`: Comma-separated key:value pairs for API authentication headers
|
|
34
44
|
|
|
35
|
-
|
|
45
|
+
### Option 2: Using with HTTP Clients (HTTP Transport)
|
|
36
46
|
|
|
37
|
-
|
|
47
|
+
To use the server with HTTP clients:
|
|
38
48
|
|
|
39
|
-
|
|
49
|
+
1. No installation required! Use npx to run the package directly:
|
|
40
50
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
-
|
|
51
|
+
```bash
|
|
52
|
+
npx @ivotoby/openapi-mcp-server \
|
|
53
|
+
--api-base-url https://api.example.com \
|
|
54
|
+
--openapi-spec https://api.example.com/openapi.json \
|
|
55
|
+
--headers "Authorization:Bearer token123" \
|
|
56
|
+
--transport http \
|
|
57
|
+
--port 3000
|
|
58
|
+
```
|
|
44
59
|
|
|
45
|
-
|
|
60
|
+
2. Interact with the server using HTTP requests:
|
|
46
61
|
|
|
47
|
-
|
|
48
|
-
|
|
62
|
+
```bash
|
|
63
|
+
# Initialize a session (first request)
|
|
64
|
+
curl -X POST http://localhost:3000/mcp \
|
|
65
|
+
-H "Content-Type: application/json" \
|
|
66
|
+
-d '{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"client":{"name":"curl-client","version":"1.0.0"},"protocol":{"name":"mcp","version":"2025-03-26"}}}'
|
|
67
|
+
|
|
68
|
+
# The response includes a Mcp-Session-Id header that you must use for subsequent requests
|
|
69
|
+
# and the InitializeResult directly in the POST response body.
|
|
70
|
+
|
|
71
|
+
# Send a request to list tools
|
|
72
|
+
# This also receives its response directly on this POST request.
|
|
73
|
+
curl -X POST http://localhost:3000/mcp \
|
|
74
|
+
-H "Content-Type: application/json" \
|
|
75
|
+
-H "Mcp-Session-Id: your-session-id" \
|
|
76
|
+
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
|
|
77
|
+
|
|
78
|
+
# Open a streaming connection for other server responses (e.g., tool execution results)
|
|
79
|
+
# This uses Server-Sent Events (SSE).
|
|
80
|
+
curl -N http://localhost:3000/mcp -H "Mcp-Session-Id: your-session-id"
|
|
81
|
+
|
|
82
|
+
# Example: Execute a tool (response will arrive on the GET stream)
|
|
83
|
+
# curl -X POST http://localhost:3000/mcp \
|
|
84
|
+
# -H "Content-Type: application/json" \
|
|
85
|
+
# -H "Mcp-Session-Id: your-session-id" \
|
|
86
|
+
# -d '{"jsonrpc":"2.0","id":2,"method":"tools/execute","params":{"name":"yourToolName", "arguments": {}}}'
|
|
87
|
+
|
|
88
|
+
# Terminate the session when done
|
|
89
|
+
curl -X DELETE http://localhost:3000/mcp -H "Mcp-Session-Id: your-session-id"
|
|
90
|
+
```
|
|
49
91
|
|
|
50
|
-
|
|
92
|
+
## Transport Types
|
|
51
93
|
|
|
52
|
-
|
|
53
|
-
|
|
94
|
+
### Stdio Transport (Default)
|
|
95
|
+
|
|
96
|
+
The stdio transport is designed for direct integration with AI systems like Claude Desktop that manage MCP connections through standard input/output. This is the simplest setup and requires no network configuration.
|
|
97
|
+
|
|
98
|
+
**When to use**: When integrating with Claude Desktop or other systems that support stdio-based MCP communication.
|
|
99
|
+
|
|
100
|
+
### Streamable HTTP Transport
|
|
101
|
+
|
|
102
|
+
The HTTP transport allows the MCP server to be accessed over HTTP, enabling web applications and other HTTP-capable clients to interact with the MCP protocol. It supports session management, streaming responses, and standard HTTP methods.
|
|
54
103
|
|
|
55
|
-
|
|
104
|
+
**Key features**:
|
|
105
|
+
|
|
106
|
+
- Session management with Mcp-Session-Id header
|
|
107
|
+
- HTTP responses for `initialize` and `tools/list` requests are sent synchronously on the POST.
|
|
108
|
+
- Other server-to-client messages (e.g., `tools/execute` results, notifications) are streamed over a GET connection using Server-Sent Events (SSE).
|
|
109
|
+
- Support for POST/GET/DELETE methods
|
|
110
|
+
|
|
111
|
+
**When to use**: When you need to expose the MCP server to web clients or systems that communicate over HTTP rather than stdio.
|
|
112
|
+
|
|
113
|
+
## Configuration Options
|
|
56
114
|
|
|
57
115
|
The server can be configured through environment variables or command line arguments:
|
|
58
116
|
|
|
@@ -63,51 +121,70 @@ The server can be configured through environment variables or command line argum
|
|
|
63
121
|
- `API_HEADERS` - Comma-separated key:value pairs for API headers
|
|
64
122
|
- `SERVER_NAME` - Name for the MCP server (default: "mcp-openapi-server")
|
|
65
123
|
- `SERVER_VERSION` - Version of the server (default: "1.0.0")
|
|
124
|
+
- `TRANSPORT_TYPE` - Transport type to use: "stdio" or "http" (default: "stdio")
|
|
125
|
+
- `HTTP_PORT` - Port for HTTP transport (default: 3000)
|
|
126
|
+
- `HTTP_HOST` - Host for HTTP transport (default: "127.0.0.1")
|
|
127
|
+
- `ENDPOINT_PATH` - Endpoint path for HTTP transport (default: "/mcp")
|
|
66
128
|
|
|
67
129
|
### Command Line Arguments
|
|
68
130
|
|
|
69
131
|
```bash
|
|
70
|
-
|
|
132
|
+
npx @ivotoby/openapi-mcp-server \
|
|
71
133
|
--api-base-url https://api.example.com \
|
|
72
134
|
--openapi-spec https://api.example.com/openapi.json \
|
|
73
135
|
--headers "Authorization:Bearer token123,X-API-Key:your-api-key" \
|
|
74
136
|
--name "my-mcp-server" \
|
|
75
|
-
--version "1.0.0"
|
|
137
|
+
--version "1.0.0" \
|
|
138
|
+
--transport http \
|
|
139
|
+
--port 3000 \
|
|
140
|
+
--host 127.0.0.1 \
|
|
141
|
+
--path /mcp
|
|
76
142
|
```
|
|
77
143
|
|
|
78
|
-
##
|
|
79
|
-
|
|
80
|
-
1. Start the development environment:
|
|
81
|
-
```bash
|
|
82
|
-
npm run inspect-watch
|
|
83
|
-
```
|
|
144
|
+
## Security Considerations
|
|
84
145
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
146
|
+
- The HTTP transport validates Origin headers to prevent DNS rebinding attacks
|
|
147
|
+
- By default, HTTP transport only binds to localhost (127.0.0.1)
|
|
148
|
+
- If exposing to other hosts, consider implementing additional authentication
|
|
88
149
|
|
|
89
150
|
## Debugging
|
|
90
151
|
|
|
91
|
-
|
|
152
|
+
To see debug logs:
|
|
92
153
|
|
|
93
|
-
1.
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
154
|
+
1. When using stdio transport with Claude Desktop:
|
|
155
|
+
|
|
156
|
+
- Logs appear in the Claude Desktop logs
|
|
157
|
+
|
|
158
|
+
2. When using HTTP transport:
|
|
97
159
|
```bash
|
|
98
|
-
|
|
160
|
+
npx @ivotoby/openapi-mcp-server --transport http 2>debug.log
|
|
99
161
|
```
|
|
100
162
|
|
|
101
|
-
##
|
|
163
|
+
## For Developers
|
|
164
|
+
|
|
165
|
+
### Development Tools
|
|
166
|
+
|
|
167
|
+
- `npm run build` - Builds the TypeScript source
|
|
168
|
+
- `npm run clean` - Removes build artifacts
|
|
169
|
+
- `npm run typecheck` - Runs TypeScript type checking
|
|
170
|
+
- `npm run lint` - Runs ESLint
|
|
171
|
+
- `npm run dev` - Watches source files and rebuilds on changes
|
|
172
|
+
- `npm run inspect-watch` - Runs the inspector with auto-reload on changes
|
|
173
|
+
|
|
174
|
+
### Development Workflow
|
|
175
|
+
|
|
176
|
+
1. Clone the repository
|
|
177
|
+
2. Install dependencies: `npm install`
|
|
178
|
+
3. Start the development environment: `npm run inspect-watch`
|
|
179
|
+
4. Make changes to the TypeScript files in `src/`
|
|
180
|
+
5. The server will automatically rebuild and restart
|
|
181
|
+
|
|
182
|
+
### Contributing
|
|
102
183
|
|
|
103
184
|
1. Fork the repository
|
|
104
185
|
2. Create a feature branch
|
|
105
186
|
3. Make your changes
|
|
106
|
-
4. Run tests and linting:
|
|
107
|
-
```bash
|
|
108
|
-
npm run typecheck
|
|
109
|
-
npm run lint
|
|
110
|
-
```
|
|
187
|
+
4. Run tests and linting: `npm run typecheck && npm run lint`
|
|
111
188
|
5. Submit a pull request
|
|
112
189
|
|
|
113
190
|
## License
|
package/dist/bundle.js
CHANGED
|
@@ -9991,7 +9991,7 @@ var require_form_data = __commonJS({
|
|
|
9991
9991
|
var CombinedStream = require_combined_stream();
|
|
9992
9992
|
var util3 = __require("util");
|
|
9993
9993
|
var path = __require("path");
|
|
9994
|
-
var
|
|
9994
|
+
var http3 = __require("http");
|
|
9995
9995
|
var https2 = __require("https");
|
|
9996
9996
|
var parseUrl = __require("url").parse;
|
|
9997
9997
|
var fs = __require("fs");
|
|
@@ -10263,7 +10263,7 @@ var require_form_data = __commonJS({
|
|
|
10263
10263
|
if (options.protocol == "https:") {
|
|
10264
10264
|
request = https2.request(options);
|
|
10265
10265
|
} else {
|
|
10266
|
-
request =
|
|
10266
|
+
request = http3.request(options);
|
|
10267
10267
|
}
|
|
10268
10268
|
this.getLength(function(err, length) {
|
|
10269
10269
|
if (err && err !== "Unknown stream") {
|
|
@@ -11160,7 +11160,7 @@ var require_follow_redirects = __commonJS({
|
|
|
11160
11160
|
"node_modules/follow-redirects/index.js"(exports, module) {
|
|
11161
11161
|
var url2 = __require("url");
|
|
11162
11162
|
var URL2 = url2.URL;
|
|
11163
|
-
var
|
|
11163
|
+
var http3 = __require("http");
|
|
11164
11164
|
var https2 = __require("https");
|
|
11165
11165
|
var Writable = __require("stream").Writable;
|
|
11166
11166
|
var assert = __require("assert");
|
|
@@ -11646,7 +11646,7 @@ var require_follow_redirects = __commonJS({
|
|
|
11646
11646
|
function isURL(value) {
|
|
11647
11647
|
return URL2 && value instanceof URL2;
|
|
11648
11648
|
}
|
|
11649
|
-
module.exports = wrap2({ http:
|
|
11649
|
+
module.exports = wrap2({ http: http3, https: https2 });
|
|
11650
11650
|
module.exports.wrap = wrap2;
|
|
11651
11651
|
}
|
|
11652
11652
|
});
|
|
@@ -15007,21 +15007,24 @@ var OpenAPISpecLoader = class {
|
|
|
15007
15007
|
}
|
|
15008
15008
|
};
|
|
15009
15009
|
if (op.parameters) {
|
|
15010
|
+
const requiredParams = [];
|
|
15010
15011
|
for (const param of op.parameters) {
|
|
15011
15012
|
if ("name" in param && "in" in param) {
|
|
15012
15013
|
const paramSchema = param.schema;
|
|
15013
|
-
tool.inputSchema.properties
|
|
15014
|
-
|
|
15015
|
-
|
|
15016
|
-
|
|
15014
|
+
if (tool.inputSchema && tool.inputSchema.properties) {
|
|
15015
|
+
tool.inputSchema.properties[param.name] = {
|
|
15016
|
+
type: paramSchema.type || "string",
|
|
15017
|
+
description: param.description || `${param.name} parameter`
|
|
15018
|
+
};
|
|
15019
|
+
}
|
|
15017
15020
|
if (param.required === true) {
|
|
15018
|
-
|
|
15019
|
-
tool.inputSchema.required = [];
|
|
15020
|
-
}
|
|
15021
|
-
tool.inputSchema.required.push(param.name);
|
|
15021
|
+
requiredParams.push(param.name);
|
|
15022
15022
|
}
|
|
15023
15023
|
}
|
|
15024
15024
|
}
|
|
15025
|
+
if (requiredParams.length > 0 && tool.inputSchema) {
|
|
15026
|
+
tool.inputSchema.required = requiredParams;
|
|
15027
|
+
}
|
|
15025
15028
|
}
|
|
15026
15029
|
tools.set(toolId, tool);
|
|
15027
15030
|
}
|
|
@@ -15087,7 +15090,6 @@ var ApiClient = class {
|
|
|
15087
15090
|
* @param headers - Optional headers to include with every request
|
|
15088
15091
|
*/
|
|
15089
15092
|
constructor(baseUrl, headers = {}) {
|
|
15090
|
-
this.baseUrl = baseUrl;
|
|
15091
15093
|
this.headers = headers;
|
|
15092
15094
|
this.axiosInstance = axios_default.create({
|
|
15093
15095
|
baseURL: baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`
|
|
@@ -15159,13 +15161,18 @@ var ApiClient = class {
|
|
|
15159
15161
|
|
|
15160
15162
|
// src/server.ts
|
|
15161
15163
|
var OpenAPIServer = class {
|
|
15164
|
+
server;
|
|
15165
|
+
toolsManager;
|
|
15166
|
+
apiClient;
|
|
15162
15167
|
constructor(config) {
|
|
15163
|
-
this.config = config;
|
|
15164
15168
|
this.server = new Server(
|
|
15165
15169
|
{ name: config.name, version: config.version },
|
|
15166
15170
|
{
|
|
15167
15171
|
capabilities: {
|
|
15168
|
-
tools: {
|
|
15172
|
+
tools: {
|
|
15173
|
+
list: true,
|
|
15174
|
+
execute: true
|
|
15175
|
+
}
|
|
15169
15176
|
}
|
|
15170
15177
|
}
|
|
15171
15178
|
);
|
|
@@ -15173,9 +15180,6 @@ var OpenAPIServer = class {
|
|
|
15173
15180
|
this.apiClient = new ApiClient(config.apiBaseUrl, config.headers);
|
|
15174
15181
|
this.initializeHandlers();
|
|
15175
15182
|
}
|
|
15176
|
-
server;
|
|
15177
|
-
toolsManager;
|
|
15178
|
-
apiClient;
|
|
15179
15183
|
/**
|
|
15180
15184
|
* Initialize request handlers
|
|
15181
15185
|
*/
|
|
@@ -15189,7 +15193,7 @@ var OpenAPIServer = class {
|
|
|
15189
15193
|
const { id, name, arguments: params } = request.params;
|
|
15190
15194
|
console.error("Received request:", request.params);
|
|
15191
15195
|
console.error("Using parameters from arguments:", params);
|
|
15192
|
-
const idOrName = id
|
|
15196
|
+
const idOrName = typeof id === "string" ? id : typeof name === "string" ? name : "";
|
|
15193
15197
|
if (!idOrName) {
|
|
15194
15198
|
throw new Error("Tool ID or name is required");
|
|
15195
15199
|
}
|
|
@@ -20098,7 +20102,22 @@ function parseHeaders(headerStr) {
|
|
|
20098
20102
|
return headers;
|
|
20099
20103
|
}
|
|
20100
20104
|
function loadConfig() {
|
|
20101
|
-
const argv = yargs_default(hideBin(process.argv)).option("
|
|
20105
|
+
const argv = yargs_default(hideBin(process.argv)).option("transport", {
|
|
20106
|
+
alias: "t",
|
|
20107
|
+
type: "string",
|
|
20108
|
+
choices: ["stdio", "http"],
|
|
20109
|
+
description: "Transport type to use (stdio or http)"
|
|
20110
|
+
}).option("port", {
|
|
20111
|
+
alias: "p",
|
|
20112
|
+
type: "number",
|
|
20113
|
+
description: "HTTP port for HTTP transport"
|
|
20114
|
+
}).option("host", {
|
|
20115
|
+
type: "string",
|
|
20116
|
+
description: "HTTP host for HTTP transport"
|
|
20117
|
+
}).option("path", {
|
|
20118
|
+
type: "string",
|
|
20119
|
+
description: "HTTP endpoint path for HTTP transport"
|
|
20120
|
+
}).option("api-base-url", {
|
|
20102
20121
|
alias: "u",
|
|
20103
20122
|
type: "string",
|
|
20104
20123
|
description: "Base URL for the API"
|
|
@@ -20118,7 +20137,16 @@ function loadConfig() {
|
|
|
20118
20137
|
alias: "v",
|
|
20119
20138
|
type: "string",
|
|
20120
20139
|
description: "Server version"
|
|
20121
|
-
}).help().
|
|
20140
|
+
}).help().parseSync();
|
|
20141
|
+
let transportType;
|
|
20142
|
+
if (argv.transport === "http" || process.env.TRANSPORT_TYPE === "http") {
|
|
20143
|
+
transportType = "http";
|
|
20144
|
+
} else {
|
|
20145
|
+
transportType = "stdio";
|
|
20146
|
+
}
|
|
20147
|
+
const httpPort = argv.port ?? (process.env.HTTP_PORT ? parseInt(process.env.HTTP_PORT, 10) : 3e3);
|
|
20148
|
+
const httpHost = argv.host || process.env.HTTP_HOST || "127.0.0.1";
|
|
20149
|
+
const endpointPath = argv.path || process.env.ENDPOINT_PATH || "/mcp";
|
|
20122
20150
|
const apiBaseUrl = argv["api-base-url"] || process.env.API_BASE_URL;
|
|
20123
20151
|
const openApiSpec = argv["openapi-spec"] || process.env.OPENAPI_SPEC_PATH;
|
|
20124
20152
|
if (!apiBaseUrl) {
|
|
@@ -20133,18 +20161,582 @@ function loadConfig() {
|
|
|
20133
20161
|
version: argv.version || process.env.SERVER_VERSION || "1.0.0",
|
|
20134
20162
|
apiBaseUrl,
|
|
20135
20163
|
openApiSpec,
|
|
20136
|
-
headers
|
|
20164
|
+
headers,
|
|
20165
|
+
transportType,
|
|
20166
|
+
httpPort,
|
|
20167
|
+
httpHost,
|
|
20168
|
+
endpointPath
|
|
20137
20169
|
};
|
|
20138
20170
|
}
|
|
20139
20171
|
|
|
20172
|
+
// src/transport/StreamableHttpServerTransport.ts
|
|
20173
|
+
import {
|
|
20174
|
+
isInitializeRequest,
|
|
20175
|
+
isJSONRPCRequest,
|
|
20176
|
+
isJSONRPCResponse
|
|
20177
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
20178
|
+
import * as http2 from "http";
|
|
20179
|
+
import { randomUUID } from "crypto";
|
|
20180
|
+
var StreamableHttpServerTransport = class {
|
|
20181
|
+
// Maps request IDs to session IDs
|
|
20182
|
+
/**
|
|
20183
|
+
* Initialize a new StreamableHttpServerTransport
|
|
20184
|
+
*
|
|
20185
|
+
* @param port HTTP port to listen on
|
|
20186
|
+
* @param host Host to bind to (default: 127.0.0.1)
|
|
20187
|
+
* @param endpointPath Endpoint path (default: /mcp)
|
|
20188
|
+
*/
|
|
20189
|
+
constructor(port, host = "127.0.0.1", endpointPath = "/mcp") {
|
|
20190
|
+
this.port = port;
|
|
20191
|
+
this.host = host;
|
|
20192
|
+
this.endpointPath = endpointPath;
|
|
20193
|
+
this.server = http2.createServer(this.handleRequest.bind(this));
|
|
20194
|
+
}
|
|
20195
|
+
server;
|
|
20196
|
+
sessions = /* @__PURE__ */ new Map();
|
|
20197
|
+
started = false;
|
|
20198
|
+
maxBodySize = 4 * 1024 * 1024;
|
|
20199
|
+
// 4MB max request size
|
|
20200
|
+
requestSessionMap = /* @__PURE__ */ new Map();
|
|
20201
|
+
/**
|
|
20202
|
+
* Callback when message is received
|
|
20203
|
+
*/
|
|
20204
|
+
onmessage;
|
|
20205
|
+
/**
|
|
20206
|
+
* Callback when error occurs
|
|
20207
|
+
*/
|
|
20208
|
+
onerror;
|
|
20209
|
+
/**
|
|
20210
|
+
* Callback when transport closes
|
|
20211
|
+
*/
|
|
20212
|
+
onclose;
|
|
20213
|
+
/**
|
|
20214
|
+
* Start the transport
|
|
20215
|
+
*/
|
|
20216
|
+
async start() {
|
|
20217
|
+
if (this.started) {
|
|
20218
|
+
throw new Error("Transport already started");
|
|
20219
|
+
}
|
|
20220
|
+
return new Promise((resolve5, reject) => {
|
|
20221
|
+
this.server.listen(this.port, this.host, () => {
|
|
20222
|
+
this.started = true;
|
|
20223
|
+
console.error(
|
|
20224
|
+
`Streamable HTTP transport listening on http://${this.host}:${this.port}${this.endpointPath}`
|
|
20225
|
+
);
|
|
20226
|
+
resolve5();
|
|
20227
|
+
});
|
|
20228
|
+
this.server.on("error", (err) => {
|
|
20229
|
+
reject(err);
|
|
20230
|
+
if (this.onerror) {
|
|
20231
|
+
this.onerror(err);
|
|
20232
|
+
}
|
|
20233
|
+
});
|
|
20234
|
+
});
|
|
20235
|
+
}
|
|
20236
|
+
/**
|
|
20237
|
+
* Close the transport
|
|
20238
|
+
*/
|
|
20239
|
+
async close() {
|
|
20240
|
+
for (const session of this.sessions.values()) {
|
|
20241
|
+
for (const response of session.activeResponses) {
|
|
20242
|
+
try {
|
|
20243
|
+
response.end();
|
|
20244
|
+
} catch (err) {
|
|
20245
|
+
}
|
|
20246
|
+
}
|
|
20247
|
+
}
|
|
20248
|
+
this.sessions.clear();
|
|
20249
|
+
return new Promise((resolve5, reject) => {
|
|
20250
|
+
this.server.close((err) => {
|
|
20251
|
+
if (err) {
|
|
20252
|
+
reject(err instanceof Error ? err : new Error(String(err)));
|
|
20253
|
+
} else {
|
|
20254
|
+
this.started = false;
|
|
20255
|
+
if (this.onclose) {
|
|
20256
|
+
this.onclose();
|
|
20257
|
+
}
|
|
20258
|
+
resolve5();
|
|
20259
|
+
}
|
|
20260
|
+
});
|
|
20261
|
+
});
|
|
20262
|
+
}
|
|
20263
|
+
/**
|
|
20264
|
+
* Send message to client(s)
|
|
20265
|
+
*
|
|
20266
|
+
* @param message JSON-RPC message
|
|
20267
|
+
*/
|
|
20268
|
+
async send(message) {
|
|
20269
|
+
console.error(`StreamableHttpServerTransport: Sending message: ${JSON.stringify(message)}`);
|
|
20270
|
+
let targetSessionId;
|
|
20271
|
+
let messageIdForThisResponse = null;
|
|
20272
|
+
if (isJSONRPCResponse(message) && message.id !== null) {
|
|
20273
|
+
messageIdForThisResponse = message.id;
|
|
20274
|
+
targetSessionId = this.requestSessionMap.get(messageIdForThisResponse);
|
|
20275
|
+
console.error(
|
|
20276
|
+
`StreamableHttpServerTransport: Potential target session for response ID ${messageIdForThisResponse}: ${targetSessionId}`
|
|
20277
|
+
);
|
|
20278
|
+
if (targetSessionId && this.initResponseHandlers.has(targetSessionId)) {
|
|
20279
|
+
console.error(
|
|
20280
|
+
`StreamableHttpServerTransport: Session ${targetSessionId} has initResponseHandlers. Invoking them for message ID ${messageIdForThisResponse}.`
|
|
20281
|
+
);
|
|
20282
|
+
const handlers = this.initResponseHandlers.get(targetSessionId);
|
|
20283
|
+
[...handlers].forEach((handler) => handler(message));
|
|
20284
|
+
if (!this.requestSessionMap.has(messageIdForThisResponse)) {
|
|
20285
|
+
console.error(
|
|
20286
|
+
`StreamableHttpServerTransport: Response for ID ${messageIdForThisResponse} was handled by an initResponseHandler (e.g., synchronous POST response for initialize or tools/list).`
|
|
20287
|
+
);
|
|
20288
|
+
return;
|
|
20289
|
+
} else {
|
|
20290
|
+
console.error(
|
|
20291
|
+
`StreamableHttpServerTransport: Response for ID ${messageIdForThisResponse} was NOT exclusively handled by an initResponseHandler or handler did not remove from requestSessionMap. Proceeding to GET stream / broadcast if applicable.`
|
|
20292
|
+
);
|
|
20293
|
+
}
|
|
20294
|
+
}
|
|
20295
|
+
if (this.requestSessionMap.has(messageIdForThisResponse)) {
|
|
20296
|
+
console.error(
|
|
20297
|
+
`StreamableHttpServerTransport: Deleting request ID ${messageIdForThisResponse} from requestSessionMap as it's being processed for GET stream or broadcast.`
|
|
20298
|
+
);
|
|
20299
|
+
this.requestSessionMap.delete(messageIdForThisResponse);
|
|
20300
|
+
}
|
|
20301
|
+
}
|
|
20302
|
+
if (!targetSessionId) {
|
|
20303
|
+
const idForLog = messageIdForThisResponse !== null ? messageIdForThisResponse : isJSONRPCRequest(message) ? message.id : "N/A";
|
|
20304
|
+
console.warn(
|
|
20305
|
+
`StreamableHttpServerTransport: No specific target session for message (ID: ${idForLog}). Broadcasting to all applicable sessions.`
|
|
20306
|
+
);
|
|
20307
|
+
for (const [sid, session2] of this.sessions.entries()) {
|
|
20308
|
+
if (session2.initialized && session2.activeResponses.size > 0) {
|
|
20309
|
+
this.sendMessageToSession(sid, session2, message);
|
|
20310
|
+
}
|
|
20311
|
+
}
|
|
20312
|
+
return;
|
|
20313
|
+
}
|
|
20314
|
+
const session = this.sessions.get(targetSessionId);
|
|
20315
|
+
if (session && session.activeResponses.size > 0) {
|
|
20316
|
+
console.error(
|
|
20317
|
+
`StreamableHttpServerTransport: Sending message (ID: ${messageIdForThisResponse}) to GET stream for session ${targetSessionId} (${session.activeResponses.size} active connections).`
|
|
20318
|
+
);
|
|
20319
|
+
this.sendMessageToSession(targetSessionId, session, message);
|
|
20320
|
+
} else if (targetSessionId) {
|
|
20321
|
+
console.error(
|
|
20322
|
+
`StreamableHttpServerTransport: No active GET connections for session ${targetSessionId} to send message (ID: ${messageIdForThisResponse}). Message might not be delivered if not handled by POST.`
|
|
20323
|
+
);
|
|
20324
|
+
}
|
|
20325
|
+
}
|
|
20326
|
+
/**
|
|
20327
|
+
* Helper method to send a message to a specific session
|
|
20328
|
+
*/
|
|
20329
|
+
sendMessageToSession(sessionId, session, message) {
|
|
20330
|
+
const messageStr = `data: ${JSON.stringify(message)}
|
|
20331
|
+
|
|
20332
|
+
`;
|
|
20333
|
+
for (const response of session.activeResponses) {
|
|
20334
|
+
try {
|
|
20335
|
+
response.write(messageStr);
|
|
20336
|
+
} catch (err) {
|
|
20337
|
+
session.activeResponses.delete(response);
|
|
20338
|
+
if (this.onerror) {
|
|
20339
|
+
this.onerror(new Error(`Failed to write to response: ${err.message}`));
|
|
20340
|
+
}
|
|
20341
|
+
}
|
|
20342
|
+
}
|
|
20343
|
+
}
|
|
20344
|
+
/**
|
|
20345
|
+
* Handle HTTP request
|
|
20346
|
+
*/
|
|
20347
|
+
handleRequest(req, res) {
|
|
20348
|
+
if (req.url !== this.endpointPath) {
|
|
20349
|
+
res.writeHead(404);
|
|
20350
|
+
res.end();
|
|
20351
|
+
return;
|
|
20352
|
+
}
|
|
20353
|
+
this.validateOrigin(req, res);
|
|
20354
|
+
switch (req.method) {
|
|
20355
|
+
case "POST":
|
|
20356
|
+
this.handlePostRequest(req, res);
|
|
20357
|
+
break;
|
|
20358
|
+
case "GET":
|
|
20359
|
+
this.handleGetRequest(req, res);
|
|
20360
|
+
break;
|
|
20361
|
+
case "DELETE":
|
|
20362
|
+
this.handleDeleteRequest(req, res);
|
|
20363
|
+
break;
|
|
20364
|
+
default:
|
|
20365
|
+
res.writeHead(405, { Allow: "POST, GET, DELETE" });
|
|
20366
|
+
res.end(
|
|
20367
|
+
JSON.stringify({
|
|
20368
|
+
jsonrpc: "2.0",
|
|
20369
|
+
error: {
|
|
20370
|
+
code: -32e3,
|
|
20371
|
+
message: "Method not allowed"
|
|
20372
|
+
},
|
|
20373
|
+
id: null
|
|
20374
|
+
})
|
|
20375
|
+
);
|
|
20376
|
+
}
|
|
20377
|
+
}
|
|
20378
|
+
/**
|
|
20379
|
+
* Validate origin header to prevent DNS rebinding attacks
|
|
20380
|
+
*/
|
|
20381
|
+
validateOrigin(req, res) {
|
|
20382
|
+
const origin2 = req.headers.origin;
|
|
20383
|
+
if (!origin2) {
|
|
20384
|
+
return true;
|
|
20385
|
+
}
|
|
20386
|
+
try {
|
|
20387
|
+
const originUrl = new URL(origin2);
|
|
20388
|
+
const isLocalhost = originUrl.hostname === "localhost" || originUrl.hostname === "127.0.0.1";
|
|
20389
|
+
if (!isLocalhost) {
|
|
20390
|
+
res.writeHead(403);
|
|
20391
|
+
res.end(
|
|
20392
|
+
JSON.stringify({
|
|
20393
|
+
jsonrpc: "2.0",
|
|
20394
|
+
error: {
|
|
20395
|
+
code: -32e3,
|
|
20396
|
+
message: "Origin not allowed"
|
|
20397
|
+
},
|
|
20398
|
+
id: null
|
|
20399
|
+
})
|
|
20400
|
+
);
|
|
20401
|
+
return false;
|
|
20402
|
+
}
|
|
20403
|
+
return true;
|
|
20404
|
+
} catch (err) {
|
|
20405
|
+
res.writeHead(400);
|
|
20406
|
+
res.end(
|
|
20407
|
+
JSON.stringify({
|
|
20408
|
+
jsonrpc: "2.0",
|
|
20409
|
+
error: {
|
|
20410
|
+
code: -32e3,
|
|
20411
|
+
message: "Invalid origin"
|
|
20412
|
+
},
|
|
20413
|
+
id: null
|
|
20414
|
+
})
|
|
20415
|
+
);
|
|
20416
|
+
return false;
|
|
20417
|
+
}
|
|
20418
|
+
}
|
|
20419
|
+
/**
|
|
20420
|
+
* Handle POST request
|
|
20421
|
+
*/
|
|
20422
|
+
handlePostRequest(req, res) {
|
|
20423
|
+
const contentType = req.headers["content-type"];
|
|
20424
|
+
if (!contentType || !contentType.includes("application/json")) {
|
|
20425
|
+
res.writeHead(415);
|
|
20426
|
+
res.end(
|
|
20427
|
+
JSON.stringify({
|
|
20428
|
+
jsonrpc: "2.0",
|
|
20429
|
+
error: {
|
|
20430
|
+
code: -32e3,
|
|
20431
|
+
message: "Unsupported Media Type: Content-Type must be application/json"
|
|
20432
|
+
},
|
|
20433
|
+
id: null
|
|
20434
|
+
})
|
|
20435
|
+
);
|
|
20436
|
+
return;
|
|
20437
|
+
}
|
|
20438
|
+
let body = "";
|
|
20439
|
+
let size = 0;
|
|
20440
|
+
req.on("data", (chunk) => {
|
|
20441
|
+
size += chunk.length;
|
|
20442
|
+
if (size > this.maxBodySize) {
|
|
20443
|
+
res.writeHead(413);
|
|
20444
|
+
res.end(
|
|
20445
|
+
JSON.stringify({
|
|
20446
|
+
jsonrpc: "2.0",
|
|
20447
|
+
error: {
|
|
20448
|
+
code: -32e3,
|
|
20449
|
+
message: "Request entity too large"
|
|
20450
|
+
},
|
|
20451
|
+
id: null
|
|
20452
|
+
})
|
|
20453
|
+
);
|
|
20454
|
+
req.destroy();
|
|
20455
|
+
return;
|
|
20456
|
+
}
|
|
20457
|
+
body += chunk.toString();
|
|
20458
|
+
});
|
|
20459
|
+
req.on("end", () => {
|
|
20460
|
+
try {
|
|
20461
|
+
const message = JSON.parse(body);
|
|
20462
|
+
if (isInitializeRequest(message)) {
|
|
20463
|
+
this.handleInitializeRequest(message, req, res);
|
|
20464
|
+
} else if (isJSONRPCRequest(message) && message.method === "tools/list") {
|
|
20465
|
+
const sessionId = req.headers["mcp-session-id"];
|
|
20466
|
+
if (!sessionId || !this.sessions.has(sessionId)) {
|
|
20467
|
+
res.writeHead(400);
|
|
20468
|
+
res.end(
|
|
20469
|
+
JSON.stringify({
|
|
20470
|
+
jsonrpc: "2.0",
|
|
20471
|
+
error: {
|
|
20472
|
+
code: -32e3,
|
|
20473
|
+
message: "Invalid session. A valid Mcp-Session-Id header is required."
|
|
20474
|
+
},
|
|
20475
|
+
id: "id" in message ? message.id : null
|
|
20476
|
+
})
|
|
20477
|
+
);
|
|
20478
|
+
return;
|
|
20479
|
+
}
|
|
20480
|
+
const session = this.sessions.get(sessionId);
|
|
20481
|
+
if (message.id !== void 0 && message.id !== null) {
|
|
20482
|
+
this.requestSessionMap.set(message.id, sessionId);
|
|
20483
|
+
if (!session.pendingRequests) {
|
|
20484
|
+
session.pendingRequests = /* @__PURE__ */ new Set();
|
|
20485
|
+
}
|
|
20486
|
+
session.pendingRequests.add(message.id);
|
|
20487
|
+
}
|
|
20488
|
+
const responseHandler = (responseMessage) => {
|
|
20489
|
+
if (isJSONRPCResponse(responseMessage) && responseMessage.id === message.id) {
|
|
20490
|
+
res.setHeader("Content-Type", "application/json");
|
|
20491
|
+
res.writeHead(200);
|
|
20492
|
+
res.end(JSON.stringify(responseMessage));
|
|
20493
|
+
this.removeInitResponseHandler(sessionId, responseHandler);
|
|
20494
|
+
if (message.id !== void 0 && message.id !== null) {
|
|
20495
|
+
this.requestSessionMap.delete(message.id);
|
|
20496
|
+
session.pendingRequests.delete(message.id);
|
|
20497
|
+
}
|
|
20498
|
+
}
|
|
20499
|
+
};
|
|
20500
|
+
this.addInitResponseHandler(sessionId, responseHandler);
|
|
20501
|
+
if (session.messageHandler) {
|
|
20502
|
+
session.messageHandler(message);
|
|
20503
|
+
} else {
|
|
20504
|
+
this.removeInitResponseHandler(sessionId, responseHandler);
|
|
20505
|
+
res.writeHead(500);
|
|
20506
|
+
res.end(
|
|
20507
|
+
JSON.stringify({
|
|
20508
|
+
jsonrpc: "2.0",
|
|
20509
|
+
error: {
|
|
20510
|
+
code: -32603,
|
|
20511
|
+
message: "Internal error: No message handler available"
|
|
20512
|
+
},
|
|
20513
|
+
id: "id" in message ? message.id : null
|
|
20514
|
+
})
|
|
20515
|
+
);
|
|
20516
|
+
}
|
|
20517
|
+
} else {
|
|
20518
|
+
const sessionId = req.headers["mcp-session-id"];
|
|
20519
|
+
if (!sessionId || !this.sessions.has(sessionId)) {
|
|
20520
|
+
res.writeHead(400);
|
|
20521
|
+
res.end(
|
|
20522
|
+
JSON.stringify({
|
|
20523
|
+
jsonrpc: "2.0",
|
|
20524
|
+
error: {
|
|
20525
|
+
code: -32e3,
|
|
20526
|
+
message: "Invalid session. A valid Mcp-Session-Id header is required."
|
|
20527
|
+
},
|
|
20528
|
+
id: "id" in message ? message.id : null
|
|
20529
|
+
})
|
|
20530
|
+
);
|
|
20531
|
+
return;
|
|
20532
|
+
}
|
|
20533
|
+
const session = this.sessions.get(sessionId);
|
|
20534
|
+
if (isJSONRPCRequest(message)) {
|
|
20535
|
+
if (session.messageHandler) {
|
|
20536
|
+
if (message.id !== void 0 && message.id !== null) {
|
|
20537
|
+
this.requestSessionMap.set(message.id, sessionId);
|
|
20538
|
+
if (!session.pendingRequests) {
|
|
20539
|
+
session.pendingRequests = /* @__PURE__ */ new Set();
|
|
20540
|
+
}
|
|
20541
|
+
session.pendingRequests.add(message.id);
|
|
20542
|
+
}
|
|
20543
|
+
session.messageHandler(message);
|
|
20544
|
+
res.writeHead(202);
|
|
20545
|
+
res.end();
|
|
20546
|
+
}
|
|
20547
|
+
} else {
|
|
20548
|
+
if (session.messageHandler) {
|
|
20549
|
+
session.messageHandler(message);
|
|
20550
|
+
res.writeHead(202);
|
|
20551
|
+
res.end();
|
|
20552
|
+
}
|
|
20553
|
+
}
|
|
20554
|
+
}
|
|
20555
|
+
} catch (err) {
|
|
20556
|
+
res.writeHead(400);
|
|
20557
|
+
res.end(
|
|
20558
|
+
JSON.stringify({
|
|
20559
|
+
jsonrpc: "2.0",
|
|
20560
|
+
error: {
|
|
20561
|
+
code: -32700,
|
|
20562
|
+
message: "Parse error",
|
|
20563
|
+
data: String(err)
|
|
20564
|
+
},
|
|
20565
|
+
id: null
|
|
20566
|
+
})
|
|
20567
|
+
);
|
|
20568
|
+
if (this.onerror) {
|
|
20569
|
+
this.onerror(new Error(`Parse error: ${String(err)}`));
|
|
20570
|
+
}
|
|
20571
|
+
}
|
|
20572
|
+
});
|
|
20573
|
+
req.on("error", (err) => {
|
|
20574
|
+
if (this.onerror) {
|
|
20575
|
+
this.onerror(err instanceof Error ? err : new Error(String(err)));
|
|
20576
|
+
}
|
|
20577
|
+
});
|
|
20578
|
+
}
|
|
20579
|
+
/**
|
|
20580
|
+
* Handle initialization request
|
|
20581
|
+
*/
|
|
20582
|
+
handleInitializeRequest(message, _req, res) {
|
|
20583
|
+
const sessionId = randomUUID();
|
|
20584
|
+
this.sessions.set(sessionId, {
|
|
20585
|
+
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
|
20586
|
+
messageHandler: this.onmessage || (() => {
|
|
20587
|
+
}),
|
|
20588
|
+
activeResponses: /* @__PURE__ */ new Set(),
|
|
20589
|
+
initialized: true,
|
|
20590
|
+
pendingRequests: /* @__PURE__ */ new Set()
|
|
20591
|
+
});
|
|
20592
|
+
if ("id" in message && message.id !== null && message.id !== void 0) {
|
|
20593
|
+
this.requestSessionMap.set(message.id, sessionId);
|
|
20594
|
+
}
|
|
20595
|
+
res.setHeader("Content-Type", "application/json");
|
|
20596
|
+
res.setHeader("Mcp-Session-Id", sessionId);
|
|
20597
|
+
const responseHandler = (responseMessage) => {
|
|
20598
|
+
if (isJSONRPCResponse(responseMessage) && "id" in message && responseMessage.id === message.id) {
|
|
20599
|
+
res.writeHead(200);
|
|
20600
|
+
res.end(JSON.stringify(responseMessage));
|
|
20601
|
+
this.removeInitResponseHandler(sessionId, responseHandler);
|
|
20602
|
+
this.requestSessionMap.delete(message.id);
|
|
20603
|
+
}
|
|
20604
|
+
};
|
|
20605
|
+
this.addInitResponseHandler(sessionId, responseHandler);
|
|
20606
|
+
if (this.onmessage) {
|
|
20607
|
+
this.onmessage(message);
|
|
20608
|
+
} else {
|
|
20609
|
+
this.removeInitResponseHandler(sessionId, responseHandler);
|
|
20610
|
+
res.writeHead(500);
|
|
20611
|
+
res.end(
|
|
20612
|
+
JSON.stringify({
|
|
20613
|
+
jsonrpc: "2.0",
|
|
20614
|
+
error: {
|
|
20615
|
+
code: -32603,
|
|
20616
|
+
message: "Internal error: No message handler available"
|
|
20617
|
+
},
|
|
20618
|
+
id: "id" in message ? message.id : null
|
|
20619
|
+
})
|
|
20620
|
+
);
|
|
20621
|
+
}
|
|
20622
|
+
}
|
|
20623
|
+
/**
|
|
20624
|
+
* Add initialize response handler
|
|
20625
|
+
*/
|
|
20626
|
+
initResponseHandlers = /* @__PURE__ */ new Map();
|
|
20627
|
+
addInitResponseHandler(sessionId, handler) {
|
|
20628
|
+
if (!this.initResponseHandlers.has(sessionId)) {
|
|
20629
|
+
this.initResponseHandlers.set(sessionId, []);
|
|
20630
|
+
}
|
|
20631
|
+
this.initResponseHandlers.get(sessionId).push(handler);
|
|
20632
|
+
}
|
|
20633
|
+
removeInitResponseHandler(sessionId, handler) {
|
|
20634
|
+
if (this.initResponseHandlers.has(sessionId)) {
|
|
20635
|
+
const handlers = this.initResponseHandlers.get(sessionId);
|
|
20636
|
+
const index = handlers.indexOf(handler);
|
|
20637
|
+
if (index !== -1) {
|
|
20638
|
+
handlers.splice(index, 1);
|
|
20639
|
+
}
|
|
20640
|
+
if (handlers.length === 0) {
|
|
20641
|
+
this.initResponseHandlers.delete(sessionId);
|
|
20642
|
+
}
|
|
20643
|
+
}
|
|
20644
|
+
}
|
|
20645
|
+
/**
|
|
20646
|
+
* Handle GET request (streaming connection)
|
|
20647
|
+
*/
|
|
20648
|
+
handleGetRequest(req, res) {
|
|
20649
|
+
const sessionId = req.headers["mcp-session-id"];
|
|
20650
|
+
if (!sessionId || !this.sessions.has(sessionId)) {
|
|
20651
|
+
res.writeHead(400);
|
|
20652
|
+
res.end(
|
|
20653
|
+
JSON.stringify({
|
|
20654
|
+
jsonrpc: "2.0",
|
|
20655
|
+
error: {
|
|
20656
|
+
code: -32e3,
|
|
20657
|
+
message: "Invalid session. A valid Mcp-Session-Id header is required."
|
|
20658
|
+
},
|
|
20659
|
+
id: null
|
|
20660
|
+
})
|
|
20661
|
+
);
|
|
20662
|
+
return;
|
|
20663
|
+
}
|
|
20664
|
+
const session = this.sessions.get(sessionId);
|
|
20665
|
+
res.setHeader("Content-Type", "text/event-stream");
|
|
20666
|
+
res.setHeader("Connection", "keep-alive");
|
|
20667
|
+
res.setHeader("Cache-Control", "no-cache, no-transform");
|
|
20668
|
+
res.setHeader("Transfer-Encoding", "chunked");
|
|
20669
|
+
res.setHeader("Mcp-Session-Id", sessionId);
|
|
20670
|
+
res.writeHead(200);
|
|
20671
|
+
session.activeResponses.add(res);
|
|
20672
|
+
req.on("close", () => {
|
|
20673
|
+
session.activeResponses.delete(res);
|
|
20674
|
+
});
|
|
20675
|
+
res.on("error", (err) => {
|
|
20676
|
+
session.activeResponses.delete(res);
|
|
20677
|
+
if (this.onerror) {
|
|
20678
|
+
this.onerror(err);
|
|
20679
|
+
}
|
|
20680
|
+
});
|
|
20681
|
+
}
|
|
20682
|
+
/**
|
|
20683
|
+
* Handle DELETE request (session termination)
|
|
20684
|
+
*/
|
|
20685
|
+
handleDeleteRequest(req, res) {
|
|
20686
|
+
const sessionId = req.headers["mcp-session-id"];
|
|
20687
|
+
if (!sessionId || !this.sessions.has(sessionId)) {
|
|
20688
|
+
res.writeHead(400);
|
|
20689
|
+
res.end(
|
|
20690
|
+
JSON.stringify({
|
|
20691
|
+
jsonrpc: "2.0",
|
|
20692
|
+
error: {
|
|
20693
|
+
code: -32e3,
|
|
20694
|
+
message: "Invalid session. A valid Mcp-Session-Id header is required."
|
|
20695
|
+
},
|
|
20696
|
+
id: null
|
|
20697
|
+
})
|
|
20698
|
+
);
|
|
20699
|
+
return;
|
|
20700
|
+
}
|
|
20701
|
+
const session = this.sessions.get(sessionId);
|
|
20702
|
+
for (const response of session.activeResponses) {
|
|
20703
|
+
try {
|
|
20704
|
+
response.end();
|
|
20705
|
+
} catch (err) {
|
|
20706
|
+
}
|
|
20707
|
+
}
|
|
20708
|
+
if (session.pendingRequests) {
|
|
20709
|
+
for (const requestId of session.pendingRequests) {
|
|
20710
|
+
this.requestSessionMap.delete(requestId);
|
|
20711
|
+
}
|
|
20712
|
+
}
|
|
20713
|
+
this.sessions.delete(sessionId);
|
|
20714
|
+
res.writeHead(204);
|
|
20715
|
+
res.end();
|
|
20716
|
+
}
|
|
20717
|
+
};
|
|
20718
|
+
|
|
20140
20719
|
// src/index.ts
|
|
20141
20720
|
async function main() {
|
|
20142
20721
|
try {
|
|
20143
20722
|
const config = loadConfig();
|
|
20144
20723
|
const server = new OpenAPIServer(config);
|
|
20145
|
-
|
|
20146
|
-
|
|
20147
|
-
|
|
20724
|
+
let transport;
|
|
20725
|
+
if (config.transportType === "http") {
|
|
20726
|
+
transport = new StreamableHttpServerTransport(
|
|
20727
|
+
config.httpPort,
|
|
20728
|
+
config.httpHost,
|
|
20729
|
+
config.endpointPath
|
|
20730
|
+
);
|
|
20731
|
+
await server.start(transport);
|
|
20732
|
+
console.error(
|
|
20733
|
+
`OpenAPI MCP Server running on http://${config.httpHost}:${config.httpPort}${config.endpointPath}`
|
|
20734
|
+
);
|
|
20735
|
+
} else {
|
|
20736
|
+
transport = new StdioServerTransport();
|
|
20737
|
+
await server.start(transport);
|
|
20738
|
+
console.error("OpenAPI MCP Server running on stdio");
|
|
20739
|
+
}
|
|
20148
20740
|
} catch (error) {
|
|
20149
20741
|
console.error("Failed to start server:", error);
|
|
20150
20742
|
process.exit(1);
|
|
@@ -20155,6 +20747,7 @@ export {
|
|
|
20155
20747
|
ApiClient,
|
|
20156
20748
|
OpenAPIServer,
|
|
20157
20749
|
OpenAPISpecLoader,
|
|
20750
|
+
StreamableHttpServerTransport,
|
|
20158
20751
|
ToolsManager,
|
|
20159
20752
|
loadConfig,
|
|
20160
20753
|
parseHeaders
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ivotoby/openapi-mcp-server",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "An MCP server that exposes OpenAPI endpoints as resources",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -44,10 +44,8 @@
|
|
|
44
44
|
"@semantic-release/github": "^9.2.6",
|
|
45
45
|
"@semantic-release/npm": "^11.0.3",
|
|
46
46
|
"@semantic-release/release-notes-generator": "^12.1.0",
|
|
47
|
-
"@types/chai": "^4.3.11",
|
|
48
|
-
"@types/mocha": "^10.0.6",
|
|
49
47
|
"@types/node": "^22.13.11",
|
|
50
|
-
"@types/
|
|
48
|
+
"@types/yargs": "^17.0.33",
|
|
51
49
|
"@typescript-eslint/eslint-plugin": "^6.12.0",
|
|
52
50
|
"@typescript-eslint/parser": "^6.12.0",
|
|
53
51
|
"dotenv": "^16.4.7",
|
|
@@ -56,13 +54,10 @@
|
|
|
56
54
|
"eslint-config-prettier": "^10.1.5",
|
|
57
55
|
"eslint-plugin-perfectionist": "^4.7.0",
|
|
58
56
|
"eslint-plugin-prettier": "^5.4.0",
|
|
59
|
-
"jest": "^29.7.0",
|
|
60
57
|
"msw": "^2.7.0",
|
|
61
58
|
"nodemon": "^3.1.7",
|
|
62
59
|
"prettier": "^3.4.2",
|
|
63
60
|
"semantic-release": "^22.0.12",
|
|
64
|
-
"sinon": "^17.0.1",
|
|
65
|
-
"ts-jest": "^29.1.1",
|
|
66
61
|
"typescript": "^5.3.2",
|
|
67
62
|
"typescript-eslint": "^8.22.0",
|
|
68
63
|
"vitest": "^3.1.3"
|