48 lines
2.1 KiB
TypeScript
48 lines
2.1 KiB
TypeScript
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 })
|
|
}
|
|
}
|