Skip to Content

Transport Modes

Transports define how your server talks to AI clients. Pick based on how you’re deploying:

TransportHow it worksUse for
stdioClient spawns your server as a subprocess, communicates via stdin/stdoutClaude Desktop, VS Code, Cursor, CLI tools
HTTPYour server runs standalone, clients connect via HTTP + SSECloud deployments, multiple clients, load balancers

Rule of thumb: Use stdio for desktop apps (they spawn your server). Use HTTP when your server runs independently.

stdio Transport

Communicates via standard input/output. The AI client spawns your server as a subprocess and pipes JSON-RPC messages through stdin/stdout.

Usage

TypeScript
import { ArcadeMCP } from 'arcade-mcp-server'; import { z } from 'zod'; const app = new ArcadeMCP({ name: 'my-server', version: '1.0.0' }); app.tool('ping', { description: 'Health check', input: z.object({}), handler: () => 'pong', }); app.run({ transport: 'stdio' });
Terminal
bun run server.ts

Claude Desktop Configuration

Recommended: Use the Arcade CLI

Install the Arcade CLI, then configure Claude Desktop:

Terminal
pip install arcade-cli # requires Python arcade configure claude

This configures Claude Desktop to run your server as a stdio subprocess.

Manual configuration:

Edit Claude Desktop’s config file:

PlatformPath
macOS~/Library/Application Support/Claude/claude_desktop_config.json
WindowsC:\Users\<you>\AppData\Roaming\Claude\claude_desktop_config.json
Linux~/.config/Claude/claude_desktop_config.json
JSON
{ "mcpServers": { "my-tools": { "command": "bun", "args": ["run", "/path/to/server.ts"], "cwd": "/path/to/your/tools" } } }

With stdio, all stdout is protocol data. Use console.error() for logging, never console.log().

HTTP Transport

REST API with Server-Sent Events (SSE) for streaming. Built on Elysia .

Usage

TypeScript
import { ArcadeMCP } from 'arcade-mcp-server'; import { z } from 'zod'; const app = new ArcadeMCP({ name: 'my-server', version: '1.0.0' }); app.tool('ping', { description: 'Health check', input: z.object({}), handler: () => 'pong', }); app.run({ transport: 'http', host: '0.0.0.0', port: 8080 });

How HTTP Transport Works

The client-server flow:

  1. Request/Response: Client POSTs JSON-RPC to /mcp, gets a JSON response. This works independently — no SSE connection required.
  2. Real-time updates (optional): For progress updates or logs during long-running , clients open an SSE stream via GET /mcp before making POST requests. The SDK routes progress.report() and log.info() calls to this stream.
  3. Session cleanup: Client can DELETE /mcp to end a session (for multi-client scenarios with session tracking)

You just write handlers and return values. The SDK handles protocol wiring — you don’t need to manage SSE connections manually.

SSE and Progress Updates

When you call progress.report() or log.info() in your handler, the SDK automatically streams those updates to the client via SSE:

TypeScript
app.tool('processData', { description: 'Process a list of items with progress updates', input: z.object({ items: z.array(z.string()) }), handler: async ({ input, progress, log }) => { await log.info('Starting processing...'); for (let i = 0; i < input.items.length; i++) { await processItem(input.items[i]); await progress.report(i + 1, input.items.length); // Sent via SSE } return { processed: input.items.length }; }, });

The client receives these updates in real-time over the SSE stream. You don’t need to manage the streaming — just call the methods and the SDK routes them to the right connection.

Endpoints

EndpointMethodDescription
/worker/healthGETHealth check (returns 200 OK)
/mcpGETOpens an SSE stream for server-initiated messages (progress updates, logs). Clients keep this open to receive real-time updates during tool execution.
/mcpPOSTClient sends JSON-RPC requests (tool calls, list requests). Returns JSON response.
/mcpDELETEEnds a client session. Use when your client tracks session IDs across requests (e.g., cleanup when a browser tab closes). Most stdio clients don’t use this.

clients should include Accept: application/json, text/event-stream on POST requests. The SDK returns JSON for immediate responses and streams via SSE when needed.

Development Mode

Enable hot reload for faster development:

TypeScript
app.run({ transport: 'http', host: '127.0.0.1', port: 8000, reload: true, });

When reload: true, the SDK watches for file changes in your current working directory and restarts the server automatically when you save TypeScript, JavaScript, or .env files.

TLS / HTTPS

For production, use a reverse proxy (nginx, Caddy) for TLS termination. This is the recommended approach:

NGINX
# nginx example server { listen 443 ssl; ssl_certificate /path/to/cert.pem; ssl_certificate_key /path/to/key.pem; location / { proxy_pass http://127.0.0.1:8000; } }

For native TLS in Bun, see the Bun.serve TLS docs .

Docker

DOCKERFILE
FROM oven/bun:1 WORKDIR /app COPY package.json bun.lock ./ RUN bun install --frozen-lockfile COPY . . EXPOSE 8000 HEALTHCHECK --interval=30s --timeout=3s \ CMD bun -e "fetch('http://localhost:8000/worker/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))" CMD ["bun", "run", "server.ts"]
Terminal
docker build -t my-mcp-server . docker run -p 8000:8000 -e ARCADE_API_KEY=arc_... my-mcp-server

Environment Variables

Terminal
# Server identity MCP_SERVER_NAME="My MCP Server" MCP_SERVER_VERSION="1.0.0" # Logging MCP_MIDDLEWARE_LOG_LEVEL=DEBUG # Runtime overrides (override app.run() options) ARCADE_SERVER_TRANSPORT=http ARCADE_SERVER_HOST=0.0.0.0 ARCADE_SERVER_PORT=8080 ARCADE_SERVER_RELOAD=false

Access with Bun.env or process.env:

TypeScript
const port = Number(process.env.ARCADE_SERVER_PORT) || 8000;

Production Checklist

Before deploying an HTTP server to production, verify each item:

Security

  • HTTPS enabled — Use a reverse proxy (nginx, Caddy) or native TLS
  • Authentication — Require or tokens via Arcade or custom middleware
  • Rate limiting — Add rate limiting middleware to prevent abuse
  • Bind address — Use 127.0.0.1 for local-only, or firewall rules for public

Reliability

  • Error handling — Use specific error types (RetryableToolError, FatalToolError) — see Errors
  • Lifecycle hooks — Add onStart/onStop for database connections, cleanup
  • Health endpoint — Verify /worker/health returns 200 OK

Observability

  • Logging — Set MCP_MIDDLEWARE_LOG_LEVEL=INFO (or DEBUG for troubleshooting)
  • Metrics — Add middleware to track request counts, latencies
  • Error tracking — Integrate with Sentry, Datadog, or similar

Deployment

  • Environment variables — Store secrets in env vars, not code
  • Container health checks — Docker HEALTHCHECK or Kubernetes probes
  • Graceful shutdown — Server waits for in-flight requests on SIGTERM

For a complete production example, see Examples.

Security

stdio

  • Runs in the same process security as the parent (Claude Desktop, etc.)
  • No network ports opened
  • No additional auth needed — the parent process already trusts you

HTTP

HTTP transport opens network endpoints. Lock it down before deploying to production.

RiskMitigation
Unencrypted trafficUse HTTPS (reverse proxy or TLS config)
Unauthorized accessRequire auth tokens via Arcade or custom middleware
Abuse/DoSAdd rate limiting middleware
Open to the internetBind to 127.0.0.1 for local-only, or use firewall rules

Lifecycle Hooks

Hook into server startup, shutdown, and HTTP requests:

TypeScript
app.onStart(async () => { await db.connect(); console.error('Server ready'); }); app.onStop(async () => { await db.disconnect(); console.error('Server stopped'); }); // HTTP request logging (HTTP transport only) app.onRequest(({ request }) => { console.error(`${request.method} ${request.url}`); });
HookWhen it runsTransport
onStartBefore server accepts connectionsBoth
onStopDuring graceful shutdownBoth
onRequestEvery HTTP requestHTTP only

Transport Selection

Select transport based on environment or CLI args:

TypeScript
import { ArcadeMCP } from 'arcade-mcp-server'; import { z } from 'zod'; const app = new ArcadeMCP({ name: 'my-server', version: '1.0.0' }); app.tool('ping', { description: 'Health check', input: z.object({}), handler: () => 'pong', }); // Check command line args const transport = process.argv[2] === 'stdio' ? 'stdio' : 'http'; app.run({ transport, port: 8000 });
Terminal
# Run with HTTP bun run server.ts # Run with stdio (for Claude Desktop) bun run server.ts stdio
Last updated on

Transport Modes - Arcade MCP TypeScript Reference | Arcade Docs