Merge branch 'main' into tool-search
This commit is contained in:
commit
b59f9ce4bc
6 changed files with 117 additions and 9 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -15,4 +15,5 @@ sdk
|
|||
client/playwright-report/
|
||||
client/results.json
|
||||
client/test-results/
|
||||
client/e2e/test-results/
|
||||
mcp.json
|
||||
|
|
|
|||
|
|
@ -3,3 +3,4 @@ server/build
|
|||
CODE_OF_CONDUCT.md
|
||||
SECURITY.md
|
||||
mcp.json
|
||||
.claude/settings.local.json
|
||||
|
|
@ -410,6 +410,9 @@ npx @modelcontextprotocol/inspector --cli https://my-mcp-server.example.com
|
|||
# Connect to a remote MCP server (with Streamable HTTP transport)
|
||||
npx @modelcontextprotocol/inspector --cli https://my-mcp-server.example.com --transport http --method tools/list
|
||||
|
||||
# Connect to a remote MCP server (with custom headers)
|
||||
npx @modelcontextprotocol/inspector --cli https://my-mcp-server.example.com --transport http --method tools/list --header "X-API-Key: your-api-key"
|
||||
|
||||
# Call a tool on a remote server
|
||||
npx @modelcontextprotocol/inspector --cli https://my-mcp-server.example.com --method tools/call --tool-name remotetool --tool-arg param=value
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ type Args = {
|
|||
cli: boolean;
|
||||
transport?: "stdio" | "sse" | "streamable-http";
|
||||
serverUrl?: string;
|
||||
headers?: Record<string, string>;
|
||||
};
|
||||
|
||||
type CliOptions = {
|
||||
|
|
@ -25,6 +26,7 @@ type CliOptions = {
|
|||
cli?: boolean;
|
||||
transport?: string;
|
||||
serverUrl?: string;
|
||||
header?: Record<string, string>;
|
||||
};
|
||||
|
||||
type ServerConfig =
|
||||
|
|
@ -127,6 +129,9 @@ async function runCli(args: Args): Promise<void> {
|
|||
// Build CLI arguments
|
||||
const cliArgs = [cliPath];
|
||||
|
||||
// Add target URL/command first
|
||||
cliArgs.push(args.command, ...args.args);
|
||||
|
||||
// Add transport flag if specified
|
||||
if (args.transport && args.transport !== "stdio") {
|
||||
// Convert streamable-http back to http for CLI mode
|
||||
|
|
@ -135,8 +140,12 @@ async function runCli(args: Args): Promise<void> {
|
|||
cliArgs.push("--transport", cliTransport);
|
||||
}
|
||||
|
||||
// Add command and remaining args
|
||||
cliArgs.push(args.command, ...args.args);
|
||||
// Add headers if specified
|
||||
if (args.headers) {
|
||||
for (const [key, value] of Object.entries(args.headers)) {
|
||||
cliArgs.push("--header", `${key}: ${value}`);
|
||||
}
|
||||
}
|
||||
|
||||
await spawnPromise("node", cliArgs, {
|
||||
env: { ...process.env, ...args.envArgs },
|
||||
|
|
@ -201,6 +210,30 @@ function parseKeyValuePair(
|
|||
return { ...previous, [key as string]: val };
|
||||
}
|
||||
|
||||
function parseHeaderPair(
|
||||
value: string,
|
||||
previous: Record<string, string> = {},
|
||||
): Record<string, string> {
|
||||
const colonIndex = value.indexOf(":");
|
||||
|
||||
if (colonIndex === -1) {
|
||||
throw new Error(
|
||||
`Invalid header format: ${value}. Use "HeaderName: Value" format.`,
|
||||
);
|
||||
}
|
||||
|
||||
const key = value.slice(0, colonIndex).trim();
|
||||
const val = value.slice(colonIndex + 1).trim();
|
||||
|
||||
if (key === "" || val === "") {
|
||||
throw new Error(
|
||||
`Invalid header format: ${value}. Use "HeaderName: Value" format.`,
|
||||
);
|
||||
}
|
||||
|
||||
return { ...previous, [key]: val };
|
||||
}
|
||||
|
||||
function parseArgs(): Args {
|
||||
const program = new Command();
|
||||
|
||||
|
|
@ -227,7 +260,13 @@ function parseArgs(): Args {
|
|||
.option("--server <n>", "server name from config file")
|
||||
.option("--cli", "enable CLI mode")
|
||||
.option("--transport <type>", "transport type (stdio, sse, http)")
|
||||
.option("--server-url <url>", "server URL for SSE/HTTP transport");
|
||||
.option("--server-url <url>", "server URL for SSE/HTTP transport")
|
||||
.option(
|
||||
"--header <headers...>",
|
||||
'HTTP headers as "HeaderName: Value" pairs (for HTTP/SSE transports)',
|
||||
parseHeaderPair,
|
||||
{},
|
||||
);
|
||||
|
||||
// Parse only the arguments before --
|
||||
program.parse(preArgs);
|
||||
|
|
@ -280,6 +319,7 @@ function parseArgs(): Args {
|
|||
envArgs: { ...(config.env || {}), ...(options.e || {}) },
|
||||
cli: options.cli || false,
|
||||
transport: "stdio",
|
||||
headers: options.header,
|
||||
};
|
||||
} else if (config.type === "sse" || config.type === "streamable-http") {
|
||||
return {
|
||||
|
|
@ -289,6 +329,7 @@ function parseArgs(): Args {
|
|||
cli: options.cli || false,
|
||||
transport: config.type,
|
||||
serverUrl: config.url,
|
||||
headers: options.header,
|
||||
};
|
||||
} else {
|
||||
// Backwards compatibility: if no type field, assume stdio
|
||||
|
|
@ -298,6 +339,7 @@ function parseArgs(): Args {
|
|||
envArgs: { ...((config as any).env || {}), ...(options.e || {}) },
|
||||
cli: options.cli || false,
|
||||
transport: "stdio",
|
||||
headers: options.header,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -319,6 +361,7 @@ function parseArgs(): Args {
|
|||
cli: options.cli || false,
|
||||
transport: transport as "stdio" | "sse" | "streamable-http" | undefined,
|
||||
serverUrl: options.serverUrl,
|
||||
headers: options.header,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -40,11 +40,13 @@ type Args = {
|
|||
toolName?: string;
|
||||
toolArg?: Record<string, JsonValue>;
|
||||
transport?: "sse" | "stdio" | "http";
|
||||
headers?: Record<string, string>;
|
||||
};
|
||||
|
||||
function createTransportOptions(
|
||||
target: string[],
|
||||
transport?: "sse" | "stdio" | "http",
|
||||
headers?: Record<string, string>,
|
||||
): TransportOptions {
|
||||
if (target.length === 0) {
|
||||
throw new Error(
|
||||
|
|
@ -91,11 +93,16 @@ function createTransportOptions(
|
|||
command: isUrl ? undefined : command,
|
||||
args: isUrl ? undefined : commandArgs,
|
||||
url: isUrl ? command : undefined,
|
||||
headers,
|
||||
};
|
||||
}
|
||||
|
||||
async function callMethod(args: Args): Promise<void> {
|
||||
const transportOptions = createTransportOptions(args.target, args.transport);
|
||||
const transportOptions = createTransportOptions(
|
||||
args.target,
|
||||
args.transport,
|
||||
args.headers,
|
||||
);
|
||||
const transport = createTransport(transportOptions);
|
||||
const client = new Client({
|
||||
name: "inspector-cli",
|
||||
|
|
@ -196,6 +203,30 @@ function parseKeyValuePair(
|
|||
return { ...previous, [key as string]: parsedValue };
|
||||
}
|
||||
|
||||
function parseHeaderPair(
|
||||
value: string,
|
||||
previous: Record<string, string> = {},
|
||||
): Record<string, string> {
|
||||
const colonIndex = value.indexOf(":");
|
||||
|
||||
if (colonIndex === -1) {
|
||||
throw new Error(
|
||||
`Invalid header format: ${value}. Use "HeaderName: Value" format.`,
|
||||
);
|
||||
}
|
||||
|
||||
const key = value.slice(0, colonIndex).trim();
|
||||
const val = value.slice(colonIndex + 1).trim();
|
||||
|
||||
if (key === "" || val === "") {
|
||||
throw new Error(
|
||||
`Invalid header format: ${value}. Use "HeaderName: Value" format.`,
|
||||
);
|
||||
}
|
||||
|
||||
return { ...previous, [key]: val };
|
||||
}
|
||||
|
||||
function parseArgs(): Args {
|
||||
const program = new Command();
|
||||
|
||||
|
|
@ -275,12 +306,24 @@ function parseArgs(): Args {
|
|||
}
|
||||
return value as "sse" | "http" | "stdio";
|
||||
},
|
||||
)
|
||||
//
|
||||
// HTTP headers
|
||||
//
|
||||
.option(
|
||||
"--header <headers...>",
|
||||
'HTTP headers as "HeaderName: Value" pairs (for HTTP/SSE transports)',
|
||||
parseHeaderPair,
|
||||
{},
|
||||
);
|
||||
|
||||
// Parse only the arguments before --
|
||||
program.parse(preArgs);
|
||||
|
||||
const options = program.opts() as Omit<Args, "target">;
|
||||
const options = program.opts() as Omit<Args, "target"> & {
|
||||
header?: Record<string, string>;
|
||||
};
|
||||
|
||||
let remainingArgs = program.args;
|
||||
|
||||
// Add back any arguments that came after --
|
||||
|
|
@ -295,6 +338,7 @@ function parseArgs(): Args {
|
|||
return {
|
||||
target: finalArgs,
|
||||
...options,
|
||||
headers: options.header, // commander.js uses 'header' field, map to 'headers'
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -306,8 +350,9 @@ async function main(): Promise<void> {
|
|||
try {
|
||||
const args = parseArgs();
|
||||
await callMethod(args);
|
||||
// Explicitly exit to ensure process terminates in CI
|
||||
process.exit(0);
|
||||
|
||||
// Let Node.js naturally exit instead of force-exiting
|
||||
// process.exit(0) was causing stdout truncation
|
||||
} catch (error) {
|
||||
handleError(error);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ export type TransportOptions = {
|
|||
command?: string;
|
||||
args?: string[];
|
||||
url?: string;
|
||||
headers?: Record<string, string>;
|
||||
};
|
||||
|
||||
function createStdioTransport(options: TransportOptions): Transport {
|
||||
|
|
@ -64,11 +65,25 @@ export function createTransport(options: TransportOptions): Transport {
|
|||
const url = new URL(options.url);
|
||||
|
||||
if (transportType === "sse") {
|
||||
return new SSEClientTransport(url);
|
||||
const transportOptions = options.headers
|
||||
? {
|
||||
requestInit: {
|
||||
headers: options.headers,
|
||||
},
|
||||
}
|
||||
: undefined;
|
||||
return new SSEClientTransport(url, transportOptions);
|
||||
}
|
||||
|
||||
if (transportType === "http") {
|
||||
return new StreamableHTTPClientTransport(url);
|
||||
const transportOptions = options.headers
|
||||
? {
|
||||
requestInit: {
|
||||
headers: options.headers,
|
||||
},
|
||||
}
|
||||
: undefined;
|
||||
return new StreamableHTTPClientTransport(url, transportOptions);
|
||||
}
|
||||
|
||||
throw new Error(`Unsupported transport type: ${transportType}`);
|
||||
|
|
|
|||
Loading…
Reference in a new issue