@modelcontextprotocol/inspector 0.10.1 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli/build/cli.js +195 -0
- package/cli/build/client/connection.js +33 -0
- package/cli/build/client/index.js +6 -0
- package/cli/build/client/prompts.js +23 -0
- package/cli/build/client/resources.js +30 -0
- package/cli/build/client/tools.js +64 -0
- package/cli/build/client/types.js +1 -0
- package/cli/build/error-handler.js +18 -0
- package/cli/build/index.js +166 -0
- package/cli/build/transport.js +47 -0
- package/client/dist/assets/OAuthCallback-kYD-IoGw.js +84 -0
- package/client/dist/assets/index-D5WIP3OH.css +2204 -0
- package/client/dist/assets/index-DoxCWJPh.js +30180 -0
- package/client/dist/index.html +14 -0
- package/client/dist/mcp.svg +12 -0
- package/package.json +7 -5
- package/server/build/index.js +300 -0
- package/server/build/mcpProxy.js +32 -0
package/cli/build/cli.js
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { dirname, resolve } from "path";
|
|
6
|
+
import { spawnPromise } from "spawn-rx";
|
|
7
|
+
import { fileURLToPath } from "url";
|
|
8
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
function handleError(error) {
|
|
10
|
+
let message;
|
|
11
|
+
if (error instanceof Error) {
|
|
12
|
+
message = error.message;
|
|
13
|
+
}
|
|
14
|
+
else if (typeof error === "string") {
|
|
15
|
+
message = error;
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
message = "Unknown error";
|
|
19
|
+
}
|
|
20
|
+
console.error(message);
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
function delay(ms) {
|
|
24
|
+
return new Promise((resolve) => setTimeout(resolve, ms, true));
|
|
25
|
+
}
|
|
26
|
+
async function runWebClient(args) {
|
|
27
|
+
const inspectorServerPath = resolve(__dirname, "../../", "server", "build", "index.js");
|
|
28
|
+
// Path to the client entry point
|
|
29
|
+
const inspectorClientPath = resolve(__dirname, "../../", "client", "bin", "client.js");
|
|
30
|
+
const CLIENT_PORT = process.env.CLIENT_PORT ?? "6274";
|
|
31
|
+
const SERVER_PORT = process.env.SERVER_PORT ?? "6277";
|
|
32
|
+
console.log("Starting MCP inspector...");
|
|
33
|
+
const abort = new AbortController();
|
|
34
|
+
let cancelled = false;
|
|
35
|
+
process.on("SIGINT", () => {
|
|
36
|
+
cancelled = true;
|
|
37
|
+
abort.abort();
|
|
38
|
+
});
|
|
39
|
+
let server;
|
|
40
|
+
let serverOk;
|
|
41
|
+
try {
|
|
42
|
+
server = spawnPromise("node", [
|
|
43
|
+
inspectorServerPath,
|
|
44
|
+
...(args.command ? [`--env`, args.command] : []),
|
|
45
|
+
...(args.args ? [`--args=${args.args.join(" ")}`] : []),
|
|
46
|
+
], {
|
|
47
|
+
env: {
|
|
48
|
+
...process.env,
|
|
49
|
+
PORT: SERVER_PORT,
|
|
50
|
+
MCP_ENV_VARS: JSON.stringify(args.envArgs),
|
|
51
|
+
},
|
|
52
|
+
signal: abort.signal,
|
|
53
|
+
echoOutput: true,
|
|
54
|
+
});
|
|
55
|
+
// Make sure server started before starting client
|
|
56
|
+
serverOk = await Promise.race([server, delay(2 * 1000)]);
|
|
57
|
+
}
|
|
58
|
+
catch (error) { }
|
|
59
|
+
if (serverOk) {
|
|
60
|
+
try {
|
|
61
|
+
await spawnPromise("node", [inspectorClientPath], {
|
|
62
|
+
env: { ...process.env, PORT: CLIENT_PORT },
|
|
63
|
+
signal: abort.signal,
|
|
64
|
+
echoOutput: true,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
catch (e) {
|
|
68
|
+
if (!cancelled || process.env.DEBUG)
|
|
69
|
+
throw e;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
async function runCli(args) {
|
|
74
|
+
const projectRoot = resolve(__dirname, "..");
|
|
75
|
+
const cliPath = resolve(projectRoot, "build", "index.js");
|
|
76
|
+
const abort = new AbortController();
|
|
77
|
+
let cancelled = false;
|
|
78
|
+
process.on("SIGINT", () => {
|
|
79
|
+
cancelled = true;
|
|
80
|
+
abort.abort();
|
|
81
|
+
});
|
|
82
|
+
try {
|
|
83
|
+
await spawnPromise("node", [cliPath, args.command, ...args.args], {
|
|
84
|
+
env: { ...process.env, ...args.envArgs },
|
|
85
|
+
signal: abort.signal,
|
|
86
|
+
echoOutput: true,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
catch (e) {
|
|
90
|
+
if (!cancelled || process.env.DEBUG) {
|
|
91
|
+
throw e;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
function loadConfigFile(configPath, serverName) {
|
|
96
|
+
try {
|
|
97
|
+
const resolvedConfigPath = path.isAbsolute(configPath)
|
|
98
|
+
? configPath
|
|
99
|
+
: path.resolve(process.cwd(), configPath);
|
|
100
|
+
if (!fs.existsSync(resolvedConfigPath)) {
|
|
101
|
+
throw new Error(`Config file not found: ${resolvedConfigPath}`);
|
|
102
|
+
}
|
|
103
|
+
const configContent = fs.readFileSync(resolvedConfigPath, "utf8");
|
|
104
|
+
const parsedConfig = JSON.parse(configContent);
|
|
105
|
+
if (!parsedConfig.mcpServers || !parsedConfig.mcpServers[serverName]) {
|
|
106
|
+
const availableServers = Object.keys(parsedConfig.mcpServers || {}).join(", ");
|
|
107
|
+
throw new Error(`Server '${serverName}' not found in config file. Available servers: ${availableServers}`);
|
|
108
|
+
}
|
|
109
|
+
const serverConfig = parsedConfig.mcpServers[serverName];
|
|
110
|
+
return serverConfig;
|
|
111
|
+
}
|
|
112
|
+
catch (err) {
|
|
113
|
+
if (err instanceof SyntaxError) {
|
|
114
|
+
throw new Error(`Invalid JSON in config file: ${err.message}`);
|
|
115
|
+
}
|
|
116
|
+
throw err;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
function parseKeyValuePair(value, previous = {}) {
|
|
120
|
+
const parts = value.split("=");
|
|
121
|
+
const key = parts[0];
|
|
122
|
+
const val = parts.slice(1).join("=");
|
|
123
|
+
if (val === undefined || val === "") {
|
|
124
|
+
throw new Error(`Invalid parameter format: ${value}. Use key=value format.`);
|
|
125
|
+
}
|
|
126
|
+
return { ...previous, [key]: val };
|
|
127
|
+
}
|
|
128
|
+
function parseArgs() {
|
|
129
|
+
const program = new Command();
|
|
130
|
+
const argSeparatorIndex = process.argv.indexOf("--");
|
|
131
|
+
let preArgs = process.argv;
|
|
132
|
+
let postArgs = [];
|
|
133
|
+
if (argSeparatorIndex !== -1) {
|
|
134
|
+
preArgs = process.argv.slice(0, argSeparatorIndex);
|
|
135
|
+
postArgs = process.argv.slice(argSeparatorIndex + 1);
|
|
136
|
+
}
|
|
137
|
+
program
|
|
138
|
+
.name("inspector-bin")
|
|
139
|
+
.allowExcessArguments()
|
|
140
|
+
.allowUnknownOption()
|
|
141
|
+
.option("-e <env>", "environment variables in KEY=VALUE format", parseKeyValuePair, {})
|
|
142
|
+
.option("--config <path>", "config file path")
|
|
143
|
+
.option("--server <n>", "server name from config file")
|
|
144
|
+
.option("--cli", "enable CLI mode");
|
|
145
|
+
// Parse only the arguments before --
|
|
146
|
+
program.parse(preArgs);
|
|
147
|
+
const options = program.opts();
|
|
148
|
+
const remainingArgs = program.args;
|
|
149
|
+
// Add back any arguments that came after --
|
|
150
|
+
const finalArgs = [...remainingArgs, ...postArgs];
|
|
151
|
+
// Validate that config and server are provided together
|
|
152
|
+
if ((options.config && !options.server) ||
|
|
153
|
+
(!options.config && options.server)) {
|
|
154
|
+
throw new Error("Both --config and --server must be provided together. If you specify one, you must specify the other.");
|
|
155
|
+
}
|
|
156
|
+
// If config file is specified, load and use the options from the file. We must merge the args
|
|
157
|
+
// from the command line and the file together, or we will miss the method options (--method,
|
|
158
|
+
// etc.)
|
|
159
|
+
if (options.config && options.server) {
|
|
160
|
+
const config = loadConfigFile(options.config, options.server);
|
|
161
|
+
return {
|
|
162
|
+
command: config.command,
|
|
163
|
+
args: [...(config.args || []), ...finalArgs],
|
|
164
|
+
envArgs: { ...(config.env || {}), ...(options.e || {}) },
|
|
165
|
+
cli: options.cli || false,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
// Otherwise use command line arguments
|
|
169
|
+
const command = finalArgs[0] || "";
|
|
170
|
+
const args = finalArgs.slice(1);
|
|
171
|
+
return {
|
|
172
|
+
command,
|
|
173
|
+
args,
|
|
174
|
+
envArgs: options.e || {},
|
|
175
|
+
cli: options.cli || false,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
async function main() {
|
|
179
|
+
process.on("uncaughtException", (error) => {
|
|
180
|
+
handleError(error);
|
|
181
|
+
});
|
|
182
|
+
try {
|
|
183
|
+
const args = parseArgs();
|
|
184
|
+
if (args.cli) {
|
|
185
|
+
runCli(args);
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
await runWebClient(args);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
handleError(error);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
main();
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export const validLogLevels = [
|
|
2
|
+
"trace",
|
|
3
|
+
"debug",
|
|
4
|
+
"info",
|
|
5
|
+
"warn",
|
|
6
|
+
"error",
|
|
7
|
+
];
|
|
8
|
+
export async function connect(client, transport) {
|
|
9
|
+
try {
|
|
10
|
+
await client.connect(transport);
|
|
11
|
+
}
|
|
12
|
+
catch (error) {
|
|
13
|
+
throw new Error(`Failed to connect to MCP server: ${error instanceof Error ? error.message : String(error)}`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export async function disconnect(transport) {
|
|
17
|
+
try {
|
|
18
|
+
await transport.close();
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
throw new Error(`Failed to disconnect from MCP server: ${error instanceof Error ? error.message : String(error)}`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
// Set logging level
|
|
25
|
+
export async function setLoggingLevel(client, level) {
|
|
26
|
+
try {
|
|
27
|
+
const response = await client.setLoggingLevel(level);
|
|
28
|
+
return response;
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
throw new Error(`Failed to set logging level: ${error instanceof Error ? error.message : String(error)}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// List available prompts
|
|
2
|
+
export async function listPrompts(client) {
|
|
3
|
+
try {
|
|
4
|
+
const response = await client.listPrompts();
|
|
5
|
+
return response;
|
|
6
|
+
}
|
|
7
|
+
catch (error) {
|
|
8
|
+
throw new Error(`Failed to list prompts: ${error instanceof Error ? error.message : String(error)}`);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
// Get a prompt
|
|
12
|
+
export async function getPrompt(client, name, args) {
|
|
13
|
+
try {
|
|
14
|
+
const response = await client.getPrompt({
|
|
15
|
+
name,
|
|
16
|
+
arguments: args || {},
|
|
17
|
+
});
|
|
18
|
+
return response;
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
throw new Error(`Failed to get prompt: ${error instanceof Error ? error.message : String(error)}`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// List available resources
|
|
2
|
+
export async function listResources(client) {
|
|
3
|
+
try {
|
|
4
|
+
const response = await client.listResources();
|
|
5
|
+
return response;
|
|
6
|
+
}
|
|
7
|
+
catch (error) {
|
|
8
|
+
throw new Error(`Failed to list resources: ${error instanceof Error ? error.message : String(error)}`);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
// Read a resource
|
|
12
|
+
export async function readResource(client, uri) {
|
|
13
|
+
try {
|
|
14
|
+
const response = await client.readResource({ uri });
|
|
15
|
+
return response;
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
throw new Error(`Failed to read resource ${uri}: ${error instanceof Error ? error.message : String(error)}`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
// List resource templates
|
|
22
|
+
export async function listResourceTemplates(client) {
|
|
23
|
+
try {
|
|
24
|
+
const response = await client.listResourceTemplates();
|
|
25
|
+
return response;
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
throw new Error(`Failed to list resource templates: ${error instanceof Error ? error.message : String(error)}`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
export async function listTools(client) {
|
|
2
|
+
try {
|
|
3
|
+
const response = await client.listTools();
|
|
4
|
+
return response;
|
|
5
|
+
}
|
|
6
|
+
catch (error) {
|
|
7
|
+
throw new Error(`Failed to list tools: ${error instanceof Error ? error.message : String(error)}`);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
function convertParameterValue(value, schema) {
|
|
11
|
+
if (!value) {
|
|
12
|
+
return value;
|
|
13
|
+
}
|
|
14
|
+
if (schema.type === "number" || schema.type === "integer") {
|
|
15
|
+
return Number(value);
|
|
16
|
+
}
|
|
17
|
+
if (schema.type === "boolean") {
|
|
18
|
+
return value.toLowerCase() === "true";
|
|
19
|
+
}
|
|
20
|
+
if (schema.type === "object" || schema.type === "array") {
|
|
21
|
+
try {
|
|
22
|
+
return JSON.parse(value);
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
return value;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return value;
|
|
29
|
+
}
|
|
30
|
+
function convertParameters(tool, params) {
|
|
31
|
+
const result = {};
|
|
32
|
+
const properties = tool.inputSchema.properties || {};
|
|
33
|
+
for (const [key, value] of Object.entries(params)) {
|
|
34
|
+
const paramSchema = properties[key];
|
|
35
|
+
if (paramSchema) {
|
|
36
|
+
result[key] = convertParameterValue(value, paramSchema);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
// If no schema is found for this parameter, keep it as string
|
|
40
|
+
result[key] = value;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return result;
|
|
44
|
+
}
|
|
45
|
+
export async function callTool(client, name, args) {
|
|
46
|
+
try {
|
|
47
|
+
const toolsResponse = await listTools(client);
|
|
48
|
+
const tools = toolsResponse.tools;
|
|
49
|
+
const tool = tools.find((t) => t.name === name);
|
|
50
|
+
let convertedArgs = args;
|
|
51
|
+
if (tool) {
|
|
52
|
+
// Convert parameters based on the tool's schema
|
|
53
|
+
convertedArgs = convertParameters(tool, args);
|
|
54
|
+
}
|
|
55
|
+
const response = await client.callTool({
|
|
56
|
+
name: name,
|
|
57
|
+
arguments: convertedArgs,
|
|
58
|
+
});
|
|
59
|
+
return response;
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
throw new Error(`Failed to call tool ${name}: ${error instanceof Error ? error.message : String(error)}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
function formatError(error) {
|
|
2
|
+
let message;
|
|
3
|
+
if (error instanceof Error) {
|
|
4
|
+
message = error.message;
|
|
5
|
+
}
|
|
6
|
+
else if (typeof error === "string") {
|
|
7
|
+
message = error;
|
|
8
|
+
}
|
|
9
|
+
else {
|
|
10
|
+
message = "Unknown error";
|
|
11
|
+
}
|
|
12
|
+
return message;
|
|
13
|
+
}
|
|
14
|
+
export function handleError(error) {
|
|
15
|
+
const errorMessage = formatError(error);
|
|
16
|
+
console.error(errorMessage);
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
3
|
+
import { Command } from "commander";
|
|
4
|
+
import { callTool, connect, disconnect, getPrompt, listPrompts, listResources, listResourceTemplates, listTools, readResource, setLoggingLevel, validLogLevels, } from "./client/index.js";
|
|
5
|
+
import { handleError } from "./error-handler.js";
|
|
6
|
+
import { createTransport } from "./transport.js";
|
|
7
|
+
function createTransportOptions(target) {
|
|
8
|
+
if (target.length === 0) {
|
|
9
|
+
throw new Error("Target is required. Specify a URL or a command to execute.");
|
|
10
|
+
}
|
|
11
|
+
const [command, ...commandArgs] = target;
|
|
12
|
+
if (!command) {
|
|
13
|
+
throw new Error("Command is required.");
|
|
14
|
+
}
|
|
15
|
+
const isUrl = command.startsWith("http://") || command.startsWith("https://");
|
|
16
|
+
if (isUrl && commandArgs.length > 0) {
|
|
17
|
+
throw new Error("Arguments cannot be passed to a URL-based MCP server.");
|
|
18
|
+
}
|
|
19
|
+
return {
|
|
20
|
+
transportType: isUrl ? "sse" : "stdio",
|
|
21
|
+
command: isUrl ? undefined : command,
|
|
22
|
+
args: isUrl ? undefined : commandArgs,
|
|
23
|
+
url: isUrl ? command : undefined,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
async function callMethod(args) {
|
|
27
|
+
const transportOptions = createTransportOptions(args.target);
|
|
28
|
+
const transport = createTransport(transportOptions);
|
|
29
|
+
const client = new Client({
|
|
30
|
+
name: "inspector-cli",
|
|
31
|
+
version: "0.5.1",
|
|
32
|
+
});
|
|
33
|
+
try {
|
|
34
|
+
await connect(client, transport);
|
|
35
|
+
let result;
|
|
36
|
+
// Tools methods
|
|
37
|
+
if (args.method === "tools/list") {
|
|
38
|
+
result = await listTools(client);
|
|
39
|
+
}
|
|
40
|
+
else if (args.method === "tools/call") {
|
|
41
|
+
if (!args.toolName) {
|
|
42
|
+
throw new Error("Tool name is required for tools/call method. Use --tool-name to specify the tool name.");
|
|
43
|
+
}
|
|
44
|
+
result = await callTool(client, args.toolName, args.toolArg || {});
|
|
45
|
+
}
|
|
46
|
+
// Resources methods
|
|
47
|
+
else if (args.method === "resources/list") {
|
|
48
|
+
result = await listResources(client);
|
|
49
|
+
}
|
|
50
|
+
else if (args.method === "resources/read") {
|
|
51
|
+
if (!args.uri) {
|
|
52
|
+
throw new Error("URI is required for resources/read method. Use --uri to specify the resource URI.");
|
|
53
|
+
}
|
|
54
|
+
result = await readResource(client, args.uri);
|
|
55
|
+
}
|
|
56
|
+
else if (args.method === "resources/templates/list") {
|
|
57
|
+
result = await listResourceTemplates(client);
|
|
58
|
+
}
|
|
59
|
+
// Prompts methods
|
|
60
|
+
else if (args.method === "prompts/list") {
|
|
61
|
+
result = await listPrompts(client);
|
|
62
|
+
}
|
|
63
|
+
else if (args.method === "prompts/get") {
|
|
64
|
+
if (!args.promptName) {
|
|
65
|
+
throw new Error("Prompt name is required for prompts/get method. Use --prompt-name to specify the prompt name.");
|
|
66
|
+
}
|
|
67
|
+
result = await getPrompt(client, args.promptName, args.promptArgs || {});
|
|
68
|
+
}
|
|
69
|
+
// Logging methods
|
|
70
|
+
else if (args.method === "logging/setLevel") {
|
|
71
|
+
if (!args.logLevel) {
|
|
72
|
+
throw new Error("Log level is required for logging/setLevel method. Use --log-level to specify the log level.");
|
|
73
|
+
}
|
|
74
|
+
result = await setLoggingLevel(client, args.logLevel);
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
throw new Error(`Unsupported method: ${args.method}. Supported methods include: tools/list, tools/call, resources/list, resources/read, resources/templates/list, prompts/list, prompts/get, logging/setLevel`);
|
|
78
|
+
}
|
|
79
|
+
console.log(JSON.stringify(result, null, 2));
|
|
80
|
+
}
|
|
81
|
+
finally {
|
|
82
|
+
try {
|
|
83
|
+
await disconnect(transport);
|
|
84
|
+
}
|
|
85
|
+
catch (disconnectError) {
|
|
86
|
+
throw disconnectError;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function parseKeyValuePair(value, previous = {}) {
|
|
91
|
+
const parts = value.split("=");
|
|
92
|
+
const key = parts[0];
|
|
93
|
+
const val = parts.slice(1).join("=");
|
|
94
|
+
if (val === undefined || val === "") {
|
|
95
|
+
throw new Error(`Invalid parameter format: ${value}. Use key=value format.`);
|
|
96
|
+
}
|
|
97
|
+
return { ...previous, [key]: val };
|
|
98
|
+
}
|
|
99
|
+
function parseArgs() {
|
|
100
|
+
const program = new Command();
|
|
101
|
+
// Find if there's a -- in the arguments and split them
|
|
102
|
+
const argSeparatorIndex = process.argv.indexOf("--");
|
|
103
|
+
let preArgs = process.argv;
|
|
104
|
+
let postArgs = [];
|
|
105
|
+
if (argSeparatorIndex !== -1) {
|
|
106
|
+
preArgs = process.argv.slice(0, argSeparatorIndex);
|
|
107
|
+
postArgs = process.argv.slice(argSeparatorIndex + 1);
|
|
108
|
+
}
|
|
109
|
+
program
|
|
110
|
+
.name("inspector-cli")
|
|
111
|
+
.allowUnknownOption()
|
|
112
|
+
.argument("<target...>", "Command and arguments or URL of the MCP server")
|
|
113
|
+
//
|
|
114
|
+
// Method selection
|
|
115
|
+
//
|
|
116
|
+
.option("--method <method>", "Method to invoke")
|
|
117
|
+
//
|
|
118
|
+
// Tool-related options
|
|
119
|
+
//
|
|
120
|
+
.option("--tool-name <toolName>", "Tool name (for tools/call method)")
|
|
121
|
+
.option("--tool-arg <pairs...>", "Tool argument as key=value pair", parseKeyValuePair, {})
|
|
122
|
+
//
|
|
123
|
+
// Resource-related options
|
|
124
|
+
//
|
|
125
|
+
.option("--uri <uri>", "URI of the resource (for resources/read method)")
|
|
126
|
+
//
|
|
127
|
+
// Prompt-related options
|
|
128
|
+
//
|
|
129
|
+
.option("--prompt-name <promptName>", "Name of the prompt (for prompts/get method)")
|
|
130
|
+
.option("--prompt-args <pairs...>", "Prompt arguments as key=value pairs", parseKeyValuePair, {})
|
|
131
|
+
//
|
|
132
|
+
// Logging options
|
|
133
|
+
//
|
|
134
|
+
.option("--log-level <level>", "Logging level (for logging/setLevel method)", (value) => {
|
|
135
|
+
if (!validLogLevels.includes(value)) {
|
|
136
|
+
throw new Error(`Invalid log level: ${value}. Valid levels are: ${validLogLevels.join(", ")}`);
|
|
137
|
+
}
|
|
138
|
+
return value;
|
|
139
|
+
});
|
|
140
|
+
// Parse only the arguments before --
|
|
141
|
+
program.parse(preArgs);
|
|
142
|
+
const options = program.opts();
|
|
143
|
+
let remainingArgs = program.args;
|
|
144
|
+
// Add back any arguments that came after --
|
|
145
|
+
const finalArgs = [...remainingArgs, ...postArgs];
|
|
146
|
+
if (!options.method) {
|
|
147
|
+
throw new Error("Method is required. Use --method to specify the method to invoke.");
|
|
148
|
+
}
|
|
149
|
+
return {
|
|
150
|
+
target: finalArgs,
|
|
151
|
+
...options,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
async function main() {
|
|
155
|
+
process.on("uncaughtException", (error) => {
|
|
156
|
+
handleError(error);
|
|
157
|
+
});
|
|
158
|
+
try {
|
|
159
|
+
const args = parseArgs();
|
|
160
|
+
await callMethod(args);
|
|
161
|
+
}
|
|
162
|
+
catch (error) {
|
|
163
|
+
handleError(error);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
main();
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
|
2
|
+
import { getDefaultEnvironment, StdioClientTransport, } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
3
|
+
import { findActualExecutable } from "spawn-rx";
|
|
4
|
+
function createSSETransport(options) {
|
|
5
|
+
const baseUrl = new URL(options.url ?? "");
|
|
6
|
+
const sseUrl = new URL("/sse", baseUrl);
|
|
7
|
+
return new SSEClientTransport(sseUrl);
|
|
8
|
+
}
|
|
9
|
+
function createStdioTransport(options) {
|
|
10
|
+
let args = [];
|
|
11
|
+
if (options.args !== undefined) {
|
|
12
|
+
args = options.args;
|
|
13
|
+
}
|
|
14
|
+
const processEnv = {};
|
|
15
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
16
|
+
if (value !== undefined) {
|
|
17
|
+
processEnv[key] = value;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
const defaultEnv = getDefaultEnvironment();
|
|
21
|
+
const env = {
|
|
22
|
+
...processEnv,
|
|
23
|
+
...defaultEnv,
|
|
24
|
+
};
|
|
25
|
+
const { cmd: actualCommand, args: actualArgs } = findActualExecutable(options.command ?? "", args);
|
|
26
|
+
return new StdioClientTransport({
|
|
27
|
+
command: actualCommand,
|
|
28
|
+
args: actualArgs,
|
|
29
|
+
env,
|
|
30
|
+
stderr: "pipe",
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
export function createTransport(options) {
|
|
34
|
+
const { transportType } = options;
|
|
35
|
+
try {
|
|
36
|
+
if (transportType === "stdio") {
|
|
37
|
+
return createStdioTransport(options);
|
|
38
|
+
}
|
|
39
|
+
if (transportType === "sse") {
|
|
40
|
+
return createSSETransport(options);
|
|
41
|
+
}
|
|
42
|
+
throw new Error(`Unsupported transport type: ${transportType}`);
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
throw new Error(`Failed to create transport: ${error instanceof Error ? error.message : String(error)}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { u as useToast, r as reactExports, j as jsxRuntimeExports, S as SESSION_KEYS, I as InspectorOAuthClientProvider, a as auth } from "./index-DoxCWJPh.js";
|
|
2
|
+
const parseOAuthCallbackParams = (location) => {
|
|
3
|
+
const params = new URLSearchParams(location);
|
|
4
|
+
const code = params.get("code");
|
|
5
|
+
if (code) {
|
|
6
|
+
return { successful: true, code };
|
|
7
|
+
}
|
|
8
|
+
const error = params.get("error");
|
|
9
|
+
const error_description = params.get("error_description");
|
|
10
|
+
const error_uri = params.get("error_uri");
|
|
11
|
+
if (error) {
|
|
12
|
+
return { successful: false, error, error_description, error_uri };
|
|
13
|
+
}
|
|
14
|
+
return {
|
|
15
|
+
successful: false,
|
|
16
|
+
error: "invalid_request",
|
|
17
|
+
error_description: "Missing code or error in response",
|
|
18
|
+
error_uri: null
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
const generateOAuthErrorDescription = (params) => {
|
|
22
|
+
const error = params.error;
|
|
23
|
+
const errorDescription = params.error_description;
|
|
24
|
+
const errorUri = params.error_uri;
|
|
25
|
+
return [
|
|
26
|
+
`Error: ${error}.`,
|
|
27
|
+
errorDescription ? `Details: ${errorDescription}.` : "",
|
|
28
|
+
errorUri ? `More info: ${errorUri}.` : ""
|
|
29
|
+
].filter(Boolean).join("\n");
|
|
30
|
+
};
|
|
31
|
+
const OAuthCallback = ({ onConnect }) => {
|
|
32
|
+
const { toast } = useToast();
|
|
33
|
+
const hasProcessedRef = reactExports.useRef(false);
|
|
34
|
+
reactExports.useEffect(() => {
|
|
35
|
+
const handleCallback = async () => {
|
|
36
|
+
if (hasProcessedRef.current) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
hasProcessedRef.current = true;
|
|
40
|
+
const notifyError = (description) => void toast({
|
|
41
|
+
title: "OAuth Authorization Error",
|
|
42
|
+
description,
|
|
43
|
+
variant: "destructive"
|
|
44
|
+
});
|
|
45
|
+
const params = parseOAuthCallbackParams(window.location.search);
|
|
46
|
+
if (!params.successful) {
|
|
47
|
+
return notifyError(generateOAuthErrorDescription(params));
|
|
48
|
+
}
|
|
49
|
+
const serverUrl = sessionStorage.getItem(SESSION_KEYS.SERVER_URL);
|
|
50
|
+
if (!serverUrl) {
|
|
51
|
+
return notifyError("Missing Server URL");
|
|
52
|
+
}
|
|
53
|
+
let result;
|
|
54
|
+
try {
|
|
55
|
+
const serverAuthProvider = new InspectorOAuthClientProvider(serverUrl);
|
|
56
|
+
result = await auth(serverAuthProvider, {
|
|
57
|
+
serverUrl,
|
|
58
|
+
authorizationCode: params.code
|
|
59
|
+
});
|
|
60
|
+
} catch (error) {
|
|
61
|
+
console.error("OAuth callback error:", error);
|
|
62
|
+
return notifyError(`Unexpected error occurred: ${error}`);
|
|
63
|
+
}
|
|
64
|
+
if (result !== "AUTHORIZED") {
|
|
65
|
+
return notifyError(
|
|
66
|
+
`Expected to be authorized after providing auth code, got: ${result}`
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
toast({
|
|
70
|
+
title: "Success",
|
|
71
|
+
description: "Successfully authenticated with OAuth",
|
|
72
|
+
variant: "default"
|
|
73
|
+
});
|
|
74
|
+
onConnect(serverUrl);
|
|
75
|
+
};
|
|
76
|
+
handleCallback().finally(() => {
|
|
77
|
+
window.history.replaceState({}, document.title, "/");
|
|
78
|
+
});
|
|
79
|
+
}, [toast, onConnect]);
|
|
80
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center justify-center h-screen", children: /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-lg text-gray-500", children: "Processing OAuth callback..." }) });
|
|
81
|
+
};
|
|
82
|
+
export {
|
|
83
|
+
OAuthCallback as default
|
|
84
|
+
};
|