anyapi-mcp-server 1.5.1 → 1.6.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/build/config.js CHANGED
@@ -62,7 +62,17 @@ Required:
62
62
  Optional:
63
63
  --header HTTP header as "Key: Value" (repeatable)
64
64
  Supports \${ENV_VAR} interpolation in values
65
- --log Path to request/response log file (NDJSON format)`;
65
+ --log Path to request/response log file (NDJSON format)
66
+
67
+ OAuth 2.0 (all optional, but client-id/client-secret/token-url are required together):
68
+ --oauth-client-id OAuth client ID
69
+ --oauth-client-secret OAuth client secret
70
+ --oauth-token-url OAuth token endpoint URL
71
+ --oauth-auth-url OAuth authorization endpoint URL (authorization_code flow)
72
+ --oauth-scopes Comma-separated scopes (e.g. "read,write")
73
+ --oauth-flow "authorization_code" (default) or "client_credentials"
74
+ --oauth-param Extra auth URL param as "key=value" (repeatable)
75
+ All OAuth values support \${ENV_VAR} interpolation`;
66
76
  export async function loadConfig() {
67
77
  const name = getArg("--name");
68
78
  const specUrls = getAllArgs("--spec");
@@ -84,11 +94,55 @@ export async function loadConfig() {
84
94
  headers[key] = interpolateEnv(value);
85
95
  }
86
96
  const logPath = getArg("--log");
97
+ // --- OAuth CLI flags ---
98
+ const oauthClientId = getArg("--oauth-client-id");
99
+ const oauthClientSecret = getArg("--oauth-client-secret");
100
+ const oauthTokenUrl = getArg("--oauth-token-url");
101
+ const oauthAuthUrl = getArg("--oauth-auth-url");
102
+ const oauthScopes = getArg("--oauth-scopes");
103
+ const oauthFlow = getArg("--oauth-flow");
104
+ const oauthParams = getAllArgs("--oauth-param");
105
+ const hasAnyOAuth = oauthClientId || oauthClientSecret || oauthTokenUrl;
106
+ if (hasAnyOAuth && !(oauthClientId && oauthClientSecret && oauthTokenUrl)) {
107
+ console.error("ERROR: --oauth-client-id, --oauth-client-secret, and --oauth-token-url must all be provided together.");
108
+ process.exit(1);
109
+ }
110
+ let oauth;
111
+ if (oauthClientId && oauthClientSecret && oauthTokenUrl) {
112
+ const extraParams = {};
113
+ for (const raw of oauthParams) {
114
+ const eqIdx = raw.indexOf("=");
115
+ if (eqIdx === -1) {
116
+ console.error(`ERROR: Invalid --oauth-param format "${raw}". Expected "key=value"`);
117
+ process.exit(1);
118
+ }
119
+ extraParams[raw.slice(0, eqIdx)] = interpolateEnv(raw.slice(eqIdx + 1));
120
+ }
121
+ const flow = (oauthFlow ? interpolateEnv(oauthFlow) : "authorization_code");
122
+ if (flow !== "authorization_code" && flow !== "client_credentials") {
123
+ console.error(`ERROR: Invalid --oauth-flow "${flow}". Must be "authorization_code" or "client_credentials".`);
124
+ process.exit(1);
125
+ }
126
+ oauth = {
127
+ clientId: interpolateEnv(oauthClientId),
128
+ clientSecret: interpolateEnv(oauthClientSecret),
129
+ tokenUrl: interpolateEnv(oauthTokenUrl),
130
+ authUrl: oauthAuthUrl ? interpolateEnv(oauthAuthUrl) : undefined,
131
+ scopes: oauthScopes
132
+ ? interpolateEnv(oauthScopes)
133
+ .split(/[,\s]+/)
134
+ .filter(Boolean)
135
+ : [],
136
+ flow,
137
+ extraParams,
138
+ };
139
+ }
87
140
  return {
88
141
  name,
89
142
  specs,
90
143
  baseUrl: interpolateEnv(baseUrl).replace(/\/+$/, ""),
91
144
  headers: Object.keys(headers).length > 0 ? headers : undefined,
92
145
  logPath: logPath ? resolve(logPath) : undefined,
146
+ oauth,
93
147
  };
94
148
  }
@@ -0,0 +1,179 @@
1
+ /**
2
+ * Rich API error carrying status, raw body, and response headers
3
+ * for structured error reporting.
4
+ */
5
+ export class ApiError extends Error {
6
+ status;
7
+ statusText;
8
+ bodyText;
9
+ responseHeaders;
10
+ constructor(message, status, statusText, bodyText, responseHeaders = {}) {
11
+ super(message);
12
+ this.status = status;
13
+ this.statusText = statusText;
14
+ this.bodyText = bodyText;
15
+ this.responseHeaders = responseHeaders;
16
+ this.name = "ApiError";
17
+ }
18
+ }
19
+ /**
20
+ * Extract a human-readable error message from common API error response formats:
21
+ * - RFC 7807 Problem Details: { type, title, status, detail }
22
+ * - Stripe/generic: { error: { message, code } }
23
+ * - Simple: { error: "message" } or { message: "..." }
24
+ * - GraphQL-style: { errors: [{ message }] }
25
+ * - SOAP/Apigee: { fault: { faultstring } }
26
+ */
27
+ export function extractErrorMessage(bodyText) {
28
+ if (!bodyText.trim())
29
+ return undefined;
30
+ let parsed;
31
+ try {
32
+ parsed = JSON.parse(bodyText);
33
+ }
34
+ catch {
35
+ return undefined;
36
+ }
37
+ if (typeof parsed !== "object" || parsed === null)
38
+ return undefined;
39
+ const obj = parsed;
40
+ // RFC 7807 Problem Details
41
+ if (typeof obj.detail === "string") {
42
+ const title = typeof obj.title === "string" ? obj.title : undefined;
43
+ return title ? `${title}: ${obj.detail}` : obj.detail;
44
+ }
45
+ // { error: { message, code? } }
46
+ if (obj.error && typeof obj.error === "object") {
47
+ const errObj = obj.error;
48
+ if (typeof errObj.message === "string") {
49
+ const code = typeof errObj.code === "string" ? ` (${errObj.code})` : "";
50
+ return `${errObj.message}${code}`;
51
+ }
52
+ }
53
+ // { error: "message" }
54
+ if (typeof obj.error === "string") {
55
+ return obj.error;
56
+ }
57
+ // { message: "..." }
58
+ if (typeof obj.message === "string") {
59
+ return obj.message;
60
+ }
61
+ // { errors: [{ message }] }
62
+ if (Array.isArray(obj.errors)) {
63
+ const messages = obj.errors
64
+ .filter((e) => typeof e === "object" &&
65
+ e !== null &&
66
+ typeof e.message === "string")
67
+ .map((e) => e.message);
68
+ if (messages.length > 0)
69
+ return messages.join("; ");
70
+ }
71
+ // { fault: { faultstring } }
72
+ if (obj.fault && typeof obj.fault === "object") {
73
+ const fault = obj.fault;
74
+ if (typeof fault.faultstring === "string")
75
+ return fault.faultstring;
76
+ }
77
+ return undefined;
78
+ }
79
+ function getSuggestion(status, endpoint) {
80
+ switch (status) {
81
+ case 400: {
82
+ let msg = "Bad request. Check that all required parameters are provided and the request body matches the expected schema.";
83
+ if (endpoint) {
84
+ const required = endpoint.parameters.filter((p) => p.required);
85
+ if (required.length > 0) {
86
+ msg += ` Required parameters: ${required.map((p) => `${p.name} (${p.in})`).join(", ")}.`;
87
+ }
88
+ if (endpoint.hasRequestBody && endpoint.requestBodySchema) {
89
+ const requiredFields = Object.entries(endpoint.requestBodySchema.properties)
90
+ .filter(([, p]) => p.required)
91
+ .map(([name]) => name);
92
+ if (requiredFields.length > 0) {
93
+ msg += ` Required body fields: ${requiredFields.join(", ")}.`;
94
+ }
95
+ }
96
+ }
97
+ return msg;
98
+ }
99
+ case 401:
100
+ return "Authentication required. Use the auth tool to authenticate via OAuth, or provide credentials via --header (e.g. --header \"Authorization: Bearer <token>\") or per-request headers parameter.";
101
+ case 403:
102
+ return "Forbidden. Your credentials don't have permission for this operation. Verify your API key or token has the required scopes.";
103
+ case 404: {
104
+ let msg = "Resource not found. Verify the path and parameter values are correct.";
105
+ if (endpoint) {
106
+ const pathParams = endpoint.parameters.filter((p) => p.in === "path");
107
+ if (pathParams.length > 0) {
108
+ msg += ` Check path parameters: ${pathParams.map((p) => p.name).join(", ")}.`;
109
+ }
110
+ }
111
+ msg += " Use list_api to confirm available endpoints.";
112
+ return msg;
113
+ }
114
+ case 405:
115
+ return "Method not allowed for this endpoint. Use list_api to check which HTTP methods are supported.";
116
+ case 409:
117
+ return "Conflict. The resource may already exist or be in a state that prevents this operation.";
118
+ case 415:
119
+ return "Unsupported media type. The API may expect a different Content-Type header.";
120
+ case 422: {
121
+ let msg = "Validation failed. Check that request body fields match the expected types and formats.";
122
+ if (endpoint?.requestBodySchema) {
123
+ const props = Object.entries(endpoint.requestBodySchema.properties)
124
+ .map(([name, p]) => `${name}: ${p.type}${p.required ? " (required)" : ""}`)
125
+ .join(", ");
126
+ if (props)
127
+ msg += ` Expected fields: ${props}.`;
128
+ }
129
+ return msg;
130
+ }
131
+ case 429:
132
+ return "Rate limit exceeded (retries exhausted). Wait before trying again or reduce request frequency.";
133
+ default:
134
+ if (status >= 500) {
135
+ return "Server error. This is likely a temporary issue with the API. Try again later.";
136
+ }
137
+ return "Check the API documentation for this endpoint using explain_api.";
138
+ }
139
+ }
140
+ const MAX_RAW_BODY = 500;
141
+ /**
142
+ * Build a rich error response object from an API error or exhausted retry error.
143
+ */
144
+ export function buildErrorContext(error, method, path, endpoint) {
145
+ const isApiError = error instanceof ApiError;
146
+ const status = isApiError ? error.status : error.status;
147
+ const statusText = isApiError ? error.statusText : "";
148
+ const bodyText = isApiError ? error.bodyText : "";
149
+ const extracted = bodyText ? extractErrorMessage(bodyText) : undefined;
150
+ const result = {
151
+ error: extracted ?? error.message,
152
+ status,
153
+ ...(statusText ? { statusText } : {}),
154
+ endpoint: `${method} ${path}`,
155
+ suggestion: getSuggestion(status, endpoint),
156
+ };
157
+ // Include raw body snippet when structured extraction failed
158
+ if (!extracted && bodyText.length > 0) {
159
+ result.rawBody =
160
+ bodyText.length > MAX_RAW_BODY
161
+ ? bodyText.slice(0, MAX_RAW_BODY) + "..."
162
+ : bodyText;
163
+ }
164
+ // Include spec info for validation-related errors
165
+ if ((status === 400 || status === 422) && endpoint) {
166
+ if (endpoint.parameters.length > 0) {
167
+ result.specParameters = endpoint.parameters.map((p) => ({
168
+ name: p.name,
169
+ in: p.in,
170
+ required: p.required,
171
+ ...(p.description ? { description: p.description } : {}),
172
+ }));
173
+ }
174
+ if (endpoint.hasRequestBody && endpoint.requestBodySchema) {
175
+ result.specRequestBody = endpoint.requestBodySchema;
176
+ }
177
+ }
178
+ return result;
179
+ }
package/build/index.js CHANGED
@@ -8,10 +8,43 @@ import { callApi } from "./api-client.js";
8
8
  import { initLogger } from "./logger.js";
9
9
  import { generateSuggestions } from "./query-suggestions.js";
10
10
  import { getOrBuildSchema, executeQuery, schemaToSDL, truncateIfArray, computeShapeHash, } from "./graphql-schema.js";
11
+ import { ApiError, buildErrorContext } from "./error-context.js";
12
+ import { RetryableError } from "./retry.js";
13
+ import { isNonJsonResult } from "./response-parser.js";
14
+ import { startAuth, exchangeCode, awaitCallback, storeTokens, getTokens, isTokenExpired, initTokenStorage, } from "./oauth.js";
11
15
  const WRITE_METHODS = new Set(["POST", "PUT", "DELETE", "PATCH"]);
12
16
  const config = await loadConfig();
13
17
  initLogger(config.logPath ?? null);
14
18
  const apiIndex = new ApiIndex(config.specs);
19
+ // --- OAuth: merge spec-derived security info and init token storage ---
20
+ if (config.oauth) {
21
+ const schemes = apiIndex.getOAuthSchemes();
22
+ if (schemes.length > 0) {
23
+ const scheme = schemes[0];
24
+ if (!config.oauth.authUrl && scheme.authorizationUrl) {
25
+ config.oauth.authUrl = scheme.authorizationUrl;
26
+ }
27
+ if (config.oauth.scopes.length === 0 && scheme.scopes.length > 0) {
28
+ config.oauth.scopes = scheme.scopes;
29
+ }
30
+ }
31
+ initTokenStorage(config.name);
32
+ }
33
+ function formatToolError(error, method, path) {
34
+ if ((error instanceof ApiError || error instanceof RetryableError) && method && path) {
35
+ const endpoint = apiIndex.getEndpoint(method, path);
36
+ const context = buildErrorContext(error, method, path, endpoint);
37
+ return {
38
+ content: [{ type: "text", text: JSON.stringify(context, null, 2) }],
39
+ isError: true,
40
+ };
41
+ }
42
+ const message = error instanceof Error ? error.message : String(error);
43
+ return {
44
+ content: [{ type: "text", text: JSON.stringify({ error: message }) }],
45
+ isError: true,
46
+ };
47
+ }
15
48
  const server = new McpServer({
16
49
  name: config.name,
17
50
  version: "1.2.1",
@@ -128,12 +161,25 @@ server.tool("call_api", `Inspect a ${config.name} API endpoint. Makes a real req
128
161
  "Overrides default --header values."),
129
162
  }, async ({ method, path, params, body, headers }) => {
130
163
  try {
131
- const data = await callApi(config, method, path, params, body, headers, "populate");
164
+ const { data, responseHeaders: respHeaders } = await callApi(config, method, path, params, body, headers, "populate");
165
+ // Non-JSON response — skip GraphQL layer, return raw parsed data
166
+ if (isNonJsonResult(data)) {
167
+ return {
168
+ content: [
169
+ { type: "text", text: JSON.stringify({
170
+ rawResponse: data,
171
+ responseHeaders: respHeaders,
172
+ hint: "This endpoint returned a non-JSON response. The raw parsed content is shown above. " +
173
+ "GraphQL schema inference is not available for non-JSON responses — use the data directly.",
174
+ }, null, 2) },
175
+ ],
176
+ };
177
+ }
132
178
  const endpoint = apiIndex.getEndpoint(method, path);
133
179
  const bodyHash = WRITE_METHODS.has(method) && body ? computeShapeHash(body) : undefined;
134
180
  const { schema, shapeHash } = getOrBuildSchema(data, method, path, endpoint?.requestBodySchema, bodyHash);
135
181
  const sdl = schemaToSDL(schema);
136
- const result = { graphqlSchema: sdl, shapeHash };
182
+ const result = { graphqlSchema: sdl, shapeHash, responseHeaders: respHeaders };
137
183
  if (bodyHash)
138
184
  result.bodyHash = bodyHash;
139
185
  if (endpoint && endpoint.parameters.length > 0) {
@@ -171,13 +217,7 @@ server.tool("call_api", `Inspect a ${config.name} API endpoint. Makes a real req
171
217
  };
172
218
  }
173
219
  catch (error) {
174
- const message = error instanceof Error ? error.message : String(error);
175
- return {
176
- content: [
177
- { type: "text", text: JSON.stringify({ error: message }) },
178
- ],
179
- isError: true,
180
- };
220
+ return formatToolError(error, method, path);
181
221
  }
182
222
  });
183
223
  // --- Tool 3: query_api ---
@@ -229,7 +269,20 @@ server.tool("query_api", `Fetch data from a ${config.name} API endpoint, returni
229
269
  .describe("Client-side slice: items to skip in already-fetched response (default: 0). For API pagination, use params instead."),
230
270
  }, async ({ method, path, params, body, query, headers, limit, offset }) => {
231
271
  try {
232
- const rawData = await callApi(config, method, path, params, body, headers, "consume");
272
+ const { data: rawData, responseHeaders: respHeaders } = await callApi(config, method, path, params, body, headers, "consume");
273
+ // Non-JSON response — skip GraphQL layer, return raw parsed data
274
+ if (isNonJsonResult(rawData)) {
275
+ return {
276
+ content: [
277
+ { type: "text", text: JSON.stringify({
278
+ rawResponse: rawData,
279
+ responseHeaders: respHeaders,
280
+ hint: "This endpoint returned a non-JSON response. GraphQL querying is not available. " +
281
+ "The raw parsed content is shown above.",
282
+ }, null, 2) },
283
+ ],
284
+ };
285
+ }
233
286
  const endpoint = apiIndex.getEndpoint(method, path);
234
287
  const bodyHash = WRITE_METHODS.has(method) && body ? computeShapeHash(body) : undefined;
235
288
  const { schema, shapeHash } = getOrBuildSchema(rawData, method, path, endpoint?.requestBodySchema, bodyHash);
@@ -237,6 +290,7 @@ server.tool("query_api", `Fetch data from a ${config.name} API endpoint, returni
237
290
  const queryResult = await executeQuery(schema, data, query);
238
291
  if (typeof queryResult === "object" && queryResult !== null) {
239
292
  queryResult._shapeHash = shapeHash;
293
+ queryResult._responseHeaders = respHeaders;
240
294
  if (bodyHash)
241
295
  queryResult._bodyHash = bodyHash;
242
296
  if (truncated) {
@@ -255,13 +309,7 @@ server.tool("query_api", `Fetch data from a ${config.name} API endpoint, returni
255
309
  };
256
310
  }
257
311
  catch (error) {
258
- const message = error instanceof Error ? error.message : String(error);
259
- return {
260
- content: [
261
- { type: "text", text: JSON.stringify({ error: message }) },
262
- ],
263
- isError: true,
264
- };
312
+ return formatToolError(error, method, path);
265
313
  }
266
314
  });
267
315
  // --- Tool 4: explain_api ---
@@ -382,7 +430,17 @@ server.tool("batch_query", `Fetch data from multiple ${config.name} API endpoint
382
430
  }, async ({ requests }) => {
383
431
  try {
384
432
  const settled = await Promise.allSettled(requests.map(async (req) => {
385
- const rawData = await callApi(config, req.method, req.path, req.params, req.body, req.headers, "none");
433
+ const { data: rawData, responseHeaders: respHeaders } = await callApi(config, req.method, req.path, req.params, req.body, req.headers, "none");
434
+ // Non-JSON response — skip GraphQL layer
435
+ if (isNonJsonResult(rawData)) {
436
+ return {
437
+ method: req.method,
438
+ path: req.path,
439
+ data: rawData,
440
+ responseHeaders: respHeaders,
441
+ nonJson: true,
442
+ };
443
+ }
386
444
  const endpoint = apiIndex.getEndpoint(req.method, req.path);
387
445
  const bodyHash = WRITE_METHODS.has(req.method) && req.body
388
446
  ? computeShapeHash(req.body)
@@ -393,6 +451,7 @@ server.tool("batch_query", `Fetch data from multiple ${config.name} API endpoint
393
451
  method: req.method,
394
452
  path: req.path,
395
453
  data: queryResult,
454
+ responseHeaders: respHeaders,
396
455
  shapeHash,
397
456
  ...(bodyHash ? { bodyHash } : {}),
398
457
  };
@@ -401,9 +460,12 @@ server.tool("batch_query", `Fetch data from multiple ${config.name} API endpoint
401
460
  if (outcome.status === "fulfilled") {
402
461
  return outcome.value;
403
462
  }
404
- const message = outcome.reason instanceof Error
405
- ? outcome.reason.message
406
- : String(outcome.reason);
463
+ const reason = outcome.reason;
464
+ if (reason instanceof ApiError || reason instanceof RetryableError) {
465
+ const endpoint = apiIndex.getEndpoint(requests[i].method, requests[i].path);
466
+ return buildErrorContext(reason, requests[i].method, requests[i].path, endpoint);
467
+ }
468
+ const message = reason instanceof Error ? reason.message : String(reason);
407
469
  return {
408
470
  method: requests[i].method,
409
471
  path: requests[i].path,
@@ -426,6 +488,120 @@ server.tool("batch_query", `Fetch data from multiple ${config.name} API endpoint
426
488
  };
427
489
  }
428
490
  });
491
+ // --- Tool 6: auth (only when OAuth is configured) ---
492
+ if (config.oauth) {
493
+ server.tool("auth", `Manage OAuth 2.0 authentication for ${config.name}. ` +
494
+ "Use action 'start' to begin the OAuth flow (returns an authorization URL for " +
495
+ "authorization_code flow, or completes token exchange for client_credentials). " +
496
+ "Use action 'exchange' to complete the flow — the callback is captured automatically " +
497
+ "via a localhost server, or you can provide a 'code' manually. " +
498
+ "Use action 'status' to check the current token status.", {
499
+ action: z
500
+ .enum(["start", "exchange", "status"])
501
+ .describe("'start' begins auth flow, 'exchange' completes code exchange, 'status' shows token info"),
502
+ code: z
503
+ .string()
504
+ .optional()
505
+ .describe("Authorization code from the OAuth provider (optional for 'exchange' — " +
506
+ "if omitted, waits for the localhost callback automatically)"),
507
+ }, async ({ action, code }) => {
508
+ try {
509
+ if (action === "start") {
510
+ const result = await startAuth(config.oauth);
511
+ if ("url" in result) {
512
+ return {
513
+ content: [
514
+ {
515
+ type: "text",
516
+ text: JSON.stringify({
517
+ message: "Open this URL to authorize. A local callback server is listening. " +
518
+ "After you approve, call auth with action 'exchange' to complete authentication.",
519
+ authorizationUrl: result.url,
520
+ flow: config.oauth.flow,
521
+ }, null, 2),
522
+ },
523
+ ],
524
+ };
525
+ }
526
+ // client_credentials: tokens obtained directly
527
+ storeTokens(result.tokens);
528
+ return {
529
+ content: [
530
+ {
531
+ type: "text",
532
+ text: JSON.stringify({
533
+ message: "Authentication successful (client_credentials flow).",
534
+ tokenType: result.tokens.tokenType,
535
+ expiresIn: Math.round((result.tokens.expiresAt - Date.now()) / 1000),
536
+ scope: result.tokens.scope ?? null,
537
+ }, null, 2),
538
+ },
539
+ ],
540
+ };
541
+ }
542
+ if (action === "exchange") {
543
+ const tokens = code
544
+ ? await exchangeCode(config.oauth, code)
545
+ : await awaitCallback(config.oauth);
546
+ storeTokens(tokens);
547
+ return {
548
+ content: [
549
+ {
550
+ type: "text",
551
+ text: JSON.stringify({
552
+ message: "Authentication successful.",
553
+ tokenType: tokens.tokenType,
554
+ expiresIn: Math.round((tokens.expiresAt - Date.now()) / 1000),
555
+ hasRefreshToken: !!tokens.refreshToken,
556
+ scope: tokens.scope ?? null,
557
+ }, null, 2),
558
+ },
559
+ ],
560
+ };
561
+ }
562
+ // action === "status"
563
+ const tokens = getTokens();
564
+ if (!tokens) {
565
+ return {
566
+ content: [
567
+ {
568
+ type: "text",
569
+ text: JSON.stringify({
570
+ authenticated: false,
571
+ message: "No tokens stored. Use auth with action 'start' to authenticate.",
572
+ }, null, 2),
573
+ },
574
+ ],
575
+ };
576
+ }
577
+ const expired = isTokenExpired();
578
+ return {
579
+ content: [
580
+ {
581
+ type: "text",
582
+ text: JSON.stringify({
583
+ authenticated: true,
584
+ tokenType: tokens.tokenType,
585
+ expired,
586
+ expiresIn: Math.round((tokens.expiresAt - Date.now()) / 1000),
587
+ hasRefreshToken: !!tokens.refreshToken,
588
+ scope: tokens.scope ?? null,
589
+ }, null, 2),
590
+ },
591
+ ],
592
+ };
593
+ }
594
+ catch (error) {
595
+ const message = error instanceof Error ? error.message : String(error);
596
+ return {
597
+ content: [
598
+ { type: "text", text: JSON.stringify({ error: message }) },
599
+ ],
600
+ isError: true,
601
+ };
602
+ }
603
+ });
604
+ }
429
605
  async function main() {
430
606
  const transport = new StdioServerTransport();
431
607
  await server.connect(transport);