reloaderoo
Version:
Hot-reload your MCP servers without restarting your AI coding assistant. Works excellently with VSCode MCP, well with Claude Code. A transparent development proxy for the Model Context Protocol that enables seamless server restarts during development.
196 lines • 7.18 kB
JavaScript
/**
* Inspect command implementation
*
* Provides CLI commands for inspecting and debugging MCP servers
*/
import { Command } from 'commander';
// Child process spawning is handled by StdioClientTransport
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
/**
* Parse command and args from argv array after '--'
*/
function parseChildCommand(argv) {
const dashIndex = argv.indexOf('--');
if (dashIndex === -1 || dashIndex >= argv.length - 1) {
return null;
}
return {
command: argv[dashIndex + 1],
args: argv.slice(dashIndex + 2)
};
}
/**
* Create a standard inspection action handler
*/
function createInspectionAction(operation) {
return async (...args) => {
// Extract options (always the last argument from commander)
const options = args[args.length - 1];
const commandArgs = args.slice(0, -1); // Remove options from args
// Parse child command from process.argv
const childInfo = parseChildCommand(process.argv);
if (!childInfo) {
console.error(JSON.stringify({ error: 'Child command required after --' }, null, 2));
process.exit(1);
}
let client;
// Set a timeout for the entire operation
const timeout = parseInt(options.timeout || '30000', 10);
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error(`Operation timed out after ${timeout}ms`)), timeout));
try {
const operationPromise = (async () => {
// Create MCP client transport with stdio
const transport = new StdioClientTransport({
command: childInfo.command,
args: childInfo.args,
cwd: options.workingDir || process.cwd(),
env: process.env
});
client = new Client({
name: 'reloaderoo-inspector',
version: '1.0.0'
}, {
capabilities: {}
});
// Connect the client
await client.connect(transport);
// Execute the operation
const result = await operation(client, ...commandArgs);
// Output the raw result
console.log(JSON.stringify(result, null, 2));
})();
await Promise.race([operationPromise, timeoutPromise]);
}
catch (error) {
const errorOutput = {
error: error instanceof Error ? error.message : String(error)
};
console.error(JSON.stringify(errorOutput, null, 2));
process.exit(1);
}
finally {
// Cleanup
if (client) {
try {
await client.close();
}
catch {
// Ignore cleanup errors
}
}
// Transport cleanup is handled by client.close()
process.exit(0);
}
};
}
/**
* Create the inspect command with all subcommands
*/
export function createInspectCommand() {
const inspect = new Command('inspect')
.description('Inspect and debug MCP servers')
.addHelpText('after', `
Examples:
$ reloaderoo inspect list-tools -- node server.js
$ reloaderoo inspect call-tool get_weather --params '{"location": "London"}' -- node server.js
$ reloaderoo inspect server-info -- node server.js
`);
// Common options for all inspect subcommands
const addCommonOptions = (cmd) => {
return cmd
.option('-w, --working-dir <dir>', 'Working directory for the child process')
.option('-t, --timeout <ms>', 'Operation timeout in milliseconds', '30000');
};
// Server info command
addCommonOptions(inspect.command('server-info')
.description('Get server information and capabilities')
.action(createInspectionAction(async (client) => {
// Get server capabilities
const capabilities = client.getServerCapabilities();
// Return basic server info
return {
protocolVersion: '2024-11-05',
capabilities
};
})));
// List tools command
addCommonOptions(inspect.command('list-tools')
.description('List all available tools')
.action(createInspectionAction(async (client) => {
const result = await client.listTools();
return result;
})));
// Call tool command
addCommonOptions(inspect.command('call-tool <name>')
.description('Call a specific tool')
.option('-p, --params <json>', 'Tool parameters as JSON string')
.action(createInspectionAction(async (client, name, options) => {
let params = undefined;
if (options.params) {
try {
params = JSON.parse(options.params);
}
catch (error) {
throw new Error(`Invalid JSON parameters: ${error}`);
}
}
const result = await client.callTool({
name,
arguments: params
});
return result;
})));
// List resources command
addCommonOptions(inspect.command('list-resources')
.description('List all available resources')
.action(createInspectionAction(async (client) => {
const result = await client.listResources();
return result;
})));
// Read resource command
addCommonOptions(inspect.command('read-resource <uri>')
.description('Read a specific resource')
.action(createInspectionAction(async (client, uri) => {
const result = await client.readResource({
uri
});
return result;
})));
// List prompts command
addCommonOptions(inspect.command('list-prompts')
.description('List all available prompts')
.action(createInspectionAction(async (client) => {
const result = await client.listPrompts();
return result;
})));
// Get prompt command
addCommonOptions(inspect.command('get-prompt <name>')
.description('Get a specific prompt')
.option('-a, --args <json>', 'Prompt arguments as JSON string')
.action(createInspectionAction(async (client, name, options) => {
let args = undefined;
if (options.args) {
try {
args = JSON.parse(options.args);
}
catch (error) {
throw new Error(`Invalid JSON arguments: ${error}`);
}
}
const result = await client.getPrompt({
name,
arguments: args
});
return result;
})));
// Ping command - Use proper MCP ping
addCommonOptions(inspect.command('ping')
.description('Check server connectivity')
.action(createInspectionAction(async (client) => {
const result = await client.ping();
return result;
})));
return inspect;
}
//# sourceMappingURL=inspect.js.map