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.
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.
defineServer({
transport: { type: 'sse', port: 3510 },
});SSE endpoints
| Method | Path | Description |
|---|---|---|
GET | /sse | Open SSE stream (creates new session) |
POST | /message?sessionId=xxx | Send message |
GET | / | Server status JSON |
OPTIONS | * | CORS preflight (auto-handled) |
Client connection flow:
GET /sse→ establishes SSE connection, assigns session IDPOST /message?sessionId=xxx→ sends MCP messages- On disconnect, session is automatically 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, OPTIONSAccess-Control-Allow-Headers: Content-Type
Use corsPlugin for custom configuration.
Streamable HTTP
Latest MCP transport spec. Single endpoint handles all communication.
defineServer({
transport: { type: 'http', port: 3510 },
});| Method | Description |
|---|---|
POST | MCP message handling |
GET | Server status JSON |
DELETE | Session 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).
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 + capabilitiestools/list→ tool schemas with annotationstools/call→ executes through the full middleware chainresources/list,prompts/list→ if registerednotifications/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:
defineServer({
transport: { type: 'auto', port: 3510 },
});Detection order:
MCP_TRANSPORTenv variable overrides everything (stdio,http,sse,workers)- Workers environment detected (globalThis.caches exists, no process.stdin) →
workers - stdin is not TTY →
stdio(MCP client spawned the process) - stdin is TTY →
http(developer running directly)
# 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.jsTransportConfig
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:
transport.port(explicit)dev.port(dev mode setting)3100(hardcoded default)
// 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' } }); // → 3100Which to use?
| Transport | Use case | Client |
|---|---|---|
stdio | Local tools, Claude Desktop direct | Claude Desktop, mcp-cli |
sse | Remote servers, existing MCP infra, mcp-proxy compat | Cursor, VS Code, mcp-proxy |
http | New deployments, Streamable HTTP spec, behind reverse proxy | Latest MCP clients |
workers | Cloudflare Workers edge deployment, serverless | PlayMCP, 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.