ASP.NET Core reverse proxy using YARP that sits between AI coding agents (such as Claude Code) and their LLM API endpoints. Captures all API traffic including streaming SSE responses and exposes a real-time web dashboard via SignalR.
- Port 8888 (
localhost): YARP reverse proxy forwarding to LLM API (HTTP) - Port 9999 (
localhost): YARP reverse proxy forwarding to MCP server (HTTP), destination configured at runtime - Port 5000 (
localhost): Dashboard (HTTP) - Port 5001 (
localhost): Dashboard (HTTPS, auto-launches browser) - Single project, single NuGet dependency (
Yarp.ReverseProxy)
dotnet build
dotnet runWindows:
publish.batmacOS / Linux:
bash publish.shEach script builds both projects for the current platform. Outputs to Published/ (gitignored):
Published\CodingAgentExplorer\- framework-dependent, requires .NET 10 runtimePublished\HookAgent\HookAgent.exe- single-file, current platform (Windows: win-x64; macOS/Linux: detected at build time)
Set the API base URL to point at the proxy:
export ANTHROPIC_BASE_URL=http://localhost:8888Then use Claude Code normally. All requests flow through the proxy and appear on the dashboard at https://localhost:5001.
The MCP Observer proxies traffic between Claude Code and any MCP server on port 9999. The destination URL is configured at runtime via the dashboard at https://localhost:5001/mcp/index.html.
Register the proxy as an MCP server in Claude Code:
claude mcp add --transport http mcp_proxy http://localhost:9999The destination URL is stored in McpProxyConfig (singleton) and triggers a YARP config reload via IChangeToken when updated. SSE keep-alive GET requests to / with no body are silently dropped and not stored.
Program.cs- App setup: YARP, SignalR, dual-port Kestrel, API endpointsModels/- DTOs: ProxiedRequest, ClaudeRequestBody, SseEvent, HookEvent, McpDestinationRequestServices/RequestStore.cs- In-memory circular buffer (ConcurrentQueue, max 1000)Services/HookEventStore.cs- In-memory store for hook eventsServices/McpRequestStore.cs- In-memory store for MCP requests (max 500)Services/McpProxyConfig.cs- Holds the runtime MCP destination URL, signals YARP on changeProxy/CaptureTransformProvider.cs- YARP ITransformProvider for request/response captureProxy/DynamicProxyConfigProvider.cs- Dynamic YARP config for Claude (8888) and MCP (9999) routesHubs/DashboardHub.cs- SignalR hub for real-time dashboard updateswwwroot/- Dashboard SPA (vanilla HTML/JS/CSS + SignalR client)wwwroot/mcp/- MCP Observer pageHookAgent/- Single-file CLI tool used as a Claude Code hook commandpublish.bat- Publishes both projects toPublished/(Windows, win-x64 HookAgent)publish.sh- Publishes both projects toPublished/(detects current platform for HookAgent)
- Introducing the Coding Agent Explorer .NET - Overview of the project, architecture, and design decisions
- Exploring Claude Code Hooks with the Coding Agent Explorer (.NET) - Deep dive into the HookAgent and Claude Code hook system integration
- Never use em dashes (—) in README.md or any documentation. Use a comma, hyphen, colon, or reword the sentence instead.
- Never use a dash (hyphen) between two sentences or clauses as a separator. Use a period, comma, colon, or reword instead.
- YARP
ITransformProviderfor intercepting requests/responses (not middleware) SuppressResponseBody = true+ manual line-by-line forwarding for SSE streaming- Kestrel: port 8888 (HTTP proxy), port 9999 (MCP proxy), port 5000 (HTTP dashboard), port 5001 (HTTPS dashboard)
- API keys are redacted from stored request headers
- Streaming SSE events are parsed to extract token usage, message ID, stop reason, and time-to-first-token
- MCP destination URL is held in
McpProxyConfigsingleton; changing it cancels aCancellationTokenSourceto signal YARP to reload routes viaDynamicProxyConfigProvider - MCP SSE keep-alive polling (GET / with no body) is filtered out in
StoreAndNotifybefore reaching the store or SignalR