Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
48 changes: 27 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,18 +67,17 @@ import { StackOneToolSet } from "@stackone/ai";

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

const tools = await toolset.fetchTools({
actions: ["hris_*"],
});
const tools = await toolset.fetchTools();

await openai.chat.completions.create({
model: "gpt-5.1",
messages: [
{
role: "system",
content: "You are a helpful HR assistant.",
content: "You are a helpful HR assistant using BambooHR.",
},
{
role: "user",
Expand All @@ -100,11 +99,10 @@ import { StackOneToolSet } from "@stackone/ai";

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

const tools = await toolset.fetchTools({
actions: ["hris_*"],
});
const tools = await toolset.fetchTools();

await generateText({
model: openai("gpt-5.1"),
Expand All @@ -122,10 +120,11 @@ 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("hris_list_employees");
const employeeTool = tools.getTool("bamboohr_list_employees");
const employees = await employeeTool.execute();
```

Expand All @@ -148,20 +147,27 @@ StackOne uses account IDs to identify different integrations. You can specify th
```typescript
import { StackOneToolSet } from "@stackone/ai";

// Method 1: Set at toolset initialisation
const toolset = new StackOneToolSet({ accountId: "your-account-id" });

// Method 2: Use setAccounts for filtering when fetching
toolset.setAccounts(["account-123", "account-456"]);
// Single account - simplest approach
const toolset = new StackOneToolSet({ accountId: "your-bamboohr-account" });
const tools = await toolset.fetchTools();

// Method 3: Set directly on a tool instance
// Multiple accounts - returns tools from both integrations
const multiAccountToolset = new StackOneToolSet();
const allTools = await multiAccountToolset.fetchTools({
accountIds: ["bamboohr-account-123", "workday-account-456"],
});

// Filter to specific integration when using multiple accounts
const bamboohrOnly = await multiAccountToolset.fetchTools({
accountIds: ["bamboohr-account-123", "workday-account-456"],
actions: ["bamboohr_*"], // Only BambooHR tools
});

// Set directly on a tool instance
tools.setAccountId("direct-account-id");
const currentAccountId = tools.getAccountId(); // Get the current account ID
```

[View full example](examples/account-id-usage.ts)

## Features

### Filtering Tools with fetchTools()
Expand Down Expand Up @@ -265,7 +271,7 @@ const searchResult = await filterTool.execute({
// Step 2: Execute a discovered tool
const executeTool = metaTools.getTool("meta_execute_tool");
const result = await executeTool.execute({
toolName: "hris_create_time_off",
toolName: "bamboohr_create_time_off",
params: {
employeeId: "emp_123",
startDate: "2024-01-15",
Expand Down Expand Up @@ -299,7 +305,7 @@ const toolset = new StackOneToolSet({
});

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

// Use dryRun to see the request details
const dryRunResult = await employeeTool.execute(
Expand Down Expand Up @@ -372,7 +378,7 @@ const feedbackTool = tools.getTool("meta_collect_tool_feedback");
const result = await feedbackTool.execute({
feedback: "The tools worked great! Very easy to use.",
account_id: "acc_123456",
tool_names: ["hris_list_employees", "hris_create_time_off"],
tool_names: ["bamboohr_list_employees", "bamboohr_create_time_off"],
});
```

Expand All @@ -385,14 +391,14 @@ The feedback tool supports both single and multiple account IDs. When you provid
await feedbackTool.execute({
feedback: "The tools worked great! Very easy to use.",
account_id: "acc_123456",
tool_names: ["hris_list_employees", "hris_create_time_off"],
tool_names: ["bamboohr_list_employees", "bamboohr_create_time_off"],
});

// Multiple account IDs (array)
await feedbackTool.execute({
feedback: "The tools worked great! Very easy to use.",
account_id: ["acc_123456", "acc_789012"],
tool_names: ["hris_list_employees", "hris_create_time_off"],
tool_names: ["bamboohr_list_employees", "bamboohr_create_time_off"],
});
```

Expand Down
8 changes: 3 additions & 5 deletions examples/ai-sdk-integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ if (!apiKey) {
}

// Replace with your actual account ID from StackOne dashboard
const accountId = 'your-hris-account-id';
const accountId = 'your-bamboohr-account-id';

const aiSdkIntegration = async (): Promise<void> => {
// Initialise StackOne
Expand All @@ -24,10 +24,8 @@ const aiSdkIntegration = async (): Promise<void> => {
baseUrl: process.env.STACKONE_BASE_URL ?? 'https://api.stackone.com',
});

// Fetch HRIS tools via MCP
const tools = await toolset.fetchTools({
actions: ['hris_get_*'],
});
// Fetch all tools for this account via MCP
const tools = await toolset.fetchTools();

// Convert to AI SDK tools
const aiSdkTools = await tools.toAISDK();
Expand Down
6 changes: 3 additions & 3 deletions examples/fetch-tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ console.log(`Loaded ${toolsByProviders.length} tools for HiBob and BambooHR`);
// Example 5: Filter by actions with exact match
console.log('\n=== Example 5: Filter by actions (exact match) ===');
const toolsByActions = await toolset.fetchTools({
actions: ['hris_list_employees', 'hris_create_employee'],
actions: ['bamboohr_list_employees', 'bamboohr_create_employee'],
});
console.log(`Loaded ${toolsByActions.length} tools matching exact action names`);

Expand All @@ -71,9 +71,9 @@ console.log(

// Execute a tool
console.log('\n=== Executing a tool ===');
const tool = allTools.getTool('hris_list_employees');
const tool = allTools.getTool('bamboohr_list_employees');
if (!tool) {
throw new Error('Tool hris_list_employees not found in the catalog');
throw new Error('Tool bamboohr_list_employees not found in the catalog');
}

const result = await tool.execute({
Expand Down
14 changes: 6 additions & 8 deletions examples/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
import process from 'node:process';

// Replace with your actual account ID from StackOne dashboard
const accountId = 'your-hris-account-id';
const accountId = 'your-bamboohr-account-id';

/**
* # Quickstart
Expand All @@ -54,17 +54,15 @@ const quickstart = async (): Promise<void> => {
baseUrl: process.env.STACKONE_BASE_URL ?? 'https://api.stackone.com',
});

// Fetch HRIS-related tools via MCP
const tools = await toolset.fetchTools({
actions: ['hris_*'],
});
// Fetch all tools for this account via MCP
const tools = await toolset.fetchTools();

// Verify we have tools
assert(tools.length > 0, 'Expected to find HRIS tools');
assert(tools.length > 0, 'Expected to find tools');

// Use a specific tool
const employeeTool = tools.getTool('hris_list_employees');
assert(employeeTool !== undefined, 'Expected to find hris_list_employees tool');
const employeeTool = tools.getTool('bamboohr_list_employees');
assert(employeeTool !== undefined, 'Expected to find bamboohr_list_employees tool');

// Execute the tool and verify the response
const result = await employeeTool.execute();
Expand Down
26 changes: 13 additions & 13 deletions examples/meta-tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ if (!apiKey) {
}

// Replace with your actual account ID from StackOne dashboard
const accountId = 'your-hris-account-id';
const accountId = 'your-bamboohr-account-id';

/**
* Example 1: Using meta tools with AI SDK for dynamic tool discovery
Expand Down Expand Up @@ -70,13 +70,13 @@ const metaToolsWithOpenAI = async (): Promise<void> => {
baseUrl: process.env.STACKONE_BASE_URL ?? 'https://api.stackone.com',
});

// Fetch HRIS tools via MCP
const hrisTools = await toolset.fetchTools({
actions: ['hris_*'],
// Fetch BambooHR tools via MCP
const bamboohrTools = await toolset.fetchTools({
actions: ['bamboohr_*'],
});

// Get meta tools
const metaTools = await hrisTools.metaTools();
const metaTools = await bamboohrTools.metaTools();
const openAIMetaTools = metaTools.toOpenAI();

// Create an HR assistant that can discover and use tools dynamically
Expand Down Expand Up @@ -161,9 +161,9 @@ const directMetaToolUsage = async (): Promise<void> => {
try {
// Prepare parameters based on the tool's schema
let params: Record<string, unknown> = {};
if (firstTool.name === 'hris_list_employees') {
if (firstTool.name === 'bamboohr_list_employees') {
params = { limit: 5 };
} else if (firstTool.name === 'hris_create_employee') {
} else if (firstTool.name === 'bamboohr_create_employee') {
params = {
name: 'John Doe',
email: 'john.doe@example.com',
Expand Down Expand Up @@ -194,16 +194,16 @@ const dynamicToolRouter = async (): Promise<void> => {
baseUrl: process.env.STACKONE_BASE_URL ?? 'https://api.stackone.com',
});

// Fetch tools from multiple categories via MCP
const hrisTools = await toolset.fetchTools({
actions: ['hris_*'],
// Fetch tools from multiple integrations via MCP
const bamboohrTools = await toolset.fetchTools({
actions: ['bamboohr_*'],
});
const atsTools = await toolset.fetchTools({
actions: ['ats_*'],
const workdayTools = await toolset.fetchTools({
actions: ['workday_*'],
});

// Combine tools
const combinedTools = new Tools([...hrisTools.toArray(), ...atsTools.toArray()]);
const combinedTools = new Tools([...bamboohrTools.toArray(), ...workdayTools.toArray()]);

// Get meta tools for the combined set
const metaTools = await combinedTools.metaTools();
Expand Down
14 changes: 6 additions & 8 deletions examples/openai-integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ if (!apiKey) {
}

// Replace with your actual account ID from StackOne dashboard
const accountId = 'your-hris-account-id';
const accountId = 'your-bamboohr-account-id';

const openaiIntegration = async (): Promise<void> => {
// Initialise StackOne
Expand All @@ -23,10 +23,8 @@ const openaiIntegration = async (): Promise<void> => {
baseUrl: process.env.STACKONE_BASE_URL ?? 'https://api.stackone.com',
});

// Fetch HRIS tools via MCP
const tools = await toolset.fetchTools({
actions: ['hris_get_*'],
});
// Fetch all tools for this account via MCP
const tools = await toolset.fetchTools();
const openAITools = tools.toOpenAI();

// Initialise OpenAI client
Expand All @@ -38,7 +36,7 @@ const openaiIntegration = async (): Promise<void> => {
messages: [
{
role: 'system',
content: 'You are a helpful assistant that can access HRIS information.',
content: 'You are a helpful assistant that can access BambooHR information.',
},
{
role: 'user',
Expand All @@ -58,8 +56,8 @@ const openaiIntegration = async (): Promise<void> => {
const toolCall = choice.message.tool_calls[0];
assert(toolCall.type === 'function', 'Expected tool call to be a function');
assert(
toolCall.function.name === 'hris_get_employee',
'Expected tool call to be hris_get_employee',
toolCall.function.name === 'bamboohr_get_employee',
'Expected tool call to be bamboohr_get_employee',
);

// Parse the arguments to verify they contain the expected fields
Expand Down
9 changes: 6 additions & 3 deletions examples/openai-responses-integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ const openaiResponsesIntegration = async (): Promise<void> => {
// Initialise StackOne
const toolset = new StackOneToolSet({ accountId });

// Fetch HRIS tools via MCP
// Fetch tools via MCP
const tools = await toolset.fetchTools({
actions: ['_list_*'],
actions: ['*_list_*'],
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 12, 2025

Choose a reason for hiding this comment

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

P1: Mismatch between fetched tools and expected tool call: The filter *_list_* only fetches list operations, but the assertion expects bamboohr_get_employee (a get operation) which won't be available. Either change the filter to include get operations (e.g., ['*_list_*', '*_get_*']) or update the assertion to expect a list tool.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At examples/openai-responses-integration.ts, line 25:

<comment>Mismatch between fetched tools and expected tool call: The filter `*_list_*` only fetches list operations, but the assertion expects `bamboohr_get_employee` (a get operation) which won&#39;t be available. Either change the filter to include get operations (e.g., `[&#39;*_list_*&#39;, &#39;*_get_*&#39;]`) or update the assertion to expect a list tool.</comment>

<file context>
@@ -20,9 +20,9 @@ const openaiResponsesIntegration = async (): Promise&lt;void&gt; =&gt; {
+	// Fetch tools via MCP
 	const tools = await toolset.fetchTools({
-		actions: [&#39;_list_*&#39;],
+		actions: [&#39;*_list_*&#39;],
 	});
 	const openAIResponsesTools = tools.toOpenAIResponses();
</file context>
Suggested change
actions: ['*_list_*'],
actions: ['*_list_*', '*_get_*'],
Fix with Cubic

});
const openAIResponsesTools = tools.toOpenAIResponses();

Expand All @@ -49,7 +49,10 @@ const openaiResponsesIntegration = async (): Promise<void> => {
assert(toolCalls.length > 0, 'Expected at least one tool call');

const toolCall = toolCalls[0];
assert(toolCall.name === 'hris_get_employee', 'Expected tool call to be hris_get_employee');
assert(
toolCall.name === 'bamboohr_get_employee',
'Expected tool call to be bamboohr_get_employee',
);

// Parse the arguments to verify they contain the expected fields
const args = JSON.parse(toolCall.arguments);
Expand Down
10 changes: 5 additions & 5 deletions mocks/handlers.openai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export const openaiHandlers = [
type: 'function_call',
id: 'call_mock_create',
call_id: 'call_mock_create',
name: 'hris_create_employee',
name: 'bamboohr_create_employee',
arguments: JSON.stringify({
name: 'John Doe',
personal_email: 'john.doe@example.com',
Expand Down Expand Up @@ -100,7 +100,7 @@ export const openaiHandlers = [
'';
const hasTools = body.tools && body.tools.length > 0;

// For openai-integration.ts - returns tool call for hris_get_employee
// For openai-integration.ts - returns tool call for bamboohr_get_employee
if (hasTools && userMessage.includes('c28xIQaWQ6MzM5MzczMDA2NzMzMzkwNzIwNA')) {
return HttpResponse.json({
id: 'chatcmpl-mock',
Expand All @@ -118,7 +118,7 @@ export const openaiHandlers = [
id: 'call_mock',
type: 'function',
function: {
name: 'hris_get_employee',
name: 'bamboohr_get_employee',
arguments: JSON.stringify({
id: 'c28xIQaWQ6MzM5MzczMDA2NzMzMzkwNzIwNA',
fields: 'phone_number',
Expand All @@ -134,7 +134,7 @@ export const openaiHandlers = [
});
}

// For human-in-the-loop.ts - returns tool call for hris_create_employee
// For human-in-the-loop.ts - returns tool call for bamboohr_create_employee
if (hasTools && userMessage.includes('Create a new employee')) {
return HttpResponse.json({
id: 'chatcmpl-mock-hitl',
Expand All @@ -152,7 +152,7 @@ export const openaiHandlers = [
id: 'call_mock_create',
type: 'function',
function: {
name: 'hris_create_employee',
name: 'bamboohr_create_employee',
arguments: JSON.stringify({
name: 'John Doe',
personal_email: 'john.doe@example.com',
Expand Down
Loading
Loading