Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/lib/server/mcp/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Client } from "@modelcontextprotocol/sdk/client";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
import type { McpServerConfig } from "./httpClient";
import { logger } from "$lib/server/logger";
// use console.* for lightweight diagnostics in production logs

export type OpenAiTool = {
Expand Down Expand Up @@ -82,7 +83,7 @@ async function listServerTools(
const response = await client.listTools({});
const tools = Array.isArray(response?.tools) ? (response.tools as ListedTool[]) : [];
try {
console.debug(
logger.debug(
{
server: server.name,
url: server.url,
Expand Down
49 changes: 25 additions & 24 deletions src/lib/server/textGeneration/mcp/runMcpFlow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { hasAuthHeader, isStrictHfMcpLogin, hasNonEmptyToken } from "$lib/server
import { buildImageRefResolver } from "./fileRefs";
import { prepareMessagesWithFiles } from "$lib/server/textGeneration/utils/prepareFiles";
import { makeImageProcessor } from "$lib/server/endpoints/images";
import { logger } from "$lib/server/logger";

export type RunMcpFlowContext = Pick<
TextGenerationContext,
Expand All @@ -45,7 +46,7 @@ export async function* runMcpFlow({
// Start from env-configured servers
let servers = getMcpServers();
try {
console.debug(
logger.debug(
{ baseServers: servers.map((s) => ({ name: s.name, url: s.url })), count: servers.length },
"[mcp] base servers loaded"
);
Expand Down Expand Up @@ -74,7 +75,7 @@ export async function* runMcpFlow({
for (const s of custom) byName.set(s.name, s);
servers = [...byName.values()];
try {
console.debug(
logger.debug(
{
customProvidedCount: custom.length,
mergedServers: servers.map((s) => ({
Expand All @@ -96,7 +97,7 @@ export async function* runMcpFlow({
const before = servers.map((s) => s.name);
servers = servers.filter((s) => names.includes(s.name));
try {
console.debug(
logger.debug(
{ selectedNames: names, before, after: servers.map((s) => s.name) },
"[mcp] applied name selection"
);
Expand All @@ -108,7 +109,7 @@ export async function* runMcpFlow({

// If selection/merge yielded no servers, bail early with clearer log
if (servers.length === 0) {
console.warn("[mcp] no MCP servers selected after merge/name filter");
logger.warn({}, "[mcp] no MCP servers selected after merge/name filter");
return false;
}

Expand All @@ -125,15 +126,15 @@ export async function* runMcpFlow({
try {
const rejected = before.filter((b) => !servers.includes(b));
if (rejected.length > 0) {
console.warn(
logger.warn(
{ rejected: rejected.map((r) => ({ name: r.name, url: r.url })) },
"[mcp] rejected servers by URL safety"
);
}
} catch {}
}
if (servers.length === 0) {
console.warn("[mcp] all selected MCP servers rejected by URL safety guard");
logger.warn({}, "[mcp] all selected MCP servers rejected by URL safety guard");
return false;
}

Expand Down Expand Up @@ -163,14 +164,14 @@ export async function* runMcpFlow({
});
if (overlayApplied.length > 0) {
try {
console.debug({ overlayApplied }, "[mcp] forwarded HF token to servers");
logger.debug({ overlayApplied }, "[mcp] forwarded HF token to servers");
} catch {}
}
}
} catch {
// best-effort overlay; continue if anything goes wrong
}
console.debug(
logger.debug(
{ count: servers.length, servers: servers.map((s) => s.name) },
"[mcp] servers configured"
);
Expand All @@ -182,7 +183,7 @@ export async function* runMcpFlow({
try {
const supportsTools = Boolean((model as unknown as { supportsTools?: boolean }).supportsTools);
const toolsEnabled = Boolean(forceTools) || supportsTools;
console.debug(
logger.debug(
{
model: model.id ?? model.name,
supportsTools,
Expand All @@ -192,7 +193,7 @@ export async function* runMcpFlow({
"[mcp] tools gate evaluation"
);
if (!toolsEnabled) {
console.info(
logger.info(
{ model: model.id ?? model.name },
"[mcp] tools disabled for model; skipping MCP flow"
);
Expand Down Expand Up @@ -226,7 +227,7 @@ export async function* runMcpFlow({
});

if (!runMcp) {
console.info(
logger.info(
{ model: targetModel.id ?? targetModel.name, resolvedRoute },
"[mcp] runMcp=false (routing chose non-tools candidate)"
);
Expand All @@ -235,13 +236,13 @@ export async function* runMcpFlow({

const { tools: oaTools, mapping } = await getOpenAiToolsForMcp(servers, { signal: abortSignal });
try {
console.info(
logger.info(
{ toolCount: oaTools.length, toolNames: oaTools.map((t) => t.function.name) },
"[mcp] openai tool defs built"
);
} catch {}
if (oaTools.length === 0) {
console.warn("[mcp] zero tools available after listing; skipping MCP flow");
logger.warn({}, "[mcp] zero tools available after listing; skipping MCP flow");
return false;
}

Expand Down Expand Up @@ -273,7 +274,7 @@ export async function* runMcpFlow({
});

const mmEnabled = (forceMultimodal ?? false) || targetModel.multimodal;
console.info(
logger.info(
{
targetModel: targetModel.id ?? targetModel.name,
mmEnabled,
Expand Down Expand Up @@ -390,7 +391,7 @@ export async function* runMcpFlow({
route: resolvedRoute,
model: candidateModelId,
};
console.debug(
logger.debug(
{ route: resolvedRoute, model: candidateModelId },
"[mcp] router metadata emitted"
);
Expand Down Expand Up @@ -425,7 +426,7 @@ export async function* runMcpFlow({
model: "",
provider: providerHeader as unknown as import("@huggingface/inference").InferenceProvider,
};
console.debug({ provider: providerHeader }, "[mcp] provider metadata emitted");
logger.debug({ provider: providerHeader }, "[mcp] provider metadata emitted");
}

const toolCallState: Record<number, { id?: string; name?: string; arguments: string }> = {};
Expand Down Expand Up @@ -461,7 +462,7 @@ export async function* runMcpFlow({
.map((k) => Number(k))
.sort((a, b) => a - b)[0] ?? 0
];
console.info(
logger.info(
{ firstCallName: first?.name, hasId: Boolean(first?.id) },
"[mcp] observed streamed tool_call delta"
);
Expand Down Expand Up @@ -526,7 +527,7 @@ export async function* runMcpFlow({
}
}
}
console.info(
logger.info(
{ sawToolCalls: Object.keys(toolCallState).length > 0, tokens: tokenCount, loop },
"[mcp] completion stream closed"
);
Expand All @@ -536,7 +537,7 @@ export async function* runMcpFlow({
const missingId = Object.values(toolCallState).some((c) => c?.name && !c?.id);
let calls: NormalizedToolCall[];
if (missingId) {
console.debug(
logger.debug(
{ loop },
"[mcp] missing tool_call id in stream; retrying non-stream to recover ids"
);
Expand Down Expand Up @@ -611,7 +612,7 @@ export async function* runMcpFlow({
];
toolMsgCount = event.summary.toolMessages?.length ?? 0;
toolRunCount = event.summary.toolRuns?.length ?? 0;
console.info(
logger.info(
{ toolMsgCount, toolRunCount },
"[mcp] tools executed; continuing loop for follow-up completion"
);
Expand All @@ -635,13 +636,13 @@ export async function* runMcpFlow({
text: lastAssistantContent,
interrupted: false,
};
console.info(
logger.info(
{ length: lastAssistantContent.length, loop },
"[mcp] final answer emitted (no tool_calls)"
);
return true;
}
console.warn("[mcp] exceeded tool-followup loops; falling back");
logger.warn({}, "[mcp] exceeded tool-followup loops; falling back");
} catch (err) {
const msg = String(err ?? "");
const isAbort =
Expand All @@ -651,10 +652,10 @@ export async function* runMcpFlow({
msg.includes("Request was aborted");
if (isAbort) {
// Expected on user stop; keep logs quiet and do not treat as error
console.debug("[mcp] aborted by user");
logger.debug({}, "[mcp] aborted by user");
return false;
}
console.warn({ err: msg }, "[mcp] flow failed, falling back to default endpoint");
logger.warn({ err: msg }, "[mcp] flow failed, falling back to default endpoint");
} finally {
// ensure MCP clients are closed after the turn
await drainPool();
Expand Down
18 changes: 9 additions & 9 deletions src/routes/api/mcp/health/+server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,16 +89,16 @@ export const POST: RequestHandler = async ({ request, locals }) => {

// Try Streamable HTTP transport first
try {
logger.info(`[MCP Health] Trying HTTP transport for ${url}`);
logger.info({}, `[MCP Health] Trying HTTP transport for ${url}`);
client = new Client({
name: "chat-ui-health-check",
version: "1.0.0",
});

const transport = new StreamableHTTPClientTransport(baseUrl, { requestInit });
logger.info(`[MCP Health] Connecting to ${url}...`);
logger.info({}, `[MCP Health] Connecting to ${url}...`);
await client.connect(transport);
logger.info(`[MCP Health] Connected successfully via HTTP`);
logger.info({}, `[MCP Health] Connected successfully via HTTP`);

// Connection successful, get tools
const toolsResponse = await client.listTools();
Expand Down Expand Up @@ -141,7 +141,7 @@ export const POST: RequestHandler = async ({ request, locals }) => {
} catch (error) {
httpError = error instanceof Error ? error : new Error(String(error));
lastError = httpError;
logger.info("Streamable HTTP failed, trying SSE transport...", lastError.message);
logger.warn(lastError.message, "Streamable HTTP failed, trying SSE transport...");

// Close failed client
try {
Expand All @@ -152,16 +152,16 @@ export const POST: RequestHandler = async ({ request, locals }) => {

// Try SSE transport
try {
logger.info(`[MCP Health] Trying SSE transport for ${url}`);
logger.info({}, `[MCP Health] Trying SSE transport for ${url}`);
client = new Client({
name: "chat-ui-health-check",
version: "1.0.0",
});

const sseTransport = new SSEClientTransport(baseUrl, { requestInit });
logger.info(`[MCP Health] Connecting via SSE...`);
logger.info({}, `[MCP Health] Connecting via SSE...`);
await client.connect(sseTransport);
logger.info(`[MCP Health] Connected successfully via SSE`);
logger.info({}, `[MCP Health] Connected successfully via SSE`);

// Connection successful, get tools
const toolsResponse = await client.listTools();
Expand Down Expand Up @@ -211,7 +211,7 @@ export const POST: RequestHandler = async ({ request, locals }) => {
{ cause: sseError instanceof Error ? sseError : undefined }
);
}
logger.error("Both transports failed. Last error:", lastError);
logger.error(lastError, "Both transports failed.");
}
}

Expand Down Expand Up @@ -253,7 +253,7 @@ export const POST: RequestHandler = async ({ request, locals }) => {
clearTimeout(timeoutId);
return res;
} catch (error) {
logger.error("MCP health check failed:", error);
logger.error(error, "MCP health check failed");

// Clean up client if it exists
try {
Expand Down
Loading