Already have HTTP support? If your MCP server already supports HTTP transport, you can skip ahead to Step 5: Update smithery.yaml to configure your deployment settings.
Here’s a typical STDIO-based MCP server that you might be starting with:
Copy
Ask AI
// src/index.tsimport { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";// Create STDIO serverconst server = new McpServer({ name: "Character Counter", version: "1.0.0"});// Get server token from environment variableconst serverToken = process.env.SERVER_TOKEN;// Register toolserver.registerTool("count_characters", { description: "Count occurrences of a specific character in text", inputSchema: { text: { type: "string", description: "The text to search in" }, character: { type: "string", description: "The character to count" } }}, async ({ text, character }) => { // Tool implementation with API key validation, etc. // ...});// Run with STDIO transportconst transport = new StdioServerTransport();await server.server.connect(transport);
Now let’s migrate this to work with custom HTTP containers. First, set up your Express server with the necessary imports and middleware:
Copy
Ask AI
// src/index.tsimport express, { Request, Response } from "express";import cors from "cors";import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";import { z } from "zod";const app = express();const PORT = process.env.PORT || 8081;// CORS configuration for browser-based MCP clientsapp.use(cors({ origin: '*', // Configure appropriately for production exposedHeaders: ['Mcp-Session-Id', 'mcp-protocol-version'], allowedHeaders: ['Content-Type', 'mcp-session-id'],}));app.use(express.json());
Skip this entire step if no configuration needed: If your MCP server doesn’t need any configuration (API keys, settings, etc.), you can skip this entire step. Your server will work perfectly fine without any configuration handling.
If your server needs configuration (like API keys, user preferences, etc.):
Copy
Ask AI
// src/index.ts (continued - only add if you need configuration)import type { Request, Response } from "express"import { z } from "zod"import { parseAndValidateConfig } from "@smithery/sdk"// Define session configuration schemaexport const configSchema = z.object({ serverToken: z.string().optional().describe("Server access token"), caseSensitive: z.boolean().optional().default(false).describe("Whether character matching should be case sensitive"),})// Use SDK helper in your handlerapp.all('/mcp', (req: Request, res: Response) => { const result = parseAndValidateConfig(req, configSchema) if (result.error) { return res.status(result.value.status).json(result.value) } const config = result.value // ... proceed with your server using `config`})
Create the server function and register your tools:
Copy
Ask AI
// src/index.ts// Create MCP server with your toolsexport default function createServer({ config,}: { config: z.infer<typeof configSchema>;}) { const server = new McpServer({ name: "Character Counter", version: "1.0.0", }); server.registerTool("count_characters", { title: "Count Characters", description: "Count occurrences of a specific character in text", inputSchema: { text: z.string().describe("The text to search in"), character: z.string().describe("The character to count (single character)"), }, }, async ({ text, character }) => { // Validate server access if (!validateServerAccess(config.serverToken)) { throw new Error("Server access validation failed. Please provide a valid serverToken."); } // Apply user preferences from config const searchText = config.caseSensitive ? text : text.toLowerCase(); const searchChar = config.caseSensitive ? character : character.toLowerCase(); // Count occurrences of the specific character const count = searchText.split(searchChar).length - 1; return { content: [ { type: "text", text: `The character "${character}" appears ${count} times in the text.` } ], }; } ); return server.server;}
Now that we have our HTTP server ready, we can optionally add STDIO support for backward compatibility. This gives you the best of both worlds:
HTTP deployment: Custom Express server with full control for scalable HTTP deployment
Local development: Keep the familiar STDIO transport for backwards compatibility
NPM distribution: Publish your server so others can install and run it locally
To enable STDIO support, add a condition to your main() function:
Copy
Ask AI
// src/index.ts// Main function to start the server in the appropriate modeasync function main() { const transport = process.env.TRANSPORT || 'stdio'; if (transport === 'http') { // Run in HTTP mode app.listen(PORT, () => { console.log(`MCP HTTP Server listening on port ${PORT}`); }); } else { // Optional: if you need backward compatibility, add stdio transport const serverToken = process.env.SERVER_TOKEN; const caseSensitive = process.env.CASE_SENSITIVE === 'true'; // Create server with configuration const server = createServer({ config: { serverToken, caseSensitive, }, }); // Start receiving messages on stdin and sending messages on stdout const stdioTransport = new StdioServerTransport(); await server.connect(stdioTransport); console.error("MCP Server running in stdio mode"); }}// Start the servermain().catch((error) => { console.error("Server error:", error); process.exit(1);});
How it works: When you deploy with custom containers, your Express server handles HTTP requests at the /mcp endpoint. The main() function only runs STDIO mode when you execute the file directly (like node dist/index.js), giving you STDIO support for local development.
Local vs Deployed Ports: For local testing, you can use any port (like 8080 above). However, when deployed to Smithery, your server must listen on the PORT environment variable, which Smithery will set to 8081.
Test interactively:
Once your server is running in HTTP mode, you can test it interactively using the Smithery playground:
Copy
Ask AI
npx @smithery/cli playground --port 8080
Config Handling Limitation: The Smithery playground doesn’t currently support config handling for custom containers (Python/TypeScript). Your deployed server will support configuration, but local testing with the playground will use default/empty config values. We’re actively working on adding this support.
This guide showed you how to migrate a TypeScript MCP server from STDIO to HTTP transport using custom Docker containers. You’ll implement an Express server with proper CORS configuration, handle MCP requests at the /mcp endpoint, parse configuration from query parameters, and create a Dockerfile for containerized deployment. This approach gives you full control over your server environment and middleware while supporting both HTTP and STDIO transport modes.Need help? Join our Discord or email support@smithery.ai for assistance.