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:
Fabian Fischer
2026-06-05 16:29:47 +02:00
committed by GitHub
parent c89177c211
commit 44e9287389
5 changed files with 98 additions and 1 deletions

View File

@@ -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;
}
/**

View File

@@ -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", [

View File

@@ -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;
}
/**

View File

@@ -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();
});
});

View File

@@ -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");