Skip to content
Closed
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
16 changes: 14 additions & 2 deletions .mcp.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
"mcpServers": {
"playwright": {
"type": "stdio",
"command": "pnpm",
"command": "bun",
"args": [
"dlx",
"x",
"@playwright/mcp@latest"
],
"env": {}
Expand All @@ -20,6 +20,18 @@
"grep": {
"type": "http",
"url": "https://mcp.grep.app"
},
"oramaDocs": {
"type": "stdio",
"command": "bun",
"args": [
"x",
"sitemcp",
"https://docs.orama.com/",
"--concurrency",
"10"
],
"env": {}
}
}
}
122 changes: 122 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,128 @@ These toolsets provide functionality to filter, transform, and execute tools. Th

Under the hood the StackOneToolSet uses the same OpenAPIParser as the OpenAPIToolSet, but provides some convenience methods for using StackOne API keys and account IDs.

### Meta Tools (Beta)

> [!WARNING]
> Meta tools are currently in beta and their API may change in future versions.

StackOne AI SDK includes two powerful meta tools that enable AI agents to discover and orchestrate tool usage intelligently:

#### Discovering Relevant Tools

Use the `metaRelevantTool()` method to get a tool that discovers other tools based on natural language queries:

```typescript
import { StackOneToolSet } from "@stackone/ai";

const toolset = new StackOneToolSet();
const tools = toolset.getTools("hris_*", "account-id");

// Get the meta tool for finding relevant tools
const relevantToolsFinder = tools.metaRelevantTool();

// Find tools for specific tasks
const result = await relevantToolsFinder.execute({
query: "list employees",
limit: 5,
minScore: 0.5
});

// Returns tools ranked by relevance with scores and match reasons
```

#### Executing Tool Chains

Use the `metaExecuteTool()` method to orchestrate multiple tool executions with parameter passing:

```typescript
const tools = toolset.getTools("hris_*", "account-id");

// Get the meta tool for executing tool chains
const toolChain = tools.metaExecuteTool();

// Execute a complex workflow
const result = await toolChain.execute({
steps: [
{
toolName: "hris_list_employees",
parameters: { page_size: "10" },
stepName: "Get recent employees"
},
{
toolName: "hris_get_employee",
parameters: {
id: "{{step0.result.items[0].id}}" // Reference previous results
},
condition: "{{step0.result.items.length}} > 0", // Conditional execution
stepName: "Get employee details"
}
],
accountId: "your-account-id"
Copy link
Contributor

Choose a reason for hiding this comment

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

adding account id here feels pretty weird. @glebedel what do you think?

});
```

#### Getting Both Meta Tools

You can get both meta tools at once using the `metaTools()` method:

```typescript
const tools = toolset.getTools("hris_*", "account-id");

// Get both meta tools
const { metaRelevantTool, metaExecuteTool } = tools.metaTools();

// Use them in your AI agent
const aiTools = new Tools([
metaRelevantTool,
metaExecuteTool
]).toAISDK();
```

[View full examples](examples/meta-tools.ts)

### Workflow Planning

While building agents you may find that your workflow is too complex for a general purpose agent.

StackOne AI SDK provides access to a state of the art planning agent which allows you to create, cache, and execute complex workflows on [verticals supported by StackOne](https://www.stackone.com/integrations).

For example, onboard a new hire from your ATS to your HRIS.

```typescript
import { StackOneToolSet } from "@stackone/ai";

const toolset = new StackOneToolSet();

const onboardWorkflow = await toolset.plan({
key: "custom_onboarding",
input: "Onboard the last new hire from Teamtailor to Workday",
model: "stackone-planner-latest",
tools: ["hris_*", "ats_*"],
accountIds: ["teamtailor_account_id", "workday_account_id"],
cache: true, // saves the plan to $HOME/.stackone/plans
});

// Execute the workflow
await onboardWorkflow.execute();
```

Or use it as a tool in a larger agent (using AI SDK)

```typescript
await generateText({
model: openai("gpt-4o"),
prompt: "You are a workplace agent, onboard the latest hires to our systems",
Comment on lines +99 to +130
Copy link
Contributor

Choose a reason for hiding this comment

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

remove this

tools: onboardWorkflow.toAISDK(),
maxSteps: 3,
});
```

> [!NOTE]
> The workflow planner is in closed beta and only available to design partners. Apply for the waitlist [here](https://www.stackone.com/demo).

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

## Installation

```bash
Expand Down
155 changes: 75 additions & 80 deletions bun.lock

Large diffs are not rendered by default.

194 changes: 194 additions & 0 deletions examples/meta-tools.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
/**
* Meta Tools Example
*
* This example demonstrates how to use the beta meta tools for intelligent
* tool discovery and orchestration.
*
* @beta These are beta features and may change in future versions
*/

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

// Example 1: Using metaRelevantTool to discover tools
async function discoverToolsExample() {
console.log('\n=== Example 1: Discovering Relevant Tools ===');

const toolset = new StackOneToolSet();
const tools = toolset.getTools('hris_*', ACCOUNT_IDS.HRIS);

// Get the meta tool for finding relevant tools
const relevantToolsFinder = tools.metaRelevantTool();

// Example queries for different use cases
const queries = [
'list employees',
'get employee',
'create employee',
'list companies',
'get company',
'list applications',
];

for (const query of queries) {
console.log(`\nSearching for: "${query}"`);
const result = await relevantToolsFinder.execute({
query,
limit: 3,
minScore: 0.5,
});

console.log(`Found ${result.resultsCount} tools:`);
for (const tool of result.tools as Array<{
name: string;
score: number;
matchReason: string;
}>) {
console.log(` - ${tool.name} (score: ${tool.score.toFixed(2)}) - ${tool.matchReason}`);
}
}
}

// Example 2: Using metaExecuteTool for complex workflows
async function toolChainExample() {
console.log('\n=== Example 2: Executing Tool Chains ===');

const toolset = new StackOneToolSet();
const tools = toolset.getTools('hris_*', ACCOUNT_IDS.HRIS);

// Get the meta tool for executing tool chains
const toolChain = tools.metaExecuteTool();

// Example: Employee onboarding workflow
console.log('\nExecuting employee onboarding workflow...');

const result = await toolChain.execute({
steps: [
{
toolName: 'hris_list_employees',
parameters: {
filter: {
updated_after: '2024-01-01T00:00:00Z',
},
page_size: '10',
},
stepName: 'Get recent employees',
},
{
toolName: 'hris_get_employee',
parameters: {
id: '{{step0.result.items[0].id}}', // Get first employee ID
},
stepName: 'Get employee details',
condition: '{{step0.result.items.length}} > 0',
},
],
accountId: ACCOUNT_IDS.HRIS,
Copy link
Contributor

Choose a reason for hiding this comment

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

we would want to inherit this from the toolset or the tools previously. we shouldnt supply account ids at runtime

});

console.log(`\nWorkflow execution ${result.success ? 'succeeded' : 'failed'}`);
console.log(`Total execution time: ${result.executionTime}ms`);

for (const step of result.stepResults as Array<{
stepIndex: number;
stepName: string;
success: boolean;
skipped?: boolean;
error?: string;
}>) {
console.log(`\nStep ${step.stepIndex + 1}: ${step.stepName}`);
console.log(` Status: ${step.success ? 'Success' : 'Failed'}`);
if (step.skipped) {
console.log(' Skipped due to condition');
}
if (step.error) {
console.log(` Error: ${step.error}`);
}
}
}

// Example 3: Using meta tools with AI agents
async function aiAgentExample() {
console.log('\n=== Example 3: Meta Tools with AI Agents ===');

// Check if OpenAI API key is set
if (!process.env.OPENAI_API_KEY) {
console.log('Skipping AI agent example - OPENAI_API_KEY environment variable not set');
return;
}

const toolset = new StackOneToolSet();
const tools = toolset.getTools('hris_*', ACCOUNT_IDS.HRIS);

// Get both meta tools
const { metaRelevantTool, metaExecuteTool } = tools.metaTools();

// Convert meta tools to AI SDK format
const aiTools = new Tools([metaRelevantTool, metaExecuteTool]).toAISDK();

try {
// Use with AI agent
const { text, toolCalls } = await generateText({
model: openai('gpt-4o-mini'),
tools: aiTools,
prompt: `You are an HR assistant. First, use meta_relevant_tool to find tools
related to listing employees. Then use meta_execute_tool to get the
list of employees and fetch details for the first one.`,
maxSteps: 5,
});

console.log('\nAI Agent Response:');
console.log(text);

console.log('\nTool calls made:');
for (const call of toolCalls || []) {
console.log(`- ${call.toolName}`);
}
} catch (error) {
console.error('Error in AI agent example:', error instanceof Error ? error.message : error);
}
}

// Example 4: Different ways to use meta tools
async function usageExample() {
console.log('\n=== Example 4: Different Ways to Use Meta Tools ===');

const toolset = new StackOneToolSet();

// Method 1: Get tools and then get meta tools
const tools = toolset.getTools('hris_*', ACCOUNT_IDS.HRIS);

// Get individual meta tools
const relevantTool = tools.metaRelevantTool();
console.log(`Got meta tool: ${relevantTool.name}`);

const executeTool = tools.metaExecuteTool();
console.log(`Got meta tool: ${executeTool.name}`);

// Get both meta tools at once
const { metaRelevantTool, metaExecuteTool } = tools.metaTools();
console.log(`Got both meta tools: ${metaRelevantTool.name}, ${metaExecuteTool.name}`);
}

// Main execution
async function main() {
console.log('Meta Tools Examples (Beta)');
console.log('==========================');
console.log('Note: These are beta features and may change in future versions.\n');

try {
await discoverToolsExample();
await toolChainExample();
await aiAgentExample();
await usageExample();
} catch (error) {
console.error('Error in examples:', error);
}
}

// Only run if this file is executed directly
if (import.meta.main) {
main();
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"format": "biome format --write ."
},
"dependencies": {
"@orama/orama": "^3.1.11",
"json-schema": "^0.4.0"
},
"devDependencies": {
Expand Down
12 changes: 12 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,15 @@ export type {
ParameterLocation,
ToolDefinition,
} from './types';

// Meta tools (beta)
export {
ExecuteToolChain,
GetRelevantTools,
type ToolChainConfig,
type ToolChainResult,
type ToolChainStep,
type ToolChainStepResult,
type ToolSearchConfig,
type ToolSearchResult,
} from './meta-tools';
4 changes: 4 additions & 0 deletions src/meta-tools/consts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/**
* Beta warning constant for meta tools
*/
export const BETA_WARNING = 'This is a beta feature and may change in future versions.';
Loading