mirror of
https://github.com/Sea-Haven-Industries/sh-mcp.git
synced 2026-10-07 09:19:13 +00:00
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).
228 lines
6.4 KiB
TypeScript
228 lines
6.4 KiB
TypeScript
/**
|
|
* 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;
|
|
}
|