@modelcontextprotocol/inspector 0.10.2 → 0.12.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.
@@ -5,7 +5,7 @@
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-CMyU_PZH.js"></script>
8
+ <script type="module" crossorigin src="/assets/index-CHwMp9hp.js"></script>
9
9
  <link rel="stylesheet" crossorigin href="/assets/index-D5WIP3OH.css">
10
10
  </head>
11
11
  <body>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@modelcontextprotocol/inspector",
3
- "version": "0.10.2",
3
+ "version": "0.12.0",
4
4
  "description": "Model Context Protocol inspector",
5
5
  "license": "MIT",
6
6
  "author": "Anthropic, PBC (https://anthropic.com)",
@@ -26,6 +26,7 @@
26
26
  "build-server": "cd server && npm run build",
27
27
  "build-client": "cd client && npm run build",
28
28
  "build-cli": "cd cli && npm run build",
29
+ "clean": "rimraf ./node_modules ./client/node_modules ./cli/node_modules ./build ./client/dist ./server/build ./cli/build ./package-lock.json && npm install",
29
30
  "dev": "concurrently \"cd client && npm run dev\" \"cd server && npm run dev\"",
30
31
  "dev:windows": "concurrently \"cd client && npm run dev\" \"cd server && npm run dev:windows\"",
31
32
  "start": "node client/bin/start.js",
@@ -35,13 +36,14 @@
35
36
  "test-cli": "cd cli && npm run test",
36
37
  "prettier-fix": "prettier --write .",
37
38
  "prettier-check": "prettier --check .",
39
+ "prepare": "npm run build",
38
40
  "publish-all": "npm publish --workspaces --access public && npm publish --access public"
39
41
  },
40
42
  "dependencies": {
41
- "@modelcontextprotocol/inspector-cli": "^0.10.2",
42
- "@modelcontextprotocol/inspector-client": "^0.10.2",
43
- "@modelcontextprotocol/inspector-server": "^0.10.2",
44
- "@modelcontextprotocol/sdk": "^1.10.0",
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",
45
47
  "concurrently": "^9.0.1",
46
48
  "shell-quote": "^1.8.2",
47
49
  "spawn-rx": "^5.1.2",
@@ -52,7 +54,9 @@
52
54
  "@types/jest": "^29.5.14",
53
55
  "@types/node": "^22.7.5",
54
56
  "@types/shell-quote": "^1.7.5",
57
+ "jest-fixed-jsdom": "^0.0.9",
55
58
  "prettier": "3.3.3",
59
+ "rimraf": "^6.0.1",
56
60
  "typescript": "^5.4.2"
57
61
  }
58
62
  }
@@ -4,11 +4,19 @@ import { parseArgs } from "node:util";
4
4
  import { parse as shellParseArgs } from "shell-quote";
5
5
  import { SSEClientTransport, SseError, } from "@modelcontextprotocol/sdk/client/sse.js";
6
6
  import { StdioClientTransport, getDefaultEnvironment, } from "@modelcontextprotocol/sdk/client/stdio.js";
7
+ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
8
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
7
9
  import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
8
10
  import express from "express";
9
11
  import { findActualExecutable } from "spawn-rx";
10
12
  import mcpProxy from "./mcpProxy.js";
13
+ import { randomUUID } from "node:crypto";
11
14
  const SSE_HEADERS_PASSTHROUGH = ["authorization"];
15
+ const STREAMABLE_HTTP_HEADERS_PASSTHROUGH = [
16
+ "authorization",
17
+ "mcp-session-id",
18
+ "last-event-id",
19
+ ];
12
20
  const defaultEnvironment = {
13
21
  ...getDefaultEnvironment(),
14
22
  ...(process.env.MCP_ENV_VARS ? JSON.parse(process.env.MCP_ENV_VARS) : {}),
@@ -22,7 +30,11 @@ const { values } = parseArgs({
22
30
  });
23
31
  const app = express();
24
32
  app.use(cors());
25
- let webAppTransports = [];
33
+ app.use((req, res, next) => {
34
+ res.header("Access-Control-Expose-Headers", "mcp-session-id");
35
+ next();
36
+ });
37
+ const webAppTransports = new Map(); // Transports by sessionId
26
38
  const createTransport = async (req) => {
27
39
  const query = req.query;
28
40
  console.log("Query parameters:", query);
@@ -69,15 +81,107 @@ const createTransport = async (req) => {
69
81
  console.log("Connected to SSE transport");
70
82
  return transport;
71
83
  }
84
+ 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
+ }
95
+ const transport = new StreamableHTTPClientTransport(new URL(query.url), {
96
+ requestInit: {
97
+ headers,
98
+ },
99
+ });
100
+ await transport.start();
101
+ console.log("Connected to Streamable HTTP transport");
102
+ return transport;
103
+ }
72
104
  else {
73
105
  console.error(`Invalid transport type: ${transportType}`);
74
106
  throw new Error("Invalid transport type specified");
75
107
  }
76
108
  };
77
109
  let backingServerTransport;
78
- app.get("/sse", async (req, res) => {
110
+ app.get("/mcp", async (req, res) => {
111
+ const sessionId = req.headers["mcp-session-id"];
112
+ console.log(`Received GET message for sessionId ${sessionId}`);
79
113
  try {
80
- console.log("New SSE connection");
114
+ const transport = webAppTransports.get(sessionId);
115
+ if (!transport) {
116
+ res.status(404).end("Session not found");
117
+ return;
118
+ }
119
+ else {
120
+ await transport.handleRequest(req, res);
121
+ }
122
+ }
123
+ catch (error) {
124
+ console.error("Error in /mcp route:", error);
125
+ res.status(500).json(error);
126
+ }
127
+ });
128
+ app.post("/mcp", async (req, res) => {
129
+ const sessionId = req.headers["mcp-session-id"];
130
+ console.log(`Received POST message for sessionId ${sessionId}`);
131
+ if (!sessionId) {
132
+ try {
133
+ console.log("New streamable-http connection");
134
+ try {
135
+ await backingServerTransport?.close();
136
+ backingServerTransport = await createTransport(req);
137
+ }
138
+ catch (error) {
139
+ if (error instanceof SseError && error.code === 401) {
140
+ console.error("Received 401 Unauthorized from MCP server:", error.message);
141
+ res.status(401).json(error);
142
+ return;
143
+ }
144
+ throw error;
145
+ }
146
+ console.log("Connected MCP client to backing server transport");
147
+ const webAppTransport = new StreamableHTTPServerTransport({
148
+ sessionIdGenerator: randomUUID,
149
+ onsessioninitialized: (sessionId) => {
150
+ webAppTransports.set(sessionId, webAppTransport);
151
+ console.log("Created streamable web app transport " + sessionId);
152
+ },
153
+ });
154
+ await webAppTransport.start();
155
+ mcpProxy({
156
+ transportToClient: webAppTransport,
157
+ transportToServer: backingServerTransport,
158
+ });
159
+ await webAppTransport.handleRequest(req, res, req.body);
160
+ }
161
+ catch (error) {
162
+ console.error("Error in /mcp POST route:", error);
163
+ res.status(500).json(error);
164
+ }
165
+ }
166
+ else {
167
+ try {
168
+ const transport = webAppTransports.get(sessionId);
169
+ if (!transport) {
170
+ res.status(404).end("Transport not found for sessionId " + sessionId);
171
+ }
172
+ else {
173
+ await transport.handleRequest(req, res);
174
+ }
175
+ }
176
+ catch (error) {
177
+ console.error("Error in /mcp route:", error);
178
+ res.status(500).json(error);
179
+ }
180
+ }
181
+ });
182
+ app.get("/stdio", async (req, res) => {
183
+ try {
184
+ console.log("New connection");
81
185
  try {
82
186
  await backingServerTransport?.close();
83
187
  backingServerTransport = await createTransport(req);
@@ -92,21 +196,49 @@ app.get("/sse", async (req, res) => {
92
196
  }
93
197
  console.log("Connected MCP client to backing server transport");
94
198
  const webAppTransport = new SSEServerTransport("/message", res);
95
- console.log("Created web app transport");
96
- webAppTransports.push(webAppTransport);
199
+ webAppTransports.set(webAppTransport.sessionId, webAppTransport);
97
200
  console.log("Created web app transport");
98
201
  await webAppTransport.start();
99
- if (backingServerTransport instanceof StdioClientTransport) {
100
- backingServerTransport.stderr.on("data", (chunk) => {
101
- webAppTransport.send({
102
- jsonrpc: "2.0",
103
- method: "notifications/stderr",
104
- params: {
105
- content: chunk.toString(),
106
- },
107
- });
202
+ backingServerTransport.stderr.on("data", (chunk) => {
203
+ webAppTransport.send({
204
+ jsonrpc: "2.0",
205
+ method: "notifications/stderr",
206
+ params: {
207
+ content: chunk.toString(),
208
+ },
108
209
  });
210
+ });
211
+ mcpProxy({
212
+ transportToClient: webAppTransport,
213
+ transportToServer: backingServerTransport,
214
+ });
215
+ console.log("Set up MCP proxy");
216
+ }
217
+ catch (error) {
218
+ console.error("Error in /stdio route:", error);
219
+ res.status(500).json(error);
220
+ }
221
+ });
222
+ app.get("/sse", async (req, res) => {
223
+ try {
224
+ console.log("New SSE connection. NOTE: The sse transport is deprecated and has been replaced by streamable-http");
225
+ try {
226
+ await backingServerTransport?.close();
227
+ backingServerTransport = await createTransport(req);
228
+ }
229
+ catch (error) {
230
+ if (error instanceof SseError && error.code === 401) {
231
+ console.error("Received 401 Unauthorized from MCP server:", error.message);
232
+ res.status(401).json(error);
233
+ return;
234
+ }
235
+ throw error;
109
236
  }
237
+ console.log("Connected MCP client to backing server transport");
238
+ const webAppTransport = new SSEServerTransport("/message", res);
239
+ webAppTransports.set(webAppTransport.sessionId, webAppTransport);
240
+ console.log("Created web app transport");
241
+ await webAppTransport.start();
110
242
  mcpProxy({
111
243
  transportToClient: webAppTransport,
112
244
  transportToServer: backingServerTransport,
@@ -122,7 +254,7 @@ app.post("/message", async (req, res) => {
122
254
  try {
123
255
  const sessionId = req.query.sessionId;
124
256
  console.log(`Received message for sessionId ${sessionId}`);
125
- const transport = webAppTransports.find((t) => t.sessionId === sessionId);
257
+ const transport = webAppTransports.get(sessionId);
126
258
  if (!transport) {
127
259
  res.status(404).end("Session not found");
128
260
  return;