vigile_timeline
Fetch a security timeline for an incident or topic from Vigile memory.
Instructions
Fetch a security timeline for an incident or topic from Vigile memory.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| topic | No | Topic selector for timeline lookup | |
| incident_id | No | Incident ID selector (preferred when known) |
Implementation Reference
- src/tools/timeline.ts:7-58 (handler)The `timelineMemory` function is the core handler for the vigile_timeline tool. It accepts a baseUrl, apiKey, and selector (topic or incident_id), calls the Vigile API at /api/v1/memory/timeline, and returns a formatted timeline string with events, confidence, and provenance info.
export async function timelineMemory( baseUrl: string, apiKey: string, selector: { topic?: string; incident_id?: string }, ): Promise<string> { const body: Record<string, string> = {}; if (selector.incident_id) { body.incident_id = selector.incident_id; } else if (selector.topic) { body.topic = selector.topic; } else { return "timeline requires either topic or incident_id."; } const { ok, status, data } = await fetchVigile(baseUrl, apiKey, "/api/v1/memory/timeline", { method: "POST", body: JSON.stringify(body), }); if (!ok) { if (status === 403 && data?.detail?.error === "memory_timeline_upgrade_required") { return [ "Memory timeline requires a Pro plan or above.", `Current tier: ${data?.detail?.current_tier || "unknown"}`, ].join("\n"); } return `Memory timeline failed: ${data?.detail?.message || data?.detail || `HTTP ${status}`}`; } const events = Array.isArray(data?.events) ? data.events : []; const lines = [ `## Memory Timeline: ${data?.selector || body.incident_id || body.topic}`, "", data?.answer_context || "No timeline context returned.", "", `Confidence: ${typeof data?.confidence === "number" ? data.confidence.toFixed(2) : "0.00"}`, `Provenance Complete: ${Boolean(data?.provenance_complete)}`, "", "### Events", ]; if (!events.length) { lines.push("No timeline events returned."); } else { for (const event of events) { lines.push( `- ${event.timestamp} | ${event.event_type} | ${event.summary} | source: ${event.source_id}` ); } } return lines.join("\n"); } - src/index.ts:150-161 (schema)The tool registration with schema definitions for vigile_timeline. Uses zod schemas: 'topic' (optional string, 2-200 chars) and 'incident_id' (optional string, 2-120 chars).
server.tool( "vigile_timeline", "Fetch a security timeline for an incident or topic from Vigile memory.", { topic: z.string().min(2).max(200).optional().describe("Topic selector for timeline lookup"), incident_id: z.string().min(2).max(120).optional().describe("Incident ID selector (preferred when known)"), }, async ({ topic, incident_id }) => { const result = await timelineMemory(API_BASE, API_KEY, { topic, incident_id }); return { content: [{ type: "text" as const, text: result }] }; } ); - src/index.ts:150-161 (registration)The `server.tool()` call that registers the tool with the name "vigile_timeline" and wires it to the timelineMemory handler.
server.tool( "vigile_timeline", "Fetch a security timeline for an incident or topic from Vigile memory.", { topic: z.string().min(2).max(200).optional().describe("Topic selector for timeline lookup"), incident_id: z.string().min(2).max(120).optional().describe("Incident ID selector (preferred when known)"), }, async ({ topic, incident_id }) => { const result = await timelineMemory(API_BASE, API_KEY, { topic, incident_id }); return { content: [{ type: "text" as const, text: result }] }; } ); - src/tools/api.ts:5-46 (helper)The `fetchVigile` helper function used by timelineMemory to make HTTP requests to the Vigile API. Handles authentication, error sanitization, and response parsing.
export async function fetchVigile( baseUrl: string, apiKey: string, path: string, options?: { method?: string; body?: string } ): Promise<{ ok: boolean; status: number; data: any }> { const headers: Record<string, string> = { "Content-Type": "application/json", "User-Agent": "vigile-mcp/0.1.7", }; if (apiKey) { headers["Authorization"] = `Bearer ${apiKey}`; } try { const res = await fetch(`${baseUrl}${path}`, { method: options?.method || "GET", headers, body: options?.body, }); const data = await res.json().catch(() => null); return { ok: res.ok, status: res.status, data }; } catch (error: any) { // Sanitize error message — don't leak internal details like // hostnames, ports, file paths, or stack traces const rawMsg = error?.message || "Unknown error"; const safeMsg = rawMsg.includes("ECONNREFUSED") || rawMsg.includes("ENOTFOUND") ? "API server unreachable" : rawMsg.includes("ETIMEDOUT") || rawMsg.includes("timeout") ? "Request timed out" : rawMsg.includes("ECONNRESET") ? "Connection reset" : "Connection failed"; return { ok: false, status: 0, data: { detail: safeMsg }, }; } }