MCP in Microsoft Agent Framework: Exposing AI Agent as an MCP Tool-with a twist

The title of the article is not clickbait. It is intentional. There is a very specific reason behind it.
I will explain the reason, the problems and why I think that the title reflects what I am about to discuss in this article.
Model Context Protocol (MCP)
To get started with MCP, there are many online resources available that cover everything from MCP fundamental concepts and architecture to tools, resources, prompts and so on and so forth.
You can refer to the following links to understand what MCP is all about
Introduction to MCP >> https://modelcontextprotocol.io/docs/2026-07-28/getting-started/intro
C# MCP SDK >> https://csharp.sdk.modelcontextprotocol.io/v1/api/ModelContextProtocol.Server.McpServerTool.html
Build MCP Server in C# >> https://devblogs.microsoft.com/dotnet/build-a-model-context-protocol-mcp-server-in-csharp/
I don't want to repeat the same MCP concepts in this article that are already covered in abundance in the above resources and are more technically superior and in-depth compared to my abilities to explain them :)
The intention of this article is to focus on a specific issue that I faced.
Problem Statement
So, I had a scenario where I wanted to expose an AIAgent as an MCP tool . The conventional approach is to expose it as the .AsAIFunction() method to the McpServerTool class.
McpServerTool tool = McpServerTool.Create(agent.AsAIFunction());
and then add McpServerTool (tool) to the builder services collection
builder.Services
.AddMcpServer()
.WithHttpTransport()
.WithTools([tool]);
This registers the McpServerTool with the MCP server, adding it to the server's tool collection so that the tool can be exposed to MCP clients.
Assume that there is an agent called SomeAIAgent with SomeAIFunction registered as one of its tools.
AIAgent SomeAIAgent = new OpenAIClient(apiKey)
.......,
.......,
.AsAIAgent(.....),
tools: [AIFunctionFactory.Create(SomeAIFunction)]
);
But here comes the twist.
What if your AIFunctions are registered with DI (Dependency Injection) and you have to resolve and invoke these AIFunctions as MCP endpoint ?
Look at the following example where an AIFunction is registered with the DI container.
builder.Services.AddSingleton<AIFunction>(sp =>
{
return AIFunctionFactory.Create(SomeAIFunction, new AIFunctionFactoryOptions { Name = "SomeAIFunctionName", Description = "Some Description", SerializerOptions = SomeeSerializerContext.Default.Options });
});
This is where things starts to get interesting.
Normally, to use the above function I will have to rewrite them in a class marked [McpServerToolType] and expose methods with the [McpServerTool] annotation.
For example the following screenshot taken from the example here shows how MCP Server tool is defined so that they can be exposed through a MCP endpoint.
Of course I didn't wanted to redesign all my AIFunctions to be inline and compatible with the above design. This would had required a lot of code refactoring.
So I had to somehow figure out a way where in I can use the DI registered AIFunctions without having to rewrite them all over again.
MCP Inspector
But before we get into more details of the issue , lets first walkthrough the basic steps of setting up and installing MCP inspector to test our MCP endpoints.
To run MCP inspector you need Node.js in your system. To verify if Node.js is installed , run the following command in PowerShell
node -v
If it does not return a version number then it indicates that Node.js is not installed.
Execute the following command in PowerShell to install Node.js
winget install OpenJS.NodeJS.LTS
Accept the license agreement and verify the installation by re executing the following command.
node -v
It should now return the node version number.
Once its verified that node.js is installed , execute the following command to install MCP inspector
npm install -g @modelcontextprotocol/inspector
Now execute the following command in PowerShell to run MCP inspector.
npx -y @modelcontextprotocol/inspector
This will open the MCP inspector UI in a new browser page.
We will have to add MCP server details which basically is going to be the AIAgent that we are going to expose as a MCP tool. We will come back to this later in the article.
Project Setup
Create a new ASP.Net core project and add the following packages
dotnet add package Azure;
dotnet add package Azure.AI.OpenAI;
dotnet add package Microsoft.Agents.AI;
dotnet add package Microsoft.Extensions.AI;
dotnet add package ModelContextProtocol.Server;
dotnet add package System.Text.Json.Serialization;
dotnet add package Microsoft.Extensions.Configuration;
dotnet add package Microsoft.Extensions.DependencyInjection;
dotnet add package Microsoft.Extensions.Logging;
Add appsetting.json to the project
"AppSettings": {
"Chat_DeploymentName": "Deployment Name",
"EndPoint": "Azure OpenAI endpoint",
"ApiKey": "Azure OpenAI API key"
}
Code
Once all the artifacts in place, add the following code to Program.cs
Program.cs >>
var configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false)
.Build();
Create a web application instance
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
Read credentials and register Chatclient
var credential = new AzureKeyCredential(configuration["AppSettings:ApiKey"]);
servicecollection.AddKeyedChatClient(
"ChatClient",
(
sp =>
new AzureOpenAIClient(new Uri(configuration["AppSettings:EndPoint"]), credential) .GetChatClient(configuration["AppSettings:Chat_DeploymentName"])
.AsIChatClient()
)
);
Inject and register a ChatClientAgent
builder.Services.AddSingleton<ChatClientAgent>(sp =>
{
return new ChatClientAgent(sp.GetKeyedService<IChatClient>("ChatClient"));
});
Lets have a function called ReturnCityTemperature that returns the temperature of a given city. This function is to be registered with the DI container as an AIFunction.
We define the required request and response objects for CityTemperature
CityTemperatureSearchRequest & CityTemperatureSearchResponse >>
public class CityTemperatureSearchRequest
{
public string City { get; set; }
}
public class CityTemperatureSearchResponse
{
public string City { get; set; }
public string Temperature { get; set; }
}
Next, create serialization metadata for both request and response types.
[JsonSerializable(typeof(CityTemperatureSearchRequest))]
[JsonSerializable(typeof(CityTemperatureSearchResponse))]
internal sealed partial class CityTemperatureSerializerContext : JsonSerializerContext;
The following delegate returns the underlying data
ReturnCityTemperature >>
public static Func<CityTemperatureSearchRequest, CityTemperatureSearchResponse> ReturnCityTemperature = (CityTemperatureSearchRequest) =>
{
switch (CityTemperatureSearchRequest.City)
{
case "Mumbai":
return new CityTemperatureSearchResponse
{
City = "Mumbai",
Temperature = "40"
};
break;
case "Pune":
return new CityTemperatureSearchResponse
{
City = "Pune",
Temperature = "41"
};
break;
case "Delhi":
return new CityTemperatureSearchResponse
{
City = "Delhi",
Temperature = "42"
};
break;
case "Chennai":
return new CityTemperatureSearchResponse
{
City = "Chennai",
Temperature = "43"
};
break;
}
return new CityTemperatureSearchResponse
{
City = CityTemperatureSearchRequest.City,
Temperature = "Unknown"
};
};
Register the above delegate function with the DI container as an AIFunction along with the corresponding SerializerOptions that were defined earlier.
builder.Services.AddSingleton <AIFunction> (sp => {
return AIFunctionFactory.Create(ReturnCityTemperature, new AIFunctionFactoryOptions {
Name = "ReturnCityTemperature", Description = "Gets the temperature of a city", SerializerOptions = CityTemperatureSerializerContext.Default.Options
});
});
Register the MCP Server >>
MCP server is added to the service collection and with streamable HTTP configured through WithHttpTransport extension and mapped to "api/mcp".
builder.Services.AddMcpServer()
.WithHttpTransport(options =>
{
options.Stateless = true;
}
);
WebApplication app = builder.Build();
app.MapMcp(pattern: "api/mcp");
app.Run();
There is a very important extension called .WithToolsFromAssembly() that auto discovers all classes and tools annotated with [McpServerToolType] and [McpServerTool] respectively.
which unfortunately will be of no use for me, as my tools are DI registered.
Now, here comes the interesting and the important aspect.
How to expose all the DI registered tools through the MCP Server ?
This is how to do it
builder.Services.AddSingleton < McpServerTool > (sp => {
var chatClientList = sp.GetRequiredKeyedService < IChatClient > ("ChatClient");
var aifunctions = sp.GetServices < AIFunction > ();
List < AITool > functions = new(aifunctions);
var options = new ChatClientAgentOptions {
ChatOptions = new ChatOptions {
Tools = functions
}
};
var agent = chatClientList.AsAIAgent(options);
return McpServerTool.Create(agent.AsAIFunction(new AIFunctionFactoryOptions {
Name = "WeatherAgent"
}));
});
builder.Services.AddMcpServer()
.WithHttpTransport(options => {
options.Stateless = true;
});
WebApplication app = builder.Build();
app.MapMcp(pattern: "api/mcp");
app.Run();
Lets dig into the above code step by step >>
Register McpServerTool as a singleton in the DI container.
builder.Services.AddSingleton<McpServerTool>
Fetch keyed IChatClient and AIFunction from the DI container.
Add them to the collection variable functions of type List
var chatClientList = sp.GetRequiredKeyedService<IChatClient>("ChatClient");
var aifunctions = sp.GetServices<AIFunction>();
List<AITool> functions = new(aifunctions);
var options = new ChatClientAgentOptions
{
ChatOptions = new ChatOptions
{
Tools = functions
}
};
var agent = chatClientList.AsAIAgent(options);
Set ChatClientAgentOptions and assign functions variable to Tools property in ChatOptions and then assign it to chatClientList with an .AsAIAgent() extension method chain to create an AIAgent.
var agent = chatClientList.AsAIAgent(options);
Now return the agent as an AIFunction with the required options
return McpServerTool.Create(agent.AsAIFunction(new AIFunctionFactoryOptions
{
Name = "WeatherAgent"
}));
Now things even get more interesting.
Let me send a prompt that queries the underlying data.
What is the temperature in Pune?
The result is just the response text with no additional details that you would normally expect from an agent response.
Now, let me make a few changes in how I expose the agent to the MCP Server.
builder.Services.AddSingleton<McpServerTool>(sp =>
{
var chatClientList = sp.GetRequiredKeyedService<IChatClient>("ChatClient");
var aifunctions = sp.GetServices<AIFunction>();
List<AITool> functions = new(aifunctions);
var options = new ChatClientAgentOptions
{
ChatOptions = new ChatOptions
{
Tools = functions
}
};
var agent = chatClientList.AsAIAgent(options);
var agentFunction = AIFunctionFactory.Create(
async (string message) =>
{
var response = await agent.RunAsync(message);
return response;
},
new AIFunctionFactoryOptions
{
Name = "WeatherAgent"
});
return McpServerTool.Create(agentFunction);
});
builder.Services.AddMcpServer()
.WithHttpTransport(options =>
{
options.Stateless = true;
}
);
WebApplication app = builder.Build();
app.MapMcp(pattern: "api/mcp");
app.Run();
}
So the changes that I have done is that I am executing the agent by capturing the user i/p and agent response in the AIFunction itself and then exposing the AIFunction(agentFunction) to the McpServerTool.
I created an on the fly AIFunction that takes user input (message argument) and executed the agent within the AIFunction and it then returns the agent response.
Now instead of passing the agent as AIFunction (agent.AsAIFunction) to the MCPServerTool, I am sending the agentFunction to it
var agentFunction = AIFunctionFactory.Create(
async (string message) =>
{
var response = await agent.RunAsync(message);
return response;
},
new AIFunctionFactoryOptions
{
Name = "WeatherAgent"
});
return McpServerTool.Create(agentFunction);
});
and now try the execution with the same prompt that I tried earlier
What is the temperature in Pune?
and this time the agent output format changes, which is lot more detailed
The reason for this behavior ? Honestly I don't know :).
I only found out this behavior by accident. I will have to put in some time to understand this behavior.
Lets now look into how to configure MCP to test the agent.
Once MCP inspector is installed and running, click the Add Server option on top right and select Add manually option
In the pop up that follows, select streamable-http
Select WithHttpTransport because we have used this option to set our MCP Server
In the next pop up, add the project endpoint
In this example its running on : http://localhost:5176
Under Servers option the registered MCP server should be visible.
If everything has been configured properly the server should display the Connected status which the indicates the client has successfully connected to the server.
Select the Tools option and it will list the Name of the AIFunction that was used to set the MCPServerTool.
Run the prompt and now you will have a more detailed output.
Execution
Final Take
Though this is an edge case there might be possibility that you might face such a situation where your agentic architecture revolves more around DI pattern.
Rather than introducing a completely separate approach for exposing the agent through MCP, you can leverage the existing DI-based approach that i have suggested in this article to integrate MCP with it. This allows the agent to remain consistent with the application's DI pattern while making it compatible with the MCP requirements.
Thanks for reading !!!



