Skip to content

Commit 42efcf9

Browse files
committed
feat(settings): resolve remote MCP servers from mcpServers
Teach mergeMcpServers to recognize remote (Streamable HTTP) entries: a server with a `url` (or `type: "http"`) resolves to a remote config with project settings overriding user settings and headers merged across layers. Stdio servers keep their existing command/args/env resolution.
1 parent 5315fed commit 42efcf9

2 files changed

Lines changed: 52 additions & 0 deletions

File tree

packages/core/src/settings.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,24 @@ function mergeMcpServers(
442442
for (const name of serverNames) {
443443
const userConfig = userServers[name];
444444
const projectConfig = projectServers[name];
445+
446+
// Remote (Streamable HTTP) server: selected by a url or an explicit
447+
// type: "http". Project settings override user settings; headers merge.
448+
const url = trimString(projectConfig?.url) || trimString(userConfig?.url);
449+
const type = projectConfig?.type ?? userConfig?.type;
450+
if (url && type !== "stdio") {
451+
const headers = {
452+
...(userConfig?.headers ?? {}),
453+
...(projectConfig?.headers ?? {}),
454+
};
455+
const config: McpServerConfig = { type: "http", url };
456+
if (Object.keys(headers).length > 0) {
457+
config.headers = headers;
458+
}
459+
merged[name] = config;
460+
continue;
461+
}
462+
445463
const command = projectConfig?.command ?? userConfig?.command;
446464
if (!command) {
447465
continue;

packages/core/src/tests/settings-and-notify.test.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,40 @@ test("resolveSettingsSources merges MCP env with documented priority", () => {
303303
});
304304
});
305305

306+
test("resolveSettingsSources resolves remote MCP servers with merged headers", () => {
307+
const resolved = resolveSettingsSources(
308+
{
309+
mcpServers: {
310+
remote: {
311+
url: "https://user.example/mcp",
312+
headers: { Authorization: "Bearer user", "X-User": "1" },
313+
},
314+
},
315+
},
316+
{
317+
mcpServers: {
318+
remote: {
319+
url: "https://project.example/mcp",
320+
headers: { Authorization: "Bearer project" },
321+
},
322+
},
323+
},
324+
{
325+
model: "default-model",
326+
baseURL: "https://default.example.com",
327+
},
328+
{}
329+
);
330+
331+
const remote = resolved.mcpServers?.remote;
332+
assert.equal(remote?.type, "http");
333+
// Project url and header win; user-only header is preserved.
334+
assert.equal(remote?.url, "https://project.example/mcp");
335+
assert.deepEqual(remote?.headers, { Authorization: "Bearer project", "X-User": "1" });
336+
// Remote servers carry no stdio fields.
337+
assert.equal(remote?.command, undefined);
338+
});
339+
306340
test("resolveSettings defaults DeepSeek v4 models to thinking mode", () => {
307341
const resolved = resolveSettings(
308342
{

0 commit comments

Comments
 (0)