sh-mcp/packages/knowledge-base/src/tools.ts

123 lines
3.7 KiB
TypeScript
Raw Normal View History

Phase 0b slice: monorepo scaffold + @sh-mcp/shared core + integration packages (#2) * Phase 0b slice: monorepo scaffold + shared core + integration packages The 0a-INDEPENDENT code slice (one-shot via af-0b-package-slice workflow: Haiku scaffold + Sonnet packages, Sonnet fix-to-green). Nothing deploys; no CDK/servers. - Monorepo scaffold: npm workspaces, strict TS (NodeNext), vitest (80% gate), eslint 9 flat config, prettier; ci.yaml/deploy.yaml callers (Node 24, enable-qemu). - @sh-mcp/shared: transport-agnostic core — Scope/AuthContext/ToolDef, ToolRegistry, redact()+maskValue() (PII), OpenAPI 3.1 generator. AUTH STUBBED behind an AuthProvider interface (TODO auth-layer-0a); JWT/aud/client_id/JWKS/deny-list deferred per design.md §2. - 9 integration packages (qbo, google-maps, internal-data, payments, knowledge-base, gmail, calendar, tasks, reminders): tools against shared, external deps mocked behind injected client interfaces; finance handlers call redact(). Verified green: tsc -b clean, vitest 245/245, eslint 0 errors. Auth mechanism intentionally deferred until the 0a spike resolves it (G16/§0.4). * Complete Cognito auth provider + Phase 1 build brief Finish the WIP CognitoAuthProvider (client_id allow-list as audience boundary, finance TTL ceiling, deny-list, scope-prefix stripping) with its test suite, and check in docs/build-plan-phase-1.md so the Phase 1 work has its governing brief in-tree (design.md §2.5). * ci: disable cdk synth for Phase 0b (no CDK app yet) The reusable ci-typescript-cdk workflow defaults run-cdk-synth: true, but the Phase 0b package scaffold has no cdk.json or stacks, so cdk synth fails with '--app is required'. Disable it here; Phase 1 re-enables it with the server CDK stubs.
2026-06-26 12:42:17 -04:00
/**
* 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<SearchKnowledgeBaseInput, SearchKnowledgeBaseOutput>[] {
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<SearchKnowledgeBaseOutput> {
// 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];
}