| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189 |
- /**
- * MCP Stdio Transport
- *
- * Handles JSON-RPC 2.0 communication over stdin/stdout for MCP protocol.
- */
- import * as readline from 'readline';
- import { captureException } from '../sentry';
- /**
- * JSON-RPC 2.0 Request
- */
- export interface JsonRpcRequest {
- jsonrpc: '2.0';
- id: string | number;
- method: string;
- params?: unknown;
- }
- /**
- * JSON-RPC 2.0 Response
- */
- export interface JsonRpcResponse {
- jsonrpc: '2.0';
- id: string | number | null;
- result?: unknown;
- error?: JsonRpcError;
- }
- /**
- * JSON-RPC 2.0 Error
- */
- export interface JsonRpcError {
- code: number;
- message: string;
- data?: unknown;
- }
- /**
- * JSON-RPC 2.0 Notification (no id, no response expected)
- */
- export interface JsonRpcNotification {
- jsonrpc: '2.0';
- method: string;
- params?: unknown;
- }
- // Standard JSON-RPC error codes
- export const ErrorCodes = {
- ParseError: -32700,
- InvalidRequest: -32600,
- MethodNotFound: -32601,
- InvalidParams: -32602,
- InternalError: -32603,
- } as const;
- export type MessageHandler = (message: JsonRpcRequest | JsonRpcNotification) => Promise<void>;
- /**
- * Stdio Transport for MCP
- *
- * Reads JSON-RPC messages from stdin and writes responses to stdout.
- */
- export class StdioTransport {
- private rl: readline.Interface | null = null;
- private messageHandler: MessageHandler | null = null;
- /**
- * Start listening for messages on stdin
- */
- start(handler: MessageHandler): void {
- this.messageHandler = handler;
- this.rl = readline.createInterface({
- input: process.stdin,
- output: process.stdout,
- terminal: false,
- });
- this.rl.on('line', async (line) => {
- await this.handleLine(line);
- });
- this.rl.on('close', () => {
- process.exit(0);
- });
- }
- /**
- * Stop listening
- */
- stop(): void {
- if (this.rl) {
- this.rl.close();
- this.rl = null;
- }
- }
- /**
- * Send a response
- */
- send(response: JsonRpcResponse): void {
- const json = JSON.stringify(response);
- process.stdout.write(json + '\n');
- }
- /**
- * Send a notification (no id)
- */
- notify(method: string, params?: unknown): void {
- const notification: JsonRpcNotification = {
- jsonrpc: '2.0',
- method,
- params,
- };
- process.stdout.write(JSON.stringify(notification) + '\n');
- }
- /**
- * Send a success response
- */
- sendResult(id: string | number, result: unknown): void {
- this.send({
- jsonrpc: '2.0',
- id,
- result,
- });
- }
- /**
- * Send an error response
- */
- sendError(id: string | number | null, code: number, message: string, data?: unknown): void {
- this.send({
- jsonrpc: '2.0',
- id,
- error: { code, message, data },
- });
- }
- /**
- * Handle an incoming line of JSON
- */
- private async handleLine(line: string): Promise<void> {
- const trimmed = line.trim();
- if (!trimmed) return;
- let parsed: unknown;
- try {
- parsed = JSON.parse(trimmed);
- } catch {
- this.sendError(null, ErrorCodes.ParseError, 'Parse error: invalid JSON');
- return;
- }
- // Validate basic JSON-RPC structure
- if (!this.isValidMessage(parsed)) {
- this.sendError(null, ErrorCodes.InvalidRequest, 'Invalid Request: not a valid JSON-RPC 2.0 message');
- return;
- }
- if (this.messageHandler) {
- try {
- await this.messageHandler(parsed as JsonRpcRequest | JsonRpcNotification);
- } catch (err) {
- captureException(err, { operation: 'mcp-message-handler' });
- const message = parsed as JsonRpcRequest;
- if ('id' in message) {
- this.sendError(
- message.id,
- ErrorCodes.InternalError,
- `Internal error: ${err instanceof Error ? err.message : String(err)}`
- );
- }
- }
- }
- }
- /**
- * Check if message is a valid JSON-RPC 2.0 message
- */
- private isValidMessage(msg: unknown): boolean {
- if (typeof msg !== 'object' || msg === null) return false;
- const obj = msg as Record<string, unknown>;
- if (obj.jsonrpc !== '2.0') return false;
- if (typeof obj.method !== 'string') return false;
- return true;
- }
- }
|