Running an MCP server with SSO for a whole company
An MCP server with SSO answers who is calling. It does not answer what they may reach, and the gap between those two questions is where most rollouts get stuck.
What an MCP server with SSO actually settles
Single sign-on answers one question well: who is making this call. That matters for a Model Context Protocol endpoint because a single address serves the whole company. For an endpoint that any employee can point a client at, that question has to be answered before anything else, so getting it right first is the correct order.
Elaichi supports enterprise SAML and OIDC, both built in-house rather than through a third-party auth vendor, plus SCIM v2 for users and groups. Domains are verified with a DNS TXT record. Alongside the enterprise path there is sign-in with Google, GitHub and Microsoft, email codes, TOTP multi-factor with single-use recovery codes, and passkeys.
Onboarding runs four ways, and which one you pick changes how much of the rest is automatic. Emailed single-use invite links can carry roles and teams already assigned. Verified-domain auto-join admits anybody with a company address and gives them a configurable default role. SCIM provisions from the directory. Just-in-time SSO creates the account at first sign-in.
All four land a person in the organization. None of them decides what that person's agent may do, and that is the part worth spending the rest of a rollout on.
Identity is not authorization
The distinction sounds pedantic until an endpoint is live. Authentication establishes a subject. Authorization decides what that subject may reach. An MCP endpoint that has the first and not the second hands every authenticated employee the full reach of every connected account.
Three layers sit behind sign-in, and keeping them distinct is the single most common way a design goes wrong.
Permissions are role-based: roughly 38 action strings such as tool:execute,
restriction:manage, audit:view and connector:create, grouped into roles.
The system roles form a strict subset chain, Guest inside Member inside
Team Admin inside People Admin inside Org Admin inside Org Owner, each
tier built by spreading the one below so the subset relation holds structurally
rather than by convention. Billing Admin and Auditor sit off that chain.
Resource sharing is a second, separate primitive: a grant of view, use or
edit on a resource to a user, a team, or the whole organization. A member sees
only what they own or what was explicitly shared with them, and no org-level
permission silently widens a listing. That includes org owners and admins.
Restrictions are the third layer, and they are the one that maps most directly onto what people mean when they say governance: which connectors and which individual tools a target may reach.
How restriction rules resolve
Targets are role or user only. There is no organization target, and that is
a deliberate design rather than an omission. The organization default is the
absence of any rule, which means allow-all, so the way to tighten the default is
to write rules against roles rather than to configure an org-wide setting that
does not exist.
Precedence runs user override, then role rule, then org default. A user-targeted rule replaces role rules entirely rather than layering on top of them, which is worth internalizing before writing one: giving a single person a narrow exception discards every role rule that would otherwise have applied to them.
Within the winning layer, allow rules union, block rules union, and blocks always beat allows.
The behavior most likely to surprise you is the allowlist stage. It engages on the presence of an allow rule, not on its contents. An allow rule naming nothing therefore denies everything. That is the strictest rule expressible in the system, and it is reachable by accident, so it is worth knowing before you meet it in production.
Enforcement happens at four points against the same resolver, not once at the door: browse, connect, advertise and execute, plus a final check on the fully-substituted outbound URL.
What the endpoint checks on every call
tool:execute gates the whole endpoint ahead of every narrower scope. Without
it, tools/list comes back empty and a call returns an in-band error naming the
permission that was missing. Guest, Auditor and Billing Admin do not have it,
which is what makes those seats safe to hand out freely.
OAuth scopes form a second ladder: mcp:read, mcp:write, mcp:destructive
and mcp:tools. A tool classified forbidden is reachable under no scope at
all. mcp:tools does not replace the ladder, so a connected tool whose method
is a delete needs mcp:destructive as well as mcp:tools.
There is one address for every client in the organization: POST /mcp, standard
MCP over Streamable HTTP, JSON-RPC 2.0, stateless, behind OAuth. Everyone points
at the same endpoint and signs in. There is no per-user server to mint, list or
revoke, because the endpoint is constant and the grant is what varies.
Propagation has a number, and it is not zero
When you change what a role may reach, the change is not instant, and planning around the real figure is better than assuming the optimistic one.
Role membership and restrictions resolve through a 60-second cache plus edge propagation, so a change takes effect within about two minutes, on every surface alike: the endpoint, the console and the REST interface.
Grant revocation is different and faster. revoked_at is re-read from the
organization store on every single call with no cache, and removing or
suspending a member revokes every live grant in the same transaction as the
membership change. For those, the next call really is the boundary.
Two mechanisms, two timings. Quoting the wrong one during an incident is how a team concludes that a control did not work when it simply had not landed yet.
The discovery threshold changes what the model sees
Past a threshold of 30 tools, connected tools collapse behind two meta-tools,
search_tools and execute_tool. The threshold counts catalog operations and
connected tools together, and the control-plane catalog alone runs to dozens of
operations, so one connected app is normally enough to trip it. Collapse is the
normal case rather than an edge case, and a rollout plan that assumes a flat
tool list will be wrong on day one.
Only the connected half collapses. Control-plane operations stay listed
individually, and search_tools never returns one. Tools withheld by a
restriction are excluded from the count, because they were handed to nobody.
execute_tool is only a naming indirection. It unwraps to the same name and the
same arguments and falls through the identical gates. There is no separate
execution path in it and no privilege attached to it, which is the property that
makes the collapse safe rather than merely convenient.
One limitation to state before you deploy
An MCP server never sees a user prompt. Prompt-injection defenses that work in an agent window therefore cannot apply at the endpoint, and Elaichi's do not.
What does hold on POST /mcp: role-based access per operation, the forbidden
classification, output redaction, OAuth scope limits and full audit logging. Each
call writes one entry, succeeded or failed, naming the account actually reached.
Argument names and counts are recorded; argument values never are.
That is a real set of controls and it is worth deploying. It is not the same thing as a guarantee about model behavior, and a rollout that is sold internally as the latter will lose credibility the first time a model does something odd within its permitted scope.
If you are still deciding whether to centralize at all, the tradeoff is set out in the case for and against self-hosting. For a worked example, read how one team gets governed access. See what the catalog covers or how the controls are described for reviewers.