/** * Tool definitions for the internal-data package. * * Three read-only ops-tier tools backed by the injected InternalDataClient: * - lookup_work_order * - lookup_purchase_order * - lookup_site * * All three require the `ops:read` scope (§3 of design.md). * * Finance-tier note: these tools are ops-tier, so `redact()` is not called on * their responses. If a future refactor moves payment or financial amounts here, * call redact() on those fields per the design requirement that finance handlers * MUST redact sensitive output. * * The client is passed in at server startup (not imported from a module-level * singleton) so that tests can inject a mock without AWS credentials. */ import { defineTool, requireScope } from '@sh-mcp/shared'; import type { AuthContext } from '@sh-mcp/shared'; import type { InternalDataClient } from './client.js'; // --------------------------------------------------------------------------- // Input / output types // --------------------------------------------------------------------------- export interface LookupWorkOrderInput { workOrderId: string; } export interface LookupWorkOrderOutput { found: boolean; workOrder?: { workOrderId: string; title: string; status: string; siteId?: string; assignedTo?: string; createdAt: string; updatedAt: string; description?: string; }; } export interface LookupPurchaseOrderInput { purchaseOrderId: string; } export interface LookupPurchaseOrderOutput { found: boolean; purchaseOrder?: { purchaseOrderId: string; vendor: string; status: string; totalAmount?: number; currency?: string; issuedAt: string; updatedAt: string; lineItems?: Array<{ description: string; quantity: number; unitPrice: number }>; }; } export interface LookupSiteInput { siteId: string; } export interface LookupSiteOutput { found: boolean; site?: { siteId: string; name: string; address?: string; region?: string; status: string; assignedTechnicians?: string[]; }; } // --------------------------------------------------------------------------- // Tool factories — call makeTools(client) once at server startup // --------------------------------------------------------------------------- export function makeTools(client: InternalDataClient) { const lookupWorkOrder = defineTool({ name: 'lookup_work_order', description: 'Retrieve a Sea Haven work order by its ID. Returns current status, assigned site, ' + 'assigned technician, and description. Returns found=false when the ID does not exist.', tier: 'ops', requiredScope: 'ops:read', inputSchema: { type: 'object', required: ['workOrderId'], additionalProperties: false, properties: { workOrderId: { type: 'string', description: 'The unique work-order identifier (e.g. WO-20240101-001).', minLength: 1, maxLength: 128, }, }, }, handler: async ( input: LookupWorkOrderInput, ctx: AuthContext, ): Promise => { requireScope(ctx, 'ops:read'); const record = await client.getWorkOrder(input.workOrderId); if (record === null) { return { found: false }; } return { found: true, workOrder: { workOrderId: record.workOrderId, title: record.title, status: record.status, siteId: record.siteId, assignedTo: record.assignedTo, createdAt: record.createdAt, updatedAt: record.updatedAt, description: record.description, }, }; }, }); const lookupPurchaseOrder = defineTool({ name: 'lookup_purchase_order', description: 'Retrieve a Sea Haven purchase order by its ID. Returns vendor name, status, total amount, ' + 'and line items. Returns found=false when the ID does not exist.', tier: 'ops', requiredScope: 'ops:read', inputSchema: { type: 'object', required: ['purchaseOrderId'], additionalProperties: false, properties: { purchaseOrderId: { type: 'string', description: 'The unique purchase-order identifier (e.g. PO-2024-00123).', minLength: 1, maxLength: 128, }, }, }, handler: async ( input: LookupPurchaseOrderInput, ctx: AuthContext, ): Promise => { requireScope(ctx, 'ops:read'); const record = await client.getPurchaseOrder(input.purchaseOrderId); if (record === null) { return { found: false }; } return { found: true, purchaseOrder: { purchaseOrderId: record.purchaseOrderId, vendor: record.vendor, status: record.status, totalAmount: record.totalAmount, currency: record.currency, issuedAt: record.issuedAt, updatedAt: record.updatedAt, lineItems: record.lineItems, }, }; }, }); const lookupSite = defineTool({ name: 'lookup_site', description: 'Retrieve a Sea Haven site assignment record by its ID. Returns site name, address, ' + 'region, status, and assigned technicians. Returns found=false when the ID does not exist.', tier: 'ops', requiredScope: 'ops:read', inputSchema: { type: 'object', required: ['siteId'], additionalProperties: false, properties: { siteId: { type: 'string', description: 'The unique site identifier (e.g. SITE-NYC-001).', minLength: 1, maxLength: 128, }, }, }, handler: async (input: LookupSiteInput, ctx: AuthContext): Promise => { requireScope(ctx, 'ops:read'); const record = await client.getSite(input.siteId); if (record === null) { return { found: false }; } return { found: true, site: { siteId: record.siteId, name: record.name, address: record.address, region: record.region, status: record.status, assignedTechnicians: record.assignedTechnicians, }, }; }, }); return [lookupWorkOrder, lookupPurchaseOrder, lookupSite] as const; }