Skip to Content

Types

Core TypeScript types used in the SDK.

Most only need ToolContext. The other types are for advanced use cases like custom transports or protocol-level work.

Types marked with 📘 are not yet documented in the Python SDK reference (though most exist in the Python implementation). This helps track documentation parity.

Common Types

ToolContext<TInput, TSecrets, TAuth> 📘

passed to your handlers. Destructure what you need:

TypeScript
interface AuthInfo { token: string; /** Provider-specific user data (e.g., sub, email, name). Shape depends on provider and scopes. */ userInfo: Record<string, unknown>; } interface ToolContext< TInput, TSecrets extends string = string, TAuth extends boolean = false > { /** Validated input matching your Zod schema */ input: TInput; /** OAuth token and user info — non-optional when requiresAuth is set */ authorization: TAuth extends true ? AuthInfo : AuthInfo | undefined; /** * Get a secret value. Only keys declared in requiresSecrets are allowed. * Validated at startup — guaranteed to exist at runtime. */ getSecret(key: TSecrets): string; /** Metadata from Arcade auth (e.g., client_id, coordinator_url) */ metadata: Record<string, unknown>; /** User identifier for this request (from auth, config, or session) */ userId: string | null; /** Unique identifier for this MCP session (null for stdio without session management) */ sessionId: string | null; /** Unique identifier for this specific request */ requestId: string | null; // ─── Runtime Capabilities ───────────────────────────────────────── /** MCP protocol logging — sent to the AI client, not console */ log: { debug(message: string): Promise<void>; info(message: string): Promise<void>; warning(message: string): Promise<void>; error(message: string): Promise<void>; }; /** Progress reporting for long-running operations */ progress: { report(progress: number, total?: number, message?: string): Promise<void>; }; /** Read resources exposed by the server */ resources: { /** Read a resource, returns array (may have multiple parts) */ read(uri: string): Promise<ResourceContents[]>; /** Convenience: read and return first content item, throws if not found */ get(uri: string): Promise<ResourceContents>; /** List available resources */ list(): Promise<Resource[]>; /** List URI templates for dynamic resources */ listTemplates(): Promise<ResourceTemplate[]>; /** List client roots (directories the client has shared) */ listRoots(): Promise<Root[]>; }; /** Call other tools from within a tool handler */ tools: { /** Execute a tool and get raw result */ call(name: string, args?: Record<string, unknown>): Promise<CallToolResult>; /** List available tools */ list(): Promise<Tool[]>; }; /** Access prompts exposed by the server */ prompts: { get(name: string, args?: Record<string, string>): Promise<GetPromptResult>; list(): Promise<Prompt[]>; }; /** Request LLM completions from the client (if supported) */ sampling: { createMessage(request: CreateMessageRequest): Promise<CreateMessageResult>; }; /** Create rich UI elements (elicitation, forms) */ ui: { /** * Request structured input from the user. * Schema must be an object with primitive properties only * (string, number, integer, boolean). String formats: email, uri, date, date-time. */ elicit<T extends z.ZodObject<any>>( message: string, schema: T, options?: { timeout?: number } ): Promise<ElicitResult<z.infer<T>>>; }; /** Notify client when server capabilities change */ notifications: { tools: { listChanged(): Promise<void> }; resources: { listChanged(): Promise<void> }; prompts: { listChanged(): Promise<void> }; }; } // ─── Supporting Types ─────────────────────────────────────────────── /** Content returned when reading a resource */ interface ResourceContents { uri: string; mimeType?: string; text?: string; blob?: string; // Base64-encoded binary } /** Resource metadata */ interface Resource { uri: string; name: string; description?: string; mimeType?: string; } /** URI template for dynamic resources */ interface ResourceTemplate { uriTemplate: string; name: string; description?: string; mimeType?: string; } /** Client root directory */ interface Root { uri: string; name?: string; } /** Tool metadata */ interface Tool { name: string; description?: string; inputSchema: JsonSchema; } /** Prompt metadata */ interface Prompt { name: string; description?: string; arguments?: PromptArgument[]; } /** Result from getting a prompt */ interface GetPromptResult { description?: string; messages: PromptMessage[]; } /** Request for LLM sampling */ interface CreateMessageRequest { messages: SamplingMessage[]; systemPrompt?: string; maxTokens: number; temperature?: number; stopSequences?: string[]; } /** Message for/from sampling */ interface SamplingMessage { role: 'user' | 'assistant'; content: TextContent | ImageContent | AudioContent; } /** Result from sampling.createMessage */ interface CreateMessageResult { role: 'assistant'; content: TextContent | ImageContent | AudioContent; model: string; stopReason?: 'endTurn' | 'stopSequence' | 'maxTokens'; } /** Result from tools.call */ interface CallToolResult { content: ContentItem[]; structuredContent?: Record<string, unknown>; isError?: boolean; } /** Result from elicitation UI */ interface ElicitResult<T> { action: 'accept' | 'decline' | 'cancel'; content?: T; } /** JSON Schema (subset used by MCP) */ type JsonSchema = Record<string, unknown>; /** Prompt argument definition */ interface PromptArgument { name: string; description?: string; required?: boolean; } /** Message in a prompt */ interface PromptMessage { role: 'user' | 'assistant'; content: TextContent | ImageContent | AudioContent | EmbeddedResource | ResourceLink; } /** Text content block */ interface TextContent { type: 'text'; text: string; } /** Image content block */ interface ImageContent { type: 'image'; data: string; // Base64 encoded mimeType: string; } /** Audio content block */ interface AudioContent { type: 'audio'; data: string; // Base64 encoded mimeType: string; } /** Embedded resource content */ interface EmbeddedResource { type: 'resource'; resource: ResourceContents; } /** Link to a resource by URI */ interface ResourceLink { type: 'resource_link'; uri: string; name: string; description?: string; mimeType?: string; }

Example — Type-safe authorization:

TypeScript
import { z } from 'zod'; import { Google } from 'arcade-mcp-server/auth'; // Without requiresAuth: authorization is optional app.tool('publicTool', { input: z.object({ query: z.string() }), handler: ({ authorization }) => { authorization?.token; // Must use optional chaining }, }); // With requiresAuth: authorization is guaranteed app.tool('privateTool', { input: z.object({ query: z.string() }), requiresAuth: Google({ scopes: ['profile'] }), handler: ({ authorization }) => { authorization.token; // ✅ No optional chaining needed }, });

Example — Type-safe secrets:

TypeScript
app.tool('search', { input: z.object({ query: z.string() }), requiresSecrets: ['API_KEY'] as const, // as const is required! handler: ({ getSecret }) => { getSecret('API_KEY'); // ✅ Autocomplete works // getSecret('OTHER'); // ❌ TypeScript error }, });

Why as const? Without it, TypeScript infers string[] instead of the literal tuple ['API_KEY']. The as const preserves the exact strings so getSecret() can type-check against them.

Secrets are validated at startup. Missing secrets fail fast with a clear error.

Runtime Capabilities 📘

Tools have access to 8 runtime capabilities via the object. Destructure what you need:

TypeScript
app.tool('processData', { input: z.object({ uri: z.string() }), handler: async ({ input, log, // MCP protocol logging progress, // Progress reporting resources, // Read server resources tools, // Call other tools prompts, // Access prompts sampling, // Request LLM completions ui, // Elicitation / forms notifications // Notify list changes }) => { // Log progress to the client await log.info('Starting...'); await progress.report(0, 3); // Read a resource (get() returns single item, read() returns array) const content = await resources.get(input.uri); await progress.report(1, 3); // Call another tool (returns CallToolResult) const result = await tools.call('analyze', { data: content.text }); if (result.isError) throw new Error('Analysis failed'); await progress.report(2, 3); // Ask user for confirmation via elicitation const confirm = await ui.elicit('Proceed?', z.object({ confirmed: z.boolean(), })); if (confirm.action !== 'accept' || !confirm.content?.confirmed) { return 'Cancelled by user'; } await progress.report(3, 3, 'Done'); return result.structuredContent ?? result.content; }, });
CapabilityDescription
logSend debug/info/warning/error messages to the client
progressReport progress for long-running operations
resourcesRead resources exposed by the server
toolsCall other tools from within a tool handler
promptsAccess prompt templates defined on the server
samplingRequest LLM completions from the client
uiCreate elicitation forms for user input
notificationsNotify client when tools/resources/prompts change

Not all clients support all capabilities. sampling and ui.elicit depend on client support.

Elicitation Schema Restrictions: limits elicitation schemas to object types with primitive properties only (string, number, integer, boolean). String properties support formats: email, uri, date, date-time. Nested objects and arrays are not allowed.

Type Inference with Zod 📘

The SDK fully leverages Zod’s type inference. Your handler receives typed input automatically:

TypeScript
import { z } from 'zod'; const searchInput = z.object({ query: z.string(), limit: z.number().int().min(1).max(100).default(10), filters: z.object({ category: z.enum(['all', 'docs', 'code']).optional(), after: z.coerce.date().optional(), // Coerces ISO strings to Date }).optional(), }); app.tool('search', { input: searchInput, handler: ({ input }) => { // TypeScript knows: // - input.query is string // - input.limit is number (default applied) // - input.filters?.category is 'all' | 'docs' | 'code' | undefined // - input.filters?.after is Date | undefined return search(input); }, });

Tool Response Types 📘

Handlers can return any value. The SDK auto-wraps:

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

ContentItem 📘

For full control over responses, return content items directly:

TypeScript
type ContentItem = | TextContent | ImageContent | AudioContent | EmbeddedResource | ResourceLink;

Individual content types (TextContent, ImageContent, etc.) are defined in the ToolContext section above.

Example:

TypeScript
import { z } from 'zod'; app.tool('screenshot', { input: z.object({}), handler: async () => { const screenshot = await captureScreen(); return { content: [{ type: 'image', data: screenshot.toBase64(), mimeType: 'image/png', }], }; }, });

CallToolResult

The complete result structure:

TypeScript
interface CallToolResult { /** Content items to return to the client */ content: ContentItem[]; /** Optional structured data (for programmatic access) */ structuredContent?: Record<string, unknown>; /** Whether this result represents an error */ isError?: boolean; }

Important: When isError: true, the AI client knows the failed and can decide whether to retry. Always set this flag when returning error information — don’t just return an error message as regular content.

Returning errors correctly:

TypeScript
app.tool('fetchUser', { input: z.object({ id: z.string() }), handler: async ({ input }) => { const user = await db.findUser(input.id); if (!user) { // ✅ Correct: Set isError so AI knows this failed return { content: [{ type: 'text', text: `User ${input.id} not found` }], isError: true, }; } return user; }, });

For most error cases, throw specific error types instead — see Errors.

Schema Types 📘

The SDK uses Zod 4 for input validation.

Converting Schemas to JSON Schema 📘

Use z.toJSONSchema() to convert Zod schemas for AI clients:

TypeScript
import { z } from 'zod'; const schema = z.object({ firstName: z.string().describe('Your first name'), lastName: z.string().meta({ title: 'last_name' }), age: z.number().meta({ examples: [12, 99] }), }); z.toJSONSchema(schema); // => { // type: 'object', // properties: { // firstName: { type: 'string', description: 'Your first name' }, // lastName: { type: 'string', title: 'last_name' }, // age: { type: 'number', examples: [12, 99] } // }, // required: ['firstName', 'lastName', 'age'] // }

Adding Metadata 📘

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

TypeScript
// Simple description (Zod 3 compatible) z.string().describe('User email address'); // Rich metadata (Zod 4) z.string().meta({ id: 'email_address', title: 'Email', description: 'User email address', examples: ['user@example.com'], }); // .describe() is shorthand for .meta({ description: ... }) z.string().describe('An email'); // equivalent to: z.string().meta({ description: 'An email' });

Both are preserved in JSON Schema output.

Extracting TypeScript Types 📘

Use z.infer to extract TypeScript types from schemas:

TypeScript
import { z } from 'zod'; const userSchema = z.object({ id: z.string().uuid(), name: z.string(), email: z.string().email(), role: z.enum(['admin', 'user', 'guest']), }); // Extract the type type User = z.infer<typeof userSchema>; // Use in your code function processUser(user: User) { // user.id is string // user.role is 'admin' | 'user' | 'guest' }

Protocol Types 📘

These are low-level types for custom transports or debugging.

MCPMessage 📘

Base type for all protocol messages:

TypeScript
type MCPMessage = JSONRPCRequest | JSONRPCResponse | JSONRPCNotification; interface JSONRPCRequest { jsonrpc: '2.0'; id: string | number; method: string; params?: Record<string, unknown>; } interface JSONRPCResponse { jsonrpc: '2.0'; id: string | number; result?: unknown; error?: { code: number; message: string; data?: unknown; }; } interface JSONRPCNotification { jsonrpc: '2.0'; method: string; params?: Record<string, unknown>; }

ServerSession 📘

TypeScript
interface ServerSession { /** Unique session identifier */ id: string; /** Client information (if provided during initialization) */ clientInfo?: { name: string; version: string; }; /** Client capabilities (tools, resources, prompts support) */ capabilities?: Record<string, unknown>; }

SessionMessage

Also documented in Python SDK
TypeScript
interface SessionMessage { message: MCPMessage; session: ServerSession; }

Auth Types 📘

AuthProvider 📘

TypeScript
interface AuthProvider { /** Provider identifier (e.g., 'google', 'github') */ provider: string; /** OAuth scopes required */ scopes: readonly string[]; }

Creating Auth Providers 📘

TypeScript
import { Google, GitHub, Slack } from 'arcade-mcp-server/auth'; // Google with specific scopes Google({ scopes: ['profile', 'email'] }) // GitHub with repo access GitHub({ scopes: ['repo', 'user'] }) // Slack Slack({ scopes: ['chat:write', 'users:read'] })

Error Adapter Types 📘

ErrorAdapter 📘

Translates vendor-specific exceptions into Arcade errors:

TypeScript
interface ErrorAdapter { /** Identifier for logging/metrics */ slug: string; /** Translate an exception into an Arcade tool error, or null if not handled */ fromException(error: unknown): ToolError | null; } /** Base class for tool execution errors */ class ToolError extends Error { canRetry: boolean; statusCode?: number; developerMessage?: string; } /** Error from an upstream service (API, database, etc.) */ class UpstreamError extends ToolError {} /** Error that should not be retried */ class FatalToolError extends ToolError {} /** Error that can be retried */ class RetryableToolError extends ToolError { retryAfterMs?: number; }

Built-in Adapters 📘

TypeScript
import { SlackErrorAdapter, GoogleErrorAdapter, MicrosoftGraphErrorAdapter, HTTPErrorAdapter, GraphQLErrorAdapter, } from 'arcade-mcp-server/adapters'; app.tool('sendMessage', { input: z.object({ channel: z.string(), text: z.string() }), adapters: [new SlackErrorAdapter()], handler: async ({ input }) => { // SlackApiError → UpstreamError automatically await slack.chat.postMessage(input); return 'Sent!'; }, });
AdapterHandles
SlackErrorAdapterSlack SDK errors
GoogleErrorAdapterGoogle API Client errors
MicrosoftGraphErrorAdapterMicrosoft Graph SDK errors
HTTPErrorAdapterfetch/HTTP library errors
GraphQLErrorAdapterGraphQL client errors

Adapters are tried in order. First match wins. HTTPErrorAdapter is always added as fallback.

Utility Types 📘

MaterializedTool 📘

A object with type inference. Created via tool():

TypeScript
import { tool } from 'arcade-mcp-server'; import { z } from 'zod'; const myTool = tool({ description: 'Does something', input: z.object({ value: z.string() }), handler: ({ input }) => `Got: ${input.value}`, // input is typed }); // Register with app.tool() or runtime APIs — name is always first arg app.tool('my-tool', myTool); await server.tools.add('my-tool', myTool);

ToolOptions 📘

Complete configuration type:

TypeScript
interface ToolOptions< TInput, TSecrets extends string = string, TAuth extends AuthProvider | undefined = undefined > { description?: string; input: z.ZodType<TInput>; handler: ( context: ToolContext<TInput, TSecrets, TAuth extends AuthProvider ? true : false> ) => unknown | Promise<unknown>; requiresAuth?: TAuth; requiresSecrets?: readonly TSecrets[]; requiresMetadata?: string[]; adapters?: ErrorAdapter[]; }

The TAuth type parameter enables conditional typing: when requiresAuth is set, authorization becomes non-optional in the handler .

ArcadeMCPOptions 📘

TypeScript
interface ArcadeMCPOptions { name?: string; version?: string; title?: string; instructions?: string; logLevel?: 'DEBUG' | 'INFO' | 'WARNING' | 'ERROR'; transport?: 'stdio' | 'http'; host?: string; port?: number; reload?: boolean; adapter?: object; // For Node.js: node() from @elysiajs/node }
Last updated on

Types - Arcade MCP TypeScript Reference | Arcade Docs