allow passing env vars to server from command line

This commit is contained in:
Ashwin Bhat
2024-12-13 16:34:42 +00:00
parent 5a5873277c
commit a976aefb39
4 changed files with 88 additions and 26 deletions

View File

@@ -14,10 +14,17 @@ To inspect an MCP server implementation, there's no need to clone this repo. Ins
npx @modelcontextprotocol/inspector build/index.js npx @modelcontextprotocol/inspector build/index.js
``` ```
You can also pass arguments along which will get passed as arguments to your MCP server: You can pass both arguments and environment variables to your MCP server. Arguments are passed directly to your server, while environment variables can be set using the `-e` flag:
``` ```bash
npx @modelcontextprotocol/inspector build/index.js arg1 arg2 ... # Pass arguments only
npx @modelcontextprotocol/inspector build/index.js arg1 arg2
# Pass environment variables only
npx @modelcontextprotocol/inspector -e KEY=value -e KEY2=value2 build/index.js
# Pass both environment variables and arguments
npx @modelcontextprotocol/inspector -e KEY=value -e KEY2=value2 build/index.js arg1 arg2
``` ```
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: 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:

View File

@@ -11,8 +11,24 @@ function delay(ms) {
} }
async function main() { async function main() {
// Get command line arguments // Parse command line arguments
const [, , command, ...mcpServerArgs] = process.argv; const args = process.argv.slice(2);
const envVars = {};
const mcpServerArgs = [];
let command = null;
for (let i = 0; i < args.length; i++) {
if (args[i] === "-e" && i + 1 < args.length) {
const [key, value] = args[++i].split("=");
if (key && value) {
envVars[key] = value;
}
} else if (!command) {
command = args[i];
} else {
mcpServerArgs.push(args[i]);
}
}
const inspectorServerPath = resolve( const inspectorServerPath = resolve(
__dirname, __dirname,
@@ -52,7 +68,11 @@ async function main() {
...(mcpServerArgs ? [`--args=${mcpServerArgs.join(" ")}`] : []), ...(mcpServerArgs ? [`--args=${mcpServerArgs.join(" ")}`] : []),
], ],
{ {
env: { ...process.env, PORT: SERVER_PORT }, env: {
...process.env,
PORT: SERVER_PORT,
MCP_ENV_VARS: JSON.stringify(envVars),
},
signal: abort.signal, signal: abort.signal,
echoOutput: true, echoOutput: true,
}, },

View File

@@ -6,6 +6,8 @@ import {
CircleHelp, CircleHelp,
Bug, Bug,
Github, Github,
Eye,
EyeOff,
} from "lucide-react"; } from "lucide-react";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
@@ -54,6 +56,7 @@ const Sidebar = ({
}: SidebarProps) => { }: SidebarProps) => {
const [theme, setTheme] = useTheme(); const [theme, setTheme] = useTheme();
const [showEnvVars, setShowEnvVars] = useState(false); const [showEnvVars, setShowEnvVars] = useState(false);
const [shownEnvVars, setShownEnvVars] = useState<Record<string, boolean>>({});
return ( return (
<div className="w-80 bg-card border-r border-border flex flex-col h-full"> <div className="w-80 bg-card border-r border-border flex flex-col h-full">
@@ -134,20 +137,40 @@ const Sidebar = ({
{showEnvVars && ( {showEnvVars && (
<div className="space-y-2"> <div className="space-y-2">
{Object.entries(env).map(([key, value], idx) => ( {Object.entries(env).map(([key, value], idx) => (
<div key={idx} className="grid grid-cols-[1fr,auto] gap-2"> <div key={idx} className="space-y-2 pb-4">
<div className="space-y-1"> <div className="flex gap-2">
<Input <Input
placeholder="Key" placeholder="Key"
value={key} value={key}
onChange={(e) => { onChange={(e) => {
const newKey = e.target.value;
const newEnv = { ...env }; const newEnv = { ...env };
delete newEnv[key]; delete newEnv[key];
newEnv[e.target.value] = value; newEnv[newKey] = value;
setEnv(newEnv); setEnv(newEnv);
setShownEnvVars((prev) => {
const { [key]: shown, ...rest } = prev;
return shown ? { ...rest, [newKey]: true } : rest;
});
}} }}
className="font-mono" className="font-mono"
/> />
<Button
variant="destructive"
size="icon"
className="h-9 w-9 p-0 shrink-0"
onClick={() => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { [key]: _removed, ...rest } = env;
setEnv(rest);
}}
>
×
</Button>
</div>
<div className="flex gap-2">
<Input <Input
type={shownEnvVars[key] ? "text" : "password"}
placeholder="Value" placeholder="Value"
value={value} value={value}
onChange={(e) => { onChange={(e) => {
@@ -157,25 +180,35 @@ const Sidebar = ({
}} }}
className="font-mono" className="font-mono"
/> />
<Button
variant="outline"
size="icon"
className="h-9 w-9 p-0 shrink-0"
onClick={() => {
setShownEnvVars((prev) => ({
...prev,
[key]: !prev[key],
}));
}}
>
{shownEnvVars[key] ? (
<Eye className="h-4 w-4" />
) : (
<EyeOff className="h-4 w-4" />
)}
</Button>
</div> </div>
<Button
variant="destructive"
onClick={() => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { [key]: removed, ...rest } = env;
setEnv(rest);
}}
>
Remove
</Button>
</div> </div>
))} ))}
<Button <Button
variant="outline" variant="outline"
className="w-full mt-2"
onClick={() => { onClick={() => {
const key = "";
const newEnv = { ...env }; const newEnv = { ...env };
newEnv[""] = ""; newEnv[key] = "";
setEnv(newEnv); setEnv(newEnv);
setShownEnvVars({});
}} }}
> >
Add Environment Variable Add Environment Variable

View File

@@ -15,6 +15,11 @@ import express from "express";
import mcpProxy from "./mcpProxy.js"; import mcpProxy from "./mcpProxy.js";
import { findActualExecutable } from "spawn-rx"; import { findActualExecutable } from "spawn-rx";
const defaultEnvironment = {
...getDefaultEnvironment(),
...(process.env.MCP_ENV_VARS ? JSON.parse(process.env.MCP_ENV_VARS) : {}),
};
// Polyfill EventSource for an SSE client in Node.js // Polyfill EventSource for an SSE client in Node.js
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
(global as any).EventSource = EventSource; (global as any).EventSource = EventSource;
@@ -40,13 +45,12 @@ const createTransport = async (query: express.Request["query"]) => {
if (transportType === "stdio") { if (transportType === "stdio") {
const command = query.command as string; const command = query.command as string;
const origArgs = shellParseArgs(query.args as string) as string[]; const origArgs = shellParseArgs(query.args as string) as string[];
const env = query.env ? JSON.parse(query.env as string) : undefined; const queryEnv = query.env ? JSON.parse(query.env as string) : {};
const env = { ...process.env, ...defaultEnvironment, ...queryEnv };
const { cmd, args } = findActualExecutable(command, origArgs); const { cmd, args } = findActualExecutable(command, origArgs);
console.log( console.log(`Stdio transport: command=${cmd}, args=${args}`);
`Stdio transport: command=${cmd}, args=${args}, env=${JSON.stringify(env)}`,
);
const transport = new StdioClientTransport({ const transport = new StdioClientTransport({
command: cmd, command: cmd,
@@ -136,8 +140,6 @@ app.post("/message", async (req, res) => {
app.get("/config", (req, res) => { app.get("/config", (req, res) => {
try { try {
const defaultEnvironment = getDefaultEnvironment();
res.json({ res.json({
defaultEnvironment, defaultEnvironment,
defaultCommand: values.env, defaultCommand: values.env,