Skip to content

Commit 936345b

Browse files
committed
allow passing env vars to server from command line
1 parent 1797fbf commit 936345b

File tree

3 files changed

+105
-28
lines changed

3 files changed

+105
-28
lines changed

bin/cli.js

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,24 @@ function delay(ms) {
1111
}
1212

1313
async function main() {
14-
// Get command line arguments
15-
const [, , command, ...mcpServerArgs] = process.argv;
14+
// Parse command line arguments
15+
const args = process.argv.slice(2);
16+
const envVars = {};
17+
const mcpServerArgs = [];
18+
let command = null;
19+
20+
for (let i = 0; i < args.length; i++) {
21+
if (args[i] === "-e" && i + 1 < args.length) {
22+
const [key, value] = args[++i].split("=");
23+
if (key && value) {
24+
envVars[key] = value;
25+
}
26+
} else if (!command) {
27+
command = args[i];
28+
} else {
29+
mcpServerArgs.push(args[i]);
30+
}
31+
}
1632

1733
const inspectorServerPath = resolve(
1834
__dirname,
@@ -52,7 +68,11 @@ async function main() {
5268
...(mcpServerArgs ? [`--args=${mcpServerArgs.join(" ")}`] : []),
5369
],
5470
{
55-
env: { ...process.env, PORT: SERVER_PORT },
71+
env: {
72+
...process.env,
73+
PORT: SERVER_PORT,
74+
MCP_ENV_VARS: JSON.stringify(envVars),
75+
},
5676
signal: abort.signal,
5777
echoOutput: true,
5878
},

client/src/components/Sidebar.tsx

Lines changed: 74 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
import { useState } from "react";
2-
import { Play, ChevronDown, ChevronRight, CircleHelp, Bug, Github } from "lucide-react";
2+
import {
3+
Play,
4+
ChevronDown,
5+
ChevronRight,
6+
Bug,
7+
CircleHelp,
8+
Github,
9+
Eye,
10+
EyeOff,
11+
} from "lucide-react";
312
import { Button } from "@/components/ui/button";
413
import { Input } from "@/components/ui/input";
514
import {
@@ -47,6 +56,7 @@ const Sidebar = ({
4756
}: SidebarProps) => {
4857
const [theme, setTheme] = useTheme();
4958
const [showEnvVars, setShowEnvVars] = useState(false);
59+
const [shownEnvVars, setShownEnvVars] = useState<Record<string, boolean>>({});
5060

5161
return (
5262
<div className="w-80 bg-card border-r border-border flex flex-col h-full">
@@ -127,20 +137,40 @@ const Sidebar = ({
127137
{showEnvVars && (
128138
<div className="space-y-2">
129139
{Object.entries(env).map(([key, value], idx) => (
130-
<div key={idx} className="grid grid-cols-[1fr,auto] gap-2">
131-
<div className="space-y-1">
140+
<div key={idx} className="space-y-2 pb-4">
141+
<div className="flex gap-2">
132142
<Input
133143
placeholder="Key"
134144
value={key}
135145
onChange={(e) => {
146+
const newKey = e.target.value;
136147
const newEnv = { ...env };
137148
delete newEnv[key];
138-
newEnv[e.target.value] = value;
149+
newEnv[newKey] = value;
139150
setEnv(newEnv);
151+
setShownEnvVars((prev) => {
152+
const { [key]: shown, ...rest } = prev;
153+
return shown ? { ...rest, [newKey]: true } : rest;
154+
});
140155
}}
141156
className="font-mono"
142157
/>
158+
<Button
159+
variant="destructive"
160+
size="icon"
161+
className="h-9 w-9 p-0 shrink-0"
162+
onClick={() => {
163+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
164+
const { [key]: _removed, ...rest } = env;
165+
setEnv(rest);
166+
}}
167+
>
168+
×
169+
</Button>
170+
</div>
171+
<div className="flex gap-2">
143172
<Input
173+
type={shownEnvVars[key] ? "text" : "password"}
144174
placeholder="Value"
145175
value={value}
146176
onChange={(e) => {
@@ -150,25 +180,35 @@ const Sidebar = ({
150180
}}
151181
className="font-mono"
152182
/>
183+
<Button
184+
variant="outline"
185+
size="icon"
186+
className="h-9 w-9 p-0 shrink-0"
187+
onClick={() => {
188+
setShownEnvVars((prev) => ({
189+
...prev,
190+
[key]: !prev[key],
191+
}));
192+
}}
193+
>
194+
{shownEnvVars[key] ? (
195+
<Eye className="h-4 w-4" />
196+
) : (
197+
<EyeOff className="h-4 w-4" />
198+
)}
199+
</Button>
153200
</div>
154-
<Button
155-
variant="destructive"
156-
onClick={() => {
157-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
158-
const { [key]: removed, ...rest } = env;
159-
setEnv(rest);
160-
}}
161-
>
162-
Remove
163-
</Button>
164201
</div>
165202
))}
166203
<Button
167204
variant="outline"
205+
className="w-full mt-2"
168206
onClick={() => {
207+
const key = "";
169208
const newEnv = { ...env };
170-
newEnv[""] = "";
209+
newEnv[key] = "";
171210
setEnv(newEnv);
211+
setShownEnvVars({});
172212
}}
173213
>
174214
Add Environment Variable
@@ -243,18 +283,33 @@ const Sidebar = ({
243283
</Select>
244284

245285
<div className="flex items-center space-x-2">
246-
<a href="https://modelcontextprotocol.io/docs/tools/inspector" target="_blank" rel="noopener noreferrer">
286+
<a
287+
href="https://modelcontextprotocol.io/docs/tools/inspector"
288+
target="_blank"
289+
rel="noopener noreferrer"
290+
>
247291
<Button variant="ghost" title="Inspector Documentation">
248292
<CircleHelp className="w-4 h-4 text-gray-800" />
249293
</Button>
250294
</a>
251-
<a href="https://modelcontextprotocol.io/docs/tools/debugging" target="_blank" rel="noopener noreferrer">
295+
<a
296+
href="https://modelcontextprotocol.io/docs/tools/debugging"
297+
target="_blank"
298+
rel="noopener noreferrer"
299+
>
252300
<Button variant="ghost" title="Debugging Guide">
253301
<Bug className="w-4 h-4 text-gray-800" />
254302
</Button>
255303
</a>
256-
<a href="https://github.com/modelcontextprotocol/inspector" target="_blank" rel="noopener noreferrer">
257-
<Button variant="ghost" title="Report bugs or contribute on GitHub">
304+
<a
305+
href="https://github.com/modelcontextprotocol/inspector"
306+
target="_blank"
307+
rel="noopener noreferrer"
308+
>
309+
<Button
310+
variant="ghost"
311+
title="Report bugs or contribute on GitHub"
312+
>
258313
<Github className="w-4 h-4 text-gray-800" />
259314
</Button>
260315
</a>

server/src/index.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ import express from "express";
1515
import mcpProxy from "./mcpProxy.js";
1616
import { findActualExecutable } from "spawn-rx";
1717

18+
const defaultEnvironment = {
19+
...getDefaultEnvironment(),
20+
...(process.env.MCP_ENV_VARS ? JSON.parse(process.env.MCP_ENV_VARS) : {}),
21+
};
22+
1823
// Polyfill EventSource for an SSE client in Node.js
1924
// eslint-disable-next-line @typescript-eslint/no-explicit-any
2025
(global as any).EventSource = EventSource;
@@ -40,13 +45,12 @@ const createTransport = async (query: express.Request["query"]) => {
4045
if (transportType === "stdio") {
4146
const command = query.command as string;
4247
const origArgs = shellParseArgs(query.args as string) as string[];
43-
const env = query.env ? JSON.parse(query.env as string) : undefined;
48+
const queryEnv = query.env ? JSON.parse(query.env as string) : {};
49+
const env = { ...process.env, ...defaultEnvironment, ...queryEnv };
4450

4551
const { cmd, args } = findActualExecutable(command, origArgs);
4652

47-
console.log(
48-
`Stdio transport: command=${cmd}, args=${args}, env=${JSON.stringify(env)}`,
49-
);
53+
console.log(`Stdio transport: command=${cmd}, args=${args}`);
5054

5155
const transport = new StdioClientTransport({
5256
command: cmd,
@@ -136,8 +140,6 @@ app.post("/message", async (req, res) => {
136140

137141
app.get("/config", (req, res) => {
138142
try {
139-
const defaultEnvironment = getDefaultEnvironment();
140-
141143
res.json({
142144
defaultEnvironment,
143145
defaultCommand: values.env,

0 commit comments

Comments
 (0)