Stdio Transport
Explaining the Model Context Protocol (MCP) server's communication via stdio, JSON-RPC, and its tsup bundling configuration, including the noExternal quirk.
Stdio Transport
The odys application leverages the Model Context Protocol (MCP) to expose its internal capabilities as tools that can be invoked by external agents, such as AI models. This document delves into the core mechanism by which the odys MCP server communicates, specifically focusing on its use of standard I/O (stdio) for transport, the JSON-RPC structure of its interactions, and the build process that packages it for execution. Understanding these components is crucial for comprehending how odys integrates with the broader MCP ecosystem.
Overview
The heart of the odys MCP server resides in mcp-server/src/server.ts. This file initializes a Server instance from the @modelcontextprotocol/sdk/server package, declaring the server's identity as odys with version 0.1.0 and its capability to expose tools. Communication with this server is established through a StdioServerTransport, which means all requests and responses flow over standard input and output streams.
The server is designed to handle two primary types of requests defined by the MCP SDK: ListToolsRequestSchema and CallToolRequestSchema. When a ListToolsRequestSchema is received, the server responds by providing the TOOL_DEFINITIONS which enumerate the 4 available tools: get_appointments_today, get_customer_by_phone, get_revenue_summary, and update_appointment_status. For a CallToolRequestSchema, the server dynamically dispatches the request to the corresponding tool function, passing along the provided arguments. This dispatch logic is implemented using a switch statement that matches the tool name from the request parameters. Any errors during tool execution are caught, logged to console.error, and returned as a structured error response, including an isError: true flag.
The server's lifecycle is managed by an asynchronous main function, which connects the Server instance to the StdioServerTransport and logs its operational status. Should any fatal error occur during startup or operation, the process exits with an error code.
Stdio Communication and JSON-RPC
The choice of StdioServerTransport for the odys MCP server is a deliberate one, enabling inter-process communication without the overhead of network sockets. This transport mechanism is particularly well-suited for scenarios where the MCP server runs as a child process, managed by a parent application that can pipe data to its standard input and read from its standard output. This setup is common in local development environments or specific deployment models where direct process interaction is preferred.
Underneath this transport layer, the communication adheres to the JSON-RPC specification. This means that all messages exchanged between the client (the entity invoking the MCP server) and the server are JSON objects, structured according to the JSON-RPC 2.0 standard. Requests include a method (e.g., list_tools, call_tool), params, and an id, while responses contain either a result or an error object, along with the matching id. The @modelcontextprotocol/sdk handles the serialization and deserialization of these JSON messages, abstracting away the low-level details for the application developer.
The server's setRequestHandler calls are central to this JSON-RPC interaction. For ListToolsRequestSchema, it simply returns the TOOL_DEFINITIONS. For CallToolRequestSchema, it parses the incoming request to extract the tool name and arguments, then executes the appropriate function from src/tools.js. The result of this execution is then wrapped in a JSON object and sent back as a response.
Bundling with tsup and noExternal Configuration
The odys MCP server is bundled into a single executable JavaScript file using tsup, a fast TypeScript bundler. The configuration for this process is defined in mcp-server/tsup.config.ts.
The tsup configuration specifies src/server.ts as the entry point, targeting node20 for execution. It outputs an esm (ECMAScript Module) format to the dist directory, ensuring a clean build by setting clean: true. The bundle: true and splitting: false options indicate that all code, including dependencies, should be consolidated into a single output file, dist/server.js, without creating multiple chunks. For this server, sourcemap: false and minify: false are chosen, likely prioritizing faster builds and easier debugging of the deployed artifact over minimal file size.
A critical aspect of this bundling strategy is the noExternal option, which is set to ["zod", "@modelcontextprotocol/sdk"]. This configuration instructs tsup to explicitly bundle these two packages directly into the output dist/server.js file, rather than treating them as external dependencies that would need to be installed in node_modules at runtime. This decision ensures that the odys MCP server is a self-contained executable, reducing potential issues related to dependency resolution or version mismatches in the deployment environment. It guarantees that the exact versions of zod and @modelcontextprotocol/sdk used during the build are the ones available at runtime.
Finally, the banner: { js: "#!/usr/bin/env node" } adds a shebang to the output file, making it directly executable on Unix-like systems. The onSuccess: "chmod +x dist/server.js" command then ensures that the generated dist/server.js file has execute permissions, making it directly executable on Unix-like systems (e.g., ./mcp-server/dist/server.js). While the .mcp.json configuration explicitly invokes node (i.e., node ./mcp-server/dist/server.js), the shebang and execute permissions are good practice for general script execution.
Design Decisions
The architectural choices for the odys MCP server reflect a preference for simplicity, self-containment, and clear inter-process communication:
- Stdio Transport: The decision to use
StdioServerTransportwas likely driven by the need for a straightforward, low-overhead communication channel for local execution or specific containerized environments. It avoids the complexities of network configuration and port management, making it easy to integrate as a child process. - JSON-RPC: Adopting JSON-RPC provides a standardized, language-agnostic protocol for remote procedure calls. This makes the MCP server accessible to a wide range of clients and tools, ensuring clear request/response semantics and robust error handling.
tsupBundling: Bundling the server withtsupinto a singledist/server.jsfile simplifies deployment significantly. It eliminates the need to managenode_modulesin the deployment target, ensuring that the server runs with a consistent set of dependencies.noExternalfor Core Dependencies: Explicitly bundlingzodand@modelcontextprotocol/sdkvianoExternalis a strong choice for reliability. These are fundamental libraries for the server's operation (schema validation and MCP protocol implementation, respectively). By embedding them, the build process guarantees that the server will always have access to the correct versions, preventing runtime errors that could arise from environmental differences or missing dependencies. The trade-off is a slightly larger bundle size, but for critical infrastructure components, this is often an acceptable compromise for increased stability.
Potential Improvements
While the current implementation is functional and well-structured, several areas could be considered for future enhancements:
- Dynamic Tool Registration: The
switchstatement within theCallToolRequestSchemahandler inmcp-server/src/server.tsdirectly maps tool names to their respective functions. As the number of tools grows beyond the current 4, thisswitchstatement will become increasingly verbose and harder to maintain. A more dynamic approach, such as storing tool functions in aMapor an object where keys are tool names, could simplify the dispatch logic and make it easier to add or remove tools without modifying the core server logic. - Structured Error Responses: Currently, tool execution errors are caught and returned as an MCP error response, which includes
isError: trueand acontentfield containing a JSON object{ error: message }inmcp-server/src/server.ts. For more sophisticated clients, providing structured error codes, types, or additional context (e.g., a stack trace in development environments) could significantly improve debugging and client-side error handling. This would allow clients to differentiate between various types of tool failures programmatically. - Enhanced Logging: The server uses
console.errorfor logging critical events. While effective for basic output, integrating a dedicated logging library (e.g., Winston, Pino) could provide more control over log levels, output formats (e.g., JSON for easier parsing by log aggregators), and destinations. This would greatly enhance observability and debugging capabilities, especially in production environments.
References
mcp-server/src/server.tsmcp-server/tsup.config.ts.mcp.json