Authorizing Unattended Agents: MCP Access When No Human Is at the Keyboard

Most MCP authorization writing assumes a person is present. A user clicks connect, an authorization code flow runs, the server validates a token that carries that user's identity, and every tool call is attributable to them.

That assumption is breaking. Customers now run agents on a schedule, in CI, and in response to webhooks: a nightly reconciliation agent, a triage agent that wakes on an alert, a build-time agent that queries your product and files a ticket. Nobody is at the keyboard. The authorization code flow has nothing to prompt.

The usual reaction is to issue a long-lived API key or a shared service account and move on. That decision is what fails the security review nine months later, when the customer asks who the agent acted for and what it was allowed to touch. This is how to handle unattended access without giving up either answer.

Decide first whether it is unattended at all

Three cases get confused with each other, and they have different answers.

  • Delayed, but user-initiated. A person started the work and the agent finishes it hours later. This is not unattended. It is a long-running task with an offline access token behind it. Use a refresh token bound to that user, keep the task's owner recorded, and re-check authorization when the work resumes.
  • Autonomous, acting for the organisation. A scheduled job that belongs to the customer's tenant, not to any employee. This is a workload, and it needs a workload identity.
  • Autonomous, acting for a person. A standing agent that watches a person's inbox or queue when they are offline. This is the hardest case, and it is delegation, not a workload. Treat it as an explicit standing grant from that user, with its own consent, its own lifetime, and its own revocation path.

Write down which of the three a given client is before choosing a grant type. Most production incidents in this area come from a case-two credential being used to do case-three work.

The grant for a workload

For case two, the OAuth client credentials grant is the correct mechanism. The client authenticates as itself, the authorization server issues a token whose subject is the client, and your MCP server validates it exactly as it validates a user token: issuer, signature, expiry, and audience. RFC 8707 resource indicators still apply, so the token is bound to your server and cannot be replayed against another resource.

Two constraints matter more here than in the user flow.

First, client authentication. A shared secret in an environment variable is the weakest form and should not be the only form for a token that has no human consent behind it. Prefer private key JWT client authentication (RFC 7523) or mutual TLS, and prefer workload identity federation where the customer's platform offers it: the agent presents a platform-issued identity token from its CI system or cloud workload identity, the authorization server trusts that issuer, and no long-lived secret exists anywhere. That is also the answer a reviewer wants to hear when they ask where the credential is stored.

Second, sender constraint. If the token is not bound to the client that requested it, a leaked bearer token is a fully functional autonomous agent. DPoP or mTLS-bound tokens remove that outcome. For unattended clients this is easier than for interactive ones, because the client is a server you control the deployment of.

One identity per tenant per job, never one per vendor

The common shortcut is a single service account shared by every background agent across every customer. It is convenient and it makes cross-tenant leakage a one-bug problem.

Issue a distinct client identity per tenant and per job function. The nightly reconciliation agent for tenant A is not the same client as the triage agent for tenant A, and neither is the same client as anything belonging to tenant B. Then bind the tenant server-side, from the token's claims, and never from a tool argument. The rule that holds for user tokens holds harder here: a tool argument is a suggestion, never an authority.

This also gives the customer something they will eventually want, which is the ability to turn off one agent without turning off all of them.

Scope down, hard

An unattended client has no user to notice it doing something strange, so the scope set is the only real boundary.

Start from the job, not from the tool catalogue. A reconciliation agent reads ledger entries and writes a report; it does not need refund, user-management, or configuration tools. Grant exactly the scopes for the tools that job calls, and enforce the mapping in the server so that a tool outside the grant is rejected before any business logic runs.

Then add the limits that a human would otherwise supply implicitly:

  • A time window, where the job only runs at certain hours and the server rejects calls outside it.
  • A call ceiling per run, so a loop that fails to terminate hits a wall instead of your database.
  • A blast-radius cap on write tools: a maximum number of records per run, and a hard stop on irreversible operations.
  • No access to elicitation or sampling. There is nobody to answer a prompt, and a tool that waits for one will hang.

Irreversible operations deserve a decision of their own. Either exclude them from unattended clients entirely, or require the agent to stage the change and leave approval to a human path. Saying "unattended clients cannot delete" in your documentation is a strong, cheap answer in a review.

Audit when there is no subject

Your audit schema probably assumes a user. Do not invent one.

Record the client identity as the actor, leave the subject field empty or set to a documented sentinel, and add the fields that make an unattended call reconstructable: the job or schedule identifier, the run identifier that ties every call in one execution together, the trigger (cron, webhook, manual replay), and the tenant. Where a human authorised the standing grant, record who consented and when, because that is the question an incident reviewer will ask first.

The test is simple. Given one audit event, can a customer's security analyst tell that no human was present, identify which scheduled job produced it, and find every other call from the same run? If not, the schema is not finished.

Lifetime, rotation, and shutdown

Unattended credentials outlive the people who created them. Plan for that.

Keep access tokens short and mint them per run. Rotate client credentials on a stated schedule and support two valid keys during rotation so a rotation never needs an outage. Expire the grant itself, not just the token: a standing grant with no end date becomes an orphan the moment its owner changes teams. An annual re-consent, or a grant that expires unless renewed, is the mechanism that prevents quiet accumulation.

And give the customer an off switch they can reach. Revoking a client must stop calls quickly, which means your server checks revocation rather than trusting cached introspection results for an hour. State the real window, whatever it is.

What a reviewer will ask

  1. Which clients can call this server without a user present, and who approved each one?
  2. How does the client authenticate, and where does that credential live?
  3. What can an unattended client do that a user cannot, and what can it not do at all?
  4. Show one scheduled run end to end in the audit log.
  5. If we disable that agent right now, when does the last call stop?

A server built as described answers all five with a client register, a scope table, and a sample run.

When you do not need this

If every call to your server is made by a person in a chat client, do not build any of this yet. Adding a client credentials path before a customer needs one only adds a credential to defend.

And if a customer asks for unattended access purely to avoid re-authenticating an interactive agent, the answer is usually refresh tokens, not a workload identity. Fixing the session problem is cheaper, and it keeps every action attributable to a person.

We design these paths as part of a server build, and unattended access is one of the first things our security audits look for in an existing server. If background agents are calling your MCP server today, tell us how they authenticate.