mcp-quiz-server
Version:
🧠AI-Powered Quiz Management via Model Context Protocol (MCP) - Create, manage, and take quizzes directly from VS Code, Claude, and other AI agents.
148 lines (147 loc) • 5.64 kB
JavaScript
"use strict";
/**
* @moduleName: STDIO Transport Implementation
* @version: 2.0.0
* @since: 2025-07-25
* @lastUpdated: 2025-07-25
* @projectSummary: Enhanced MCP Quiz Server - STDIO Transport Implementation
* @techStack: TypeScript, Node.js STDIO, JSON-RPC 2.0, MCP Protocol
* @dependency: Node.js process module
* @interModuleDependency: ./transport-abstraction, ../types/mcp-types
* @requirementsTraceability:
* {@link Requirements.REQ_MCP_001} (JSON-RPC 2.0 MCP Protocol)
* @briefDescription: STDIO transport implementation for VS Code integration and local development
* @methods: initialize, send, onMessage, close, processStdin
* @contributors: GitHub Copilot, VS Code Integration Team
* @examples:
* - VS Code MCP server integration
* - Local development testing
* @vulnerabilitiesAssessment: Process isolation, no network exposure, input validation for JSON-RPC
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.StdioTransport = void 0;
const transport_abstraction_1 = require("./transport-abstraction");
/**
* STDIO transport implementation for VS Code and local development
*/
class StdioTransport extends transport_abstraction_1.BaseTransport {
constructor(config) {
super();
this.name = 'stdio';
this.version = '1.0.0';
this.capabilities = transport_abstraction_1.TRANSPORT_CAPABILITIES[transport_abstraction_1.TransportType.STDIO];
this.inputBuffer = '';
this.isInitialized = false;
this.encoding = 'utf8';
this.encoding = (config === null || config === void 0 ? void 0 : config.encoding) || 'utf8';
}
async initialize() {
if (this.isInitialized) {
return;
}
console.error('🔌 Initializing STDIO transport...');
// Set up STDIN processing
process.stdin.setEncoding(this.encoding);
process.stdin.on('data', this.handleStdinData.bind(this));
process.stdin.on('end', this.handleStdinEnd.bind(this));
// Set up process signal handlers
process.on('SIGINT', this.handleShutdown.bind(this));
process.on('SIGTERM', this.handleShutdown.bind(this));
this.isInitialized = true;
this.metrics.currentConnections = 1;
this.emit('onConnect');
console.error('✅ STDIO transport ready for JSON-RPC 2.0 communication');
}
async send(response) {
if (!this.isInitialized) {
throw new Error('STDIO transport not initialized');
}
try {
const jsonMessage = JSON.stringify(response);
process.stdout.write(jsonMessage + '\n');
this.metrics.messagesSent++;
}
catch (error) {
this.metrics.errors++;
throw new Error(`Failed to send STDIO message: ${error}`);
}
}
async close() {
if (!this.isInitialized) {
return;
}
console.error('🛑 Closing STDIO transport...');
// Clean up event listeners
process.stdin.removeAllListeners('data');
process.stdin.removeAllListeners('end');
this.isInitialized = false;
this.metrics.currentConnections = 0;
this.emit('onDisconnect', 'Manual close');
}
isReady() {
return this.isInitialized;
}
async handleStdinData(chunk) {
this.inputBuffer += chunk;
// Process complete JSON-RPC messages (delimited by newlines)
const lines = this.inputBuffer.split('\n');
this.inputBuffer = lines.pop() || ''; // Keep incomplete line in buffer
for (const line of lines) {
if (line.trim()) {
await this.processJsonRpcMessage(line.trim());
}
}
}
async processJsonRpcMessage(message) {
try {
const request = JSON.parse(message);
// Validate JSON-RPC 2.0 format
if (request.jsonrpc !== '2.0') {
await this.sendErrorResponse(null, -32600, 'Invalid Request - must be JSON-RPC 2.0');
return;
}
if (!request.method) {
await this.sendErrorResponse(request.id, -32600, 'Invalid Request - missing method');
return;
}
// Create transport metadata
const metadata = {
transportType: transport_abstraction_1.TransportType.STDIO,
timestamp: new Date().toISOString(),
messageId: `stdio-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
encoding: this.encoding,
};
// Process message through base transport
const response = await this.processMessage(request, metadata);
await this.send(response);
}
catch (error) {
this.metrics.errors++;
await this.sendErrorResponse(null, -32700, 'Parse error', error instanceof Error ? error.message : String(error));
}
}
async sendErrorResponse(id, code, message, data) {
const errorResponse = {
jsonrpc: '2.0',
id,
error: {
code,
message,
data,
},
};
await this.send(errorResponse);
}
handleStdinEnd() {
console.error('📪 STDIN closed, shutting down...');
this.emit('onDisconnect', 'STDIN closed');
process.exit(0);
}
handleShutdown() {
console.error('🔄 Received shutdown signal...');
this.close().then(() => {
process.exit(0);
});
}
}
exports.StdioTransport = StdioTransport;