@smartbear/mcp 0.17.1 → 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.js +47 -19
- package/dist/qmetry/client.js +10 -2
- package/dist/reflect/client.js +26 -12
- 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
package/dist/pactflow/client.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import zod__default from "zod";
|
|
2
2
|
import { MCP_SERVER_NAME, MCP_SERVER_VERSION } from "../common/info.js";
|
|
3
3
|
import { isSamplingPolyfillResult } from "../common/pollyfills.js";
|
|
4
|
+
import { getRequestHeader } from "../common/request-context.js";
|
|
4
5
|
import { ToolError } from "../common/tools.js";
|
|
5
6
|
import { getOADMatcherRecommendations, getUserMatcherSelection } from "./client/prompt-utils.js";
|
|
6
7
|
import { PROMPTS } from "./client/prompts.js";
|
|
@@ -18,7 +19,9 @@ class PactflowClient {
|
|
|
18
19
|
toolPrefix = "contract-testing";
|
|
19
20
|
configPrefix = "Pact-Broker";
|
|
20
21
|
config = ConfigurationSchema;
|
|
21
|
-
|
|
22
|
+
token;
|
|
23
|
+
username;
|
|
24
|
+
password;
|
|
22
25
|
aiBaseUrl;
|
|
23
26
|
baseUrl;
|
|
24
27
|
_clientType;
|
|
@@ -37,23 +40,15 @@ class PactflowClient {
|
|
|
37
40
|
* @param config - Connection config (base_url + token OR username/password).
|
|
38
41
|
*/
|
|
39
42
|
async configure(server, config) {
|
|
43
|
+
this.token = config.token;
|
|
44
|
+
this.username = config.username;
|
|
45
|
+
this.password = config.password;
|
|
40
46
|
if (typeof config.token === "string") {
|
|
41
|
-
this.headers = {
|
|
42
|
-
Authorization: `Bearer ${config.token}`,
|
|
43
|
-
"Content-Type": "application/json",
|
|
44
|
-
"User-Agent": `${MCP_SERVER_NAME}/${MCP_SERVER_VERSION}`
|
|
45
|
-
};
|
|
46
47
|
this._clientType = "pactflow";
|
|
47
48
|
} else if (typeof config.username === "string" && typeof config.password === "string") {
|
|
48
|
-
const authString = `${config.username}:${config.password}`;
|
|
49
|
-
this.headers = {
|
|
50
|
-
Authorization: `Basic ${Buffer.from(authString).toString("base64")}`,
|
|
51
|
-
"Content-Type": "application/json",
|
|
52
|
-
"User-Agent": `${MCP_SERVER_NAME}/${MCP_SERVER_VERSION}`
|
|
53
|
-
};
|
|
54
49
|
this._clientType = "pact_broker";
|
|
55
50
|
} else {
|
|
56
|
-
|
|
51
|
+
this._clientType = "pactflow";
|
|
57
52
|
}
|
|
58
53
|
this.baseUrl = config.base_url;
|
|
59
54
|
this.aiBaseUrl = `${this.baseUrl}/api/ai`;
|
|
@@ -155,7 +150,7 @@ class PactflowClient {
|
|
|
155
150
|
async getStatus(statusUrl) {
|
|
156
151
|
const response = await fetch(statusUrl, {
|
|
157
152
|
method: "HEAD",
|
|
158
|
-
headers: this.
|
|
153
|
+
headers: this.requestHeaders
|
|
159
154
|
});
|
|
160
155
|
return {
|
|
161
156
|
status: response.status,
|
|
@@ -164,7 +159,40 @@ class PactflowClient {
|
|
|
164
159
|
}
|
|
165
160
|
/** Returns the current auth/content-type headers used for all requests. */
|
|
166
161
|
get requestHeaders() {
|
|
167
|
-
|
|
162
|
+
let contextToken = getRequestHeader("Pact-Token") || getRequestHeader("Authorization");
|
|
163
|
+
if (Array.isArray(contextToken)) {
|
|
164
|
+
contextToken = contextToken[0];
|
|
165
|
+
}
|
|
166
|
+
if (contextToken) {
|
|
167
|
+
let authHeader = contextToken;
|
|
168
|
+
if (!contextToken.startsWith("Basic ") && !contextToken.startsWith("Bearer ")) {
|
|
169
|
+
authHeader = `Bearer ${contextToken}`;
|
|
170
|
+
}
|
|
171
|
+
return {
|
|
172
|
+
Authorization: authHeader,
|
|
173
|
+
"Content-Type": "application/json",
|
|
174
|
+
"User-Agent": `${MCP_SERVER_NAME}/${MCP_SERVER_VERSION}`
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
if (this.token) {
|
|
178
|
+
let authHeader = this.token;
|
|
179
|
+
if (!authHeader.startsWith("Basic ") && !authHeader.startsWith("Bearer ")) {
|
|
180
|
+
authHeader = `Bearer ${authHeader}`;
|
|
181
|
+
}
|
|
182
|
+
return {
|
|
183
|
+
Authorization: authHeader,
|
|
184
|
+
"Content-Type": "application/json",
|
|
185
|
+
"User-Agent": `${MCP_SERVER_NAME}/${MCP_SERVER_VERSION}`
|
|
186
|
+
};
|
|
187
|
+
} else if (this.username && this.password) {
|
|
188
|
+
const authString = `${this.username}:${this.password}`;
|
|
189
|
+
return {
|
|
190
|
+
Authorization: `Basic ${Buffer.from(authString).toString("base64")}`,
|
|
191
|
+
"Content-Type": "application/json",
|
|
192
|
+
"User-Agent": `${MCP_SERVER_NAME}/${MCP_SERVER_VERSION}`
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
return void 0;
|
|
168
196
|
}
|
|
169
197
|
/**
|
|
170
198
|
* Fetches the final result of a completed async operation.
|
|
@@ -176,7 +204,7 @@ class PactflowClient {
|
|
|
176
204
|
async getResult(resultUrl) {
|
|
177
205
|
const response = await fetch(resultUrl, {
|
|
178
206
|
method: "GET",
|
|
179
|
-
headers: this.
|
|
207
|
+
headers: this.requestHeaders
|
|
180
208
|
});
|
|
181
209
|
if (!response.ok) {
|
|
182
210
|
throw new ToolError(`HTTP error! status: ${response.status}`);
|
|
@@ -224,7 +252,7 @@ class PactflowClient {
|
|
|
224
252
|
try {
|
|
225
253
|
const response = await fetch(url, {
|
|
226
254
|
method,
|
|
227
|
-
headers: this.
|
|
255
|
+
headers: this.requestHeaders,
|
|
228
256
|
...body && { body: JSON.stringify(body) }
|
|
229
257
|
});
|
|
230
258
|
if (!response.ok) {
|
|
@@ -374,7 +402,7 @@ class PactflowClient {
|
|
|
374
402
|
try {
|
|
375
403
|
const response = await fetch(url, {
|
|
376
404
|
method: "GET",
|
|
377
|
-
headers: this.
|
|
405
|
+
headers: this.requestHeaders
|
|
378
406
|
});
|
|
379
407
|
if (!response.ok) {
|
|
380
408
|
const errorText = await response.text().catch(() => "");
|
|
@@ -399,7 +427,7 @@ class PactflowClient {
|
|
|
399
427
|
try {
|
|
400
428
|
const response = await fetch(url, {
|
|
401
429
|
method: "GET",
|
|
402
|
-
headers: this.
|
|
430
|
+
headers: this.requestHeaders
|
|
403
431
|
});
|
|
404
432
|
if (!response.ok) {
|
|
405
433
|
const errorText = await response.text().catch(() => "");
|
package/dist/qmetry/client.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import zod__default from "zod";
|
|
2
|
+
import { getRequestHeader } from "../common/request-context.js";
|
|
2
3
|
import { findAutoResolveConfig, autoResolveViewIdAndFolderPath } from "./client/auto-resolve.js";
|
|
3
4
|
import { QMETRY_HANDLER_MAP } from "./client/handlers.js";
|
|
4
5
|
import { getProjectInfo } from "./client/project.js";
|
|
5
6
|
import { TOOLS } from "./client/tools/index.js";
|
|
6
7
|
import { QMETRY_DEFAULTS } from "./config/constants.js";
|
|
7
8
|
const ConfigurationSchema = zod__default.object({
|
|
8
|
-
api_key: zod__default.string().describe("QMetry API key for authentication"),
|
|
9
|
+
api_key: zod__default.string().describe("QMetry API key for authentication").optional(),
|
|
9
10
|
base_url: zod__default.string().url().optional().describe(
|
|
10
11
|
"Optional QMetry base URL for custom or region-specific endpoints"
|
|
11
12
|
)
|
|
@@ -25,9 +26,16 @@ class QmetryClient {
|
|
|
25
26
|
}
|
|
26
27
|
}
|
|
27
28
|
isConfigured() {
|
|
28
|
-
return
|
|
29
|
+
return true;
|
|
29
30
|
}
|
|
30
31
|
getToken() {
|
|
32
|
+
let contextToken = getRequestHeader("Qmetry-Token") || getRequestHeader("apikey");
|
|
33
|
+
if (Array.isArray(contextToken)) {
|
|
34
|
+
contextToken = contextToken[0];
|
|
35
|
+
}
|
|
36
|
+
if (contextToken) {
|
|
37
|
+
return contextToken;
|
|
38
|
+
}
|
|
31
39
|
if (!this.token) throw new Error("Client not configured");
|
|
32
40
|
return this.token;
|
|
33
41
|
}
|
package/dist/reflect/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 { API_KEY_HEADER } from "./config/constants.js";
|
|
5
6
|
import { SapTest } from "./prompt/sap-test.js";
|
|
@@ -18,11 +19,10 @@ import { ListSegments } from "./tool/tests/list-segments.js";
|
|
|
18
19
|
import { ListTests } from "./tool/tests/list-tests.js";
|
|
19
20
|
import { RunTest } from "./tool/tests/run-test.js";
|
|
20
21
|
const ConfigurationSchema = z.object({
|
|
21
|
-
api_token: z.string().describe("Reflect API authentication token")
|
|
22
|
+
api_token: z.string().describe("Reflect API authentication token").optional()
|
|
22
23
|
});
|
|
23
24
|
class ReflectClient {
|
|
24
|
-
|
|
25
|
-
apiToken = "";
|
|
25
|
+
_apiToken;
|
|
26
26
|
activeConnections = /* @__PURE__ */ new Map();
|
|
27
27
|
sessionStates = /* @__PURE__ */ new Map();
|
|
28
28
|
mcpSessionConnections = /* @__PURE__ */ new Map();
|
|
@@ -31,21 +31,35 @@ class ReflectClient {
|
|
|
31
31
|
configPrefix = "Reflect";
|
|
32
32
|
config = ConfigurationSchema;
|
|
33
33
|
async configure(_server, config, _cache) {
|
|
34
|
-
this.
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
34
|
+
this._apiToken = config.api_token;
|
|
35
|
+
}
|
|
36
|
+
getAuthToken() {
|
|
37
|
+
const contextHeader = getRequestHeader("Reflect-Api-Token") || getRequestHeader("X-API-KEY") || getRequestHeader("Authorization");
|
|
38
|
+
if (contextHeader) {
|
|
39
|
+
let token = Array.isArray(contextHeader) ? contextHeader[0] : contextHeader;
|
|
40
|
+
if (token.startsWith("Bearer ")) {
|
|
41
|
+
token = token.substring(7);
|
|
42
|
+
}
|
|
43
|
+
return token;
|
|
44
|
+
}
|
|
45
|
+
return this._apiToken || null;
|
|
40
46
|
}
|
|
41
47
|
isConfigured() {
|
|
42
|
-
return
|
|
48
|
+
return true;
|
|
43
49
|
}
|
|
44
50
|
getApiToken() {
|
|
45
|
-
return this.
|
|
51
|
+
return this.getAuthToken() || "";
|
|
46
52
|
}
|
|
47
53
|
getHeaders() {
|
|
48
|
-
|
|
54
|
+
const token = this.getAuthToken();
|
|
55
|
+
if (!token) {
|
|
56
|
+
throw new Error("Reflect API token not found");
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
[API_KEY_HEADER]: token,
|
|
60
|
+
"Content-Type": "application/json",
|
|
61
|
+
"User-Agent": `${MCP_SERVER_NAME}/${MCP_SERVER_VERSION}`
|
|
62
|
+
};
|
|
49
63
|
}
|
|
50
64
|
getSessionState(sessionId) {
|
|
51
65
|
return this.sessionStates.get(sessionId);
|
|
@@ -11,10 +11,13 @@ function hasErrorsFound(value) {
|
|
|
11
11
|
}
|
|
12
12
|
class SwaggerAPI {
|
|
13
13
|
config;
|
|
14
|
-
|
|
14
|
+
userAgent;
|
|
15
15
|
constructor(config, userAgent) {
|
|
16
16
|
this.config = config;
|
|
17
|
-
this.
|
|
17
|
+
this.userAgent = userAgent;
|
|
18
|
+
}
|
|
19
|
+
get headers() {
|
|
20
|
+
return this.config.getHeaders(this.userAgent);
|
|
18
21
|
}
|
|
19
22
|
/**
|
|
20
23
|
* Core response parsing logic shared between different response handlers.
|
|
@@ -1,28 +1,36 @@
|
|
|
1
1
|
class SwaggerConfiguration {
|
|
2
|
-
|
|
2
|
+
tokenProvider;
|
|
3
3
|
portalBasePath;
|
|
4
4
|
registryBasePath;
|
|
5
5
|
uiBasePath;
|
|
6
6
|
userManagementBasePath;
|
|
7
|
-
|
|
7
|
+
defaultHeaders;
|
|
8
8
|
constructor(param) {
|
|
9
|
-
|
|
9
|
+
if (typeof param.token === "string") {
|
|
10
|
+
this.tokenProvider = () => param.token;
|
|
11
|
+
} else {
|
|
12
|
+
this.tokenProvider = param.token;
|
|
13
|
+
}
|
|
10
14
|
this.portalBasePath = param.portalBasePath || "https://api.portal.swaggerhub.com/v1";
|
|
11
15
|
this.registryBasePath = param.registryBasePath || "https://api.swaggerhub.com";
|
|
12
16
|
this.uiBasePath = param.uiBasePath || "https://app.swaggerhub.com";
|
|
13
17
|
this.userManagementBasePath = param.userManagementBasePath || `${this.registryBasePath}/user-management/v1`;
|
|
14
|
-
this.
|
|
15
|
-
Authorization: `Bearer ${this.token}`,
|
|
18
|
+
this.defaultHeaders = {
|
|
16
19
|
"Content-Type": "application/json",
|
|
17
20
|
...param.headers
|
|
18
21
|
};
|
|
19
22
|
}
|
|
20
23
|
/**
|
|
21
|
-
* Get headers with User-Agent included
|
|
24
|
+
* Get headers with User-Agent included and dynamic Auth token
|
|
22
25
|
*/
|
|
23
26
|
getHeaders(userAgent) {
|
|
27
|
+
const token = this.tokenProvider();
|
|
28
|
+
if (!token) {
|
|
29
|
+
throw new Error("Swagger API token not found");
|
|
30
|
+
}
|
|
24
31
|
return {
|
|
25
|
-
|
|
32
|
+
Authorization: `Bearer ${token}`,
|
|
33
|
+
...this.defaultHeaders,
|
|
26
34
|
"User-Agent": userAgent
|
|
27
35
|
};
|
|
28
36
|
}
|
package/dist/swagger/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 "./config-utils.js";
|
|
4
5
|
import { SwaggerAPI } from "./client/api.js";
|
|
5
6
|
import { SwaggerConfiguration } from "./client/configuration.js";
|
|
@@ -8,21 +9,23 @@ import "./client/registry-types.js";
|
|
|
8
9
|
import { TOOLS } from "./client/tools.js";
|
|
9
10
|
import "./client/user-management-types.js";
|
|
10
11
|
const ConfigurationSchema = z.object({
|
|
11
|
-
api_key: z.string().describe("Swagger API key for authentication"),
|
|
12
|
+
api_key: z.string().describe("Swagger API key for authentication").optional(),
|
|
12
13
|
portal_base_path: z.string().optional().describe("Base path for Portal API requests (optional)"),
|
|
13
14
|
registry_base_path: z.string().optional().describe("Base path for Registry API requests (optional)"),
|
|
14
15
|
ui_base_path: z.string().optional().describe("Base URL for the SwaggerHub UI (optional)")
|
|
15
16
|
});
|
|
16
17
|
class SwaggerClient {
|
|
17
18
|
api;
|
|
19
|
+
_apiKey;
|
|
18
20
|
name = "Swagger";
|
|
19
21
|
toolPrefix = "swagger";
|
|
20
22
|
configPrefix = "Swagger";
|
|
21
23
|
config = ConfigurationSchema;
|
|
22
24
|
async configure(_server, config, _cache) {
|
|
25
|
+
this._apiKey = config.api_key;
|
|
23
26
|
this.api = new SwaggerAPI(
|
|
24
27
|
new SwaggerConfiguration({
|
|
25
|
-
token:
|
|
28
|
+
token: () => this.getAuthToken(),
|
|
26
29
|
portalBasePath: config.portal_base_path,
|
|
27
30
|
registryBasePath: config.registry_base_path,
|
|
28
31
|
uiBasePath: config.ui_base_path
|
|
@@ -30,6 +33,17 @@ class SwaggerClient {
|
|
|
30
33
|
`${MCP_SERVER_NAME}/${MCP_SERVER_VERSION}`
|
|
31
34
|
);
|
|
32
35
|
}
|
|
36
|
+
getAuthToken() {
|
|
37
|
+
const contextHeader = getRequestHeader("Swagger-Api-Key") || getRequestHeader("Authorization");
|
|
38
|
+
if (contextHeader) {
|
|
39
|
+
let token = Array.isArray(contextHeader) ? contextHeader[0] : contextHeader;
|
|
40
|
+
if (token.startsWith("Bearer ")) {
|
|
41
|
+
token = token.substring(7);
|
|
42
|
+
}
|
|
43
|
+
return token;
|
|
44
|
+
}
|
|
45
|
+
return this._apiKey || null;
|
|
46
|
+
}
|
|
33
47
|
isConfigured() {
|
|
34
48
|
return this.api !== void 0;
|
|
35
49
|
}
|
package/dist/zephyr/client.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import zod__default from "zod";
|
|
2
|
+
import { getRequestHeader } from "../common/request-context.js";
|
|
2
3
|
import { ApiClient } from "./common/api-client.js";
|
|
3
4
|
import { GetEnvironments } from "./tool/environment/get-environments.js";
|
|
4
5
|
import { CreateFolder } from "./tool/folder/create-folder.js";
|
|
@@ -37,21 +38,34 @@ import { UpdateTestExecution } from "./tool/test-execution/update-test-execution
|
|
|
37
38
|
import { UpdateTestExecutionSteps } from "./tool/test-execution/update-test-steps.js";
|
|
38
39
|
const BASE_URL_DEFAULT = "https://api.zephyrscale.smartbear.com/v2";
|
|
39
40
|
const ConfigurationSchema = zod__default.object({
|
|
40
|
-
api_token: zod__default.string().describe("Zephyr Scale API token for authentication"),
|
|
41
|
+
api_token: zod__default.string().describe("Zephyr Scale API token for authentication").optional(),
|
|
41
42
|
base_url: zod__default.string().url().optional().describe("Zephyr Scale API base URL").default(BASE_URL_DEFAULT)
|
|
42
43
|
});
|
|
43
44
|
class ZephyrClient {
|
|
44
45
|
apiClient;
|
|
46
|
+
_apiToken;
|
|
45
47
|
name = "Zephyr";
|
|
46
48
|
toolPrefix = "zephyr";
|
|
47
49
|
configPrefix = "Zephyr";
|
|
48
50
|
config = ConfigurationSchema;
|
|
49
51
|
async configure(_server, config, _cache) {
|
|
52
|
+
this._apiToken = config.api_token;
|
|
50
53
|
this.apiClient = new ApiClient(
|
|
51
|
-
|
|
54
|
+
() => this.getAuthToken(),
|
|
52
55
|
config.base_url || BASE_URL_DEFAULT
|
|
53
56
|
);
|
|
54
57
|
}
|
|
58
|
+
getAuthToken() {
|
|
59
|
+
const contextHeader = getRequestHeader("Zephyr-Api-Token") || getRequestHeader("Authorization");
|
|
60
|
+
if (contextHeader) {
|
|
61
|
+
let token = Array.isArray(contextHeader) ? contextHeader[0] : contextHeader;
|
|
62
|
+
if (token.startsWith("Bearer ")) {
|
|
63
|
+
token = token.substring(7);
|
|
64
|
+
}
|
|
65
|
+
return token;
|
|
66
|
+
}
|
|
67
|
+
return this._apiToken || null;
|
|
68
|
+
}
|
|
55
69
|
isConfigured() {
|
|
56
70
|
return this.apiClient !== void 0;
|
|
57
71
|
}
|
|
@@ -2,10 +2,24 @@ import { ToolError } from "../../common/tools.js";
|
|
|
2
2
|
import { AuthService } from "./auth-service.js";
|
|
3
3
|
class ApiClient {
|
|
4
4
|
baseUrl;
|
|
5
|
-
|
|
6
|
-
constructor(
|
|
5
|
+
tokenProvider;
|
|
6
|
+
constructor(tokenOrProvider, baseUrl) {
|
|
7
7
|
this.baseUrl = baseUrl.trim().replace(/\/$/, "");
|
|
8
|
-
|
|
8
|
+
if (typeof tokenOrProvider === "string") {
|
|
9
|
+
this.tokenProvider = () => tokenOrProvider;
|
|
10
|
+
} else {
|
|
11
|
+
this.tokenProvider = tokenOrProvider;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
get defaultHeaders() {
|
|
15
|
+
return this.getHeaders();
|
|
16
|
+
}
|
|
17
|
+
getHeaders() {
|
|
18
|
+
const token = this.tokenProvider();
|
|
19
|
+
if (!token) {
|
|
20
|
+
throw new ToolError("Zephyr API token not found");
|
|
21
|
+
}
|
|
22
|
+
return new AuthService(token).getAuthHeaders();
|
|
9
23
|
}
|
|
10
24
|
getUrl(endpoint, params) {
|
|
11
25
|
const url = new URL(this.baseUrl + endpoint);
|
|
@@ -21,7 +35,7 @@ class ApiClient {
|
|
|
21
35
|
async get(endpoint, params) {
|
|
22
36
|
const response = await fetch(this.getUrl(endpoint, params), {
|
|
23
37
|
method: "GET",
|
|
24
|
-
headers: this.
|
|
38
|
+
headers: this.getHeaders()
|
|
25
39
|
});
|
|
26
40
|
return await this.validateAndGetResponseBody(response);
|
|
27
41
|
}
|
|
@@ -29,7 +43,7 @@ class ApiClient {
|
|
|
29
43
|
const response = await fetch(this.getUrl(endpoint), {
|
|
30
44
|
method: "POST",
|
|
31
45
|
headers: {
|
|
32
|
-
...this.
|
|
46
|
+
...this.getHeaders(),
|
|
33
47
|
"Content-Type": "application/json"
|
|
34
48
|
},
|
|
35
49
|
body: JSON.stringify(body)
|
|
@@ -40,7 +54,7 @@ class ApiClient {
|
|
|
40
54
|
const response = await fetch(this.getUrl(endpoint), {
|
|
41
55
|
method: "PUT",
|
|
42
56
|
headers: {
|
|
43
|
-
...this.
|
|
57
|
+
...this.getHeaders(),
|
|
44
58
|
"Content-Type": "application/json"
|
|
45
59
|
},
|
|
46
60
|
body: JSON.stringify(body)
|