/**
* Shared tool registry - single source of truth for all tools.
* Tools defined here work in both Node.js and Cloudflare Workers.
*/
import type { ZodObject, ZodRawShape } from 'zod';
import type { ToolContext, ToolResult } from './types.js';
// Re-export types for convenience
export type { SharedToolDefinition, ToolContext, ToolResult } from './types.js';
export { defineTool } from './types.js';
/**
* Simplified tool interface for the registry (type-erased for storage).
*/
export interface RegisteredTool {
name: string;
title?: string;
description: string;
inputSchema: ZodObject<ZodRawShape>;
outputSchema?: ZodRawShape;
annotations?: Record<string, unknown>;
handler: (args: Record<string, unknown>, context: ToolContext) => Promise<ToolResult>;
}
import { checkAvailabilityTool } from './check-availability.js';
import { createEventTool } from './create-event.js';
import { deleteEventTool } from './delete-event.js';
// Import all tools
import { listCalendarsTool } from './list-calendars.js';
import { respondToEventTool } from './respond-to-event.js';
import { searchEventsTool } from './search-events.js';
import { updateEventTool } from './update-event.js';
/**
* All shared tools available in both runtimes.
* Add new tools here to make them available everywhere.
*/
export const sharedTools: RegisteredTool[] = [
listCalendarsTool as unknown as RegisteredTool,
searchEventsTool as unknown as RegisteredTool,
checkAvailabilityTool as unknown as RegisteredTool,
createEventTool as unknown as RegisteredTool,
updateEventTool as unknown as RegisteredTool,
deleteEventTool as unknown as RegisteredTool,
respondToEventTool as unknown as RegisteredTool,
];
/**
* Get a tool by name.
*/
export function getSharedTool(name: string): RegisteredTool | undefined {
return sharedTools.find((t) => t.name === name);
}
/**
* Get all tool names.
*/
export function getSharedToolNames(): string[] {
return sharedTools.map((t) => t.name);
}
/**
* Execute a shared tool by name.
* Handles input validation, output validation, and error wrapping.
*
* Per MCP spec: When outputSchema is defined, structuredContent is required
* (unless isError is true). The SDK validates this automatically for Node,
* and we replicate that behavior here for Workers.
*/
export async function executeSharedTool(
name: string,
args: Record<string, unknown>,
context: ToolContext,
): Promise<ToolResult> {
const tool = getSharedTool(name);
if (!tool) {
return {
content: [{ type: 'text', text: `Unknown tool: ${name}` }],
isError: true,
};
}
try {
// Check for cancellation before starting
if (context.signal?.aborted) {
return {
content: [{ type: 'text', text: 'Operation was cancelled' }],
isError: true,
};
}
// Validate input using Zod schema
const parseResult = tool.inputSchema.safeParse(args);
if (!parseResult.success) {
const errors = parseResult.error.errors
.map(
(e: { path: (string | number)[]; message: string }) =>
`${e.path.join('.')}: ${e.message}`,
)
.join(', ');
return {
content: [{ type: 'text', text: `Invalid input: ${errors}` }],
isError: true,
};
}
const result = await tool.handler(
parseResult.data as Record<string, unknown>,
context,
);
// Validate outputSchema compliance (per MCP spec)
// When outputSchema is defined, structuredContent is required unless isError is true
if (tool.outputSchema && !result.isError) {
if (!result.structuredContent) {
return {
content: [
{
type: 'text',
text: 'Tool with outputSchema must return structuredContent (unless isError is true)',
},
],
isError: true,
};
}
// Note: Full Zod validation of structuredContent against outputSchema
// could be added here if needed for stricter compliance
}
return result;
} catch (error) {
// Check if this was an abort
if (context.signal?.aborted) {
return {
content: [{ type: 'text', text: 'Operation was cancelled' }],
isError: true,
};
}
return {
content: [{ type: 'text', text: `Tool error: ${(error as Error).message}` }],
isError: true,
};
}
}