| title | Dynamic MCP Routing |
|---|---|
| parent | MCP |
| grand_parent | Extensibility |
| nav_order | 3 |
A Power Platform connector that routes MCP Streamable HTTP traffic to one of several MCP server instances, selected at configuration time via a dropdown. Demonstrates how to use a catalog service, x-ms-dynamic-values, x-ms-agentic-protocol, and a script.csx URL rewriter to give a single connector access to multiple independent MCP servers.
flowchart TD
CS["Copilot Studio<br/>(MCP client)"]
CONN["Power Platform Connector<br/>x-ms-agentic-protocol: mcp-streamable-1.0"]
SCRIPT["script.csx<br/>Rewrites URL based on<br/>selected instance"]
CAT["Catalog Server<br/>GET /instances"]
MCP["MCP Server<br/>(single process, path-based routing)"]
C["/instances/contoso/mcp"]
F["/instances/fabrikam/mcp"]
N["/instances/northwind/mcp"]
CS -->|"MCP protocol"| CONN
CONN -->|"ListInstances"| CAT
CONN -->|"InvokeMCP"| SCRIPT
SCRIPT -->|"rewritten URL"| MCP
MCP --> C
MCP --> F
MCP --> N
Three components:
-
Catalog server (
src/catalog/) — REST endpoint returning a list of MCP server instances with their endpoint URLs. The connector'sListInstancesoperation hits this to populate the instance dropdown. -
MCP server (
src/mcp-server/) — Single Express server hosting multiple independent MCP servers at/instances/:id/mcp. Each instance advertises its own tools (list_projects,get_project_details) with instance-specific data. Stateless: a freshServer+StreamableHTTPServerTransportis created per request. -
Power Platform connector (
connector/) — Swagger definition with two operations:ListInstances(internal, for dropdown) — calls the catalogInvokeMCP— annotated withx-ms-agentic-protocol: mcp-streamable-1.0, with aninstanceUrlparameter populated viax-ms-dynamic-valuesscript.csxrewrites theInvokeMCPrequest URL from the catalog host to the selected instance's MCP endpoint
The connector's InvokeMCP parameter uses x-ms-dynamic-values to call ListInstances, which returns instances with their mcpUrl:
[
{ "id": "contoso", "name": "Contoso", "mcpUrl": "https://host/instances/contoso/mcp" },
{ "id": "fabrikam", "name": "Fabrikam", "mcpUrl": "https://host/instances/fabrikam/mcp" }
]The agent builder picks an instance from the dropdown when adding the connector action.
The swagger host points at the catalog server. When Copilot Studio calls InvokeMCP, script.csx intercepts the request, reads the instanceUrl query parameter, and rewrites the full URL (scheme, host, port, path) to the selected instance's MCP endpoint:
var targetUri = new Uri(instanceUrl);
var builder = new UriBuilder(Context.Request.RequestUri)
{
Scheme = targetUri.Scheme,
Host = targetUri.Host,
Port = targetUri.Port,
Path = targetUri.AbsolutePath
};Each instance endpoint is a fully independent MCP server. Copilot Studio handles the MCP protocol (initialize, tools/list, tools/call) natively. The mock data includes three fictional organizations with project portfolio data.
dynamic-mcp-routing-typescript/
├── src/
│ ├── catalog/
│ │ └── index.ts # Catalog REST server
│ └── mcp-server/
│ ├── index.ts # Multi-instance MCP server
│ └── data.ts # Mock instances, projects, details
├── connector/
│ ├── apiDefinition.swagger.json # Swagger with x-ms-agentic-protocol
│ ├── apiProperties.json # No connection parameters
│ └── script.csx # URL rewriter for dynamic routing
├── scripts/
│ ├── deploy.sh # One-step deploy for macOS / Linux
│ └── deploy.ps1 # One-step deploy for Windows
├── package.json
├── tsconfig.json
└── README.md
- Node.js 18+
- Dev Tunnels CLI
- paconn CLI (
pip install paconn) - A Power Platform environment with Copilot Studio access
npm install
npm run buildIn two terminals:
# Terminal 1 — Catalog server (port 3000)
npm run start:catalog
# Terminal 2 — MCP server (port 3001)
npm run start:mcpUsing the CLI:
devtunnel host -p 3000 -p 3001 --allow-anonymousThis outputs two URLs like:
https://abc123-3000.euw.devtunnels.ms (catalog)
https://abc123-3001.euw.devtunnels.ms (MCP)
Restart the catalog server with the MCP tunnel URL:
MCP_SERVER_BASE=https://abc123-3001.euw.devtunnels.ms npm run start:catalogUpdate connector/apiDefinition.swagger.json — set host to your catalog tunnel hostname (e.g. abc123-3000.euw.devtunnels.ms), then:
python3 -m paconn login
python3 -m paconn create \
-e YOUR_ENVIRONMENT_ID \
-d connector/apiDefinition.swagger.json \
-p connector/apiProperties.json \
-x connector/script.csxOr use the one-step deploy script that handles login, servers, tunnel, and connector deployment:
Bash (macOS / Linux):
./scripts/deploy.sh YOUR_ENVIRONMENT_ID [TENANT_ID]PowerShell (Windows):
.\scripts\deploy.ps1 -EnvironmentId YOUR_ENVIRONMENT_ID [-TenantId TENANT_ID]- Open your agent in Copilot Studio
- Go to Tools > Add tool > filter by Model Context Protocol
- Search for "Dynamic MCP Connector" and add it
- Under Inputs, select an instance from the Instance dropdown (e.g. "Contoso", "Fabrikam", "Northwind")
- The Tools section will populate with the MCP tools for the selected instance — tools won't appear until you pick an instance
- Click Save
Once the connector is added to an agent:
- "What projects are available?" — calls
list_projects - "Show me the details for the ERP rollout" — calls
get_project_detailswithprojectId: "erp-rollout" - "What are the risks on the supply chain project?" — calls
get_project_detailsfor Fabrikam
Add a new instance: Add an entry to the instances array in src/mcp-server/data.ts and src/catalog/index.ts, along with its projects and details.
Add a new tool: Register additional tools in the createServer function in src/mcp-server/index.ts using ListToolsRequestSchema and CallToolRequestSchema handlers.
Remove dynamic routing: If you only need a single MCP server, simplify by removing the catalog server and script.csx, and point the swagger host directly at the MCP server.