@modelcontextprotocol/inspector 0.16.5 → 0.16.6

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 CHANGED
@@ -29,6 +29,14 @@ npx @modelcontextprotocol/inspector
29
29
 
30
30
  The server will start up and the UI will be accessible at `http://localhost:6274`.
31
31
 
32
+ ### Docker Container
33
+
34
+ You can also start it in a Docker container with the following command:
35
+
36
+ ```bash
37
+ docker run --rm --network host -p 6274:6274 -p 6277:6277 ghcr.io/modelcontextprotocol/inspector:latest
38
+ ```
39
+
32
40
  ### From an MCP server repository
33
41
 
34
42
  To inspect an MCP server implementation, there's no need to clone this repo. Instead, use `npx`. For example, if your server is built at `build/index.js`:
@@ -387,6 +395,9 @@ npx @modelcontextprotocol/inspector --cli node build/index.js --method tools/lis
387
395
  # Call a specific tool
388
396
  npx @modelcontextprotocol/inspector --cli node build/index.js --method tools/call --tool-name mytool --tool-arg key=value --tool-arg another=value2
389
397
 
398
+ # Call a tool with JSON arguments
399
+ npx @modelcontextprotocol/inspector --cli node build/index.js --method tools/call --tool-name mytool --tool-arg 'options={"format": "json", "max_tokens": 100}'
400
+
390
401
  # List available resources
391
402
  npx @modelcontextprotocol/inspector --cli node build/index.js --method resources/list
392
403
 
@@ -399,6 +410,9 @@ npx @modelcontextprotocol/inspector --cli https://my-mcp-server.example.com
399
410
  # Connect to a remote MCP server (with Streamable HTTP transport)
400
411
  npx @modelcontextprotocol/inspector --cli https://my-mcp-server.example.com --transport http --method tools/list
401
412
 
413
+ # Connect to a remote MCP server (with custom headers)
414
+ npx @modelcontextprotocol/inspector --cli https://my-mcp-server.example.com --transport http --method tools/list --header "X-API-Key: your-api-key"
415
+
402
416
  # Call a tool on a remote server
403
417
  npx @modelcontextprotocol/inspector --cli https://my-mcp-server.example.com --method tools/call --tool-name remotetool --tool-arg param=value
404
418
 
package/cli/build/cli.js CHANGED
@@ -54,6 +54,10 @@ async function runWebClient(args) {
54
54
  await spawnPromise("node", [inspectorClientPath, ...startArgs], {
55
55
  signal: abort.signal,
56
56
  echoOutput: true,
57
+ // pipe the stdout through here, prevents issues with buffering and
58
+ // dropping the end of console.out after 8192 chars due to node
59
+ // closing the stdout pipe before the output has finished flushing
60
+ stdio: "inherit",
57
61
  });
58
62
  }
59
63
  catch (e) {
@@ -73,18 +77,28 @@ async function runCli(args) {
73
77
  try {
74
78
  // Build CLI arguments
75
79
  const cliArgs = [cliPath];
80
+ // Add target URL/command first
81
+ cliArgs.push(args.command, ...args.args);
76
82
  // Add transport flag if specified
77
83
  if (args.transport && args.transport !== "stdio") {
78
84
  // Convert streamable-http back to http for CLI mode
79
85
  const cliTransport = args.transport === "streamable-http" ? "http" : args.transport;
80
86
  cliArgs.push("--transport", cliTransport);
81
87
  }
82
- // Add command and remaining args
83
- cliArgs.push(args.command, ...args.args);
88
+ // Add headers if specified
89
+ if (args.headers) {
90
+ for (const [key, value] of Object.entries(args.headers)) {
91
+ cliArgs.push("--header", `${key}: ${value}`);
92
+ }
93
+ }
84
94
  await spawnPromise("node", cliArgs, {
85
95
  env: { ...process.env, ...args.envArgs },
86
96
  signal: abort.signal,
87
97
  echoOutput: true,
98
+ // pipe the stdout through here, prevents issues with buffering and
99
+ // dropping the end of console.out after 8192 chars due to node
100
+ // closing the stdout pipe before the output has finished flushing
101
+ stdio: "inherit",
88
102
  });
89
103
  }
90
104
  catch (e) {
@@ -126,6 +140,18 @@ function parseKeyValuePair(value, previous = {}) {
126
140
  }
127
141
  return { ...previous, [key]: val };
128
142
  }
143
+ function parseHeaderPair(value, previous = {}) {
144
+ const colonIndex = value.indexOf(":");
145
+ if (colonIndex === -1) {
146
+ throw new Error(`Invalid header format: ${value}. Use "HeaderName: Value" format.`);
147
+ }
148
+ const key = value.slice(0, colonIndex).trim();
149
+ const val = value.slice(colonIndex + 1).trim();
150
+ if (key === "" || val === "") {
151
+ throw new Error(`Invalid header format: ${value}. Use "HeaderName: Value" format.`);
152
+ }
153
+ return { ...previous, [key]: val };
154
+ }
129
155
  function parseArgs() {
130
156
  const program = new Command();
131
157
  const argSeparatorIndex = process.argv.indexOf("--");
@@ -144,7 +170,8 @@ function parseArgs() {
144
170
  .option("--server <n>", "server name from config file")
145
171
  .option("--cli", "enable CLI mode")
146
172
  .option("--transport <type>", "transport type (stdio, sse, http)")
147
- .option("--server-url <url>", "server URL for SSE/HTTP transport");
173
+ .option("--server-url <url>", "server URL for SSE/HTTP transport")
174
+ .option("--header <headers...>", 'HTTP headers as "HeaderName: Value" pairs (for HTTP/SSE transports)', parseHeaderPair, {});
148
175
  // Parse only the arguments before --
149
176
  program.parse(preArgs);
150
177
  const options = program.opts();
@@ -186,6 +213,7 @@ function parseArgs() {
186
213
  envArgs: { ...(config.env || {}), ...(options.e || {}) },
187
214
  cli: options.cli || false,
188
215
  transport: "stdio",
216
+ headers: options.header,
189
217
  };
190
218
  }
191
219
  else if (config.type === "sse" || config.type === "streamable-http") {
@@ -196,6 +224,7 @@ function parseArgs() {
196
224
  cli: options.cli || false,
197
225
  transport: config.type,
198
226
  serverUrl: config.url,
227
+ headers: options.header,
199
228
  };
200
229
  }
201
230
  else {
@@ -206,6 +235,7 @@ function parseArgs() {
206
235
  envArgs: { ...(config.env || {}), ...(options.e || {}) },
207
236
  cli: options.cli || false,
208
237
  transport: "stdio",
238
+ headers: options.header,
209
239
  };
210
240
  }
211
241
  }
@@ -224,6 +254,7 @@ function parseArgs() {
224
254
  cli: options.cli || false,
225
255
  transport: transport,
226
256
  serverUrl: options.serverUrl,
257
+ headers: options.header,
227
258
  };
228
259
  }
229
260
  async function main() {
@@ -11,9 +11,24 @@ export async function listPrompts(client) {
11
11
  // Get a prompt
12
12
  export async function getPrompt(client, name, args) {
13
13
  try {
14
+ // Convert all arguments to strings for prompt arguments
15
+ const stringArgs = {};
16
+ if (args) {
17
+ for (const [key, value] of Object.entries(args)) {
18
+ if (typeof value === "string") {
19
+ stringArgs[key] = value;
20
+ }
21
+ else if (value === null || value === undefined) {
22
+ stringArgs[key] = String(value);
23
+ }
24
+ else {
25
+ stringArgs[key] = JSON.stringify(value);
26
+ }
27
+ }
28
+ }
14
29
  const response = await client.getPrompt({
15
30
  name,
16
- arguments: args || {},
31
+ arguments: stringArgs,
17
32
  });
18
33
  return response;
19
34
  }
@@ -49,8 +49,18 @@ export async function callTool(client, name, args) {
49
49
  const tool = tools.find((t) => t.name === name);
50
50
  let convertedArgs = args;
51
51
  if (tool) {
52
- // Convert parameters based on the tool's schema
53
- convertedArgs = convertParameters(tool, args);
52
+ // Convert parameters based on the tool's schema, but only for string values
53
+ // since we now accept pre-parsed values from the CLI
54
+ const stringArgs = {};
55
+ for (const [key, value] of Object.entries(args)) {
56
+ if (typeof value === "string") {
57
+ stringArgs[key] = value;
58
+ }
59
+ }
60
+ if (Object.keys(stringArgs).length > 0) {
61
+ const convertedStringArgs = convertParameters(tool, stringArgs);
62
+ convertedArgs = { ...args, ...convertedStringArgs };
63
+ }
54
64
  }
55
65
  const response = await client.callTool({
56
66
  name: name,
@@ -4,7 +4,8 @@ import { Command } from "commander";
4
4
  import { callTool, connect, disconnect, getPrompt, listPrompts, listResources, listResourceTemplates, listTools, readResource, setLoggingLevel, validLogLevels, } from "./client/index.js";
5
5
  import { handleError } from "./error-handler.js";
6
6
  import { createTransport } from "./transport.js";
7
- function createTransportOptions(target, transport) {
7
+ import { awaitableLog } from "./utils/awaitable-log.js";
8
+ function createTransportOptions(target, transport, headers) {
8
9
  if (target.length === 0) {
9
10
  throw new Error("Target is required. Specify a URL or a command to execute.");
10
11
  }
@@ -46,10 +47,11 @@ function createTransportOptions(target, transport) {
46
47
  command: isUrl ? undefined : command,
47
48
  args: isUrl ? undefined : commandArgs,
48
49
  url: isUrl ? command : undefined,
50
+ headers,
49
51
  };
50
52
  }
51
53
  async function callMethod(args) {
52
- const transportOptions = createTransportOptions(args.target, args.transport);
54
+ const transportOptions = createTransportOptions(args.target, args.transport, args.headers);
53
55
  const transport = createTransport(transportOptions);
54
56
  const client = new Client({
55
57
  name: "inspector-cli",
@@ -101,7 +103,7 @@ async function callMethod(args) {
101
103
  else {
102
104
  throw new Error(`Unsupported method: ${args.method}. Supported methods include: tools/list, tools/call, resources/list, resources/read, resources/templates/list, prompts/list, prompts/get, logging/setLevel`);
103
105
  }
104
- console.log(JSON.stringify(result, null, 2));
106
+ await awaitableLog(JSON.stringify(result, null, 2));
105
107
  }
106
108
  finally {
107
109
  try {
@@ -119,6 +121,27 @@ function parseKeyValuePair(value, previous = {}) {
119
121
  if (val === undefined || val === "") {
120
122
  throw new Error(`Invalid parameter format: ${value}. Use key=value format.`);
121
123
  }
124
+ // Try to parse as JSON first
125
+ let parsedValue;
126
+ try {
127
+ parsedValue = JSON.parse(val);
128
+ }
129
+ catch {
130
+ // If JSON parsing fails, keep as string
131
+ parsedValue = val;
132
+ }
133
+ return { ...previous, [key]: parsedValue };
134
+ }
135
+ function parseHeaderPair(value, previous = {}) {
136
+ const colonIndex = value.indexOf(":");
137
+ if (colonIndex === -1) {
138
+ throw new Error(`Invalid header format: ${value}. Use "HeaderName: Value" format.`);
139
+ }
140
+ const key = value.slice(0, colonIndex).trim();
141
+ const val = value.slice(colonIndex + 1).trim();
142
+ if (key === "" || val === "") {
143
+ throw new Error(`Invalid header format: ${value}. Use "HeaderName: Value" format.`);
144
+ }
122
145
  return { ...previous, [key]: val };
123
146
  }
124
147
  function parseArgs() {
@@ -171,7 +194,11 @@ function parseArgs() {
171
194
  throw new Error(`Invalid transport type: ${value}. Valid types are: ${validTransports.join(", ")}`);
172
195
  }
173
196
  return value;
174
- });
197
+ })
198
+ //
199
+ // HTTP headers
200
+ //
201
+ .option("--header <headers...>", 'HTTP headers as "HeaderName: Value" pairs (for HTTP/SSE transports)', parseHeaderPair, {});
175
202
  // Parse only the arguments before --
176
203
  program.parse(preArgs);
177
204
  const options = program.opts();
@@ -184,6 +211,7 @@ function parseArgs() {
184
211
  return {
185
212
  target: finalArgs,
186
213
  ...options,
214
+ headers: options.header, // commander.js uses 'header' field, map to 'headers'
187
215
  };
188
216
  }
189
217
  async function main() {
@@ -38,10 +38,24 @@ export function createTransport(options) {
38
38
  }
39
39
  const url = new URL(options.url);
40
40
  if (transportType === "sse") {
41
- return new SSEClientTransport(url);
41
+ const transportOptions = options.headers
42
+ ? {
43
+ requestInit: {
44
+ headers: options.headers,
45
+ },
46
+ }
47
+ : undefined;
48
+ return new SSEClientTransport(url, transportOptions);
42
49
  }
43
50
  if (transportType === "http") {
44
- return new StreamableHTTPClientTransport(url);
51
+ const transportOptions = options.headers
52
+ ? {
53
+ requestInit: {
54
+ headers: options.headers,
55
+ },
56
+ }
57
+ : undefined;
58
+ return new StreamableHTTPClientTransport(url, transportOptions);
45
59
  }
46
60
  throw new Error(`Unsupported transport type: ${transportType}`);
47
61
  }
@@ -0,0 +1,7 @@
1
+ export function awaitableLog(logValue) {
2
+ return new Promise((resolve) => {
3
+ process.stdout.write(logValue, () => {
4
+ resolve();
5
+ });
6
+ });
7
+ }
@@ -1,4 +1,4 @@
1
- import { u as useToast, r as reactExports, j as jsxRuntimeExports, p as parseOAuthCallbackParams, g as generateOAuthErrorDescription, S as SESSION_KEYS, I as InspectorOAuthClientProvider, a as auth } from "./index-BIvDs4Sc.js";
1
+ import { u as useToast, r as reactExports, j as jsxRuntimeExports, p as parseOAuthCallbackParams, g as generateOAuthErrorDescription, S as SESSION_KEYS, I as InspectorOAuthClientProvider, a as auth } from "./index-B_qh-Btq.js";
2
2
  const OAuthCallback = ({ onConnect }) => {
3
3
  const { toast } = useToast();
4
4
  const hasProcessedRef = reactExports.useRef(false);
@@ -1,4 +1,4 @@
1
- import { r as reactExports, S as SESSION_KEYS, p as parseOAuthCallbackParams, j as jsxRuntimeExports, g as generateOAuthErrorDescription } from "./index-BIvDs4Sc.js";
1
+ import { r as reactExports, S as SESSION_KEYS, p as parseOAuthCallbackParams, j as jsxRuntimeExports, g as generateOAuthErrorDescription } from "./index-B_qh-Btq.js";
2
2
  const OAuthDebugCallback = ({ onConnect }) => {
3
3
  reactExports.useEffect(() => {
4
4
  let isProcessed = false;
@@ -21,6 +21,14 @@ const OAuthDebugCallback = ({ onConnect }) => {
21
21
  if (storedState) {
22
22
  try {
23
23
  restoredState = JSON.parse(storedState);
24
+ if (restoredState && typeof restoredState.resource === "string") {
25
+ restoredState.resource = new URL(restoredState.resource);
26
+ }
27
+ if (restoredState && typeof restoredState.authorizationUrl === "string") {
28
+ restoredState.authorizationUrl = new URL(
29
+ restoredState.authorizationUrl
30
+ );
31
+ }
24
32
  sessionStorage.removeItem(SESSION_KEYS.AUTH_DEBUGGER_STATE);
25
33
  } catch (e) {
26
34
  console.error("Failed to parse stored auth state:", e);
@@ -15121,6 +15121,7 @@ const enumType = ZodEnum.create;
15121
15121
  ZodPromise.create;
15122
15122
  const optionalType = ZodOptional.create;
15123
15123
  ZodNullable.create;
15124
+ const NEVER = INVALID;
15124
15125
  const LATEST_PROTOCOL_VERSION = "2025-06-18";
15125
15126
  const SUPPORTED_PROTOCOL_VERSIONS = [
15126
15127
  LATEST_PROTOCOL_VERSION,
@@ -16023,7 +16024,7 @@ unionType([
16023
16024
  CallToolRequestSchema,
16024
16025
  ListToolsRequestSchema
16025
16026
  ]);
16026
- const ClientNotificationSchema = unionType([
16027
+ unionType([
16027
16028
  CancelledNotificationSchema,
16028
16029
  ProgressNotificationSchema,
16029
16030
  InitializedNotificationSchema,
@@ -16041,7 +16042,7 @@ unionType([
16041
16042
  ElicitRequestSchema,
16042
16043
  ListRootsRequestSchema
16043
16044
  ]);
16044
- const ServerNotificationSchema = unionType([
16045
+ unionType([
16045
16046
  CancelledNotificationSchema,
16046
16047
  ProgressNotificationSchema,
16047
16048
  LoggingMessageNotificationSchema,
@@ -16070,9 +16071,22 @@ class McpError extends Error {
16070
16071
  this.name = "McpError";
16071
16072
  }
16072
16073
  }
16074
+ const SafeUrlSchema = stringType().url().superRefine((val, ctx) => {
16075
+ if (!URL.canParse(val)) {
16076
+ ctx.addIssue({
16077
+ code: ZodIssueCode.custom,
16078
+ message: "URL must be parseable",
16079
+ fatal: true
16080
+ });
16081
+ return NEVER;
16082
+ }
16083
+ }).refine((url) => {
16084
+ const u2 = new URL(url);
16085
+ return u2.protocol !== "javascript:" && u2.protocol !== "data:" && u2.protocol !== "vbscript:";
16086
+ }, { message: "URL cannot use javascript:, data:, or vbscript: scheme" });
16073
16087
  const OAuthProtectedResourceMetadataSchema = objectType({
16074
16088
  resource: stringType().url(),
16075
- authorization_servers: arrayType(stringType().url()).optional(),
16089
+ authorization_servers: arrayType(SafeUrlSchema).optional(),
16076
16090
  jwks_uri: stringType().url().optional(),
16077
16091
  scopes_supported: arrayType(stringType()).optional(),
16078
16092
  bearer_methods_supported: arrayType(stringType()).optional(),
@@ -16088,17 +16102,17 @@ const OAuthProtectedResourceMetadataSchema = objectType({
16088
16102
  }).passthrough();
16089
16103
  const OAuthMetadataSchema = objectType({
16090
16104
  issuer: stringType(),
16091
- authorization_endpoint: stringType(),
16092
- token_endpoint: stringType(),
16093
- registration_endpoint: stringType().optional(),
16105
+ authorization_endpoint: SafeUrlSchema,
16106
+ token_endpoint: SafeUrlSchema,
16107
+ registration_endpoint: SafeUrlSchema.optional(),
16094
16108
  scopes_supported: arrayType(stringType()).optional(),
16095
16109
  response_types_supported: arrayType(stringType()),
16096
16110
  response_modes_supported: arrayType(stringType()).optional(),
16097
16111
  grant_types_supported: arrayType(stringType()).optional(),
16098
16112
  token_endpoint_auth_methods_supported: arrayType(stringType()).optional(),
16099
16113
  token_endpoint_auth_signing_alg_values_supported: arrayType(stringType()).optional(),
16100
- service_documentation: stringType().optional(),
16101
- revocation_endpoint: stringType().optional(),
16114
+ service_documentation: SafeUrlSchema.optional(),
16115
+ revocation_endpoint: SafeUrlSchema.optional(),
16102
16116
  revocation_endpoint_auth_methods_supported: arrayType(stringType()).optional(),
16103
16117
  revocation_endpoint_auth_signing_alg_values_supported: arrayType(stringType()).optional(),
16104
16118
  introspection_endpoint: stringType().optional(),
@@ -16108,11 +16122,11 @@ const OAuthMetadataSchema = objectType({
16108
16122
  }).passthrough();
16109
16123
  const OpenIdProviderMetadataSchema = objectType({
16110
16124
  issuer: stringType(),
16111
- authorization_endpoint: stringType(),
16112
- token_endpoint: stringType(),
16113
- userinfo_endpoint: stringType().optional(),
16114
- jwks_uri: stringType(),
16115
- registration_endpoint: stringType().optional(),
16125
+ authorization_endpoint: SafeUrlSchema,
16126
+ token_endpoint: SafeUrlSchema,
16127
+ userinfo_endpoint: SafeUrlSchema.optional(),
16128
+ jwks_uri: SafeUrlSchema,
16129
+ registration_endpoint: SafeUrlSchema.optional(),
16116
16130
  scopes_supported: arrayType(stringType()).optional(),
16117
16131
  response_types_supported: arrayType(stringType()),
16118
16132
  response_modes_supported: arrayType(stringType()).optional(),
@@ -16140,8 +16154,8 @@ const OpenIdProviderMetadataSchema = objectType({
16140
16154
  request_parameter_supported: booleanType().optional(),
16141
16155
  request_uri_parameter_supported: booleanType().optional(),
16142
16156
  require_request_uri_registration: booleanType().optional(),
16143
- op_policy_uri: stringType().optional(),
16144
- op_tos_uri: stringType().optional()
16157
+ op_policy_uri: SafeUrlSchema.optional(),
16158
+ op_tos_uri: SafeUrlSchema.optional()
16145
16159
  }).passthrough();
16146
16160
  const OpenIdProviderDiscoveryMetadataSchema = OpenIdProviderMetadataSchema.merge(OAuthMetadataSchema.pick({
16147
16161
  code_challenge_methods_supported: true
@@ -16161,18 +16175,18 @@ const OAuthErrorResponseSchema = objectType({
16161
16175
  error_uri: stringType().optional()
16162
16176
  });
16163
16177
  const OAuthClientMetadataSchema = objectType({
16164
- redirect_uris: arrayType(stringType()).refine((uris) => uris.every((uri) => URL.canParse(uri)), { message: "redirect_uris must contain valid URLs" }),
16178
+ redirect_uris: arrayType(SafeUrlSchema),
16165
16179
  token_endpoint_auth_method: stringType().optional(),
16166
16180
  grant_types: arrayType(stringType()).optional(),
16167
16181
  response_types: arrayType(stringType()).optional(),
16168
16182
  client_name: stringType().optional(),
16169
- client_uri: stringType().optional(),
16170
- logo_uri: stringType().optional(),
16183
+ client_uri: SafeUrlSchema.optional(),
16184
+ logo_uri: SafeUrlSchema.optional(),
16171
16185
  scope: stringType().optional(),
16172
16186
  contacts: arrayType(stringType()).optional(),
16173
- tos_uri: stringType().optional(),
16187
+ tos_uri: SafeUrlSchema.optional(),
16174
16188
  policy_uri: stringType().optional(),
16175
- jwks_uri: stringType().optional(),
16189
+ jwks_uri: SafeUrlSchema.optional(),
16176
16190
  jwks: anyType().optional(),
16177
16191
  software_id: stringType().optional(),
16178
16192
  software_version: stringType().optional(),
@@ -16632,7 +16646,7 @@ async function tryMetadataDiscovery(url, protocolVersion, fetchFn = fetch) {
16632
16646
  return await fetchWithCorsRetry(url, headers, fetchFn);
16633
16647
  }
16634
16648
  function shouldAttemptFallback(response, pathname) {
16635
- return !response || response.status === 404 && pathname !== "/";
16649
+ return !response || response.status >= 400 && response.status < 500 && pathname !== "/";
16636
16650
  }
16637
16651
  async function discoverMetadataWithFallback(serverUrl, wellKnownType, fetchFn, opts) {
16638
16652
  var _a, _b;
@@ -16888,6 +16902,19 @@ const generateOAuthErrorDescription = (params) => {
16888
16902
  errorUri ? `More info: ${errorUri}.` : ""
16889
16903
  ].filter(Boolean).join("\n");
16890
16904
  };
16905
+ function validateRedirectUrl(url) {
16906
+ try {
16907
+ const parsedUrl = new URL(url);
16908
+ if (parsedUrl.protocol !== "http:" && parsedUrl.protocol !== "https:") {
16909
+ throw new Error("Authorization URL must be HTTP or HTTPS");
16910
+ }
16911
+ } catch (error) {
16912
+ if (error instanceof Error && error.message === "Authorization URL must be HTTP or HTTPS") {
16913
+ throw error;
16914
+ }
16915
+ throw new Error(`Invalid URL: ${url}`);
16916
+ }
16917
+ }
16891
16918
  const discoverScopes = async (serverUrl, resourceMetadata) => {
16892
16919
  try {
16893
16920
  const metadata = await discoverAuthorizationServerMetadata(
@@ -16996,9 +17023,7 @@ class InspectorOAuthClientProvider {
16996
17023
  sessionStorage.setItem(key, JSON.stringify(tokens));
16997
17024
  }
16998
17025
  redirectToAuthorization(authorizationUrl) {
16999
- if (authorizationUrl.protocol !== "http:" && authorizationUrl.protocol !== "https:") {
17000
- throw new Error("Authorization URL must be HTTP or HTTPS");
17001
- }
17026
+ validateRedirectUrl(authorizationUrl.href);
17002
17027
  window.location.href = authorizationUrl.href;
17003
17028
  }
17004
17029
  saveCodeVerifier(codeVerifier) {
@@ -24053,7 +24078,7 @@ function splitLines(chunk) {
24053
24078
  const crIndex = chunk.indexOf("\r", searchIndex), lfIndex = chunk.indexOf(`
24054
24079
  `, searchIndex);
24055
24080
  let lineEnd = -1;
24056
- if (crIndex !== -1 && lfIndex !== -1 ? lineEnd = Math.min(crIndex, lfIndex) : crIndex !== -1 ? lineEnd = crIndex : lfIndex !== -1 && (lineEnd = lfIndex), lineEnd === -1) {
24081
+ if (crIndex !== -1 && lfIndex !== -1 ? lineEnd = Math.min(crIndex, lfIndex) : crIndex !== -1 ? crIndex === chunk.length - 1 ? lineEnd = -1 : lineEnd = crIndex : lfIndex !== -1 && (lineEnd = lfIndex), lineEnd === -1) {
24057
24082
  incompleteLine = chunk.slice(searchIndex);
24058
24083
  break;
24059
24084
  } else {
@@ -24841,16 +24866,7 @@ class StreamableHTTPClientTransport {
24841
24866
  return this._protocolVersion;
24842
24867
  }
24843
24868
  }
24844
- const StdErrNotificationSchema = NotificationSchema.extend({
24845
- method: literalType("notifications/stderr"),
24846
- params: objectType({
24847
- content: stringType()
24848
- })
24849
- });
24850
- ClientNotificationSchema.or(
24851
- StdErrNotificationSchema
24852
- ).or(ServerNotificationSchema).or(NotificationSchema);
24853
- const version$1 = "0.16.5";
24869
+ const version$1 = "0.16.6";
24854
24870
  const packageJson = {
24855
24871
  version: version$1
24856
24872
  };
@@ -24982,7 +24998,6 @@ function useConnection({
24982
24998
  oauthScope,
24983
24999
  config,
24984
25000
  onNotification,
24985
- onStdErrNotification,
24986
25001
  onPendingRequest,
24987
25002
  onElicitationRequest,
24988
25003
  getRoots,
@@ -25036,7 +25051,7 @@ function useConnection({
25036
25051
  mcpRequestOptions.onprogress = (params) => {
25037
25052
  if (onNotification) {
25038
25053
  onNotification({
25039
- method: "notification/progress",
25054
+ method: "notifications/progress",
25040
25055
  params
25041
25056
  });
25042
25057
  }
@@ -25221,11 +25236,18 @@ function useConnection({
25221
25236
  let transportOptions;
25222
25237
  let mcpProxyServerUrl;
25223
25238
  switch (transportType) {
25224
- case "stdio":
25239
+ case "stdio": {
25225
25240
  mcpProxyServerUrl = new URL(`${getMCPProxyAddress(config)}/stdio`);
25226
25241
  mcpProxyServerUrl.searchParams.append("command", command);
25227
25242
  mcpProxyServerUrl.searchParams.append("args", args);
25228
25243
  mcpProxyServerUrl.searchParams.append("env", JSON.stringify(env));
25244
+ const proxyFullAddress = config.MCP_PROXY_FULL_ADDRESS.value;
25245
+ if (proxyFullAddress) {
25246
+ mcpProxyServerUrl.searchParams.append(
25247
+ "proxyFullAddress",
25248
+ proxyFullAddress
25249
+ );
25250
+ }
25229
25251
  transportOptions = {
25230
25252
  authProvider: serverAuthProvider,
25231
25253
  eventSourceInit: {
@@ -25239,9 +25261,17 @@ function useConnection({
25239
25261
  }
25240
25262
  };
25241
25263
  break;
25242
- case "sse":
25264
+ }
25265
+ case "sse": {
25243
25266
  mcpProxyServerUrl = new URL(`${getMCPProxyAddress(config)}/sse`);
25244
25267
  mcpProxyServerUrl.searchParams.append("url", sseUrl);
25268
+ const proxyFullAddressSSE = config.MCP_PROXY_FULL_ADDRESS.value;
25269
+ if (proxyFullAddressSSE) {
25270
+ mcpProxyServerUrl.searchParams.append(
25271
+ "proxyFullAddress",
25272
+ proxyFullAddressSSE
25273
+ );
25274
+ }
25245
25275
  transportOptions = {
25246
25276
  eventSourceInit: {
25247
25277
  fetch: (url, init) => fetch(url, {
@@ -25254,6 +25284,7 @@ function useConnection({
25254
25284
  }
25255
25285
  };
25256
25286
  break;
25287
+ }
25257
25288
  case "streamable-http":
25258
25289
  mcpProxyServerUrl = new URL(`${getMCPProxyAddress(config)}/mcp`);
25259
25290
  mcpProxyServerUrl.searchParams.append("url", sseUrl);
@@ -25297,12 +25328,6 @@ function useConnection({
25297
25328
  return Promise.resolve();
25298
25329
  };
25299
25330
  }
25300
- if (onStdErrNotification) {
25301
- client2.setNotificationHandler(
25302
- StdErrNotificationSchema,
25303
- onStdErrNotification
25304
- );
25305
- }
25306
25331
  let capabilities;
25307
25332
  try {
25308
25333
  const transport = transportType === "streamable-http" ? new StreamableHTTPClientTransport(mcpProxyServerUrl, {
@@ -25363,6 +25388,15 @@ function useConnection({
25363
25388
  if ((capabilities == null ? void 0 : capabilities.logging) && defaultLoggingLevel) {
25364
25389
  lastRequest = "logging/setLevel";
25365
25390
  await client2.setLoggingLevel(defaultLoggingLevel);
25391
+ pushHistory(
25392
+ {
25393
+ method: "logging/setLevel",
25394
+ params: {
25395
+ level: defaultLoggingLevel
25396
+ }
25397
+ },
25398
+ {}
25399
+ );
25366
25400
  lastRequest = "";
25367
25401
  }
25368
25402
  if (onElicitationRequest) {
@@ -26567,6 +26601,7 @@ const OAuthFlowProgress = ({
26567
26601
  updateAuthState,
26568
26602
  proceedToNextStep
26569
26603
  }) => {
26604
+ const { toast: toast2 } = useToast();
26570
26605
  const provider = reactExports.useMemo(
26571
26606
  () => new DebugInspectorOAuthClientProvider(serverUrl),
26572
26607
  [serverUrl]
@@ -26697,11 +26732,24 @@ const OAuthFlowProgress = ({
26697
26732
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
26698
26733
  /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs break-all", children: authState.authorizationUrl }),
26699
26734
  /* @__PURE__ */ jsxRuntimeExports.jsx(
26700
- "a",
26735
+ "button",
26701
26736
  {
26702
- href: authState.authorizationUrl,
26703
- target: "_blank",
26704
- rel: "noopener noreferrer",
26737
+ onClick: () => {
26738
+ try {
26739
+ validateRedirectUrl(authState.authorizationUrl);
26740
+ window.open(
26741
+ authState.authorizationUrl,
26742
+ "_blank",
26743
+ "noopener noreferrer"
26744
+ );
26745
+ } catch (error) {
26746
+ toast2({
26747
+ title: "Invalid URL",
26748
+ description: error instanceof Error ? error.message : "The authorization URL is not valid",
26749
+ variant: "destructive"
26750
+ });
26751
+ }
26752
+ },
26705
26753
  className: "flex items-center text-blue-500 hover:text-blue-700",
26706
26754
  "aria-label": "Open authorization URL in new tab",
26707
26755
  title: "Open authorization URL",
@@ -26787,7 +26835,18 @@ const OAuthFlowProgress = ({
26787
26835
  Button,
26788
26836
  {
26789
26837
  variant: "outline",
26790
- onClick: () => window.open(authState.authorizationUrl, "_blank"),
26838
+ onClick: () => {
26839
+ try {
26840
+ validateRedirectUrl(authState.authorizationUrl);
26841
+ window.open(authState.authorizationUrl, "_blank");
26842
+ } catch (error) {
26843
+ toast2({
26844
+ title: "Invalid URL",
26845
+ description: error instanceof Error ? error.message : "The authorization URL is not valid",
26846
+ variant: "destructive"
26847
+ });
26848
+ }
26849
+ },
26791
26850
  children: "Open in New Tab"
26792
26851
  }
26793
26852
  )
@@ -26915,6 +26974,20 @@ const AuthDebugger = ({
26915
26974
  while (currentState.oauthStep !== "complete") {
26916
26975
  await oauthMachine.executeStep(currentState);
26917
26976
  if (currentState.oauthStep === "authorization_code" && currentState.authorizationUrl) {
26977
+ try {
26978
+ validateRedirectUrl(currentState.authorizationUrl);
26979
+ } catch (error) {
26980
+ updateAuthState({
26981
+ ...currentState,
26982
+ isInitiatingAuth: false,
26983
+ latestError: error instanceof Error ? error : new Error(String(error)),
26984
+ statusMessage: {
26985
+ type: "error",
26986
+ message: `Invalid authorization URL: ${error instanceof Error ? error.message : String(error)}`
26987
+ }
26988
+ });
26989
+ return;
26990
+ }
26918
26991
  sessionStorage.setItem(
26919
26992
  SESSION_KEYS.AUTH_DEBUGGER_STATE,
26920
26993
  JSON.stringify(currentState)
@@ -30322,15 +30395,9 @@ function getWindowScrollBarX(element, rect) {
30322
30395
  }
30323
30396
  return rect.left + leftScroll;
30324
30397
  }
30325
- function getHTMLOffset(documentElement, scroll, ignoreScrollbarX) {
30326
- if (ignoreScrollbarX === void 0) {
30327
- ignoreScrollbarX = false;
30328
- }
30398
+ function getHTMLOffset(documentElement, scroll) {
30329
30399
  const htmlRect = documentElement.getBoundingClientRect();
30330
- const x = htmlRect.left + scroll.scrollLeft - (ignoreScrollbarX ? 0 : (
30331
- // RTL <body> scrollbar.
30332
- getWindowScrollBarX(documentElement, htmlRect)
30333
- ));
30400
+ const x = htmlRect.left + scroll.scrollLeft - getWindowScrollBarX(documentElement, htmlRect);
30334
30401
  const y = htmlRect.top + scroll.scrollTop;
30335
30402
  return {
30336
30403
  x,
@@ -30368,7 +30435,7 @@ function convertOffsetParentRelativeRectToViewportRelativeRect(_ref) {
30368
30435
  offsets.y = offsetRect.y + offsetParent.clientTop;
30369
30436
  }
30370
30437
  }
30371
- const htmlOffset = documentElement && !isOffsetParentAnElement && !isFixed ? getHTMLOffset(documentElement, scroll, true) : createCoords(0);
30438
+ const htmlOffset = documentElement && !isOffsetParentAnElement && !isFixed ? getHTMLOffset(documentElement, scroll) : createCoords(0);
30372
30439
  return {
30373
30440
  width: rect.width * scale.x,
30374
30441
  height: rect.height * scale.y,
@@ -30397,6 +30464,7 @@ function getDocumentRect(element) {
30397
30464
  y
30398
30465
  };
30399
30466
  }
30467
+ const SCROLLBAR_MAX = 25;
30400
30468
  function getViewportRect(element, strategy) {
30401
30469
  const win = getWindow(element);
30402
30470
  const html = getDocumentElement(element);
@@ -30414,6 +30482,19 @@ function getViewportRect(element, strategy) {
30414
30482
  y = visualViewport.offsetTop;
30415
30483
  }
30416
30484
  }
30485
+ const windowScrollbarX = getWindowScrollBarX(html);
30486
+ if (windowScrollbarX <= 0) {
30487
+ const doc = html.ownerDocument;
30488
+ const body = doc.body;
30489
+ const bodyStyles = getComputedStyle(body);
30490
+ const bodyMarginInline = doc.compatMode === "CSS1Compat" ? parseFloat(bodyStyles.marginLeft) + parseFloat(bodyStyles.marginRight) || 0 : 0;
30491
+ const clippingStableScrollbarWidth = Math.abs(html.clientWidth - body.clientWidth - bodyMarginInline);
30492
+ if (clippingStableScrollbarWidth <= SCROLLBAR_MAX) {
30493
+ width -= clippingStableScrollbarWidth;
30494
+ }
30495
+ } else if (windowScrollbarX <= SCROLLBAR_MAX) {
30496
+ width += windowScrollbarX;
30497
+ }
30417
30498
  return {
30418
30499
  width,
30419
30500
  height,
@@ -36391,7 +36472,7 @@ const useTheme = () => {
36391
36472
  [theme, setThemeWithSideEffect]
36392
36473
  );
36393
36474
  };
36394
- const version = "0.16.5";
36475
+ const version = "0.16.6";
36395
36476
  var [createTooltipContext, createTooltipScope] = createContextScope("Tooltip", [
36396
36477
  createPopperScope
36397
36478
  ]);
@@ -36896,8 +36977,6 @@ const Sidebar = ({
36896
36977
  setOauthScope,
36897
36978
  onConnect,
36898
36979
  onDisconnect,
36899
- stdErrNotifications,
36900
- clearStdErrNotifications,
36901
36980
  logLevel,
36902
36981
  sendLogLevelRequest,
36903
36982
  loggingSupported,
@@ -37039,6 +37118,7 @@ const Sidebar = ({
37039
37118
  placeholder: "Command",
37040
37119
  value: command,
37041
37120
  onChange: (e) => setCommand(e.target.value),
37121
+ onBlur: (e) => setCommand(e.target.value.trim()),
37042
37122
  className: "font-mono"
37043
37123
  }
37044
37124
  )
@@ -37508,30 +37588,7 @@ const Sidebar = ({
37508
37588
  ]
37509
37589
  }
37510
37590
  )
37511
- ] }),
37512
- stdErrNotifications.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx(jsxRuntimeExports.Fragment, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-4 border-t border-gray-200 pt-4", children: [
37513
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center", children: [
37514
- /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "text-sm font-medium", children: "Error output from MCP server" }),
37515
- /* @__PURE__ */ jsxRuntimeExports.jsx(
37516
- Button,
37517
- {
37518
- variant: "outline",
37519
- size: "sm",
37520
- onClick: clearStdErrNotifications,
37521
- className: "h-8 px-2",
37522
- children: "Clear"
37523
- }
37524
- )
37525
- ] }),
37526
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-2 max-h-80 overflow-y-auto", children: stdErrNotifications.map((notification, index2) => /* @__PURE__ */ jsxRuntimeExports.jsx(
37527
- "div",
37528
- {
37529
- className: "text-sm text-red-500 font-mono py-2 border-b border-gray-200 last:border-b-0",
37530
- children: notification.params.content
37531
- },
37532
- index2
37533
- )) })
37534
- ] }) })
37591
+ ] })
37535
37592
  ] })
37536
37593
  ] }) }),
37537
37594
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "p-4 border-t", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between", children: [
@@ -38521,7 +38578,6 @@ const App = () => {
38521
38578
  const [transportType, setTransportType] = reactExports.useState(getInitialTransportType);
38522
38579
  const [logLevel, setLogLevel] = reactExports.useState("debug");
38523
38580
  const [notifications, setNotifications] = reactExports.useState([]);
38524
- const [stdErrNotifications, setStdErrNotifications] = reactExports.useState([]);
38525
38581
  const [roots, setRoots] = reactExports.useState([]);
38526
38582
  const [env, setEnv] = reactExports.useState({});
38527
38583
  const [config, setConfig] = reactExports.useState(
@@ -38600,12 +38656,6 @@ const App = () => {
38600
38656
  onNotification: (notification) => {
38601
38657
  setNotifications((prev) => [...prev, notification]);
38602
38658
  },
38603
- onStdErrNotification: (notification) => {
38604
- setStdErrNotifications((prev) => [
38605
- ...prev,
38606
- notification
38607
- ]);
38608
- },
38609
38659
  onPendingRequest: (request, resolve, reject) => {
38610
38660
  setPendingSampleRequests((prev) => [
38611
38661
  ...prev,
@@ -39043,9 +39093,6 @@ const App = () => {
39043
39093
  );
39044
39094
  setLogLevel(level);
39045
39095
  };
39046
- const clearStdErrNotifications = () => {
39047
- setStdErrNotifications([]);
39048
- };
39049
39096
  const AuthDebuggerWrapper = () => /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "auth", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
39050
39097
  AuthDebugger,
39051
39098
  {
@@ -39057,13 +39104,13 @@ const App = () => {
39057
39104
  ) });
39058
39105
  if (window.location.pathname === "/oauth/callback") {
39059
39106
  const OAuthCallback = React.lazy(
39060
- () => __vitePreload(() => import("./OAuthCallback-BxZPCak0.js"), true ? [] : void 0)
39107
+ () => __vitePreload(() => import("./OAuthCallback-Cmffn8om.js"), true ? [] : void 0)
39061
39108
  );
39062
39109
  return /* @__PURE__ */ jsxRuntimeExports.jsx(reactExports.Suspense, { fallback: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { children: "Loading..." }), children: /* @__PURE__ */ jsxRuntimeExports.jsx(OAuthCallback, { onConnect: onOAuthConnect }) });
39063
39110
  }
39064
39111
  if (window.location.pathname === "/oauth/callback/debug") {
39065
39112
  const OAuthDebugCallback = React.lazy(
39066
- () => __vitePreload(() => import("./OAuthDebugCallback-CwfxQhTF.js"), true ? [] : void 0)
39113
+ () => __vitePreload(() => import("./OAuthDebugCallback-BhzcPtuF.js"), true ? [] : void 0)
39067
39114
  );
39068
39115
  return /* @__PURE__ */ jsxRuntimeExports.jsx(reactExports.Suspense, { fallback: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { children: "Loading..." }), children: /* @__PURE__ */ jsxRuntimeExports.jsx(OAuthDebugCallback, { onConnect: onOAuthDebugConnect }) });
39069
39116
  }
@@ -39105,11 +39152,9 @@ const App = () => {
39105
39152
  setOauthScope,
39106
39153
  onConnect: connectMcpServer,
39107
39154
  onDisconnect: disconnectMcpServer,
39108
- stdErrNotifications,
39109
39155
  logLevel,
39110
39156
  sendLogLevelRequest,
39111
- loggingSupported: !!(serverCapabilities == null ? void 0 : serverCapabilities.logging) || false,
39112
- clearStdErrNotifications
39157
+ loggingSupported: !!(serverCapabilities == null ? void 0 : serverCapabilities.logging) || false
39113
39158
  }
39114
39159
  ),
39115
39160
  /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -944,9 +944,6 @@ video {
944
944
  .h-screen {
945
945
  height: 100vh;
946
946
  }
947
- .max-h-80 {
948
- max-height: 20rem;
949
- }
950
947
  .max-h-96 {
951
948
  max-height: 24rem;
952
949
  }
@@ -1407,9 +1404,6 @@ video {
1407
1404
  .pr-8 {
1408
1405
  padding-right: 2rem;
1409
1406
  }
1410
- .pt-4 {
1411
- padding-top: 1rem;
1412
- }
1413
1407
  .text-left {
1414
1408
  text-align: left;
1415
1409
  }
@@ -1625,6 +1619,10 @@ video {
1625
1619
  .ring-offset-background {
1626
1620
  --tw-ring-offset-color: hsl(var(--background));
1627
1621
  }
1622
+ .blur {
1623
+ --tw-blur: blur(8px);
1624
+ filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
1625
+ }
1628
1626
  .filter {
1629
1627
  filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
1630
1628
  }
@@ -1768,10 +1766,6 @@ h1 {
1768
1766
  color: hsl(var(--muted-foreground));
1769
1767
  }
1770
1768
 
1771
- .last\:border-b-0:last-child {
1772
- border-bottom-width: 0px;
1773
- }
1774
-
1775
1769
  .hover\:border-\[\#646cff\]:hover {
1776
1770
  --tw-border-opacity: 1;
1777
1771
  border-color: rgb(100 108 255 / var(--tw-border-opacity, 1));
@@ -5,8 +5,8 @@
5
5
  <link rel="icon" type="image/svg+xml" href="/mcp.svg" />
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
7
  <title>MCP Inspector</title>
8
- <script type="module" crossorigin src="/assets/index-BIvDs4Sc.js"></script>
9
- <link rel="stylesheet" crossorigin href="/assets/index-BWZfLo5E.css">
8
+ <script type="module" crossorigin src="/assets/index-B_qh-Btq.js"></script>
9
+ <link rel="stylesheet" crossorigin href="/assets/index-C217Ot8c.css">
10
10
  </head>
11
11
  <body>
12
12
  <div id="root" class="w-full"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@modelcontextprotocol/inspector",
3
- "version": "0.16.5",
3
+ "version": "0.16.6",
4
4
  "description": "Model Context Protocol inspector",
5
5
  "license": "MIT",
6
6
  "author": "Anthropic, PBC (https://anthropic.com)",
@@ -41,16 +41,16 @@
41
41
  "prettier-fix": "prettier --write .",
42
42
  "prettier-check": "prettier --check .",
43
43
  "lint": "prettier --check . && cd client && npm run lint",
44
- "prepare": "npm run build",
44
+ "prepare": "husky && npm run build",
45
45
  "publish-all": "npm publish --workspaces --access public && npm publish --access public",
46
46
  "update-version": "node scripts/update-version.js",
47
47
  "check-version": "node scripts/check-version-consistency.js"
48
48
  },
49
49
  "dependencies": {
50
- "@modelcontextprotocol/inspector-cli": "^0.16.5",
51
- "@modelcontextprotocol/inspector-client": "^0.16.5",
52
- "@modelcontextprotocol/inspector-server": "^0.16.5",
53
- "@modelcontextprotocol/sdk": "^1.17.3",
50
+ "@modelcontextprotocol/inspector-cli": "^0.16.6",
51
+ "@modelcontextprotocol/inspector-client": "^0.16.6",
52
+ "@modelcontextprotocol/inspector-server": "^0.16.6",
53
+ "@modelcontextprotocol/sdk": "^1.17.5",
54
54
  "concurrently": "^9.2.0",
55
55
  "open": "^10.2.0",
56
56
  "shell-quote": "^1.8.3",
@@ -63,12 +63,19 @@
63
63
  "@types/jest": "^29.5.14",
64
64
  "@types/node": "^22.17.0",
65
65
  "@types/shell-quote": "^1.7.5",
66
+ "husky": "^9.1.7",
66
67
  "jest-fixed-jsdom": "^0.0.9",
68
+ "lint-staged": "^16.1.5",
67
69
  "prettier": "^3.6.2",
68
70
  "rimraf": "^6.0.1",
69
71
  "typescript": "^5.4.2"
70
72
  },
71
73
  "engines": {
72
74
  "node": ">=22.7.5"
75
+ },
76
+ "lint-staged": {
77
+ "**/*.{js,ts,jsx,tsx,json,md}": [
78
+ "prettier --write"
79
+ ]
73
80
  }
74
81
  }
@@ -131,7 +131,7 @@ const createTransport = async (req) => {
131
131
  console.log("Query parameters:", JSON.stringify(query));
132
132
  const transportType = query.transportType;
133
133
  if (transportType === "stdio") {
134
- const command = query.command;
134
+ const command = query.command.trim();
135
135
  const origArgs = shellParseArgs(query.args);
136
136
  const queryEnv = query.env ? JSON.parse(query.env) : {};
137
137
  const env = { ...defaultEnvironment, ...process.env, ...queryEnv };
@@ -280,7 +280,6 @@ app.get("/stdio", originValidationMiddleware, authMiddleware, async (req, res) =
280
280
  let serverTransport;
281
281
  try {
282
282
  serverTransport = await createTransport(req);
283
- console.log("Created server transport");
284
283
  }
285
284
  catch (error) {
286
285
  if (error instanceof SseError && error.code === 401) {
@@ -290,32 +289,89 @@ app.get("/stdio", originValidationMiddleware, authMiddleware, async (req, res) =
290
289
  }
291
290
  throw error;
292
291
  }
293
- const webAppTransport = new SSEServerTransport("/message", res);
294
- console.log("Created client transport");
292
+ const proxyFullAddress = req.query.proxyFullAddress || "";
293
+ const prefix = proxyFullAddress || "";
294
+ const endpoint = `${prefix}/message`;
295
+ const webAppTransport = new SSEServerTransport(endpoint, res);
295
296
  webAppTransports.set(webAppTransport.sessionId, webAppTransport);
297
+ console.log("Created client transport");
296
298
  serverTransports.set(webAppTransport.sessionId, serverTransport);
299
+ console.log("Created server transport");
297
300
  await webAppTransport.start();
298
301
  serverTransport.stderr.on("data", (chunk) => {
299
302
  if (chunk.toString().includes("MODULE_NOT_FOUND")) {
303
+ // Server command not found, remove transports
304
+ const message = "Command not found, transports removed";
300
305
  webAppTransport.send({
301
306
  jsonrpc: "2.0",
302
- method: "notifications/stderr",
307
+ method: "notifications/message",
303
308
  params: {
304
- content: "Command not found, transports removed",
309
+ level: "emergency",
310
+ logger: "proxy",
311
+ data: {
312
+ message,
313
+ },
305
314
  },
306
315
  });
307
316
  webAppTransport.close();
308
317
  serverTransport.close();
309
318
  webAppTransports.delete(webAppTransport.sessionId);
310
319
  serverTransports.delete(webAppTransport.sessionId);
311
- console.error("Command not found, transports removed");
320
+ console.error(message);
312
321
  }
313
322
  else {
323
+ // Inspect message and attempt to assign a RFC 5424 Syslog Protocol level
324
+ let level;
325
+ let message = chunk.toString().trim();
326
+ let ucMsg = chunk.toString().toUpperCase();
327
+ if (ucMsg.includes("DEBUG")) {
328
+ level = "debug";
329
+ }
330
+ else if (ucMsg.includes("INFO")) {
331
+ level = "info";
332
+ }
333
+ else if (ucMsg.includes("NOTICE")) {
334
+ level = "notice";
335
+ }
336
+ else if (ucMsg.includes("WARN")) {
337
+ level = "warning";
338
+ }
339
+ else if (ucMsg.includes("ERROR")) {
340
+ level = "error";
341
+ }
342
+ else if (ucMsg.includes("CRITICAL")) {
343
+ level = "critical";
344
+ }
345
+ else if (ucMsg.includes("ALERT")) {
346
+ level = "alert";
347
+ }
348
+ else if (ucMsg.includes("EMERGENCY")) {
349
+ level = "emergency";
350
+ }
351
+ else if (ucMsg.includes("SIGINT")) {
352
+ message = "SIGINT received. Server shutdown.";
353
+ level = "emergency";
354
+ }
355
+ else if (ucMsg.includes("SIGHUP")) {
356
+ message = "SIGHUP received. Server shutdown.";
357
+ level = "emergency";
358
+ }
359
+ else if (ucMsg.includes("SIGTERM")) {
360
+ message = "SIGTERM received. Server shutdown.";
361
+ level = "emergency";
362
+ }
363
+ else {
364
+ level = "info";
365
+ }
314
366
  webAppTransport.send({
315
367
  jsonrpc: "2.0",
316
- method: "notifications/stderr",
368
+ method: "notifications/message",
317
369
  params: {
318
- content: chunk.toString(),
370
+ level,
371
+ logger: "stdio",
372
+ data: {
373
+ message,
374
+ },
319
375
  },
320
376
  });
321
377
  }
@@ -332,7 +388,7 @@ app.get("/stdio", originValidationMiddleware, authMiddleware, async (req, res) =
332
388
  });
333
389
  app.get("/sse", originValidationMiddleware, authMiddleware, async (req, res) => {
334
390
  try {
335
- console.log("New SSE connection request. NOTE: The sse transport is deprecated and has been replaced by StreamableHttp");
391
+ console.log("New SSE connection request. NOTE: The SSE transport is deprecated and has been replaced by StreamableHttp");
336
392
  let serverTransport;
337
393
  try {
338
394
  serverTransport = await createTransport(req);
@@ -357,7 +413,10 @@ app.get("/sse", originValidationMiddleware, authMiddleware, async (req, res) =>
357
413
  }
358
414
  }
359
415
  if (serverTransport) {
360
- const webAppTransport = new SSEServerTransport("/message", res);
416
+ const proxyFullAddress = req.query.proxyFullAddress || "";
417
+ const prefix = proxyFullAddress || "";
418
+ const endpoint = `${prefix}/message`;
419
+ const webAppTransport = new SSEServerTransport(endpoint, res);
361
420
  webAppTransports.set(webAppTransport.sessionId, webAppTransport);
362
421
  console.log("Created client transport");
363
422
  serverTransports.set(webAppTransport.sessionId, serverTransport);