diff --git a/apps/benchmark/server/mesh.ts b/apps/benchmark/server/mesh.ts index 4980ba30e1..00ad8530cb 100644 --- a/apps/benchmark/server/mesh.ts +++ b/apps/benchmark/server/mesh.ts @@ -272,7 +272,7 @@ export async function startMesh(port: number): Promise { gatewayId: string, strategy?: VirtualMCPtoolSelectionStrategy, ): string => { - const url = new URL(`/mcp/gateway/${gatewayId}`, baseUrl); + const url = new URL(`/mcp/${gatewayId}`, baseUrl); if (strategy) { url.searchParams.set("mode", strategy); } diff --git a/apps/docs/client/src/content/en/mcp-mesh/api-reference.mdx b/apps/docs/client/src/content/en/mcp-mesh/api-reference.mdx index b2f4a49833..9a30c29afc 100644 --- a/apps/docs/client/src/content/en/mcp-mesh/api-reference.mdx +++ b/apps/docs/client/src/content/en/mcp-mesh/api-reference.mdx @@ -20,10 +20,9 @@ Proxies MCP requests to a single upstream connection. ### Agent (virtual server) -- `POST /mcp/gateway/:gatewayId` -- `POST /mcp/gateway` (default Agent; requires `x-org-id` or `x-org-slug`) +- `POST /mcp/:connectionId` (where `connectionId` is the Agent's connection ID) -Aggregates tools/resources/prompts across multiple connections. +Agents are virtual MCPs stored as connections with `connection_type = 'VIRTUAL'`. They aggregate tools/resources/prompts across multiple connections. Supports `?mode=` query parameter for tool selection strategy (passthrough, smart_tool_selection, code_execution). ## OAuth discovery diff --git a/apps/docs/client/src/content/en/mcp-mesh/connect-clients.mdx b/apps/docs/client/src/content/en/mcp-mesh/connect-clients.mdx index 4daf540ce4..a584cfb041 100644 --- a/apps/docs/client/src/content/en/mcp-mesh/connect-clients.mdx +++ b/apps/docs/client/src/content/en/mcp-mesh/connect-clients.mdx @@ -16,8 +16,7 @@ There are two common ways to connect clients to the Mesh: ## Endpoints you’ll use - **Management MCP**: `POST /mcp` -- **Connection proxy**: `POST /mcp/:connectionId` -- **Agent (virtual server)**: `POST /mcp/gateway/:gatewayId` (or omit `:gatewayId` to use the org default Agent with headers) +- **Connection proxy**: `POST /mcp/:connectionId` (works for both regular connections and Agents/virtual MCPs) If your goal is a "single MCP URL" for a curated tool surface, use **Agents**. If you want a direct pipe to one upstream MCP, use **Connection proxy**. diff --git a/apps/docs/client/src/content/en/mcp-mesh/mcp-gateways.mdx b/apps/docs/client/src/content/en/mcp-mesh/mcp-gateways.mdx index 757ee00b60..71eedecfbb 100644 --- a/apps/docs/client/src/content/en/mcp-mesh/mcp-gateways.mdx +++ b/apps/docs/client/src/content/en/mcp-mesh/mcp-gateways.mdx @@ -18,12 +18,9 @@ This is how you create a curated endpoint for clients without exposing every too ## Endpoint -- `POST /mcp/gateway/:gatewayId` +- `POST /mcp/:connectionId` (where `connectionId` is the Agent's connection ID) -You can also omit `:gatewayId` and rely on the organization's default Agent by providing one of these headers: - -- `x-org-id`, or -- `x-org-slug` +Agents are virtual MCPs stored as connections with `connection_type = 'VIRTUAL'`. They use the same endpoint pattern as regular connections. They support the `?mode=` query parameter for the tool exposure strategy (passthrough, smart_tool_selection, code_execution). ## Selection modes diff --git a/apps/docs/client/src/content/pt-br/mcp-mesh/api-reference.mdx b/apps/docs/client/src/content/pt-br/mcp-mesh/api-reference.mdx index 097f979e5f..ce4f229fdf 100644 --- a/apps/docs/client/src/content/pt-br/mcp-mesh/api-reference.mdx +++ b/apps/docs/client/src/content/pt-br/mcp-mesh/api-reference.mdx @@ -20,10 +20,9 @@ Faz proxy de requisições MCP para uma única connection upstream. ### Agent (MCP virtual) -- `POST /mcp/gateway/:gatewayId` (compatibilidade: o path HTTP ainda usa `gateway`, mas refere-se ao Agent) -- `POST /mcp/gateway` (Agent padrão da org; requer `x-org-id` ou `x-org-slug`) +- `POST /mcp/:connectionId` (onde `connectionId` é o ID da connection do Agent) -Faz proxy de requisições MCP para um agent (agregação + estratégia de exposição). +Agents são MCPs virtuais armazenados como connections com `connection_type = 'VIRTUAL'`. Eles agregam tools/resources/prompts de múltiplas connections. Suporta parâmetro de query `?mode=` para estratégia de seleção de tools (passthrough, smart_tool_selection, code_execution). ## Endpoints de autenticação (alto nível) diff --git a/apps/mesh/src/api/routes/proxy.ts b/apps/mesh/src/api/routes/proxy.ts index cf9cef5be7..724cbd1621 100644 --- a/apps/mesh/src/api/routes/proxy.ts +++ b/apps/mesh/src/api/routes/proxy.ts @@ -14,6 +14,10 @@ import { getMonitoringConfig } from "@/core/config"; import { createClient } from "@/mcp-clients"; import { buildRequestHeaders } from "@/mcp-clients/outbound/headers"; +import { + parseStrategyFromMode, + type ToolSelectionStrategy, +} from "@/mcp-clients/virtual-mcp"; import type { ConnectionEntity } from "@/tools/connection/schema"; import type { ServerClient } from "@decocms/bindings/mcp"; import { createServerFromClient } from "@decocms/mesh-sdk"; @@ -230,7 +234,10 @@ const DEFAULT_SERVER_CAPABILITIES = { async function createMCPProxyDoNotUseDirectly( connectionIdOrConnection: string | ConnectionEntity, ctx: MeshContext, - { superUser }: { superUser: boolean }, // this is basically used for background workers that needs cross-organization access + { + superUser, + strategy = "passthrough", + }: { superUser: boolean; strategy?: ToolSelectionStrategy }, // this is basically used for background workers that needs cross-organization access ): Promise { // Get connection details const connection = @@ -255,7 +262,7 @@ async function createMCPProxyDoNotUseDirectly( } // Create client early - needed for listTools and other operations - const client = await createClient(connection, ctx, superUser); + const client = await createClient(connection, ctx, { superUser, strategy }); // List tools from downstream connection // Uses indexed tools if available, falls back to client for connections without cached tools @@ -598,9 +605,11 @@ async function createMCPProxyDoNotUseDirectly( export async function createMCPProxy( connectionIdOrConnection: string | ConnectionEntity, ctx: MeshContext, + strategy?: ToolSelectionStrategy, ) { return createMCPProxyDoNotUseDirectly(connectionIdOrConnection, ctx, { superUser: false, + strategy, }); } @@ -608,14 +617,17 @@ export async function createMCPProxy( * Create a MCP proxy for a downstream connection with super user access * @param connectionIdOrConnection - The connection ID or connection entity * @param ctx - The mesh context + * @param strategy - Optional tool selection strategy * @returns The MCP proxy */ export async function dangerouslyCreateSuperUserMCPProxy( connectionIdOrConnection: string | ConnectionEntity, ctx: MeshContext, + strategy?: ToolSelectionStrategy, ) { return createMCPProxyDoNotUseDirectly(connectionIdOrConnection, ctx, { superUser: true, + strategy, }); } @@ -645,7 +657,9 @@ app.all("/:connectionId", async (c) => { try { try { - const client = await ctx.createMCPProxy(connectionId); + // Parse strategy from query string mode parameter (defaults to passthrough) + const strategy = parseStrategyFromMode(c.req.query("mode")); + const client = await ctx.createMCPProxy(connectionId, strategy); // Create server from client using the bridge const server = createServerFromClient(client, { @@ -710,7 +724,9 @@ app.all("/:connectionId/call-tool/:toolName", async (c) => { const ctx = c.get("meshContext"); try { - const client = await ctx.createMCPProxy(connectionId); + // Parse strategy from query string mode parameter (defaults to passthrough) + const strategy = parseStrategyFromMode(c.req.query("mode")); + const client = await ctx.createMCPProxy(connectionId, strategy); const result = await client.callTool({ name: toolName, arguments: await c.req.json(), diff --git a/apps/mesh/src/core/context-factory.ts b/apps/mesh/src/core/context-factory.ts index f84902e432..5722e94546 100644 --- a/apps/mesh/src/core/context-factory.ts +++ b/apps/mesh/src/core/context-factory.ts @@ -827,8 +827,11 @@ export async function createMeshContextFactory( ), }, eventBus: config.eventBus, - createMCPProxy: async (conn: string | ConnectionEntity) => { - return await createMCPProxy(conn, ctx); + createMCPProxy: async ( + conn: string | ConnectionEntity, + strategy?: Parameters[2], + ) => { + return await createMCPProxy(conn, ctx, strategy); }, getOrCreateClient: clientPool, }; diff --git a/apps/mesh/src/core/mesh-context.ts b/apps/mesh/src/core/mesh-context.ts index 58e38a314d..7b4920eba0 100644 --- a/apps/mesh/src/core/mesh-context.ts +++ b/apps/mesh/src/core/mesh-context.ts @@ -314,6 +314,7 @@ export interface MeshContext { // Utility for creating MCP Proxies createMCPProxy: ( conn: Parameters[0], + strategy?: Parameters[2], ) => ReturnType; // Client pool for STDIO connection reuse (LRU cache) diff --git a/apps/mesh/src/mcp-clients/index.ts b/apps/mesh/src/mcp-clients/index.ts index 1870e7d6ea..3e118b27f1 100644 --- a/apps/mesh/src/mcp-clients/index.ts +++ b/apps/mesh/src/mcp-clients/index.ts @@ -9,7 +9,7 @@ import type { MeshContext } from "@/core/mesh-context"; import type { ConnectionEntity } from "@/tools/connection/schema"; import { Client } from "@modelcontextprotocol/sdk/client/index.js"; import { createOutboundClient } from "./outbound"; -import { createVirtualClient } from "./virtual-mcp"; +import { createVirtualClient, type ToolSelectionStrategy } from "./virtual-mcp"; /** * Create an MCP client from a connection entity @@ -20,16 +20,17 @@ import { createVirtualClient } from "./virtual-mcp"; * * @param connection - Connection entity from database * @param ctx - Mesh context for creating clients - * @param superUser - Whether to use superuser mode for background processes + * @param options - Options object with superUser flag and optional strategy * @returns Client instance connected to the MCP server */ export async function createClient( connection: ConnectionEntity, ctx: MeshContext, - superUser = false, + options: { superUser?: boolean; strategy?: ToolSelectionStrategy } = {}, ): Promise { + const { superUser = false, strategy = "passthrough" } = options; if (connection.connection_type === "VIRTUAL") { - return createVirtualClient(connection, ctx); + return createVirtualClient(connection, ctx, strategy); } return createOutboundClient(connection, ctx, superUser); } diff --git a/apps/mesh/src/mcp-clients/virtual-mcp/index.ts b/apps/mesh/src/mcp-clients/virtual-mcp/index.ts index 0272083412..f12fc82f1f 100644 --- a/apps/mesh/src/mcp-clients/virtual-mcp/index.ts +++ b/apps/mesh/src/mcp-clients/virtual-mcp/index.ts @@ -33,11 +33,13 @@ function isSelfReferencingVirtual( * * @param connection - Connection entity with VIRTUAL type * @param ctx - Mesh context for creating proxies + * @param strategy - Tool selection strategy (defaults to passthrough) * @returns Client instance with aggregated tools, resources, and prompts */ export async function createVirtualClient( connection: ConnectionEntity, ctx: MeshContext, + strategy: ToolSelectionStrategy = "passthrough", ): Promise { // Virtual MCP ID is the connection ID const virtualMcpId = connection.id; @@ -49,7 +51,7 @@ export async function createVirtualClient( } // Create client from virtual MCP entity - return createVirtualClientFrom(virtualMcp, ctx, "passthrough"); + return createVirtualClientFrom(virtualMcp, ctx, strategy); } /** diff --git a/apps/mesh/src/web/components/details/virtual-mcp/virtual-mcp-share-modal.tsx b/apps/mesh/src/web/components/details/virtual-mcp/virtual-mcp-share-modal.tsx index 3066f31a65..44756868de 100644 --- a/apps/mesh/src/web/components/details/virtual-mcp/virtual-mcp-share-modal.tsx +++ b/apps/mesh/src/web/components/details/virtual-mcp/virtual-mcp-share-modal.tsx @@ -205,9 +205,9 @@ export function VirtualMCPShareModal({ }; // Build URL with mode query parameter - // Virtual MCPs (agents) are accessed via the virtual-mcp endpoint + // Virtual MCPs (agents) are accessed via the /mcp/:connectionId endpoint const virtualMcpUrl = new URL( - `/mcp/virtual-mcp/${virtualMcp.id}`, + `/mcp/${virtualMcp.id}`, window.location.origin, ); virtualMcpUrl.searchParams.set("mode", mode);