diff --git a/services/mcp-server/src/lib/platform-client.ts b/services/mcp-server/src/lib/platform-client.ts index a53a7934..8fed76d1 100644 --- a/services/mcp-server/src/lib/platform-client.ts +++ b/services/mcp-server/src/lib/platform-client.ts @@ -66,20 +66,34 @@ export async function telemetryQuery( } export interface TelemetryCluster { + /** Cluster doc ID: ${fingerprint}:${yyyyMM} */ + id: string; + /** Partition key: ${productId}:${platform}:${module} */ + pk: string; fingerprint: string; - count: number; - lastSeen: string; - sample: unknown; + severity: 'warn' | 'error' | 'fatal'; + totalCount: number; + lastSeenAt: string; + firstSeenAt: string; + platform: string; + module: string; + eventName: string; + sampleMessage?: string; + status: 'open' | 'resolved' | 'ignored'; + resolvedBy?: string; + resolvedAt?: string; } export async function telemetryClusters( - params: { productId: string; from?: string; to?: string }, + params: { from?: string; to?: string; platform?: string; module?: string }, opts: PlatformClientOptions -): Promise<{ clusters: TelemetryCluster[] }> { - const qs = new URLSearchParams({ productId: params.productId }); +): Promise<{ clusters: TelemetryCluster[]; total: number }> { + const qs = new URLSearchParams(); if (params.from) qs.set('from', params.from); if (params.to) qs.set('to', params.to); - return platformFetch<{ clusters: TelemetryCluster[] }>( + if (params.platform) qs.set('platform', params.platform); + if (params.module) qs.set('module', params.module); + return platformFetch<{ clusters: TelemetryCluster[]; total: number }>( `/api/telemetry/clusters?${qs}`, { method: 'GET' }, opts diff --git a/services/mcp-server/src/modules/a2a/agents/telemetry-analyst.ts b/services/mcp-server/src/modules/a2a/agents/telemetry-analyst.ts index 9dcd12df..0a1ea314 100644 --- a/services/mcp-server/src/modules/a2a/agents/telemetry-analyst.ts +++ b/services/mcp-server/src/modules/a2a/agents/telemetry-analyst.ts @@ -5,22 +5,9 @@ */ import { randomUUID } from 'node:crypto'; -import { telemetryClusters } from '../../../lib/platform-client.js'; +import { telemetryClusters, type TelemetryCluster } from '../../../lib/platform-client.js'; import type { DispatchDecision, TelemetryFindings, ClusterRef } from '../types.js'; -interface RawCluster { - id?: string; - pk?: string; - fingerprint?: string; - severity?: string; - totalCount?: number; - lastSeenAt?: string; - platform?: string; - module?: string; - sampleMessage?: string; - status?: string; -} - export async function analyze( decision: DispatchDecision, opts: { token?: string } @@ -34,25 +21,25 @@ export async function analyze( try { const result = await telemetryClusters( - { productId: brief.productId, from: resolvedTimeWindow.from, to: resolvedTimeWindow.to }, + { from: resolvedTimeWindow.from, to: resolvedTimeWindow.to }, { token: opts.token, requestId: runContext.requestId, productId: brief.productId } ); - const raw: RawCluster[] = (result as { clusters?: RawCluster[] }).clusters ?? []; - - clusters = raw - .filter(c => c.status !== 'resolved' && c.status !== 'ignored') - .map(c => ({ - clusterId: c.id ?? '', - pk: c.pk ?? '', - fingerprint: c.fingerprint ?? '', - severity: c.severity ?? 'error', - totalCount: c.totalCount ?? 0, - lastSeenAt: c.lastSeenAt ?? '', - platform: c.platform ?? '', - module: c.module ?? '', - sampleMessage: c.sampleMessage, - })); + clusters = result.clusters + .filter((c: TelemetryCluster) => c.status !== 'resolved' && c.status !== 'ignored') + .map( + (c: TelemetryCluster): ClusterRef => ({ + clusterId: c.id, + pk: c.pk, + fingerprint: c.fingerprint, + severity: c.severity, + totalCount: c.totalCount, + lastSeenAt: c.lastSeenAt, + platform: c.platform, + module: c.module, + sampleMessage: c.sampleMessage, + }) + ); // Filter by platform if the incident report names one if (brief.userReport.platform) { diff --git a/services/mcp-server/src/modules/a2a/pipeline-tool.ts b/services/mcp-server/src/modules/a2a/pipeline-tool.ts index 6de6e2e3..ab9686f6 100644 --- a/services/mcp-server/src/modules/a2a/pipeline-tool.ts +++ b/services/mcp-server/src/modules/a2a/pipeline-tool.ts @@ -5,7 +5,6 @@ * Agents run in sequence: Dispatcher → TelemetryAnalyst → DiagnosticsOrchestrator → ReportWriter. */ -import { z } from 'zod'; import { registerTool } from '../tools/registry.js'; import { SupportIncidentBriefSchema } from './types.js'; import { runIncidentPipeline } from './runner.js'; @@ -22,14 +21,7 @@ registerTool({ 'Requires admin role.', ].join(' '), requiredRole: 'admin', - inputSchema: SupportIncidentBriefSchema.extend({ - openDiagnosticsSession: z - .boolean() - .default(false) - .describe( - 'Open a live diagnostics session for the affected user (requires userId or anonymousInstallId)' - ), - }), + inputSchema: SupportIncidentBriefSchema, async execute(args, req: McpToolRequest) { const token = req.headers.authorization?.slice(7); return runIncidentPipeline(args, { diff --git a/services/mcp-server/src/modules/platform/telemetry-tools.ts b/services/mcp-server/src/modules/platform/telemetry-tools.ts index 86d1faf2..36651bba 100644 --- a/services/mcp-server/src/modules/platform/telemetry-tools.ts +++ b/services/mcp-server/src/modules/platform/telemetry-tools.ts @@ -39,11 +39,10 @@ registerTool({ to: z.string().optional().describe('ISO 8601 end time'), }), async execute(args, req) { - return telemetryClusters(args, { - token: tokenOf(req), - requestId: req.id, - productId: args.productId, - }); + return telemetryClusters( + { from: args.from, to: args.to }, + { token: tokenOf(req), requestId: req.id, productId: args.productId } + ); }, }); diff --git a/services/mcp-server/src/modules/support/debug-pack.ts b/services/mcp-server/src/modules/support/debug-pack.ts index a938eaeb..2773101a 100644 --- a/services/mcp-server/src/modules/support/debug-pack.ts +++ b/services/mcp-server/src/modules/support/debug-pack.ts @@ -44,11 +44,8 @@ registerTool({ let clusters: unknown[] = []; let clusterError: string | undefined; try { - const result = await telemetryClusters( - { productId: args.productId, from: args.from, to: args.to }, - opts - ); - clusters = result.clusters ?? []; + const result = await telemetryClusters({ from: args.from, to: args.to }, opts); + clusters = result.clusters; } catch (err) { clusterError = err instanceof Error ? err.message : String(err); }