@smartbear/mcp 0.17.0 → 0.18.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/dist/bugsnag/client.js +43 -4
- package/dist/collaborator/client.js +15 -4
- package/dist/common/request-context.js +20 -0
- package/dist/common/server.js +3 -0
- package/dist/common/transport-http.js +77 -14
- package/dist/common/transport-stdio.js +1 -0
- package/dist/package.json.js +1 -1
- package/dist/pactflow/client/base.js +433 -2
- package/dist/pactflow/client/tools.js +765 -1
- package/dist/pactflow/client.js +1698 -19
- package/dist/qmetry/client.js +10 -2
- package/dist/reflect/client.js +26 -12
- package/dist/reflect/tool/recording/connect-to-session.js +1 -0
- package/dist/swagger/client/api.js +5 -2
- package/dist/swagger/client/configuration.js +15 -7
- package/dist/swagger/client.js +16 -2
- package/dist/zephyr/client.js +16 -2
- package/dist/zephyr/common/api-client.js +20 -6
- package/package.json +1 -1
package/dist/bugsnag/client.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { MCP_SERVER_NAME, MCP_SERVER_VERSION } from "../common/info.js";
|
|
3
|
+
import { getRequestHeader } from "../common/request-context.js";
|
|
3
4
|
import { ToolError } from "../common/tools.js";
|
|
4
5
|
import { CurrentUserAPI } from "./client/api/CurrentUser.js";
|
|
5
6
|
import { Configuration } from "./client/api/configuration.js";
|
|
@@ -39,7 +40,7 @@ const EXCLUDED_EVENT_FIELDS = /* @__PURE__ */ new Set([
|
|
|
39
40
|
// This is searches multiple fields and is more a convenience for humans, we're removing to avoid over-matching
|
|
40
41
|
]);
|
|
41
42
|
const ConfigurationSchema = z.object({
|
|
42
|
-
auth_token: z.string().describe("BugSnag personal
|
|
43
|
+
auth_token: z.string().describe("BugSnag personal access token").optional(),
|
|
43
44
|
project_api_key: z.string().describe("BugSnag project API key").optional(),
|
|
44
45
|
endpoint: z.string().url().describe("BugSnag endpoint URL").optional()
|
|
45
46
|
});
|
|
@@ -51,6 +52,7 @@ class BugsnagClient {
|
|
|
51
52
|
_errorsApi;
|
|
52
53
|
_projectApi;
|
|
53
54
|
_appEndpoint;
|
|
55
|
+
_authToken;
|
|
54
56
|
get currentUserApi() {
|
|
55
57
|
if (!this._currentUserApi) throw new Error("Client not configured");
|
|
56
58
|
return this._currentUserApi;
|
|
@@ -78,8 +80,47 @@ class BugsnagClient {
|
|
|
78
80
|
config.project_api_key,
|
|
79
81
|
config.endpoint
|
|
80
82
|
);
|
|
83
|
+
this._projectApiKey = config.project_api_key;
|
|
84
|
+
this._authToken = config.auth_token;
|
|
85
|
+
await this.initializeApis(config);
|
|
86
|
+
}
|
|
87
|
+
getAuthToken() {
|
|
88
|
+
const contextHeader = getRequestHeader("Bugsnag-Auth-Token");
|
|
89
|
+
if (contextHeader) {
|
|
90
|
+
let token = Array.isArray(contextHeader) ? contextHeader[0] : contextHeader;
|
|
91
|
+
if (token.startsWith("token ")) {
|
|
92
|
+
token = token.substring(6);
|
|
93
|
+
}
|
|
94
|
+
return `token ${token}`;
|
|
95
|
+
}
|
|
96
|
+
const bearerToken = this.getBearerToken();
|
|
97
|
+
if (bearerToken) {
|
|
98
|
+
return bearerToken;
|
|
99
|
+
}
|
|
100
|
+
return this._authToken ? `token ${this._authToken}` : null;
|
|
101
|
+
}
|
|
102
|
+
getBearerToken() {
|
|
103
|
+
const contextHeader = getRequestHeader("Authorization");
|
|
104
|
+
if (contextHeader) {
|
|
105
|
+
let token = Array.isArray(contextHeader) ? contextHeader[0] : contextHeader;
|
|
106
|
+
if (token.startsWith("Bearer ")) {
|
|
107
|
+
token = token.substring(7);
|
|
108
|
+
}
|
|
109
|
+
return `Bearer ${token}`;
|
|
110
|
+
}
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
async initializeApis(config) {
|
|
81
114
|
const apiConfig = new Configuration({
|
|
82
|
-
apiKey:
|
|
115
|
+
apiKey: (_name) => {
|
|
116
|
+
const authToken = this.getAuthToken();
|
|
117
|
+
if (authToken) {
|
|
118
|
+
return authToken;
|
|
119
|
+
}
|
|
120
|
+
throw new Error(
|
|
121
|
+
"Authentication token not found in request headers or configuration"
|
|
122
|
+
);
|
|
123
|
+
},
|
|
83
124
|
headers: {
|
|
84
125
|
"User-Agent": `${MCP_SERVER_NAME}/${MCP_SERVER_VERSION}`,
|
|
85
126
|
"Content-Type": "application/json",
|
|
@@ -95,9 +136,7 @@ class BugsnagClient {
|
|
|
95
136
|
this._currentUserApi = new CurrentUserAPI(apiConfig);
|
|
96
137
|
this._errorsApi = new ErrorAPI(apiConfig);
|
|
97
138
|
this._projectApi = new ProjectAPI(apiConfig);
|
|
98
|
-
this._projectApiKey = config.project_api_key;
|
|
99
139
|
this._isConfigured = true;
|
|
100
|
-
return;
|
|
101
140
|
}
|
|
102
141
|
isConfigured() {
|
|
103
142
|
return this._isConfigured;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { getRequestHeader } from "../common/request-context.js";
|
|
2
3
|
const ConfigurationSchema = z.object({
|
|
3
4
|
base_url: z.url().describe("Collaborator server base URL"),
|
|
4
|
-
username: z.string().describe("Collaborator username for authentication"),
|
|
5
|
-
login_ticket: z.string().describe("Collaborator login ticket for authentication")
|
|
5
|
+
username: z.string().describe("Collaborator username for authentication").optional(),
|
|
6
|
+
login_ticket: z.string().describe("Collaborator login ticket for authentication").optional()
|
|
6
7
|
});
|
|
7
8
|
class CollaboratorClient {
|
|
8
9
|
name = "Collaborator";
|
|
@@ -18,7 +19,7 @@ class CollaboratorClient {
|
|
|
18
19
|
this.loginTicket = config.login_ticket;
|
|
19
20
|
}
|
|
20
21
|
isConfigured() {
|
|
21
|
-
return this.baseUrl !== void 0
|
|
22
|
+
return this.baseUrl !== void 0;
|
|
22
23
|
}
|
|
23
24
|
/**
|
|
24
25
|
* Calls the Collaborator API with the given commands, prepending authentication automatically.
|
|
@@ -27,10 +28,20 @@ class CollaboratorClient {
|
|
|
27
28
|
*/
|
|
28
29
|
async call(commands) {
|
|
29
30
|
const url = `${this.baseUrl}/services/json/v1`;
|
|
31
|
+
let login = this.username;
|
|
32
|
+
let ticket = this.loginTicket;
|
|
33
|
+
const contextLogin = getRequestHeader("Collaborator-Login");
|
|
34
|
+
const contextTicket = getRequestHeader("Collaborator-Ticket");
|
|
35
|
+
if (contextLogin) {
|
|
36
|
+
login = Array.isArray(contextLogin) ? contextLogin[0] : contextLogin;
|
|
37
|
+
}
|
|
38
|
+
if (contextTicket) {
|
|
39
|
+
ticket = Array.isArray(contextTicket) ? contextTicket[0] : contextTicket;
|
|
40
|
+
}
|
|
30
41
|
const body = [
|
|
31
42
|
{
|
|
32
43
|
command: "SessionService.authenticate",
|
|
33
|
-
args: { login
|
|
44
|
+
args: { login, ticket }
|
|
34
45
|
},
|
|
35
46
|
...commands
|
|
36
47
|
];
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
const requestContextStorage = new AsyncLocalStorage();
|
|
3
|
+
function withRequestContext(req, fn) {
|
|
4
|
+
return requestContextStorage.run({ headers: req.headers }, fn);
|
|
5
|
+
}
|
|
6
|
+
function getRequestContext() {
|
|
7
|
+
return requestContextStorage.getStore();
|
|
8
|
+
}
|
|
9
|
+
function getRequestHeader(name) {
|
|
10
|
+
const context = getRequestContext();
|
|
11
|
+
if (!context?.headers) return void 0;
|
|
12
|
+
const headerValue = context.headers[name] || context.headers[name.toLowerCase()];
|
|
13
|
+
return headerValue;
|
|
14
|
+
}
|
|
15
|
+
export {
|
|
16
|
+
getRequestContext,
|
|
17
|
+
getRequestHeader,
|
|
18
|
+
requestContextStorage,
|
|
19
|
+
withRequestContext
|
|
20
|
+
};
|
package/dist/common/server.js
CHANGED
|
@@ -56,6 +56,9 @@ class SmartBearMcpServer extends McpServer {
|
|
|
56
56
|
isElicitationSupported() {
|
|
57
57
|
return this.elicitationSupported;
|
|
58
58
|
}
|
|
59
|
+
getClients() {
|
|
60
|
+
return this.clients;
|
|
61
|
+
}
|
|
59
62
|
async cleanupSession(mcpSessionId) {
|
|
60
63
|
for (const client of this.clients) {
|
|
61
64
|
await client.cleanupSession?.(mcpSessionId);
|
|
@@ -4,8 +4,19 @@ import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
|
|
|
4
4
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
5
5
|
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
|
|
6
6
|
import { clientRegistry } from "./client-registry.js";
|
|
7
|
+
import { withRequestContext } from "./request-context.js";
|
|
7
8
|
import { SmartBearMcpServer } from "./server.js";
|
|
9
|
+
import { getEnvVarName } from "./transport-stdio.js";
|
|
8
10
|
import { isOptionalType } from "./zod-utils.js";
|
|
11
|
+
function getBaseUrl(req) {
|
|
12
|
+
const baseUrlOverride = process.env.BASE_URL;
|
|
13
|
+
if (baseUrlOverride) {
|
|
14
|
+
return baseUrlOverride;
|
|
15
|
+
}
|
|
16
|
+
const protocol = req.headers["x-forwarded-proto"] || "http";
|
|
17
|
+
const host = req.headers["x-forwarded-host"] || req.headers.host;
|
|
18
|
+
return `${protocol}://${host}`;
|
|
19
|
+
}
|
|
9
20
|
async function runHttpMode() {
|
|
10
21
|
const PORT = process.env.PORT ? Number.parseInt(process.env.PORT, 10) : 3e3;
|
|
11
22
|
const allowedOrigins = process.env.ALLOWED_ORIGINS?.split(",") || [
|
|
@@ -20,6 +31,7 @@ async function runHttpMode() {
|
|
|
20
31
|
// Required for StreamableHTTP
|
|
21
32
|
"x-custom-auth-headers",
|
|
22
33
|
// used by mcp-inspector
|
|
34
|
+
"mcp-protocol-version",
|
|
23
35
|
...allowedAuthHeaders
|
|
24
36
|
].join(", ");
|
|
25
37
|
const httpServer = createServer(
|
|
@@ -39,7 +51,8 @@ async function runHttpMode() {
|
|
|
39
51
|
res.end();
|
|
40
52
|
return;
|
|
41
53
|
}
|
|
42
|
-
const
|
|
54
|
+
const baseUrl = getBaseUrl(req);
|
|
55
|
+
const url = new URL(req.url || "/", baseUrl);
|
|
43
56
|
if (req.method === "GET" && url.pathname === "/health") {
|
|
44
57
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
45
58
|
res.end(
|
|
@@ -47,6 +60,17 @@ async function runHttpMode() {
|
|
|
47
60
|
);
|
|
48
61
|
return;
|
|
49
62
|
}
|
|
63
|
+
if (req.method === "GET" && (url.pathname === "/.well-known/oauth-protected-resource" || url.pathname === "/.well-known/oauth-protected-resource/mcp")) {
|
|
64
|
+
const authServerUrl = process.env.OAUTH_AUTHORIZATION_SERVER_URL || "http://localhost:7070";
|
|
65
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
66
|
+
res.end(
|
|
67
|
+
JSON.stringify({
|
|
68
|
+
resource: `${baseUrl}/mcp`,
|
|
69
|
+
authorization_servers: [authServerUrl]
|
|
70
|
+
})
|
|
71
|
+
);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
50
74
|
if (url.pathname === "/mcp") {
|
|
51
75
|
await handleStreamableHttpRequest(req, res, transports);
|
|
52
76
|
return;
|
|
@@ -190,7 +214,10 @@ async function handleStreamableHttpRequest(req, res, transports) {
|
|
|
190
214
|
);
|
|
191
215
|
return;
|
|
192
216
|
}
|
|
193
|
-
await
|
|
217
|
+
await withRequestContext(
|
|
218
|
+
req,
|
|
219
|
+
async () => await transport.handleRequest(req, res, parsedBody)
|
|
220
|
+
);
|
|
194
221
|
} catch (error) {
|
|
195
222
|
console.error("Error handling StreamableHTTP request:", error);
|
|
196
223
|
res.writeHead(500, { "Content-Type": "text/plain" });
|
|
@@ -235,10 +262,13 @@ async function handleLegacyMessageRequest(req, res, url, transports) {
|
|
|
235
262
|
req.on("end", async () => {
|
|
236
263
|
try {
|
|
237
264
|
const parsedBody = JSON.parse(body);
|
|
238
|
-
await
|
|
265
|
+
await withRequestContext(
|
|
239
266
|
req,
|
|
240
|
-
|
|
241
|
-
|
|
267
|
+
async () => await session.transport.handlePostMessage(
|
|
268
|
+
req,
|
|
269
|
+
res,
|
|
270
|
+
parsedBody
|
|
271
|
+
)
|
|
242
272
|
);
|
|
243
273
|
} catch (error) {
|
|
244
274
|
console.error("Error handling POST message:", error);
|
|
@@ -250,19 +280,49 @@ async function handleLegacyMessageRequest(req, res, url, transports) {
|
|
|
250
280
|
async function newServer(req, res) {
|
|
251
281
|
const server = new SmartBearMcpServer();
|
|
252
282
|
try {
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
283
|
+
const configuredCount = await withRequestContext(
|
|
284
|
+
req,
|
|
285
|
+
() => clientRegistry.configure(server, (client, key) => {
|
|
286
|
+
const headerName = getHeaderName(client, key);
|
|
287
|
+
const value = req.headers[headerName] || req.headers[headerName.toLowerCase()];
|
|
288
|
+
if (typeof value === "string") {
|
|
289
|
+
return value;
|
|
290
|
+
}
|
|
291
|
+
const envVarName = getEnvVarName(client, key);
|
|
292
|
+
return process.env[envVarName] || null;
|
|
293
|
+
})
|
|
294
|
+
);
|
|
295
|
+
console.log(
|
|
296
|
+
`Configured ${configuredCount} clients for new server instance`
|
|
297
|
+
);
|
|
298
|
+
if (configuredCount === 0) {
|
|
299
|
+
throw new Error(
|
|
300
|
+
"No clients successfully configured. Missing authentication headers."
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
const hasAuth = withRequestContext(
|
|
304
|
+
req,
|
|
305
|
+
() => server.getClients().some((client) => {
|
|
306
|
+
if (!client.getAuthToken) return true;
|
|
307
|
+
return client.getAuthToken() !== null;
|
|
308
|
+
})
|
|
309
|
+
);
|
|
310
|
+
if (!hasAuth) {
|
|
311
|
+
throw new Error(
|
|
312
|
+
"No clients have valid authentication credentials. Please authenticate via OAuth or provide alternative auth headers (e.g. API key or personal auth token)."
|
|
313
|
+
);
|
|
314
|
+
}
|
|
261
315
|
} catch (error) {
|
|
262
316
|
const headerHelp = getHttpHeadersHelp();
|
|
263
317
|
const errorMessage = headerHelp.length > 0 ? `Configuration error: ${error instanceof Error ? error.message : String(error)}. Please provide valid headers:
|
|
264
318
|
${headerHelp.join("\n")}` : "No clients support HTTP header configuration.";
|
|
265
|
-
|
|
319
|
+
const headers = {
|
|
320
|
+
"Content-Type": "text/plain"
|
|
321
|
+
};
|
|
322
|
+
if (req.headers.host) {
|
|
323
|
+
headers["WWW-Authenticate"] = `OAuth resource_metadata="http://${req.headers.host}/.well-known/oauth-protected-resource"`;
|
|
324
|
+
}
|
|
325
|
+
res.writeHead(401, headers);
|
|
266
326
|
res.end(errorMessage);
|
|
267
327
|
return null;
|
|
268
328
|
}
|
|
@@ -297,5 +357,8 @@ function getHttpHeadersHelp() {
|
|
|
297
357
|
return messages;
|
|
298
358
|
}
|
|
299
359
|
export {
|
|
360
|
+
getBaseUrl,
|
|
361
|
+
getHeaderName,
|
|
362
|
+
newServer,
|
|
300
363
|
runHttpMode
|
|
301
364
|
};
|
package/dist/package.json.js
CHANGED