diff --git a/README.md b/README.md index b02f1b110..e4ba55d15 100644 --- a/README.md +++ b/README.md @@ -13,13 +13,28 @@ To inspect an MCP server implementation, there's no need to clone this repo. Ins ```bash npx @modelcontextprotocol/inspector build/index.js ``` - You can also pass arguments along which will get passed as arguments to your MCP server: -``` +```bash npx @modelcontextprotocol/inspector build/index.js arg1 arg2 ... ``` +Environment variables can be passed to your MCP server using either the long form `--envVars` or short form `-e` flags: + +```bash +# Long form +npx @modelcontextprotocol/inspector build/index.js --envVars API_KEY=abc123 --envVars DEBUG=true + +# Short form +npx @modelcontextprotocol/inspector build/index.js -e API_KEY=abc123 -e DEBUG=true +``` + +Environment variables are merged with the following precedence: +- Base: process.env (system environment) +- Override: Command line envVars (using either --envVars or -e) +- Final Override: Query parameters +``` + The inspector runs both a client UI (default port 5173) and an MCP proxy server (default port 3000). Open the client UI in your browser to use the inspector. You can customize the ports if needed: ```bash @@ -45,6 +60,27 @@ npm run build npm start ``` +## Testing + +The inspector includes a comprehensive test suite. To run the tests: + +```bash +cd server +npm test +``` + +### Test Coverage + +The test suite includes: +- Environment variable handling + - Single variable with long flag (--envVars) + - Single variable with short flag (-e) + - Multiple environment variables + - Empty environment variables list + - Environment variable object merging + +To add new tests, place them in the `server/src/__tests__` directory. + ## License This project is licensed under the MIT License—see the [LICENSE](LICENSE) file for details. diff --git a/package-lock.json b/package-lock.json index 13f4248b9..34d121ea1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2312,9 +2312,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.10.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.0.tgz", - "integrity": "sha512-XC70cRZVElFHfIUB40FgZOBbgJYFKKMa5nb9lxcwYstFG/Mi+/Y0bGS+rs6Dmhmkpq4pnNiLiuZAbc02YCOnmA==", + "version": "22.10.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.5.tgz", + "integrity": "sha512-F8Q+SeGimwOo86fiovQh8qiXfFEh2/ocYv7tU5pJ3EXMSSxk1Joj5wefpFK2fHTf/N6HKGSxIDBT9f3gCxXPkQ==", "license": "MIT", "dependencies": { "undici-types": "~6.20.0" @@ -5748,9 +5748,9 @@ } }, "node_modules/spawn-rx": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/spawn-rx/-/spawn-rx-5.1.0.tgz", - "integrity": "sha512-b4HX44hI0usMiHu6LNaZUVg0BGqHuBcl+81iEhZwhvKHz1efTqD/CHBcUbm/uIe5TARy9pJolxU2NMfh6GuQBA==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/spawn-rx/-/spawn-rx-5.1.1.tgz", + "integrity": "sha512-1JdzBG9PKsvu364u+vZWvrj5VPN8hjTzevWvTJ+MdZFIxYkz6QLlhWtqtngn9IxBotyQ0/DfWKhQTQjWq9RQ0g==", "license": "MIT", "dependencies": { "debug": "^4.3.7", @@ -6954,7 +6954,10 @@ "@types/cors": "^2.8.17", "@types/eventsource": "^1.1.15", "@types/express": "^4.17.21", + "@types/node": "^22.10.5", + "@types/shell-quote": "^1.7.5", "@types/ws": "^8.5.12", + "spawn-rx": "^5.1.1", "tsx": "^4.19.0", "typescript": "^5.6.2" } diff --git a/server/package.json b/server/package.json index c79e7d2e0..c3c00e747 100644 --- a/server/package.json +++ b/server/package.json @@ -22,7 +22,10 @@ "@types/cors": "^2.8.17", "@types/eventsource": "^1.1.15", "@types/express": "^4.17.21", + "@types/node": "^22.10.5", + "@types/shell-quote": "^1.7.5", "@types/ws": "^8.5.12", + "spawn-rx": "^5.1.1", "tsx": "^4.19.0", "typescript": "^5.6.2" }, diff --git a/server/src/index.ts b/server/src/index.ts index b82b17d51..8ea2c4926 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -11,6 +11,7 @@ import { getDefaultEnvironment, } from "@modelcontextprotocol/sdk/client/stdio.js"; import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; +import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js"; import express from "express"; import mcpProxy from "./mcpProxy.js"; import { findActualExecutable } from "spawn-rx"; @@ -24,15 +25,39 @@ const { values } = parseArgs({ options: { env: { type: "string", default: "" }, args: { type: "string", default: "" }, + envVars: { + type: "string", + multiple: true, + default: [], + short: "e" + }, }, }); +// Parse environment variables from command line and set them in process.env +const envFromArgs = values.envVars.reduce((acc: Record, curr: string) => { + const [key, value] = curr.split('='); + if (key && value) { + acc[key] = value; + // Set in process.env so the main process has access + process.env[key] = value; + } + return acc; +}, {}); + const app = express(); app.use(cors()); let webAppTransports: SSEServerTransport[] = []; -const createTransport = async (query: express.Request["query"]) => { +/** + * Creates a transport based on query parameters and command line arguments. + * Environment variables can be provided in three ways: + * 1. Through process.env (base environment) + * 2. Through command line using --envVars KEY=VALUE (multiple allowed) + * 3. Through query parameters (takes precedence) + */ +const createTransport = async (query: express.Request["query"]): Promise => { console.log("Query parameters:", query); const transportType = query.transportType as string; @@ -40,18 +65,24 @@ const createTransport = async (query: express.Request["query"]) => { if (transportType === "stdio") { const command = query.command as string; const origArgs = shellParseArgs(query.args as string) as string[]; - const env = query.env ? JSON.parse(query.env as string) : undefined; + // Merge environment variables from query params and command line + const queryEnv = query.env ? JSON.parse(query.env as string) : {}; + const mergedEnv = { + ...process.env, // Include current process env + ...envFromArgs, // Add our command line env vars + ...queryEnv, // Query params take precedence + }; const { cmd, args } = findActualExecutable(command, origArgs); console.log( - `Stdio transport: command=${cmd}, args=${args}, env=${JSON.stringify(env)}`, + `Stdio transport: command=${cmd}, args=${args}, env=${JSON.stringify(mergedEnv)}`, ); const transport = new StdioClientTransport({ command: cmd, args, - env, + env: mergedEnv, stderr: "pipe", });