@modelcontextprotocol/inspector 0.16.5 → 0.16.7

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`:
@@ -90,6 +98,16 @@ The MCP Inspector provides convenient buttons to export server launch configurat
90
98
  }
91
99
  ```
92
100
 
101
+ **Streamable HTTP transport example:**
102
+
103
+ ```json
104
+ {
105
+ "type": "streamable-http",
106
+ "url": "http://localhost:3000/mcp",
107
+ "note": "For Streamable HTTP connections, add this URL directly in your MCP Client"
108
+ }
109
+ ```
110
+
93
111
  - **Servers File** - Copies a complete MCP configuration file structure to your clipboard, with your current server configuration added as `default-server`. This can be saved directly as `mcp.json`.
94
112
 
95
113
  **STDIO transport example:**
@@ -123,9 +141,23 @@ The MCP Inspector provides convenient buttons to export server launch configurat
123
141
  }
124
142
  ```
125
143
 
144
+ **Streamable HTTP transport example:**
145
+
146
+ ```json
147
+ {
148
+ "mcpServers": {
149
+ "default-server": {
150
+ "type": "streamable-http",
151
+ "url": "http://localhost:3000/mcp",
152
+ "note": "For Streamable HTTP connections, add this URL directly in your MCP Client"
153
+ }
154
+ }
155
+ }
156
+ ```
157
+
126
158
  These buttons appear in the Inspector UI after you've configured your server settings, making it easy to save and reuse your configurations.
127
159
 
128
- For SSE transport connections, the Inspector provides similar functionality for both buttons. The "Server Entry" button copies the SSE URL configuration that can be added to your existing configuration file, while the "Servers File" button creates a complete configuration file containing the SSE URL for direct use in clients.
160
+ For SSE and Streamable HTTP transport connections, the Inspector provides similar functionality for both buttons. The "Server Entry" button copies the configuration that can be added to your existing configuration file, while the "Servers File" button creates a complete configuration file containing the URL for direct use in clients.
129
161
 
130
162
  You can paste the Server Entry into your existing `mcp.json` file under your chosen server name, or use the complete Servers File payload to create a new configuration file.
131
163
 
@@ -387,6 +419,9 @@ npx @modelcontextprotocol/inspector --cli node build/index.js --method tools/lis
387
419
  # Call a specific tool
388
420
  npx @modelcontextprotocol/inspector --cli node build/index.js --method tools/call --tool-name mytool --tool-arg key=value --tool-arg another=value2
389
421
 
422
+ # Call a tool with JSON arguments
423
+ npx @modelcontextprotocol/inspector --cli node build/index.js --method tools/call --tool-name mytool --tool-arg 'options={"format": "json", "max_tokens": 100}'
424
+
390
425
  # List available resources
391
426
  npx @modelcontextprotocol/inspector --cli node build/index.js --method resources/list
392
427
 
@@ -399,6 +434,9 @@ npx @modelcontextprotocol/inspector --cli https://my-mcp-server.example.com
399
434
  # Connect to a remote MCP server (with Streamable HTTP transport)
400
435
  npx @modelcontextprotocol/inspector --cli https://my-mcp-server.example.com --transport http --method tools/list
401
436
 
437
+ # Connect to a remote MCP server (with custom headers)
438
+ npx @modelcontextprotocol/inspector --cli https://my-mcp-server.example.com --transport http --method tools/list --header "X-API-Key: your-api-key"
439
+
402
440
  # Call a tool on a remote server
403
441
  npx @modelcontextprotocol/inspector --cli https://my-mcp-server.example.com --method tools/call --tool-name remotetool --tool-arg param=value
404
442
 
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-Dg7mXHRE.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-Dg7mXHRE.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);
@@ -735,6 +735,39 @@ video {
735
735
  background-color: hsl(var(--background));
736
736
  color: hsl(var(--foreground));
737
737
  }
738
+ .container {
739
+ width: 100%;
740
+ }
741
+ @media (min-width: 640px) {
742
+
743
+ .container {
744
+ max-width: 640px;
745
+ }
746
+ }
747
+ @media (min-width: 768px) {
748
+
749
+ .container {
750
+ max-width: 768px;
751
+ }
752
+ }
753
+ @media (min-width: 1024px) {
754
+
755
+ .container {
756
+ max-width: 1024px;
757
+ }
758
+ }
759
+ @media (min-width: 1280px) {
760
+
761
+ .container {
762
+ max-width: 1280px;
763
+ }
764
+ }
765
+ @media (min-width: 1536px) {
766
+
767
+ .container {
768
+ max-width: 1536px;
769
+ }
770
+ }
738
771
  .sr-only {
739
772
  position: absolute;
740
773
  width: 1px;
@@ -944,9 +977,6 @@ video {
944
977
  .h-screen {
945
978
  height: 100vh;
946
979
  }
947
- .max-h-80 {
948
- max-height: 20rem;
949
- }
950
980
  .max-h-96 {
951
981
  max-height: 24rem;
952
982
  }
@@ -1407,9 +1437,6 @@ video {
1407
1437
  .pr-8 {
1408
1438
  padding-right: 2rem;
1409
1439
  }
1410
- .pt-4 {
1411
- padding-top: 1rem;
1412
- }
1413
1440
  .text-left {
1414
1441
  text-align: left;
1415
1442
  }
@@ -1625,6 +1652,10 @@ video {
1625
1652
  .ring-offset-background {
1626
1653
  --tw-ring-offset-color: hsl(var(--background));
1627
1654
  }
1655
+ .blur {
1656
+ --tw-blur: blur(8px);
1657
+ 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);
1658
+ }
1628
1659
  .filter {
1629
1660
  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
1661
  }
@@ -1768,10 +1799,6 @@ h1 {
1768
1799
  color: hsl(var(--muted-foreground));
1769
1800
  }
1770
1801
 
1771
- .last\:border-b-0:last-child {
1772
- border-bottom-width: 0px;
1773
- }
1774
-
1775
1802
  .hover\:border-\[\#646cff\]:hover {
1776
1803
  --tw-border-opacity: 1;
1777
1804
  border-color: rgb(100 108 255 / var(--tw-border-opacity, 1));