Skip to content

Commit 77d4963

Browse files
committed
Add support for --header flags in MCP Inspector CLI
- Add --header option to both wrapper CLI (cli.ts) and main CLI (index.ts) - Support multiple headers with "HeaderName: Value" format validation - Pass headers to HTTP and SSE transports via requestInit.headers - Add parseHeaderPair function for proper header parsing - Fix output truncation issue by removing forced process.exit(0) - Update types to include headers in Args and TransportOptions Usage: --header "Authorization: Bearer token" --header "X-Custom: value"
1 parent 16edf53 commit 77d4963

File tree

3 files changed

+112
-9
lines changed

3 files changed

+112
-9
lines changed

cli/src/cli.ts

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ type Args = {
1616
cli: boolean;
1717
transport?: "stdio" | "sse" | "streamable-http";
1818
serverUrl?: string;
19+
headers?: Record<string, string>;
1920
};
2021

2122
type CliOptions = {
@@ -25,6 +26,7 @@ type CliOptions = {
2526
cli?: boolean;
2627
transport?: string;
2728
serverUrl?: string;
29+
header?: Record<string, string>;
2830
};
2931

3032
type ServerConfig =
@@ -127,6 +129,9 @@ async function runCli(args: Args): Promise<void> {
127129
// Build CLI arguments
128130
const cliArgs = [cliPath];
129131

132+
// Add target URL/command first
133+
cliArgs.push(args.command, ...args.args);
134+
130135
// Add transport flag if specified
131136
if (args.transport && args.transport !== "stdio") {
132137
// Convert streamable-http back to http for CLI mode
@@ -135,8 +140,12 @@ async function runCli(args: Args): Promise<void> {
135140
cliArgs.push("--transport", cliTransport);
136141
}
137142

138-
// Add command and remaining args
139-
cliArgs.push(args.command, ...args.args);
143+
// Add headers if specified
144+
if (args.headers) {
145+
for (const [key, value] of Object.entries(args.headers)) {
146+
cliArgs.push("--header", `${key}: ${value}`);
147+
}
148+
}
140149

141150
await spawnPromise("node", cliArgs, {
142151
env: { ...process.env, ...args.envArgs },
@@ -201,6 +210,30 @@ function parseKeyValuePair(
201210
return { ...previous, [key as string]: val };
202211
}
203212

213+
function parseHeaderPair(
214+
value: string,
215+
previous: Record<string, string> = {},
216+
): Record<string, string> {
217+
const colonIndex = value.indexOf(":");
218+
219+
if (colonIndex === -1) {
220+
throw new Error(
221+
`Invalid header format: ${value}. Use "HeaderName: Value" format.`,
222+
);
223+
}
224+
225+
const key = value.slice(0, colonIndex).trim();
226+
const val = value.slice(colonIndex + 1).trim();
227+
228+
if (key === "" || val === "") {
229+
throw new Error(
230+
`Invalid header format: ${value}. Use "HeaderName: Value" format.`,
231+
);
232+
}
233+
234+
return { ...previous, [key]: val };
235+
}
236+
204237
function parseArgs(): Args {
205238
const program = new Command();
206239

@@ -227,7 +260,13 @@ function parseArgs(): Args {
227260
.option("--server <n>", "server name from config file")
228261
.option("--cli", "enable CLI mode")
229262
.option("--transport <type>", "transport type (stdio, sse, http)")
230-
.option("--server-url <url>", "server URL for SSE/HTTP transport");
263+
.option("--server-url <url>", "server URL for SSE/HTTP transport")
264+
.option(
265+
"--header <headers...>",
266+
'HTTP headers as "HeaderName: Value" pairs (for HTTP/SSE transports)',
267+
parseHeaderPair,
268+
{},
269+
);
231270

232271
// Parse only the arguments before --
233272
program.parse(preArgs);
@@ -280,6 +319,7 @@ function parseArgs(): Args {
280319
envArgs: { ...(config.env || {}), ...(options.e || {}) },
281320
cli: options.cli || false,
282321
transport: "stdio",
322+
headers: options.header,
283323
};
284324
} else if (config.type === "sse" || config.type === "streamable-http") {
285325
return {
@@ -289,6 +329,7 @@ function parseArgs(): Args {
289329
cli: options.cli || false,
290330
transport: config.type,
291331
serverUrl: config.url,
332+
headers: options.header,
292333
};
293334
} else {
294335
// Backwards compatibility: if no type field, assume stdio
@@ -298,6 +339,7 @@ function parseArgs(): Args {
298339
envArgs: { ...((config as any).env || {}), ...(options.e || {}) },
299340
cli: options.cli || false,
300341
transport: "stdio",
342+
headers: options.header,
301343
};
302344
}
303345
}
@@ -319,6 +361,7 @@ function parseArgs(): Args {
319361
cli: options.cli || false,
320362
transport: transport as "stdio" | "sse" | "streamable-http" | undefined,
321363
serverUrl: options.serverUrl,
364+
headers: options.header,
322365
};
323366
}
324367

cli/src/index.ts

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,13 @@ type Args = {
3030
toolName?: string;
3131
toolArg?: Record<string, string>;
3232
transport?: "sse" | "stdio" | "http";
33+
headers?: Record<string, string>;
3334
};
3435

3536
function createTransportOptions(
3637
target: string[],
3738
transport?: "sse" | "stdio" | "http",
39+
headers?: Record<string, string>,
3840
): TransportOptions {
3941
if (target.length === 0) {
4042
throw new Error(
@@ -81,11 +83,16 @@ function createTransportOptions(
8183
command: isUrl ? undefined : command,
8284
args: isUrl ? undefined : commandArgs,
8385
url: isUrl ? command : undefined,
86+
headers,
8487
};
8588
}
8689

8790
async function callMethod(args: Args): Promise<void> {
88-
const transportOptions = createTransportOptions(args.target, args.transport);
91+
const transportOptions = createTransportOptions(
92+
args.target,
93+
args.transport,
94+
args.headers,
95+
);
8996
const transport = createTransport(transportOptions);
9097
const client = new Client({
9198
name: "inspector-cli",
@@ -177,6 +184,30 @@ function parseKeyValuePair(
177184
return { ...previous, [key as string]: val };
178185
}
179186

187+
function parseHeaderPair(
188+
value: string,
189+
previous: Record<string, string> = {},
190+
): Record<string, string> {
191+
const colonIndex = value.indexOf(":");
192+
193+
if (colonIndex === -1) {
194+
throw new Error(
195+
`Invalid header format: ${value}. Use "HeaderName: Value" format.`,
196+
);
197+
}
198+
199+
const key = value.slice(0, colonIndex).trim();
200+
const val = value.slice(colonIndex + 1).trim();
201+
202+
if (key === "" || val === "") {
203+
throw new Error(
204+
`Invalid header format: ${value}. Use "HeaderName: Value" format.`,
205+
);
206+
}
207+
208+
return { ...previous, [key]: val };
209+
}
210+
180211
function parseArgs(): Args {
181212
const program = new Command();
182213

@@ -256,12 +287,24 @@ function parseArgs(): Args {
256287
}
257288
return value as "sse" | "http" | "stdio";
258289
},
290+
)
291+
//
292+
// HTTP headers
293+
//
294+
.option(
295+
"--header <headers...>",
296+
'HTTP headers as "HeaderName: Value" pairs (for HTTP/SSE transports)',
297+
parseHeaderPair,
298+
{},
259299
);
260300

261301
// Parse only the arguments before --
262302
program.parse(preArgs);
263303

264-
const options = program.opts() as Omit<Args, "target">;
304+
const options = program.opts() as Omit<Args, "target"> & {
305+
header?: Record<string, string>;
306+
};
307+
265308
let remainingArgs = program.args;
266309

267310
// Add back any arguments that came after --
@@ -276,6 +319,7 @@ function parseArgs(): Args {
276319
return {
277320
target: finalArgs,
278321
...options,
322+
headers: options.header, // commander.js uses 'header' field, map to 'headers'
279323
};
280324
}
281325

@@ -287,8 +331,9 @@ async function main(): Promise<void> {
287331
try {
288332
const args = parseArgs();
289333
await callMethod(args);
290-
// Explicitly exit to ensure process terminates in CI
291-
process.exit(0);
334+
335+
// Let Node.js naturally exit instead of force-exiting
336+
// process.exit(0) was causing stdout truncation
292337
} catch (error) {
293338
handleError(error);
294339
}

cli/src/transport.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export type TransportOptions = {
1212
command?: string;
1313
args?: string[];
1414
url?: string;
15+
headers?: Record<string, string>;
1516
};
1617

1718
function createStdioTransport(options: TransportOptions): Transport {
@@ -64,11 +65,25 @@ export function createTransport(options: TransportOptions): Transport {
6465
const url = new URL(options.url);
6566

6667
if (transportType === "sse") {
67-
return new SSEClientTransport(url);
68+
const transportOptions = options.headers
69+
? {
70+
requestInit: {
71+
headers: options.headers,
72+
},
73+
}
74+
: undefined;
75+
return new SSEClientTransport(url, transportOptions);
6876
}
6977

7078
if (transportType === "http") {
71-
return new StreamableHTTPClientTransport(url);
79+
const transportOptions = options.headers
80+
? {
81+
requestInit: {
82+
headers: options.headers,
83+
},
84+
}
85+
: undefined;
86+
return new StreamableHTTPClientTransport(url, transportOptions);
7287
}
7388

7489
throw new Error(`Unsupported transport type: ${transportType}`);

0 commit comments

Comments
 (0)