mirror of
https://github.com/getpaseo/paseo.git
synced 2026-07-29 12:01:31 +00:00
fix(claude): preserve alwaysLoad on MCP server configs (#1333)
* fix(claude): preserve alwaysLoad on MCP server configs Paseo's Claude provider normalizes McpServerConfig via toClaudeSdkMcpConfig before passing it to claude-agent-sdk. The function copied type/command/args/ env (stdio) and type/url/headers (http, sse) but dropped the alwaysLoad field for all three transports. Without alwaysLoad making it to the SDK, every MCP server's tools were deferred behind tool search. In practice the agent never discovered them via search and would respond 'no chrome-devtools tools available' even with the server fully running and connected. Add alwaysLoad to the McpServerConfig types and zod schemas, copy it through in toClaudeSdkMcpConfig for stdio/http/sse, and add unit tests covering all three transports plus the default-undefined case. Other providers ignore the field, consistent with the existing pattern where each provider normalizes only what its CLI/SDK supports. * test(claude): drop ternary in toClaudeSdkMcpConfig assertion The 'alwaysLoad' field is now declared on every branch of the discriminated union returned by toClaudeSdkMcpConfig, so the in-operator guard is no longer needed. Direct property access expresses the intent without embedding a conditional in the assertion. --------- Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
This commit is contained in:
@@ -14,6 +14,11 @@ export interface McpStdioServerConfig {
|
||||
command: string;
|
||||
args?: string[];
|
||||
env?: Record<string, string>;
|
||||
/**
|
||||
* When true, all tools from this server are always included in the prompt
|
||||
* and never deferred behind tool search. Honored by the Claude provider.
|
||||
*/
|
||||
alwaysLoad?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -23,6 +28,11 @@ export interface McpHttpServerConfig {
|
||||
type: "http";
|
||||
url: string;
|
||||
headers?: Record<string, string>;
|
||||
/**
|
||||
* When true, all tools from this server are always included in the prompt
|
||||
* and never deferred behind tool search. Honored by the Claude provider.
|
||||
*/
|
||||
alwaysLoad?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -32,6 +42,11 @@ export interface McpSseServerConfig {
|
||||
type: "sse";
|
||||
url: string;
|
||||
headers?: Record<string, string>;
|
||||
/**
|
||||
* When true, all tools from this server are always included in the prompt
|
||||
* and never deferred behind tool search. Honored by the Claude provider.
|
||||
*/
|
||||
alwaysLoad?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -266,18 +266,21 @@ const McpStdioServerConfigSchema = z.object({
|
||||
command: z.string(),
|
||||
args: z.array(z.string()).optional(),
|
||||
env: z.record(z.string()).optional(),
|
||||
alwaysLoad: z.boolean().optional(),
|
||||
});
|
||||
|
||||
const McpHttpServerConfigSchema = z.object({
|
||||
type: z.literal("http"),
|
||||
url: z.string(),
|
||||
headers: z.record(z.string()).optional(),
|
||||
alwaysLoad: z.boolean().optional(),
|
||||
});
|
||||
|
||||
const McpSseServerConfigSchema = z.object({
|
||||
type: z.literal("sse"),
|
||||
url: z.string(),
|
||||
headers: z.record(z.string()).optional(),
|
||||
alwaysLoad: z.boolean().optional(),
|
||||
});
|
||||
|
||||
const McpServerConfigSchema = z.discriminatedUnion("type", [
|
||||
|
||||
@@ -15,6 +15,11 @@ export interface McpStdioServerConfig {
|
||||
command: string;
|
||||
args?: string[];
|
||||
env?: Record<string, string>;
|
||||
/**
|
||||
* When true, all tools from this server are always included in the prompt
|
||||
* and never deferred behind tool search. Honored by the Claude provider.
|
||||
*/
|
||||
alwaysLoad?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -24,6 +29,11 @@ export interface McpHttpServerConfig {
|
||||
type: "http";
|
||||
url: string;
|
||||
headers?: Record<string, string>;
|
||||
/**
|
||||
* When true, all tools from this server are always included in the prompt
|
||||
* and never deferred behind tool search. Honored by the Claude provider.
|
||||
*/
|
||||
alwaysLoad?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -33,6 +43,11 @@ export interface McpSseServerConfig {
|
||||
type: "sse";
|
||||
url: string;
|
||||
headers?: Record<string, string>;
|
||||
/**
|
||||
* When true, all tools from this server are always included in the prompt
|
||||
* and never deferred behind tool search. Honored by the Claude provider.
|
||||
*/
|
||||
alwaysLoad?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
ClaudeAgentClient,
|
||||
convertClaudeHistoryEntry,
|
||||
normalizeClaudeAskUserQuestionUpdatedInput,
|
||||
toClaudeSdkMcpConfig,
|
||||
} from "./agent.js";
|
||||
import type { AgentTimelineItem, AgentUsage, AgentStreamEvent } from "../../agent-sdk-types.js";
|
||||
|
||||
@@ -1674,3 +1675,63 @@ describe("ClaudeAgentSession context window usage", () => {
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("toClaudeSdkMcpConfig", () => {
|
||||
test("preserves alwaysLoad on stdio servers", () => {
|
||||
expect(
|
||||
toClaudeSdkMcpConfig({
|
||||
type: "stdio",
|
||||
command: "npx",
|
||||
args: ["-y", "chrome-devtools-mcp@latest"],
|
||||
alwaysLoad: true,
|
||||
}),
|
||||
).toEqual({
|
||||
type: "stdio",
|
||||
command: "npx",
|
||||
args: ["-y", "chrome-devtools-mcp@latest"],
|
||||
env: undefined,
|
||||
alwaysLoad: true,
|
||||
});
|
||||
});
|
||||
|
||||
test("preserves alwaysLoad on http servers", () => {
|
||||
expect(
|
||||
toClaudeSdkMcpConfig({
|
||||
type: "http",
|
||||
url: "https://example.com/mcp",
|
||||
headers: { Authorization: "Bearer x" },
|
||||
alwaysLoad: true,
|
||||
}),
|
||||
).toEqual({
|
||||
type: "http",
|
||||
url: "https://example.com/mcp",
|
||||
headers: { Authorization: "Bearer x" },
|
||||
alwaysLoad: true,
|
||||
});
|
||||
});
|
||||
|
||||
test("preserves alwaysLoad on sse servers", () => {
|
||||
expect(
|
||||
toClaudeSdkMcpConfig({
|
||||
type: "sse",
|
||||
url: "https://example.com/sse",
|
||||
alwaysLoad: true,
|
||||
}),
|
||||
).toEqual({
|
||||
type: "sse",
|
||||
url: "https://example.com/sse",
|
||||
headers: undefined,
|
||||
alwaysLoad: true,
|
||||
});
|
||||
});
|
||||
|
||||
test("leaves alwaysLoad undefined when not provided (preserves default deferral)", () => {
|
||||
const result = toClaudeSdkMcpConfig({
|
||||
type: "stdio",
|
||||
command: "uvx",
|
||||
args: ["markitdown-mcp"],
|
||||
});
|
||||
expect(result.type).toBe("stdio");
|
||||
expect(result.alwaysLoad).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -800,7 +800,7 @@ function coerceSessionMetadata(metadata: AgentMetadata | undefined): Partial<Age
|
||||
return result;
|
||||
}
|
||||
|
||||
function toClaudeSdkMcpConfig(config: McpServerConfig): ClaudeSdkMcpServerConfig {
|
||||
export function toClaudeSdkMcpConfig(config: McpServerConfig): ClaudeSdkMcpServerConfig {
|
||||
switch (config.type) {
|
||||
case "stdio":
|
||||
return {
|
||||
@@ -808,18 +808,21 @@ function toClaudeSdkMcpConfig(config: McpServerConfig): ClaudeSdkMcpServerConfig
|
||||
command: config.command,
|
||||
args: config.args,
|
||||
env: config.env,
|
||||
alwaysLoad: config.alwaysLoad,
|
||||
};
|
||||
case "http":
|
||||
return {
|
||||
type: "http",
|
||||
url: config.url,
|
||||
headers: config.headers,
|
||||
alwaysLoad: config.alwaysLoad,
|
||||
};
|
||||
case "sse":
|
||||
return {
|
||||
type: "sse",
|
||||
url: config.url,
|
||||
headers: config.headers,
|
||||
alwaysLoad: config.alwaysLoad,
|
||||
};
|
||||
}
|
||||
throw new Error("Unhandled MCP server config type");
|
||||
|
||||
Reference in New Issue
Block a user