Exposing API as MCP Server in Azure API Manager is not passing POST body to API
I am trying to expose my API as an MCP server through Azure API Manager.
The API has a POST endpoint which accepts a simple body of 2 numbers
{
"number1": 10,
"number2": 5
}
It then does some very basic math on them (add, subtract, multiply, divide) and returns the data as a collection.
{
"results": [
{
"number1": 10,
"number2": 5,
"action": "Add",
"result": 15
},
{
"number1": 10,
"number2": 5,
"action": "Subtract",
"result": 5
},
{
"number1": 10,
"number2": 5,
"action": "Multiply",
"result": 50
},
{
"number1": 10,
"number2": 5,
"action": "Divide",
"result": 2
}
]
}
The API is working completely as expected when used as a regular API call.
Now, I've gone through the online Microsoft Material to expose as an MCP server.
In POSTMAN, I can connect to the MCP server and get the list of tools (just one) and it then provides a form to fill in the numbers as per screenshot below.
When I fill in the values, it populates then form on the right had side correctly but when I hit RUN (aka Send), it calls the API (I can see in the logs) but there does not appear to be any body content passed.
My logs. The vertical bars are delimiters I add to "bracket" the message better
2025-07-10T10:22:59Z [Information] MCPEvalController triggered.
2025-07-10T10:22:59Z [Information] Request Body: ||
If I change the API to a GET and just use static values (in the code), then the MCP server actually returns me the correct response.
So, why is my payload body not being passed to my API
Azure API Management
-
Krishna Chowdary Paricharla • 2,915 Reputation points • Microsoft External Staff • Moderator
2025-07-10T12:14:03.1666667+00:00 Hello Chris Hammond •,
Your MCP tool config may be sending data incorrectly. In the screenshot, the JSON payload is structured like this:
json Copy { "method": "tools/call", "params": { "name": "calculator", "arguments": { "number2": 0, "number1": 0 } } }This is wrapped, which means your backend must be able to unwrap this structure, or you need to adjust the policy to extract and forward only the
arguments.So, you can Add a policy in APIM to extract
argumentsand forward as new body:xml Copy <inbound> <base /> <set-body>@(context.Request.Body.As<JObject>()["params"]["arguments"].ToString())</set-body> </inbound>This will replace the full wrapper with:
json Copy { "number1": 10, "number2": 5 }So, then your backend receives the correct payload.
-
Chris Hammond • 46 Reputation points2025-07-10T13:27:17.5066667+00:00 Thanks @Krishna Chowdary Paricharla for the quick response.
I have added policy as per requested, but now POSTMAN denies the connection with
However, what I also tried was to connect WITHOUT policies, which works, and then add the policies once connected and then get the follow response
"message": "Error calling method: tools/call. Error: Error POSTing to endpoint (HTTP 500): { \"statusCode\": 500, \"message\": \"Internal server error\", \"activityId\": \"dfe2765f-f87a-46e2-b666-ae8fc12dba8d\" }" -
Krishna Chowdary Paricharla • 2,915 Reputation points • Microsoft External Staff • Moderator
2025-07-10T13:37:31.62+00:00 Hello Chris Hammond •,
The
500 Internal Server Errorindicates the request is now reaching the backend, but it’s likely failing due to the body format.Please check the following:
Body Format: Ensure your backend expects a flat JSON like:
json Copy { "number1": 10, "number2": 5 }Please double-check that your backend API can accept and deserialize this structure. If it's expecting a different format (e.g., the wrapper), it may throw an error.
Log the incoming request body in your API code to see exactly what it receives. That’ll help confirm if the transformation is working and what part is causing the 500 error.
Use Azure API Management trace to verify request transformation and response.
As a test, temporarily remove the transformation policy and directly send the raw payload from Postman to your backend (outside MCP flow) to confirm the backend logic is sound.
-
Chris Hammond • 46 Reputation points2025-07-10T13:46:02.3166667+00:00 The API is functioning perfectly as the raw data (under
arguments) is being processed fine when sent using a regular RESTFul POST request.Using MCP, with the policy in place, this is NOT getting to the backend service as I am viewing the Log Stream in the target Azure Function and nothing is being logged, I have explicit log messages in place with the endpoint is triggered.
Without the policy, I get log messages, but it shows the body content as empty
-
Krishna Chowdary Paricharla • 2,915 Reputation points • Microsoft External Staff • Moderator
2025-07-11T05:40:34.1366667+00:00 Hello Chris Hammond •,
Enable tracing in APIM to confirm what the body looks like after the policy is applied and what is forwarded to the backend.As a Fallback Option If MCP is sending the full wrapper and your backend can already parse it, you might not need to unwrap it in policy at all. Try removing the policy and simply log the full raw body (
params.arguments) in your function. -
Chris Hammond • 46 Reputation points2025-07-11T06:30:36.7066667+00:00 Try removing the policy and simply log the full raw body (params.arguments) in your function.This is the problem though ... NOTHING is coming, the request body is an empty string
[OpenApiOperation(operationId: "MCPEvalController", tags: new[] { "MCPEvalController" }, Summary = "Testing MCP through APIM", Visibility = OpenApiVisibilityType.Important)] [OpenApiRequestBody("application/json", typeof(MCPRequest), Description = "MCP Request Body")] [OpenApiResponseWithBody(statusCode: HttpStatusCode.OK, contentType: System.Net.Mime.MediaTypeNames.Application.Json, bodyType: typeof(MCPResponse), CustomHeaderType = typeof(OpenApiCustomResponseHeader), Summary = "OK", Description = "OK")] [Function("MCPEvalController")] public async Task<HttpResponseData> RunP([HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "Calculator")] HttpRequestData req) { var logger = req.FunctionContext.GetLogger("HttpFunction"); logger.LogInformation("MCPEvalController triggered."); string requestBody = await req.ReadAsStringAsync(); logger.LogInformation($"Request Body: |{requestBody}|");2025-07-10T10:22:59Z [Information] MCPEvalController triggered.
2025-07-10T10:22:59Z [Information] Request Body: || -
Chris Hammond • 46 Reputation points2025-07-11T07:09:54.4866667+00:00 Ahh ... A breaktrhrough .. I was too fixated on the MCP policies and thought lets check the actual underlying API.
So I created a HEADER parameter which is a copy of the body content and my function is receiving this.
<inbound> <base /> <set-header name="mytest" exists-action="append"> <value>@(context.Request.Body.As<String>())</value> </set-header> </inbound>mytest: {"number2":7,"number1":3}So, it appears that somewhere in my function is having issues
-
Krishna Chowdary Paricharla • 2,915 Reputation points • Microsoft External Staff • Moderator
2025-07-11T07:21:32.0166667+00:00 Hello Chris Hammond •,
Since your function correctly receives the body when passed as a header, this confirms that the issue is within the function’s body parsing logic, not the MCP or APIM policy.Suggested Checks:
Make sure the function reads the body properly (e.g., using
req.Body).Ensure
Content-Typeis set toapplication/json.If using model binding, verify the JSON matches the expected schema.
-
Krishna Chowdary Paricharla • 2,915 Reputation points • Microsoft External Staff • Moderator
2025-07-15T10:23:56.92+00:00 Hello Chris Hammond •,
Just following up to see if you had a chance to review my previous response and if you have any additional questions. -
Chris Hammond • 46 Reputation points2025-07-15T10:29:02.8733333+00:00 I have carried out some more testing but it is simply refusing to work. If I call the APIM endpoint for the Function App as a REST Api, it works absolutely fine, but if I call it through its MCP endpoint its not working.
Plus, now, POSTMAN cannot even Connect to the server, let alone making a request.
-
Chris Hammond • 46 Reputation points2025-07-16T08:41:18.3533333+00:00 So, further developments.
If I put the
<set-body>@(context.Request.Body.As<String>())</set-body>as part of the inbound policy of the MCP server, the whole thing breaks, cannot even connect to the server.However, if I put it in the underlying API policy then everything starts working. Wahoo...
But, just because its working, I do not believe this is the solution because I don't want to go through every single API adding what is essentially a
a = astatement. There must be something fundamentally wrong somewhere in the request chain. -
Chris Hammond • 46 Reputation points2025-07-16T08:46:56.5466667+00:00 Additionally, in the MCP Policy, I added the following to the inbound policy, but this is NOT passed on to the API
<set-header name="myHeader" exists-action="override"> <value>Hello</value> </set-header> -
Krishna Chowdary Paricharla • 2,915 Reputation points • Microsoft External Staff • Moderator
2025-07-16T14:04:08.6433333+00:00 Hello Chris Hammond •,
Great work troubleshooting this — your findings make a lot of sense.
What’s happening is that when you add
<set-body>to the MCP policy, it breaks because the MCP endpoint doesn’t directly forward the request like a normal API. It wraps the request in its own structure and handles it internally before calling your backend. So if you try to set or modify the body there, it can disrupt that process — which explains the connection issues and why Postman couldn’t reach it.On the other hand, adding the same logic in the backend API’s policy works fine because that’s where the actual request gets sent to your Azure Function. That’s the right place to reshape the request before your function handles it.
Same goes for headers like
myHeader— setting them in the MCP layer won’t automatically pass them through unless you explicitly forward them from the backend policy.So, you’re right — while putting the logic in the backend works, it’s not ideal to do it for every single API manually. This points to a bigger structural gap in how MCP handles requests versus direct API calls.
-
Chris Hammond • 46 Reputation points2025-07-17T07:58:17.4566667+00:00 So, you’re right — while putting the logic in the backend works, it’s not ideal to do it for every single API manually. This points to a bigger structural gap in how MCP handles requests versus direct API calls.
Exactly this ... So where do we go from here!
-
Chris Hammond • 46 Reputation points2025-07-17T07:59:27.5166667+00:00 I wonder if Julia Kasper reads these ;)
-
Krishna Chowdary Paricharla • 2,915 Reputation points • Microsoft External Staff • Moderator
2025-07-23T05:38:10.66+00:00 Hello Chris Hammond •,
To avoid repeating logic in every API, you can use Policy Fragments in Azure API Management. These are reusable policy blocks that can be included across multiple APIs.Reference documentation:
Next Step:
Create a shared fragment (e.g., to unwrap
params.arguments) and include it in your API policies using:xml Copy <include-fragment fragment-id="McpBodyUnwrapper" /> -
Krishna Chowdary Paricharla • 2,915 Reputation points • Microsoft External Staff • Moderator
2025-07-25T05:14:51.4+00:00 Hello Chris Hammond •,
Just checking in to see if you've had a chance to review my previous response. Let me know if you have any additional questions.
-
Chris Hammond • 46 Reputation points2025-07-25T06:41:06.0333333+00:00 Hi @Krishna Chowdary Paricharla
I've been in contact with the API Management Product Group and they have advised that this may be a bug in the system (yes, Julia's team :) )
"Thanks for reaching out. I believe the issue you are experiencing may be related to a bug with tool argument parsing which we have since resolved in our latest release version. "
They then listed the regions that have been updated unfortunately my specific region has yet to receive the update, so I am just waiting now. Once I have the updated version, I'll test again.
Thank you though for your diligent efforts to help me resolve the issue
-
Marco117 • 80 Reputation points
2025-08-08T17:25:20.0733333+00:00 Are there any new updates on this case?
I am validating the MCP exposed in API Management (it is an Azure function) with the MCP Inspector, and I am encountering the same problem: it sends an empty body.
-
Daniel Jonathan • 10 Reputation points
2025-08-14T07:12:11.8633333+00:00 do you have any updates on this? could you please share the listed regions where this bug has been fixed. (Im facing this problem in West Europe)
-
Chris Hammond • 46 Reputation points2025-10-30T12:26:05.3433333+00:00 Just to update ... I've not had time to pursue this recently due to other priorities, but I did try again today and I still have the same issues.
The REST API is working absolutely fine , but when "fronted" with the API Manager MCP server, I still get
{ "content": [ { "type": "text", "text": "" } ] } -
Marco117 • 80 Reputation points
2025-12-01T18:18:03.6733333+00:00 this doesnt work yet -_- , omg I have not able to expose simple azure function as MCP servers using APIM
Sign in to comment