Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
c37de3c
chore: remove unused sample-document
ryoppippi Dec 11, 2025
e7372a4
chore: remove fetch tools example
ryoppippi Dec 11, 2025
3b59d18
Revert "chore: remove fetch tools example"
ryoppippi Dec 11, 2025
619c637
feat(example): update interactive fetchTool debug
ryoppippi Dec 11, 2025
6197f95
Merge remote-tracking branch 'origin/main' into fix-examples
ryoppippi Dec 12, 2025
acac6b9
refactor(types): replace json-schema library with custom JSONSchema type
ryoppippi Dec 12, 2025
ddd8495
refactor(tool): use toJsonSchema() in toAISDK method
ryoppippi Dec 12, 2025
5e9caa2
refactor(tool): add type-safe schema validation for AI SDK
ryoppippi Dec 12, 2025
3ce8949
refactor(utils): add tryImport utility for optional dependencies
ryoppippi Dec 12, 2025
4be98cf
test(utils): add tests for tryImport utility
ryoppippi Dec 12, 2025
9d176dc
refactor(tool): clean up toAISDK method
ryoppippi Dec 12, 2025
2626c85
chore(oxfmt): ignore .claude/settings.local.json
ryoppippi Dec 12, 2025
df0b018
chore(deps): move @ai-sdk/provider to catalog:dev
ryoppippi Dec 12, 2025
468aea6
docs: tanstack ai jsonschema
ryoppippi Dec 12, 2025
a250963
test(examples): add E2E tests for example integrations
ryoppippi Dec 12, 2025
fa898bc
refactor(mocks): add MSW handlers for examples E2E tests
ryoppippi Dec 12, 2025
fb802c3
refactor(examples): reorganise dependencies and remove redundant inde…
ryoppippi Dec 12, 2025
d541b0b
refactor(vitest): consolidate setupFiles and simplify coverage config
ryoppippi Dec 12, 2025
3b395df
chore: update knip config and package.json
ryoppippi Dec 12, 2025
d4fbac2
Merge branch 'refactor/remove-json-schema-dependency' into fix-examples
ryoppippi Dec 12, 2025
db3d684
feat(deps): add TanStack AI and Claude Agent SDK to examples catalog
ryoppippi Dec 12, 2025
2c534f7
feat(examples): add TanStack AI integration example and tests
ryoppippi Dec 12, 2025
9666c32
feat(examples): add Claude Agent SDK integration example and tests
ryoppippi Dec 12, 2025
c69e187
docs(examples): clarify AI SDK agent pattern in comments
ryoppippi Dec 12, 2025
ba64913
docs(readme): add TanStack AI and Claude Agent SDK integration examples
ryoppippi Dec 12, 2025
a02f8f4
Merge branch 'main' into fix-examples
ryoppippi Dec 12, 2025
30f0507
docs:
ryoppippi Dec 12, 2025
8212704
Merge remote-tracking branch 'origin/main' into fix-examples
ryoppippi Dec 12, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .oxfmtrc.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@
"useTabs": true,
"semi": true,
"singleQuote": true,
"ignorePatterns": [".claude/settings.local.json"],
}
96 changes: 94 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,100 @@ await generateText({

[View full example](examples/ai-sdk-integration.ts)

### TanStack AI

```typescript
import { chat } from "@tanstack/ai";
import { openai } from "@tanstack/ai-openai";
import { z } from "zod";
import { StackOneToolSet } from "@stackone/ai";

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

const tools = await toolset.fetchTools();
const employeeTool = tools.getTool("bamboohr_get_employee");

// TanStack AI requires Zod schemas for tool input validation
const getEmployeeTool = {
name: employeeTool.name,
description: employeeTool.description,
inputSchema: z.object({
id: z.string().describe("The employee ID"),
}),
execute: async (args: { id: string }) => {
return employeeTool.execute(args);
},
};

const adapter = openai();
const stream = chat({
adapter,
model: "gpt-4o",
messages: [{ role: "user", content: "Get employee with id: abc123" }],
tools: [getEmployeeTool],
});

for await (const chunk of stream) {
// Process streaming chunks
}
```

[View full example](examples/tanstack-ai-integration.ts)

### Claude Agent SDK

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

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

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],
});

// Use with Claude Agent SDK query
const result = query({
prompt: "Get the employee with id: abc123",
options: {
model: "claude-sonnet-4-5-20250929",
mcpServers: { "stackone-tools": mcpServer },
tools: [], // Disable built-in tools
maxTurns: 3,
},
});

for await (const message of result) {
// Process streaming messages
}
```

[View full example](examples/claude-agent-sdk-integration.ts)

## Usage

```typescript
Expand All @@ -128,8 +222,6 @@ const employeeTool = tools.getTool("bamboohr_list_employees");
const employees = await employeeTool.execute();
```

[View full example](examples/index.ts)

### Authentication

Set the `STACKONE_API_KEY` environment variable:
Expand Down
52 changes: 52 additions & 0 deletions examples/ai-sdk-integration.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/**
* E2E test for ai-sdk-integration.ts example
*
* Tests the complete flow of using StackOne tools with the AI SDK.
*/

import { openai } from '@ai-sdk/openai';
import { generateText, stepCountIs } from 'ai';
import { StackOneToolSet } from '../src';

describe('ai-sdk-integration example e2e', () => {
beforeEach(() => {
vi.stubEnv('STACKONE_API_KEY', 'test-key');
vi.stubEnv('OPENAI_API_KEY', 'test-openai-key');
});

afterEach(() => {
vi.unstubAllEnvs();
});

it('should fetch tools, convert to AI SDK format, and generate text with tool calls', async () => {
const toolset = new StackOneToolSet({
accountId: 'your-bamboohr-account-id',
baseUrl: 'https://api.stackone.com',
});

// Fetch all tools for this account via MCP
const tools = await toolset.fetchTools();
expect(tools.length).toBeGreaterThan(0);

// Convert to AI SDK tools
const aiSdkTools = await tools.toAISDK();
expect(aiSdkTools).toBeDefined();
expect(Object.keys(aiSdkTools).length).toBeGreaterThan(0);

// Verify the tools have the expected structure
const toolNames = Object.keys(aiSdkTools);
expect(toolNames).toContain('bamboohr_list_employees');
expect(toolNames).toContain('bamboohr_get_employee');

// The AI SDK will automatically call the tool if needed
const { text } = await generateText({
model: openai('gpt-5'),
tools: aiSdkTools,
prompt: 'Get all details about employee with id: c28xIQaWQ6MzM5MzczMDA2NzMzMzkwNzIwNA',
stopWhen: stepCountIs(3),
});

// The mocked OpenAI response includes 'Michael' in the text
expect(text).toContain('Michael');
});
});
8 changes: 8 additions & 0 deletions examples/ai-sdk-integration.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
/**
* This example shows how to use StackOne tools with the AI SDK.
*
* The AI SDK provides an agent-like pattern through the `stopWhen` parameter
* with `stepCountIs()`. This creates a multi-step tool loop where the model
* can autonomously call tools and reason over results until the stop condition
* is met.
*
* In AI SDK v6+, you can use the `ToolLoopAgent` class for more explicit
* agent functionality.
*/

import assert from 'node:assert';
Expand Down
140 changes: 140 additions & 0 deletions examples/claude-agent-sdk-integration.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
/**
* E2E test for claude-agent-sdk-integration.ts example
*
* Tests the setup of StackOne tools with Claude Agent SDK.
*
* Note: The Claude Agent SDK spawns a subprocess to run claude-code, which
* requires the ANTHROPIC_API_KEY environment variable and a running claude-code
* installation. This test validates the tool setup and MCP server creation,
* but does not test the actual query execution.
*/

import { tool, createSdkMcpServer } from '@anthropic-ai/claude-agent-sdk';
import { z } from 'zod';
import { StackOneToolSet } from '../src';

describe('claude-agent-sdk-integration example e2e', () => {
beforeEach(() => {
vi.stubEnv('STACKONE_API_KEY', 'test-key');
});

afterEach(() => {
vi.unstubAllEnvs();
});

it('should fetch tools and create Claude Agent SDK tool wrapper', async () => {
const toolset = new StackOneToolSet({
accountId: 'your-bamboohr-account-id',
baseUrl: 'https://api.stackone.com',
});

// Fetch all tools for this account via MCP
const tools = await toolset.fetchTools();
expect(tools.length).toBeGreaterThan(0);

// Get a specific tool
const employeeTool = tools.getTool('bamboohr_get_employee');
expect(employeeTool).toBeDefined();
assert(employeeTool !== undefined);

// Create Claude Agent SDK tool from 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' as const, text: JSON.stringify(result) }],
};
},
);

expect(getEmployeeTool.name).toBe('bamboohr_get_employee');
expect(getEmployeeTool.description).toContain('employee');
expect(getEmployeeTool.inputSchema).toHaveProperty('id');
expect(typeof getEmployeeTool.handler).toBe('function');
});

it('should create MCP server with StackOne tools', async () => {
const toolset = new StackOneToolSet({
accountId: 'your-bamboohr-account-id',
baseUrl: 'https://api.stackone.com',
});

const tools = await toolset.fetchTools();
const employeeTool = tools.getTool('bamboohr_get_employee');
assert(employeeTool !== undefined);

// Create Claude Agent SDK 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' as const, text: JSON.stringify(result) }],
};
},
);

// Create an MCP server with the StackOne tool
const mcpServer = createSdkMcpServer({
name: 'stackone-tools',
version: '1.0.0',
tools: [getEmployeeTool],
});

// Verify MCP server was created
expect(mcpServer).toBeDefined();
expect(mcpServer.name).toBe('stackone-tools');
expect(mcpServer.instance).toBeDefined();
});

it('should execute tool handler directly', async () => {
const toolset = new StackOneToolSet({
accountId: 'your-bamboohr-account-id',
baseUrl: 'https://api.stackone.com',
});

const tools = await toolset.fetchTools();
const employeeTool = tools.getTool('bamboohr_get_employee');
assert(employeeTool !== undefined);

// Create Claude Agent SDK 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' as const, text: JSON.stringify(result) }],
};
},
);

// Execute the tool handler directly
const result = await getEmployeeTool.handler(
{ id: 'c28xIQaWQ6MzM5MzczMDA2NzMzMzkwNzIwNA' },
{} as unknown,
);

expect(result).toBeDefined();
expect(result.content).toHaveLength(1);
expect(result.content[0]?.type).toBe('text');

// Parse the result text and verify it contains employee data
const textContent = result.content[0];
assert(textContent?.type === 'text');
const data = JSON.parse(textContent.text) as unknown;
expect(data).toHaveProperty('data');
});
});
Loading
Loading