Files
mcpctl/src/cli/src/commands/console/format-event.ts
Michal 03827f11e4 feat: eager vLLM warmup and smart page titles in paginate stage
- Add warmup() to LlmProvider interface for eager subprocess startup
- ManagedVllmProvider.warmup() starts vLLM in background on project load
- ProviderRegistry.warmupAll() triggers all managed providers
- NamedProvider proxies warmup() to inner provider
- paginate stage generates LLM-powered descriptive page titles when
  available, cached by content hash, falls back to generic "Page N"
- project-mcp-endpoint calls warmupAll() on router creation so vLLM
  is loading while the session initializes

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 19:07:39 +00:00

311 lines
12 KiB
TypeScript

/**
* Shared formatting functions for MCP traffic events.
*
* Extracted from inspect-app.tsx so they can be reused by
* the unified timeline, action area, and provenance views.
*/
import type { TrafficEventType } from './unified-types.js';
/** Safely dig into unknown objects */
export function dig(obj: unknown, ...keys: string[]): unknown {
let cur = obj;
for (const k of keys) {
if (cur === null || cur === undefined || typeof cur !== 'object') return undefined;
cur = (cur as Record<string, unknown>)[k];
}
return cur;
}
export function trunc(s: string, maxLen: number): string {
return s.length > maxLen ? s.slice(0, maxLen - 1) + '\u2026' : s;
}
export function nameList(items: unknown[], key: string, max: number): string {
if (items.length === 0) return '(none)';
const names = items.map((it) => dig(it, key) as string).filter(Boolean);
const shown = names.slice(0, max);
const rest = names.length - shown.length;
return shown.join(', ') + (rest > 0 ? ` +${rest} more` : '');
}
export function formatTime(ts: Date | string): string {
try {
const d = typeof ts === 'string' ? new Date(ts) : ts;
return d.toLocaleTimeString('en-GB', { hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit' });
} catch {
return '??:??:??';
}
}
/** Extract meaningful summary from request params (strips jsonrpc/id boilerplate) */
export function summarizeRequest(method: string, body: unknown): string {
const params = dig(body, 'params') as Record<string, unknown> | undefined;
switch (method) {
case 'initialize': {
const name = dig(params, 'clientInfo', 'name') ?? '?';
const ver = dig(params, 'clientInfo', 'version') ?? '';
const proto = dig(params, 'protocolVersion') ?? '';
return `client=${name}${ver ? ` v${ver}` : ''} proto=${proto}`;
}
case 'tools/call': {
const toolName = dig(params, 'name') as string ?? '?';
const args = dig(params, 'arguments') as Record<string, unknown> | undefined;
if (!args || Object.keys(args).length === 0) return `${toolName}()`;
const pairs = Object.entries(args).map(([k, v]) => {
const vs = typeof v === 'string' ? v : JSON.stringify(v);
return `${k}: ${trunc(vs, 40)}`;
});
return `${toolName}(${trunc(pairs.join(', '), 80)})`;
}
case 'resources/read': {
const uri = dig(params, 'uri') as string ?? '';
return uri;
}
case 'prompts/get': {
const name = dig(params, 'name') as string ?? '';
return name;
}
case 'tools/list':
case 'resources/list':
case 'prompts/list':
case 'notifications/initialized':
return '';
default: {
if (!params || Object.keys(params).length === 0) return '';
const s = JSON.stringify(params);
return trunc(s, 80);
}
}
}
/** Extract meaningful summary from response result */
export function summarizeResponse(method: string, body: unknown, durationMs?: number): string {
const error = dig(body, 'error') as { message?: string; code?: number } | undefined;
if (error) {
return `ERROR ${error.code ?? ''}: ${error.message ?? 'unknown'}`;
}
const result = dig(body, 'result') as Record<string, unknown> | undefined;
if (!result) return '';
let summary: string;
switch (method) {
case 'initialize': {
const name = dig(result, 'serverInfo', 'name') ?? '?';
const ver = dig(result, 'serverInfo', 'version') ?? '';
const caps = dig(result, 'capabilities') as Record<string, unknown> | undefined;
const capList = caps ? Object.keys(caps).filter((k) => caps[k] && Object.keys(caps[k] as object).length > 0) : [];
summary = `server=${name}${ver ? ` v${ver}` : ''}${capList.length ? ` caps=[${capList.join(',')}]` : ''}`;
break;
}
case 'tools/list': {
const tools = (result.tools ?? []) as unknown[];
summary = `${tools.length} tools: ${nameList(tools, 'name', 6)}`;
break;
}
case 'resources/list': {
const resources = (result.resources ?? []) as unknown[];
summary = `${resources.length} resources: ${nameList(resources, 'name', 6)}`;
break;
}
case 'prompts/list': {
const prompts = (result.prompts ?? []) as unknown[];
if (prompts.length === 0) { summary = '0 prompts'; break; }
summary = `${prompts.length} prompts: ${nameList(prompts, 'name', 6)}`;
break;
}
case 'tools/call': {
const content = (result.content ?? []) as unknown[];
const isError = result.isError;
const first = content[0];
const text = (dig(first, 'text') as string) ?? '';
const prefix = isError ? 'ERROR: ' : '';
if (text) { summary = prefix + trunc(text.replace(/\n/g, ' '), 100); break; }
summary = prefix + `${content.length} content block(s)`;
break;
}
case 'resources/read': {
const contents = (result.contents ?? []) as unknown[];
const first = contents[0];
const text = (dig(first, 'text') as string) ?? '';
if (text) { summary = trunc(text.replace(/\n/g, ' '), 80); break; }
summary = `${contents.length} content block(s)`;
break;
}
case 'notifications/initialized':
summary = 'ok';
break;
default: {
if (Object.keys(result).length === 0) { summary = 'ok'; break; }
const s = JSON.stringify(result);
summary = trunc(s, 80);
break;
}
}
if (durationMs !== undefined) {
return `[${durationMs}ms] ${summary}`;
}
return summary;
}
/** Format full event body for expanded detail view (multi-line, readable) */
export function formatBodyDetail(eventType: string, method: string, body: unknown): string[] {
const bodyObj = body as Record<string, unknown> | null;
if (!bodyObj) return ['(no body)'];
const lines: string[] = [];
if (eventType.includes('request') || eventType === 'client_notification') {
const params = bodyObj['params'] as Record<string, unknown> | undefined;
if (method === 'tools/call' && params) {
lines.push(`Tool: ${params['name'] as string}`);
const args = params['arguments'] as Record<string, unknown> | undefined;
if (args && Object.keys(args).length > 0) {
lines.push('Arguments:');
for (const [k, v] of Object.entries(args)) {
const vs = typeof v === 'string' ? v : JSON.stringify(v, null, 2);
for (const vl of vs.split('\n')) {
lines.push(` ${k}: ${vl}`);
}
}
}
} else if (method === 'initialize' && params) {
const ci = params['clientInfo'] as Record<string, unknown> | undefined;
lines.push(`Client: ${ci?.['name'] ?? '?'} v${ci?.['version'] ?? '?'}`);
lines.push(`Protocol: ${params['protocolVersion'] ?? '?'}`);
const caps = params['capabilities'] as Record<string, unknown> | undefined;
if (caps) lines.push(`Capabilities: ${JSON.stringify(caps)}`);
} else if (params && Object.keys(params).length > 0) {
for (const l of JSON.stringify(params, null, 2).split('\n')) {
lines.push(l);
}
} else {
lines.push('(empty params)');
}
} else if (eventType.includes('response')) {
const error = bodyObj['error'] as Record<string, unknown> | undefined;
if (error) {
lines.push(`Error ${error['code']}: ${error['message']}`);
if (error['data']) {
for (const l of JSON.stringify(error['data'], null, 2).split('\n')) {
lines.push(` ${l}`);
}
}
} else {
const result = bodyObj['result'] as Record<string, unknown> | undefined;
if (!result) {
lines.push('(empty result)');
} else if (method === 'tools/list') {
const tools = (result['tools'] ?? []) as Array<{ name: string; description?: string }>;
lines.push(`${tools.length} tools:`);
for (const t of tools) {
lines.push(` ${t.name}${t.description ? ` \u2014 ${trunc(t.description, 60)}` : ''}`);
}
} else if (method === 'resources/list') {
const resources = (result['resources'] ?? []) as Array<{ name: string; uri?: string; description?: string }>;
lines.push(`${resources.length} resources:`);
for (const r of resources) {
lines.push(` ${r.name}${r.uri ? ` (${r.uri})` : ''}${r.description ? ` \u2014 ${trunc(r.description, 50)}` : ''}`);
}
} else if (method === 'prompts/list') {
const prompts = (result['prompts'] ?? []) as Array<{ name: string; description?: string }>;
lines.push(`${prompts.length} prompts:`);
for (const p of prompts) {
lines.push(` ${p.name}${p.description ? ` \u2014 ${trunc(p.description, 60)}` : ''}`);
}
} else if (method === 'tools/call') {
const isErr = result['isError'];
const content = (result['content'] ?? []) as Array<{ type?: string; text?: string }>;
if (isErr) lines.push('(error response)');
for (const c of content) {
if (c.text) {
for (const l of c.text.split('\n')) {
lines.push(l);
}
} else {
lines.push(`[${c.type ?? 'unknown'} content]`);
}
}
} else if (method === 'initialize') {
const si = result['serverInfo'] as Record<string, unknown> | undefined;
lines.push(`Server: ${si?.['name'] ?? '?'} v${si?.['version'] ?? '?'}`);
lines.push(`Protocol: ${result['protocolVersion'] ?? '?'}`);
const caps = result['capabilities'] as Record<string, unknown> | undefined;
if (caps) {
lines.push('Capabilities:');
for (const [k, v] of Object.entries(caps)) {
if (v && typeof v === 'object' && Object.keys(v).length > 0) {
lines.push(` ${k}: ${JSON.stringify(v)}`);
}
}
}
const instructions = result['instructions'] as string | undefined;
if (instructions) {
lines.push('');
lines.push('Instructions:');
for (const l of instructions.split('\n')) {
lines.push(` ${l}`);
}
}
} else {
for (const l of JSON.stringify(result, null, 2).split('\n')) {
lines.push(l);
}
}
}
} else {
// Lifecycle events
for (const l of JSON.stringify(bodyObj, null, 2).split('\n')) {
lines.push(l);
}
}
return lines;
}
export interface FormattedEvent {
arrow: string;
color: string;
label: string;
detail: string;
detailColor?: string | undefined;
}
export function formatEventSummary(
eventType: TrafficEventType,
method: string | undefined,
body: unknown,
upstreamName?: string,
durationMs?: number,
): FormattedEvent {
const m = method ?? '';
switch (eventType) {
case 'client_request':
return { arrow: '\u2192', color: 'green', label: m, detail: summarizeRequest(m, body) };
case 'client_response': {
const detail = summarizeResponse(m, body, durationMs);
const hasError = detail.startsWith('ERROR');
return { arrow: '\u2190', color: 'blue', label: m, detail, detailColor: hasError ? 'red' : undefined };
}
case 'client_notification':
return { arrow: '\u25C2', color: 'magenta', label: m, detail: summarizeRequest(m, body) };
case 'upstream_request':
return { arrow: ' \u21E2', color: 'yellowBright', label: `${upstreamName ?? '?'}/${m}`, detail: summarizeRequest(m, body) };
case 'upstream_response': {
const detail = summarizeResponse(m, body, durationMs);
const hasError = detail.startsWith('ERROR');
return { arrow: ' \u21E0', color: 'yellowBright', label: `${upstreamName ?? '?'}/${m}`, detail, detailColor: hasError ? 'red' : undefined };
}
case 'session_created':
return { arrow: '\u25CF', color: 'cyan', label: 'session', detail: '' };
case 'session_closed':
return { arrow: '\u25CB', color: 'red', label: 'session', detail: 'closed' };
default:
return { arrow: '?', color: 'white', label: eventType, detail: '' };
}
}