diff --git a/.vscode/mcp.json b/.vscode/mcp.json
index 65f254e..4c5f8a5 100644
--- a/.vscode/mcp.json
+++ b/.vscode/mcp.json
@@ -8,6 +8,11 @@
"MAF_DOCTOR_INIT_VERSION": "1.14.0",
"MAF_DOCTOR_WORKSPACE_ROOTS": "C:\\Users\\jesseliberty\\ai\\.net\\blogWriter;E:\\ai\\.net\\blog\\blogMigration---public"
}
+ },
+ "microsoft-learn": {
+ "type": "http",
+ "url": "https://learn.microsoft.com/api/mcp"
}
}
}
+
diff --git a/BlogWriter.csproj b/BlogWriter.csproj
index 62c5a4a..c1243c7 100644
--- a/BlogWriter.csproj
+++ b/BlogWriter.csproj
@@ -28,6 +28,7 @@
+
diff --git a/Program.cs b/Program.cs
index 7180f26..641dd42 100644
--- a/Program.cs
+++ b/Program.cs
@@ -6,6 +6,7 @@
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
+using ModelContextProtocol.Client;
using OpenAI;
// Secrets come from the .NET user-secrets store and from
@@ -115,11 +116,23 @@ async Task PostWithRetryAsync(string requestUri, object bod
name: "tavily_search",
description: "A search engine optimized for comprehensive, accurate, and trusted results.");
+// Microsoft Learn's remote MCP server exposes docs search/fetch tools the
+// Researcher can call alongside Tavily for authoritative Microsoft/Azure content.
+await using McpClient microsoftLearnMcp = await McpClient.CreateAsync(
+ new HttpClientTransport(new HttpClientTransportOptions
+ {
+ Endpoint = new Uri("https://learn.microsoft.com/api/mcp"),
+ Name = "microsoft-learn",
+ }));
+IList microsoftLearnTools = await microsoftLearnMcp.ListToolsAsync();
+
+List researcherTools = [tavilyTool, .. microsoftLearnTools];
+
// Creating a callable object
using ILoggerFactory loggerFactory = LoggerFactory.Create(builder => builder.AddConsole());
var bloggerAgent = new BloggerAgent(llm, chatOptions, loggerFactory.CreateLogger());
-var researcherAgent = new ResearcherAgent(llm, chatOptions, tavilyTool, loggerFactory.CreateLogger());
+var researcherAgent = new ResearcherAgent(llm, chatOptions, researcherTools, loggerFactory.CreateLogger());
var authorAgent = new AuthorAgent(llm, chatOptions, loggerFactory.CreateLogger());
var reviewerAgent = new ReviewerAgent(llm, chatOptions, loggerFactory.CreateLogger());
var app = new BlogWorkflow(bloggerAgent, researcherAgent, authorAgent, reviewerAgent, loggerFactory.CreateLogger());
diff --git a/ResearcherAgent.cs b/ResearcherAgent.cs
index 3b84e87..65f6bd5 100644
--- a/ResearcherAgent.cs
+++ b/ResearcherAgent.cs
@@ -9,8 +9,9 @@ namespace BlogWriter;
/// Performs research tasks with a and returns
/// concise findings.
///
-/// The agent can call the configured Tavily tool during execution and summarize
-/// results for use in later drafting stages.
+/// The agent can call the configured tools (e.g. Tavily web search, Microsoft
+/// Learn MCP) during execution and summarize results for use in later drafting
+/// stages.
///
public class ResearcherAgent : IResearcherAgent
{
@@ -28,11 +29,13 @@ public class ResearcherAgent : IResearcherAgent
// Per-call output-token cap, applied on each RunAsync to bound cost.
private readonly int? _maxOutputTokens;
- public ResearcherAgent(IChatClient llm, ChatOptions chatOptions, AIFunction tavilyTool, ILogger logger)
+ public ResearcherAgent(IChatClient llm, ChatOptions chatOptions, IEnumerable tools, ILogger logger)
{
_logger = logger;
_maxOutputTokens = chatOptions.MaxOutputTokens;
+ List toolList = [.. tools];
+
_agent = new ChatClientAgent(llm, new ChatClientAgentOptions
{
// Name surfaces in OpenTelemetry traces and agent logs.
@@ -45,29 +48,28 @@ public ResearcherAgent(IChatClient llm, ChatOptions chatOptions, AIFunction tavi
// Preserve the original sampling/cost settings.
Temperature = chatOptions.Temperature,
MaxOutputTokens = chatOptions.MaxOutputTokens,
- // Attaching the tool lets the model call it autonomously.
- Tools = [tavilyTool],
+ // Attaching the tools lets the model call them autonomously.
+ Tools = toolList,
},
})
.AsBuilder()
// Function-invocation middleware: fires around every tool call the agent
- // makes. We log each time the model invokes the Tavily search tool.
+ // makes. We log each time the model invokes one of the attached tools.
.Use(async (agent, context, next, cancellationToken) =>
{
- if (context.Function.Name == tavilyTool.Name)
- {
- _logger.LogInformation(
- "Researcher invoking Tavily tool '{Tool}' with arguments {Arguments}",
- context.Function.Name,
- context.Arguments);
- }
+ _logger.LogInformation(
+ "Researcher invoking tool '{Tool}' with arguments {Arguments}",
+ context.Function.Name,
+ context.Arguments);
return await next(context, cancellationToken);
})
.UseOpenTelemetry(sourceName: "BlogWriter.Agents")
.Build();
- _logger.LogInformation("ResearcherAgent initialized with Tavily tool: {ToolName}", tavilyTool.Name);
+ _logger.LogInformation(
+ "ResearcherAgent initialized with tools: {ToolNames}",
+ string.Join(", ", toolList.Select(t => t.Name)));
}
/// Execute research by letting the agent search and summarise.