Do conhecimento corporativo ao código: evoluindo aplicações com Work IQ, Foundry IQ e GitHub Copilot — Parte 2
Na primeira parte, configuramos toda a infraestrutura no Azure, subimos os documentos nos containers do Storage Account, criamos os knowledge sources e as knowledge bases no Foundry IQ, e usamos o WorkIQ MCP para descobrir os dois requisitos que chegaram de forma informal, um por e-mail pedindo OpenTelemetry e outro pelo Teams pedindo desconto no total do pedido. Agora vamos implementar o servidor MCP que conecta essas bases ao VS Code e deixar o GitHub Copilot colocar tudo em prática.
Agora que as bases estão prontas no Azure, precisamos de um servidor MCP que exponha essas duas bases como ferramentas dentro do VS Code. Esse é o papel do projeto Docs.mcp, um MCP server em .NET 10 que usa o SDK do Azure AI Search para consultar knowledge bases via agentic retrieval. Comece criando o projeto:
mkdir Docs.mcp
cd Docs.mcp
dotnet new web
Em seguida adicione os pacotes necessários, o SDK do Azure AI Search com a versão beta que traz suporte às knowledge bases e o pacote do Model Context Protocol para ASP.NET Core:
dotnet add package Azure.Search.Documents --version 11.8.0-beta.1
dotnet add package ModelContextProtocol.AspNetCore --version 1.2.0O .csproj final fica assim:
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Azure.Search.Documents" Version="11.8.0-beta.1" />
<PackageReference Include="ModelContextProtocol.AspNetCore" Version="1.2.0" />
</ItemGroup>
</Project>A configuração da aplicação acontece no Program.cs, que registra o servidor MCP com transport HTTP e descobre as ferramentas via reflection no assembly:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHttpClient();
builder.Services
.AddMcpServer()
.WithHttpTransport()
.WithToolsFromAssembly();
var app = builder.Build();
app.MapMcp();
app.Run();AddMcpServer()
- Registra os serviços do servidor MCP no container de DI da aplicação.
WithHttpTransport()
- Configura o transport HTTP, expondo as ferramentas para consumo via JSON-RPC sobre HTTP.
WithToolsFromAssembly()
- Varre o assembly em busca de classes decoradas com `[McpServerToolType]` e métodos com `[McpServerTool]`, registrando cada um como uma ferramenta consumível pelo cliente MCP.
MapMcp()
- Mapeia as rotas HTTP do servidor MCP no pipeline ASP.NET Core.
As credenciais e nomes das knowledge bases ficam no appsettings.json, prontos para serem sobrescritos por variáveis de ambiente ou User Secrets:
"AzureAISearch": {
"Endpoint": "<YOUR_SEARCH_SERVICE_URL>",
"ApiKey": "<YOUR_SEARCH_API_KEY>",
"DevelopmentKnowledgeBaseName": "<YOUR_DEVELOPMENT_KNOWLEDGE_BASE_NAME>",
"BusinessLogicKnowledgeBaseName": "<YOUR_BUSINESS_LOGIC_KNOWLEDGE_BASE_NAME>"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"O coração do projeto está em Tools/EnterpriseDocsTool.cs, que define duas ferramentas MCP, uma para cada knowledge base. Ambas reutilizam o mesmo método privado de consulta:
using ModelContextProtocol.Server;
using System.ComponentModel;
using Azure;
using Azure.Search.Documents.KnowledgeBases;
using Azure.Search.Documents.KnowledgeBases.Models;
namespace Docs.mcp.Tools;
[McpServerToolType]
public static class EnterpriseDocsTool
{
private static string GetSearchEndpoint(IConfiguration configuration) => configuration["AzureAISearch:Endpoint"]!;
private static string GetSearchApiKey(IConfiguration configuration) => configuration["AzureAISearch:ApiKey"]!;
private static string GetDevelopmentKnowledgeBaseName(IConfiguration configuration) => configuration["AzureAISearch:DevelopmentKnowledgeBaseName"]!;
private static string GetBusinessLogicKnowledgeBaseName(IConfiguration configuration) => configuration["AzureAISearch:BusinessLogicKnowledgeBaseName"]!;
[McpServerTool, Description("Used to retrieve information on how code standards are implemented in our company. Retrieves enterprise documentation from the Azure AI Search Knowledge Base, including internal coding standards, architectural patterns, technical guidelines, internal development guidance, and official tool documentation. It helps developers follow approved internal coding patterns and complement them with authoritative vendor docs when needed. Provide a natural language query to get consolidated, relevant content from the knowledge base.")]
public static async Task<string> QueryDevelopmentDocs(
IConfiguration configuration,
[Description("The query to retrieve consolidated documentation data for.")] string query)
{
var searchEndpoint = GetSearchEndpoint(configuration);
var searchApiKey = GetSearchApiKey(configuration);
var knowledgeBaseName = GetDevelopmentKnowledgeBaseName(configuration);
if (string.IsNullOrWhiteSpace(searchEndpoint) || string.IsNullOrWhiteSpace(searchApiKey) || string.IsNullOrWhiteSpace(knowledgeBaseName))
{
return "Azure AI Search configuration is missing. Please check Endpoint, ApiKey, and KnowledgeBaseName settings.";
}
return await QueryKnowledgeBase(searchEndpoint, searchApiKey, knowledgeBaseName, query);
}
[McpServerTool, Description("Used to retrieve business logic documentation for internal rules, domain policies, pricing, discounts, workflows, and process decisions. It helps developers and analysts apply approved business behavior consistently across systems and use official internal documentation as the source of truth. Provide a natural language query to get consolidated, relevant content from the business logic knowledge base.")]
public static async Task<string> QueryBusinessLogicDocs(
IConfiguration configuration,
[Description("The query to retrieve consolidated business logic documentation data for.")] string query)
{
var searchEndpoint = GetSearchEndpoint(configuration);
var searchApiKey = GetSearchApiKey(configuration);
var knowledgeBaseName = GetBusinessLogicKnowledgeBaseName(configuration);
if (string.IsNullOrWhiteSpace(searchEndpoint) || string.IsNullOrWhiteSpace(searchApiKey) || string.IsNullOrWhiteSpace(knowledgeBaseName))
{
return "Azure AI Search configuration is missing. Please check Endpoint, ApiKey, and BusinessLogicKnowledgeBaseName settings.";
}
return await QueryKnowledgeBase(searchEndpoint, searchApiKey, knowledgeBaseName, query);
}
private static async Task<string> QueryKnowledgeBase(string searchEndpoint, string searchApiKey, string knowledgeBaseName, string query)
{
var credential = new AzureKeyCredential(searchApiKey);
var kbClient = new KnowledgeBaseRetrievalClient(
endpoint: new Uri(searchEndpoint),
knowledgeBaseName: knowledgeBaseName,
credential: credential
);
var retrievalRequest = new KnowledgeBaseRetrievalRequest();
retrievalRequest.Messages.Add(
new KnowledgeBaseMessage(
content: new[] {
new KnowledgeBaseMessageTextContent(query)
}
) { Role = "user" }
);
try
{
var result = await kbClient.RetrieveAsync(retrievalRequest);
var response = result.Value.Response.FirstOrDefault();
if (response?.Content?.FirstOrDefault() is KnowledgeBaseMessageTextContent textContent)
{
return textContent.Text;
}
return "No response content returned from Azure AI Search.";
}
catch (Exception ex)
{
return $"Error querying Azure AI Search: {ex.Message}";
}
}
}
QueryDevelopmentDocs()
- Ferramenta MCP que recebe uma query em linguagem natural e consulta a knowledge base de padrões de desenvolvimento e documentação oficial. O atributo `[Description]` é fundamental porque é exatamente esse texto que o GitHub Copilot usa para decidir quando chamar a ferramenta.
QueryBusinessLogicDocs()
- Ferramenta MCP equivalente, mas voltada à knowledge base de regras de negócio. A descrição enfatiza palavras como rules, pricing, discounts e workflows para guiar o agente a usá-la em decisões de domínio.
QueryKnowledgeBase()
- Método privado compartilhado que monta o `KnowledgeBaseRetrievalClient` com o endpoint, a chave e o nome da knowledge base, encapsula a query em um KnowledgeBaseRetrievalRequest com role user, executa o retrieve e devolve o conteúdo textual consolidado pelo modelo configurado na knowledge base.
Para subir o servidor localmente, basta rodar:
dotnet runPor padrão a aplicação fica disponível em `http://localhost:5145`, conforme definido no launchSettings.json. Em seguida, adicione o servidor enterprise-docs ao .vscode/mcp.json, junto com o workiq que já estava configurado:
{
"servers": {
"workiq": {
"command": "npx",
"args": ["-y", "@microsoft/workiq", "mcp"]
},
"enterprise-docs": {
"type": "http",
"url": "http://localhost:5145"
}
}
}
Rode novamente MCP: List Servers na paleta de comandos do VS Code, inicie o enterprise-docs e confirme que as duas ferramentas, QueryDevelopmentDocs e QueryBusinessLogicDocs, aparecem na lista de tools disponíveis para o Copilot Agent.
A partir daqui, o fluxo fica natural. O WorkIQ é a porta de entrada para descoberta dos requisitos informais, e o enterprise-docs é a porta de entrada para a documentação oficial e as regras internas.
Com a base do Foundry IQ no lugar, podemos pedir ao Copilot a documentação consolidada das duas frentes:
Use the enterprise-docs MCP to find out how the discount should be applied to the order
total in Order.Api, and to understand the OpenTelemetry configuration standard expected
by the company and how to implement it.
A resposta vem com o detalhamento das faixas de desconto extraídas do documento em business-docs e com o passo a passo de configuração do OpenTelemetry combinando o documento interno em docs e o conteúdo oficial indexado via web crawl.


Foi criada a classe `DiscountService` com o método `GetDiscountRate`, que implementa a lógica de desconto baseada nas faixas de valor total do pedido, conforme definido no documento de regras de negócio.
Com os requisitos identificados pelo WorkIQ e a documentação consolidada pelo enterprise-docs, o último passo é pedir ao GitHub Copilot que implemente as mudanças na Order.Api seguindo essas informações. O prompt fica direto:
Implement the two requirements identified in Order.Api: apply the discount to the order
total according to the tiers returned by the enterprise-docs MCP, and configure
OpenTelemetry following the company standard returned by the enterprise-docs MCP.
Create the DiscountService and the GrossTotal, DiscountRate, DiscountAmount and NetTotal fields in the Order entity to support the discount calculation.
Keep the existing structure of Endpoints, DTOs, and Extensions.O GitHub Copilot então combina o contexto do código existente, as regras de negócio e os padrões de desenvolvimento numa implementação alinhada ao que a empresa espera.
Foram adicionados os campos GrossTotal, DiscountRate, DiscountAmount e NetTotal para armazenar o valor total antes do desconto, a taxa de desconto aplicada, o valor do desconto e o total final após o desconto.
namespace Order.Api.Entities;
public class Order
{
public int Id { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public List<OrderItem> Items { get; set; } = [];
public decimal GrossTotal { get; set; }
public decimal DiscountRate { get; set; }
public decimal DiscountAmount { get; set; }
public decimal NetTotal { get; set; }
}A configuração do OpenTelemetry foi adicionada em ConfigureServices, registrando o tracer, o meter e o logger para exportar dados para um endpoint OTLP, que pode ser configurado via variável de ambiente ou User Secrets.
public static IServiceCollection ConfigureServices(this IServiceCollection services, IConfiguration configuration)
{
services.AddOpenApi();
services.AddDbContext<AppDbContext>(options =>
options.UseInMemoryDatabase("OrderDb"));
var otlpEndpoint = configuration["OpenTelemetry:OtlpEndpoint"] ?? "http://localhost:4317";
services.AddOpenTelemetry()
.ConfigureResource(resource => resource
.AddService("order-api"))
.WithTracing(tracing => tracing
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddOtlpExporter(options => options.Endpoint = new Uri(otlpEndpoint)))
.WithMetrics(metrics => metrics
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddOtlpExporter(options => options.Endpoint = new Uri(otlpEndpoint)));
services.AddLogging(logging => logging
.AddOpenTelemetry(options =>
{
options.SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("order-api"));
options.AddOtlpExporter(exporterOptions => exporterOptions.Endpoint = new Uri(otlpEndpoint));
}));
return services;Foi criada a classe DiscountService com o método GetDiscountRate, que implementa a lógica de desconto baseada nas faixas de valor total do pedido, conforme definido no documento de regras de negócio.
namespace Order.Api.Services;
public static class DiscountService
{
public static decimal GetDiscountRate(decimal grossTotal) =>
grossTotal switch
{
< 1000m => 0m,
< 2000m => 0.10m,
< 3000m => 0.15m,
_ => 0.20m
};
}O Dto de resposta do pedido foi atualizado para incluir os novos campos relacionados ao desconto e ao total do pedido.
namespace Order.Api.Dtos;
public record OrderDto(int Id, DateTime CreatedAt, List<OrderItemDto> Items, decimal GrossTotal, decimal DiscountRate, decimal DiscountAmount, decimal NetTotal);Além disso, todos os endpoints tiveram a adição de activities, por exemplo:
using var activity = ActivitySource.StartActivity("orders.create");e algumas tags:
activity?.SetTag("order.id", order.Id);
activity?.SetTag("order.item_count", order.Items.Count);
activity?.SetTag("order.gross_total", order.GrossTotal);
activity?.SetTag("order.discount_rate", order.DiscountRate);
activity?.SetTag("order.net_total", order.NetTotal);Para fechar, também foram adicionados alguns pacotes NuGet:
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.11.2" />
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.11.1" />
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.11.1" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.11.2" />Vale lembrar que não adicionamos como o OpenTelemetry deveria ser aplicado à Order.Api, apenas pedimos para o GitHub Copilot seguir o padrão definido no documento interno, que é justamente o que torna essa abordagem tão poderosa. Customizações das implementações em projetos .NET ficam para outro dia!
Para ver os traces sendo enviados para o Aspire Dashboard, utilize o docker-compose.yaml na raiz do projeto.
Rode o Dashboard Aspire localmente:
docker-compose up -dPare e execute novamente a Order.Api para garantir que as mudanças de código sejam aplicadas e os traces com as novas tags de desconto apareçam no dashboard.
dotnet runVamos repetir a chamada HTTP feita logo no começo da primeira parte, direto pelo arquivo Order.Api.http, para criar um novo pedido com dois laptops e ver o resultado final com o desconto aplicado:
{
"id": 1,
"createdAt": "2026-04-23T02:39:39.157939Z",
"items": [
{
"productId": 1,
"productName": "Laptop",
"quantity": 2,
"unitPrice": 1299.99
}
],
"grossTotal": 2599.98,
"discountRate": 0.15,
"discountAmount": 389.9970,
"netTotal": 2209.9830
}Temos que o desconto de 15% aplicado sobre o total bruto de $2599.98, resultando num valor de desconto de $389.9970 e um total líquido de $2209.9830.

O ponto interessante desse fluxo é que cada peça resolve um problema bem específico. O WorkIQ resolve o problema do conhecimento informal espalhado pelo M365. O Foundry IQ resolve o problema do conhecimento corporativo estruturado e da combinação de fontes internas com fontes oficiais. O MCP serve de cola entre tudo isso e o GitHub Copilot, que passa a ter contexto suficiente para gerar código que respeita tanto a comunicação informal do dia a dia quanto as diretrizes formais da empresa. É essa composição que transforma o Copilot de um autocomplete avançado em um agente de fato útil dentro de um ambiente corporativo.
Você já pode baixar o projeto por esse link, e não esquece de me seguir no LinkedIn!
Até a próxima, abraços!