Server
Most should use ArcadeMCP — it handles transport and provides a simpler API. This page covers MCPServer, the low-level core for advanced use cases.
When to Use MCPServer
- Embedding — Add endpoints to an existing HTTP server
- Custom transports — WebSocket, IPC, or other non-standard transports
- Multi- — Isolated server instances with different /auth
- Testing — Instantiate without starting a transport
MCPServer
arcade-mcp-server.MCPServer
Low-level server with middleware and support.
This server provides:
- Middleware chain for request/response processing
- injection for
- Component managers for , resources, and prompts
- Direct control over server lifecycle
- Bidirectional communication with clients
Constructor
new MCPServer(options: MCPServerOptions)interface MCPServerOptions {
/** Collection of tools to serve */
catalog: ToolCatalog;
/** Server name */
name?: string;
/** Server version */
version?: string;
/** Human-readable title */
title?: string;
/** Usage instructions for AI clients */
instructions?: string;
/** Configuration object */
settings?: MCPSettings;
/** Request/response middleware */
middleware?: Middleware[];
/** Lifecycle manager for startup/shutdown */
lifespan?: LifespanManager;
/** Disable auth (development only, never in production) */
authDisabled?: boolean;
/** Arcade API key */
arcadeApiKey?: string;
/** Arcade API URL */
arcadeApiUrl?: string;
}Defaults:
| Option | Default | Notes |
|---|---|---|
name | 'ArcadeMCP' | |
version | '1.0.0' | |
arcadeApiKey | process.env.ARCADE_API_KEY | |
arcadeApiUrl | 'https://api.arcade.dev' | |
authDisabled | false | ⚠️ Never enable in production |
Methods
start()
async start(): Promise<void>Initialize the server. Call before running a transport.
stop()
async stop(): Promise<void>Gracefully shut down the server. Waits for in-flight requests to complete.
handleMessage()
async handleMessage(
message: MCPMessage,
session?: ServerSession
): Promise<MCPMessage | undefined>Handle a single message. Used internally by transports.
runConnection()
async runConnection(
readStream: ReadableStream,
writeStream: WritableStream,
initOptions?: Record<string, unknown>
): Promise<void>Run a single connection using WHATWG Streams (not Node.js streams).
Properties
| Property | Type | Description |
|---|---|---|
tools | ToolManager | Runtime tool operations (add, remove, list) |
resources | ResourceManager | Runtime resource operations |
prompts | PromptManager | Runtime prompt operations |
Examples
stdio Transport
import { MCPServer, ToolCatalog, StdioTransport } from 'arcade-mcp-server';
import { z } from 'zod';
const catalog = new ToolCatalog();
catalog.add('ping', {
description: 'Health check',
input: z.object({}),
handler: () => 'pong',
});
const server = new MCPServer({
catalog,
name: 'example',
version: '1.0.0',
});
await server.start();
try {
const transport = new StdioTransport();
await transport.run(server);
} finally {
await server.stop();
}catalog.add() is the low-level API for MCPServer. It’s equivalent to app.tool() on ArcadeMCP — both use name as first argument, options second.
HTTP Transport
import { MCPServer, ToolCatalog, HTTPTransport } from 'arcade-mcp-server';
import { z } from 'zod';
const catalog = new ToolCatalog();
catalog.add('ping', {
description: 'Health check',
input: z.object({}),
handler: () => 'pong',
});
const server = new MCPServer({
catalog,
name: 'example',
version: '1.0.0',
});
await server.start();
try {
const transport = new HTTPTransport({ host: '0.0.0.0', port: 8000 });
await transport.run(server);
} finally {
await server.stop();
}With Middleware
import {
MCPServer,
ToolCatalog,
LoggingMiddleware,
ErrorHandlingMiddleware,
} from 'arcade-mcp-server';
const server = new MCPServer({
catalog: new ToolCatalog(),
middleware: [
new ErrorHandlingMiddleware({ maskErrorDetails: true }),
new LoggingMiddleware({ logLevel: 'DEBUG' }),
],
});Middleware runs in order. ErrorHandlingMiddleware first means it catches errors
from all subsequent middleware.
With Lifespan Manager
import { MCPServer, ToolCatalog, createLifespan } from 'arcade-mcp-server';
const lifespan = createLifespan({
async onStart(server) {
await db.connect();
},
async onStop(server) {
await db.disconnect();
},
});
const server = new MCPServer({
catalog: new ToolCatalog(),
lifespan,
});Runtime Tool Management
Add and remove from a running server:
import { MCPServer, ToolCatalog, tool } from 'arcade-mcp-server';
import { z } from 'zod';
const server = new MCPServer({ catalog: new ToolCatalog() });
await server.start();
// Create a tool object (tool() provides type inference)
const dynamicTool = tool({
description: 'Added at runtime',
input: z.object({ value: z.string() }),
handler: ({ input }) => `Got: ${input.value}`,
});
// Add it to the running server — name is first arg
await server.tools.add('dynamic-tool', dynamicTool);
// List all tools
const tools = await server.tools.list();
console.error(`Server has ${tools.length} tools`);
// Remove a tool by name
await server.tools.remove('dynamic-tool');Custom Transport
For WebSocket or other custom transports, implement a class that calls server.runConnection() with readable/writable Web Streams:
import { MCPServer } from 'arcade-mcp-server';
class CustomTransport {
async run(server: MCPServer) {
// Your transport creates read/write streams for each connection
// then passes them to the server:
//
// await server.runConnection(readableStream, writableStream);
//
// The server handles MCP message parsing and routing.
// Your transport handles I/O (WebSocket frames, TCP packets, etc.)
}
}See the SDK source for StdioTransport and HTTPTransport as complete reference implementations.