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
35 changes: 35 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,41 @@ const result = await employeeTool?.execute({

`fetchTools()` reuses the credentials you already configured (for example via `STACKONE_API_KEY`) and binds the returned tool objects to StackOne's actions client.

#### Filtering Tools with fetchTools()

You can filter tools by account IDs, providers, and action patterns:

```typescript
// Filter by account IDs
toolset.setAccounts(['account-123', 'account-456']);
const tools = await toolset.fetchTools();
// OR
const tools = await toolset.fetchTools({ accountIds: ['account-123', 'account-456'] });

// Filter by providers
const tools = await toolset.fetchTools({ providers: ['hibob', 'bamboohr'] });

// Filter by actions with exact match
const tools = await toolset.fetchTools({
actions: ['hibob_list_employees', 'hibob_create_employees']
});

// Filter by actions with glob patterns
const tools = await toolset.fetchTools({ actions: ['*_list_employees'] });

// Combine multiple filters
const tools = await toolset.fetchTools({
accountIds: ['account-123'],
providers: ['hibob'],
actions: ['*_list_*']
});
```

This is especially useful when you want to:
- Limit tools to specific linked accounts
- Focus on specific HR/CRM/ATS providers
- Get only certain types of operations (e.g., all "list" operations)

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

### File Upload
Expand Down
57 changes: 53 additions & 4 deletions examples/fetch-tools.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Example: fetch the latest StackOne tool catalog and execute a tool.
* Example: fetch the latest StackOne tool catalog with filtering options.
*
* Set `STACKONE_API_KEY` (and optionally `STACKONE_BASE_URL`) before running.
* By default the script exits early in test environments where a real key is
Expand All @@ -24,10 +24,59 @@ const toolset = new StackOneToolSet({
baseUrl: process.env.STACKONE_BASE_URL ?? 'https://api.stackone.com',
});

const tools = await toolset.fetchTools();
console.log(`Loaded ${tools.length} tools`);
// Example 1: Fetch all tools
console.log('\n=== Example 1: Fetch all tools ===');
const allTools = await toolset.fetchTools();
console.log(`Loaded ${allTools.length} tools`);

const tool = tools.getTool('hris_list_employees');
// Example 2: Filter by account IDs using setAccounts()
console.log('\n=== Example 2: Filter by account IDs (using setAccounts) ===');
toolset.setAccounts(['account-123', 'account-456']);
const toolsByAccounts = await toolset.fetchTools();
console.log(`Loaded ${toolsByAccounts.length} tools for specified accounts`);

// Example 3: Filter by account IDs using options
console.log('\n=== Example 3: Filter by account IDs (using options) ===');
const toolsByAccountsOption = await toolset.fetchTools({
accountIds: ['account-789'],
});
console.log(`Loaded ${toolsByAccountsOption.length} tools for account-789`);

// Example 4: Filter by providers
console.log('\n=== Example 4: Filter by providers ===');
const toolsByProviders = await toolset.fetchTools({
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Oct 14, 2025

Choose a reason for hiding this comment

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

This provider-only example still inherits the account filter set earlier, so it will not actually demonstrate provider filtering in isolation and may produce empty results when those accounts lack these providers. Please clear the account IDs before this call or pass accountIds explicitly.

Prompt for AI agents
Address the following comment on examples/fetch-tools.ts at line 47:

<comment>This provider-only example still inherits the account filter set earlier, so it will not actually demonstrate provider filtering in isolation and may produce empty results when those accounts lack these providers. Please clear the account IDs before this call or pass accountIds explicitly.</comment>

<file context>
@@ -24,10 +24,59 @@ const toolset = new StackOneToolSet({
+
+// Example 4: Filter by providers
+console.log(&#39;\n=== Example 4: Filter by providers ===&#39;);
+const toolsByProviders = await toolset.fetchTools({
+  providers: [&#39;hibob&#39;, &#39;bamboohr&#39;],
+});
</file context>
Fix with Cubic

providers: ['hibob', 'bamboohr'],
});
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'],
});
console.log(`Loaded ${toolsByActions.length} tools matching exact action names`);

// Example 6: Filter by actions with glob pattern
console.log('\n=== Example 6: Filter by actions (glob pattern) ===');
const toolsByGlobPattern = await toolset.fetchTools({
actions: ['*_list_employees'],
});
console.log(`Loaded ${toolsByGlobPattern.length} tools matching *_list_employees pattern`);

// Example 7: Combine multiple filters
console.log('\n=== Example 7: Combine multiple filters ===');
const toolsCombined = await toolset.fetchTools({
accountIds: ['account-123'],
providers: ['hibob'],
actions: ['*_list_*'],
});
console.log(
`Loaded ${toolsCombined.length} tools for account-123, provider hibob, matching *_list_* pattern`
);

// Execute a tool
console.log('\n=== Executing a tool ===');
const tool = allTools.getTool('hris_list_employees');
if (!tool) {
throw new Error('Tool hris_list_employees not found in the catalog');
}
Expand Down
62 changes: 52 additions & 10 deletions src/toolsets/stackone.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,20 @@ export interface FetchToolsOptions {
* Only tools available on these accounts will be returned
*/
accountIds?: string[];

/**
* Filter tools by provider names
* Only tools from these providers will be returned
* @example ['hibob', 'bamboohr']
*/
providers?: string[];

/**
* Filter tools by action patterns with glob support
* Only tools matching these patterns will be returned
* @example ['*_list_employees', 'hibob_create_employees']
*/
actions?: string[];
}

/**
Expand Down Expand Up @@ -126,19 +140,16 @@ export class StackOneToolSet extends ToolSet {
}

/**
* Fetch tools from MCP with optional account ID filtering
* @param options Optional filtering options for account IDs
* Fetch tools from MCP with optional filtering
* @param options Optional filtering options for account IDs, providers, and actions
* @returns Collection of tools matching the filter criteria
*
* TODO: Add support for filtering by providers and actions
* - providers: Filter tools by provider names (e.g., ['hibob', 'bamboohr'])
* - actions: Filter tools by action patterns with glob support (e.g., ['*_list_employees'])
*/
async fetchTools(options?: FetchToolsOptions): Promise<Tools> {
// Use account IDs from options, or fall back to instance state
const effectiveAccountIds = options?.accountIds || this.accountIds;

// If account IDs are specified, fetch tools for each account and merge
// Fetch tools (with account filtering if needed)
let tools: Tools;
if (effectiveAccountIds.length > 0) {
const toolsPromises = effectiveAccountIds.map(async (accountId) => {
const headers = { 'x-account-id': accountId };
Expand All @@ -160,12 +171,43 @@ export class StackOneToolSet extends ToolSet {

const toolArrays = await Promise.all(toolsPromises);
const allTools = toolArrays.flat();
tools = new Tools(allTools);
} else {
// No account filtering - fetch all tools
tools = await super.fetchTools();
}

// Apply provider and action filters
return this.filterTools(tools, options);
}

/**
* Filter tools by providers and actions
* @param tools Tools collection to filter
* @param options Filtering options
* @returns Filtered tools collection
*/
private filterTools(tools: Tools, options?: FetchToolsOptions): Tools {
let filteredTools = tools.toArray();

return new Tools(allTools);
// Filter by providers if specified
if (options?.providers && options.providers.length > 0) {
const providerSet = new Set(options.providers.map((p) => p.toLowerCase()));
filteredTools = filteredTools.filter((tool) => {
// Extract provider from tool name (assuming format: provider_action)
const provider = tool.name.split('_')[0]?.toLowerCase();
return provider && providerSet.has(provider);
});
}

// Filter by actions if specified (with glob support)
if (options?.actions && options.actions.length > 0) {
filteredTools = filteredTools.filter((tool) =>
options.actions?.some((pattern) => this._matchGlob(tool.name, pattern))
);
}

// No account filtering - fetch all tools
return await super.fetchTools();
return new Tools(filteredTools);
}

/**
Expand Down
Loading
Loading