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
```
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:
```
npx @modelcontextprotocol/inspector build/index.js arg1 arg2 ...
```bash
# 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:

View File

@@ -11,8 +11,24 @@ function delay(ms) {
}
async function main() {
// Get command line arguments
const [, , command, ...mcpServerArgs] = process.argv;
// Parse command line arguments
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(
__dirname,
@@ -52,7 +68,11 @@ async function main() {
...(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,
echoOutput: true,
},

View File

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

View File

@@ -15,6 +15,11 @@ import express from "express";
import mcpProxy from "./mcpProxy.js";
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
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(global as any).EventSource = EventSource;
@@ -40,13 +45,12 @@ 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;
const queryEnv = query.env ? JSON.parse(query.env as string) : {};
const env = { ...process.env, ...defaultEnvironment, ...queryEnv };
const { cmd, args } = findActualExecutable(command, origArgs);
console.log(
`Stdio transport: command=${cmd}, args=${args}, env=${JSON.stringify(env)}`,
);
console.log(`Stdio transport: command=${cmd}, args=${args}`);
const transport = new StdioClientTransport({
command: cmd,
@@ -136,8 +140,6 @@ app.post("/message", async (req, res) => {
app.get("/config", (req, res) => {
try {
const defaultEnvironment = getDefaultEnvironment();
res.json({
defaultEnvironment,
defaultCommand: values.env,