fix(litellm): idempotent MCP server provisioning + self-heal on login (SB-56)
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 16s
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 16s
provisionMcpServer now GETs /v1/mcp/server first and skips the POST if the sitebridge_<id> entry already exists. upsertCustomer calls it on every existing-customer login (fire-and-forget) so pre-existing customers whose row was created before this code path shipped — or whose LiteLLM entry was deleted/rebuilt — get their /mcp/ routing restored on next login. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { query, queryOne } from "../db/pool.js";
|
||||
import { encrypt, decrypt } from "../utils/crypto.js";
|
||||
import { provisionLiteLLMKey } from "./litellm.js";
|
||||
import { provisionLiteLLMKey, provisionMcpServer } from "./litellm.js";
|
||||
|
||||
export interface Customer {
|
||||
id: string;
|
||||
@@ -67,6 +67,14 @@ export async function upsertCustomer(params: {
|
||||
);
|
||||
existing.last_login_at = new Date();
|
||||
existing.name = params.name;
|
||||
|
||||
// Self-heal: ensure LiteLLM MCP server entry exists for this customer.
|
||||
// Idempotent in provisionMcpServer; swallow errors so a LiteLLM hiccup
|
||||
// doesn't block login.
|
||||
provisionMcpServer(existing.id).catch((err) => {
|
||||
console.error(`[customers] provisionMcpServer self-heal failed for ${existing.id}:`, err);
|
||||
});
|
||||
|
||||
return { customer: existing, virtualKey: getVirtualKey(existing), isNew: false };
|
||||
}
|
||||
|
||||
|
||||
@@ -25,8 +25,26 @@ function toServerAlias(customerId: string): string {
|
||||
return `sitebridge_${customerId.replace(/-/g, "_")}`;
|
||||
}
|
||||
|
||||
async function getMcpServer(serverAlias: string): Promise<LiteLLMMCPServerResponse | null> {
|
||||
const res = await fetch(`${config.litellmUrl}/v1/mcp/server`, {
|
||||
headers: { Authorization: `Bearer ${config.litellmMasterKey}` },
|
||||
});
|
||||
if (!res.ok) throw new Error(`LiteLLM /v1/mcp/server GET failed ${res.status}`);
|
||||
const data = (await res.json()) as unknown;
|
||||
const list = (Array.isArray(data)
|
||||
? data
|
||||
: ((data as { data?: unknown; servers?: unknown }).data ??
|
||||
(data as { servers?: unknown }).servers ??
|
||||
[])) as LiteLLMMCPServerResponse[];
|
||||
return list.find((s) => s.server_name === serverAlias || s.alias === serverAlias) ?? null;
|
||||
}
|
||||
|
||||
export async function provisionMcpServer(customerId: string): Promise<string> {
|
||||
const serverAlias = toServerAlias(customerId);
|
||||
|
||||
const existing = await getMcpServer(serverAlias);
|
||||
if (existing) return serverAlias;
|
||||
|
||||
const demuxUrl = `${config.agilitonAccountUrl}/mcp/demux/${customerId}/mcp`;
|
||||
|
||||
const body = {
|
||||
|
||||
Reference in New Issue
Block a user