Skip to content

Commit 7d73718

Browse files
smakoshclaudesteebchen
authored
feat(gateway): add MCP server support (#1558)
## Summary - Add Model Context Protocol (MCP) endpoint at `/mcp` for tool discovery and execution - New MCP servers dialog in playground for managing HTTP-based MCP server connections - Integration with chat API to enable MCP tools in conversations ## Test plan - [ ] Test MCP endpoint responds to tool listing requests - [ ] Test MCP tool execution through the gateway - [ ] Verify MCP servers dialog allows adding/removing servers in playground - [ ] Test chat completions with MCP tools enabled 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added Model Context Protocol (MCP) support: manage MCP servers (add/edit/enable/disable, persisted locally) and integrate MCP tools into chat streaming with tool-name display. * **Bug Fixes** * Improved tool detection and more robust cleanup/error handling for MCP client connections and streaming flows. * **UI** * Replaced some filter controls with toggle switches for a cleaner layout; tool headers now show tool titles. <sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub> <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> Co-authored-by: Luca Steeb (bot) <contact@luca-steeb.com>
1 parent e97917b commit 7d73718

File tree

15 files changed

+3212
-1536
lines changed

15 files changed

+3212
-1536
lines changed

apps/gateway/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
"@llmgateway/logger": "workspace:*",
3636
"@llmgateway/models": "workspace:*",
3737
"@llmgateway/shared": "workspace:*",
38+
"@modelcontextprotocol/sdk": "1.25.3",
3839
"@opentelemetry/api": "1.9.0",
3940
"@opentelemetry/sdk-node": "0.205.0",
4041
"decimal.js": "10.5.0",

apps/gateway/src/app.ts

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { HealthChecker } from "@llmgateway/shared";
1818

1919
import { anthropic } from "./anthropic/anthropic.js";
2020
import { chat } from "./chat/chat.js";
21+
import { mcpHandler, registerMcpOAuthRoutes } from "./mcp/mcp.js";
2122
import { tracingMiddleware } from "./middleware/tracing.js";
2223
import { models } from "./models/route.js";
2324
import { responses } from "./responses/responses.js";
@@ -77,17 +78,29 @@ app.use(
7778
"http://localhost:3005",
7879
"http://localhost:3006",
7980
],
80-
allowHeaders: ["Content-Type", "Authorization", "Cache-Control"],
81+
allowHeaders: [
82+
"Content-Type",
83+
"Authorization",
84+
"Cache-Control",
85+
"x-api-key",
86+
"mcp-session-id",
87+
],
8188
allowMethods: ["POST", "GET", "OPTIONS", "PUT", "PATCH", "DELETE"],
82-
exposeHeaders: ["Content-Length"],
89+
exposeHeaders: ["Content-Length", "mcp-session-id"],
8390
maxAge: 600,
8491
credentials: true,
8592
}),
8693
);
8794

8895
// Middleware to check for application/json content type on POST requests
96+
// Excludes /mcp endpoint which handles its own content type validation
97+
// Excludes /oauth endpoints which accept form-urlencoded or JSON
8998
app.use("*", async (c, next) => {
90-
if (c.req.method === "POST") {
99+
if (
100+
c.req.method === "POST" &&
101+
!c.req.path.startsWith("/mcp") &&
102+
!c.req.path.startsWith("/oauth")
103+
) {
91104
const contentType = c.req.header("Content-Type");
92105
if (!contentType || !contentType.includes("application/json")) {
93106
throw new HTTPException(415, {
@@ -237,6 +250,13 @@ v1.route("/responses", responses);
237250

238251
app.route("/v1", v1);
239252

253+
// MCP endpoint - Model Context Protocol server
254+
app.all("/mcp", mcpHandler);
255+
256+
// Register MCP OAuth routes for Claude Code authentication workaround
257+
// This adds OAuth endpoints at /.well-known/oauth-authorization-server and /oauth/*
258+
registerMcpOAuthRoutes(app);
259+
240260
app.doc("/json", config);
241261

242262
app.get("/docs", swaggerUI({ url: "/json" }));

apps/gateway/src/chat/chat.ts

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -260,11 +260,25 @@ chat.openapi(completions, async (c) => {
260260
plugins,
261261
} = validationResult.data;
262262

263+
// Debug: Log tools received from the AI SDK (development only)
264+
if (process.env.NODE_ENV !== "production" && tools && tools.length > 0) {
265+
logger.debug("Tools received by gateway", { count: tools.length });
266+
for (const tool of tools) {
267+
if (tool.type === "function") {
268+
logger.debug(`Function tool: ${tool.function?.name || "unknown"}`, {
269+
hasParameters: !!tool.function?.parameters,
270+
parametersPreview: tool.function?.parameters
271+
? JSON.stringify(tool.function.parameters).slice(0, 500)
272+
: "none",
273+
});
274+
} else if (tool.type === "web_search") {
275+
logger.debug("Web search tool configured");
276+
}
277+
}
278+
}
279+
263280
// If web_search parameter is true, automatically add the web_search tool
264-
if (
265-
web_search &&
266-
(!tools || !tools.some((t: any) => t.type === "web_search"))
267-
) {
281+
if (web_search && (!tools || !tools.some((t) => t.type === "web_search"))) {
268282
tools = tools || [];
269283
tools.push({
270284
type: "web_search" as const,

0 commit comments

Comments
 (0)