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.