Skip to content

feat: add support for SSE MCP servers #5517

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions core/config/workspace/workspaceBlocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,16 @@ function getContentsForNewBlock(blockType: BlockType): ConfigYaml {
configYaml.mcpServers = [
{
name: "New MCP server",
type: "stdio",
command: "npx",
args: ["-y", "<your-mcp-server>"],
env: {},
},
{
name: "New MCP server",
type: "sse",
url: "https://example.org/sse"
},
];
break;
}
Expand Down
107 changes: 107 additions & 0 deletions core/config/yaml/loadYaml.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { MCPServer } from "@continuedev/config-yaml";
import { convertYamlMcpToContinueMcp } from "./loadYaml";

describe("MCP Server Configuration Tests", () => {
test("should convert stdio MCP server correctly", () => {
const stdioServer: MCPServer = {
name: "Test Stdio Server",
type: "stdio",
command: "uvx",
args: ["mcp-server-sqlite", "--db-path", "/test.db"],
env: { TEST_ENV: "value" },
connectionTimeout: 5000
};

const result = convertYamlMcpToContinueMcp(stdioServer);

expect(result).toEqual({
transport: {
type: "stdio",
command: "uvx",
args: ["mcp-server-sqlite", "--db-path", "/test.db"],
env: { TEST_ENV: "value" }
},
timeout: 5000
});
});

test("should convert SSE MCP server correctly", () => {
const sseServer: MCPServer = {
name: "Test SSE Server",
type: "sse",
url: "http://localhost:8150/cosmos/mcp/v1/sse",
connectionTimeout: 3000
};

const result = convertYamlMcpToContinueMcp(sseServer);

expect(result).toEqual({
transport: {
type: "sse",
url: "http://localhost:8150/cosmos/mcp/v1/sse"
},
timeout: 3000
});
});

test("should convert WebSocket MCP server correctly", () => {
const wsServer: MCPServer = {
name: "Test WebSocket Server",
type: "websocket",
url: "ws://localhost:8150/cosmos/mcp/v1/ws",
connectionTimeout: 10000
};

const result = convertYamlMcpToContinueMcp(wsServer);

expect(result).toEqual({
transport: {
type: "websocket",
url: "ws://localhost:8150/cosmos/mcp/v1/ws"
},
timeout: 10000
});
});

test("should handle legacy MCP server format for backward compatibility", () => {
// Test with old format that doesn't have a type field
const legacyServer = {
name: "Legacy Server",
command: "old-command",
args: ["--legacy"],
connectionTimeout: 2000
} as any;

const result = convertYamlMcpToContinueMcp(legacyServer);

expect(result).toEqual({
transport: {
type: "stdio",
command: "old-command",
args: ["--legacy"],
env: undefined
},
timeout: 2000
});
});

test("should handle missing optional fields", () => {
const minimalServer: MCPServer = {
name: "Minimal Server",
type: "stdio",
command: "minimal"
};

const result = convertYamlMcpToContinueMcp(minimalServer);

expect(result).toEqual({
transport: {
type: "stdio",
command: "minimal",
args: [],
env: undefined
},
timeout: undefined
});
});
});
44 changes: 34 additions & 10 deletions core/config/yaml/loadYaml.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,17 +63,42 @@ function convertYamlRuleToContinueRule(rule: Rule): RuleWithSource {
}
}

function convertYamlMcpToContinueMcp(
export function convertYamlMcpToContinueMcp(
server: MCPServer,
): ExperimentalMCPOptions {
const transportConfig = (() => {
switch (server.type) {
case "stdio":
return {
type: "stdio" as const,
command: server.command,
args: server.args ?? [],
env: server.env,
};
case "sse":
return {
type: "sse" as const,
url: server.url,
};
case "websocket":
return {
type: "websocket" as const,
url: server.url,
};
default:
// Default to stdio for backward compatibility
return {
type: "stdio" as const,
command: (server as any).command,
args: (server as any).args ?? [],
env: (server as any).env,
};
}
})();

return {
transport: {
type: "stdio",
command: server.command,
args: server.args ?? [],
env: server.env,
},
timeout: server.connectionTimeout,
transport: transportConfig,
timeout: server.connectionTimeout
};
}

Expand Down Expand Up @@ -245,7 +270,7 @@ async function configYamlToContinueConfig(options: {

config.mcpServers?.forEach((mcpServer) => {
const mcpArgVariables =
mcpServer.args?.filter((arg) => TEMPLATE_VAR_REGEX.test(arg)) ?? [];
(mcpServer.type === "stdio" ? mcpServer.args?.filter((arg) => TEMPLATE_VAR_REGEX.test(arg)) : []) ?? [];

if (mcpArgVariables.length === 0) {
return;
Expand Down Expand Up @@ -457,7 +482,6 @@ async function configYamlToContinueConfig(options: {
id: server.name,
name: server.name,
transport: {
type: "stdio",
args: [],
...server,
},
Expand Down
10 changes: 10 additions & 0 deletions docs/docs/customize/deep-dives/mcp.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,15 @@ To set up your own MCP server, read the [MCP quickstart](https://modelcontextpro
```yaml title="config.yaml"
mcpServers:
- name: My MCP Server
type: stdio
command: uvx
args:
- mcp-server-sqlite
- --db-path
- /Users/NAME/test.db
- name: My MCP Server with SSE
type: sse
url: "https://example.com/mcp-server/sse"
```
</TabItem>
<TabItem value="json" label="JSON">
Expand All @@ -38,6 +42,12 @@ To set up your own MCP server, read the [MCP quickstart](https://modelcontextpro
"command": "uvx",
"args": ["mcp-server-sqlite", "--db-path", "/Users/NAME/test.db"]
}
},
{
"transport": {
"type": "sse",
"url": "https://example.com/mcp-server/sse"
}
}
]
}
Expand Down
14 changes: 13 additions & 1 deletion docs/docs/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -379,21 +379,33 @@ prompts, context, and tool use. Continue supports any MCP server with the MCP co
**Properties:**

- `name` (**required**): The name of the MCP server.
- `type"` (**required**): The type of the MCP server. Can be "stdio", "sse", or "websocket".

**Stdio type**

- `command` (**required**): The command used to start the server.
- `args`: An optional array of arguments for the command.
- `env`: An optional map of environment variables for the server process.
- `connectionTimeout`: An optional connection timeout number to the server in milliseconds.

**SSE or Websocket type**

- `url` (**required**): The URL of the MCP server.

**Example:**

```yaml title="config.yaml"
mcpServers:
- name: My MCP Server
- name: My MCP Server with stdio
type: stdio
command: uvx
args:
- mcp-server-sqlite
- --db-path
- /Users/NAME/test.db
- name: My MCP Server with SSE
type: sse
url: "https://example.com/mcp-server/sse"
```

### `data`
Expand Down
46 changes: 40 additions & 6 deletions packages/config-yaml/src/converter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,14 +128,48 @@ function convertCustomCommand(

function convertMcp(mcp: any): NonNullable<ConfigYaml["mcpServers"]>[number] {
const { transport } = mcp;
const { command, args, env, server_name } = transport;
const { type } = transport;

return {
command,
args,
env,
name: server_name || "MCP Server",
// Common properties for all server types
const baseServer: any = {
name: mcp.name,
type: type,
};

if (mcp.faviconUrl) {
baseServer.faviconUrl = mcp.faviconUrl
}
if (mcp.connectionTimeout) {
baseServer.connectionTimeout = mcp.connectionTimeout
}

// Type-specific properties
switch (type) {
case "stdio":
const stdioServer = {
...baseServer,
command: transport.command,
};

if (transport.args) {
stdioServer.args = transport.args
}
if (transport.env) {
stdioServer.env = transport.env
}

return stdioServer;

case "sse":
case "websocket":
return {
...baseServer,
url: transport.url
};

default:
throw new Error(`Unknown MCP server type: ${type}`);
}
}

function convertDoc(
Expand Down
49 changes: 38 additions & 11 deletions packages/config-yaml/src/schemas/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,31 @@ export const contextSchema = z.object({
params: z.any().optional(),
});

const mcpServerSchema = z.object({
name: z.string(),
command: z.string(),
faviconUrl: z.string().optional(),
args: z.array(z.string()).optional(),
env: z.record(z.string()).optional(),
connectionTimeout: z.number().gt(0).optional()
});
const mcpServerSchema = z.discriminatedUnion("type", [
z.object({
name: z.string(),
type: z.literal("stdio"),
command: z.string(),
faviconUrl: z.string().optional(),
args: z.array(z.string()).optional(),
env: z.record(z.string()).optional(),
connectionTimeout: z.number().gt(0).optional()
}),
z.object({
name: z.string(),
type: z.literal("sse"),
url: z.string(),
faviconUrl: z.string().optional(),
connectionTimeout: z.number().gt(0).optional()
}),
z.object({
name: z.string(),
type: z.literal("websocket"),
url: z.string(),
faviconUrl: z.string().optional(),
connectionTimeout: z.number().gt(0).optional()
})
]);

export type MCPServer = z.infer<typeof mcpServerSchema>;

Expand Down Expand Up @@ -61,7 +78,17 @@ export const blockItemWrapperSchema = <T extends z.AnyZodObject>(
export const blockOrSchema = <T extends z.AnyZodObject>(
schema: T,
usesSchema: z.ZodTypeAny = defaultUsesSchema,
) => z.union([schema, blockItemWrapperSchema(schema, usesSchema)]);
isDiscriminatedUnion?: boolean,
) => {
if (isDiscriminatedUnion) {
return z.union([schema, z.object({
uses: usesSchema,
with: z.record(z.string()).optional(),
override: z.any().optional(),
})]);
}
return z.union([schema, blockItemWrapperSchema(schema, usesSchema)]);
};

export const commonMetadataSchema = z.object({
tags: z.string().optional(),
Expand Down Expand Up @@ -98,7 +125,7 @@ export const configYamlSchema = baseConfigYamlSchema.extend({
.optional(),
context: z.array(blockOrSchema(contextSchema)).optional(),
data: z.array(blockOrSchema(dataSchema)).optional(),
mcpServers: z.array(blockOrSchema(mcpServerSchema)).optional(),
mcpServers: z.array(blockOrSchema(mcpServerSchema as any, defaultUsesSchema, true)).optional(),
rules: z
.array(
z.union([
Expand Down Expand Up @@ -223,4 +250,4 @@ export const configSchema = z.object({
api_key: z.string().optional(),
});

export type Config = z.infer<typeof configSchema>;
export type Config = z.infer<typeof configSchema>;
Loading