From 71c6e5fc213157ca402dbaa4131b6fae17474d81 Mon Sep 17 00:00:00 2001 From: ryoppippi <1560508+ryoppippi@users.noreply.github.com> Date: Tue, 6 Jan 2026 17:02:26 +0000 Subject: [PATCH 01/11] chore(deps): add @anthropic-ai/claude-agent-sdk as peer dependency Add Claude Agent SDK as an optional peer dependency to support the new toClaudeAgentSdk() method. This allows users to convert StackOne tools to Claude Agent SDK format without manual wrapping. The package is marked as optional in peerDependenciesMeta, so users only need to install it when using Claude Agent SDK integration. --- package.json | 4 ++++ pnpm-lock.yaml | 6 ++++++ pnpm-workspace.yaml | 1 + 3 files changed, 11 insertions(+) diff --git a/package.json b/package.json index 1bcb316..6882cd0 100644 --- a/package.json +++ b/package.json @@ -81,12 +81,16 @@ "zod": "catalog:dev" }, "peerDependencies": { + "@anthropic-ai/claude-agent-sdk": "catalog:peer", "@anthropic-ai/sdk": "catalog:peer", "ai": "catalog:peer", "openai": "catalog:peer", "zod": "catalog:peer" }, "peerDependenciesMeta": { + "@anthropic-ai/claude-agent-sdk": { + "optional": true + }, "@anthropic-ai/sdk": { "optional": true }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bafca89..ae2e51c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -83,6 +83,9 @@ catalogs: specifier: ^0.2.0 version: 0.2.0 peer: + '@anthropic-ai/claude-agent-sdk': + specifier: ^0.1.67 + version: 0.1.67 '@anthropic-ai/sdk': specifier: ^0.52.0 version: 0.52.0 @@ -107,6 +110,9 @@ importers: .: dependencies: + '@anthropic-ai/claude-agent-sdk': + specifier: catalog:peer + version: 0.1.67(zod@4.1.13) '@anthropic-ai/sdk': specifier: catalog:peer version: 0.52.0 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 1a6c0b3..c996343 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -33,6 +33,7 @@ catalogs: '@tanstack/ai': ^0.2.0 '@tanstack/ai-openai': ^0.2.0 peer: + '@anthropic-ai/claude-agent-sdk': ^0.1.67 '@anthropic-ai/sdk': ^0.52.0 ai: '>=5.0.108 <7.0.0' openai: ^6.2.0 From 99dbcb29c1b81fea61482c38df9d0adc7aa7d024 Mon Sep 17 00:00:00 2001 From: ryoppippi <1560508+ryoppippi@users.noreply.github.com> Date: Tue, 6 Jan 2026 17:02:33 +0000 Subject: [PATCH 02/11] feat(utils): add JSON Schema to Zod conversion utility Add jsonSchemaToZod() function that converts JSON Schema definitions to Zod schemas at runtime. This is required for Claude Agent SDK integration, which expects Zod schemas for tool definitions. Supports converting: - Primitive types (string, number, boolean, null) - Complex types (object, array) - String formats (email, url, uuid, datetime) - Numeric constraints (min, max, integer) - Length constraints for strings and arrays - Enum values - Required/optional properties --- src/utils/json-schema-to-zod.ts | 209 ++++++++++++++++++++++++++++++++ 1 file changed, 209 insertions(+) create mode 100644 src/utils/json-schema-to-zod.ts diff --git a/src/utils/json-schema-to-zod.ts b/src/utils/json-schema-to-zod.ts new file mode 100644 index 0000000..369e3b0 --- /dev/null +++ b/src/utils/json-schema-to-zod.ts @@ -0,0 +1,209 @@ +/** + * Utility to convert JSON Schema to Zod schemas at runtime. + * This is used for Claude Agent SDK integration which requires Zod schemas. + */ + +import type { z, ZodTypeAny } from 'zod'; +import type { JSONSchema } from '../types'; +import { peerDependencies } from '../../package.json'; +import { tryImport } from './try-import'; + +/** + * Convert a JSON Schema to a Zod schema object. + * Returns a record of Zod types that can be used with the Claude Agent SDK tool() function. + * + * @param schema - JSON Schema object with properties + * @returns Promise resolving to a record of Zod types compatible with Claude Agent SDK + */ +export async function jsonSchemaToZod( + schema: JSONSchema, +): Promise<{ zodLib: typeof z; zodSchema: Record }> { + const zodLib = await tryImport( + 'zod', + `npm install zod (requires ${peerDependencies.zod})`, + ); + + const properties = schema.properties ?? {}; + const required = schema.required ?? []; + + const zodSchema: Record = {}; + + for (const [key, propSchema] of Object.entries(properties)) { + const isRequired = required.includes(key); + const zodType = convertPropertyToZod(zodLib, propSchema as JSONSchema, isRequired); + zodSchema[key] = zodType; + } + + return { zodLib, zodSchema }; +} + +/** + * Convert a single JSON Schema property to a Zod type. + */ +function convertPropertyToZod( + zodLib: typeof z, + schema: JSONSchema, + isRequired: boolean, +): ZodTypeAny { + let zodType: ZodTypeAny; + + // Handle type-based conversion + const schemaType = Array.isArray(schema.type) ? schema.type[0] : schema.type; + + switch (schemaType) { + case 'string': + zodType = createStringSchema(zodLib, schema); + break; + case 'number': + case 'integer': + zodType = createNumberSchema(zodLib, schema); + break; + case 'boolean': + zodType = zodLib.boolean(); + break; + case 'array': + zodType = createArraySchema(zodLib, schema); + break; + case 'object': + zodType = createObjectSchema(zodLib, schema); + break; + case 'null': + zodType = zodLib.null(); + break; + default: + // For unknown types or union types, use unknown + zodType = zodLib.unknown(); + } + + // Add description if present + if (schema.description) { + zodType = zodType.describe(schema.description); + } + + // Make optional if not required + if (!isRequired) { + zodType = zodType.optional(); + } + + return zodType; +} + +/** + * Create a Zod string schema with constraints. + */ +function createStringSchema(zodLib: typeof z, schema: JSONSchema): ZodTypeAny { + let stringSchema = zodLib.string(); + + // Handle enum values + if (schema.enum && schema.enum.length > 0) { + const enumValues = schema.enum.filter((v): v is string => typeof v === 'string'); + if (enumValues.length > 0) { + return zodLib.enum(enumValues as [string, ...string[]]); + } + } + + // Handle format + if (schema.format === 'email') { + stringSchema = stringSchema.email(); + } else if (schema.format === 'url' || schema.format === 'uri') { + stringSchema = stringSchema.url(); + } else if (schema.format === 'uuid') { + stringSchema = stringSchema.uuid(); + } else if (schema.format === 'date-time') { + stringSchema = stringSchema.datetime(); + } + + // Handle length constraints + if (typeof schema.minLength === 'number') { + stringSchema = stringSchema.min(schema.minLength); + } + if (typeof schema.maxLength === 'number') { + stringSchema = stringSchema.max(schema.maxLength); + } + + // Handle pattern + if (schema.pattern) { + stringSchema = stringSchema.regex(new RegExp(schema.pattern)); + } + + return stringSchema; +} + +/** + * Create a Zod number schema with constraints. + */ +function createNumberSchema(zodLib: typeof z, schema: JSONSchema): ZodTypeAny { + let numberSchema = zodLib.number(); + + // Handle enum values + if (schema.enum && schema.enum.length > 0) { + const enumValues = schema.enum.filter((v): v is number => typeof v === 'number'); + if (enumValues.length > 0) { + return zodLib.enum(enumValues.map(String) as [string, ...string[]]).transform(Number); + } + } + + // Handle range constraints + if (typeof schema.minimum === 'number') { + numberSchema = numberSchema.min(schema.minimum); + } + if (typeof schema.maximum === 'number') { + numberSchema = numberSchema.max(schema.maximum); + } + if (typeof schema.exclusiveMinimum === 'number') { + numberSchema = numberSchema.gt(schema.exclusiveMinimum); + } + if (typeof schema.exclusiveMaximum === 'number') { + numberSchema = numberSchema.lt(schema.exclusiveMaximum); + } + + // Handle integer type + if (schema.type === 'integer') { + numberSchema = numberSchema.int(); + } + + return numberSchema; +} + +/** + * Create a Zod array schema. + */ +function createArraySchema(zodLib: typeof z, schema: JSONSchema): ZodTypeAny { + let itemSchema: ZodTypeAny = zodLib.unknown(); + + if (schema.items && !Array.isArray(schema.items)) { + itemSchema = convertPropertyToZod(zodLib, schema.items as JSONSchema, true); + } + + let arraySchema = zodLib.array(itemSchema); + + // Handle length constraints + if (typeof schema.minItems === 'number') { + arraySchema = arraySchema.min(schema.minItems); + } + if (typeof schema.maxItems === 'number') { + arraySchema = arraySchema.max(schema.maxItems); + } + + return arraySchema; +} + +/** + * Create a Zod object schema. + */ +function createObjectSchema(zodLib: typeof z, schema: JSONSchema): ZodTypeAny { + if (!schema.properties) { + // Object with no defined properties - use record with string keys + return zodLib.record(zodLib.string(), zodLib.unknown()); + } + + const required = schema.required ?? []; + const shape: Record = {}; + + for (const [key, propSchema] of Object.entries(schema.properties)) { + const isRequired = required.includes(key); + shape[key] = convertPropertyToZod(zodLib, propSchema as JSONSchema, isRequired); + } + + return zodLib.object(shape); +} From 02b22279ca3fecba858f9f34bd5a58d094cff990 Mon Sep 17 00:00:00 2001 From: ryoppippi <1560508+ryoppippi@users.noreply.github.com> Date: Tue, 6 Jan 2026 17:02:43 +0000 Subject: [PATCH 03/11] feat(types): add Claude Agent SDK types Add ClaudeAgentSdkMcpServer interface and ClaudeAgentSdkOptions type to support the new toClaudeAgentSdk() method. ClaudeAgentSdkMcpServer represents the MCP server configuration that can be passed to the Claude Agent SDK query() function's mcpServers option. --- src/types.ts | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/types.ts b/src/types.ts index c8e10e3..f99ff28 100644 --- a/src/types.ts +++ b/src/types.ts @@ -201,3 +201,30 @@ export type AISDKToolDefinition = Tool & { export type AISDKToolResult = ToolSet & { [K in T]: AISDKToolDefinition; }; + +/** + * Claude Agent SDK MCP server configuration returned by toClaudeAgentSdk(). + * This type represents the MCP server instance that can be passed to the + * Claude Agent SDK query() function's mcpServers option. + * + * @see https://docs.anthropic.com/en/docs/agents-and-tools/claude-agent-sdk + */ +export interface ClaudeAgentSdkMcpServer { + type: 'sdk'; + name: string; + instance: unknown; +} + +/** + * Options for toClaudeAgentSdk() method + */ +export interface ClaudeAgentSdkOptions { + /** + * Name of the MCP server. Defaults to 'stackone-tools'. + */ + serverName?: string; + /** + * Version of the MCP server. Defaults to '1.0.0'. + */ + serverVersion?: string; +} From 450ff4f48d06531f8ce06107df1224bfda891993 Mon Sep 17 00:00:00 2001 From: ryoppippi <1560508+ryoppippi@users.noreply.github.com> Date: Tue, 6 Jan 2026 17:02:52 +0000 Subject: [PATCH 04/11] feat(tool): add toClaudeAgentSdk() method to Tools class Add toClaudeAgentSdk() method that converts all tools in a collection to Claude Agent SDK format, automatically creating an MCP server. This simplifies integration with Claude Agent SDK from requiring manual Zod schema definitions and MCP server setup to a single method call. The method: - Converts JSON Schema parameters to Zod schemas dynamically - Creates Claude Agent SDK tool definitions with handlers - Wraps tools in an MCP server via createSdkMcpServer() - Supports configurable server name and version Also adds toClaudeAgentSdkTool() helper method on BaseTool for converting individual tools. --- src/tool.ts | 90 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/src/tool.ts b/src/tool.ts index f574562..2419cd4 100644 --- a/src/tool.ts +++ b/src/tool.ts @@ -10,6 +10,8 @@ import { RequestBuilder } from './requestBuilder'; import type { AISDKToolDefinition, AISDKToolResult, + ClaudeAgentSdkMcpServer, + ClaudeAgentSdkOptions, ExecuteConfig, ExecuteOptions, HttpExecuteConfig, @@ -22,6 +24,7 @@ import type { } from './types'; import { StackOneError } from './utils/error-stackone'; +import { jsonSchemaToZod } from './utils/json-schema-to-zod'; import { TfidfIndex } from './utils/tfidf-index'; import { tryImport } from './utils/try-import'; @@ -225,6 +228,36 @@ export class BaseTool { }; } + /** + * Convert the tool to Claude Agent SDK format. + * Returns a tool definition compatible with the Claude Agent SDK's tool() function. + * + * @see https://docs.anthropic.com/en/docs/agents-and-tools/claude-agent-sdk + */ + async toClaudeAgentSdkTool(): Promise<{ + name: string; + description: string; + inputSchema: Record; + handler: ( + args: Record, + ) => Promise<{ content: Array<{ type: 'text'; text: string }> }>; + }> { + const { zodSchema } = await jsonSchemaToZod(this.toJsonSchema()); + const execute = this.execute.bind(this); + + return { + name: this.name, + description: this.description, + inputSchema: zodSchema, + handler: async (args: Record) => { + const result = await execute(args as JsonObject); + return { + content: [{ type: 'text' as const, text: JSON.stringify(result) }], + }; + }, + }; + } + /** * Convert the tool to AI SDK format */ @@ -395,6 +428,63 @@ export class Tools implements Iterable { return result; } + /** + * Convert all tools to Claude Agent SDK format. + * Returns an MCP server configuration that can be passed to the + * Claude Agent SDK query() function's mcpServers option. + * + * @example + * ```typescript + * const tools = await toolset.fetchTools(); + * const mcpServer = await tools.toClaudeAgentSdk(); + * + * const result = query({ + * prompt: 'Get employee info', + * options: { + * model: 'claude-sonnet-4-5-20250929', + * mcpServers: { + * 'stackone-tools': mcpServer, + * }, + * }, + * }); + * ``` + * + * @see https://docs.anthropic.com/en/docs/agents-and-tools/claude-agent-sdk + */ + async toClaudeAgentSdk(options: ClaudeAgentSdkOptions = {}): Promise { + const { serverName = 'stackone-tools', serverVersion = '1.0.0' } = options; + + // Import the Claude Agent SDK dynamically + const claudeAgentSdk = await tryImport( + '@anthropic-ai/claude-agent-sdk', + `npm install @anthropic-ai/claude-agent-sdk (requires ${peerDependencies['@anthropic-ai/claude-agent-sdk']})`, + ); + + // Convert all tools to Claude Agent SDK format + // We use type assertions here because the Zod types from our dynamic import + // don't perfectly match the Claude Agent SDK's expected types at compile time + const sdkTools = await Promise.all( + this.tools.map(async (baseTool) => { + const toolDef = await baseTool.toClaudeAgentSdkTool(); + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Dynamic Zod schema types + return claudeAgentSdk.tool( + toolDef.name, + toolDef.description, + toolDef.inputSchema as any, + toolDef.handler as any, + ); + }), + ); + + // Create and return the MCP server + // The return type is compatible with McpServerConfig but uses our interface + return claudeAgentSdk.createSdkMcpServer({ + name: serverName, + version: serverVersion, + tools: sdkTools, + }) as ClaudeAgentSdkMcpServer; + } + /** * Filter tools by a predicate function */ From b82da78b1c547a97d459427ab0dfb0522f0d2b8e Mon Sep 17 00:00:00 2001 From: ryoppippi <1560508+ryoppippi@users.noreply.github.com> Date: Tue, 6 Jan 2026 17:03:01 +0000 Subject: [PATCH 05/11] refactor(examples): use toClaudeAgentSdk() in Claude Agent SDK example Simplify the Claude Agent SDK example by using the new toClaudeAgentSdk() method instead of manual tool wrapping with Zod schemas. Before: Required importing tool(), createSdkMcpServer(), z from dependencies and manually defining Zod schemas for each tool. After: Single method call tools.toClaudeAgentSdk() handles all conversion and MCP server creation automatically. Also updates account ID placeholder to use generic 'hris' prefix for consistency with other examples. --- examples/claude-agent-sdk-integration.ts | 47 +++++++----------------- 1 file changed, 13 insertions(+), 34 deletions(-) diff --git a/examples/claude-agent-sdk-integration.ts b/examples/claude-agent-sdk-integration.ts index 5008f78..5e8fd22 100644 --- a/examples/claude-agent-sdk-integration.ts +++ b/examples/claude-agent-sdk-integration.ts @@ -3,12 +3,14 @@ * * Claude Agent SDK allows you to create autonomous agents with custom tools * via MCP (Model Context Protocol) servers. + * + * The `toClaudeAgentSdk()` method automatically converts StackOne tools + * to Claude Agent SDK format, handling the MCP server creation internally. */ import assert from 'node:assert'; import process from 'node:process'; -import { query, tool, createSdkMcpServer } from '@anthropic-ai/claude-agent-sdk'; -import { z } from 'zod'; +import { query } from '@anthropic-ai/claude-agent-sdk'; import { StackOneToolSet } from '@stackone/ai'; const apiKey = process.env.STACKONE_API_KEY; @@ -18,7 +20,7 @@ if (!apiKey) { } // Replace with your actual account ID from StackOne dashboard -const accountId = 'your-bamboohr-account-id'; +const accountId = 'your-hris-account-id'; const claudeAgentSdkIntegration = async (): Promise => { // Initialize StackOne @@ -27,42 +29,19 @@ const claudeAgentSdkIntegration = async (): Promise => { baseUrl: process.env.STACKONE_BASE_URL ?? 'https://api.stackone.com', }); - // Fetch tools from StackOne + // Fetch tools from StackOne and convert to Claude Agent SDK format const tools = await toolset.fetchTools(); + const mcpServer = await tools.toClaudeAgentSdk(); - // Get a specific tool - const employeeTool = tools.getTool('bamboohr_get_employee'); - assert(employeeTool !== undefined, 'Expected to find bamboohr_get_employee tool'); - - // Create a Claude Agent SDK tool from the StackOne tool - const getEmployeeTool = tool( - employeeTool.name, - employeeTool.description, - { - id: z.string().describe('The employee ID'), - }, - async (args) => { - const result = await employeeTool.execute(args); - return { - content: [{ type: 'text', text: JSON.stringify(result) }], - }; - }, - ); - - // Create an MCP server with the StackOne tool - const mcpServer = createSdkMcpServer({ - name: 'stackone-tools', - version: '1.0.0', - tools: [getEmployeeTool], - }); - - // Use the Claude Agent SDK query with the custom MCP server + // Use the Claude Agent SDK query with the StackOne MCP server + // Type assertion is needed because our interface is compatible but not identical const result = query({ prompt: 'Get the employee with id: c28xIQaWQ6MzM5MzczMDA2NzMzMzkwNzIwNA', options: { model: 'claude-sonnet-4-5-20250929', mcpServers: { - 'stackone-tools': mcpServer, + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Compatible MCP server type + 'stackone-tools': mcpServer as any, }, // Disable built-in tools, only use our custom tools tools: [], @@ -75,14 +54,14 @@ const claudeAgentSdkIntegration = async (): Promise => { for await (const message of result) { if (message.type === 'assistant') { for (const block of message.message.content) { - if (block.type === 'tool_use' && block.name === 'bamboohr_get_employee') { + if (block.type === 'tool_use' && block.name === 'hris_get_employee') { hasToolCall = true; } } } } - assert(hasToolCall, 'Expected at least one tool call to bamboohr_get_employee'); + assert(hasToolCall, 'Expected at least one tool call to hris_get_employee'); }; await claudeAgentSdkIntegration(); From 9847b7526e715bb6ae61bbea61d17263d9f6cfb3 Mon Sep 17 00:00:00 2001 From: ryoppippi <1560508+ryoppippi@users.noreply.github.com> Date: Tue, 6 Jan 2026 17:03:10 +0000 Subject: [PATCH 06/11] docs(readme): simplify Claude Agent SDK integration example Update the README to showcase the simplified toClaudeAgentSdk() API. The new example is much shorter and easier to follow: - Removed manual tool wrapping with Zod schemas - Removed createSdkMcpServer() setup code - Single method call now handles all conversion --- README.md | 24 +++--------------------- 1 file changed, 3 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 9a70533..e9ddd7e 100644 --- a/README.md +++ b/README.md @@ -281,8 +281,7 @@ npm install @stackone/ai @anthropic-ai/claude-agent-sdk zod # or: yarn/pnpm/bun ``` ```typescript -import { query, tool, createSdkMcpServer } from "@anthropic-ai/claude-agent-sdk"; -import { z } from "zod"; +import { query } from "@anthropic-ai/claude-agent-sdk"; import { StackOneToolSet } from "@stackone/ai"; const toolset = new StackOneToolSet({ @@ -290,26 +289,9 @@ const toolset = new StackOneToolSet({ accountId: "your-account-id", }); +// Fetch tools and convert to Claude Agent SDK format const tools = await toolset.fetchTools(); -const employeeTool = tools.getTool("bamboohr_get_employee"); - -// Create a Claude Agent SDK tool from the StackOne tool -const getEmployeeTool = tool( - employeeTool.name, - employeeTool.description, - { id: z.string().describe("The employee ID") }, - async (args) => { - const result = await employeeTool.execute(args); - return { content: [{ type: "text", text: JSON.stringify(result) }] }; - } -); - -// Create an MCP server with the StackOne tool -const mcpServer = createSdkMcpServer({ - name: "stackone-tools", - version: "1.0.0", - tools: [getEmployeeTool], -}); +const mcpServer = await tools.toClaudeAgentSdk(); // Use with Claude Agent SDK query const result = query({ From 45bd6538a466062bc22f2dd50a7519e4385b68cb Mon Sep 17 00:00:00 2001 From: ryoppippi <1560508+ryoppippi@users.noreply.github.com> Date: Tue, 6 Jan 2026 17:10:20 +0000 Subject: [PATCH 07/11] refactor(utils): use Zod's native z.fromJSONSchema() for JSON Schema conversion - Replace custom JSON Schema to Zod conversion with Zod v4.3+ native method - Update Zod peer dependency to require >=4.3.0 for fromJSONSchema support - Simplify json-schema-to-zod.ts from ~210 lines to ~50 lines --- pnpm-lock.yaml | 88 ++++++++-------- pnpm-workspace.yaml | 4 +- src/utils/json-schema-to-zod.ts | 181 ++------------------------------ 3 files changed, 56 insertions(+), 217 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ae2e51c..73d0d0a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -70,8 +70,8 @@ catalogs: specifier: ^4.0.15 version: 4.0.15 zod: - specifier: ^4.1.13 - version: 4.1.13 + specifier: ^4.3.0 + version: 4.3.5 examples: '@anthropic-ai/claude-agent-sdk': specifier: ^0.1.67 @@ -112,13 +112,13 @@ importers: dependencies: '@anthropic-ai/claude-agent-sdk': specifier: catalog:peer - version: 0.1.67(zod@4.1.13) + version: 0.1.67(zod@4.3.5) '@anthropic-ai/sdk': specifier: catalog:peer version: 0.52.0 '@modelcontextprotocol/sdk': specifier: catalog:prod - version: 1.24.3(zod@4.1.13) + version: 1.24.3(zod@4.3.5) '@orama/orama': specifier: catalog:prod version: 3.1.16 @@ -131,7 +131,7 @@ importers: version: 0.2.4(vitest@4.0.15(@opentelemetry/api@1.9.0)(@types/node@22.19.1)(jiti@2.6.1)(msw@2.12.3(@types/node@22.19.1)(typescript@5.9.3))(tsx@4.21.0)(yaml@2.8.2)) '@hono/mcp': specifier: catalog:dev - version: 0.1.5(@modelcontextprotocol/sdk@1.24.3(zod@4.1.13))(hono@4.10.7) + version: 0.1.5(@modelcontextprotocol/sdk@1.24.3(zod@4.3.5))(hono@4.10.7) '@types/node': specifier: catalog:dev version: 22.19.1 @@ -143,7 +143,7 @@ importers: version: 4.0.15(vitest@4.0.15(@opentelemetry/api@1.9.0)(@types/node@22.19.1)(jiti@2.6.1)(msw@2.12.3(@types/node@22.19.1)(typescript@5.9.3))(tsx@4.21.0)(yaml@2.8.2)) ai: specifier: catalog:dev - version: 6.0.7(zod@4.1.13) + version: 6.0.7(zod@4.3.5) hono: specifier: catalog:dev version: 4.10.7 @@ -161,7 +161,7 @@ importers: version: runtime:24.12.0 openai: specifier: catalog:peer - version: 6.9.1(zod@4.1.13) + version: 6.9.1(zod@4.3.5) oxfmt: specifier: catalog:dev version: 0.18.0 @@ -191,16 +191,16 @@ importers: version: 4.0.15(@opentelemetry/api@1.9.0)(@types/node@22.19.1)(jiti@2.6.1)(msw@2.12.3(@types/node@22.19.1)(typescript@5.9.3))(tsx@4.21.0)(yaml@2.8.2) zod: specifier: catalog:dev - version: 4.1.13 + version: 4.3.5 examples: dependencies: '@ai-sdk/openai': specifier: catalog:dev - version: 3.0.2(zod@4.1.13) + version: 3.0.2(zod@4.3.5) '@anthropic-ai/claude-agent-sdk': specifier: catalog:examples - version: 0.1.67(zod@4.1.13) + version: 0.1.67(zod@4.3.5) '@anthropic-ai/sdk': specifier: catalog:peer version: 0.52.0 @@ -215,16 +215,16 @@ importers: version: 0.2.0 '@tanstack/ai-openai': specifier: catalog:examples - version: 0.2.0(@tanstack/ai@0.2.0)(zod@4.1.13) + version: 0.2.0(@tanstack/ai@0.2.0)(zod@4.3.5) ai: specifier: catalog:peer - version: 6.0.7(zod@4.1.13) + version: 6.0.7(zod@4.3.5) openai: specifier: catalog:peer - version: 6.9.1(zod@4.1.13) + version: 6.9.1(zod@4.3.5) zod: specifier: catalog:dev - version: 4.1.13 + version: 4.3.5 devDependencies: '@types/node': specifier: catalog:dev @@ -2566,38 +2566,38 @@ packages: peerDependencies: zod: ^3.25 || ^4 - zod@4.1.13: - resolution: {integrity: sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==} + zod@4.3.5: + resolution: {integrity: sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g==} snapshots: - '@ai-sdk/gateway@3.0.6(zod@4.1.13)': + '@ai-sdk/gateway@3.0.6(zod@4.3.5)': dependencies: '@ai-sdk/provider': 3.0.1 - '@ai-sdk/provider-utils': 4.0.2(zod@4.1.13) + '@ai-sdk/provider-utils': 4.0.2(zod@4.3.5) '@vercel/oidc': 3.0.5 - zod: 4.1.13 + zod: 4.3.5 - '@ai-sdk/openai@3.0.2(zod@4.1.13)': + '@ai-sdk/openai@3.0.2(zod@4.3.5)': dependencies: '@ai-sdk/provider': 3.0.1 - '@ai-sdk/provider-utils': 4.0.2(zod@4.1.13) - zod: 4.1.13 + '@ai-sdk/provider-utils': 4.0.2(zod@4.3.5) + zod: 4.3.5 - '@ai-sdk/provider-utils@4.0.2(zod@4.1.13)': + '@ai-sdk/provider-utils@4.0.2(zod@4.3.5)': dependencies: '@ai-sdk/provider': 3.0.1 '@standard-schema/spec': 1.1.0 eventsource-parser: 3.0.6 - zod: 4.1.13 + zod: 4.3.5 '@ai-sdk/provider@3.0.1': dependencies: json-schema: 0.4.0 - '@anthropic-ai/claude-agent-sdk@0.1.67(zod@4.1.13)': + '@anthropic-ai/claude-agent-sdk@0.1.67(zod@4.3.5)': dependencies: - zod: 4.1.13 + zod: 4.3.5 optionalDependencies: '@img/sharp-darwin-arm64': 0.33.5 '@img/sharp-darwin-x64': 0.33.5 @@ -2821,9 +2821,9 @@ snapshots: fast-check: 4.5.2 vitest: 4.0.15(@opentelemetry/api@1.9.0)(@types/node@22.19.1)(jiti@2.6.1)(msw@2.12.3(@types/node@22.19.1)(typescript@5.9.3))(tsx@4.21.0)(yaml@2.8.2) - '@hono/mcp@0.1.5(@modelcontextprotocol/sdk@1.24.3(zod@4.1.13))(hono@4.10.7)': + '@hono/mcp@0.1.5(@modelcontextprotocol/sdk@1.24.3(zod@4.3.5))(hono@4.10.7)': dependencies: - '@modelcontextprotocol/sdk': 1.24.3(zod@4.1.13) + '@modelcontextprotocol/sdk': 1.24.3(zod@4.3.5) hono: 4.10.7 '@img/sharp-darwin-arm64@0.33.5': @@ -2932,7 +2932,7 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 - '@modelcontextprotocol/sdk@1.24.3(zod@4.1.13)': + '@modelcontextprotocol/sdk@1.24.3(zod@4.3.5)': dependencies: ajv: 8.17.1 ajv-formats: 3.0.1(ajv@8.17.1) @@ -2946,8 +2946,8 @@ snapshots: jose: 6.1.3 pkce-challenge: 5.0.1 raw-body: 3.0.2 - zod: 4.1.13 - zod-to-json-schema: 3.25.0(zod@4.1.13) + zod: 4.3.5 + zod-to-json-schema: 3.25.0(zod@4.3.5) transitivePeerDependencies: - supports-color @@ -3239,11 +3239,11 @@ snapshots: '@standard-schema/spec@1.1.0': {} - '@tanstack/ai-openai@0.2.0(@tanstack/ai@0.2.0)(zod@4.1.13)': + '@tanstack/ai-openai@0.2.0(@tanstack/ai@0.2.0)(zod@4.3.5)': dependencies: '@tanstack/ai': 0.2.0 - openai: 6.9.1(zod@4.1.13) - zod: 4.1.13 + openai: 6.9.1(zod@4.3.5) + zod: 4.3.5 transitivePeerDependencies: - ws @@ -3371,13 +3371,13 @@ snapshots: acorn@8.15.0: {} - ai@6.0.7(zod@4.1.13): + ai@6.0.7(zod@4.3.5): dependencies: - '@ai-sdk/gateway': 3.0.6(zod@4.1.13) + '@ai-sdk/gateway': 3.0.6(zod@4.3.5) '@ai-sdk/provider': 3.0.1 - '@ai-sdk/provider-utils': 4.0.2(zod@4.1.13) + '@ai-sdk/provider-utils': 4.0.2(zod@4.3.5) '@opentelemetry/api': 1.9.0 - zod: 4.1.13 + zod: 4.3.5 ajv-formats@3.0.1(ajv@8.17.1): optionalDependencies: @@ -3826,7 +3826,7 @@ snapshots: smol-toml: 1.5.2 strip-json-comments: 5.0.3 typescript: 5.9.3 - zod: 4.1.13 + zod: 4.3.5 lefthook-darwin-arm64@2.0.8: optional: true @@ -3957,9 +3957,9 @@ snapshots: dependencies: wrappy: 1.0.2 - openai@6.9.1(zod@4.1.13): + openai@6.9.1(zod@4.3.5): optionalDependencies: - zod: 4.1.13 + zod: 4.3.5 outvariant@1.4.3: {} @@ -4486,8 +4486,8 @@ snapshots: yoctocolors-cjs@2.1.3: {} - zod-to-json-schema@3.25.0(zod@4.1.13): + zod-to-json-schema@3.25.0(zod@4.3.5): dependencies: - zod: 4.1.13 + zod: 4.3.5 - zod@4.1.13: {} + zod@4.3.5: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index c996343..a1dcc25 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -27,7 +27,7 @@ catalogs: typescript: ^5.8.3 unplugin-unused: ^0.5.4 vitest: ^4.0.15 - zod: ^4.1.13 + zod: ^4.3.0 examples: '@anthropic-ai/claude-agent-sdk': ^0.1.67 '@tanstack/ai': ^0.2.0 @@ -37,7 +37,7 @@ catalogs: '@anthropic-ai/sdk': ^0.52.0 ai: '>=5.0.108 <7.0.0' openai: ^6.2.0 - zod: '>=3.25.0 <5' + zod: '>=4.3.0 <5' prod: '@modelcontextprotocol/sdk': ^1.24.3 '@orama/orama': ^3.1.11 diff --git a/src/utils/json-schema-to-zod.ts b/src/utils/json-schema-to-zod.ts index 369e3b0..13ae179 100644 --- a/src/utils/json-schema-to-zod.ts +++ b/src/utils/json-schema-to-zod.ts @@ -1,6 +1,8 @@ /** * Utility to convert JSON Schema to Zod schemas at runtime. * This is used for Claude Agent SDK integration which requires Zod schemas. + * + * Uses Zod's native z.fromJSONSchema() method available in Zod v4.3+. */ import type { z, ZodTypeAny } from 'zod'; @@ -30,180 +32,17 @@ export async function jsonSchemaToZod( for (const [key, propSchema] of Object.entries(properties)) { const isRequired = required.includes(key); - const zodType = convertPropertyToZod(zodLib, propSchema as JSONSchema, isRequired); - zodSchema[key] = zodType; - } - - return { zodLib, zodSchema }; -} - -/** - * Convert a single JSON Schema property to a Zod type. - */ -function convertPropertyToZod( - zodLib: typeof z, - schema: JSONSchema, - isRequired: boolean, -): ZodTypeAny { - let zodType: ZodTypeAny; - - // Handle type-based conversion - const schemaType = Array.isArray(schema.type) ? schema.type[0] : schema.type; - - switch (schemaType) { - case 'string': - zodType = createStringSchema(zodLib, schema); - break; - case 'number': - case 'integer': - zodType = createNumberSchema(zodLib, schema); - break; - case 'boolean': - zodType = zodLib.boolean(); - break; - case 'array': - zodType = createArraySchema(zodLib, schema); - break; - case 'object': - zodType = createObjectSchema(zodLib, schema); - break; - case 'null': - zodType = zodLib.null(); - break; - default: - // For unknown types or union types, use unknown - zodType = zodLib.unknown(); - } - - // Add description if present - if (schema.description) { - zodType = zodType.describe(schema.description); - } - - // Make optional if not required - if (!isRequired) { - zodType = zodType.optional(); - } + // Use Zod's native fromJSONSchema() to convert each property + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- JSON Schema type compatibility + let zodType = zodLib.fromJSONSchema(propSchema as any); - return zodType; -} - -/** - * Create a Zod string schema with constraints. - */ -function createStringSchema(zodLib: typeof z, schema: JSONSchema): ZodTypeAny { - let stringSchema = zodLib.string(); - - // Handle enum values - if (schema.enum && schema.enum.length > 0) { - const enumValues = schema.enum.filter((v): v is string => typeof v === 'string'); - if (enumValues.length > 0) { - return zodLib.enum(enumValues as [string, ...string[]]); + // Make optional if not required + if (!isRequired) { + zodType = zodType.optional(); } - } - - // Handle format - if (schema.format === 'email') { - stringSchema = stringSchema.email(); - } else if (schema.format === 'url' || schema.format === 'uri') { - stringSchema = stringSchema.url(); - } else if (schema.format === 'uuid') { - stringSchema = stringSchema.uuid(); - } else if (schema.format === 'date-time') { - stringSchema = stringSchema.datetime(); - } - // Handle length constraints - if (typeof schema.minLength === 'number') { - stringSchema = stringSchema.min(schema.minLength); - } - if (typeof schema.maxLength === 'number') { - stringSchema = stringSchema.max(schema.maxLength); - } - - // Handle pattern - if (schema.pattern) { - stringSchema = stringSchema.regex(new RegExp(schema.pattern)); - } - - return stringSchema; -} - -/** - * Create a Zod number schema with constraints. - */ -function createNumberSchema(zodLib: typeof z, schema: JSONSchema): ZodTypeAny { - let numberSchema = zodLib.number(); - - // Handle enum values - if (schema.enum && schema.enum.length > 0) { - const enumValues = schema.enum.filter((v): v is number => typeof v === 'number'); - if (enumValues.length > 0) { - return zodLib.enum(enumValues.map(String) as [string, ...string[]]).transform(Number); - } - } - - // Handle range constraints - if (typeof schema.minimum === 'number') { - numberSchema = numberSchema.min(schema.minimum); - } - if (typeof schema.maximum === 'number') { - numberSchema = numberSchema.max(schema.maximum); - } - if (typeof schema.exclusiveMinimum === 'number') { - numberSchema = numberSchema.gt(schema.exclusiveMinimum); - } - if (typeof schema.exclusiveMaximum === 'number') { - numberSchema = numberSchema.lt(schema.exclusiveMaximum); - } - - // Handle integer type - if (schema.type === 'integer') { - numberSchema = numberSchema.int(); - } - - return numberSchema; -} - -/** - * Create a Zod array schema. - */ -function createArraySchema(zodLib: typeof z, schema: JSONSchema): ZodTypeAny { - let itemSchema: ZodTypeAny = zodLib.unknown(); - - if (schema.items && !Array.isArray(schema.items)) { - itemSchema = convertPropertyToZod(zodLib, schema.items as JSONSchema, true); - } - - let arraySchema = zodLib.array(itemSchema); - - // Handle length constraints - if (typeof schema.minItems === 'number') { - arraySchema = arraySchema.min(schema.minItems); - } - if (typeof schema.maxItems === 'number') { - arraySchema = arraySchema.max(schema.maxItems); - } - - return arraySchema; -} - -/** - * Create a Zod object schema. - */ -function createObjectSchema(zodLib: typeof z, schema: JSONSchema): ZodTypeAny { - if (!schema.properties) { - // Object with no defined properties - use record with string keys - return zodLib.record(zodLib.string(), zodLib.unknown()); - } - - const required = schema.required ?? []; - const shape: Record = {}; - - for (const [key, propSchema] of Object.entries(schema.properties)) { - const isRequired = required.includes(key); - shape[key] = convertPropertyToZod(zodLib, propSchema as JSONSchema, isRequired); + zodSchema[key] = zodType; } - return zodLib.object(shape); + return { zodLib, zodSchema }; } From 99de7af943a509f448f10ad9006413debeb4232b Mon Sep 17 00:00:00 2001 From: ryoppippi <1560508+ryoppippi@users.noreply.github.com> Date: Thu, 8 Jan 2026 14:40:36 +0000 Subject: [PATCH 08/11] chore: use jsonSchema from ai --- src/tool.ts | 7 +++-- src/utils/json-schema-to-zod.ts | 48 --------------------------------- 2 files changed, 3 insertions(+), 52 deletions(-) delete mode 100644 src/utils/json-schema-to-zod.ts diff --git a/src/tool.ts b/src/tool.ts index 2419cd4..107406e 100644 --- a/src/tool.ts +++ b/src/tool.ts @@ -1,4 +1,4 @@ -import type { JSONSchema7 as AISDKJSONSchema } from 'ai'; +import { type JSONSchema7 as AISDKJSONSchema, jsonSchema } from 'ai'; import type { Tool as AnthropicTool } from '@anthropic-ai/sdk/resources'; import * as orama from '@orama/orama'; import type { ChatCompletionFunctionTool } from 'openai/resources/chat/completions'; @@ -24,7 +24,6 @@ import type { } from './types'; import { StackOneError } from './utils/error-stackone'; -import { jsonSchemaToZod } from './utils/json-schema-to-zod'; import { TfidfIndex } from './utils/tfidf-index'; import { tryImport } from './utils/try-import'; @@ -242,13 +241,13 @@ export class BaseTool { args: Record, ) => Promise<{ content: Array<{ type: 'text'; text: string }> }>; }> { - const { zodSchema } = await jsonSchemaToZod(this.toJsonSchema()); + const inputSchema = jsonSchema(this.toJsonSchema()); const execute = this.execute.bind(this); return { name: this.name, description: this.description, - inputSchema: zodSchema, + inputSchema, handler: async (args: Record) => { const result = await execute(args as JsonObject); return { diff --git a/src/utils/json-schema-to-zod.ts b/src/utils/json-schema-to-zod.ts deleted file mode 100644 index 13ae179..0000000 --- a/src/utils/json-schema-to-zod.ts +++ /dev/null @@ -1,48 +0,0 @@ -/** - * Utility to convert JSON Schema to Zod schemas at runtime. - * This is used for Claude Agent SDK integration which requires Zod schemas. - * - * Uses Zod's native z.fromJSONSchema() method available in Zod v4.3+. - */ - -import type { z, ZodTypeAny } from 'zod'; -import type { JSONSchema } from '../types'; -import { peerDependencies } from '../../package.json'; -import { tryImport } from './try-import'; - -/** - * Convert a JSON Schema to a Zod schema object. - * Returns a record of Zod types that can be used with the Claude Agent SDK tool() function. - * - * @param schema - JSON Schema object with properties - * @returns Promise resolving to a record of Zod types compatible with Claude Agent SDK - */ -export async function jsonSchemaToZod( - schema: JSONSchema, -): Promise<{ zodLib: typeof z; zodSchema: Record }> { - const zodLib = await tryImport( - 'zod', - `npm install zod (requires ${peerDependencies.zod})`, - ); - - const properties = schema.properties ?? {}; - const required = schema.required ?? []; - - const zodSchema: Record = {}; - - for (const [key, propSchema] of Object.entries(properties)) { - const isRequired = required.includes(key); - // Use Zod's native fromJSONSchema() to convert each property - // eslint-disable-next-line @typescript-eslint/no-explicit-any -- JSON Schema type compatibility - let zodType = zodLib.fromJSONSchema(propSchema as any); - - // Make optional if not required - if (!isRequired) { - zodType = zodType.optional(); - } - - zodSchema[key] = zodType; - } - - return { zodLib, zodSchema }; -} From b7edefdb981869024c1907bb077a941d07e4d023 Mon Sep 17 00:00:00 2001 From: ryoppippi <1560508+ryoppippi@users.noreply.github.com> Date: Thu, 8 Jan 2026 14:42:30 +0000 Subject: [PATCH 09/11] chore(deps): relax zod peer dependency to support v3.25.0+ Since we no longer use z.fromJSONSchema(), we can support Zod v3.25.0+ instead of requiring v4.3.0+. This matches the main branch and allows users with older Zod versions to use this package. --- pnpm-workspace.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index a1dcc25..d1a77c6 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -37,7 +37,7 @@ catalogs: '@anthropic-ai/sdk': ^0.52.0 ai: '>=5.0.108 <7.0.0' openai: ^6.2.0 - zod: '>=4.3.0 <5' + zod: '>=3.25.0 <5' prod: '@modelcontextprotocol/sdk': ^1.24.3 '@orama/orama': ^3.1.11 From 098ff0aae20ea96d451888549383bf16a7beb960 Mon Sep 17 00:00:00 2001 From: ryoppippi <1560508+ryoppippi@users.noreply.github.com> Date: Thu, 8 Jan 2026 14:46:37 +0000 Subject: [PATCH 10/11] refactor(types): use McpSdkServerConfigWithInstance from Claude Agent SDK Import the return type directly from @anthropic-ai/claude-agent-sdk instead of maintaining our own ClaudeAgentSdkMcpServer interface. This removes code duplication and ensures type compatibility. --- src/tool.ts | 9 +++++---- src/types.ts | 13 ------------- 2 files changed, 5 insertions(+), 17 deletions(-) diff --git a/src/tool.ts b/src/tool.ts index 107406e..bd97259 100644 --- a/src/tool.ts +++ b/src/tool.ts @@ -1,5 +1,6 @@ import { type JSONSchema7 as AISDKJSONSchema, jsonSchema } from 'ai'; import type { Tool as AnthropicTool } from '@anthropic-ai/sdk/resources'; +import type { McpSdkServerConfigWithInstance } from '@anthropic-ai/claude-agent-sdk'; import * as orama from '@orama/orama'; import type { ChatCompletionFunctionTool } from 'openai/resources/chat/completions'; import type { FunctionTool as OpenAIResponsesFunctionTool } from 'openai/resources/responses/responses'; @@ -10,7 +11,6 @@ import { RequestBuilder } from './requestBuilder'; import type { AISDKToolDefinition, AISDKToolResult, - ClaudeAgentSdkMcpServer, ClaudeAgentSdkOptions, ExecuteConfig, ExecuteOptions, @@ -450,7 +450,9 @@ export class Tools implements Iterable { * * @see https://docs.anthropic.com/en/docs/agents-and-tools/claude-agent-sdk */ - async toClaudeAgentSdk(options: ClaudeAgentSdkOptions = {}): Promise { + async toClaudeAgentSdk( + options: ClaudeAgentSdkOptions = {}, + ): Promise { const { serverName = 'stackone-tools', serverVersion = '1.0.0' } = options; // Import the Claude Agent SDK dynamically @@ -476,12 +478,11 @@ export class Tools implements Iterable { ); // Create and return the MCP server - // The return type is compatible with McpServerConfig but uses our interface return claudeAgentSdk.createSdkMcpServer({ name: serverName, version: serverVersion, tools: sdkTools, - }) as ClaudeAgentSdkMcpServer; + }); } /** diff --git a/src/types.ts b/src/types.ts index f99ff28..0190381 100644 --- a/src/types.ts +++ b/src/types.ts @@ -202,19 +202,6 @@ export type AISDKToolResult = ToolSet & { [K in T]: AISDKToolDefinition; }; -/** - * Claude Agent SDK MCP server configuration returned by toClaudeAgentSdk(). - * This type represents the MCP server instance that can be passed to the - * Claude Agent SDK query() function's mcpServers option. - * - * @see https://docs.anthropic.com/en/docs/agents-and-tools/claude-agent-sdk - */ -export interface ClaudeAgentSdkMcpServer { - type: 'sdk'; - name: string; - instance: unknown; -} - /** * Options for toClaudeAgentSdk() method */ From dff5a2960710c148a46e3b9e617ca7dc2894cfec Mon Sep 17 00:00:00 2001 From: ryoppippi <1560508+ryoppippi@users.noreply.github.com> Date: Thu, 8 Jan 2026 14:51:07 +0000 Subject: [PATCH 11/11] test(tool): add tests for toClaudeAgentSdk and toClaudeAgentSdkTool - Add unit test for BaseTool.toClaudeAgentSdkTool() conversion - Add unit tests for Tools.toClaudeAgentSdk() with default options - Add unit test for Tools.toClaudeAgentSdk() with custom server name/version --- src/tool.test.ts | 71 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/src/tool.test.ts b/src/tool.test.ts index 876f256..0291844 100644 --- a/src/tool.test.ts +++ b/src/tool.test.ts @@ -171,6 +171,25 @@ describe('StackOneTool', () => { expect(schema.properties?.id.type).toBe('string'); }); + it('should convert to Claude Agent SDK tool format', async () => { + const tool = createMockTool(); + + const claudeTool = await tool.toClaudeAgentSdkTool(); + + expect(claudeTool).toBeDefined(); + expect(claudeTool.name).toBe('test_tool'); + expect(claudeTool.description).toBe('Test tool'); + expect(claudeTool.inputSchema).toBeDefined(); + expect(typeof claudeTool.handler).toBe('function'); + + // Test the handler returns content in the expected format + const result = await claudeTool.handler({ id: 'test-123' }); + expect(result).toHaveProperty('content'); + expect(Array.isArray(result.content)).toBe(true); + expect(result.content[0]).toHaveProperty('type', 'text'); + expect(result.content[0]).toHaveProperty('text'); + }); + it('should include execution metadata by default in AI SDK conversion', async () => { const tool = createMockTool(); @@ -693,6 +712,58 @@ describe('Tools', () => { expect(typeof aiSdkTools.another_tool.execute).toBe('function'); }); + it('should convert all tools to Claude Agent SDK MCP server', async () => { + const tool1 = createMockTool(); + const tool2 = new StackOneTool( + 'another_tool', + 'Another tool', + { + type: 'object', + properties: { name: { type: 'string' } }, + }, + { + kind: 'http', + method: 'POST', + url: 'https://api.example.com/test', + bodyType: 'json', + params: [ + { + name: 'name', + location: ParameterLocation.BODY, + type: 'string', + }, + ], + }, + { + authorization: 'Bearer test_key', + }, + ); + + const tools = new Tools([tool1, tool2]); + + const mcpServer = await tools.toClaudeAgentSdk(); + + expect(mcpServer).toBeDefined(); + expect(mcpServer.type).toBe('sdk'); + expect(mcpServer.name).toBe('stackone-tools'); + expect(mcpServer.instance).toBeDefined(); + }); + + it('should convert all tools to Claude Agent SDK MCP server with custom options', async () => { + const tool1 = createMockTool(); + const tools = new Tools([tool1]); + + const mcpServer = await tools.toClaudeAgentSdk({ + serverName: 'my-custom-server', + serverVersion: '2.0.0', + }); + + expect(mcpServer).toBeDefined(); + expect(mcpServer.type).toBe('sdk'); + expect(mcpServer.name).toBe('my-custom-server'); + expect(mcpServer.instance).toBeDefined(); + }); + it('should be iterable', () => { const tool1 = createMockTool(); const tool2 = new StackOneTool(