Skip to content

Commit f02644b

Browse files
authored
feat(tool): add toClaudeAgentSdk() method for Claude Agent SDK integration [ENG-11770] (#283)
* 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. * 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 * 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. * 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. * 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. * 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 * 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 * chore: use jsonSchema from ai * 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. * 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. * 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
1 parent 6e61fd3 commit f02644b

File tree

8 files changed

+247
-100
lines changed

8 files changed

+247
-100
lines changed

README.md

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -281,35 +281,17 @@ npm install @stackone/ai @anthropic-ai/claude-agent-sdk zod # or: yarn/pnpm/bun
281281
```
282282

283283
```typescript
284-
import { query, tool, createSdkMcpServer } from "@anthropic-ai/claude-agent-sdk";
285-
import { z } from "zod";
284+
import { query } from "@anthropic-ai/claude-agent-sdk";
286285
import { StackOneToolSet } from "@stackone/ai";
287286

288287
const toolset = new StackOneToolSet({
289288
baseUrl: "https://api.stackone.com",
290289
accountId: "your-account-id",
291290
});
292291

292+
// Fetch tools and convert to Claude Agent SDK format
293293
const tools = await toolset.fetchTools();
294-
const employeeTool = tools.getTool("bamboohr_get_employee");
295-
296-
// Create a Claude Agent SDK tool from the StackOne tool
297-
const getEmployeeTool = tool(
298-
employeeTool.name,
299-
employeeTool.description,
300-
{ id: z.string().describe("The employee ID") },
301-
async (args) => {
302-
const result = await employeeTool.execute(args);
303-
return { content: [{ type: "text", text: JSON.stringify(result) }] };
304-
}
305-
);
306-
307-
// Create an MCP server with the StackOne tool
308-
const mcpServer = createSdkMcpServer({
309-
name: "stackone-tools",
310-
version: "1.0.0",
311-
tools: [getEmployeeTool],
312-
});
294+
const mcpServer = await tools.toClaudeAgentSdk();
313295

314296
// Use with Claude Agent SDK query
315297
const result = query({

examples/claude-agent-sdk-integration.ts

Lines changed: 13 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@
33
*
44
* Claude Agent SDK allows you to create autonomous agents with custom tools
55
* via MCP (Model Context Protocol) servers.
6+
*
7+
* The `toClaudeAgentSdk()` method automatically converts StackOne tools
8+
* to Claude Agent SDK format, handling the MCP server creation internally.
69
*/
710

811
import assert from 'node:assert';
912
import process from 'node:process';
10-
import { query, tool, createSdkMcpServer } from '@anthropic-ai/claude-agent-sdk';
11-
import { z } from 'zod';
13+
import { query } from '@anthropic-ai/claude-agent-sdk';
1214
import { StackOneToolSet } from '@stackone/ai';
1315

1416
const apiKey = process.env.STACKONE_API_KEY;
@@ -18,7 +20,7 @@ if (!apiKey) {
1820
}
1921

2022
// Replace with your actual account ID from StackOne dashboard
21-
const accountId = 'your-bamboohr-account-id';
23+
const accountId = 'your-hris-account-id';
2224

2325
const claudeAgentSdkIntegration = async (): Promise<void> => {
2426
// Initialize StackOne
@@ -27,42 +29,19 @@ const claudeAgentSdkIntegration = async (): Promise<void> => {
2729
baseUrl: process.env.STACKONE_BASE_URL ?? 'https://api.stackone.com',
2830
});
2931

30-
// Fetch tools from StackOne
32+
// Fetch tools from StackOne and convert to Claude Agent SDK format
3133
const tools = await toolset.fetchTools();
34+
const mcpServer = await tools.toClaudeAgentSdk();
3235

33-
// Get a specific tool
34-
const employeeTool = tools.getTool('bamboohr_get_employee');
35-
assert(employeeTool !== undefined, 'Expected to find bamboohr_get_employee tool');
36-
37-
// Create a Claude Agent SDK tool from the StackOne tool
38-
const getEmployeeTool = tool(
39-
employeeTool.name,
40-
employeeTool.description,
41-
{
42-
id: z.string().describe('The employee ID'),
43-
},
44-
async (args) => {
45-
const result = await employeeTool.execute(args);
46-
return {
47-
content: [{ type: 'text', text: JSON.stringify(result) }],
48-
};
49-
},
50-
);
51-
52-
// Create an MCP server with the StackOne tool
53-
const mcpServer = createSdkMcpServer({
54-
name: 'stackone-tools',
55-
version: '1.0.0',
56-
tools: [getEmployeeTool],
57-
});
58-
59-
// Use the Claude Agent SDK query with the custom MCP server
36+
// Use the Claude Agent SDK query with the StackOne MCP server
37+
// Type assertion is needed because our interface is compatible but not identical
6038
const result = query({
6139
prompt: 'Get the employee with id: c28xIQaWQ6MzM5MzczMDA2NzMzMzkwNzIwNA',
6240
options: {
6341
model: 'claude-sonnet-4-5-20250929',
6442
mcpServers: {
65-
'stackone-tools': mcpServer,
43+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Compatible MCP server type
44+
'stackone-tools': mcpServer as any,
6645
},
6746
// Disable built-in tools, only use our custom tools
6847
tools: [],
@@ -75,14 +54,14 @@ const claudeAgentSdkIntegration = async (): Promise<void> => {
7554
for await (const message of result) {
7655
if (message.type === 'assistant') {
7756
for (const block of message.message.content) {
78-
if (block.type === 'tool_use' && block.name === 'bamboohr_get_employee') {
57+
if (block.type === 'tool_use' && block.name === 'hris_get_employee') {
7958
hasToolCall = true;
8059
}
8160
}
8261
}
8362
}
8463

85-
assert(hasToolCall, 'Expected at least one tool call to bamboohr_get_employee');
64+
assert(hasToolCall, 'Expected at least one tool call to hris_get_employee');
8665
};
8766

8867
await claudeAgentSdkIntegration();

package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,12 +81,16 @@
8181
"zod": "catalog:dev"
8282
},
8383
"peerDependencies": {
84+
"@anthropic-ai/claude-agent-sdk": "catalog:peer",
8485
"@anthropic-ai/sdk": "catalog:peer",
8586
"ai": "catalog:peer",
8687
"openai": "catalog:peer",
8788
"zod": "catalog:peer"
8889
},
8990
"peerDependenciesMeta": {
91+
"@anthropic-ai/claude-agent-sdk": {
92+
"optional": true
93+
},
9094
"@anthropic-ai/sdk": {
9195
"optional": true
9296
},

0 commit comments

Comments
 (0)