import type { Request, Response } from 'express' import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js' import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js' import { z } from 'zod/v4' import { config } from './config.js' import { pool } from './db.js' import { log } from './log.js' export function captureLimit(value: number) { return Number.isFinite(value) ? Math.max(1, Math.min(200, Math.trunc(value))) : 50 } export function createFieldLensMcpServer() { const server = new McpServer({ name: 'fieldlens', version: '1.0.0' }) server.registerTool('list_print_captures', { description: 'Read recent FieldLens printer capture diagnostics for the authorized tenant. No print document or barcode values are included.', inputSchema: { limit: z.number().int().min(1).max(200).optional() }, annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }, }, async ({ limit }) => { const result = await pool.query(` SELECT id, device_id AS "deviceId", source, stream_format AS "streamFormat", byte_count AS "byteCount", barcode_count AS "barcodeCount", forwarded, failure_kind AS "failureKind", app_version AS "appVersion", captured_at AS "capturedAt", received_at AS "receivedAt" FROM print_capture_diagnostics WHERE tenant_id = $1 ORDER BY captured_at DESC LIMIT $2 `, [config.tenantId, captureLimit(limit ?? 50)]) return { content: [{ type: 'text', text: JSON.stringify({ captures: result.rows }) }] } }) return server } export async function handleMcpRequest(req: Request, res: Response) { const server = createFieldLensMcpServer() const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, enableJsonResponse: true }) res.once('close', () => { void transport.close() void server.close() }) try { await server.connect(transport) await transport.handleRequest(req, res, req.body) } catch { log('error', 'mcp_request_failed') if (!res.headersSent) res.status(500).json({ jsonrpc: '2.0', error: { code: -32603, message: 'Internal server error' }, id: null }) } }