Skip to Content
API & SDKsArcade MCPTypeScriptOverview

Arcade MCP (MCP Server SDK) - TypeScript Overview

arcade-mcp, the secure framework for building servers, provides a clean, minimal API to build programmatically. It handles collection, server configuration, and transport setup with a developer-friendly interface.

Installation

Terminal
bun add arcade-mcp

tsconfig: Run bun init to generate one, or ensure yours has these essentials:

JSON
{ "compilerOptions": { "strict": true, "moduleResolution": "bundler", "target": "ESNext", "module": "Preserve" } }

See Bun TypeScript docs  for the complete recommended config.

Imports

TypeScript
// Main exports import { MCPApp, MCPServer, tool } from 'arcade-mcp'; import { NotFoundError, RetryableToolError, FatalToolError } from 'arcade-mcp'; // Auth providers import { Google, GitHub, Slack } from 'arcade-mcp/auth'; // Error adapters import { SlackErrorAdapter, GoogleErrorAdapter } from 'arcade-mcp/adapters';

Quick Start

TypeScript
// server.ts import { MCPApp } from 'arcade-mcp'; import { z } from 'zod'; const app = new MCPApp({ name: 'my-server', version: '1.0.0' }); app.tool('greet', { description: 'Greet a person by name', input: z.object({ name: z.string().describe('The name of the person to greet'), }), handler: ({ input }) => `Hello, ${input.name}!`, }); app.run({ transport: 'http', port: 8000, reload: true });
Terminal
bun run server.ts

Test it works:

Terminal
curl -X POST http://localhost:8000/mcp \ -H "Content-Type: application/json" \ -H "Accept: application/json, text/event-stream" \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'

When Claude (or another AI) calls your greet with { name: "Alex" }, your handler runs and returns the greeting.

Transport auto-detection: stdio for Claude Desktop, HTTP when you specify host/port. The reload: true option enables hot reload during development.

API Reference

MCPApp

arcade-mcp.MCPApp

An Elysia-powered interface for building servers. Handles registration, configuration, and transport.

Constructor

TypeScript
new MCPApp(options?: MCPAppOptions)
TypeScript
interface MCPAppOptions { /** Server name shown to AI clients */ name?: string; /** Server version */ version?: string; /** Human-readable title */ title?: string; /** Usage instructions for AI clients */ instructions?: string; /** Logging level */ logLevel?: 'DEBUG' | 'INFO' | 'WARNING' | 'ERROR'; /** Transport type: 'stdio' for Claude Desktop, 'http' for web */ transport?: 'stdio' | 'http'; /** HTTP host (auto-selects HTTP transport if set) */ host?: string; /** HTTP port (auto-selects HTTP transport if set) */ port?: number; /** Hot reload on file changes (development only) */ reload?: boolean; }

Defaults:

OptionDefaultNotes
name'ArcadeMCP'
version'1.0.0'
logLevel'INFO'
transport'stdio'Auto-switches to 'http' if host/port set
host'127.0.0.1'
port8000

app.tool()

Register a that AI clients can call.

TypeScript
app.tool(name: string, options: ToolOptions): void
TypeScript
interface ToolOptions< TInput, TSecrets extends string = string, TAuth extends AuthProvider | undefined = undefined > { /** Tool description for AI clients */ description?: string; /** Zod schema for input validation */ input: z.ZodType<TInput>; /** Tool handler function — authorization is non-optional when requiresAuth is set */ handler: ( context: ToolContext<TInput, TSecrets, TAuth extends AuthProvider ? true : false> ) => unknown | Promise<unknown>; /** OAuth provider for user authentication */ requiresAuth?: TAuth; /** Secret keys required by this tool (use `as const` for type safety) */ requiresSecrets?: readonly TSecrets[]; /** Metadata keys required from the client */ requiresMetadata?: string[]; /** Error adapters for translating upstream errors (e.g., Slack, Google APIs) */ adapters?: ErrorAdapter[]; }

Handler (destructure what you need):

TypeScript
handler: ({ input, authorization, getSecret, getMetadata }) => { // input — Validated input matching your Zod schema // authorization — OAuth token/provider (TypeScript narrows to non-optional when requiresAuth is set) // getSecret — Retrieve secrets (type-safe with `as const`) // getMetadata — Retrieve metadata from the client }

Return values are auto-wrapped:

Return typeBecomes
string{ content: [{ type: 'text', text: '...' }] }
object{ content: [{ type: 'text', text: JSON.stringify(...) }] }
{ content: [...] }Passed through unchanged

app.run()

Start the server.

TypeScript
app.run(options?: RunOptions): Promise<void>
TypeScript
interface RunOptions { transport?: 'stdio' | 'http'; host?: string; port?: number; reload?: boolean; }

app.addToolsFromModule()

Add all from a module at once. Use the standalone tool() function to define exportable tools, then addToolsFromModule() discovers and registers them:

TypeScript
// tools/math.ts import { tool } from 'arcade-mcp'; import { z } from 'zod'; export const add = tool({ description: 'Add two numbers', input: z.object({ a: z.number(), b: z.number() }), handler: ({ input }) => input.a + input.b, }); export const multiply = tool({ description: 'Multiply two numbers', input: z.object({ a: z.number(), b: z.number() }), handler: ({ input }) => input.a * input.b, });
TypeScript
// server.ts import * as mathTools from './tools/math'; app.addToolsFromModule(mathTools);

names are inferred from export names (add, multiply). Override with explicit name if needed:

TypeScript
export const calculator = tool({ name: 'basic-calculator', // explicit name overrides 'calculator' description: 'Basic arithmetic', input: z.object({ operation: z.enum(['add', 'subtract', 'multiply', 'divide']), a: z.number(), b: z.number(), }), handler: ({ input }) => { const { operation, a, b } = input; switch (operation) { case 'add': return a + b; case 'subtract': return a - b; case 'multiply': return a * b; case 'divide': return a / b; } }, });

Runtime APIs

After the server starts, you can modify , prompts, and resources at runtime:

TypeScript
import { tool } from 'arcade-mcp'; import { z } from 'zod'; // Create and add a tool at runtime const dynamicTool = tool({ name: 'dynamic-tool', // name required for runtime registration description: 'Added at runtime', input: z.object({ value: z.string() }), handler: ({ input }) => `Got: ${input.value}`, }); await app.tools.add(dynamicTool); // Remove a tool await app.tools.remove('dynamic-tool'); // List all tools const tools = await app.tools.list();
TypeScript
// Add a prompt (reusable message templates for AI clients) await app.prompts.add(prompt, handler); // Add a resource (files, data, or content the AI can read) await app.resources.add(resource);

See the Server reference for full prompts and resources API.

Examples

Simple Tool

TypeScript
import { MCPApp } from 'arcade-mcp'; import { z } from 'zod'; const app = new MCPApp({ name: 'example-server' }); app.tool('echo', { description: 'Echo the text back', input: z.object({ text: z.string().describe('The text to echo'), }), handler: ({ input }) => `Echo: ${input.text}`, }); app.run({ transport: 'http', host: '0.0.0.0', port: 8000 });

With OAuth and Secrets

Use requiresAuth when your tool needs to act on behalf of a user (e.g., access their Google ). Use requiresSecrets for your server needs.

TypeScript
import { MCPApp } from 'arcade-mcp'; import { Google } from 'arcade-mcp/auth'; import { z } from 'zod'; const app = new MCPApp({ name: 'my-server' }); app.tool('getProfile', { description: 'Get user profile from Google', input: z.object({ userId: z.string(), }), requiresAuth: Google({ scopes: ['profile'] }), requiresSecrets: ['API_KEY'] as const, // as const enables type-safe getSecret() handler: async ({ input, authorization, getSecret }) => { const token = authorization.token; // User's OAuth token const apiKey = getSecret('API_KEY'); // ✅ Type-safe, autocomplete works // getSecret('OTHER'); // ❌ TypeScript error: not in requiresSecrets return { userId: input.userId }; }, }); app.run();

Never log or return secrets. The SDK ensures secrets stay server-side.

With Required Metadata

Request metadata from the client:

TypeScript
app.tool('contextAware', { description: 'A tool that uses client context', input: z.object({ query: z.string() }), requiresMetadata: ['sessionId', 'userAgent'], handler: ({ input, getMetadata }) => { const sessionId = getMetadata('sessionId'); // string | undefined return `Processing ${input.query} for session ${sessionId}`; }, });

Schema Metadata for AI Clients

Use .describe() for simple descriptions. Use .meta() for richer metadata:

TypeScript
app.tool('search', { description: 'Search the knowledge base', input: z.object({ query: z.string().describe('Search query'), limit: z.number() .int() .min(1) .max(100) .default(10) .meta({ title: 'Result limit', examples: [10, 25, 50], }), }), handler: ({ input }) => searchKnowledgeBase(input.query, input.limit), });

Both .describe() and .meta() are preserved when the SDK converts schemas to JSON Schema for AI clients via z.toJSONSchema().

Async Tool with Error Handling

TypeScript
import { MCPApp, NotFoundError } from 'arcade-mcp'; import { z } from 'zod'; const app = new MCPApp({ name: 'api-server' }); app.tool('getUser', { description: 'Fetch a user by ID', input: z.object({ id: z.string().uuid().describe('User ID'), }), handler: async ({ input }) => { const user = await db.users.find(input.id); if (!user) { throw new NotFoundError(`User ${input.id} not found`); } return user; }, });

Full Example with All Features

TypeScript
import { MCPApp } from 'arcade-mcp'; import { Google } from 'arcade-mcp/auth'; import { z } from 'zod'; const app = new MCPApp({ name: 'full-example', version: '1.0.0', instructions: 'Use these tools to manage documents.', logLevel: 'DEBUG', }); // Simple tool app.tool('ping', { description: 'Health check', input: z.object({}), handler: () => 'pong', }); // Complex tool with auth and secrets app.tool('createDocument', { description: 'Create a new document in Google Drive', input: z.object({ title: z.string().min(1).describe('Document title'), content: z.string().describe('Document content'), folder: z.string().optional().describe('Parent folder ID'), }), requiresAuth: Google({ scopes: ['drive.file'] }), requiresSecrets: ['DRIVE_API_KEY'] as const, handler: async ({ input, authorization, getSecret }) => { const response = await createDriveDocument({ token: authorization.token, apiKey: getSecret('DRIVE_API_KEY'), ...input, }); return { documentId: response.id, url: response.webViewLink }; }, }); // Start server if (import.meta.main) { app.run({ transport: 'http', host: '0.0.0.0', port: 8000 }); }
Terminal
bun run server.ts
Last updated on

Arcade MCP (MCP Server SDK) - TypeScript Overview | Arcade Docs