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
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
"publint": "catalog:dev",
"tsdown": "catalog:dev",
"type-fest": "catalog:dev",
"typescript": "catalog:dev",
"unplugin-unused": "catalog:dev",
"vitest": "catalog:dev"
},
Expand Down
6 changes: 6 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pnpm-workspace.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ catalogs:
publint: ^0.3.12
tsdown: ^0.17.2
type-fest: ^4.41.0
typescript: ^5.8.3
Copy link

Copilot AI Dec 14, 2025

Choose a reason for hiding this comment

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

The catalog specifies TypeScript version "^5.8.3" but the lockfile resolves to version "5.9.3". This means 5.9.3 satisfies the semver range ^5.8.3, which is expected behavior for caret ranges in semantic versioning. However, this creates a version drift between what's specified in the catalog and what's actually installed. Consider either:

  1. Updating the catalog to specify "^5.9.3" to match the installed version
  2. Or pinning to "~5.8.3" if you specifically want to stay on 5.8.x versions

This ensures the catalog accurately reflects the intended version constraints.

Suggested change
typescript: ^5.8.3
typescript: ^5.9.3

Copilot uses AI. Check for mistakes.
unplugin-unused: ^0.5.4
vitest: ^4.0.15
zod: ^4.1.13
Expand Down
51 changes: 0 additions & 51 deletions src/toolsets.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,6 @@ import { type McpToolDefinition, createMcpApp } from '../mocks/mcp-server';
import { server } from '../mocks/node';
import { StackOneToolSet, ToolSetConfigError } from './toolsets';

/**
* Test helper: Extends StackOneToolSet to expose private methods for testing
*/
class TestableStackOneToolSet extends StackOneToolSet {
// Expose private methods for testing
public testMatchesFilter(toolName: string, filterPattern: string | string[]): boolean {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Accessing private method for testing
return (this as any).matchesFilter(toolName, filterPattern);
}

public testMatchGlob(str: string, pattern: string): boolean {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Accessing private method for testing
return (this as any).matchGlob(str, pattern);
}
}

describe('StackOneToolSet', () => {
beforeEach(() => {
vi.stubEnv('STACKONE_API_KEY', 'test_key');
Expand Down Expand Up @@ -174,41 +158,6 @@ describe('StackOneToolSet', () => {
});
});

describe('glob and filter matching', () => {
it('should correctly match glob patterns', () => {
const toolset = new TestableStackOneToolSet({ apiKey: 'test_key' });

expect(toolset.testMatchGlob('bamboohr_get_employee', 'bamboohr_*')).toBe(true);
expect(toolset.testMatchGlob('bamboohr_get_employee', 'salesforce_*')).toBe(false);
expect(toolset.testMatchGlob('bamboohr_get_employee', '*_get_*')).toBe(true);
expect(toolset.testMatchGlob('bamboohr_get_employee', 'bamboohr_get_?mployee')).toBe(true);
expect(toolset.testMatchGlob('bamboohr.get.employee', 'bamboohr.get.employee')).toBe(true);
});

it('should correctly filter tools with a pattern', () => {
const toolset = new TestableStackOneToolSet({ apiKey: 'test_key' });

expect(toolset.testMatchesFilter('bamboohr_get_employee', 'bamboohr_*')).toBe(true);
expect(toolset.testMatchesFilter('salesforce_get_contact', 'bamboohr_*')).toBe(false);
expect(
toolset.testMatchesFilter('bamboohr_get_employee', ['bamboohr_*', 'salesforce_*']),
).toBe(true);
expect(
toolset.testMatchesFilter('salesforce_get_contact', ['bamboohr_*', 'salesforce_*']),
).toBe(true);
expect(
toolset.testMatchesFilter('workday_get_candidate', ['bamboohr_*', 'salesforce_*']),
).toBe(false);

// Test negative patterns
expect(toolset.testMatchesFilter('bamboohr_get_employee', ['*', '!salesforce_*'])).toBe(true);
expect(toolset.testMatchesFilter('salesforce_get_contact', ['*', '!salesforce_*'])).toBe(
false,
);
expect(toolset.testMatchesFilter('bamboohr_get_employee', ['*', '!bamboohr_*'])).toBe(false);
});
});

describe('fetchTools (MCP integration)', () => {
it('creates tools from MCP catalog and wires RPC execution', async () => {
const toolset = new StackOneToolSet({
Expand Down
26 changes: 0 additions & 26 deletions src/toolsets.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { defu } from 'defu';
import type { Arrayable } from 'type-fest';
import { DEFAULT_BASE_URL, UNIFIED_API_PREFIX } from './consts';
import { createFeedbackTool } from './feedback';
import { type StackOneHeaders, normaliseHeaders, stackOneHeadersSchema } from './headers';
Expand All @@ -13,7 +12,6 @@ import type {
RpcExecuteConfig,
ToolParameters,
} from './types';
import { toArray } from './utils/array';
import { StackOneError } from './utils/errors';

/**
Expand Down Expand Up @@ -346,30 +344,6 @@ export class StackOneToolSet {
return new Tools(filteredTools);
}

/**
* Check if a tool name matches a filter pattern
* @param toolName Tool name to check
* @param filterPattern Filter pattern or array of patterns
* @returns True if the tool name matches the filter pattern
*/
private matchesFilter(toolName: string, filterPattern: Arrayable<string>): boolean {
// Convert to array to handle both single string and array patterns
const patterns = toArray(filterPattern);

// Split into positive and negative patterns
const positivePatterns = patterns.filter((p) => !p.startsWith('!'));
const negativePatterns = patterns.filter((p) => p.startsWith('!')).map((p) => p.substring(1));

// If no positive patterns, treat as match all
const matchesPositive =
positivePatterns.length === 0 || positivePatterns.some((p) => this.matchGlob(toolName, p));

// If any negative pattern matches, exclude the tool
const matchesNegative = negativePatterns.some((p) => this.matchGlob(toolName, p));

return matchesPositive && !matchesNegative;
}

/**
* Check if a string matches a glob pattern
* @param str String to check
Expand Down
Loading