Skip to content

Conversation

@ryoppippi
Copy link
Member

@ryoppippi ryoppippi commented Jan 6, 2026

Summary

  • Add toClaudeAgentSdk() method to the Tools class for seamless Claude Agent SDK integration
  • Simplify the Claude Agent SDK example to use the new method

What Changed

  • Added @anthropic-ai/claude-agent-sdk as an optional peer dependency
  • Added ClaudeAgentSdkMcpServer and ClaudeAgentSdkOptions types in src/types.ts
  • Added toClaudeAgentSdkTool() method on BaseTool class
  • Added toClaudeAgentSdk() method on Tools class that creates an MCP server compatible with Claude Agent SDK
  • Updated example and README documentation

Test plan

  • All 436 existing tests pass
  • The example demonstrates the simplified API usage

Copilot AI review requested due to automatic review settings January 6, 2026 17:04
@ryoppippi ryoppippi requested a review from a team as a code owner January 6, 2026 17:04
@pkg-pr-new
Copy link

pkg-pr-new bot commented Jan 6, 2026

Open in StackBlitz

npm i https://pkg.pr.new/StackOneHQ/stackone-ai-node/@stackone/ai@283

commit: dff5a29

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3 issues found across 8 files

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="src/utils/json-schema-to-zod.ts">

<violation number="1" location="src/utils/json-schema-to-zod.ts:51">
P1: Union type arrays are not properly handled. JSON Schema commonly uses `[&quot;string&quot;, &quot;null&quot;]` for nullable types, but only the first type is used here. This will cause validation failures when `null` values are passed for nullable fields. Consider checking if the type array contains `&quot;null&quot;` and applying `.nullable()` to the Zod schema.</violation>

<violation number="2" location="src/utils/json-schema-to-zod.ts:126">
P1: Rule violated: **Flag Security Vulnerabilities**

Potential ReDoS vulnerability: `new RegExp(schema.pattern)` creates a regex from potentially untrusted JSON Schema input without validation. Malicious patterns with nested quantifiers (e.g., `(a+)+$`) can cause catastrophic backtracking, hanging the event loop. Consider using a safe regex library like `safe-regex` or `re2` to validate patterns before compilation, or set a timeout for regex operations.</violation>
</file>

<file name="src/tool.ts">

<violation number="1" location="src/tool.ts:252">
P1: Missing error handling in handler. Unlike `toAISDK()`, this handler doesn&#39;t wrap the `execute` call in try/catch. Unhandled errors could crash the Claude Agent SDK runtime.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

let zodType: ZodTypeAny;

// Handle type-based conversion
const schemaType = Array.isArray(schema.type) ? schema.type[0] : schema.type;
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Union type arrays are not properly handled. JSON Schema commonly uses ["string", "null"] for nullable types, but only the first type is used here. This will cause validation failures when null values are passed for nullable fields. Consider checking if the type array contains "null" and applying .nullable() to the Zod schema.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/utils/json-schema-to-zod.ts, line 51:

<comment>Union type arrays are not properly handled. JSON Schema commonly uses `[&quot;string&quot;, &quot;null&quot;]` for nullable types, but only the first type is used here. This will cause validation failures when `null` values are passed for nullable fields. Consider checking if the type array contains `&quot;null&quot;` and applying `.nullable()` to the Zod schema.</comment>

<file context>
@@ -0,0 +1,209 @@
+	let zodType: ZodTypeAny;
+
+	// Handle type-based conversion
+	const schemaType = Array.isArray(schema.type) ? schema.type[0] : schema.type;
+
+	switch (schemaType) {
</file context>

✅ Addressed in cecb6ec

name: this.name,
description: this.description,
inputSchema: zodSchema,
handler: async (args: Record<string, unknown>) => {
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Missing error handling in handler. Unlike toAISDK(), this handler doesn't wrap the execute call in try/catch. Unhandled errors could crash the Claude Agent SDK runtime.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/tool.ts, line 252:

<comment>Missing error handling in handler. Unlike `toAISDK()`, this handler doesn&#39;t wrap the `execute` call in try/catch. Unhandled errors could crash the Claude Agent SDK runtime.</comment>

<file context>
@@ -225,6 +228,36 @@ export class BaseTool {
+			name: this.name,
+			description: this.description,
+			inputSchema: zodSchema,
+			handler: async (args: Record&lt;string, unknown&gt;) =&gt; {
+				const result = await execute(args as JsonObject);
+				return {
</file context>
Fix with Cubic


// Handle pattern
if (schema.pattern) {
stringSchema = stringSchema.regex(new RegExp(schema.pattern));
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Rule violated: Flag Security Vulnerabilities

Potential ReDoS vulnerability: new RegExp(schema.pattern) creates a regex from potentially untrusted JSON Schema input without validation. Malicious patterns with nested quantifiers (e.g., (a+)+$) can cause catastrophic backtracking, hanging the event loop. Consider using a safe regex library like safe-regex or re2 to validate patterns before compilation, or set a timeout for regex operations.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/utils/json-schema-to-zod.ts, line 126:

<comment>Potential ReDoS vulnerability: `new RegExp(schema.pattern)` creates a regex from potentially untrusted JSON Schema input without validation. Malicious patterns with nested quantifiers (e.g., `(a+)+$`) can cause catastrophic backtracking, hanging the event loop. Consider using a safe regex library like `safe-regex` or `re2` to validate patterns before compilation, or set a timeout for regex operations.</comment>

<file context>
@@ -0,0 +1,209 @@
+
+	// Handle pattern
+	if (schema.pattern) {
+		stringSchema = stringSchema.regex(new RegExp(schema.pattern));
+	}
+
</file context>

✅ Addressed in cecb6ec

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a new toClaudeAgentSdk() method to seamlessly integrate StackOne tools with the Claude Agent SDK, eliminating the need for manual tool conversion. The implementation includes a runtime JSON Schema to Zod conversion utility and simplifies the integration example significantly.

Key Changes:

  • Added toClaudeAgentSdk() method on the Tools class that creates an MCP server compatible with Claude Agent SDK
  • Created jsonSchemaToZod() utility for runtime conversion of JSON Schema to Zod schemas
  • Simplified the Claude Agent SDK example by removing 30+ lines of manual tool conversion code

Reviewed changes

Copilot reviewed 7 out of 8 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/utils/json-schema-to-zod.ts New utility to convert JSON Schema to Zod schemas at runtime, supporting strings, numbers, booleans, arrays, objects, and various constraints
src/types.ts Added ClaudeAgentSdkMcpServer and ClaudeAgentSdkOptions type definitions for the new integration
src/tool.ts Added toClaudeAgentSdkTool() method to BaseTool and toClaudeAgentSdk() method to Tools class for SDK conversion
package.json Added @anthropic-ai/claude-agent-sdk as optional peer dependency
pnpm-workspace.yaml Added Claude Agent SDK version to peer catalog
pnpm-lock.yaml Updated lockfile with Claude Agent SDK dependency
examples/claude-agent-sdk-integration.ts Simplified example to use new toClaudeAgentSdk() method, reducing code complexity
README.md Updated documentation to show the simplified API usage
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported
Comments suppressed due to low confidence (4)

src/utils/json-schema-to-zod.ts:143

  • The number enum handling uses zodLib.enum() with string values and then transforms them back to numbers. However, Zod's enum only accepts string values. For numeric enums, you should use z.union() with z.literal() values instead. This approach will fail at runtime when the enum is validated.

Consider replacing the number enum handling with a union of literal values, for example:

  • Use zodLib.union([zodLib.literal(enumValues[0]), ...]) for numeric enums
  • Or use zodLib.number().refine((val) => enumValues.includes(val)) to validate the value is in the allowed set
    package.json:103
  • The zod peer dependency is missing from peerDependenciesMeta. Since zod is listed in peerDependencies and is used for Claude Agent SDK integration (which is optional), it should also be marked as optional in peerDependenciesMeta to prevent installation warnings when users don't need this functionality.
	"peerDependenciesMeta": {
		"@anthropic-ai/claude-agent-sdk": {
			"optional": true
		},
		"@anthropic-ai/sdk": {
			"optional": true
		},
		"ai": {
			"optional": true
		},
		"openai": {
			"optional": true
		}
	},

src/utils/json-schema-to-zod.ts:51

  • When JSON Schema has a union type (e.g., type: ['string', 'null']), the code only handles the first type in the array. This can result in incorrect validation. For example, a schema with type: ['string', 'null'] would only validate strings and reject null values. Consider using z.union() to properly handle multiple types, or at least document this limitation in the function's JSDoc.
    src/utils/json-schema-to-zod.ts:209
  • The new jsonSchemaToZod utility lacks test coverage. Other utilities in the src/utils/ directory have corresponding test files (e.g., array.test.ts, tfidf-index.test.ts, try-import.test.ts). This utility handles complex JSON Schema to Zod conversions with multiple edge cases (enums, constraints, nested objects, arrays) that should be tested to ensure correctness. Please add a json-schema-to-zod.test.ts file with comprehensive test cases.
/**
 * 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<string, ZodTypeAny> }> {
	const zodLib = await tryImport<typeof import('zod')>(
		'zod',
		`npm install zod (requires ${peerDependencies.zod})`,
	);

	const properties = schema.properties ?? {};
	const required = schema.required ?? [];

	const zodSchema: Record<string, ZodTypeAny> = {};

	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 };
}


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 231 to 259
/**
* 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<string, unknown>;
handler: (
args: Record<string, unknown>,
) => 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<string, unknown>) => {
const result = await execute(args as JsonObject);
return {
content: [{ type: 'text' as const, text: JSON.stringify(result) }],
};
},
};
}
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new toClaudeAgentSdkTool() method lacks test coverage. The codebase has comprehensive tests for similar conversion methods like toAISDK() (see src/tool.test.ts lines 156-443). The toClaudeAgentSdkTool() method should have tests to verify correct conversion of tool definitions, handling of the inputSchema (Zod schema), and proper execution handler wrapping.

Copilot uses AI. Check for mistakes.
Comment on lines +253 to +256
const result = await execute(args as JsonObject);
return {
content: [{ type: 'text' as const, text: JSON.stringify(result) }],
};
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The handler in toClaudeAgentSdkTool() doesn't include error handling, unlike the similar handler in toAISDK() (lines 295-301). If execute() throws an error, it will propagate up and potentially crash the Claude Agent SDK query. Consider wrapping the execute call in a try-catch block and returning an error message in the response format, similar to how it's done in the toAISDK() method.

Suggested change
const result = await execute(args as JsonObject);
return {
content: [{ type: 'text' as const, text: JSON.stringify(result) }],
};
try {
const result = await execute(args as JsonObject);
return {
content: [{ type: 'text' as const, text: JSON.stringify(result) }],
};
} catch (error) {
const message =
error instanceof Error ? error.message : String(error);
return {
content: [
{
type: 'text' as const,
text: `Error executing tool "${this.name}": ${message}`,
},
],
};
}

Copilot uses AI. Check for mistakes.
src/tool.ts Outdated
Comment on lines 454 to 486
async toClaudeAgentSdk(options: ClaudeAgentSdkOptions = {}): Promise<ClaudeAgentSdkMcpServer> {
const { serverName = 'stackone-tools', serverVersion = '1.0.0' } = options;

// Import the Claude Agent SDK dynamically
const claudeAgentSdk = await tryImport<typeof import('@anthropic-ai/claude-agent-sdk')>(
'@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;
}
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new toClaudeAgentSdk() method lacks test coverage. The codebase has comprehensive tests for similar conversion methods like toAISDK() on the Tools class (see src/tool.test.ts line 687). The toClaudeAgentSdk() method should have tests to verify proper MCP server creation, correct handling of multiple tools, and validation of the returned server configuration.

Copilot uses AI. Check for mistakes.
src/tool.ts Outdated
Comment on lines 237 to 251
async toClaudeAgentSdkTool(): Promise<{
name: string;
description: string;
inputSchema: Record<string, unknown>;
handler: (
args: Record<string, unknown>,
) => 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,
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The return type for inputSchema is declared as Record<string, unknown>, but the actual value returned is zodSchema which is of type Record<string, ZodTypeAny>. This type mismatch could lead to confusion and potential runtime issues. Consider updating the return type to accurately reflect that it returns Zod schemas, or add a comment explaining why the broader type is used.

Copilot uses AI. Check for mistakes.
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.
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
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.
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.
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.
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
…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
@ryoppippi ryoppippi force-pushed the feat/to-claude-agent-sdk branch from cecb6ec to 45bd653 Compare January 8, 2026 14:35
@ryoppippi ryoppippi changed the title feat(tool): add toClaudeAgentSdk() method for Claude Agent SDK integration feat(tool): add toClaudeAgentSdk() method for Claude Agent SDK integration [ENG-11770] Jan 8, 2026
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.
… 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.
- 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
@ryoppippi ryoppippi merged commit f02644b into main Jan 8, 2026
16 checks passed
@ryoppippi ryoppippi deleted the feat/to-claude-agent-sdk branch January 8, 2026 14:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants