Skip to content

Automation  ·  AI operations

How to build MCP server access control

Most MCP servers answer anyone who has the address and a token, and the token belongs to whoever set the server up. Here is how access to a managed MCP server should work, the prompts that build it, and what it takes to keep it right.

Built with Tray Headless

  1. System Okta
  2. Step Read group membership
  3. Step Match groups to servers
  4. Step Check personal sign-in
  5. System MCP server
Also Access request

Access to each server comes from identity provider groups, so joining or leaving a group is the only change anybody has to make.

The short answer

What is MCP server access control?

MCP server access control has four parts: each server is open to named groups from the identity provider instead of to individuals, every caller signs in with their own Tray account, calls are capped by the gateway's rate limiting and, where a team needs it, by a per-person check in the tool workflow, and access closes the same day someone leaves a group. The mistake that causes most exposure is granting access to people one by one. Nobody removes those grants, so a server built for one team ends up answering a contractor who left in the spring.

Stage 2 of 8: Control who can connect. Part of AI and MCP governance, end to end : every stage, the systems it runs on and the guide that builds it.

What matters here

  • Grant access to groups, never to individuals. A group is the only thing that already changes when someone moves team or leaves.
  • Make every caller sign in as themselves. A shared token turns every call into nobody's call.
  • Rely on the gateway's rate limiting, and if one looping agent must not use up the limit for everyone, count calls per person inside the tool workflow.
  • Close access from the identity provider, the same day. A separate list of MCP users is a list nobody updates.
  • Every server needs a named owner before it goes live. Access requests for a server with no owner wait forever.

Who this is for

You run IT, security or AI operations. MCP servers are moving onto a managed path, and the question now is who should be able to reach each one.

How it works in practice

What happens between someone being added to a group and their assistant being able to call a tool.

  1. 1

    Each server is mapped to groups

    The sales tools server to the sales group, the service desk server to IT, written down in one table an owner can read.

  2. 2

    Group membership is read on a schedule and on change

    From the identity provider, so joiners get access the day they start and leavers lose it the day they go.

  3. 3

    Every caller signs in with their own Tray account

    Only people with an active Tray account in the organisation can call tools, so each call belongs to a named person, never to a shared token.

  4. 4

    Calls are capped

    The gateway's rate limiting caps calls to the server. Where a team needs a cap per person, the tool workflow counts that person's calls and refuses past it, so a looping agent slows one person down instead of the whole team.

  5. 5

    Requests for access go to the owner

    Through the normal access request flow, with the server, the group and the reason attached.

  6. 6

    Changes are logged

    Who was given access, by which group, and when it ended, so an access review is a report instead of a hunt.

What MCP server access control is made of

Four parts. The first one decides whether the other three stay true after six months.

Group-based access

Servers open to identity provider groups, never to named people. Access follows the person's job without anyone remembering to change it.

Personal sign-in

Every caller signs in with their own Tray account, and only people with an active account in the organisation can call tools. No shared tokens, so every call has a name on it.

Call limits

The gateway's rate limiting caps calls to each server. For a cap per person, the tool workflow counts calls by the signed-in person and refuses past it, with a lower cap on tools that change records.

Same-day removal

Leaving a group closes access on the next sync, and offboarding triggers a sync straight away instead of waiting for the schedule.

The Tray Headless prompts

Paste these into Claude Code or Codex with the Tray Headless plugin installed. Each stage runs on its own. The systems named in them are the worked example rather than a requirement, and every prompt says so.

Once per project, run /tray-workflows:set-workspace to pick the workspace these build in. Point it at a sandbox first.

  1. 1

    Set up and map servers to groups

    The mapping is the policy, so it has to be readable by an owner.

    Headless skills build-workflow

    Use build-workflow. The systems in play are Okta, ServiceNow, Slack
    and Snowflake, or whatever we run in those seats. Before you plan
    anything, tell me which of them are already authenticated in the
    workspace, because I do not want a connector stubbed that I have not
    authenticated.
    
    Build a mapping table with one row per managed MCP server: the server,
    its owner, the identity provider groups that may use it, and whether
    its tools only read or can also change records.
    
    Refuse to publish a server that has no owner or no group. A server
    with no owner never gets reviewed, and a server with no group ends up
    granted to people one at a time.

    Keep the table somewhere the owners can read it. If only the platform team can see who has access, only the platform team will ever check.

  2. 2

    Read group membership and apply it

    Access has to follow the person's job without anyone remembering.

    Headless skills build-workflow

    Use build-workflow. Read group membership from Okta on a schedule, and
    also whenever Okta reports a change to one of the mapped groups.
    
    For each mapped server, work out who should have access, compare it
    with who has access now, and apply the difference in Tray.ai: add the
    people who joined the group and remove the people who left.
    
    Never add a person directly. If someone needs access and is not in the
    group, the answer is to add them to the group through an access
    request, so the grant shows up in the same place as every other one.
  3. 3

    Make every call belong to a person

    A shared token makes every call nobody's call.

    Headless skills tray-patterns

    Set every managed server to use personal sign-in, so each caller
    signs in with their own Tray account and anyone without an active
    account in the organisation is refused. Do not set up a shared token
    for a team or an agent.
    
    Record the signed-in person on every call, because "who made this
    call" is the first question in every review.
  4. 4

    Cap calls per person in the workflow

    One looping agent should not use up the gateway's limit for everyone.

    Headless skills tray-gotchas tray-patterns

    Use tray-gotchas. The gateway's own rate limiting caps calls to each
    server. On top of that, add a check at the start of each tool workflow
    that counts calls by the signed-in person in the last hour and refuses
    the call past a cap, with a lower cap on tools that change records
    than on tools that only read.
    
    When a person hits the cap, return a message that says so and when
    it resets, so the agent stops instead of retrying. Send the owner a
    Slack message if the same person hits the cap three times in a day,
    because that is usually an agent stuck in a loop.
  5. 5

    Close access the day someone leaves

    A separate list of MCP users is a list nobody updates.

    Headless skills build-workflow

    Use build-workflow. When Okta deactivates a user or ServiceNow closes
    an offboarding ticket, run the sync for that person straight away
    rather than waiting for the schedule, and remove them from every
    managed server.
    
    Revoke any personal tokens they created for MCP at the same time, and
    log what was removed. Check the next morning that nothing still
    answers for them, and alert security if anything does.
  6. 6

    Check it end to end, then hand the table to the owners

    Because who can reach a server is the owner's decision, not the builder's.

    Run the per-step schema checks and the whole-workflow audit before this
    touches production. Test with a person who is in no mapped group and
    confirm they are refused, and with a leaver and confirm access closes
    the same day.
    
    Then open the same workflow in Tray Build so server owners and
    security can change group mappings and per-person caps in the visual
    canvas, without asking the person who wrote it.

What it connects to

Access comes from the identity provider and lands on the server. Requests and changes go where IT already works.

Okta

Read group membership and user status, which is the only source of who should reach each server.

Reads

Azure Active Directory

The same group source for teams on Microsoft identity, read the same way.

Reads

ServiceNow

Take access requests for a server to its owner, and trigger removal when an offboarding ticket closes.

Reads and writes

Slack

Tell owners about new requests and people who keep hitting their cap.

Writes

Snowflake

Keep the history of who had access, through which group, from when to when, for access reviews.

Writes

Same build, other stacks

The design does not change if you run something else in one of these seats. The same prompts build it against Google BigQuery, Microsoft Teams, Jira, Databricks, Google Chat or Jira Service Desk.

Connections in this build

Field mapping, templates and common problems for each pairing: Okta + ServiceNow, Azure Active Directory + ServiceNow, Okta + Slack and Azure Active Directory + Slack.

Named systems are the ones most teams run, not the only ones that work. Each is an authentication in your Tray workspace, referenced by name, so the workflow never holds a credential. Where we have a connector page, the name links to it.

Running it in production

This decides who can point an AI agent at company systems. It has to stay right after the people who built it move on.

It runs on the platform, not on your laptop

Group changes arrive at any hour, and the sync runs on the same engine as everything else, with retries and a full history.

The identity provider stays the source

Nobody edits access on the server directly. If it is not in a group, it is not access.

Credentials live in the workspace, never in the repo

The identity provider and service desk connections are separate authentications with the narrowest scope that reads groups and tickets.

Owners own their servers

Group mappings and per-person caps open in Tray Build, so the person accountable for a server can change who reaches it.

Test the leaver first

Before production, deactivate a test user and confirm every server stops answering for them the same day. That is the case that matters most.

Questions people ask

Why grant access to groups and not to people?

Because groups already change when someone moves team or leaves. Grants to individuals are never removed, so access drifts further from people's jobs every month.

Why must every caller sign in as themselves?

Because a shared token hides who made a call. With personal sign-in, every caller needs an active Tray account in the organisation, and every call in the log has a name on it.

Can calls be limited per person?

The gateway applies its own rate limiting. If a team needs a cap per person, the tool workflow counts calls by the signed-in person and refuses past the cap, so one looping agent slows down one person instead of the whole team.

How is this different from scoping an agent's tools?

Scoping decides which tools an agent gets for its job. Server access control decides which people can reach a server at all. Most teams need both.

What should happen when someone leaves?

Their access to every managed server closes the same day, from the identity provider, with any personal tokens revoked and a check the next morning that nothing still answers for them.

Further reading

Background on the same subject, for the case rather than the build.

Last reviewed October 2026.