Skip to content

Commit 60ed7b8

Browse files
authored
Merge pull request #7956 from continuedev/dallin/JSON-MCP-support
feat: support standard MCP JSON formats
2 parents f940da1 + 1332f52 commit 60ed7b8

File tree

33 files changed

+2184
-579
lines changed

33 files changed

+2184
-579
lines changed

core/config/load.ts

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import path from "path";
66
import {
77
ConfigResult,
88
ConfigValidationError,
9+
mergeConfigYamlRequestOptions,
910
ModelRole,
1011
} from "@continuedev/config-yaml";
1112
import * as JSONC from "comment-json";
@@ -25,6 +26,7 @@ import {
2526
IdeType,
2627
ILLM,
2728
ILLMLogger,
29+
InternalMcpOptions,
2830
LLMOptions,
2931
ModelDescription,
3032
RerankerDescription,
@@ -57,8 +59,9 @@ import {
5759
} from "../util/paths";
5860
import { localPathToUri } from "../util/pathToUri";
5961

60-
import { PolicySingleton } from "../control-plane/PolicySingleton";
62+
import { loadJsonMcpConfigs } from "../context/mcp/json/loadJsonMcpConfigs";
6163
import CustomContextProviderClass from "../context/providers/CustomContextProvider";
64+
import { PolicySingleton } from "../control-plane/PolicySingleton";
6265
import { getBaseToolDefinitions } from "../tools";
6366
import { resolveRelativePathInDir } from "../util/ideUtils";
6467
import { getWorkspaceRcConfigs } from "./json/loadRcConfigs";
@@ -550,17 +553,27 @@ async function intermediateToFinalConfig({
550553
if (orgPolicy?.policy?.allowMcpServers === false) {
551554
await mcpManager.shutdown();
552555
} else {
553-
mcpManager.setConnections(
554-
(config.experimental?.modelContextProtocolServers ?? []).map(
555-
(server, index) => ({
556-
id: `continue-mcp-server-${index + 1}`,
557-
name: `MCP Server`,
558-
...server,
559-
requestOptions: config.requestOptions,
560-
}),
556+
const mcpOptions: InternalMcpOptions[] = (
557+
config.experimental?.modelContextProtocolServers ?? []
558+
).map((server, index) => ({
559+
id: `continue-mcp-server-${index + 1}`,
560+
name: `MCP Server`,
561+
requestOptions: mergeConfigYamlRequestOptions(
562+
server.transport.type !== "stdio"
563+
? server.transport.requestOptions
564+
: undefined,
565+
config.requestOptions,
561566
),
562-
false,
567+
...server.transport,
568+
}));
569+
const { errors: jsonMcpErrors, mcpServers } = await loadJsonMcpConfigs(
570+
ide,
571+
true,
572+
config.requestOptions,
563573
);
574+
errors.push(...jsonMcpErrors);
575+
mcpOptions.push(...mcpServers);
576+
mcpManager.setConnections(mcpOptions, false);
564577
}
565578

566579
// Handle experimental modelRole config values for apply and edit

core/config/loadLocalAssistants.ts

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { BLOCK_TYPES } from "@continuedev/config-yaml";
12
import ignore from "ignore";
23
import * as URI from "uri-js";
34
import { IDE } from "..";
@@ -6,12 +7,32 @@ import {
67
DEFAULT_IGNORE_FILETYPES,
78
} from "../indexing/ignore";
89
import { walkDir } from "../indexing/walkDir";
10+
import { RULES_MARKDOWN_FILENAME } from "../llm/rules/constants";
911
import { getGlobalFolderWithName } from "../util/paths";
1012
import { localPathToUri } from "../util/pathToUri";
11-
import { joinPathsToUri } from "../util/uri";
13+
import { getUriPathBasename, joinPathsToUri } from "../util/uri";
14+
import { SYSTEM_PROMPT_DOT_FILE } from "./getWorkspaceContinueRuleDotFiles";
15+
export function isContinueConfigRelatedUri(uri: string): boolean {
16+
return (
17+
uri.endsWith(".continuerc.json") ||
18+
uri.endsWith(".prompt") ||
19+
uri.endsWith("AGENTS.md") ||
20+
uri.endsWith("AGENT.md") ||
21+
uri.endsWith("CLAUDE.md") ||
22+
uri.endsWith(SYSTEM_PROMPT_DOT_FILE) ||
23+
(uri.includes(".continue") &&
24+
(uri.endsWith(".yaml") ||
25+
uri.endsWith(".yml") ||
26+
uri.endsWith(".json"))) ||
27+
[...BLOCK_TYPES, "agents", "assistants"].some((blockType) =>
28+
uri.includes(`.continue/${blockType}`),
29+
)
30+
);
31+
}
1232

13-
export function isLocalDefinitionFile(uri: string): boolean {
14-
if (!uri.endsWith(".yaml") && !uri.endsWith(".yml") && !uri.endsWith(".md")) {
33+
export function isContinueAgentConfigFile(uri: string): boolean {
34+
const isYaml = uri.endsWith(".yaml") || uri.endsWith(".yml");
35+
if (!isYaml) {
1536
return false;
1637
}
1738

@@ -22,6 +43,10 @@ export function isLocalDefinitionFile(uri: string): boolean {
2243
);
2344
}
2445

46+
export function isColocatedRulesFile(uri: string): boolean {
47+
return getUriPathBasename(uri) === RULES_MARKDOWN_FILENAME;
48+
}
49+
2550
async function getDefinitionFilesInDir(
2651
ide: IDE,
2752
dir: string,

core/config/yaml/loadYaml.ts

Lines changed: 35 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,14 @@ import {
1515
} from "@continuedev/config-yaml";
1616
import { dirname } from "node:path";
1717

18-
import { ContinueConfig, IDE, IdeInfo, IdeSettings, ILLMLogger } from "../..";
18+
import {
19+
ContinueConfig,
20+
IDE,
21+
IdeInfo,
22+
IdeSettings,
23+
ILLMLogger,
24+
InternalMcpOptions,
25+
} from "../..";
1926
import { MCPManagerSingleton } from "../../context/mcp/MCPManagerSingleton";
2027
import { ControlPlaneClient } from "../../control-plane/client";
2128
import TransformersJsEmbeddingsProvider from "../../llm/llms/TransformersJsEmbeddingsProvider";
@@ -25,6 +32,7 @@ import { modifyAnyConfigWithSharedConfig } from "../sharedConfig";
2532

2633
import { convertPromptBlockToSlashCommand } from "../../commands/slash/promptBlockSlashCommand";
2734
import { slashCommandFromPromptFile } from "../../commands/slash/promptFileSlashCommand";
35+
import { loadJsonMcpConfigs } from "../../context/mcp/json/loadJsonMcpConfigs";
2836
import { getControlPlaneEnvSync } from "../../control-plane/env";
2937
import { PolicySingleton } from "../../control-plane/PolicySingleton";
3038
import { getBaseToolDefinitions } from "../../tools";
@@ -34,7 +42,10 @@ import { getAllDotContinueDefinitionFiles } from "../loadLocalAssistants";
3442
import { unrollLocalYamlBlocks } from "./loadLocalYamlBlocks";
3543
import { LocalPlatformClient } from "./LocalPlatformClient";
3644
import { llmsFromModelConfig } from "./models";
37-
import { convertYamlRuleToContinueRule } from "./yamlToContinueConfig";
45+
import {
46+
convertYamlMcpConfigToInternalMcpOptions,
47+
convertYamlRuleToContinueRule,
48+
} from "./yamlToContinueConfig";
3849

3950
async function loadConfigYaml(options: {
4051
overrideConfigYaml: AssistantUnrolled | undefined;
@@ -227,17 +238,19 @@ export async function configYamlToContinueConfig(options: {
227238
}));
228239

229240
config.mcpServers?.forEach((mcpServer) => {
230-
const mcpArgVariables =
231-
mcpServer.args?.filter((arg) => TEMPLATE_VAR_REGEX.test(arg)) ?? [];
241+
if ("args" in mcpServer) {
242+
const mcpArgVariables =
243+
mcpServer.args?.filter((arg) => TEMPLATE_VAR_REGEX.test(arg)) ?? [];
232244

233-
if (mcpArgVariables.length === 0) {
234-
return;
235-
}
245+
if (mcpArgVariables.length === 0) {
246+
return;
247+
}
236248

237-
localErrors.push({
238-
fatal: false,
239-
message: `MCP server "${mcpServer.name}" has unsubstituted variables in args: ${mcpArgVariables.join(", ")}. Please refer to https://docs.continue.dev/hub/secrets/secret-types for managing hub secrets.`,
240-
});
249+
localErrors.push({
250+
fatal: false,
251+
message: `MCP server "${mcpServer.name}" has unsubstituted variables in args: ${mcpArgVariables.join(", ")}. Please refer to https://docs.continue.dev/hub/secrets/secret-types for managing hub secrets.`,
252+
});
253+
}
241254
});
242255

243256
// Prompt files -
@@ -381,25 +394,18 @@ export async function configYamlToContinueConfig(options: {
381394
if (orgPolicy?.policy?.allowMcpServers === false) {
382395
await mcpManager.shutdown();
383396
} else {
384-
mcpManager.setConnections(
385-
(config.mcpServers ?? []).map((server) => ({
386-
id: server.name,
387-
name: server.name,
388-
sourceFile: server.sourceFile,
389-
transport: {
390-
type: "stdio",
391-
args: [],
392-
requestOptions: mergeConfigYamlRequestOptions(
393-
server.requestOptions,
394-
config.requestOptions,
395-
),
396-
...(server as any), // TODO: fix the types on mcpServers in config-yaml
397-
},
398-
timeout: server.connectionTimeout,
399-
})),
400-
false,
401-
{ ide },
397+
const mcpOptions: InternalMcpOptions[] = (config.mcpServers ?? []).map(
398+
(server) =>
399+
convertYamlMcpConfigToInternalMcpOptions(server, config.requestOptions),
400+
);
401+
const { errors: jsonMcpErrors, mcpServers } = await loadJsonMcpConfigs(
402+
ide,
403+
true,
404+
config.requestOptions,
402405
);
406+
localErrors.push(...jsonMcpErrors);
407+
mcpOptions.push(...mcpServers);
408+
mcpManager.setConnections(mcpOptions, false, { ide });
403409
}
404410

405411
return { config: continueConfig, errors: localErrors };
Lines changed: 51 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
1-
import { MCPServer, Rule } from "@continuedev/config-yaml";
2-
import { ExperimentalMCPOptions, RuleWithSource } from "../..";
1+
import {
2+
MCPServer,
3+
mergeConfigYamlRequestOptions,
4+
RequestOptions,
5+
Rule,
6+
} from "@continuedev/config-yaml";
7+
import {
8+
InternalMcpOptions,
9+
InternalSseMcpOptions,
10+
InternalStdioMcpOptions,
11+
InternalStreamableHttpMcpOptions,
12+
RuleWithSource,
13+
} from "../..";
314

415
export function convertYamlRuleToContinueRule(rule: Rule): RuleWithSource {
516
if (typeof rule === "string") {
@@ -21,17 +32,43 @@ export function convertYamlRuleToContinueRule(rule: Rule): RuleWithSource {
2132
}
2233
}
2334

24-
export function convertYamlMcpToContinueMcp(
25-
server: MCPServer,
26-
): ExperimentalMCPOptions {
27-
return {
28-
transport: {
29-
type: "stdio",
30-
command: server.command,
31-
args: server.args ?? [],
32-
env: server.env,
33-
cwd: server.cwd,
34-
} as any, // TODO: Fix the mcpServers types in config-yaml (discriminated union)
35-
timeout: server.connectionTimeout,
35+
export function convertYamlMcpConfigToInternalMcpOptions(
36+
config: MCPServer,
37+
globalRequestOptions?: RequestOptions,
38+
): InternalMcpOptions {
39+
const { connectionTimeout, faviconUrl, name, sourceFile } = config;
40+
const shared = {
41+
id: name,
42+
name,
43+
faviconUrl: faviconUrl,
44+
timeout: connectionTimeout,
45+
sourceFile,
3646
};
47+
// Stdio
48+
if ("command" in config) {
49+
const { args, command, cwd, env, type } = config;
50+
const stdioOptions: InternalStdioMcpOptions = {
51+
type,
52+
command,
53+
args,
54+
cwd,
55+
env,
56+
...shared,
57+
};
58+
return stdioOptions;
59+
}
60+
// HTTP/SSE
61+
const { type, url, requestOptions } = config;
62+
const httpSseConfig:
63+
| InternalStreamableHttpMcpOptions
64+
| InternalSseMcpOptions = {
65+
type,
66+
url,
67+
requestOptions: mergeConfigYamlRequestOptions(
68+
requestOptions,
69+
globalRequestOptions,
70+
),
71+
...shared,
72+
};
73+
return httpSseConfig;
3774
}

0 commit comments

Comments
 (0)