Skip to content

Transport ​

air supports three MCP transport types. Set via the transport config.

stdio ​

Standard input/output. Claude Desktop launches your server as a child process and communicates via stdin/stdout.

typescript
defineServer({
  transport: { type: 'stdio' },
});

No port needed. Logs are sent to stderr (stdout is reserved for MCP protocol).

SSE (Server-Sent Events) ​

HTTP-based remote connection. A separate MCP server instance is created per session. Since v0.4.0, air uses its own AirSSETransport with built-in heartbeat, reconnection recovery, and idle session cleanup.

typescript
defineServer({
  transport: { type: 'sse', port: 3510 },
});

SSE options (v0.4.0+) ​

OptionDefaultDescription
sseHeartbeatMs30000Ping interval to detect dead connections. 0 to disable
sseReplayBufferSize100Messages kept for Last-Event-ID reconnection
sseReplayTtlMs300000Replay buffer TTL (5 minutes)
sseIdleTimeoutMs600000Auto-close sessions idle for 10 minutes
maxSseSessions200Max concurrent SSE sessions (503 when exceeded)
typescript
defineServer({
  transport: { type: 'sse', port: 3510 },
  sseHeartbeatMs: 30_000,
  sseReplayBufferSize: 100,
  sseReplayTtlMs: 300_000,
  sseIdleTimeoutMs: 600_000,
  maxSseSessions: 200,
});

SSE endpoints ​

MethodPathDescription
GET/sseOpen SSE stream (creates new session)
POST/message?sessionId=xxxSend message
GET/Server status JSON
GET/healthHealth check with session count (v0.4.0+)
OPTIONS*CORS preflight (auto-handled)

Client connection flow:

  1. GET /sse → establishes SSE connection, assigns session ID
  2. POST /message?sessionId=xxx → sends MCP messages
  3. On disconnect, session is automatically cleaned up

Reconnection (v0.4.0+) ​

Every SSE message includes an id: field. When a client reconnects with Last-Event-ID header, missed messages are automatically replayed from the buffer.

// Client reconnects with header:
// Last-Event-ID: 42
// → Server replays messages 43, 44, 45, ...

Heartbeat (v0.4.0+) ​

A :ping comment is sent every sseHeartbeatMs to detect broken connections early. If the write fails, the session is immediately closed and cleaned up.

Terminal output:

[air] SSE server listening on port 3510
[air] SSE client connected (session: a1b2c3d4-...)
[air] SSE client disconnected (session: a1b2c3d4-...)

CORS ​

SSE transport sets CORS headers automatically:

  • Access-Control-Allow-Origin: *
  • Access-Control-Allow-Methods: GET, POST, OPTIONS
  • Access-Control-Allow-Headers: Content-Type

Use corsPlugin for custom configuration.

Streamable HTTP ​

Latest MCP transport spec. Single endpoint handles all communication.

typescript
defineServer({
  transport: { type: 'http', port: 3510 },
});
MethodDescription
POSTMCP message handling
GETServer status JSON
DELETESession termination

Each session gets a unique ID via crypto.randomUUID().

Cloudflare Workers ​

Edge deployment transport. No Node.js http server — instead, air exposes a fetch() handler compatible with the Workers runtime. MCP protocol is handled as JSON-RPC 2.0 directly (no SDK dependency needed at runtime).

typescript
import { defineServer, defineTool } from '@airmcp-dev/core';

const server = defineServer({
  name: 'my-edge-server',
  transport: { type: 'workers' },
  tools: [
    defineTool('hello', {
      params: { name: 'string' },
      handler: async ({ name }) => `Hello, ${name}!`,
    }),
  ],
});

export default {
  async fetch(request: Request, env: any): Promise<Response> {
    const url = new URL(request.url);

    // MCP endpoint
    if (request.method === 'POST' && url.pathname === '/') {
      return server.fetch!(request);
    }

    // Status
    if (request.method === 'GET' && url.pathname === '/') {
      return Response.json(server.status());
    }

    return new Response('Not Found', { status: 404 });
  },
};

server.fetch handles:

  • initialize → server info + capabilities (tools, resources, prompts)
  • tools/list → tool schemas with annotations
  • tools/call → executes through the full middleware chain
  • resources/list → list registered resources
  • resources/read → read resource content by URI (v0.4.0+)
  • prompts/list → list registered prompts
  • prompts/get → get prompt messages by name (v0.4.0+)
  • notifications/initialized → 204 No Content

TIP

Workers transport doesn't call server.start(). The server is ready immediately — just call server.fetch(request) in your Workers fetch handler.

WARNING

stdio and sse transports are not available in Workers. Only workers (and http for Node.js) support the Streamable HTTP protocol.

auto (default) ​

Omit type or set 'auto' to auto-detect based on environment:

typescript
defineServer({
  transport: { type: 'auto', port: 3510 },
});

Detection order:

  1. MCP_TRANSPORT env variable overrides everything (stdio, http, sse, workers)
  2. Workers environment detected (globalThis.caches exists, no process.stdin) → workers
  3. stdin is not TTY → stdio (MCP client spawned the process)
  4. stdin is TTY → http (developer running directly)
bash
# Force via env variable
MCP_TRANSPORT=sse node dist/index.js

# Piped → auto-detects stdio
echo '{}' | node dist/index.js

# Direct terminal → auto-detects http
node dist/index.js

TransportConfig ​

typescript
interface TransportConfig {
  type?: 'stdio' | 'sse' | 'http' | 'workers' | 'auto';  // Default: 'auto'
  port?: number;                               // HTTP/SSE port
  host?: string;                               // Default: 'localhost'
  basePath?: string;                           // Default: '/'
}

Port resolution order ​

Port is determined by:

  1. transport.port (explicit)
  2. dev.port (dev mode setting)
  3. 3100 (hardcoded default)
typescript
// transport.port takes priority
defineServer({ transport: { type: 'sse', port: 3510 } });  // → 3510

// Falls back to dev.port
defineServer({ transport: { type: 'sse' }, dev: { port: 4000 } });  // → 4000

// Falls back to 3100
defineServer({ transport: { type: 'sse' } });  // → 3100

Which to use? ​

TransportUse caseClient
stdioLocal tools, Claude Desktop directClaude Desktop, mcp-cli
sseRemote servers, existing MCP infra, mcp-proxy compatCursor, VS Code, mcp-proxy
httpNew deployments, Streamable HTTP spec, behind reverse proxyLatest MCP clients
workersCloudflare Workers edge deployment, serverlessPlayMCP, any MCP client via remote URL

TIP

When deploying behind a reverse proxy (Nginx, Cloudflare), use http transport. SSE requires special proxy config for long-lived connections (proxy_read_timeout 86400).

INFO

With stdio transport, all console.log output can mix into the MCP protocol stream. air's builtin logger automatically redirects to stderr.

Released under the Apache-2.0 License.