Citadel Publish Contract
Onboard and publish centrally protected AI assets — Tools (MCP) and Agents (A2A) — through the AI Hub Gateway, using the same declarative, bicepparam-driven approach as the Access Contract and Backend Contract.
Phase 1 scope: publishing the asset endpoints, backends, baseline policies, usage tracking, and optional API Center registration. This does not create APIM Products/Subscriptions — governed access (who may call a published asset) is Access Contract scope, handled in phase 2.
📖 Full documentation: Publish contract guide
Asset types
assetType |
Result | Gateway endpoint | Backend |
|---|---|---|---|
mcp-from-api |
Converts an existing onboarded REST API into an MCP tool server (mcpTools wrap operations) |
{gateway}/{path}/mcp |
Reuses the source API's backend |
mcp-existing |
Publishes a remote/native MCP server through the gateway | {gateway}/{path} |
Dedicated backend (<name>-backend) |
a2a |
Publishes a native APIM A2A agent endpoint + agent card (e.g. a Foundry-hosted agent) | {gateway}/{path} (card at /.well-known/agent.json) |
Policy managed-identity auth (no backend object) |
MCP endpoint suffix: APIM appends
/mcpto the path only for API→MCP tools (mcp-from-api). Native/remote MCP servers (mcp-existing) are served at{gateway}/{path}with no appended/mcp.⚠️
mcp-from-apisource API must NOT require its own subscription key. The MCP server forwardstools/calltosourceApiNameinternally, so if that source API was onboarded withsubscriptionRequired: truethe internal call fails with401 — missing subscription key(whileinitialize/tools/liststill succeed). Onboard the source API withsubscriptionRequired: falseand let the published MCP server's own subscription/Access Contract gate govern access. See Publish contract guide.🔐 Keep the source API protected (opt-in). Set
forwardSubscriptionKeyToSource: trueon anmcp-from-apiasset to forward the caller's subscription key into a custom header (sourceSubscriptionKeyHeaderName, defaultx-mcp-sub-key) that survives the internal hop — so the same contract key reaches both the MCP tool and a subscription-protected source API (no anonymous direct access). Requires the source API onboarded withsubscriptionRequired: truereading that custom header, and the source API added to the Access Contract'sapiNameMapping. See Publish contract guide.
Layout
bicep/infra/citadel-publish-contracts/
├── main.bicep # Orchestrator (subscription scope)
├── main.bicepparam # Sample: 3 assets (API->MCP, native MCP, A2A)
├── publish-contract-guide.md # Primary guide
├── README.md # This file
├── modules/
│ ├── publishBackend.bicep # Per-asset backend + circuit breaker + auth
│ ├── publishMcpFromApi.bicep # API -> MCP tool server
│ ├── publishMcpExisting.bicep # Remote/native MCP server
│ ├── publishA2aAgent.bicep # Native A2A agent endpoint
│ ├── publishApiCenter.bicep # Optional API Center registration
│ └── publishPolicyFragments.bicep# Ensures mcp-usage / a2a-usage fragments exist
└── policies/
├── baseline-mcp-policy.xml # Default MCP policy (auth + usage; no response-body reads)
└── baseline-a2a-policy.xml # Default A2A policy (auth + usage)
contracts/ # per-contract definitions (git-ignored; see below)
└── <contract-name>/<env>/main.bicepparam # using '../../../main.bicep'
Folder structure & source control
Like the Access Contract, each publish contract lives under contracts/<contract-name>/<env>/main.bicepparam (the using points three levels up to main.bicep). The contracts/ tree is git-ignored in this accelerator (generated params carry environment-specific ids); teams source-control their own contracts in their repo following this layout. The validation notebook writes its generated contract to contracts/sample-assets/dev/.
Deploy
az deployment sub create `
--name publish-sample-assets-dev `
--location <region> `
--template-file bicep/infra/citadel-publish-contracts/main.bicep `
--parameters bicep/infra/citadel-publish-contracts/contracts/<contract-name>/<env>/main.bicepparam
The root main.bicepparam is a reference sample (
using 'main.bicep'); real contracts live undercontracts/<contract-name>/<env>/(using '../../../main.bicep').
Resiliency
Each mcp-existing asset gets a native APIM circuit breaker on its backend (defaults: 3 failures / PT5M → open PT1M, honoring Retry-After). Override per asset via backend.circuitBreaker, or disable globally with configureCircuitBreaker=false. This mirrors the Backend Contract's resiliency model. (a2a assets authenticate in policy against a full URL and have no backend object; add a retry policy for agent resilience.)
Usage tracking
The baseline policies emit inbound request-count metrics to the mcp-usage / a2a-usage App Insights namespaces (tool/agent name via the deploymentName dimension). Two scheduled Logic App workflows (mcp-usage-ingestion, agent-usage-ingestion) aggregate these into the mcp-usage-container / agent-usage-container Cosmos collections — mirroring the LLM usage pipeline, with no Event Hub dependency.
MCP responses stream (SSE); MCP policies must not read the response body. Usage, auth, and any PII/content-safety you layer on run inbound only.
API Center (optional)
Set publishToApiCenter: true on an asset and supply the apiCenter coordinates to register it (kind mcp/a2a) into Azure API Center. Off by default.