From ea16f8df19267b5d840db535ae4854b96150d630 Mon Sep 17 00:00:00 2001 From: saravanakumardb1 Date: Thu, 5 Mar 2026 14:25:01 -0800 Subject: [PATCH] feat(mcp-server): add 6 platform.schemas.* + platform.sdks.* tools from DOMAIN_PACKAGES_AND_SDKS.md - platform.schemas.telemetryEvent: TelemetryEventDoc field schema + clustering keys + privacy rules - platform.schemas.diagnosticsSession: DebugSession schema + full endpoint map - platform.sdks.swiftCapabilities: ByteLystPlatformSDK 13 modules with phase + description - platform.sdks.kotlinCapabilities: Android/KMP SDK feature inventory + KMP adoption - platform.sdks.crossProductAdoption: 8 capabilities x 6 products adoption matrix + known gaps - platform.sdks.auditActivity: live telemetry query to verify SDK wired + event breakdown per platform/module - MCP server now at 97 tools across 16 namespaces --- .../src/modules/platform/sdk-tools.ts | 519 ++++++++++++++++++ services/mcp-server/src/server.ts | 1 + 2 files changed, 520 insertions(+) create mode 100644 services/mcp-server/src/modules/platform/sdk-tools.ts diff --git a/services/mcp-server/src/modules/platform/sdk-tools.ts b/services/mcp-server/src/modules/platform/sdk-tools.ts new file mode 100644 index 00000000..d36afa88 --- /dev/null +++ b/services/mcp-server/src/modules/platform/sdk-tools.ts @@ -0,0 +1,519 @@ +/** + * Platform SDK + Schema tools — sourced from DOMAIN_PACKAGES_AND_SDKS.md + * + * Tools: + * platform.schemas.telemetryEvent — TelemetryEventDoc field schema + * platform.schemas.diagnosticsSession — DebugSession field schema + * platform.sdks.swiftCapabilities — ByteLystPlatformSDK Swift modules + status + * platform.sdks.kotlinCapabilities — Android/KMP SDK feature availability + * platform.sdks.crossProductAdoption — per-product SDK capability adoption matrix + * platform.sdks.auditActivity — live telemetry query to verify SDK wired + active + */ + +import { z } from 'zod'; +import { registerTool } from '../tools/registry.js'; +import type { McpToolRequest } from '../tools/types.js'; +import { telemetryQuery } from '../../lib/platform-client.js'; + +// ── Static schema definitions ───────────────────────────────────────────────── + +const TELEMETRY_EVENT_SCHEMA = { + description: 'TelemetryEventDoc — ingested by platform-service POST /api/telemetry/events', + fields: { + productId: { + type: 'string', + required: true, + description: 'Product identifier (e.g. lysnrai, mindlyst, chronomind)', + }, + installId: { + type: 'string', + required: true, + description: 'Anonymous install ID from @bytelyst/telemetry-client', + }, + eventName: { + type: 'string', + required: true, + description: 'Dot-separated event name (e.g. dictation.start, sync.conflict)', + }, + module: { + type: 'string', + required: true, + description: 'Source module (e.g. audio, sync, keyboard_extension)', + }, + platform: { + type: 'enum', + required: true, + values: ['ios', 'android', 'web', 'desktop', 'watch', 'macos'], + description: 'Client platform', + }, + severity: { + type: 'enum', + required: true, + values: ['info', 'warn', 'error', 'fatal'], + description: 'Event severity level', + }, + message: { type: 'string', required: false, description: 'Human-readable event message' }, + metadata: { + type: 'object', + required: false, + description: 'Arbitrary key-value pairs (no PII)', + }, + appVersion: { type: 'string', required: false, description: 'App version string (e.g. 1.2.3)' }, + osVersion: { type: 'string', required: false, description: 'OS version string' }, + sessionId: { + type: 'string', + required: false, + description: 'Diagnostics session ID if captured during active session', + }, + timestamp: { + type: 'string', + required: false, + description: 'ISO 8601 timestamp; defaults to server time if omitted', + }, + }, + clusteringKeys: ['productId', 'platform', 'module', 'eventName', 'severity'], + privacyRules: [ + 'No PII in any field — use anonymousInstallId, never userId in metadata', + 'message field is indexed and searchable — keep safe for logging', + 'metadata values are stored as-is — validate at client before sending', + ], +}; + +const DIAGNOSTICS_SESSION_SCHEMA = { + description: 'DebugSession — created via platform-service POST /api/diagnostics/sessions', + fields: { + id: { type: 'string', required: true, description: 'Session UUID' }, + productId: { type: 'string', required: true, description: 'Product scope for this session' }, + status: { + type: 'enum', + required: true, + values: ['active', 'completed', 'expired', 'cancelled'], + }, + collectionLevel: { + type: 'enum', + required: true, + values: ['standard', 'debug', 'trace'], + description: 'standard=info+, debug=debug+, trace=all spans', + }, + captureLogs: { + type: 'boolean', + required: true, + description: 'Collect log entries from @bytelyst/diagnostics-client', + }, + captureNetwork: { type: 'boolean', required: true, description: 'Collect OTel network spans' }, + captureScreenshots: { + type: 'boolean', + required: true, + description: 'Allow screenshot uploads (SAS token issued)', + }, + maxDurationMinutes: { + type: 'number', + required: true, + description: 'Session auto-expires after this many minutes (max 60)', + }, + logCount: { type: 'number', required: true, description: 'Number of log entries ingested' }, + traceCount: { type: 'number', required: true, description: 'Number of OTel spans ingested' }, + createdAt: { type: 'string', required: true, description: 'ISO 8601 creation timestamp' }, + expiresAt: { type: 'string', required: true, description: 'ISO 8601 expiry timestamp' }, + targetUserId: { + type: 'string', + required: false, + description: 'Authenticated user ID being debugged', + }, + targetAnonymousId: { + type: 'string', + required: false, + description: 'Anonymous install ID (for unauthenticated debug)', + }, + }, + endpoints: { + create: 'POST /api/diagnostics/sessions', + list: 'GET /api/diagnostics/sessions?productId=&status=&limit=', + get: 'GET /api/diagnostics/sessions/:id', + update: 'PATCH /api/diagnostics/sessions/:id', + getLogs: 'GET /api/diagnostics/sessions/:id/logs', + getTraces: 'GET /api/diagnostics/sessions/:id/traces', + getScreenshots: 'GET /api/diagnostics/sessions/:id/screenshots', + }, +}; + +// ── Swift SDK capabilities ──────────────────────────────────────────────────── + +const SWIFT_SDK_CAPABILITIES = { + package: 'ByteLystPlatformSDK', + source: '../learning_ai_common_plat/packages/swift-platform-sdk/', + note: 'All product iOS apps consume this via thin wrappers in Platform/ directory', + modules: [ + { + id: 'BLPlatformConfig', + phase: 'mvp', + description: 'Product config loading (productId, baseURL, bundleId, appGroup)', + }, + { + id: 'BLPlatformClient', + phase: 'mvp', + description: 'Authenticated fetch wrapper — used internally by other modules', + }, + { + id: 'BLKeychain', + phase: 'mvp', + description: 'Secure credential storage (service-scoped CRUD)', + }, + { + id: 'BLTelemetryClient', + phase: 'mvp', + description: 'Structured event telemetry → platform-service /api/telemetry/events', + }, + { + id: 'BLKillSwitchClient', + phase: 'mvp', + description: 'Kill switch poll → /api/settings/kill-switch (fail-open)', + }, + { + id: 'BLCrashReporter', + phase: 'mvp', + description: 'Crash + fatal error reporting → telemetry event', + }, + { + id: 'BLAuthClient', + phase: 'phase2', + description: 'JWT login/refresh/logout via platform-service /api/auth', + }, + { + id: 'BLFeatureFlagClient', + phase: 'phase2', + description: 'Feature flag poll → /api/flags/poll (interval-based)', + }, + { + id: 'BLSyncEngine', + phase: 'phase2', + description: 'Offline queue + conflict detection + server sync', + }, + { + id: 'BLBlobClient', + phase: 'phase2', + description: 'Azure Blob SAS upload/download via platform-service', + }, + { + id: 'BLAuditLogger', + phase: 'phase2', + description: 'Structured audit trail → platform-service /api/audit', + }, + { + id: 'BLBiometricAuth', + phase: 'phase3', + description: 'FaceID/TouchID gate over BLKeychain credentials', + }, + { + id: 'BLLicenseClient', + phase: 'phase3', + description: 'License validation → platform-service /api/licenses', + }, + ], + wrapperPattern: + 'Each product creates a Platform/ dir with thin wrapper files — never re-implement SDK logic', +}; + +// ── Kotlin/Android SDK capabilities ────────────────────────────────────────── + +const KOTLIN_SDK_CAPABILITIES = { + note: 'Android products use direct Fastify REST client (no dedicated Kotlin SDK package yet)', + platformServiceIntegration: [ + { + feature: 'Telemetry', + class: 'TelemetryRepository / SyncRepository', + status: 'live', + products: ['lysnrai', 'mindlyst', 'jarvisjr', 'chronomind', 'nomgap', 'peakpulse'], + }, + { + feature: 'Feature flags', + class: 'FeatureFlagRepository', + status: 'live', + products: ['lysnrai', 'mindlyst', 'jarvisjr', 'chronomind', 'nomgap'], + }, + { + feature: 'Auth', + class: 'AuthRepository + Keystore JWT storage', + status: 'live', + products: ['lysnrai', 'mindlyst', 'jarvisjr', 'chronomind', 'nomgap'], + }, + { + feature: 'Platform sync', + class: 'SyncRepository (Retrofit / OkHttp)', + status: 'live', + products: ['lysnrai', 'chronomind', 'peakpulse'], + }, + { + feature: 'Kill switch', + class: 'KillSwitchRepository', + status: 'partial', + products: ['lysnrai', 'nomgap', 'jarvisjr'], + }, + { + feature: 'Diagnostics', + class: 'DiagnosticsRepository (stub)', + status: 'stub', + products: ['all — pending Kotlin SDK module'], + }, + ], + kmpSharedLogic: { + products: ['mindlyst'], + framework: 'Kotlin Multiplatform (KMP)', + sharedModule: 'shared/src/commonMain/', + note: 'MindLyst uses KMP for shared business logic across Android, iOS, and Web', + }, +}; + +// ── Cross-product SDK adoption matrix ──────────────────────────────────────── + +const CROSS_PRODUCT_ADOPTION = { + lastUpdated: '2026-03-05', + capabilities: [ + { + capability: 'Telemetry (@bytelyst/telemetry-client)', + products: { + lysnrai: true, + mindlyst: true, + chronomind: true, + nomgap: true, + jarvisjr: true, + peakpulse: true, + }, + notes: + 'All 6 products wired; MMKV-backed on RN, Beacon transport on web, OSLog bridge on iOS', + }, + { + capability: 'Diagnostics (@bytelyst/diagnostics-client)', + products: { + lysnrai: true, + mindlyst: true, + chronomind: true, + nomgap: true, + jarvisjr: true, + peakpulse: true, + }, + notes: 'All 6 products wired — session-scoped flush endpoints', + }, + { + capability: 'Feature flags (/api/flags/poll)', + products: { + lysnrai: true, + mindlyst: true, + chronomind: true, + nomgap: true, + jarvisjr: true, + peakpulse: false, + }, + notes: 'PeakPulse is iOS-only (uses BLFeatureFlagClient stub pending Phase 2)', + }, + { + capability: 'Kill switch (@bytelyst/kill-switch-client)', + products: { + lysnrai: true, + mindlyst: false, + chronomind: false, + nomgap: true, + jarvisjr: true, + peakpulse: true, + }, + notes: 'MindLyst + ChronoMind use direct GET /settings/kill-switch; not the shared client', + }, + { + capability: 'Auth (@bytelyst/auth-client)', + products: { + lysnrai: true, + mindlyst: true, + chronomind: true, + nomgap: true, + jarvisjr: true, + peakpulse: true, + }, + notes: + 'All products; JWT stored in Keychain (iOS), Keystore (Android), httpOnly cookies (web)', + }, + { + capability: 'Extraction service (@bytelyst/extraction)', + products: { + lysnrai: true, + mindlyst: true, + chronomind: false, + nomgap: false, + jarvisjr: false, + peakpulse: false, + }, + notes: + 'MindLyst (triage + memory-insight + reflection-enrichment); LysnrAI (transcript extraction)', + }, + { + capability: 'Platform sync (BLSyncEngine / SyncRepository)', + products: { + lysnrai: true, + mindlyst: false, + chronomind: true, + nomgap: false, + jarvisjr: false, + peakpulse: true, + }, + notes: 'LysnrAI sessions, ChronoMind timers/routines, PeakPulse sessions', + }, + { + capability: 'Blob storage (@bytelyst/blob)', + products: { + lysnrai: true, + mindlyst: true, + chronomind: false, + nomgap: false, + jarvisjr: false, + peakpulse: false, + }, + notes: 'LysnrAI audio recordings; MindLyst voice capture + image attachments', + }, + ], + gaps: [ + 'Diagnostics Kotlin SDK module pending — Android products use stub', + 'PeakPulse feature flags not yet wired (Phase 2 item)', + 'MindLyst + ChronoMind kill switch not using shared client package', + 'NomGap + JarvisJr platform sync not wired (fasting sessions / coaching sessions stored locally only)', + ], +}; + +// ── Tool registrations ──────────────────────────────────────────────────────── + +registerTool({ + name: 'platform.schemas.telemetryEvent', + description: + 'Returns the TelemetryEventDoc schema: all fields, types, required flags, descriptions, clustering keys, and privacy rules. Use this before constructing telemetry queries or building MCP tools that interact with telemetry data. No auth required beyond viewer role.', + requiredRole: 'viewer', + inputSchema: z.object({}), + async execute() { + return TELEMETRY_EVENT_SCHEMA; + }, +}); + +registerTool({ + name: 'platform.schemas.diagnosticsSession', + description: + 'Returns the DebugSession schema: all fields, types, status enum values, collection levels, and the full endpoint map for the diagnostics API. Use this when building tools or agents that create or interact with diagnostics sessions.', + requiredRole: 'viewer', + inputSchema: z.object({}), + async execute() { + return DIAGNOSTICS_SESSION_SCHEMA; + }, +}); + +registerTool({ + name: 'platform.sdks.swiftCapabilities', + description: + 'Returns the ByteLystPlatformSDK Swift module list with phase (mvp/phase2/phase3) and description. Use this to understand which iOS/macOS/watchOS platform capabilities are available and which products use which modules.', + requiredRole: 'viewer', + inputSchema: z.object({}), + async execute() { + return SWIFT_SDK_CAPABILITIES; + }, +}); + +registerTool({ + name: 'platform.sdks.kotlinCapabilities', + description: + 'Returns the Android/KMP SDK capability inventory: platform-service integration status per product, and KMP shared logic products. Use this to understand Android platform SDK adoption and identify gaps.', + requiredRole: 'viewer', + inputSchema: z.object({}), + async execute() { + return KOTLIN_SDK_CAPABILITIES; + }, +}); + +registerTool({ + name: 'platform.sdks.crossProductAdoption', + description: + 'Returns the cross-product SDK capability adoption matrix: for each of the 8 shared capabilities, shows which products have it wired + any known gaps. Use this for ecosystem health assessment or to identify integration opportunities.', + requiredRole: 'viewer', + inputSchema: z.object({}), + async execute() { + return CROSS_PRODUCT_ADOPTION; + }, +}); + +registerTool({ + name: 'platform.sdks.auditActivity', + description: [ + 'Live telemetry audit for a product — verifies whether the SDK is wired and actively sending events.', + 'Queries the last N days of telemetry events for the given productId and returns:', + 'event count, platform breakdown, top modules, top eventNames, and last seen timestamp.', + 'Use this to confirm a new SDK integration is working, or to detect a product that has gone silent.', + 'Requires admin role.', + ].join(' '), + requiredRole: 'admin', + inputSchema: z.object({ + productId: z + .enum(['lysnrai', 'mindlyst', 'chronomind', 'nomgap', 'jarvisjr', 'peakpulse']) + .describe('Product to audit'), + days: z + .number() + .int() + .min(1) + .max(30) + .default(7) + .describe('Lookback window in days (default 7)'), + }), + async execute(args, req: McpToolRequest) { + const token = req.headers.authorization?.slice(7); + const to = new Date().toISOString(); + const from = new Date(Date.now() - args.days * 24 * 60 * 60 * 1000).toISOString(); + + const result = await telemetryQuery( + { productId: args.productId, from, to, limit: 100 }, + { token, requestId: req.id, productId: args.productId } + ); + + const events = result.events as Array>; + + // Aggregate platform breakdown + const byPlatform: Record = {}; + const byModule: Record = {}; + const byEvent: Record = {}; + let lastSeenAt: string | null = null; + + for (const e of events) { + const p = String(e['platform'] ?? 'unknown'); + const m = String(e['module'] ?? 'unknown'); + const n = String(e['eventName'] ?? 'unknown'); + const ts = String(e['timestamp'] ?? ''); + byPlatform[p] = (byPlatform[p] ?? 0) + 1; + byModule[m] = (byModule[m] ?? 0) + 1; + byEvent[n] = (byEvent[n] ?? 0) + 1; + if (!lastSeenAt || ts > lastSeenAt) lastSeenAt = ts; + } + + const topModules = Object.entries(byModule) + .sort(([, a], [, b]) => b - a) + .slice(0, 5) + .map(([module, count]) => ({ module, count })); + + const topEvents = Object.entries(byEvent) + .sort(([, a], [, b]) => b - a) + .slice(0, 10) + .map(([eventName, count]) => ({ eventName, count })); + + const isActive = result.total > 0; + const sdkAdoption = CROSS_PRODUCT_ADOPTION.capabilities.map(cap => ({ + capability: cap.capability, + wired: cap.products[args.productId as keyof typeof cap.products] ?? false, + })); + + return { + productId: args.productId, + windowDays: args.days, + from, + to, + isActive, + totalEvents: result.total, + sampledEvents: events.length, + lastSeenAt, + byPlatform, + topModules, + topEvents, + sdkAdoption, + }; + }, +}); diff --git a/services/mcp-server/src/server.ts b/services/mcp-server/src/server.ts index 46aeb285..ef55af92 100644 --- a/services/mcp-server/src/server.ts +++ b/services/mcp-server/src/server.ts @@ -48,6 +48,7 @@ import './modules/peakpulse/peakpulse-tools.js'; import './modules/tracker/tracker-tools.js'; import './modules/platform/ops-tools.js'; import './modules/platform/webhooks-tools.js'; +import './modules/platform/sdk-tools.js'; const app = await createServiceApp({ name: 'mcp-server',