@polymorphism-tech/morph-spec 7.0.0 → 7.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/README.md +367 -367
  2. package/framework/agents.json +132 -127
  3. package/framework/rubrics/backend.json +41 -36
  4. package/framework/rules/ai-agents-standards.md +45 -15
  5. package/framework/rules/neon-standards.md +25 -26
  6. package/framework/standards/STANDARDS.json +1729 -1133
  7. package/framework/standards/ai-agents/agent-session.md +160 -0
  8. package/framework/standards/ai-agents/batching.md +193 -0
  9. package/framework/standards/ai-agents/context-providers.md +188 -0
  10. package/framework/standards/ai-agents/mcp-server.md +250 -0
  11. package/framework/standards/ai-agents/mcp-tools.md +197 -0
  12. package/framework/standards/ai-agents/middleware-patterns.md +207 -0
  13. package/framework/standards/ai-agents/modalities-image-gen.md +173 -0
  14. package/framework/standards/ai-agents/modalities-speech-to-text.md +171 -0
  15. package/framework/standards/ai-agents/modalities-text-to-speech.md +181 -0
  16. package/framework/standards/ai-agents/modalities-vision.md +201 -0
  17. package/framework/standards/ai-agents/multi-agent-patterns.md +155 -0
  18. package/framework/standards/ai-agents/production.md +114 -258
  19. package/framework/standards/ai-agents/prompts-in-db.md +214 -0
  20. package/framework/standards/ai-agents/providers/model-registry.md +158 -0
  21. package/framework/standards/ai-agents/rag-custom-pgvector.md +243 -0
  22. package/framework/standards/ai-agents/service-tier-flex.md +135 -0
  23. package/framework/standards/ai-agents/setup.md +142 -316
  24. package/framework/standards/ai-agents/structured-output.md +249 -0
  25. package/framework/standards/ai-agents/sweet-spot.md +109 -0
  26. package/framework/standards/ai-agents/workflows.md +114 -284
  27. package/framework/standards/architecture/vertical-slice/vertical-slice.md +433 -429
  28. package/framework/standards/backend/api/validation.md +92 -88
  29. package/framework/standards/backend/database/postgresql/database.md +336 -332
  30. package/framework/standards/backend/database/vector-search-rag.md +599 -541
  31. package/framework/standards/backend/dotnet/fluent-validation-vsa.md +4 -0
  32. package/framework/standards/backend/dotnet/result-pattern.md +4 -0
  33. package/framework/standards/backend/dotnet/scalar-ui.md +176 -172
  34. package/framework/standards/backend/dotnet/scrutor.md +4 -0
  35. package/framework/standards/backend/dotnet/vsa-handler-patterns.md +278 -274
  36. package/framework/standards/backend/integrations/asaas/asaas-api.md +260 -216
  37. package/framework/standards/backend/integrations/hangfire/hangfire-jobs.md +354 -350
  38. package/framework/standards/backend/integrations/neon-auth/neon-auth.md +30 -19
  39. package/framework/standards/backend/integrations/resend/resend-email.md +400 -385
  40. package/framework/standards/data/nosql/cache/redis.md +106 -97
  41. package/framework/standards/data/nosql/cosmos-db.md +127 -118
  42. package/framework/standards/data/vector-search/rag-chunking.md +113 -104
  43. package/framework/standards/frontend/design-system/ai-image-generation.md +4 -0
  44. package/framework/standards/frontend/design-system/animations.md +164 -160
  45. package/framework/standards/frontend/design-system/naming.md +68 -64
  46. package/framework/standards/frontend/gsap/gsap-core.md +14 -8
  47. package/framework/standards/frontend/gsap/gsap-plugins.md +26 -21
  48. package/framework/standards/frontend/gsap/gsap-scrolltrigger.md +9 -7
  49. package/framework/standards/frontend/nextjs/app-router.md +127 -123
  50. package/framework/standards/frontend/nextjs/components.md +136 -132
  51. package/framework/standards/frontend/nextjs/data-fetching.md +130 -126
  52. package/framework/standards/frontend/nextjs/forms.md +134 -128
  53. package/framework/standards/frontend/nextjs/motion-patterns.md +328 -322
  54. package/framework/standards/frontend/nextjs/naming-conventions.md +71 -67
  55. package/framework/standards/frontend/nextjs/nextjs-patterns.md +229 -225
  56. package/framework/standards/frontend/nextjs/project-structure.md +106 -102
  57. package/framework/standards/frontend/nextjs/state-management.md +76 -72
  58. package/framework/standards/frontend/nextjs/testing.md +115 -111
  59. package/framework/standards/infrastructure/docker/easypanel-deploy.md +196 -192
  60. package/framework/standards/infrastructure/neon/neon-pgrag.md +6 -2
  61. package/framework/standards/infrastructure/neon/neon-pgvector.md +4 -0
  62. package/framework/standards/infrastructure/neon/neon-setup.md +5 -1
  63. package/framework/standards/integration/mcp/mcp-tools.md +471 -462
  64. package/framework/templates/REGISTRY.json +1745 -1239
  65. package/framework/templates/code/dotnet/ai-agents/AgentBootstrap.cs.template +39 -0
  66. package/framework/templates/code/dotnet/ai-agents/EmbeddingService.cs.template +68 -0
  67. package/framework/templates/code/dotnet/ai-agents/ModelRegistry.cs.template +117 -0
  68. package/framework/templates/code/dotnet/ai-agents/PromptRepository.cs.template +95 -0
  69. package/framework/templates/code/dotnet/ai-agents/ResilientToolMiddleware.cs.template +61 -0
  70. package/framework/templates/code/dotnet/ai-agents/VectorSearchTool.cs.template +107 -0
  71. package/framework/templates/code/dotnet/ai-agents/model-registry.json.template +58 -0
  72. package/framework/templates/code/sql/ai-agents/agent-prompts-migration.sql.template +58 -0
  73. package/framework/templates/code/sql/ai-agents/pgvector-embeddings-migration.sql.template +91 -0
  74. package/package.json +89 -83
  75. package/src/core/templates/template-registry.js +289 -256
  76. package/src/lib/generators/context-generator.js +516 -518
  77. package/src/lib/generators/scaffold-generator.js +23 -26
  78. package/framework/standards/infrastructure/neon/neon-semantic-kernel.md +0 -310
  79. package/framework/templates/integrations/semantic-kernel-neon.cs +0 -125
@@ -0,0 +1,250 @@
1
+ # MCP Server — Exposing project capabilities as MCP
2
+
3
+ > **Scope:** stacks=["dotnet"]
4
+ > **Layer:** 2 (on-keyword)
5
+ > **Keywords:** mcp server, expose mcp, build mcp server, mcp host, publish tools, McpServerTool, AddMcpServer
6
+ > **Read by Claude in:** implement (quando um projeto expõe capacidades como MCP server para consumo externo)
7
+
8
+ **Verified against:** MAF 1.0 GA + ModelContextProtocol C# SDK server-side (`[McpServerToolType]`/`[McpServerTool]`, `WithStdioServerTransport`/`WithHttpTransport`, `ModelContextProtocol.AspNetCore`). Last-verified: 2026-05-19.
9
+
10
+ ---
11
+
12
+ ## Quando criar um MCP server
13
+
14
+ Crie um MCP server quando você quer **expor capacidades do projeto** de forma padronizada para **agentes ou ferramentas externas** consumirem. O protocolo MCP garante interoperabilidade: qualquer agente compatível (Claude Code, outro agent MAF, VS Code Copilot, etc.) pode descobrir e usar as tools do servidor.
15
+
16
+ **Crie um MCP server quando:**
17
+
18
+ - Claude Code (ou outro agente externo) precisa executar queries/ações neste projeto
19
+ - Outro projeto MAF precisa consumir lógica encapsulada aqui via protocolo padrão
20
+ - Você quer expor capacidades de negócio de forma descobrível via `ListTools`
21
+ - O projeto é um serviço de infraestrutura que deve ser consumível cross-framework
22
+
23
+ **NÃO crie um MCP server quando:**
24
+
25
+ - As tools são usadas apenas pelos agentes do próprio projeto → use tools nativas `[Description]`
26
+ - É uma lógica de um único endpoint interno → `AIFunctionFactory.Create()` é suficiente
27
+ - A integração é ponto-a-ponto com um único consumidor → DI direta ou A2A é mais simples
28
+
29
+ ---
30
+
31
+ ## A API
32
+
33
+ **Packages:**
34
+
35
+ ```xml
36
+ <!-- SDK cliente+servidor do protocolo MCP -->
37
+ <PackageReference Include="ModelContextProtocol" Version="0.1.*" />
38
+
39
+ <!-- Pacote base MAF (para AsAIAgent, AsAIFunction) -->
40
+ <PackageReference Include="Microsoft.Agents.AI" Version="1.0.*" />
41
+
42
+ <!-- Apenas se o transporte for HTTP (server-side) -->
43
+ <PackageReference Include="ModelContextProtocol.AspNetCore" Version="0.1.*" />
44
+ ```
45
+
46
+ > `ModelContextProtocol` versão 0.1.* (preview) — GA previsto. Verifique versão corrente no NuGet antes de travar.
47
+
48
+ **Dois jeitos de declarar tools:**
49
+
50
+ | Padrão | Quando usar |
51
+ |--------|-------------|
52
+ | Atributos `[McpServerToolType]` + `[McpServerTool]` | Tools são métodos estáticos do projeto — padrão canônico do SDK. Descoberta automática via `WithToolsFromAssembly()`. |
53
+ | `McpServerTool.Create(...)` | Embrulhar um agente MAF (`agent.AsAIFunction()`) ou um `AIFunction` construído em runtime como tool. |
54
+
55
+ **Padrão de servidor (stdio):**
56
+
57
+ ```
58
+ Tools ([McpServerTool] ou McpServerTool.Create)
59
+ → services.AddMcpServer() // registra o servidor
60
+ → .WithStdioServerTransport() // transporte: stdio (Claude Code, subprocesso)
61
+ → .WithToolsFromAssembly() // descobre [McpServerTool] no assembly
62
+ ou .WithTools([tool]) // registra tools criadas em runtime
63
+ → Host.Build().RunAsync() // sobe o servidor
64
+ ```
65
+
66
+ **Transport stdio** é o padrão para servidores locais (subprocessos, Claude Code tools). **HTTP server-side** usa o pacote `ModelContextProtocol.AspNetCore`: troque `WithStdioServerTransport()` por `WithHttpTransport(o => o.Stateless = true)` e exponha o endpoint com `app.MapMcp()` num `WebApplication`.
67
+
68
+ ---
69
+
70
+ ## Exemplo C# — tools por atributo (stdio)
71
+
72
+ Padrão canônico: tools são métodos estáticos decorados, descobertos por assembly.
73
+
74
+ ```csharp
75
+ using System.ComponentModel;
76
+ using Microsoft.Extensions.DependencyInjection;
77
+ using Microsoft.Extensions.Hosting;
78
+ using ModelContextProtocol.Server;
79
+
80
+ HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
81
+
82
+ builder.Services
83
+ .AddMcpServer()
84
+ .WithStdioServerTransport() // stdio: Claude Code / subprocesso
85
+ .WithToolsFromAssembly(); // descobre [McpServerToolType] neste assembly
86
+
87
+ await builder.Build().RunAsync();
88
+
89
+ // ─── Tool exposta via atributo ──────────────────────────────────────────────
90
+ [McpServerToolType]
91
+ public static class OrderTools
92
+ {
93
+ [McpServerTool, Description("Returns the current status of an order given its ID")]
94
+ public static async Task<string> GetOrderStatus(
95
+ [Description("The order identifier")] string orderId,
96
+ CancellationToken ct)
97
+ {
98
+ // Validação de input antes de qualquer I/O — ver seção Segurança
99
+ if (string.IsNullOrWhiteSpace(orderId) || orderId.Length > 36)
100
+ return "Error: invalid orderId format.";
101
+
102
+ // lógica real do projeto aqui
103
+ return $"Order {orderId}: Confirmed";
104
+ }
105
+ }
106
+ ```
107
+
108
+ > Tools por atributo podem ser instância (não-estáticas) — nesse caso o tipo é resolvido via DI,
109
+ > permitindo injetar repositories/serviços no construtor. Métodos estáticos são o caso mais simples.
110
+
111
+ ## Exemplo C# — agente MAF como tool (runtime)
112
+
113
+ Quando a tool é um agente MAF, use `McpServerTool.Create` com `agent.AsAIFunction()`:
114
+
115
+ ```csharp
116
+ using Microsoft.Agents.AI;
117
+ using Microsoft.Extensions.AI;
118
+ using Microsoft.Extensions.DependencyInjection;
119
+ using Microsoft.Extensions.Hosting;
120
+ using ModelContextProtocol.Server;
121
+
122
+ // _modelRegistry vem de ModelRegistry injetado (ai-agents-providers-model-registry)
123
+ AIAgent queryAgent = _modelRegistry.GetChatClient("text-default")
124
+ .AsAIAgent(
125
+ instructions: "You answer questions about project data using the available tools.",
126
+ name: "ProjectQueryAgent");
127
+
128
+ McpServerTool agentTool = McpServerTool.Create(queryAgent.AsAIFunction());
129
+
130
+ HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
131
+
132
+ builder.Services
133
+ .AddMcpServer()
134
+ .WithStdioServerTransport()
135
+ .WithTools([agentTool]); // tool criada em runtime → WithTools, não WithToolsFromAssembly
136
+
137
+ await builder.Build().RunAsync();
138
+ ```
139
+
140
+ ## Exemplo C# — transport HTTP
141
+
142
+ Para expor o MCP server via HTTP (consumível pela rede), use `ModelContextProtocol.AspNetCore`:
143
+
144
+ ```csharp
145
+ using ModelContextProtocol.Server;
146
+
147
+ var builder = WebApplication.CreateBuilder(args);
148
+
149
+ builder.Services
150
+ .AddMcpServer()
151
+ .WithHttpTransport(options => options.Stateless = true) // stateless: escalável
152
+ .WithToolsFromAssembly();
153
+
154
+ var app = builder.Build();
155
+
156
+ app.MapMcp(); // expõe o endpoint MCP (Streamable HTTP)
157
+ app.Run();
158
+ ```
159
+
160
+ > `Stateless = true` é recomendado para servidores que não precisam de requests server-to-client
161
+ > (sampling/elicitation) — escala melhor e dispensa gestão de sessão. Para HTTP em produção,
162
+ > proteja `MapMcp()` com autenticação — ver seção Segurança.
163
+
164
+ **Registrar no Claude Code (`settings.json` do projeto consumidor):**
165
+
166
+ ```json
167
+ {
168
+ "mcpServers": {
169
+ "my-project-tools": {
170
+ "command": "dotnet",
171
+ "args": ["run", "--project", "src/MyProject.McpServer"]
172
+ }
173
+ }
174
+ }
175
+ ```
176
+
177
+ ---
178
+
179
+ ## Tool nativa vs MCP server exposto
180
+
181
+ | Dimensão | Tool nativa (`[Description]`) | MCP server exposto |
182
+ |----------|------------------------------|--------------------|
183
+ | **Consumidor** | Agentes do próprio projeto | Agentes externos (Claude Code, outros projetos) |
184
+ | **Protocolo** | Chamada direta em processo | Protocolo MCP (stdio ou HTTP) |
185
+ | **Descoberta** | Registro manual no agente | `ListTools` — autodescoberta |
186
+ | **Quando usar** | Lógica interna do projeto | Capacidades que cruzam fronteiras de projeto/processo |
187
+ | **Overhead** | Zero | Processo separado, IPC via stdio/HTTP |
188
+ | **Testabilidade** | Unitária com xUnit/nSubstitute | Requer host em execução ou mock do transport |
189
+
190
+ **Regra**: se o único consumidor é um agente do próprio projeto → tool nativa. Se o consumidor é externo → MCP server.
191
+
192
+ ---
193
+
194
+ ## Segurança
195
+
196
+ Um MCP server exposto é uma **superfície de ataque real** — qualquer agente com acesso ao transporte pode invocar suas tools.
197
+
198
+ **Obrigatório antes de expor:**
199
+
200
+ - **Validação de input:** valide todos os parâmetros recebidos (IDs, datas, strings livres) antes de executar lógica de negócio. MCP não valida tipos por você.
201
+ - **Escopo mínimo:** exponha apenas as tools necessárias. Não exponha operações de escrita se o consumidor só lê.
202
+ - **Autenticação (HTTP transport):** se usar HTTP, exija bearer token ou API key via header. stdio é implicitamente restrito ao processo pai.
203
+ - **Nunca expor secrets via tools:** tools não devem retornar API keys, tokens, ou dados de configuração interna.
204
+ - **Rate limiting:** se o MCP server for exposto via HTTP em produção, adicione rate limiting na camada de transporte (reverse proxy / middleware ASP.NET).
205
+
206
+ A validação de input é mostrada inline no exemplo `GetOrderStatus` acima — valide IDs/datas/strings
207
+ **antes de qualquer I/O** e devolva uma mensagem de erro descritiva (o consumidor é um LLM, que
208
+ corrige o argumento na próxima iteração). Para tools por atributo que precisam de repositories,
209
+ prefira o tipo de instância resolvido por DI a métodos estáticos com estado global.
210
+
211
+ ---
212
+
213
+ ## Anti-patterns
214
+
215
+ | Anti-pattern | Problema | Solução |
216
+ |-------------|---------|---------|
217
+ | Criar MCP server para tools que só os agentes do próprio projeto usam | Overhead de processo + IPC sem benefício; mais difícil de testar | Tool nativa `[Description]` + `AIFunctionFactory.Create()` |
218
+ | Expor tools sem validação de input | Agentes externos (ou alucinações de LLM) podem enviar dados malformados | Validar todos os parâmetros antes de qualquer I/O |
219
+ | Tools sem escopo definido — expor tudo "por conveniência" | Superfície de ataque ampla; dificulta manutenção | Expor apenas o necessário; revisar a lista a cada release |
220
+ | HTTP transport sem autenticação | Qualquer processo na rede pode invocar as tools | Bearer token / API key obrigatório em HTTP; stdio é suficiente para uso local |
221
+ | Subir o MCP server no mesmo processo da aplicação principal | Acoplamento; falha no servidor MCP afeta a app | Projeto separado (`MyProject.McpServer`) com entrypoint próprio |
222
+
223
+ ---
224
+
225
+ ## Checklist (verifiable by morph-eval)
226
+
227
+ - [ ] `ModelContextProtocol` e `Microsoft.Agents.AI` no `.csproj`; `ModelContextProtocol.AspNetCore` apenas se HTTP
228
+ - [ ] Tools declaradas via `[McpServerToolType]` + `[McpServerTool]` (estáticas) ou `McpServerTool.Create(...)` (agente MAF / runtime)
229
+ - [ ] `AddMcpServer()` com `WithStdioServerTransport()` (stdio) ou `WithHttpTransport(o => o.Stateless = true)` (HTTP)
230
+ - [ ] `WithToolsFromAssembly()` para tools por atributo, ou `WithTools([...])` para tools de runtime
231
+ - [ ] Entrypoint: `await builder.Build().RunAsync()` (stdio) ou `app.MapMcp(); app.Run()` (HTTP)
232
+ - [ ] Agente criado via `_modelRegistry.GetChatClient("alias").AsAIAgent(...)` — provider-agnostic
233
+ - [ ] Validação de input em todas as tools antes de executar lógica de negócio
234
+ - [ ] Escopo mínimo: apenas tools necessárias para o consumidor externo
235
+ - [ ] HTTP transport (se usado) protegido por autenticação
236
+ - [ ] Nenhum `Guid.NewGuid()` — usar `Guid.CreateVersion7()` se IDs são necessários
237
+
238
+ ---
239
+
240
+ ## References
241
+
242
+ - `ai-agents-setup` — pacotes base MAF, DI, quick start
243
+ - `ai-agents-mcp-tools` — consumir MCP servers externos em um agente MAF
244
+ - `ai-agents-production` — middleware pipeline, `ResilientToolMiddleware`, segurança
245
+ - [ModelContextProtocol C# SDK](https://github.com/modelcontextprotocol/csharp-sdk)
246
+ - [Expose Agent as MCP Server — MAF docs](https://learn.microsoft.com/en-us/agent-framework/agents/tools/local-mcp-tools)
247
+
248
+ ---
249
+
250
+ *MORPH-SPEC by Polymorphism Tech — ai-agents/mcp-server.md v1.0 (2026-05-19)*
@@ -0,0 +1,197 @@
1
+ # MCP Tools — Agent consuming MCP servers
2
+
3
+ > **Scope:** stacks=["dotnet"]
4
+ > **Layer:** 2 (on-keyword)
5
+ > **Keywords:** mcp, model context protocol, mcp client, consume mcp server, mcp tools, external tools
6
+ > **Read by Claude in:** implement (quando um agente usa tools de um MCP server externo)
7
+
8
+ **Verified against:** MAF 1.0 GA + ModelContextProtocol C# SDK. Last-verified: 2026-05-19.
9
+
10
+ ---
11
+
12
+ ## O que é (no contexto de um agente MAF)
13
+
14
+ MCP (Model Context Protocol) é um protocolo aberto que padroniza como um agente expõe e consome tools. Um agente MAF pode se conectar a qualquer **MCP server externo** (GitHub, filesystem, Postgres, ferramentas internas de outro projeto) e usar as tools desse servidor exatamente como se fossem tools nativas — o MAF trata tudo como `AITool`.
15
+
16
+ Casos de uso típicos:
17
+
18
+ - Agente de repositório que usa o GitHub MCP server para listar PRs e issues
19
+ - Agente de dados que usa o PostgreSQL MCP server para rodar queries
20
+ - Agente de documentação que usa o filesystem MCP server para ler arquivos locais
21
+ - Agente corporativo que consome um MCP server interno de outro projeto
22
+
23
+ ---
24
+
25
+ ## A API
26
+
27
+ **Package:**
28
+
29
+ ```xml
30
+ <PackageReference Include="ModelContextProtocol" Version="0.1.*" />
31
+ ```
32
+
33
+ > O pacote `ModelContextProtocol` é o SDK C# oficial do protocolo. Versão 0.1.* (preview) — GA previsto; verifique no NuGet antes de travar versão.
34
+
35
+ **Fluxo de consumo:**
36
+
37
+ 1. `McpClientFactory.CreateAsync(transport)` — conecta ao MCP server e retorna um client `IDisposable`
38
+ 2. `mcpClient.ListToolsAsync()` — descobre as tools disponíveis no servidor
39
+ 3. `mcpTools.Cast<AITool>()` — converte para `AITool` (interface do MAF / `Microsoft.Extensions.AI`)
40
+ 4. Passa as tools ao agente via `AsAIAgent(..., tools: [.. mcpTools.Cast<AITool>()])`
41
+
42
+ **Transports suportados:**
43
+
44
+ | Transport | Classe | Quando usar |
45
+ |-----------|--------|------------|
46
+ | stdio | `StdioClientTransport` | MCP server local (processo filho — `npx`, executável) |
47
+ | HTTP (SSE) | `SseClientTransport` | MCP server remoto via HTTP/SSE |
48
+
49
+ **Regra importante:** use `await using` no `mcpClient` — o dispose fecha o transporte corretamente.
50
+
51
+ ---
52
+
53
+ ## Exemplo C#
54
+
55
+ Agente que consome o GitHub MCP server e responde perguntas sobre repositórios:
56
+
57
+ ```csharp
58
+ using ModelContextProtocol;
59
+ using ModelContextProtocol.Client;
60
+ using Microsoft.Agents.AI;
61
+ using Microsoft.Extensions.AI;
62
+
63
+ // 1. Conectar ao MCP server (GitHub via npx)
64
+ await using var mcpClient = await McpClientFactory.CreateAsync(
65
+ new StdioClientTransport(new()
66
+ {
67
+ Name = "GitHubServer",
68
+ Command = "npx",
69
+ Arguments = ["-y", "--verbose", "@modelcontextprotocol/server-github"],
70
+ }));
71
+
72
+ // 2. Descobrir as tools do servidor
73
+ var mcpTools = await mcpClient.ListToolsAsync();
74
+
75
+ // 3. Criar o agente MAF com as MCP tools registradas
76
+ // _modelRegistry vem de IModelRegistry (ai-agents-providers-model-registry)
77
+ AIAgent agent = _modelRegistry.GetChatClient("text-default")
78
+ .AsAIAgent(
79
+ instructions: "You answer questions about GitHub repositories.",
80
+ tools: [.. mcpTools.Cast<AITool>()]);
81
+
82
+ // 4. Usar o agente normalmente
83
+ var response = await agent.RunAsync(
84
+ "Summarize the last 4 commits to microsoft/agent-framework");
85
+ Console.WriteLine(response.Text);
86
+ ```
87
+
88
+ **Variante com MCP server remoto (HTTP/SSE):**
89
+
90
+ ```csharp
91
+ using ModelContextProtocol.Client;
92
+
93
+ await using var mcpClient = await McpClientFactory.CreateAsync(
94
+ new SseClientTransport(new()
95
+ {
96
+ Name = "RemoteToolsServer",
97
+ Uri = new Uri("https://tools.example.com/mcp"),
98
+ }));
99
+
100
+ var mcpTools = await mcpClient.ListToolsAsync();
101
+ // Mesmo padrão para criar o agente...
102
+ ```
103
+
104
+ **Múltiplos MCP servers:**
105
+
106
+ ```csharp
107
+ // Conectar a dois servidores e combinar as tools
108
+ await using var githubClient = await McpClientFactory.CreateAsync(
109
+ new StdioClientTransport(new()
110
+ {
111
+ Name = "GitHub",
112
+ Command = "npx",
113
+ Arguments = ["-y", "@modelcontextprotocol/server-github"],
114
+ }));
115
+
116
+ await using var fsClient = await McpClientFactory.CreateAsync(
117
+ new StdioClientTransport(new()
118
+ {
119
+ Name = "Filesystem",
120
+ Command = "npx",
121
+ Arguments = ["-y", "@modelcontextprotocol/server-filesystem", "/repo"],
122
+ }));
123
+
124
+ var allTools = (await githubClient.ListToolsAsync())
125
+ .Concat(await fsClient.ListToolsAsync())
126
+ .Cast<AITool>()
127
+ .ToArray();
128
+
129
+ AIAgent agent = _modelRegistry.GetChatClient("text-default")
130
+ .AsAIAgent(
131
+ instructions: "You help navigate the codebase.",
132
+ tools: allTools);
133
+ ```
134
+
135
+ ---
136
+
137
+ ## Popular MCP Servers
138
+
139
+ | Server npm | Propósito |
140
+ |-----------|-----------|
141
+ | `@modelcontextprotocol/server-github` | Repos, issues, PRs do GitHub |
142
+ | `@modelcontextprotocol/server-filesystem` | Operações no sistema de arquivos |
143
+ | `@modelcontextprotocol/server-sqlite` | Acesso a banco de dados SQLite |
144
+ | `@modelcontextprotocol/server-postgres` | Acesso a banco de dados PostgreSQL |
145
+
146
+ ---
147
+
148
+ ## MCP server vs tool nativa
149
+
150
+ | Cenário | Decisão | Motivo |
151
+ |---------|---------|--------|
152
+ | O servidor existe e é mantido por terceiros (GitHub, Postgres...) | Consumir MCP server | Reutilização imediata; sem esforço de manutenção |
153
+ | A lógica é específica deste projeto (ex.: buscar pedido no banco) | Escrever tool nativa `[Description]` | Mais simples, sem overhead de protocolo, testável diretamente |
154
+ | O servidor interno já existe e está publicado como MCP | Consumir MCP server | Evita duplicar lógica |
155
+ | Precisa de controle total de retries, logging, validação | Tool nativa + middleware MAF | Middleware `ResilientToolMiddleware` dá controle fino |
156
+
157
+ **Regra**: prefira MCP server quando o servidor já existe e é mantido por terceiros. Prefira tool nativa quando a lógica é do próprio projeto.
158
+
159
+ ---
160
+
161
+ ## Anti-patterns
162
+
163
+ | Anti-pattern | Problema | Solução |
164
+ |-------------|---------|---------|
165
+ | Criar um MCP server próprio só para o agente do mesmo projeto chamar | Overhead desnecessário de protocolo e deploy | Escrever tool nativa com `[Description]` + `AIFunctionFactory.Create()` |
166
+ | Não tratar indisponibilidade do MCP server | O MCP server pode estar offline ou lento; o agente quebra | Usar `ResilientToolMiddleware` para capturar `HttpRequestException` e `TimeoutException` (ver `ai-agents-production`) |
167
+ | Não usar `await using` no `mcpClient` | Processo filho (npx) fica zumbi; leak de recursos | Sempre `await using var mcpClient = await McpClientFactory.CreateAsync(...)` |
168
+ | Hardcodar o model diretamente (`new ChatClient("gpt-4o", key)`) | Viola provider-agnostic; difícil de trocar | Sempre `_modelRegistry.GetChatClient("alias")` (ver `ai-agents-providers-model-registry`) |
169
+
170
+
171
+ ---
172
+
173
+ ## Checklist (verifiable by morph-eval)
174
+
175
+ - [ ] `ModelContextProtocol` presente no `.csproj`
176
+ - [ ] `await using var mcpClient` — dispose correto do transporte
177
+ - [ ] `McpClientFactory.CreateAsync(new StdioClientTransport(...))` com `Name`, `Command`, `Arguments`
178
+ - [ ] `ListToolsAsync()` chamado antes de criar o agente
179
+ - [ ] `mcpTools.Cast<AITool>()` para registrar no agente
180
+ - [ ] Agente criado via `_modelRegistry.GetChatClient("alias").AsAIAgent(...)` — provider-agnostic
181
+ - [ ] `ResilientToolMiddleware` ou equivalente registrado (o MCP server pode ficar indisponível)
182
+ - [ ] Nenhum `Guid.NewGuid()` — usar `Guid.CreateVersion7()` se IDs são necessários
183
+
184
+ ---
185
+
186
+ ## References
187
+
188
+ - `ai-agents-setup` — pacotes base MAF, DI, quick start
189
+ - `ai-agents-production` — panorama de MCP Integration, `ResilientToolMiddleware`, middleware pipeline
190
+ - `ai-agents-mcp-server` — expor capacidades do próprio projeto como MCP server
191
+ - `integration/mcp/mcp-tools.md` — MCPs no workflow do morph-spec (Playwright, Context7, Neon) — contexto complementar, escopo diferente
192
+ - [ModelContextProtocol C# SDK](https://github.com/modelcontextprotocol/csharp-sdk)
193
+ - [Using MCP Tools — MAF docs](https://learn.microsoft.com/agent-framework/user-guide/model-context-protocol/using-mcp-tools)
194
+
195
+ ---
196
+
197
+ *MORPH-SPEC by Polymorphism Tech — ai-agents/mcp-tools.md v1.0 (2026-05-19)*
@@ -0,0 +1,207 @@
1
+ # Middleware Patterns — Intercepting agent behavior
2
+
3
+ > **Scope:** stacks=["dotnet"]
4
+ > **Layer:** 1 (on-keyword)
5
+ > **Keywords:** middleware, agent middleware, function calling middleware, retry, anti-hallucination, resilience, telemetry middleware, agent run middleware
6
+ > **Read by Claude in:** implement (when wiring cross-cutting behavior into an agent)
7
+
8
+ **Verified against:** MAF 1.0 GA. Last-verified: 2026-05-19.
9
+
10
+ ---
11
+
12
+ ## Os 3 níveis de middleware
13
+
14
+ | Tipo | Intercepts | Use Case |
15
+ |------|-----------|----------|
16
+ | **Agent Run** | Invocação completa do agent | Logging, auth, rate limiting |
17
+ | **Function Calling** | Chamadas individuais de tools | Error handling, validação, auditoria |
18
+ | **IChatClient** | Chamadas brutas ao LLM | Telemetry, caching, content filtering |
19
+
20
+ Cada nível tem uma assinatura de delegate distinta. Os três podem coexistir no mesmo agent — cada um atua na sua camada sem interferir nos outros.
21
+
22
+ ---
23
+
24
+ ## Registrando middleware
25
+
26
+ O registration point é sempre `.AsBuilder().Use(...).Build()`. Encadeie múltiplos middlewares antes de chamar `Build()`.
27
+
28
+ ```csharp
29
+ using Microsoft.Agents.AI;
30
+ using Microsoft.Extensions.AI;
31
+
32
+ // Obter o IChatClient via Model Registry — provider-agnostic
33
+ IChatClient chatClient = _modelRegistry.GetChatClient("text-default");
34
+
35
+ // IChatClient-level middleware (antes de criar o agent)
36
+ IChatClient instrumentedClient = chatClient
37
+ .AsBuilder()
38
+ .Use(getResponseFunc: TelemetryMiddleware, getStreamingResponseFunc: null)
39
+ .Build();
40
+
41
+ // Agent Run + Function Calling middleware
42
+ AIAgent agent = instrumentedClient
43
+ .AsAIAgent(instructions: "You are a helpful assistant.")
44
+ .AsBuilder()
45
+ .Use(runFunc: LoggingMiddleware, runStreamingFunc: null)
46
+ .Use(ResilientToolMiddleware) // function calling middleware
47
+ .Build();
48
+ ```
49
+
50
+ > Se apenas middleware não-streaming for fornecido, chamadas streaming rodam em modo não-streaming.
51
+
52
+ ---
53
+
54
+ ## Pattern: telemetria
55
+
56
+ IChatClient-level middleware que mede latência e token usage de cada chamada bruta ao LLM.
57
+
58
+ ```csharp
59
+ using System.Diagnostics;
60
+ using Microsoft.Agents.AI;
61
+ using Microsoft.Extensions.AI;
62
+
63
+ async Task<ChatResponse> TelemetryMiddleware(
64
+ IEnumerable<ChatMessage> messages,
65
+ ChatOptions? options,
66
+ IChatClient innerChatClient,
67
+ CancellationToken cancellationToken)
68
+ {
69
+ var sw = Stopwatch.StartNew();
70
+ var response = await innerChatClient.GetResponseAsync(messages, options, cancellationToken);
71
+ sw.Stop();
72
+ Console.WriteLine($"LLM call: {sw.ElapsedMilliseconds}ms, Tokens: {response.Usage?.TotalTokenCount}");
73
+ return response;
74
+ }
75
+ ```
76
+
77
+ Para o setup completo de OpenTelemetry (traces, métricas, Aspire Dashboard, `AgentOpenTelemetryConsts.DefaultSourceName`), consulte `ai-agents-production` §Observability. Não duplique aqui — este middleware é um ponto de entrada, não a solução completa.
78
+
79
+ ---
80
+
81
+ ## Pattern: resilência (retry + rate-limit)
82
+
83
+ Function Calling middleware que intercepta erros de tools antes que o agent receba exceções brutas. Devolver uma string descritiva ao agent permite que ele se autocorrija ou informe o usuário adequadamente.
84
+
85
+ ```csharp
86
+ using Microsoft.Agents.AI;
87
+ using Microsoft.Extensions.AI;
88
+
89
+ async ValueTask<object?> ResilientToolMiddleware(
90
+ AIAgent agent,
91
+ FunctionInvocationContext context,
92
+ Func<FunctionInvocationContext, CancellationToken, ValueTask<object?>> next,
93
+ CancellationToken cancellationToken)
94
+ {
95
+ try
96
+ {
97
+ return await next(context, cancellationToken);
98
+ }
99
+ catch (HttpRequestException ex) when (ex.StatusCode == System.Net.HttpStatusCode.TooManyRequests)
100
+ {
101
+ return "Service is temporarily busy. Please try again in a moment.";
102
+ }
103
+ catch (TimeoutException)
104
+ {
105
+ return $"Tool '{context.Function.Name}' timed out. Try a simpler query.";
106
+ }
107
+ catch (Exception ex)
108
+ {
109
+ return $"Error in {context.Function.Name}: {ex.Message}";
110
+ }
111
+ }
112
+ ```
113
+
114
+ Template completo com registro e stub de anti-hallucination: `templates/code/dotnet/ai-agents/ResilientToolMiddleware.cs.template`.
115
+
116
+ ---
117
+
118
+ ## Pattern: anti-hallucination
119
+
120
+ O LLM gera os argumentos de tool call baseado em linguagem natural — pode gerar formatos inválidos (datas malformadas, IDs fora de range, strings vazias). Validar **antes** de chamar `next()` permite que o agent se autocorrija na próxima iteração, sem precisar de retry externo.
121
+
122
+ ```csharp
123
+ using Microsoft.Agents.AI;
124
+ using Microsoft.Extensions.AI;
125
+
126
+ async ValueTask<object?> AntiHallucinationMiddleware(
127
+ AIAgent agent,
128
+ FunctionInvocationContext context,
129
+ Func<FunctionInvocationContext, CancellationToken, ValueTask<object?>> next,
130
+ CancellationToken cancellationToken)
131
+ {
132
+ // Validate 'date' argument before calling the tool
133
+ if (context.Arguments.TryGetValue("date", out var dateArg)
134
+ && !DateTime.TryParse(dateArg?.ToString(), out _))
135
+ {
136
+ return $"Invalid date format for '{context.Function.Name}'. Expected ISO 8601 (e.g. 2026-01-31).";
137
+ }
138
+
139
+ // Validate 'orderId' is a positive integer
140
+ if (context.Arguments.TryGetValue("orderId", out var idArg)
141
+ && (!int.TryParse(idArg?.ToString(), out var id) || id <= 0))
142
+ {
143
+ return $"Invalid orderId '{idArg}' for '{context.Function.Name}'. Must be a positive integer.";
144
+ }
145
+
146
+ return await next(context, cancellationToken);
147
+ }
148
+ ```
149
+
150
+ **Princípio:** devolva uma mensagem de erro descritiva — o LLM vai corrigir o argumento na próxima iteração. Não lance exceção; não chame `next()` com argumento inválido.
151
+
152
+ ---
153
+
154
+ ## Decision tree
155
+
156
+ ```
157
+ Precisa de middleware no agent?
158
+ │
159
+ ├─ Logar / autorizar a invocação completa do agent?
160
+ │ └─ Agent Run middleware
161
+ │ .Use(runFunc: MyMiddleware, runStreamingFunc: null)
162
+ │
163
+ ├─ Tratar falhas de tool / validar argumentos do LLM?
164
+ │ └─ Function Calling middleware
165
+ │ .Use(ResilientToolMiddleware)
166
+ │
167
+ └─ Medir / cachear / filtrar chamadas brutas ao LLM?
168
+ └─ IChatClient middleware
169
+ .Use(getResponseFunc: TelemetryMiddleware, getStreamingResponseFunc: null)
170
+ — aplicado no IChatClient.AsBuilder() ANTES de .AsAIAgent()
171
+ ```
172
+
173
+ ---
174
+
175
+ ## Anti-patterns
176
+
177
+ | Anti-pattern | Problema |
178
+ |--------------|---------|
179
+ | Swallow exception sem retornar contexto ao agent | O agent não consegue se autocorrigir — para silenciosamente |
180
+ | Middleware que muta estado global | Viola isolation; causa race conditions em requests concorrentes |
181
+ | Fornecer apenas middleware não-streaming | Chamadas streaming rodam em modo não-streaming silenciosamente — downgrade imperceptível |
182
+ | Lógica de negócio em middleware | Middleware é cross-cutting only; lógica de domínio pertence ao tool ou ao handler |
183
+ | Registrar middleware depois de `Build()` | `Build()` finaliza o pipeline — chamadas após não têm efeito |
184
+
185
+ ---
186
+
187
+ ## Checklist (verifiable by morph-eval)
188
+
189
+ - [ ] Middleware registrado via `.AsBuilder().Use(...).Build()` — nunca depois de `Build()`
190
+ - [ ] IChatClient middleware aplicado no `chatClient.AsBuilder()` antes de `.AsAIAgent()`
191
+ - [ ] Function Calling middleware retorna string descritiva em vez de lançar exceção
192
+ - [ ] Anti-hallucination: argumentos validados antes de chamar `next()`
193
+ - [ ] Nenhum middleware com `using Microsoft.SemanticKernel` — MAF 1.0 GA não usa SK
194
+ - [ ] Streaming: se nenhum middleware streaming for fornecido, downgrade documentado como decisão consciente
195
+ - [ ] Middleware que chama serviços externos tem timeout configurado
196
+
197
+ ---
198
+
199
+ ## References
200
+
201
+ - `ai-agents-setup` — pacotes, DI, quick start
202
+ - `ai-agents-production` — panorama completo: observability, A2A, MCP, caching, middleware-as-one-of-many-concerns
203
+ - `ai-agents-providers-model-registry` — `_modelRegistry.GetChatClient(alias)`, provider-agnostic pattern
204
+
205
+ ---
206
+
207
+ *MORPH-SPEC by Polymorphism Tech — ai-agents/middleware-patterns.md v1.0 (2026-05-19)*