A single Python service that demonstrates Scenario 2 of the Embr × Foundry POC:
A Foundry-hosted agent reaches into an Embr-hosted app as a tool. The same app exposes the same tools via two surfaces side-by-side, so we can compare both integration paths from one deployment:
- OpenAPI skill (
/api/*+/openapi.json) — for Foundry's custom-OpenAPI-tool feature.- MCP server (
/mcp, streamable HTTP) — for Foundry's MCP tool feature via a project connection.
┌─────────────────────────────┐
│ Foundry-hosted agent │
└──────────────┬──────────────┘
│
┌───────────────────┴────────────────────┐
│ │
registers │ (as OpenAPI skill) registers (as MCP tool)
│ │
▼ ▼
┌────────────────────┐ ┌────────────────────┐
│ /api/weather │ │ /mcp │
│ /api/time │ │ (streamable HTTP) │
└─────────┬──────────┘ └─────────┬──────────┘
│ same implementation │
└────────────┬────────────────────────┘
▼
┌────────────────────┐
│ app/tools.py │ (get_weather, get_time)
└────────────────────┘
python3.12 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
# Optional: set TOOL_API_KEY to require an x-api-key header on /api/*
cp .env.example .env
uvicorn app.main:app --reload --port 8000Quick checks:
# OpenAPI surface
curl 'http://localhost:8000/api/weather?location=Seattle'
curl 'http://localhost:8000/api/time?timezone=America/New_York'
curl http://localhost:8000/openapi.json | jq .info
# MCP surface — initialize handshake
curl -X POST http://localhost:8000/mcp/ \
-H 'content-type: application/json' \
-H 'accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"1"}}}'Interactive API docs: http://localhost:8000/docs
gh repo create embr-foundry-tool-sample-python --source=. --public --push
embr quickstart deploy <your-user>/embr-foundry-tool-sample-pythonGrab the public URL from embr deployments list (something like
https://production-embr-foundry-tool-sample-python-<suffix>.app.embr.azure).
For the rest of this guide, store it in a shell var:
APP_URL="https://production-embr-foundry-tool-sample-python-<suffix>.app.embr.azure"- Go to ai.azure.com, + New project, accept defaults.
- Models + endpoints → + Deploy model → Deploy base model → pick
gpt-4o-mini(or any model that supports tool-calling).
- In the project, open Agents → + New agent.
- Pick the model deployment from 3a.
- Instructions: "You are a helpful assistant that uses the available tools to answer questions about weather and time. Prefer tools over guessing."
- Save — now you can add tools to it.
- On the agent page click + Add → Custom tool → OpenAPI 3.0 specified tool.
- Give it a name (e.g.,
embr_tools_openapi). - Paste the URL of your spec —
${APP_URL}/openapi.json, e.g.:https://production-embr-foundry-tool-sample-python-<suffix>.app.embr.azure/openapi.json - Authentication: Foundry does not allow anonymous OpenAPI tools. Pick API key (or any option) and fill in dummy values — the server ignores the key unless you set
TOOL_API_KEYviaembr variables set. This is one of the platform gaps we're documenting. - Save.
- Back in the project, open Connected resources → + New connection → Custom keys → MCP server.
- Server label:
embr_tools_mcp. Server URL —${APP_URL}/mcp/, e.g.:(trailing slash matters — the app mounts MCP athttps://production-embr-foundry-tool-sample-python-<suffix>.app.embr.azure/mcp//mcpand FastMCP's streamable-HTTP handler is at/) - Auth:
None(v1) — or set a bearer token if you've locked it down later. - Save the connection. Then on the agent page, + Add → MCP server → pick the connection.
In the agent playground:
- "What's the weather in Tokyo right now?" — the model should call
getWeather(OpenAPI) orget_weather(MCP) depending on which one Foundry picks up first. Force it to compare by disabling one tool at a time. - "What time is it in Sydney?" — should call
getTime/get_time.
.
├── app/
│ ├── __init__.py
│ ├── tools.py # Shared tool logic (get_weather, get_time)
│ ├── mcp_server.py # FastMCP server wrapping the same logic
│ └── main.py # FastAPI app: OpenAPI routes + mounts MCP at /mcp
├── embr.yaml
├── requirements.txt
├── .env.example
└── README.md
- Unauthenticated in v1. Both surfaces are wide-open by default.
TOOL_API_KEYcan gate/api/*with a header check, but that's all. - Foundry refuses anonymous OpenAPI tools — you're forced to declare an auth scheme in the spec even if the backend ignores it. Candidate Embr platform feature: auto-generate a throwaway API key on
embr.yaml: expose_as: toolso this isn't a manual step. - MCP mount path quirk.
FastMCP.streamable_http_app()mounts its own/mcpsub-route by default, so withoutstreamable_http_path="/"you'd end up serving at/mcp/mcp. Worth documenting. - FastMCP DNS-rebinding protection rejects requests with HTTP 421
Invalid Host headerwhen deployed behind any proxy — because itshostparam defaults to127.0.0.1, which auto-enables a localhost-only allow-list (seemcp/server/fastmcp/server.py:178-181). Fix: passhost="0.0.0.0"toFastMCP(...). This sample already does this; flagging it for anyone copying the pattern. - No rate limiting. A real tool service needs per-tenant throttling.