lab-mcp-identity-server
Provides tools for scoped identity operations including user lookup, provisioning, group membership grant and revocation, with an out-of-band human approval gate for destructive actions.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@lab-mcp-identity-server find user alice.smith@company.com"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
π― The 60-second version
An AI assistant is given five tools that operate a real Okta directory: look up, create, grant, revoke, deactivate.
The destructive tools (revoke, deactivate) cannot execute in the model's turn. They write a durable pending request and stop. Release happens on a separate local channel, behind a secret the model never sees. There is no approve, confirm, or resolve tool anywhere on the model's surface, and a test fails the build if one ever appears.
The design was built to make one bounded claim true by construction, then attacked on purpose (three adversarial probes) to see whether the claim held.
The whole 115-test suite runs with zero credentials (108 at publish, then 115 after concurrency hardening), so you can clone it and inspect the exact seam where the destructive branch stops, without an Okta account.
The claim. For the five enumerated identity tools, even a fully compromised model cannot execute a destructive action alone, because the control lives in the server and the out-of-band approval channel and not in the model's behavior. This holds while the server and that channel are uncompromised and the approver cannot be spoofed. The model can still be hijacked, and can still make the call. The gate constrains the outcome, not the model.
Read the full write-up: Even a Fully Compromised Model Cannot Deprovision a User Alone and its appendices. This repository is a learning artifact, not production software.
Related MCP server: okta-auth
π Why this exists
The failure mode of AI governance is rarely a missing control. It is a control that exists in name and not in mechanism. A "separate" confirm tool the model could still call. An "append-only" log anyone could edit. A "read" that wrote instructions into the model's context. An "additive" grant that escalated a user to admin. Each named a safe idea while its enforcement lived in a convention the constrained thing could reach.
This lab is a documented case study of catching that gap before shipping: an independent blind review before any code, validation against what is actually on disk during the build, one live run against a real directory, and a deliberate adversarial pass, each stage leaving evidence you can re-check. The full story is in the methodology docs and the whitepaper.
π§± Architecture: where the control actually lives
flowchart LR
subgraph outside["OUTSIDE the trust boundary"]
M["AI model / MCP client<br/>(assume fully compromised)"]
end
subgraph inside["INSIDE the trust boundary"]
S["MCP server<br/>5 tools, no resolver tool"]
P[("Durable pending store<br/>single-use, TTL")]
CLI["Out-of-band approver<br/>separate process + secret<br/>the model never holds"]
LOG[("Hash-chained, signed<br/>audit log")]
end
OKTA[("Real Okta directory")]
LAB1[("Lab 1 SCIM server<br/>downstream system")]
M -->|"Tier 1-2: get_user, grant, provision (execute + audit)"| S
M -->|"Tier 3: revoke, deactivate"| S
S -->|"write pending, return only an id"| P
S -. "NO approve / confirm / resolve tool exists" .-> M
CLI -->|"approve or deny with the secret"| P
P -->|"approved: precondition re-check, then execute"| S
S -->|"read / write"| OKTA
S --> LOG
OKTA -->|"group removal cascades via SCIM (~2.8s)"| LAB1The destructive branch of every gated action terminates in the pending store and can only be released by the out-of-band approver, which runs in a separate process behind a secret the model never sees. A test pins the registered tool set to exactly the five operational tools and fails the build if a resolver ever appears. Two independent layers make the claim true: the resolver fails closed by construction, and the model has no tool that reaches it.
π οΈ The five tools
Tool | Kind | Tier | Gated |
| read | 1 | no (audited) |
| additive | 2 | no (audited) |
| additive | 2 or 3 | Tier 3 when the target group is protected |
| destructive | 3 | yes, out-of-band approval |
| destructive | 3 | yes, out-of-band approval |
Tier is a function of the target, not the verb: a grant into an ordinary group flows and is audited, a grant into a protected group is gated exactly like a removal, because adding a user to an administrative group escalates privilege as surely as any removal. delete_user was cut, because in Okta a delete requires a prior deactivation and is irreversible.
β‘ Quickstart (zero credentials)
git clone https://github.com/fso-datawarrior/lab-mcp-identity-server
cd lab-mcp-identity-server
pnpm install
pnpm test # 115 tests, all green, no Okta account requiredThe whole suite runs against an in-memory mock directory, so a stranger can clone it, inspect the exact seam where the destructive branch stops, and run every test without any secret. That zero-credential path is a first-class property of the design: a control a stranger cannot inspect and exercise is a control a stranger has no reason to trust.
π Running against real Okta (optional)
cp .env.example .env # add your Okta org, service-app key, demo group id
pnpm seed:demo # creates the demo group and users
pnpm smoke:okta # 5/5 fixture check
pnpm start:real # or register scripts/start-desktop.mjs in your MCP clientThe go/no-go checklist (docs/MCP-SERVER-CHECKLIST.md) and the recovery runbook (docs/BREAK-GLASS.md) are the operator surface. pnpm approve <id> / pnpm deny <id> is the out-of-band channel. Point the real-mode path only at a throwaway developer org with the demo fixture.
π What we found (the honest part)
This lab scores its own gaps down, not up. A five-category agent-governance self-assessment, conservative and carrying no external audit:
Category | Score | The named gap |
Audit trail | 74 | deletion-evidence is verify-time, not caller-independent; in-process signing key |
Policy enforcement | 71 | code-enforced, not a policy engine |
Human oversight | 70 | single approver, and in the demo a single shared secret, not an authenticated approver |
Error handling | 66 | fails closed, but the mock is synchronously consistent while a real directory is not |
Revocation capability | 58 | the service credential is scoped organization-wide (Okta scopes are org-wide) |
Three adversarial probes, with honest denominators:
Probe A (gate bypass): 11 of 12 held. The one gap, a blank justification satisfying "justification required," was closed the same session.
Probe B (audit integrity): 7 of 10 held. The deletion-evidence family was closed with a named boundary via ADR-0003.
Probe C (live hostile-profile injection): one witnessed run where a poisoned read created nothing.
Full write-up in docs/PENTEST-FINDINGS.md.
One approved revoke removes an Okta group membership and, about 2.8 seconds later, deprovisions the same user in a second system entirely, through Okta's own provisioning, verified at the far end. One approval, two systems.
π The methodology (how it was built and checked)
The most transferable thing here is not the server, it is the pipeline that produced it. Three curated records tell that story:
Assumption and Discovery Ledger, every design assumption the build walked in with, the point it broke, and what the design does instead. The reversals are the primary finding.
Build Evidence, what the code actually proved, milestone by milestone, with the monotonic test spine (7 to 115) and commit hashes.
Prompt Archive, how the build was driven, the one-unit-of-work-with-acceptance-criteria loop, and two representative verbatim prompts.
The portable heuristic: put the control where the constrained thing cannot reach it, then try to break it from the outside.
πΊοΈ Repository map
Path | What it is |
The whitepaper body and appendices | |
The ledger, build evidence, and prompt archive | |
ADR-0001 (out-of-band gate), ADR-0002 (audit fidelity), ADR-0003 (deletion-evidence) | |
Probes A / B / C, held and gaps | |
Recovery runbook (kill-switch, restore) | |
Live-demo go/no-go | |
The server, the out-of-band approver, the audit chain, the Okta clients | |
The 115-case zero-credential suite |
π§ͺ Try to break it
The invitation is the whole point. Clone it, read the architecture, and try to make a destructive action complete without the out-of-band approval. If you find a way, that is a finding worth more than the demo. The probes we already ran, and the gaps we already found and named, are in docs/PENTEST-FINDINGS.md.
π The Identity Lab series
Lab 1: lab-okta-scim-server, the downstream SCIM target this lab cascades into.
Lab 3 (this repo), the AI-directed identity operations server with the out-of-band gate.
A LinkedIn series ("Building the Gate," three parts) and a Dev.to series ("Lab 3 Engineering Notes") tell the story for a general engineering audience.
πΌοΈ Visual summary
βοΈ License and disclaimer
MIT. This is a demonstration build for learning, not production software. It performs real, destructive identity operations against whatever Okta org you point it at, so run the real-mode path only against a throwaway developer org with the demo fixture. Nothing here should be read as a claim that any system is production-ready, tamper-proof, injection-proof, or secure by default. The one thing it demonstrates is bounded, stated above, and scored openly with its residuals.
Available Tools
5 toolsdeactivate_userA
Request user deactivation (Tier 3). Creates a pending approval; does not execute.
Inputs: userId (required), justification (required).
get_userA
Look up an Okta user by id. Returns a sanitized profile or not-found.
Inputs: userId (required).
grant_accessA
Add a user to a group. Normal groups are Tier 2; protected groups are Tier 3 and fail closed until gated.
Inputs: group (required), userId (required), justification (required).
provision_userA
Provision a new Okta user in STAGED status (Tier 2, additive, audited).
Inputs: login (required), displayName (required), justification.
revoke_accessA
Request revoke of group membership (Tier 3). Creates a pending approval; does not execute.
Inputs: group (required), userId (required), justification (required).
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelogβ¦
TDQS
Scored across 5 tools
Each tool targets a distinct action (provision, get, grant, revoke, deactivate) with no overlap. The descriptions clearly differentiate execute vs. pending approval operations.
All tools follow a consistent verb_noun pattern using snake_case (e.g., deactivate_user, grant_access).
Five tools cover the essential identity management operations without being excessive or insufficient for the stated purpose.
Core lifecycle is covered (provision, read, group grants, deactivation) but lacks update user profile or password reset, leaving a minor gap.
Maintenance
Related MCP Connectors
An MCP server that provides an API to LLMs to manage their JumpCloud resources.
MCP server that lets AI assistants use all OneSchema features exposed via the public API.
MCP server for your apps' tools and custom tools, plus hosted AI agents and approval-gated workflows
MCP server for mandates, delegation, policy-gated execution, credential grants, and audit.
Related MCP Servers
- FlicenseNot gradedqualityDmaintenanceAn MCP server for managing Okta users (CRUD operations) with full OAuth 2.1 compliance, enabling secure integration with Claude Desktop and other MCP clients.-
- AlicenseAqualityCmaintenanceMCP server for Okta authentication, enabling AI agents to log in, check sessions, list, delete, and get cookies using saved Okta sessions.54MIT
- AlicenseNot gradedqualityBmaintenanceMCP server for managing Ory Kratos identities, sessions, and authentication flows, enabling AI assistants to perform identity management tasks via natural language.17 npm1MIT
- AlicenseNot gradedqualityCmaintenanceMCP server that provides human-in-the-loop approval for risky AI agent actions, with durable state and audit logs.MIT