import { describe, it, expect } from 'vitest'; import { ToolRegistry, defineTool } from './registry.js'; import { visibleTools } from './visibility.js'; import type { AuthContext, Scope } from './types.js'; function tool(name: string, requiredScope: Scope, tier: 'ops' | 'finance' = 'ops') { return defineTool({ name, description: name, tier, requiredScope, inputSchema: { type: 'object' }, handler: async () => ({}), }); } function registry(): ToolRegistry { const r = new ToolRegistry(); r.register(tool('lookup', 'ops:read')); r.register(tool('search_inbox', 'gmail:self')); r.register(tool('lookup_payment', 'finance:read', 'finance')); return r; } const ctx = (scopes: Scope[]): AuthContext => ({ sub: 'u', scopes, aud: 'a' }); describe('visibleTools (tool-hiding)', () => { it('shows only tools whose requiredScope the caller holds', () => { const names = visibleTools(registry(), ctx(['ops:read'])).map((t) => t.name); expect(names).toEqual(['lookup']); }); it('hides finance tools from a caller without finance:read', () => { const names = visibleTools(registry(), ctx(['ops:read', 'gmail:self'])).map((t) => t.name); expect(names).toContain('search_inbox'); expect(names).not.toContain('lookup_payment'); }); it('reveals finance tools to a finance caller', () => { const names = visibleTools(registry(), ctx(['finance:read'])).map((t) => t.name); expect(names).toEqual(['lookup_payment']); }); it('shows nothing to a scope-less caller', () => { expect(visibleTools(registry(), ctx([]))).toHaveLength(0); }); });