Organising the AI Foundry: A Practical Guide for Enterprise Readiness
July 17, 2025[Launched] Generally Available: Azure Database for PostgreSQL flexible server: Indonesia Central
July 17, 2025In many use cases, using remote MCP servers is not uncommon. In particular, if you’re using Azure API Management (APIM), Azure API Center (APIC) or Copilot Studio in Power Platform, integrating with remote MCP servers is inevitable.
MCP (Model Context Protocol) is a protocol used for defining server endpoints that support AI agent workflows, often in Copilot scenarios.
Background
A remote MCP server is basically an HTTP API app that exposes an endpoint of either /mcp
or /sse
depending on the spec the servers support. This endpoint should be accessible from Copilot Studio through a custom connector, APIM or APIC. The easiest and simplest way of exposing this endpoint is to provide an OpenAPI doc. The OpenAPI doc is very simple because it has only one endpoint, and the structure is always the same. Here’s the Swagger (OpenAPI v2) doc, referred from the Copilot Studio doc.
swagger: ‘2.0’
info:
title: Contoso
description: MCP Test Specification, YAML for streamable MCP support in Copilot Studio
version: 1.0.0
host: contoso.com
basePath: /
schemes:
– https
paths:
/mcp:
post:
summary: Contoso Lead Management Server
x-ms-agentic-protocol: mcp-streamable-1.0
operationId: InvokeMCP
responses:
‘200’:
description: Success
But, whenever you write up a remote MCP server, you have to manually provide this Swagger doc. What if it’s automatically served directly from the MCP server on-the-fly? It reduces manual maintenance overhead and ensures consistency. Doesn’t it sound awesome? Let’s figure this out.
Use Microsoft.AspNetCore.OpenApi
to generate OpenAPI doc on-the-fly
As mentioned earlier, an MCP server is basically an HTTP API app, and it shares ASP.NET Core Web API features. So, all we need to do is to inject the OpenAPI service to Program.cs
. Here’s the codebase for the MCP server. It declares the remote MCP server with streamable HTTP by setting stateless = true
.
…
// Activate MCP server capability.
builder.Services.AddMcpServer()
.WithHttpTransport(o => o.Stateless = true)
.WithToolsFromAssembly();
var app = builder.Build();
…
// Add endpoint for MCP.
app.MapMcp(“/mcp”);
app.Run();
Now, if you want to activate the Swagger doc, add the following lines of code.
…
// Activate MCP server capability.
builder.Services.AddMcpServer()
.WithHttpTransport(o => o.Stateless = true)
.WithToolsFromAssembly();
// 👇👇👇 Add 👇👇👇
// Add OpenAPI doc capability.
builder.Services.AddHttpContextAccessor();
builder.Services.AddOpenApi(“swagger”, o =>
{
// For swagger.json
o.OpenApiVersion = Microsoft.OpenApi.OpenApiSpecVersion.OpenApi2_0;
o.AddDocumentTransformer();
});
builder.Services.AddOpenApi(“openapi”, o =>
{
// For openapi.json
o.OpenApiVersion = Microsoft.OpenApi.OpenApiSpecVersion.OpenApi3_0;
o.AddDocumentTransformer();
});
// 👆👆👆 Add 👆👆👆
var app = builder.Build();
…
// Add endpoint for MCP.
app.MapMcp(“/mcp”);
// 👇👇👇 Add 👇👇👇
// Add endpoint for OpenAPI docs.
app.MapOpenApi(“/{documentName}.json”);
// 👆👆👆 Add 👆👆👆
app.Run();
You may have noticed the McpDocumentTransformer
class. It defines how the OpenAPI doc is generated. As mentioned earlier, the OpenAPI doc for MCP servers are always the same. You can use IHttpContextAccessor
instance to dynamically generate the server URL, but it’s not necessary.
public class McpDocumentTransformer(IHttpContextAccessor accessor) : IOpenApiDocumentTransformer
{
public Task TransformAsync(OpenApiDocument document, OpenApiDocumentTransformerContext context, CancellationToken cancellationToken)
{
document.Info = new OpenApiInfo
{
Title = “MCP server”,
Version = “1.0.0”,
Description = “A simple MCP server.”
};
document.Servers =
[
new OpenApiServer
{
Url = accessor.HttpContext != null
? $”{accessor.HttpContext.Request.Scheme}://{accessor.HttpContext.Request.Host}/”
: “http://localhost:8080/”
}
];
var pathItem = new OpenApiPathItem();
pathItem.AddOperation(OperationType.Post, new OpenApiOperation
{
Summary = “MCP endpoint”,
Extensions = new Dictionary
{
[“x-ms-agentic-protocol”] = new OpenApiString(“mcp-streamable-1.0”)
},
OperationId = “InvokeMCP”,
Responses = new OpenApiResponses
{
[“200”] = new OpenApiResponse
{
Description = “Success”,
}
}
});
document.Paths ??= [];
document.Paths.Add(“/mcp”, pathItem);
return Task.CompletedTask;
}
}
That’s it! After enabling the OpenAPI capability, your MCP server has three endpoints:
MCP server
├─ /mcp (endpoint)
├─ /swagger.json (auto-generated) – Copilot Studio
└─ /openapi.json (auto-generated) – APIM or APIC
Run your MCP server and navigate to http://localhost:8080/swagger.json
for Swagger (OpenAPI v2) or http://localhost:8080/swagger.json
for OpenAPI v3 doc, and you’ll be able to see the OpenAPI docs rendered on-the-fly. Therefore, you can use the Swagger doc for Copilot Studio and the OpenAPI doc for APIM or APIC integration.
CORS considerations
Now, this MCP server can be deployed to Azure – either App Service or Container Apps. However, to connect to the MCP server from Power Platform or APIM, you need to enable CORS. Here are two possible ways.
1. Enable CORS policy in your cloud instance
Depending on your cloud instance you’re using, there are various ways you can follow:
2. Enable CORS policy in your MCP server
Alternatively, you can enable the CORS policy within your application.
Either approach works. The former leverages built-in features of Azure services, while the latter is a cloud-agnostic option.
Sample app?
Of course, there is a sample app for you to check out. Visit the MCP Samples in .NET repository and find the to-do management MCP server. There are other sample apps in the same repo so you can try them out on your local machine and Azure.
More resources
If you’d like to learn more about MCP in .NET, here are some additional resources worth exploring: