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

foundry-mcp-aca-jobs

Skill8 starsChanged 12 days agoMITView on GitHub

Build job-backed MCP servers for Foundry when the control plane must stay responsive and execution belongs in ACA Jobs. USE FOR: MCP Tasks, SEP-2663, ACA Jobs, durable result claims, callbacks, external job orchestration, and shared-image worker handoff. DO NOT USE FOR: producer-side MCP hosting (use foundry-mcp-aca), Service Bus/queue/event-dispatch workflows, or business logic that should run directly in the MCP server or Docket container.

We split this skill into the rules it gives an agent

16 rules. About 0 are common, 9 are distinctive, and 7 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈9

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 7

    Very specific

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

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Use foundry-mcp-aca for producer-side MCP hosting.

    line 63
  2. Distinctive:

    Do not model this skill as Service Bus, queues, topics, subscriptions, or any event-dispatch fanout pattern.

    line 95
  3. Very specific:

    Prefer the public MCP Tasks result-claim path whenever the client supports it.

    line 100
  4. Distinctive:

    start the ACA Job with the exact job policy digest and command contract.

    line 169
  5. Very specific:

    If the same taskId arrives again with the same request fingerprint, return the existing record.

    line 171
  6. Distinctive:

    If no execution is bound and multiple executions match, select a candidate by status priority, start time, and execution ID, persist it …

    line 182
  7. Distinctive:

    If start returns only after reconciliation has already made the record terminal, ETag-adopt the late execution ID only when no execution is …

    line 188
  8. Very specific:

    If the start never becomes observable, the task stays Starting until the reconciliation budget expires.

    line 195
  9. Very specific:

    After the existing observation budget, only an unbound uncertain start fails with START_RECONCILIATION_EXHAUSTED.

    line 197
  10. Very specific:

    Before persisting worker success or failure, the ETag mutator must still observe the claiming worker's exact non-null workerClaimToken.

    line 238
  11. Distinctive:

    Use principal object ID mode for hosted agent instance identities.

    line 307
  12. Distinctive:

    Do not accept caller-supplied image references, commands, environment overrides, ARM IDs, secrets, large result bodies, callback URLs, or …

    line 311
  13. Distinctive:

    No default resource discovery, missing-input provisioning or shared grant fallback is allowed.

    line 332
  14. Distinctive:

    Before the first write, authenticated GET/list checks bind those exact resources, UAMI client/principal mappings and active executor …

    line 337
  15. Very specific:

    Use azd up or azd deploy to run the converged postdeploy flow.

    line 429
  16. Very specific:

    Do not pre-create that resource group.

    line 447

≈ “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

ACA Job-backed companion to foundry-mcp-aca. The MCP server owns protocol and state; the ACA Job owns execution.

Foundry MCP ACA Jobs

Copy verbatim from the canonical files below. Do not duplicate code bodies inline here.

Canonical fileContract
references/python/app/init.pyPackage exports and canonical imports
references/python/app/aca_jobs.pyACA job adapter and control-plane client
references/python/app/aca_tasks_extension.pyTasks extension adapter boundary
references/python/app/callbacks.pyCallback transport and payload writer
references/python/app/control_store.pyDurable control-store contract
references/python/app/job_worker.pyACA Job worker entrypoint
references/python/app/mcp_server.pyFastMCP server assembly
references/python/app/models.pyControl-record models and lifecycle mapping
references/python/app/orchestrator.pyStart, reconcile, and cancel orchestration
references/python/app/telemetry.pySafe telemetry wiring
templates/DockerfileShared runtime image
templates/pyproject.tomlRuntime dependency lock contract
templates/uv.lockHash-verified runtime dependency resolution
templates/azure.yamlazd service wiring and postdeploy flow
templates/bicepconfig.jsonEnables the Bicep assertion feature used by the deployment contract
templates/infra/main.bicepComposition root; requires the sibling catalog checkout layout described in Deploy with azd
templates/infra/main.parameters.jsonComplete azd parameter contract
templates/infra/app.bicepMCP app module
templates/infra/cosmos.bicepDurable control-store module
templates/infra/identity-rbac.bicepLeast-privilege identity/RBAC module
templates/infra/identity-rbac/assignments.bicepApp and worker data-plane role assignments
templates/infra/identity-rbac/job-operator.bicepScoped ACA Job operator role
templates/infra/identity-rbac/uami.bicepUser-assigned managed identity module
templates/infra/scripts/pyproject.tomlPostdeploy helper dependency contract
templates/infra/scripts/uv.lockHash-verified postdeploy helper resolution
templates/infra/scripts/converge_image.pyDigest convergence helper
templates/infra/scripts/verify_deployment.pyDeployment verification helper
../azd-patterns/references/bicep/aca-job.bicepCanonical ACA Job module

When to use this skill

  • A request can run longer than a normal tool call and needs durable progress.
  • The client may or may not advertise MCP Tasks, so the server needs a compatibility path.
  • You want the control plane to stay responsive while the job does the work.
  • The result should be stored externally and surfaced as a stable URL, not streamed inline as a large payload.
  • The worker must run in a separate ACA Job, not inside the MCP server process.

Use foundry-mcp-aca for producer-side MCP hosting. Use azd-patterns for the canonical ACA Job module and digest-convergence pattern.

Architecture and shared-image contract

This skill uses one immutable image and two runtime roles:

  1. the FastMCP control plane serves the API, Tasks adapter, callbacks, and lifecycle reconciliation;
  2. the ACA Job worker runs the business handler and writes the result; and
  3. both roles are built from the same image digest, but launched with different commands and different user-assigned managed identities.
RoleEntry pointIdentityResponsibility
MCP apppython -m app.mcp_serverapp UAMIprotocol, policy, callbacks, durable task state
ACA Jobpython -m app.job_workerjob UAMIexecution, output persistence, callback delivery

The image is built once from templates/Dockerfile, then converged in postdeploy so the app and job both point to the same digest. Never let the app and job drift onto separate images, commands, or identities.

Protocol contract

The server supports two contract paths:

  • the standards-first MCP Tasks path for clients that advertise the Tasks extension; and
  • the compatibility tools path for clients that do not.

Both paths share the same control record, callback, and durable-result contract. Do not model this skill as Service Bus, queues, topics, subscriptions, or any event-dispatch fanout pattern.

Standards-first MCP Tasks path

Prefer the public MCP Tasks result-claim path whenever the client supports it. AcaTasksExtension binds the io.modelcontextprotocol/tasks extension to the durable control record, worker claim, and callback flow.

KI-001 releases only when the first-class server result-claim API is public: validation must import TasksExtension from fastmcp_tasks, inspect its lifespan source, confirm docket_lifespan is present there, and confirm the AcaTasksExtension source/lifespan does not contain that Docket path.

Compatibility tools

For clients without MCP Tasks, expose the explicit tools:

  • start_aca_job(jobType, idempotencyKey, inputRef, callbackAlias)
  • get_aca_job_status(taskId)
  • cancel_aca_job(taskId)

These tools are a compatibility path, not a second workflow model. They still write the same control record and still persist results through the same worker. Their public response shapes are closed:

  • start_aca_job returns exactly taskId, jobType, status, acaExecutionId, pollAfterMs (2000), and effectState.
  • get_aca_job_status returns exactly taskId, jobType, status, acaExecutionId, resultUrl, errorCode, createdAt, updatedAt, and effectState.
  • cancel_aca_job returns exactly taskId, status, cancellationRequested (true), and effectState.

effectState is separate from lifecycle: UNKNOWN is never cleared by a timeout, missing result or cancellation. Native MCP Tasks completed results carry the same value in structuredContent; protocol working/cancelled alone makes no business-effect claim. Only an independent authorized readback may establish VERIFIED; the generic worker does not infer it from a handler result.

status is the public lifecycle value (Accepted, Starting, Running, Succeeded, Failed, or Cancelled). Never return the internal TaskRecord or expose owner scopes, hashes, worker tokens, ETags, or internal timestamps.

Control record and lifecycle

Version 2 adds private nativeOperation metadata and a separate effectState. Existing records default to UNKNOWN, never “no effect”. Lifecycle failure due to an exhausted observation budget does not prove business failure or authorize replay. Public status adapters must not expose signed/raw operation material.

The control record is the source of truth. It carries taskId, ownerScope, jobType, idempotencyKeyHash, requestFingerprint, inputRef, callbackAlias, lifecycleState, acaExecutionId, resultUrl, errorCode, callbackDeliveryState, callbackErrorCode, cancellationRequestedAt, startAttemptedAt, startAttemptCount, workerClaimedAt, workerClaimToken, workerClaimExpiresAt, createdAt, updatedAt, completedAt, and _etag.

Control record stateMCP statusPublic result
Accepted / Starting / Runningworkingpoll again
Succeeded + resultUrl presentcompletedisError: false, structuredContent.status = Succeeded
Failed + business errorcompletedisError: true, content carries the stable error code
Failed + RESULT_REFERENCE_MISSINGcompletedisError: true, content carries RESULT_REFERENCE_MISSING
Cancelledcancelledno result payload

RESULT_REFERENCE_MISSING is an internal failed terminal state. It is never a protocol-level failed response; the MCP projection stays completed with isError: true.

Idempotency and uncertain-start reconciliation

The start flow is:

  1. derive a deterministic taskId from the owner scope, job type, and idempotency-key hash;
  2. persist the new record with a request fingerprint;
  3. claim the record with an ETag; and
  4. start the ACA Job with the exact job policy digest and command contract.

If the same taskId arrives again with the same request fingerprint, return the existing record. If the fingerprint changes, the store raises IDEMPOTENCY_KEY_REUSED.

Uncertain starts are reconciled deterministically:

  • Accepted/Starting tasks are re-checked until the ACA execution appears.
  • A persisted acaExecutionId is the authoritative bound execution. Re-fetch the ETag-protected record before choosing a candidate, never overwrite a worker-bound execution, bind before stopping duplicates, and stop only discovered non-authoritative executions.
  • If no execution is bound and multiple executions match, select a candidate by status priority, start time, and execution ID, persist it with an ETag, then best-effort stop the losers.
  • A cancellation request that races with start is retained during binding. Once that execution ID is durable, issue a cooperative best-effort stop without changing the returned nonterminal task state.
  • If start returns only after reconciliation has already made the record terminal, ETag-adopt the late execution ID only when no execution is recorded, preserve every lifecycle, error, result, cancellation, and worker-claim field, then best-effort stop that exact orphan. If another execution ID is already recorded, preserve it and every terminal field without an ETag write, and best-effort stop the different late execution. Ordinary polling of the already-known execution ID is a pure no-op and must not stop it.
  • If the start never becomes observable, the task stays Starting until the reconciliation budget expires.
  • After the existing observation budget, only an unbound uncertain start fails with START_RECONCILIATION_EXHAUSTED; a bound or worker-claimed execution remains authoritative even when an ARM list is temporarily empty.

No automatic replay (2.0). A grace period plus an empty eventually-consistent execution list does not prove that the original POST was rejected. Reconciliation now only reads/binds the original execution; it does not issue another start. The monitoring outcome START_RECONCILIATION_EXHAUSTED leaves effectState: UNKNOWN. Retain the same task/fingerprint and native receipt for the owner.

AcaJobsAdapter now requires a durable record_operation callback before start. The server wires it to the existing ETag-protected control store; raw SDK response hooks save request/operation identity before result interpretation. Per-I/O and poller waits are bounded. A client deadline does not cancel remote execution; reconcile rather than creating another task ID. The 5.0 SDK calls use polling=False: a timed LROPoller.result alone would leave its background polling thread running. Read/bind the original operation through the existing reconciler; a submitted stop is not terminal-state proof.

Terminal reconciliation uses a distinct ten-minute unresolved-reconciliation budget anchored at the existing durable startAttemptedAt, falling back to updatedAt for records without a start timestamp. A bound execution reported as Degraded/Unknown, or Succeeded without a durable resultUrl, stays Running inside that budget. A later Processing observation never downgrades an already-Running task to Starting. Never terminalize while an active worker lease for the exact claim token has workerClaimExpiresAt in the future. When startAttemptedAt is absent, only the first unresolved transition may advance updatedAt; identical unresolved polls are true no-ops that preserve the budget anchor and ETag. Once the budget has elapsed and no lease is active, fail with ACA_EXECUTION_STATE_UNRESOLVED or RESULT_REFERENCE_MISSING; clear worker claim fields only with that terminal failure.

While the business handler runs, the worker renews workerClaimExpiresAt under the current ETag and exact claim token at an interval below the lease. It stops the heartbeat before success or failure persistence. Synchronous handlers run off the event loop, and any awaitable they return is still awaited. Heartbeat store failures are best-effort: record only the task ID and stable error code or exception class, retry on the next interval, and let the lease expire naturally during a persistent outage. A heartbeat failure must never replace a successful handler result or expose exception payloads, URLs, or secrets. Before persisting worker success or failure, the ETag mutator must still observe the claiming worker's exact non-null workerClaimToken. If a replacement worker has taken over, the stale worker exits successfully after safe claim-lost telemetry; it must not mutate authoritative state or send a callback.

The store itself uses optimistic concurrency (_etag) and rejects stale claims. Cosmos documents are filtered to known TaskRecord names and aliases: unknown future fields are ignored, while malformed known fields and Cosmos failures map to CONTROL_STORE_UNAVAILABLE. ETag replacement preserves non-system unknown fields from the raw point read, excludes id and underscore-prefixed service metadata, then overlays the canonical known record so cleared known fields cannot be resurrected.

A previous worker with an expired lease and no durable output has an unknown effect, not a clean retry opportunity. Return WORKER_EFFECT_UNRESOLVED without invoking its business handler again. Existing output may still be reconciled without running the handler. Lease/ETag fencing prevents competing state writes; it cannot alone deduplicate an external business API after a crash. Independent readback must name the actual reader identity and consistency contract. See the shared custody guide.

Callback contract

Callbacks are minimal and fixed:

{"taskId":"...","acaExecutionId":"...","status":"Succeeded","resultUrl":"https://..."}

The callback payload never carries the business result body. The worker writes the result to blob storage first, then sends only the URL.

Callback auth modes are explicit:

  • managed_identity sends a bearer token for the configured audience.
  • key_vault sends X-Callback-Key using the named secret.

The callback route only trusts the configured callback principal. If an existing callback blob already exists with different bytes, the server raises CALLBACK_PAYLOAD_CONFLICT; identical payloads are idempotent. Delivery retries are bounded and may end in CALLBACK_DELIVERY_EXHAUSTED or CALLBACK_DELIVERY_REJECTED.

Security and least-privilege RBAC

Trust the ACA Easy Auth header path only when MCP_ACA_JOBS_AUTH_MODE is exactly aca-easy-auth.

Allowlists are policy-driven, not caller-driven:

  • jobType
  • callbackAlias
  • inputHosts
  • resultHosts
  • optional allowed_owner_scopes

The app and job use separate UAMIs, even though they share the same image digest. That separation is intentional: one identity reads and updates the control plane; the other executes the job and writes output.

Easy Auth caller allowlisting supports two mutually exclusive modes:

  • client ID mode sets only defaultAuthorizationPolicy.allowedApplications;
  • principal object ID mode sets only defaultAuthorizationPolicy.allowedPrincipals.identities.

Choose exactly one nonempty list. With the 2025-01-01 authConfig API, setting both lists applies both checks with logical AND; it does not broaden access. Use principal object ID mode for hosted agent instance identities. Client ID mode remains available for app-only callers whose token identifies an allowlisted client application.

Do not accept caller-supplied image references, commands, environment overrides, ARM IDs, secrets, large result bodies, callback URLs, or event dispatch hooks.

Deploy with azd

The deployment contract is azd only. Copy the canonical files verbatim; do not fork the template shapes here.

Explicit CI reuse, not the ordinary default. The opt-in templates/infra/ci-reuse.bicep entrypoint is resource-group scoped and composes the existing app, Cosmos and canonical azd-patterns Job modules. It never creates a resource group, identity, database, role definition or role assignment. The ordinary subscription main.bicep and its defaults remain the provisioning consumer.

templates/infra/scripts/ci_reuse.py stages that route only with MCP_ACA_JOBS_CI_REUSE=existing. It requires explicit standing resource IDs for RG, environment, app/worker identities, Cosmos account/database, storage and registry; expected tenant/subscription, executor client/principal, existing audience and exact account endpoints. No default resource discovery, missing-input provisioning or shared grant fallback is allowed. IDs/endpoints are operator inputs, never committed inventory. All standing resources currently must belong to the selected RG; other layouts require a reviewed contract rather than inferred scopes.

Before the first write, authenticated GET/list checks bind those exact resources, UAMI client/principal mappings and active executor context, require the database to exist, and compare endpoints. The executor's existing Cosmos role may be database-scoped reader; app and worker need database-scoped writers. Blob rights must cover the new run containers, and app Job operator rights must cover the run Job. Conditional or insufficient permissions block without grants. The supported registry route is explicitly existing legacy registry-wide access, not repository-only isolation. Read-only ARM checks do not prove network reachability or absence of deny assignments; the approved runner/runtime network path and SecuredByPerimeter posture must already be established. No network setting is changed by this route.

The staged CI main.bicep creates only the run's app, Job, Cosmos control container inside the standing database, and two run-specific blob containers. The image has a new UUID repository with explicit documented docker.registry, docker.image, and docker.tag inputs; the normal SERVICE_MCP_IMAGE_NAME output and canonical convergence helper verify the actual build result. Preprovision/predeploy hooks re-check standing inputs, the active azd target, and the staged Bicep/parameter hashes. Removing the reuse marker while keeping reuse enabled fails closed.

Cleanup is not azd down, RG deletion or database deletion. Retain the private ci-run-owned.json pre-write absence record, ci-created.json actual creation observations and ci-image.json digest receipt. Only matching run objects may be deleted; replaced/unregistered resources, a changed image or extra image tags block deletion. Each supported resource read must verify absence; an auth/network error is not absence. On partial provision, use the read-only record action to reconcile exact created objects, never re-provision to reconstruct evidence. Missing native creation provenance requires owner disposition, not broader cleanup. Standing database/identities/grants/storage/ registry/RG are never cleanup targets. Hosted agents/build images are separate run receipts; verify their supported deletion or obtain explicit bounded retention. This path has local contract coverage; live CI reuse acceptance is required on the integrated source before release. The staged .dockerignore and .azdignore allow only Dockerfile, dependency files and app/; private preflight/ownership captures and .azure are not build/upload inputs. Native creation timestamps and exact image metadata remain required ownership evidence; an API that omits them blocks automatic cleanup for an explicit owner disposition, never a database/RG delete fallback. The fixture's prompt and hosted model calls use one recorded dispatch with SDK retries disabled. A malformed or lost response preserves UNKNOWN and cannot be retried using a new operation journal. The hosted target must be absent before deployment; cleanup deletes only the observed version with matching creation identity, not an entire pre-existing agent. The two new image repositories are checked absent before build, explicitly selected in azd and independently recorded by digest before their targeted cleanup.

skills/foundry-mcp-aca-jobs/templates/infra/main.bicep requires the sibling catalog checkout layout: skills/foundry-mcp-aca-jobs and skills/azd-patterns must coexist under the same catalog root because the composition root imports skills/azd-patterns/references/bicep/aca-job.bicep. Copy or scaffold the template with this exact layout; do not duplicate or rewrite the shared Bicep:

<workdir>/
└── skills/
    ├── foundry-mcp-aca-jobs/
    │   └── templates/
    │       ├── bicepconfig.json
    │       └── infra/main.bicep
    └── azd-patterns/
        └── references/bicep/aca-job.bicep
WORKDIR="<workdir>"
mkdir -p "$WORKDIR/skills/foundry-mcp-aca-jobs" \
  "$WORKDIR/skills/azd-patterns/references/bicep"
cp -R skills/foundry-mcp-aca-jobs/templates "$WORKDIR/skills/foundry-mcp-aca-jobs/"
cp skills/azd-patterns/references/bicep/aca-job.bicep "$WORKDIR/skills/azd-patterns/references/bicep/"
  • templates/azure.yaml
  • templates/bicepconfig.json
  • templates/uv.lock
  • templates/infra/main.bicep
  • templates/infra/main.parameters.json
  • templates/infra/app.bicep
  • templates/infra/cosmos.bicep
  • templates/infra/identity-rbac.bicep
  • templates/infra/identity-rbac/assignments.bicep
  • templates/infra/identity-rbac/job-operator.bicep
  • templates/infra/identity-rbac/uami.bicep
  • templates/infra/scripts/pyproject.toml
  • templates/infra/scripts/uv.lock
  • templates/infra/scripts/converge_image.py
  • templates/infra/scripts/verify_deployment.py
  • templates/pyproject.toml
  • templates/Dockerfile
  • ../azd-patterns/references/bicep/aca-job.bicep

Use azd up or azd deploy to run the converged postdeploy flow. Do not hand roll az deploy sequences or reimplement the digest convergence logic here. Post-deploy hosted-agent callers use principal object ID mode and are allowlisted by the instance identity object ID in defaultAuthorizationPolicy.allowedPrincipals.identities.

For a brownfield platform, keep resourceGroupName as the child resource group that receives the app, Job, and UAMIs, and set platformResourceGroupName to the resource group containing the existing ACR, Container Apps environment, storage account, and optional Cosmos account. The template scopes those existing resources explicitly across resource groups while preserving the single-resource-group default. The named output and callback blob containers are created in the existing storage account; they are not pre-existing containers.

The subscription-scope composition root creates resourceGroupName itself and applies resourceGroupTags only when the object is nonempty. The empty default omits the ARM tags value so an existing resource group's tags are preserved. Do not pre-create that resource group. Its exact azd outputs include MCP_APP_NAME, ACA_JOB_NAME, MCP_ACA_JOBS_STORAGE_ACCOUNT_URL, MCP_ACA_JOBS_STORAGE_ACCOUNT_NAME, MCP_ACA_JOBS_COSMOS_ENDPOINT, MCP_ACA_JOBS_COSMOS_ACCOUNT_NAME, and MCP_ACA_JOBS_COSMOS_USE_EXISTING_ACCOUNT; the last value is the lowercase string true or false. Postdeploy consumes these outputs directly, including the Cosmos module endpoint created by an ordinary greenfield azd up.

Operate and observe

Runtime behavior is bounded and observable:

  • MCP_ACA_JOBS_RECONCILE_INTERVAL_SECONDS
  • MCP_ACA_JOBS_LEASE_MINUTES
  • MCP_ACA_JOBS_POLICY_JSON
  • CONTAINER_APP_JOB_EXECUTION_NAME

Telemetry only emits safe fields:

task.id, job.type, task.state, aca.execution.id, operation, outcome, error.code, and azure.request.id.

The worker persists output before callback delivery, so a callback outage does not erase the result. The server's /health route stays light; operational visibility comes from task status, result URLs, and telemetry.

Stable errors

These codes are user-visible and must stay stable within the 2.x contract:

FamilyStable codes
TASK_*TASK_NOT_FOUND, TASK_FORBIDDEN
INVALID_*INVALID_JOB_TYPE, INVALID_CALLBACK_ALIAS, INVALID_INPUT_REFERENCE, INVALID_RESULT_REFERENCE
IDEMPOTENCY_*IDEMPOTENCY_KEY_REUSED
ARM_*ARM_STATUS_UNAVAILABLE, ARM_START_REJECTED, ARM_STOP_REJECTED
START_*START_RECONCILIATION_EXHAUSTED
ACA_EXECUTION_*ACA_EXECUTION_FAILED, ACA_EXECUTION_STOPPED, ACA_EXECUTION_STATE_UNRESOLVED
RESULT_*RESULT_REFERENCE_MISSING
CALLBACK_*CALLBACK_DELIVERY_REJECTED, CALLBACK_DELIVERY_EXHAUSTED, CALLBACK_PAYLOAD_CONFLICT
CONTROL_STORE_*CONTROL_STORE_UNAVAILABLE
DEPLOYMENT_*DEPLOYMENT_CONTRACT_MISMATCH
WORKER_*WORKER_EXECUTION_FAILED, WORKER_EFFECT_UNRESOLVED
OPERATION_*OPERATION_CUSTODY_REQUIRED

RESULT_REFERENCE_MISSING stays in the failed terminal family, but the MCP projection is still completed with isError: true.

Test the implementation

The implementation is pinned to contract tests, not prose:

  • scripts/tests/test_foundry_mcp_aca_jobs_template.py
  • scripts/tests/test_foundry_mcp_aca_jobs_protocol.py
  • scripts/tests/test_foundry_mcp_aca_jobs_azure.py
  • scripts/tests/test_foundry_mcp_aca_jobs_callbacks.py
  • scripts/tests/test_foundry_mcp_aca_jobs_store.py
  • scripts/tests/test_foundry_mcp_aca_jobs_orchestrator.py
  • scripts/tests/test_foundry_mcp_aca_jobs_worker.py
  • scripts/tests/test_foundry_mcp_aca_jobs_models.py
  • scripts/tests/test_foundry_mcp_aca_jobs_telemetry.py

Pin validation must print exactly:

  • ok fastmcp external tasks adapter
  • ok aca jobs sdk surface
  • ok azure ai projects prompt mcp authorization surface
  • ok foundry-mcp-aca-jobs imports

Non-goals

  • general MCP hosting or producer-side server deployment
  • running business logic directly in the MCP server process
  • Docket-based execution or any other alternate job runtime
  • Service Bus, queues, topics, subscriptions, or event-dispatch orchestration
  • caller-supplied image digests, commands, environment overrides, secrets, or callback URLs
  • large inline result payloads or opaque callback bodies
  • hand-rolled provisioning that bypasses azd
  • sharing one UAMI between the app and the job

Related skills

SkillWhen to use it
foundry-mcp-acaproducer-side MCP server hosting
azd-patternscanonical ACA Job Bicep and digest convergence
foundry-prompt-agentsprompt-agent fallback when tool work needs durable job-backed execution or callbacks
foundry-hosted-agentshosted agents that consume MCP tools
foundry-observabilitytelemetry and log/trace wiring

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.