Rene Fichtmueller d43b9f5298 feat: TokenVault MVP — hybrid MCP + proxy for LLM token savings
4-package monorepo:
- @tokenvault/core: Fastify 5.x proxy server, 7-stage pipeline,
  3 provider adapters (Anthropic, OpenAI, Ollama), PostgreSQL
  ticket system, cost calculator with real provider pricing
- @tokenvault/mcp: MCP server (stdio) with tv_ticket, tv_cost,
  tv_health tools for IDE integration
- @tokenvault/client: TypeScript SDK with createTokenVaultClient()
- @tokenvault/dashboard: Single-file HTML dashboard with MAGATAMA
  CI style (indigo #6366f1), bilingual DE+EN, 4 tabs

OpenAI-compatible proxy at /v1/chat/completions — drop-in replacement.
Every LLM request becomes a trackable ticket (TV-00001).
2026-04-14 10:10:22 +02:00

86 lines
3.8 KiB
TypeScript

import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { z } from 'zod';
const CORE_URL = process.env['TOKENVAULT_URL'] ?? 'http://localhost:3300';
async function fetchCore<T>(path: string): Promise<T> {
const res = await fetch(`${CORE_URL}${path}`);
if (!res.ok) throw new Error(`TokenVault API error: ${res.status}`);
return res.json() as Promise<T>;
}
const server = new McpServer({
name: 'tokenvault',
version: '0.1.0',
});
// ─── tv_ticket: View and search tickets ──────────────────────────────────────
server.tool(
'tv_ticket',
'View, search, and manage TokenVault tickets. Every LLM request = 1 ticket.',
{
action: z.enum(['list', 'get', 'stats']).describe('Action to perform'),
id: z.string().optional().describe('Ticket ID (for get action)'),
provider: z.string().optional().describe('Filter by provider'),
project: z.string().optional().describe('Filter by project'),
period: z.string().optional().describe('Stats period: today, week, month, all'),
limit: z.number().optional().describe('Max results (default 20)'),
},
async ({ action, id, provider, project, period, limit }) => {
if (action === 'get' && id) {
const ticket = await fetchCore(`/v1/tickets/${id}`);
return { content: [{ type: 'text' as const, text: JSON.stringify(ticket, null, 2) }] };
}
if (action === 'stats') {
const stats = await fetchCore(`/v1/tickets/stats?period=${period ?? 'today'}`);
return { content: [{ type: 'text' as const, text: JSON.stringify(stats, null, 2) }] };
}
const params = new URLSearchParams();
if (provider) params.set('provider', provider);
if (project) params.set('project', project);
params.set('limit', String(limit ?? 20));
const data = await fetchCore(`/v1/tickets?${params}`);
return { content: [{ type: 'text' as const, text: JSON.stringify(data, null, 2) }] };
},
);
// ─── tv_cost: Cost dashboard ─────────────────────────────────────────────────
server.tool(
'tv_cost',
'Show cost dashboard: spend, savings, forecasts. Tracks every token across all LLM providers.',
{
period: z.enum(['today', 'week', 'month', 'all']).optional().describe('Time period'),
group_by: z.enum(['provider', 'model', 'project', 'team']).optional().describe('Group breakdown by'),
},
async ({ period, group_by }) => {
const [summary, breakdown] = await Promise.all([
fetchCore(`/v1/cost?period=${period ?? 'month'}`),
group_by ? fetchCore(`/v1/cost/breakdown?group_by=${group_by}`) : Promise.resolve(null),
]);
const parts = [`# Cost Summary (${period ?? 'month'})\n${JSON.stringify(summary, null, 2)}`];
if (breakdown) {
parts.push(`\n# Breakdown by ${group_by}\n${JSON.stringify(breakdown, null, 2)}`);
}
return { content: [{ type: 'text' as const, text: parts.join('\n') }] };
},
);
// ─── tv_health: Service health ───────────────────────────────────────────────
server.tool(
'tv_health',
'Check TokenVault service health and configured providers.',
{},
async () => {
const health = await fetchCore('/health');
return { content: [{ type: 'text' as const, text: JSON.stringify(health, null, 2) }] };
},
);
// ─── Start ───────────────────────────────────────────────────────────────────
const transport = new StdioServerTransport();
await server.connect(transport);