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.

Quick Reference

You want to…Use
Access validated inputinput
Get OAuth tokenauthorization.token
Read a secretgetSecret('KEY')
Log to AI clientlog.info(), log.error()
Report progressprogress.report(current, total)
Read a resourceresources.get(uri)
Call another tooltools.call(name, args)
Ask user for inputui.elicit(message, schema)

All are properties of ToolContext, passed to your handler.

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