A minimal .NET / ASP.NET Core tool server that exposes the same two tools through two Foundry-consumable surfaces:
- OpenAPI (custom skill) at
/api/*+ spec at/openapi/v1.json. - MCP (Model Context Protocol) streamable-HTTP at
/mcp.
Same code path, two doors. Lets you compare the two integration paths from a single deployment.
This is the .NET sibling of embr-foundry-tool-sample-python.
- ASP.NET Core minimal-API web app on
net10.0. - Two tools:
getWeatherandgetTime— pure static methods, decorated with[Description]for both surfaces. - OpenAPI spec generated by
Microsoft.AspNetCore.OpenApiwith anx-api-keysecurity scheme declared (Foundry's custom-OpenAPI tool import rejects anonymous specs). - MCP server via the official
ModelContextProtocol.AspNetCoreSDK, mounted at/mcpwith stateless HTTP transport. Tools are auto-discovered via[McpServerTool]attributes.
app/
Program.cs # endpoint mapping, OpenAPI transformer, MCP wiring
Tools.cs # core tool implementations + DTOs
McpTools.cs # [McpServerToolType] wrappers over Tools.* for the MCP server
embr.yaml # Embr deploy config (platform: dotnet)
.env.example # optional TOOL_API_KEY for runtime auth
| Path | Purpose |
|---|---|
/health |
Liveness probe |
/ |
Index — points at the two surfaces |
/api/weather?location=... |
OpenAPI tool (returns WeatherResponse) |
/api/time?timezone=... |
OpenAPI tool (returns TimeResponse) |
/openapi/v1.json |
OpenAPI 3.1 spec — feed this to Foundry's "custom OpenAPI tool" |
/mcp |
MCP streamable-HTTP — register as a remote MCP tool in Foundry |
The OpenAPI spec declares an x-api-key header security scheme (Foundry
requires at least one auth scheme on imported tools). At runtime:
- If
TOOL_API_KEYis unset, requests are accepted without a header. This is the easy mode for the Foundry import flow and local development. - If
TOOL_API_KEYis set,/api/*requests must includex-api-key: <value>or get a401.
The MCP surface does not enforce the header — Foundry's MCP connection configuration is the auth boundary there.
cd app
dotnet runThen:
curl http://localhost:5000/health
curl 'http://localhost:5000/api/weather?location=Seattle'
curl 'http://localhost:5000/api/time?timezone=America/New_York'
curl http://localhost:5000/openapi/v1.json | jq .For the MCP surface, post a JSON-RPC initialize:
curl -s -X POST http://localhost:5000/mcp \
-H 'Accept: application/json,text/event-stream' \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"curl","version":"1"}}}'gh repo create embr-foundry-tool-sample-dotnet --source=. --public --push
embr quickstart deploy <your-user>/embr-foundry-tool-sample-dotnet
# optional:
embr variables set TOOL_API_KEY <secret> -p <projectId> -e <envId> -sOnce deployed, the URL Foundry needs is:
- OpenAPI:
https://<your-deploy>/openapi/v1.json - MCP:
https://<your-deploy>/mcp
- Tool implementations are pure (no external deps), so behavior is fully identical between the OpenAPI route and the MCP tool call.
app.MapMcp("/mcp")handles both/mcpand/mcp/cleanly — no manual trailing-slash workaround needed (unlike the Python FastMCP variant).