sh-mcp/packages/internal-data/src/tools.ts

229 lines
6.4 KiB
TypeScript
Raw Normal View History

/**
* 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<LookupWorkOrderInput, LookupWorkOrderOutput>({
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<LookupWorkOrderOutput> => {
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<LookupPurchaseOrderInput, LookupPurchaseOrderOutput>({
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<LookupPurchaseOrderOutput> => {
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<LookupSiteInput, LookupSiteOutput>({
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<LookupSiteOutput> => {
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;
}