@modelcontextprotocol/inspector 0.12.0 → 0.14.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.
@@ -0,0 +1,33 @@
1
+ const parseOAuthCallbackParams = (location) => {
2
+ const params = new URLSearchParams(location);
3
+ const code = params.get("code");
4
+ if (code) {
5
+ return { successful: true, code };
6
+ }
7
+ const error = params.get("error");
8
+ const error_description = params.get("error_description");
9
+ const error_uri = params.get("error_uri");
10
+ if (error) {
11
+ return { successful: false, error, error_description, error_uri };
12
+ }
13
+ return {
14
+ successful: false,
15
+ error: "invalid_request",
16
+ error_description: "Missing code or error in response",
17
+ error_uri: null
18
+ };
19
+ };
20
+ const generateOAuthErrorDescription = (params) => {
21
+ const error = params.error;
22
+ const errorDescription = params.error_description;
23
+ const errorUri = params.error_uri;
24
+ return [
25
+ `Error: ${error}.`,
26
+ errorDescription ? `Details: ${errorDescription}.` : "",
27
+ errorUri ? `More info: ${errorUri}.` : ""
28
+ ].filter(Boolean).join("\n");
29
+ };
30
+ export {
31
+ generateOAuthErrorDescription as g,
32
+ parseOAuthCallbackParams as p
33
+ };
@@ -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-CHwMp9hp.js"></script>
9
- <link rel="stylesheet" crossorigin href="/assets/index-D5WIP3OH.css">
8
+ <script type="module" crossorigin src="/assets/index-CfNcvwpX.js"></script>
9
+ <link rel="stylesheet" crossorigin href="/assets/index-1IEtev7A.css">
10
10
  </head>
11
11
  <body>
12
12
  <div id="root"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@modelcontextprotocol/inspector",
3
- "version": "0.12.0",
3
+ "version": "0.14.0",
4
4
  "description": "Model Context Protocol inspector",
5
5
  "license": "MIT",
6
6
  "author": "Anthropic, PBC (https://anthropic.com)",
@@ -36,15 +36,17 @@
36
36
  "test-cli": "cd cli && npm run test",
37
37
  "prettier-fix": "prettier --write .",
38
38
  "prettier-check": "prettier --check .",
39
+ "lint": "prettier --check . && cd client && npm run lint",
39
40
  "prepare": "npm run build",
40
41
  "publish-all": "npm publish --workspaces --access public && npm publish --access public"
41
42
  },
42
43
  "dependencies": {
43
- "@modelcontextprotocol/inspector-cli": "^0.12.0",
44
- "@modelcontextprotocol/inspector-client": "^0.12.0",
45
- "@modelcontextprotocol/inspector-server": "^0.12.0",
46
- "@modelcontextprotocol/sdk": "^1.11.0",
44
+ "@modelcontextprotocol/inspector-cli": "^0.14.0",
45
+ "@modelcontextprotocol/inspector-client": "^0.14.0",
46
+ "@modelcontextprotocol/inspector-server": "^0.14.0",
47
+ "@modelcontextprotocol/sdk": "^1.12.1",
47
48
  "concurrently": "^9.0.1",
49
+ "open": "^10.1.0",
48
50
  "shell-quote": "^1.8.2",
49
51
  "spawn-rx": "^5.1.2",
50
52
  "ts-node": "^10.9.2",
@@ -28,13 +28,43 @@ const { values } = parseArgs({
28
28
  args: { type: "string", default: "" },
29
29
  },
30
30
  });
31
+ // Function to get HTTP headers.
32
+ // Supports only "sse" and "streamable-http" transport types.
33
+ const getHttpHeaders = (req, transportType) => {
34
+ const headers = {
35
+ Accept: transportType === "sse"
36
+ ? "text/event-stream"
37
+ : "text/event-stream, application/json",
38
+ };
39
+ const defaultHeaders = transportType === "sse"
40
+ ? SSE_HEADERS_PASSTHROUGH
41
+ : STREAMABLE_HTTP_HEADERS_PASSTHROUGH;
42
+ for (const key of defaultHeaders) {
43
+ if (req.headers[key] === undefined) {
44
+ continue;
45
+ }
46
+ const value = req.headers[key];
47
+ headers[key] = Array.isArray(value) ? value[value.length - 1] : value;
48
+ }
49
+ // If the header "x-custom-auth-header" is present, use its value as the custom header name.
50
+ if (req.headers["x-custom-auth-header"] !== undefined) {
51
+ const customHeaderName = req.headers["x-custom-auth-header"];
52
+ const lowerCaseHeaderName = customHeaderName.toLowerCase();
53
+ if (req.headers[lowerCaseHeaderName] !== undefined) {
54
+ const value = req.headers[lowerCaseHeaderName];
55
+ headers[customHeaderName] = value;
56
+ }
57
+ }
58
+ return headers;
59
+ };
31
60
  const app = express();
32
61
  app.use(cors());
33
62
  app.use((req, res, next) => {
34
63
  res.header("Access-Control-Expose-Headers", "mcp-session-id");
35
64
  next();
36
65
  });
37
- const webAppTransports = new Map(); // Transports by sessionId
66
+ const webAppTransports = new Map(); // Web app transports by web app sessionId
67
+ const serverTransports = new Map(); // Server Transports by web app sessionId
38
68
  const createTransport = async (req) => {
39
69
  const query = req.query;
40
70
  console.log("Query parameters:", query);
@@ -58,16 +88,7 @@ const createTransport = async (req) => {
58
88
  }
59
89
  else if (transportType === "sse") {
60
90
  const url = query.url;
61
- const headers = {
62
- Accept: "text/event-stream",
63
- };
64
- for (const key of SSE_HEADERS_PASSTHROUGH) {
65
- if (req.headers[key] === undefined) {
66
- continue;
67
- }
68
- const value = req.headers[key];
69
- headers[key] = Array.isArray(value) ? value[value.length - 1] : value;
70
- }
91
+ const headers = getHttpHeaders(req, transportType);
71
92
  console.log(`SSE transport: url=${url}, headers=${Object.keys(headers)}`);
72
93
  const transport = new SSEClientTransport(new URL(url), {
73
94
  eventSourceInit: {
@@ -82,16 +103,7 @@ const createTransport = async (req) => {
82
103
  return transport;
83
104
  }
84
105
  else if (transportType === "streamable-http") {
85
- const headers = {
86
- Accept: "text/event-stream, application/json",
87
- };
88
- for (const key of STREAMABLE_HTTP_HEADERS_PASSTHROUGH) {
89
- if (req.headers[key] === undefined) {
90
- continue;
91
- }
92
- const value = req.headers[key];
93
- headers[key] = Array.isArray(value) ? value[value.length - 1] : value;
94
- }
106
+ const headers = getHttpHeaders(req, transportType);
95
107
  const transport = new StreamableHTTPClientTransport(new URL(query.url), {
96
108
  requestInit: {
97
109
  headers,
@@ -106,7 +118,6 @@ const createTransport = async (req) => {
106
118
  throw new Error("Invalid transport type specified");
107
119
  }
108
120
  };
109
- let backingServerTransport;
110
121
  app.get("/mcp", async (req, res) => {
111
122
  const sessionId = req.headers["mcp-session-id"];
112
123
  console.log(`Received GET message for sessionId ${sessionId}`);
@@ -128,12 +139,12 @@ app.get("/mcp", async (req, res) => {
128
139
  app.post("/mcp", async (req, res) => {
129
140
  const sessionId = req.headers["mcp-session-id"];
130
141
  console.log(`Received POST message for sessionId ${sessionId}`);
142
+ let serverTransport;
131
143
  if (!sessionId) {
132
144
  try {
133
145
  console.log("New streamable-http connection");
134
146
  try {
135
- await backingServerTransport?.close();
136
- backingServerTransport = await createTransport(req);
147
+ serverTransport = await createTransport(req);
137
148
  }
138
149
  catch (error) {
139
150
  if (error instanceof SseError && error.code === 401) {
@@ -143,18 +154,19 @@ app.post("/mcp", async (req, res) => {
143
154
  }
144
155
  throw error;
145
156
  }
146
- console.log("Connected MCP client to backing server transport");
157
+ console.log("Connected MCP client to server transport");
147
158
  const webAppTransport = new StreamableHTTPServerTransport({
148
159
  sessionIdGenerator: randomUUID,
149
160
  onsessioninitialized: (sessionId) => {
150
161
  webAppTransports.set(sessionId, webAppTransport);
162
+ serverTransports.set(sessionId, serverTransport);
151
163
  console.log("Created streamable web app transport " + sessionId);
152
164
  },
153
165
  });
154
166
  await webAppTransport.start();
155
167
  mcpProxy({
156
168
  transportToClient: webAppTransport,
157
- transportToServer: backingServerTransport,
169
+ transportToServer: serverTransport,
158
170
  });
159
171
  await webAppTransport.handleRequest(req, res, req.body);
160
172
  }
@@ -179,12 +191,36 @@ app.post("/mcp", async (req, res) => {
179
191
  }
180
192
  }
181
193
  });
194
+ app.delete("/mcp", async (req, res) => {
195
+ const sessionId = req.headers["mcp-session-id"];
196
+ console.log(`Received DELETE message for sessionId ${sessionId}`);
197
+ let serverTransport;
198
+ if (sessionId) {
199
+ try {
200
+ serverTransport = serverTransports.get(sessionId);
201
+ if (!serverTransport) {
202
+ res.status(404).end("Transport not found for sessionId " + sessionId);
203
+ }
204
+ else {
205
+ await serverTransport.terminateSession();
206
+ webAppTransports.delete(sessionId);
207
+ serverTransports.delete(sessionId);
208
+ console.log(`Transports removed for sessionId ${sessionId}`);
209
+ }
210
+ res.status(200).end();
211
+ }
212
+ catch (error) {
213
+ console.error("Error in /mcp route:", error);
214
+ res.status(500).json(error);
215
+ }
216
+ }
217
+ });
182
218
  app.get("/stdio", async (req, res) => {
183
219
  try {
184
220
  console.log("New connection");
221
+ let serverTransport;
185
222
  try {
186
- await backingServerTransport?.close();
187
- backingServerTransport = await createTransport(req);
223
+ serverTransport = await createTransport(req);
188
224
  }
189
225
  catch (error) {
190
226
  if (error instanceof SseError && error.code === 401) {
@@ -197,9 +233,10 @@ app.get("/stdio", async (req, res) => {
197
233
  console.log("Connected MCP client to backing server transport");
198
234
  const webAppTransport = new SSEServerTransport("/message", res);
199
235
  webAppTransports.set(webAppTransport.sessionId, webAppTransport);
200
- console.log("Created web app transport");
236
+ serverTransports.set(webAppTransport.sessionId, serverTransport);
237
+ console.log("Created client/server transports");
201
238
  await webAppTransport.start();
202
- backingServerTransport.stderr.on("data", (chunk) => {
239
+ serverTransport.stderr.on("data", (chunk) => {
203
240
  webAppTransport.send({
204
241
  jsonrpc: "2.0",
205
242
  method: "notifications/stderr",
@@ -210,7 +247,7 @@ app.get("/stdio", async (req, res) => {
210
247
  });
211
248
  mcpProxy({
212
249
  transportToClient: webAppTransport,
213
- transportToServer: backingServerTransport,
250
+ transportToServer: serverTransport,
214
251
  });
215
252
  console.log("Set up MCP proxy");
216
253
  }
@@ -222,9 +259,9 @@ app.get("/stdio", async (req, res) => {
222
259
  app.get("/sse", async (req, res) => {
223
260
  try {
224
261
  console.log("New SSE connection. NOTE: The sse transport is deprecated and has been replaced by streamable-http");
262
+ let serverTransport;
225
263
  try {
226
- await backingServerTransport?.close();
227
- backingServerTransport = await createTransport(req);
264
+ serverTransport = await createTransport(req);
228
265
  }
229
266
  catch (error) {
230
267
  if (error instanceof SseError && error.code === 401) {
@@ -237,11 +274,13 @@ app.get("/sse", async (req, res) => {
237
274
  console.log("Connected MCP client to backing server transport");
238
275
  const webAppTransport = new SSEServerTransport("/message", res);
239
276
  webAppTransports.set(webAppTransport.sessionId, webAppTransport);
240
- console.log("Created web app transport");
277
+ console.log("Created client transport");
278
+ serverTransports.set(webAppTransport.sessionId, serverTransport);
279
+ console.log("Created server transport");
241
280
  await webAppTransport.start();
242
281
  mcpProxy({
243
282
  transportToClient: webAppTransport,
244
- transportToServer: backingServerTransport,
283
+ transportToServer: serverTransport,
245
284
  });
246
285
  console.log("Set up MCP proxy");
247
286
  }
@@ -1,3 +1,4 @@
1
+ import { isJSONRPCRequest } from "@modelcontextprotocol/sdk/types.js";
1
2
  function onClientError(error) {
2
3
  console.error("Error from inspector client:", error);
3
4
  }
@@ -8,7 +9,21 @@ export default function mcpProxy({ transportToClient, transportToServer, }) {
8
9
  let transportToClientClosed = false;
9
10
  let transportToServerClosed = false;
10
11
  transportToClient.onmessage = (message) => {
11
- transportToServer.send(message).catch(onServerError);
12
+ transportToServer.send(message).catch((error) => {
13
+ // Send error response back to client if it was a request (has id) and connection is still open
14
+ if (isJSONRPCRequest(message) && !transportToClientClosed) {
15
+ const errorResponse = {
16
+ jsonrpc: "2.0",
17
+ id: message.id,
18
+ error: {
19
+ code: -32001,
20
+ message: error.message,
21
+ data: error,
22
+ },
23
+ };
24
+ transportToClient.send(errorResponse).catch(onClientError);
25
+ }
26
+ });
12
27
  };
13
28
  transportToServer.onmessage = (message) => {
14
29
  transportToClient.send(message).catch(onClientError);