/** * MCP tool definitions for the knowledge-base package. * * Exports a factory so callers can inject the KnowledgeBaseClient * (production: BedrockKnowledgeBaseClient; tests: mock). */ import { defineTool, requireScope } from '@sh-mcp/shared'; import type { AuthContext, ToolDef } from '@sh-mcp/shared'; import type { KnowledgeBaseClient } from './client.js'; // --------------------------------------------------------------------------- // Input / output types // --------------------------------------------------------------------------- export interface SearchKnowledgeBaseInput { /** Natural-language question or keyword query. */ query: string; /** * Maximum number of passages to return (1–20). * Defaults to 5 when omitted. */ maxResults?: number; } export interface SearchKnowledgeBaseResultItem { /** Source document reference (S3 URI, Notion page title, etc.). */ source: string; /** Relevance score in [0, 1]. */ score: number; /** Relevant text passage. */ passage: string; } export interface SearchKnowledgeBaseOutput { /** Ordered list of matching passages, most relevant first. */ results: SearchKnowledgeBaseResultItem[]; /** Total number of results returned. */ count: number; } // --------------------------------------------------------------------------- // Tool factory // --------------------------------------------------------------------------- /** * Build the knowledge-base tools array with the supplied client injected. * * Production usage: * import { createBedrockClientFromEnv } from './client.js'; * const tools = createKnowledgeBaseTools(createBedrockClientFromEnv()); * * Test usage: * const tools = createKnowledgeBaseTools(mockClient); */ export function createKnowledgeBaseTools( client: KnowledgeBaseClient ): ToolDef[] { const searchKnowledgeBase = defineTool< SearchKnowledgeBaseInput, SearchKnowledgeBaseOutput >({ name: 'search_knowledge_base', description: 'Search the Sea Haven internal knowledge base for relevant information. ' + 'The knowledge base is populated from Notion pages, purchase-order records, ' + 'and work-order records. Use this tool to answer questions about company ' + 'procedures, vendor details, site information, or historical work orders.', tier: 'ops', requiredScope: 'ops:read', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Natural-language question or keyword search query.', minLength: 1, maxLength: 1000, }, maxResults: { type: 'integer', description: 'Maximum number of passages to return (1–20). Defaults to 5.', minimum: 1, maximum: 20, default: 5, }, }, required: ['query'], additionalProperties: false, }, async handler( input: SearchKnowledgeBaseInput, ctx: AuthContext ): Promise { // Server-side scope enforcement — never rely solely on UI tool-hiding. requireScope(ctx, 'ops:read'); // NOTE: This is an ops-tier tool. The output contains knowledge-base // passages (not finance data), so redact() is not called here. // Finance-tier tools in other packages MUST call redact() on sensitive fields. const results = await client.retrieve({ query: input.query, maxResults: input.maxResults ?? 5, }); return { results: results.map((r) => ({ source: r.source, score: r.score, passage: r.passage, })), count: results.length, }; }, }); return [searchKnowledgeBase]; }