@smartbear/mcp 0.12.0 → 0.13.1
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 +30 -6
- package/dist/bugsnag/client/api/CurrentUser.js +50 -26
- package/dist/bugsnag/client/api/Error.js +158 -57
- package/dist/bugsnag/client/api/Project.js +398 -243
- package/dist/bugsnag/client/api/api.js +4087 -3837
- package/dist/bugsnag/client/api/base.js +155 -173
- package/dist/bugsnag/client/api/configuration.js +28 -25
- package/dist/bugsnag/client/filters.js +11 -20
- package/dist/bugsnag/client.js +1398 -1277
- package/dist/bugsnag/input-schemas.js +39 -57
- package/dist/collaborator/client.js +335 -371
- package/dist/common/bugsnag.js +5 -3
- package/dist/common/cache.js +50 -57
- package/dist/common/client-registry.js +106 -119
- package/dist/common/info.js +7 -3
- package/dist/common/register-clients.js +0 -16
- package/dist/common/server.js +270 -228
- package/dist/common/tools.js +19 -0
- package/dist/common/transport-http.js +252 -343
- package/dist/common/transport-stdio.js +40 -37
- package/dist/common/zod-utils.js +20 -0
- package/dist/index.js +18 -23
- package/dist/package.json.js +11 -0
- package/dist/pactflow/client/ai.js +142 -169
- package/dist/pactflow/client/base.js +41 -51
- package/dist/pactflow/client/prompt-utils.js +93 -84
- package/dist/pactflow/client/prompts.js +95 -92
- package/dist/pactflow/client/tools.js +94 -83
- package/dist/pactflow/client/utils.js +60 -64
- package/dist/pactflow/client.js +399 -320
- package/dist/qmetry/client/api/client-api.js +43 -41
- package/dist/qmetry/client/api/error-handler.js +264 -310
- package/dist/qmetry/client/auto-resolve.js +78 -99
- package/dist/qmetry/client/automation.js +139 -162
- package/dist/qmetry/client/handlers.js +49 -46
- package/dist/qmetry/client/issues.js +133 -115
- package/dist/qmetry/client/project.js +153 -174
- package/dist/qmetry/client/requirement.js +82 -70
- package/dist/qmetry/client/testcase.js +240 -208
- package/dist/qmetry/client/testsuite.js +332 -293
- package/dist/qmetry/client/tools/automation-tools.js +291 -288
- package/dist/qmetry/client/tools/index.js +16 -13
- package/dist/qmetry/client/tools/issue-tools.js +534 -543
- package/dist/qmetry/client/tools/project-tools.js +635 -656
- package/dist/qmetry/client/tools/requirement-tools.js +525 -528
- package/dist/qmetry/client/tools/testcase-tools.js +773 -786
- package/dist/qmetry/client/tools/testsuite-tools.js +1069 -1083
- package/dist/qmetry/client/utils.js +8 -14
- package/dist/qmetry/client.js +111 -109
- package/dist/qmetry/config/constants.js +48 -44
- package/dist/qmetry/config/rest-endpoints.js +51 -48
- package/dist/qmetry/types/automation.js +7 -7
- package/dist/qmetry/types/common.js +763 -1049
- package/dist/qmetry/types/issues.js +26 -19
- package/dist/qmetry/types/project.js +32 -25
- package/dist/qmetry/types/requirements.js +26 -21
- package/dist/qmetry/types/testcase.js +55 -44
- package/dist/qmetry/types/testsuite.js +66 -52
- package/dist/reflect/client.js +284 -226
- package/dist/swagger/client/api.js +645 -662
- package/dist/swagger/client/configuration.js +31 -33
- package/dist/swagger/client/portal-types.js +204 -244
- package/dist/swagger/client/registry-types.js +62 -96
- package/dist/swagger/client/tools.js +148 -158
- package/dist/swagger/client/user-management-types.js +11 -22
- package/dist/swagger/client.js +143 -135
- package/dist/swagger/config-utils.js +10 -16
- package/dist/zephyr/client.js +43 -42
- package/dist/zephyr/common/api-client.js +35 -30
- package/dist/zephyr/common/auth-service.js +16 -13
- package/dist/zephyr/common/rest-api-schemas.js +3173 -5146
- package/dist/zephyr/tool/environment/get-environments.js +66 -66
- package/dist/zephyr/tool/priority/get-priorities.js +41 -41
- package/dist/zephyr/tool/project/get-project.js +37 -37
- package/dist/zephyr/tool/project/get-projects.js +46 -46
- package/dist/zephyr/tool/status/get-statuses.js +47 -47
- package/dist/zephyr/tool/test-case/get-test-case.js +37 -37
- package/dist/zephyr/tool/test-case/get-test-cases.js +62 -62
- package/dist/zephyr/tool/test-cycle/get-test-cycle.js +37 -37
- package/dist/zephyr/tool/test-cycle/get-test-cycles.js +70 -70
- package/dist/zephyr/tool/test-execution/get-test-execution.js +37 -37
- package/dist/zephyr/tool/test-execution/get-test-executions.js +43 -43
- package/package.json +5 -5
- package/dist/bugsnag/client/api/index.js +0 -6
- package/dist/common/types.js +0 -6
- package/dist/qmetry/client/tools/types.js +0 -1
- package/dist/swagger/client/index.js +0 -6
- package/dist/tests/unit/bugsnag/utils/factories.js +0 -86
- package/dist/zephyr/tool/zephyr-tool.js +0 -1
|
@@ -5,373 +5,282 @@ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/
|
|
|
5
5
|
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
|
|
6
6
|
import { clientRegistry } from "./client-registry.js";
|
|
7
7
|
import { SmartBearMcpServer } from "./server.js";
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
8
|
+
import { isOptionalType } from "./zod-utils.js";
|
|
9
|
+
async function runHttpMode() {
|
|
10
|
+
const PORT = process.env.PORT ? Number.parseInt(process.env.PORT, 10) : 3e3;
|
|
11
|
+
const allowedOrigins = process.env.ALLOWED_ORIGINS?.split(",") || [
|
|
12
|
+
"http://localhost:3000"
|
|
13
|
+
];
|
|
14
|
+
const transports = /* @__PURE__ */ new Map();
|
|
15
|
+
const allowedAuthHeaders = getHttpHeaders();
|
|
16
|
+
const allowedHeaders = [
|
|
17
|
+
"Content-Type",
|
|
18
|
+
"Authorization",
|
|
19
|
+
"MCP-Session-Id",
|
|
20
|
+
// Required for StreamableHTTP
|
|
21
|
+
"x-custom-auth-headers",
|
|
22
|
+
// used by mcp-inspector
|
|
23
|
+
...allowedAuthHeaders
|
|
24
|
+
].join(", ");
|
|
25
|
+
const httpServer = createServer(
|
|
26
|
+
async (req, res) => {
|
|
27
|
+
const origin = req.headers.origin || "";
|
|
28
|
+
if (allowedOrigins.includes(origin)) {
|
|
29
|
+
res.setHeader("Access-Control-Allow-Origin", origin);
|
|
30
|
+
}
|
|
31
|
+
res.setHeader(
|
|
32
|
+
"Access-Control-Allow-Methods",
|
|
33
|
+
"GET, POST, DELETE, OPTIONS"
|
|
34
|
+
);
|
|
35
|
+
res.setHeader("Access-Control-Allow-Headers", allowedHeaders);
|
|
36
|
+
res.setHeader("Access-Control-Expose-Headers", "MCP-Session-Id");
|
|
37
|
+
if (req.method === "OPTIONS") {
|
|
38
|
+
res.writeHead(200);
|
|
39
|
+
res.end();
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const url = new URL(req.url || "/", `http://${req.headers.host}`);
|
|
43
|
+
if (req.method === "GET" && url.pathname === "/health") {
|
|
44
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
45
|
+
res.end(
|
|
46
|
+
JSON.stringify({ status: "ok", timestamp: (/* @__PURE__ */ new Date()).toISOString() })
|
|
47
|
+
);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (url.pathname === "/mcp") {
|
|
51
|
+
await handleStreamableHttpRequest(req, res, transports);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (req.method === "GET" && url.pathname === "/sse") {
|
|
55
|
+
await handleLegacySseRequest(req, res, transports);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (req.method === "POST" && url.pathname === "/message") {
|
|
59
|
+
await handleLegacyMessageRequest(req, res, url, transports);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
63
|
+
res.end("Not found");
|
|
64
|
+
}
|
|
65
|
+
);
|
|
66
|
+
httpServer.listen(PORT, () => {
|
|
67
|
+
console.log(`[MCP HTTP Server] Listening on http://localhost:${PORT}`);
|
|
68
|
+
console.log(
|
|
69
|
+
`[MCP HTTP Server] Health check: http://localhost:${PORT}/health`
|
|
70
|
+
);
|
|
71
|
+
console.log(
|
|
72
|
+
`[MCP HTTP Server] Modern endpoint: http://localhost:${PORT}/mcp (Streamable HTTP)`
|
|
73
|
+
);
|
|
74
|
+
console.log(
|
|
75
|
+
`[MCP HTTP Server] Legacy endpoint: http://localhost:${PORT}/sse (SSE)`
|
|
76
|
+
);
|
|
77
|
+
const headerHelp = getHttpHeadersHelp();
|
|
78
|
+
if (headerHelp.length > 0) {
|
|
79
|
+
console.log(
|
|
80
|
+
`[MCP HTTP Server] Send configuration headers:
|
|
81
|
+
${headerHelp.join("\n")}`
|
|
82
|
+
);
|
|
83
|
+
} else {
|
|
84
|
+
console.warn(
|
|
85
|
+
`[MCP HTTP Server] No clients support HTTP header configuration`
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
79
89
|
}
|
|
80
|
-
/**
|
|
81
|
-
* Parse request body for POST requests
|
|
82
|
-
* Reads the request stream and parses it as JSON
|
|
83
|
-
* @returns Parsed JSON object or undefined if not a POST request or parsing fails
|
|
84
|
-
*/
|
|
85
90
|
async function parseRequestBody(req) {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
}
|
|
102
|
-
});
|
|
91
|
+
if (req.method !== "POST") {
|
|
92
|
+
return void 0;
|
|
93
|
+
}
|
|
94
|
+
let body = "";
|
|
95
|
+
req.on("data", (chunk) => {
|
|
96
|
+
body += chunk.toString();
|
|
97
|
+
});
|
|
98
|
+
return new Promise((resolve) => {
|
|
99
|
+
req.on("end", () => {
|
|
100
|
+
try {
|
|
101
|
+
resolve(JSON.parse(body));
|
|
102
|
+
} catch (error) {
|
|
103
|
+
console.error("Error parsing request body:", error);
|
|
104
|
+
resolve(void 0);
|
|
105
|
+
}
|
|
103
106
|
});
|
|
107
|
+
});
|
|
104
108
|
}
|
|
105
|
-
/**
|
|
106
|
-
* Get existing transport for session or return error response
|
|
107
|
-
* Validates that the session exists and uses StreamableHTTP transport
|
|
108
|
-
* @returns StreamableHTTPServerTransport if valid, null otherwise (with error response sent)
|
|
109
|
-
*/
|
|
110
109
|
function getExistingTransport(sessionId, transports, res) {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
})
|
|
125
|
-
|
|
110
|
+
const existing = transports.get(sessionId);
|
|
111
|
+
if (existing && existing.transport instanceof StreamableHTTPServerTransport) {
|
|
112
|
+
return existing.transport;
|
|
113
|
+
}
|
|
114
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
115
|
+
res.end(
|
|
116
|
+
JSON.stringify({
|
|
117
|
+
jsonrpc: "2.0",
|
|
118
|
+
error: {
|
|
119
|
+
code: -32e3,
|
|
120
|
+
message: "Bad Request: Session exists but uses a different transport protocol"
|
|
121
|
+
},
|
|
122
|
+
id: null
|
|
123
|
+
})
|
|
124
|
+
);
|
|
125
|
+
return null;
|
|
126
126
|
}
|
|
127
|
-
/**
|
|
128
|
-
* Create new transport for initialize request
|
|
129
|
-
* Sets up a new MCP server instance with configuration from HTTP headers,
|
|
130
|
-
* creates a StreamableHTTP transport, and registers session lifecycle handlers
|
|
131
|
-
* @returns StreamableHTTPServerTransport if successful, null if server initialization fails
|
|
132
|
-
*/
|
|
133
127
|
async function createNewTransport(req, res, transports) {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
128
|
+
const server = await newServer(req, res);
|
|
129
|
+
if (!server) {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
const transport = new StreamableHTTPServerTransport({
|
|
133
|
+
sessionIdGenerator: () => randomUUID(),
|
|
134
|
+
onsessioninitialized: (newSessionId) => {
|
|
135
|
+
console.log(`[MCP] New session initialized: ${newSessionId}`);
|
|
136
|
+
transports.set(newSessionId, { server, transport });
|
|
138
137
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
// Clean up session on close
|
|
149
|
-
transport.onclose = () => {
|
|
150
|
-
if (transport.sessionId) {
|
|
151
|
-
console.log(`[MCP] Session closed: ${transport.sessionId}`);
|
|
152
|
-
transports.delete(transport.sessionId);
|
|
153
|
-
}
|
|
154
|
-
};
|
|
155
|
-
// Connect server to transport to start handling messages
|
|
156
|
-
await server.connect(transport);
|
|
157
|
-
return transport;
|
|
138
|
+
});
|
|
139
|
+
transport.onclose = () => {
|
|
140
|
+
if (transport.sessionId) {
|
|
141
|
+
console.log(`[MCP] Session closed: ${transport.sessionId}`);
|
|
142
|
+
transports.delete(transport.sessionId);
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
await server.connect(transport);
|
|
146
|
+
return transport;
|
|
158
147
|
}
|
|
159
|
-
/**
|
|
160
|
-
* Handle modern Streamable HTTP requests
|
|
161
|
-
* This is the main endpoint (/mcp) for the modern MCP StreamableHTTP transport.
|
|
162
|
-
*
|
|
163
|
-
* Request flow:
|
|
164
|
-
* 1. First request (initialize): No session ID, body contains initialize request
|
|
165
|
-
* - Creates new server + transport, generates session ID
|
|
166
|
-
* 2. Subsequent requests: Include MCP-Session-Id header
|
|
167
|
-
* - Routes to existing transport for the session
|
|
168
|
-
*/
|
|
169
148
|
async function handleStreamableHttpRequest(req, res, transports) {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
},
|
|
200
|
-
id: null,
|
|
201
|
-
}));
|
|
202
|
-
return;
|
|
203
|
-
}
|
|
204
|
-
// Delegate to transport to handle the MCP protocol message
|
|
205
|
-
await transport.handleRequest(req, res, parsedBody);
|
|
206
|
-
}
|
|
207
|
-
catch (error) {
|
|
208
|
-
console.error("Error handling StreamableHTTP request:", error);
|
|
209
|
-
res.writeHead(500, { "Content-Type": "text/plain" });
|
|
210
|
-
res.end("Internal server error");
|
|
149
|
+
try {
|
|
150
|
+
const sessionId = req.headers["mcp-session-id"];
|
|
151
|
+
const parsedBody = await parseRequestBody(req);
|
|
152
|
+
let transport;
|
|
153
|
+
if (sessionId && transports.has(sessionId)) {
|
|
154
|
+
const existingTransport = getExistingTransport(
|
|
155
|
+
sessionId,
|
|
156
|
+
transports,
|
|
157
|
+
res
|
|
158
|
+
);
|
|
159
|
+
if (!existingTransport) return;
|
|
160
|
+
transport = existingTransport;
|
|
161
|
+
} else if (!sessionId && req.method === "POST" && parsedBody && isInitializeRequest(parsedBody)) {
|
|
162
|
+
const newTransport = await createNewTransport(req, res, transports);
|
|
163
|
+
if (!newTransport) return;
|
|
164
|
+
transport = newTransport;
|
|
165
|
+
} else {
|
|
166
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
167
|
+
res.end(
|
|
168
|
+
JSON.stringify({
|
|
169
|
+
jsonrpc: "2.0",
|
|
170
|
+
error: {
|
|
171
|
+
code: -32e3,
|
|
172
|
+
message: "Bad Request: Invalid request"
|
|
173
|
+
},
|
|
174
|
+
id: null
|
|
175
|
+
})
|
|
176
|
+
);
|
|
177
|
+
return;
|
|
211
178
|
}
|
|
179
|
+
await transport.handleRequest(req, res, parsedBody);
|
|
180
|
+
} catch (error) {
|
|
181
|
+
console.error("Error handling StreamableHTTP request:", error);
|
|
182
|
+
res.writeHead(500, { "Content-Type": "text/plain" });
|
|
183
|
+
res.end("Internal server error");
|
|
184
|
+
}
|
|
212
185
|
}
|
|
213
|
-
/**
|
|
214
|
-
* Handle legacy SSE connection requests (GET /sse)
|
|
215
|
-
*
|
|
216
|
-
* SSE (Server-Sent Events) transport maintains a long-lived connection
|
|
217
|
-
* for server-to-client messages, with a separate POST endpoint for client-to-server.
|
|
218
|
-
*
|
|
219
|
-
* This is kept for backwards compatibility with older MCP clients.
|
|
220
|
-
* New integrations should use the modern StreamableHTTP transport (/mcp).
|
|
221
|
-
*/
|
|
222
186
|
async function handleLegacySseRequest(req, res, transports) {
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
res.on("close", () => {
|
|
234
|
-
transports.delete(transport.sessionId);
|
|
235
|
-
});
|
|
236
|
-
// Connect server to transport (this also starts the transport automatically)
|
|
237
|
-
await server.connect(transport);
|
|
187
|
+
const server = await newServer(req, res);
|
|
188
|
+
if (!server) {
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
const transport = new SSEServerTransport("/message", res);
|
|
192
|
+
transports.set(transport.sessionId, { server, transport });
|
|
193
|
+
res.on("close", () => {
|
|
194
|
+
transports.delete(transport.sessionId);
|
|
195
|
+
});
|
|
196
|
+
await server.connect(transport);
|
|
238
197
|
}
|
|
239
|
-
/**
|
|
240
|
-
* Handle legacy POST message requests (POST /message?sessionId=xxx)
|
|
241
|
-
*
|
|
242
|
-
* This endpoint is part of the legacy SSE transport, handling client-to-server messages.
|
|
243
|
-
* The SSE transport uses:
|
|
244
|
-
* - GET /sse: Server-to-client events (long-lived connection)
|
|
245
|
-
* - POST /message: Client-to-server messages (individual requests)
|
|
246
|
-
*
|
|
247
|
-
* New integrations should use the modern StreamableHTTP transport (/mcp).
|
|
248
|
-
*/
|
|
249
198
|
async function handleLegacyMessageRequest(req, res, url, transports) {
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
199
|
+
const sessionId = url.searchParams.get("sessionId");
|
|
200
|
+
if (!sessionId) {
|
|
201
|
+
res.writeHead(400, { "Content-Type": "text/plain" });
|
|
202
|
+
res.end("Missing sessionId parameter");
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
const session = transports.get(sessionId);
|
|
206
|
+
if (!session) {
|
|
207
|
+
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
208
|
+
res.end("Session not found");
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
if (!(session.transport instanceof SSEServerTransport)) {
|
|
212
|
+
res.writeHead(400, { "Content-Type": "text/plain" });
|
|
213
|
+
res.end("Invalid transport for this endpoint");
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
let body = "";
|
|
217
|
+
req.on("data", (chunk) => {
|
|
218
|
+
body += chunk.toString();
|
|
219
|
+
});
|
|
220
|
+
req.on("end", async () => {
|
|
221
|
+
try {
|
|
222
|
+
const parsedBody = JSON.parse(body);
|
|
223
|
+
await session.transport.handlePostMessage(
|
|
224
|
+
req,
|
|
225
|
+
res,
|
|
226
|
+
parsedBody
|
|
227
|
+
);
|
|
228
|
+
} catch (error) {
|
|
229
|
+
console.error("Error handling POST message:", error);
|
|
230
|
+
res.writeHead(500, { "Content-Type": "text/plain" });
|
|
231
|
+
res.end("Internal server error");
|
|
269
232
|
}
|
|
270
|
-
|
|
271
|
-
let body = "";
|
|
272
|
-
req.on("data", (chunk) => {
|
|
273
|
-
body += chunk.toString();
|
|
274
|
-
});
|
|
275
|
-
req.on("end", async () => {
|
|
276
|
-
try {
|
|
277
|
-
const parsedBody = JSON.parse(body);
|
|
278
|
-
// Route message to the SSE transport for processing
|
|
279
|
-
await session.transport.handlePostMessage(req, res, parsedBody);
|
|
280
|
-
}
|
|
281
|
-
catch (error) {
|
|
282
|
-
console.error("Error handling POST message:", error);
|
|
283
|
-
res.writeHead(500, { "Content-Type": "text/plain" });
|
|
284
|
-
res.end("Internal server error");
|
|
285
|
-
}
|
|
286
|
-
});
|
|
233
|
+
});
|
|
287
234
|
}
|
|
288
|
-
/**
|
|
289
|
-
* Create a new MCP server instance with configuration from HTTP headers
|
|
290
|
-
*
|
|
291
|
-
* Configuration is read from HTTP headers in the format:
|
|
292
|
-
* {ClientPrefix}-{Field-Name} (e.g., Bugsnag-Auth-Token, Reflect-Api-Token)
|
|
293
|
-
*
|
|
294
|
-
* The ClientRegistry validates the configuration and initializes enabled clients.
|
|
295
|
-
* If configuration fails, an error response is sent and null is returned.
|
|
296
|
-
*
|
|
297
|
-
* @returns SmartBearMcpServer instance if successful, null if configuration fails
|
|
298
|
-
*/
|
|
299
235
|
async function newServer(req, res) {
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
: "No clients support HTTP header configuration.";
|
|
320
|
-
res.writeHead(401, { "Content-Type": "text/plain" });
|
|
321
|
-
res.end(errorMessage);
|
|
322
|
-
return null;
|
|
323
|
-
}
|
|
324
|
-
return server;
|
|
236
|
+
const server = new SmartBearMcpServer();
|
|
237
|
+
try {
|
|
238
|
+
await clientRegistry.configure(server, (client, key) => {
|
|
239
|
+
const headerName = getHeaderName(client, key);
|
|
240
|
+
const value = req.headers[headerName] || req.headers[headerName.toLowerCase()];
|
|
241
|
+
if (typeof value === "string") {
|
|
242
|
+
return value;
|
|
243
|
+
}
|
|
244
|
+
return null;
|
|
245
|
+
});
|
|
246
|
+
} catch (error) {
|
|
247
|
+
const headerHelp = getHttpHeadersHelp();
|
|
248
|
+
const errorMessage = headerHelp.length > 0 ? `Configuration error: ${error instanceof Error ? error.message : String(error)}. Please provide valid headers:
|
|
249
|
+
${headerHelp.join("\n")}` : "No clients support HTTP header configuration.";
|
|
250
|
+
res.writeHead(401, { "Content-Type": "text/plain" });
|
|
251
|
+
res.end(errorMessage);
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
return server;
|
|
325
255
|
}
|
|
326
|
-
/**
|
|
327
|
-
* Convert a config key to HTTP header name format
|
|
328
|
-
*
|
|
329
|
-
* Examples:
|
|
330
|
-
* - auth_token -> Auth-Token
|
|
331
|
-
* - project_api_key -> Project-Api-Key
|
|
332
|
-
* - base_url -> Base-Url
|
|
333
|
-
*
|
|
334
|
-
* Combined with configPrefix: Bugsnag-Auth-Token, Reflect-Api-Token, etc.
|
|
335
|
-
*
|
|
336
|
-
* @param client The client instance (provides configPrefix)
|
|
337
|
-
* @param key The config key in snake_case
|
|
338
|
-
* @returns Header name in format: {ConfigPrefix}-{Pascal-Kebab-Case}
|
|
339
|
-
*/
|
|
340
256
|
function getHeaderName(client, key) {
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
.join("-")}`;
|
|
257
|
+
return `${client.configPrefix}-${key.split("_").map(
|
|
258
|
+
(part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase()
|
|
259
|
+
).join("-")}`;
|
|
345
260
|
}
|
|
346
|
-
/**
|
|
347
|
-
* Get all HTTP headers that clients support for authentication
|
|
348
|
-
* Returns a list of header names (in kebab-case) that should be allowed
|
|
349
|
-
*/
|
|
350
261
|
function getHttpHeaders() {
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
for (const
|
|
354
|
-
|
|
355
|
-
headers.add(getHeaderName(entry, configKey));
|
|
356
|
-
}
|
|
262
|
+
const headers = /* @__PURE__ */ new Set();
|
|
263
|
+
for (const entry of clientRegistry.getAll()) {
|
|
264
|
+
for (const configKey of Object.keys(entry.config.shape)) {
|
|
265
|
+
headers.add(getHeaderName(entry, configKey));
|
|
357
266
|
}
|
|
358
|
-
|
|
267
|
+
}
|
|
268
|
+
return Array.from(headers).sort((a, b) => a.localeCompare(b));
|
|
359
269
|
}
|
|
360
|
-
/**
|
|
361
|
-
* Get human-readable list of HTTP headers for logging/error messages
|
|
362
|
-
* Organized by client
|
|
363
|
-
*/
|
|
364
270
|
function getHttpHeadersHelp() {
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
}
|
|
271
|
+
const messages = [];
|
|
272
|
+
for (const entry of clientRegistry.getAll()) {
|
|
273
|
+
messages.push(` - ${entry.name}:`);
|
|
274
|
+
for (const [configKey, requirement] of Object.entries(entry.config.shape)) {
|
|
275
|
+
const headerName = getHeaderName(entry, configKey);
|
|
276
|
+
const requiredTag = isOptionalType(requirement) ? " (optional)" : " (required)";
|
|
277
|
+
messages.push(
|
|
278
|
+
` - ${headerName}${requiredTag}: ${requirement.description}`
|
|
279
|
+
);
|
|
375
280
|
}
|
|
376
|
-
|
|
281
|
+
}
|
|
282
|
+
return messages;
|
|
377
283
|
}
|
|
284
|
+
export {
|
|
285
|
+
runHttpMode
|
|
286
|
+
};
|