ui: Add request timeout for MCP tool calls (#23138)
* feat: Add request timeout for MCP tool calls in llama-ui * feat: MCP Settings tab with max timeout setting
This commit is contained in:
committed by
GitHub
parent
3a92bc99db
commit
0253fb21f5
@@ -79,6 +79,8 @@
|
|||||||
<div class="relative w-full">
|
<div class="relative w-full">
|
||||||
<Input
|
<Input
|
||||||
id={field.key}
|
id={field.key}
|
||||||
|
type={field.isPositiveInteger ? 'number' : 'text'}
|
||||||
|
{...field.isPositiveInteger ? { min: '1', step: '1' } : {}}
|
||||||
value={currentValue}
|
value={currentValue}
|
||||||
oninput={(e) => {
|
oninput={(e) => {
|
||||||
// Update local config immediately for real-time badge feedback
|
// Update local config immediately for real-time badge feedback
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ export const SETTINGS_SECTION_SLUGS = {
|
|||||||
PENALTIES: 'penalties',
|
PENALTIES: 'penalties',
|
||||||
AGENTIC: 'agentic',
|
AGENTIC: 'agentic',
|
||||||
DEVELOPER: 'developer',
|
DEVELOPER: 'developer',
|
||||||
|
MCP: 'mcp',
|
||||||
TOOLS: 'tools',
|
TOOLS: 'tools',
|
||||||
IMPORT_EXPORT: 'import-export'
|
IMPORT_EXPORT: 'import-export'
|
||||||
} as const;
|
} as const;
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ export const SETTINGS_KEYS = {
|
|||||||
DRY_PENALTY_LAST_N: 'dry_penalty_last_n',
|
DRY_PENALTY_LAST_N: 'dry_penalty_last_n',
|
||||||
// MCP
|
// MCP
|
||||||
MCP_SERVERS: 'mcpServers',
|
MCP_SERVERS: 'mcpServers',
|
||||||
|
MCP_REQUEST_TIMEOUT_SECONDS: 'mcpRequestTimeoutSeconds',
|
||||||
AGENTIC_MAX_TURNS: 'agenticMaxTurns',
|
AGENTIC_MAX_TURNS: 'agenticMaxTurns',
|
||||||
ALWAYS_SHOW_AGENTIC_TURNS: 'alwaysShowAgenticTurns',
|
ALWAYS_SHOW_AGENTIC_TURNS: 'alwaysShowAgenticTurns',
|
||||||
AGENTIC_MAX_TOOL_PREVIEW_LINES: 'agenticMaxToolPreviewLines',
|
AGENTIC_MAX_TOOL_PREVIEW_LINES: 'agenticMaxToolPreviewLines',
|
||||||
|
|||||||
@@ -23,7 +23,8 @@ import type {
|
|||||||
SettingsSectionEntry,
|
SettingsSectionEntry,
|
||||||
SettingsSection
|
SettingsSection
|
||||||
} from '$lib/types';
|
} from '$lib/types';
|
||||||
import { CLI_FLAGS } from '$lib/constants';
|
import { CLI_FLAGS, DEFAULT_MCP_CONFIG } from '$lib/constants';
|
||||||
|
import McpLogo from '$lib/components/app/mcp/McpLogo.svelte';
|
||||||
import { SETTINGS_KEYS } from './settings-keys';
|
import { SETTINGS_KEYS } from './settings-keys';
|
||||||
import { ROUTES, SETTINGS_SECTION_SLUGS } from './routes';
|
import { ROUTES, SETTINGS_SECTION_SLUGS } from './routes';
|
||||||
import { TITLE_GENERATION } from './title-generation';
|
import { TITLE_GENERATION } from './title-generation';
|
||||||
@@ -35,6 +36,7 @@ export const SETTINGS_SECTION_TITLES = {
|
|||||||
PENALTIES: 'Penalties',
|
PENALTIES: 'Penalties',
|
||||||
AGENTIC: 'Agentic',
|
AGENTIC: 'Agentic',
|
||||||
TOOLS: 'Tools',
|
TOOLS: 'Tools',
|
||||||
|
MCP: 'MCP',
|
||||||
IMPORT_EXPORT: 'Import/Export',
|
IMPORT_EXPORT: 'Import/Export',
|
||||||
DEVELOPER: 'Developer'
|
DEVELOPER: 'Developer'
|
||||||
} as const;
|
} as const;
|
||||||
@@ -657,6 +659,22 @@ const SETTINGS_REGISTRY: Record<string, SettingsSectionEntry> = {
|
|||||||
section: SETTINGS_SECTION_SLUGS.DEVELOPER
|
section: SETTINGS_SECTION_SLUGS.DEVELOPER
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
[SETTINGS_SECTION_SLUGS.MCP]: {
|
||||||
|
title: SETTINGS_SECTION_TITLES.MCP,
|
||||||
|
slug: SETTINGS_SECTION_SLUGS.MCP,
|
||||||
|
icon: McpLogo,
|
||||||
|
settings: [
|
||||||
|
{
|
||||||
|
key: SETTINGS_KEYS.MCP_REQUEST_TIMEOUT_SECONDS,
|
||||||
|
label: 'Request timeout (seconds)',
|
||||||
|
help: 'Default timeout for individual MCP tool calls. Can be overridden per server.',
|
||||||
|
defaultValue: DEFAULT_MCP_CONFIG.requestTimeoutSeconds,
|
||||||
|
type: SettingsFieldType.INPUT,
|
||||||
|
section: SETTINGS_SECTION_SLUGS.MCP,
|
||||||
|
isPositiveInteger: true
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
@@ -727,6 +745,7 @@ export const SETTINGS_CHAT_SECTIONS: SettingsSection[] = [
|
|||||||
label: s.label,
|
label: s.label,
|
||||||
type: s.type,
|
type: s.type,
|
||||||
isExperimental: s.isExperimental,
|
isExperimental: s.isExperimental,
|
||||||
|
isPositiveInteger: s.isPositiveInteger,
|
||||||
help: s.help,
|
help: s.help,
|
||||||
options: s.options
|
options: s.options
|
||||||
}))
|
}))
|
||||||
|
|||||||
@@ -665,7 +665,9 @@ export class MCPService {
|
|||||||
tools: [],
|
tools: [],
|
||||||
serverName,
|
serverName,
|
||||||
transportType,
|
transportType,
|
||||||
connectionTimeMs: 0
|
connectionTimeMs: 0,
|
||||||
|
requestTimeoutMs:
|
||||||
|
serverConfig.requestTimeoutMs ?? DEFAULT_MCP_CONFIG.requestTimeoutSeconds * 1000
|
||||||
});
|
});
|
||||||
|
|
||||||
const connectionTimeMs = Math.round(performance.now() - startTime);
|
const connectionTimeMs = Math.round(performance.now() - startTime);
|
||||||
@@ -694,7 +696,9 @@ export class MCPService {
|
|||||||
clientCapabilities: effectiveCapabilities,
|
clientCapabilities: effectiveCapabilities,
|
||||||
protocolVersion: DEFAULT_MCP_CONFIG.protocolVersion,
|
protocolVersion: DEFAULT_MCP_CONFIG.protocolVersion,
|
||||||
instructions,
|
instructions,
|
||||||
connectionTimeMs
|
connectionTimeMs,
|
||||||
|
requestTimeoutMs:
|
||||||
|
serverConfig.requestTimeoutMs ?? DEFAULT_MCP_CONFIG.requestTimeoutSeconds * 1000
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -813,7 +817,7 @@ export class MCPService {
|
|||||||
const result = await connection.client.callTool(
|
const result = await connection.client.callTool(
|
||||||
{ name: params.name, arguments: params.arguments },
|
{ name: params.name, arguments: params.arguments },
|
||||||
undefined,
|
undefined,
|
||||||
{ signal }
|
{ signal, timeout: connection.requestTimeoutMs }
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -168,7 +168,9 @@ class MCPStore {
|
|||||||
enabled: Boolean((entry as { enabled?: unknown })?.enabled),
|
enabled: Boolean((entry as { enabled?: unknown })?.enabled),
|
||||||
url,
|
url,
|
||||||
name: (entry as { name?: string })?.name,
|
name: (entry as { name?: string })?.name,
|
||||||
requestTimeoutSeconds: DEFAULT_MCP_CONFIG.requestTimeoutSeconds,
|
requestTimeoutSeconds:
|
||||||
|
(entry as { requestTimeoutSeconds?: number })?.requestTimeoutSeconds ??
|
||||||
|
DEFAULT_MCP_CONFIG.requestTimeoutSeconds,
|
||||||
headers: headers || undefined,
|
headers: headers || undefined,
|
||||||
useProxy: Boolean((entry as { useProxy?: unknown })?.useProxy)
|
useProxy: Boolean((entry as { useProxy?: unknown })?.useProxy)
|
||||||
} satisfies MCPServerSettingsEntry;
|
} satisfies MCPServerSettingsEntry;
|
||||||
@@ -554,7 +556,8 @@ class MCPStore {
|
|||||||
url: serverData.url.trim(),
|
url: serverData.url.trim(),
|
||||||
name: serverData.name,
|
name: serverData.name,
|
||||||
headers: serverData.headers?.trim() || undefined,
|
headers: serverData.headers?.trim() || undefined,
|
||||||
requestTimeoutSeconds: DEFAULT_MCP_CONFIG.requestTimeoutSeconds,
|
requestTimeoutSeconds:
|
||||||
|
Number(config().mcpRequestTimeoutSeconds) || DEFAULT_MCP_CONFIG.requestTimeoutSeconds,
|
||||||
useProxy: serverData.useProxy
|
useProxy: serverData.useProxy
|
||||||
};
|
};
|
||||||
settingsStore.updateConfig(SETTINGS_KEYS.MCP_SERVERS, JSON.stringify([...servers, newServer]));
|
settingsStore.updateConfig(SETTINGS_KEYS.MCP_SERVERS, JSON.stringify([...servers, newServer]));
|
||||||
|
|||||||
Vendored
+2
@@ -135,6 +135,8 @@ export interface MCPConnection {
|
|||||||
protocolVersion?: string;
|
protocolVersion?: string;
|
||||||
instructions?: string;
|
instructions?: string;
|
||||||
connectionTimeMs: number;
|
connectionTimeMs: number;
|
||||||
|
/** Configured timeout for individual requests (tool calls, etc.) in milliseconds */
|
||||||
|
requestTimeoutMs: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Vendored
+1
@@ -42,6 +42,7 @@ export interface SettingsFieldConfig {
|
|||||||
label: string;
|
label: string;
|
||||||
type: SettingsFieldType;
|
type: SettingsFieldType;
|
||||||
isExperimental?: boolean;
|
isExperimental?: boolean;
|
||||||
|
isPositiveInteger?: boolean;
|
||||||
help?: string;
|
help?: string;
|
||||||
options?: Array<{ value: string; label: string; icon?: typeof Icon }>;
|
options?: Array<{ value: string; label: string; icon?: typeof Icon }>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ export function detectMcpTransportFromUrl(url: string): MCPTransportType {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses MCP server settings from a JSON string or array.
|
* Parses MCP server settings from a JSON string or array.
|
||||||
* requestTimeoutSeconds is not user-configurable in the UI, so we always use the default value.
|
* Preserves per-server requestTimeoutSeconds if stored, otherwise falls back to the global default.
|
||||||
* @param rawServers - The raw servers to parse
|
* @param rawServers - The raw servers to parse
|
||||||
* @returns An empty array if the input is invalid.
|
* @returns An empty array if the input is invalid.
|
||||||
*/
|
*/
|
||||||
@@ -88,7 +88,9 @@ export function parseMcpServerSettings(rawServers: unknown): MCPServerSettingsEn
|
|||||||
enabled: Boolean((entry as { enabled?: unknown })?.enabled),
|
enabled: Boolean((entry as { enabled?: unknown })?.enabled),
|
||||||
url,
|
url,
|
||||||
name: (entry as { name?: string })?.name,
|
name: (entry as { name?: string })?.name,
|
||||||
requestTimeoutSeconds: DEFAULT_MCP_CONFIG.requestTimeoutSeconds,
|
requestTimeoutSeconds:
|
||||||
|
(entry as { requestTimeoutSeconds?: number })?.requestTimeoutSeconds ??
|
||||||
|
DEFAULT_MCP_CONFIG.requestTimeoutSeconds,
|
||||||
headers: headers || undefined,
|
headers: headers || undefined,
|
||||||
useProxy: Boolean((entry as { useProxy?: unknown })?.useProxy)
|
useProxy: Boolean((entry as { useProxy?: unknown })?.useProxy)
|
||||||
} satisfies MCPServerSettingsEntry;
|
} satisfies MCPServerSettingsEntry;
|
||||||
|
|||||||
Reference in New Issue
Block a user