agentleFS
aiappsgbbaiappsgbb/awesome-gbb/skills/foundry-mcp-auth/SKILL.md

foundry-mcp-auth

Skill8 starsChanged 12 days agoMITView on GitHub

Use when a custom Foundry MCP server needs delegated user authentication, OAuth identity passthrough, audience-correct access tokens, per-user scope authorization, or connection-consent troubleshooting. USE FOR: custom Entra OAuth MCP, Prompt Agent MCP connection, Hosted FoundryToolbox caller context, oauth_consent_request, CONSENT_REQUIRED, delegated versus app-only identity, MCP protected resource metadata, OAuth versus literal Entra OBO. Fabric/OneDrive/SharePoint authentication readiness, not connector implementation. DO NOT USE FOR: generic Toolbox management (use foundry-toolbox), hosted runtime deployment (use foundry-hosted-agents), durable MCP tasks or jobs (use foundry-mcp-aca-jobs), web frontend sign-in, or general network provisioning.

  • Installs packages

We split this skill into the rules it gives an agent

25 rules. About 0 are common, 19 are distinctive, and 6 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈19

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 6

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Never send Microsoft-audience tokens to a custom MCP, copy a personal token into an agent definition, or fall back to managed identity when …

    line 41
  2. Distinctive:

    No speculative adapter is shipped.

    line 58
  3. Distinctive:

    Use two independent users for final isolation acceptance.

    line 62
  4. Distinctive:

    Use a tenant-specific Entra v2 issuer/JWKS and the API application ID as JWT audience.

    line 95
  5. Very specific:

    Keep include_fastmcp_meta=False: the pinned Hosted Toolbox consumer does not accept the private _fastmcp metadata key.

    line 111
  6. Distinctive:

    No identity field comes from tool arguments or the label mapping.

    line 137
  7. Distinctive:

    Only the digest and correlation ID enter the proof audit.

    line 138
  8. Very specific:

    Use the GA ARM helper with API 2026-05-01.

    line 152
  9. Distinctive:

    Do not change shared namespace modes or expose the MCP publicly to bypass it.

    line 156
  10. Distinctive:

    Stop unchanged deterministic retries and collect sanitized request shape, versions, service codes and correlation IDs.

    line 157
  11. Distinctive:

    Follow the connection contract before executing any cloud command.

    line 161
  12. Distinctive:

    Use the runbook's update/readback procedure rather than assuming that creating a version changes every endpoint or existing conversation.

    line 230
  13. Very specific:

    Use the canonical Hosted Dockerfile's default container identity.

    line 233
  14. Distinctive:

    Keep jobs' FastMCP 4 / MCP 2 environment unchanged.

    line 274
  15. Distinctive:

    Run local tests with no Azure credentials or endpoints.

    line 276
  16. Distinctive:

    Create the deployable Hosted project with the canonical staging helper:

    line 286
  17. Very specific:

    Run Hosted azd commands from that new directory.

    line 292
  18. Distinctive:

    Before every Azure shell/subprocess export both isolated CLI config dirs from the operator's tenant index.

    line 306
  19. Very specific:

    After Gate B, run the server azd up from templates/ and the Hosted brownfield workflow from the directory created by stage_hosted.py, …

    line 325
  20. Distinctive:

    Use a short unique azd environment name (at most 20 characters).

    line 331
  21. Very specific:

    Check both account network injection and the project capability-host configuration.

    line 333
  22. Distinctive:

    Do not equate retaining infrastructure with a non-expiring OAuth token.

    line 345
  23. Distinctive:

    Keep exact resource IDs, image digests, versions, consent status and safe identity evidence in a private runbook, separate from public …

    line 346
  24. Distinctive:

    No personal refresh tokens, copied portal tokens, browser storage scraping, automatic browser consent, Graph data or extra web application.

    line 355
  25. Distinctive:

    Before release: complete all four original paths, first and later consent, denial, refresh/revocation, two-user concurrency and private …

    line 358

≈ “Common” is an estimate: at least 1 in 5 of the 308,232 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Foundry MCP delegated authentication

UNRELEASED CANDIDATE - 3 OF 4 PATHS VERIFIED for one real user. Hosted/direct is NOT DEMONSTRATED; this is not four-path compatibility, multi-user certification or a literal OBO exchange. See the validation notes for evidence, dependency cohorts, issue triage and remaining acceptance gates. Cloud changes require Gate B, the operator's explicit approval. User/admin consent always remains a human action; approval to configure resources does not authorize automated consent.

Identity boundaries

BoundaryIdentity and credential
User -> FoundryExisting tenant-isolated Azure CLI user credential for backend tests, or user sign-in to Playground; same tenant as the project. Neither implies MCP consent.
Hosted agent -> ToolboxAgent runtime credential, with audience https://ai.azure.com; upstream wrapper forwards the trusted request-scoped x-agent-foundry-call-id.
Foundry/Toolbox -> custom MCPCredentials from the user's custom OAuth connection, minted for the MCP API's own audience and delegated permission.
MCP -> another API, if separately requiredExplicit Entra OBO exchange for the second audience, not replay of the inbound access token. Not implemented in this candidate.

OAuth identity passthrough is not proof of Entra OBO. It is native connection-managed authorization/refresh. UserEntraToken is a different mechanism for supported services, not a universal custom-server substitute. Never send Microsoft-audience tokens to a custom MCP, copy a personal token into an agent definition, or fall back to managed identity when user context is missing. Do not decode, fabricate, persist, or send the platform call ID to the custom MCP.

Released support and remaining acceptance

PathIntegration contract
Prompt -> direct MCPMCPTool.project_connection_id references custom OAuth; Foundry emits consent output.
Hosted -> Toolbox -> MCPUpstream FoundryToolbox carries platform auth and request context; inner MCPToolboxTool references OAuth.
Prompt -> Toolbox -> MCPNative UserEntraToken connection targets first-party Toolbox; its inner MCP connection stays custom OAuth2.
Hosted -> direct MCPExperimental native FoundryChatClient.get_mcp_tool(project_connection_id=...); not a validated substitute for the Hosted/Toolbox recipe.

The pinned hosting release converts Toolbox consent during initialization, not arbitrary late CONSENT_REQUIRED tool errors. An output-converter upgrade alone does not prove caller-context propagation or late exception handling. No speculative adapter is shipped. A user's initial consent after another user initialized the shared host, expiry, and revocation must remain explicit gates, not silently successful retries.

Use two independent users for final isolation acceptance. Local signed-token concurrency tests are not a substitute for two actual Entra users.

For future Microsoft resources, use Microsoft resource profiles. They are source-reviewed readiness contracts, NOT TESTED connectors. The custom-MCP identity proof grants no Graph, SharePoint, OneDrive or Fabric access. Select the native service route or an explicitly designed downstream OBO boundary; never reuse the first-party AI bridge for an arbitrary resource.

Canonical artifacts

Copy/import these implementations rather than repeating their bodies:

FileOwned contract
authorization.pyResource-server authorization: signature, issuer, audience, lifetime, delegated scope, pseudonymous receipts and synthetic ownership.
delegated_server.pyResource-server authorization: HTTP MCP, PRM, request-local identity and deny logging.
connection.yamlConnection setup: secret-free definition using environment references, not secret command-line arguments.
connection-contract.mdConnection setup: resource/client separation, scopes, callback, create/read and consent lifecycle.
provision_connection.pyConnection setup: live-proven GA ARM builder and opt-in create/update with safe readback.
configure_foundry.pyAgent integration: pure SDK builders and explicit Gate-B-only create function.
invoke_agent.pyAgent integration: distinct Prompt/Hosted endpoints, consent continuation and failure-status handling.
agent_instructions.pyAgent integration: shared fresh identity-tool calls and faithful rendering of actual claims, separate from synthetic items.
hosted_agent.pyAgent integration: minimal composition using the upstream Toolbox/Responses wrappers.
stage_hosted.pyLocal recipe: stage canonical build inputs inside a standalone Hosted project, never through .. service paths.
templates/Local recipe: separate server, management and hosted dependency environments; brownfield azd composition.
playground.mdLive acceptance: operator-driven Playground protocol, including all unresolved cases.
demo-runbook.mdReproducing and operating the retained demo without resource expiry or automatic cleanup.
microsoft-resource-profiles.mdMicrosoft resource readiness: route-specific audiences, permissions, consent, licenses, network and future acceptance requirements; no connector runtime.

Resource-server authorization

Use a tenant-specific Entra v2 issuer/JWKS and the API application ID as JWT audience. Pin RS256; require expiry, issued-at and identity claims; validate not-before when present. Read signed scp, not scope, roles, a model argument or a claimed user header. azp identifies the OAuth client, not the user. Invalid credentials return 401; a valid app-only or insufficiently scoped token returns 403. JWKS transport failures surface as errors, never as successful or anonymous tool calls.

Three identifiers are deliberately different: MCP resource URL https://<host>/mcp, JWT audience <api-application-id>, and OAuth requested scope api://<api-application-id>/demo.read. The JWT permission is demo.read. The verifier maps that validated permission to the full scope advertised by PRM; offline_access is not a business permission required in the access token. The MCP is a resource server only: no token broker, OAuth proxy, DCR or callback service. Entra clients must be pre-registered.

Keep include_fastmcp_meta=False: the pinned Hosted Toolbox consumer does not accept the private _fastmcp metadata key. This disables only vendor-specific metadata, not authentication or authorization. After changing an already-published tool schema, create a fresh Toolbox version and bind the Hosted agent to that version to avoid stale metadata.

who_am_i() and list_my_demo_items() take no identity arguments. Each call uses its authenticated request context. Receipts never contain JWTs, raw subject/tenant IDs, emails or secrets by default. Pseudonyms use a random process-local HMAC key; they change on restart. The demo uses one replica. Cross-restart identity reconciliation is not a feature of this mock.

For redacted live identity evidence, the operator may configure MCP_SUBJECT_LABELS_JSON: a map from explicitly approved Entra object IDs to distinct generic labels such as user-a and user-b. The server adds subject_label only by looking up the signature-validated oid; neither a prompt nor a tool argument controls it. Default receipts expose no raw IDs or names. Unconfigured subjects return unmapped, which must not count as proof of an expected user. Labels do not grant permission or replace the normal JWT/scope checks. Keep actual mappings in private deployment configuration, not this repo.

For an explicit operator-approved identity proof, set MCP_IDENTITY_PROOF_ENABLED=true on the MCP server (default false). who_am_i then returns a claims-only object with oid, tid, aud, scp, azp, iss, exp, correlation ID and SHA-256 digest of the inbound bearer. It does not mix actual claims with a synthetic label or hashed identity. No identity field comes from tool arguments or the label mapping. Only the digest and correlation ID enter the proof audit; raw IDs remain in the authenticated response/private evidence, never normal logs. Never return or store the bearer itself. Compare oid/tid with an independently verified caller identity and match the receipt to server audit. The user's Foundry token and their MCP token have different audiences; prove the same user, not byte-identical tokens. This does not query Graph. The same verified bearer may include upn, preferred_username or name. The opt-in view returns those optional string claims under their original names only when present. Never infer UPN from oid, relabel preferred_username as UPN, or use either mutable display value for authorization. Default receipts and normal logs do not expose these values.

Connection setup

Use the GA ARM helper with API 2026-05-01. Classify connection-creation/schema errors separately from consent, JWT and private-MCP failures. A generated connector-definition error is not fixed by rotating credentials or inventing connection metadata knobs. Do not change shared namespace modes or expose the MCP publicly to bypass it. Stop unchanged deterministic retries and collect sanitized request shape, versions, service codes and correlation IDs; see the validation notes for the historical service error and its evidence boundary.

Follow the connection contract before executing any cloud command. Use custom OAuth with a dedicated resource API and a separate OAuth client, minimal delegated permissions and exact platform redirect URI. Preserve other redirect URIs if editing an existing registration. The connection belongs to a Foundry project; reuse it across agents only where supported. Consent is per user, project and connection, not globally per portal login.

oauth_consent_request / MCP CONSENT_REQUIRED is distinct from mcp_approval_request. Read-only tool approval may be never; this never auto-approves OAuth or admin consent. Denial must not become empty data.

Agent integration

The management invoker now requires a durable record callback. Package the canonical operation evidence helper beside it, or add that canonical directory to the management environment's module path. Intent is saved before dispatch and returned IDs/status before application parsing; pending responses remain pending. No automatic replay, consent approval or permission expansion is added. A failed SDK parse retains available error/request metadata and UNKNOWN rather than inventing an ID. This change does not promote the delegated-auth candidate or close its remaining multi-user/consent release gates.

Both Prompt and Hosted use the canonical shared instructions: identity questions trigger a fresh who_am_i call, and actual claims are rendered in the final answer when the operator enabled their disclosure. Synthetic items are queried only when explicitly requested. Test ordinary identity questions and follow-ups with the service's normal tool choice; checking nested tool data alone does not prove the user-visible answer is correct.

For Prompt/Toolbox, use build_toolbox_bridge_properties with the first-party Foundry project endpoint and versioned Toolbox, then build_prompt_toolbox_definition with that bridge connection ID. Its UserEntraToken audience is https://ai.azure.com, which is used only at the Microsoft Toolbox boundary. The existing inner custom MCP OAuth connection still requests the custom API audience/scope. Never point this first-party bridge directly at the custom MCP. A static-bearer SDK example is not the canonical delegated recipe.

The pure builder returns a Prompt definition and an inner Toolbox MCP tool, both referencing the same connection ID. It never puts authorization or user headers into the tool configuration. create_bindings is a cloud write, not a local test: only call it after Gate B and verify the connection auth type separately. It reads without credentials and rejects target mismatch.

For Hosted, deploy the Responses 2.0.0 runtime and set a versioned TOOLBOX_ENDPOINT. Use FoundryToolbox itself; do not copy its private auth transport. The runtime credential grants platform access only, while the platform resolves the downstream user using the trusted call context. Constructing the wrapper locally or supplying a fabricated header is not proof of that platform chain.

Invoke a Prompt Agent through project Responses with agent_reference. Invoke Hosted through project.get_openai_client(agent_name=hosted_name): the project-level agent_reference route is rejected for Hosted. Pass the normal user credential and, where needed, a supported private proxy transport; never copy portal tokens or add invented caller headers. The API can return HTTP success with response.status == "failed": inspect response.error and tool outputs, not just the HTTP status or assistant prose. The canonical helper takes an http_client_factory, not a shared HTTP client: the OpenAI client owns and closes its transport after each call. The factory supplies a new private transport for subsequent turns/consent continuation. Use agent_version to select the Prompt direct or Toolbox version; Hosted uses its deployed endpoint routing. For ordinary-question acceptance, pass require_tool=False so the helper leaves tool selection to the service. The default True is a forced-tool smoke, not proof that an ordinary question chooses the tool. Check the current/default version and final answer as well as the actual tool output. Use the runbook's update/readback procedure rather than assuming that creating a version changes every endpoint or existing conversation.

Use the canonical Hosted Dockerfile's default container identity. A fixed USER 65532 is incompatible with the writable /home/session/.sessions platform mount. The independent ACA server keeps its non-root identity. Do not replace session storage with an auth bypass.

Ownership remains: foundry-toolbox owns the upstream consumer and Toolbox management; foundry-hosted-agents owns runtime hosting; foundry-prompt-agents owns Prompt lifecycle; foundry-mcp-aca / azd-patterns own ACA and shared IaC. This recipe owns only their delegated-auth composition.

Local recipe

These commands run from a full, writable catalog checkout, not from the consumer project's unrelated repository root or a copied skill folder. Shared azd-patterns modules, scripts/tests and validation notes are catalog dependencies. Local plugin loading does not install Python dependencies; air-gapped use requires pre-staged wheels for each Python/platform cohort. Real Entra/Foundry calls still require their approved network paths.

From the repository root, choose distinct paths for the three venvs. Install each dependency set separately; never add the management SDK to Hosted:

python3 -m venv .scratch/mcp-auth-server
.scratch/mcp-auth-server/bin/pip install ./skills/foundry-mcp-auth/templates
python3 -m venv .scratch/mcp-auth-management
.scratch/mcp-auth-management/bin/pip install ./skills/foundry-mcp-auth/templates/management
python3 -m venv .scratch/mcp-auth-hosted
.scratch/mcp-auth-hosted/bin/pip install ./skills/foundry-mcp-auth/templates/hosted

Server: FastMCP 2.14 / MCP 1.29 / PyJWT 2.10. Management: Projects SDK 2.6. The server azd template uses language: docker and native docker.remoteBuild so the approved registry builds the image without a local Docker daemon. Image builds are cloud operations and still require Gate B. Hosted uses core 1.16 / OpenAI provider 1.14 with the catalog's hosting 1.0.0b260730, Foundry provider 1.10.4 and Projects SDK 2.3, respecting its <2.4 requirement. OpenAI provider 1.10 lacks the _feature_usage import required by the Foundry provider despite satisfying its declared lower bound. Honor the provider's SDK upper bound and validate each isolated cohort before changing pins; an all-package upgrade is not a compatibility strategy. Keep jobs' FastMCP 4 / MCP 2 environment unchanged.

Run local tests with no Azure credentials or endpoints. These checks are LOCAL ONLY, not proof of platform delegation:

.scratch/mcp-auth-server/bin/python -m unittest scripts.tests.test_foundry_mcp_auth_policy scripts.tests.test_foundry_mcp_auth_contract
PYTHONPATH=skills/foundry-mcp-auth .scratch/mcp-auth-server/bin/python -m unittest discover -s skills/foundry-mcp-auth/test-fixture -p 'test_server.py'
PYTHONPATH=skills/foundry-mcp-auth .scratch/mcp-auth-management/bin/python -m unittest discover -s skills/foundry-mcp-auth/test-fixture -p 'test_management.py'
PYTHONPATH=skills/foundry-mcp-auth .scratch/mcp-auth-hosted/bin/python -m unittest discover -s skills/foundry-mcp-auth/test-fixture -p 'test_hosted.py'

Create the deployable Hosted project with the canonical staging helper:

python3 skills/foundry-mcp-auth/references/python/stage_hosted.py .scratch/delegated-hosted

Run Hosted azd commands from that new directory. The extension rejects parent-directory service paths even when ordinary Container Apps accepts them. Staging copies only the canonical Dockerfile, dependency manifest and entrypoint and shared instructions; no credentials or caches. Consult the validation notes for the recorded deployment tool versions.

For manual local serving, set MCP_TENANT_ID, MCP_API_CLIENT_ID and MCP_BASE_URL (HTTPS origin; loopback HTTP only for local work), then run python -m references.python.delegated_server from this skill directory. Production serving uses real Entra JWTs; test signing keys exist only in tests. There is no auth-disable flag.

Gate B and private deployment

Before every Azure shell/subprocess export both isolated CLI config dirs from the operator's tenant index; explicitly select the approved subscription, not its index default. Assert tenant/subscription immediately before writes using azure-tenant-isolation. Never force login when the isolated session works. No environment IDs belong in this public recipe.

Gate B must approve the resource allow-list, Entra/RBAC changes, credentials, cost and cleanup. Check real runtime routes and DNS before deploying: Prompt/Toolbox/Hosted -> MCP, browser -> Playground/consent, and permitted Entra token/JWKS egress. No hidden public fallback. APIM is optional, not a portal integration prerequisite.

The server template uses an existing internal VNet-connected ACA environment, registry and pull identity in the approved resource group. It reuses azd-patterns/references/bicep/aca-app.bicep; pre-grant AcrPull and verify registry reachability. It does not create a VNet or patch the hub. App-level external ingress on an internal ACA environment permits VNet callers; it does not alone determine internet exposure.

After Gate B, run the server azd up from templates/ and the Hosted brownfield workflow from the directory created by stage_hosted.py, following the existing hosted skill's environment contract. Keep the server template under the skill so shared Bicep modules resolve. The placeholder image is not the authenticated server: do not expose/advertise the app or create its connection until azd deploy has replaced it and auth probes pass. Use a short unique azd environment name (at most 20 characters).

Check both account network injection and the project capability-host configuration. Where Basic hosting is appropriate, use azd-patterns' foundry-project-capability-host.bicep without BYO datastore connections. Inventory actual project state and obtain approval for this project-wide change; do not blindly recreate existing hosts. Allow a bounded propagation window, and verify the actual runtime route. A wrapped downstream MCP 401 can prove network reachability, never delegated success.

For a retained demo, no default cleanup schedule, shutdown date or resource expiresAt tag is part of this recipe. Client credentials and OAuth tokens still have technical lifetimes: use an operator-approved, tenant-accepted long-lived client credential, managed rotation, and platform token refresh. Do not equate retaining infrastructure with a non-expiring OAuth token. Keep exact resource IDs, image digests, versions, consent status and safe identity evidence in a private runbook, separate from public skill docs.

Live acceptance and publication

The unattended fixture is deliberately separate from the interactive protocol. A workload identity passing a fixture is not delegated E2E. Missing prerequisites or unsupported paths are explicit failures/blocks, never skipped-to-PASS. No personal refresh tokens, copied portal tokens, browser storage scraping, automatic browser consent, Graph data or extra web application.

Before release: complete all four original paths, first and later consent, denial, refresh/revocation, two-user concurrency and private reachability. Record sanitized evidence against the exact candidate SHA. Do not call this candidate production-ready merely because local tests or pin imports pass. Literal OBO additionally requires an explicitly approved audience-distinct mock API and a real A->B exchange. Wave 2 web sign-in remains separate: authorization code/PKCE, audience-correct backend tokens, supported MSAL OBO, session/consent handling; never reuse tokens obtained from the portal. The single status matrix also records future licensed resources as NOT TESTED. Documentation does not enable those routes or satisfy their licensing, consent or resource grants.

Sources

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.