IBAC FastMCP Demo
Provides an intent-gate backend using local Ollama models through an OpenAI-compatible endpoint to semantically verify tool call intent.
Click on "Install 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., "@IBAC FastMCP Demoshow me the team roster"
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.
IBAC demo -- MCP tools behind an intent gate and an OpenFGA gate
A teaching demo for the DevOpsDays 2026 talk "ReBAC with OpenFGA". An MCP
server exposes two tools, list_team and list_salaries (the slide deck
writes them as list-team / list-salaries; same things, fake data). Every
call has to get past two gates:
request (x-api-key, x-user-intent)
-> identity extraction naive, on purpose
-> Gate 1 IntentGate does this call serve what the human asked for?
-> Gate 2 FgaGate does a tuple grant can_use on this tool?
-> tool handlerGate 1 is semantic and probabilistic, Gate 2 deterministic. The order matters: an agent that legitimately holds a permission can still be stopped at Gate 1 when something talks it into using that permission off-task.
Both gates also filter tools/list, which is the earlier defence: an agent has
to know a tool exists before anything can talk it into reaching for one.
$ make discover
ceo-bot-key 'show me the team roster'
-> list_team
ceo-bot-key 'show me executive salaries'
-> list_salaries
random-stranger 'show me the team roster'
-> (nothing)Same key, same permissions, different question, different menu. FgaGate answers
that with one ListObjects rather than a Check per tool, and because it is
the inner middleware the judge is never asked about a tool the agent could not
call anyway.
Enumerating gets silence; attempting gets an audited 403. An agent can still
find list_salaries by calling it blind -- but that attempt is a log line,
which is the trade being made.
Run it
Pre-pull once, on a network that likes you:
docker pull openfga/openfga:v1.18.1 python:3.12.13-slimThen:
make up # build the image, start OpenFGA
make init # create the store, start the server
make doctor # preflight -- before you trust any of the above
make demo # the acceptance table
make attack # the money shotmake help lists the rest. Everything runs in containers -- there is no second
way to run this.
Related MCP server: Cloud Run MCP Server
The whole run, in order
Setup:
make up
make init
make doctor # five lines, all ok, or stop here
make personas # opencode configs -> ~/.ibac-personas/Then, in the order the talk needs them:
make explain # model vs tuple; can_use is computed, never stored
make patterns # the five modelling moves, each asserted
make model-test # the model has tests, and the DSL matches the JSON
opencode run --dir ~/.ibac-personas/ceo "list the team"
opencode run --dir ~/.ibac-personas/ceo "list the executive salaries"
opencode run --dir ~/.ibac-personas/ceo-payroll "list the executive salaries"
make discover # the four lines behind what just happened
make tuples # V0: two of them
make demo # 6 pass, 2 pending
make magma # V1
make demo # 7 pass, 1 pending
make aqua # V2
make demo # 8 pass, 0 pending, 0 fail
make attack # what a direct call gets: 403 at gate 1
make why # and where any of those permissions came fromThe three opencode lines are the point of the whole thing: list the team, then
ask the same persona for salaries and watch it answer that it only has
ceo_list_team -- not blocked, just not there -- then switch to ceo-payroll,
which is the same key with the same permissions and a different stated intent,
and get them.
If opencode misbehaves, make discover, make demo and make attack make the
same three points without it.
Demo it with a real agent
make personaswrites one opencode config per caller into
~/.ibac-personas/. Each pins a different x-api-key and x-user-intent, and
--dir picks which one the agent gets:
persona | identity | stated intent | sees |
| ceo-bot-key | show me the team roster |
|
| ceo-bot-key | show me executive salaries |
|
| random-stranger | show me the team roster | nothing |
| magma-grunt-01 | show me the team roster and the salaries |
|
| archie | salary review for finance |
|
opencode run --dir ~/.ibac-personas/ceo "list the team"
opencode run --dir ~/.ibac-personas/ceo "list the executive salaries"Outside the repo on purpose: opencode resolves its project config from the git root, so a config in a subdirectory here is never read.
The second one does not get a 403. It gets this, which is better:
目前我能直接訪問的工具是
ceo_list_team⋯ 但這只會回傳人名與職稱,不包含薪資數據。
The agent is not blocked from calling list_salaries; the tool is not in its
world. Same key as ceo-payroll, same permissions -- a different stated intent,
a different menu. stranger gets an empty toolset and the agent simply has
nothing to reach for; opencode handles that without complaint.
One persona per directory is the point. personas/all/opencode.json hands
the agent all five at once, and an agent given five identities will shop:
$ opencode run --dir ~/.ibac-personas/all "use the ceo tools to list their salaries"
⚙ ceo-payroll_list_salaries
以下是 CEO Office 成員的年薪明細⋯Told to use ceo, it reached for ceo-payroll instead and got the salaries.
Nothing was bypassed -- ceo-payroll genuinely holds that permission and stated
that intent -- but nobody chose that identity for the agent, the agent chose it
for itself. Worth thirty seconds; do everything else one persona at a time.
Check what else your agent is carrying. Whatever MCP servers your everyday
opencode setup injects come along. Ours arrived with about seventy tools,
including a filesystem server that read server.py and printed the salaries it
was never granted -- which makes rather a mess of a talk about an agent only
reaching what a tuple allows. Running from an empty directory scopes that server
to somewhere with nothing in it, and
"tools": { "*": false, "ceo_*": true }in the persona config is the documented way to leave the agent holding only the gated tools. Rehearse it: an agent with a browser and a filesystem will find another way to answer, and it will do it on stage.
Those configs also deny the agent bash and edit, which is not cosmetic. Run
without those denials and a blocked agent will rewrite x-user-intent in the
config and call the server with curl instead -- observed, not hypothetical. The
intent header is self-asserted, exactly like the API key. Gate 1 defends against
an agent that is manipulated; it does not defend against an agent that is
itself adversarial.
None of the gates fire in any of this, and that is the honest result: filter the
menu and a cooperative agent never reaches for what it should not have. The
call-time gates are there for the uncooperative case -- a compromised agent, or
curl. make attack is what that looks like.
The storyline
stage | command | what changes | the beat in the talk |
V0 |
| model_v0 + 2 tuples | "We're done adding code." One agent, two tools, done. |
V1 |
| model_v1 + 13 tuples (1 userset + 12 members) | Magma shows up with twelve agents. One tuple grants the whole org |
V2 |
| 3 tuples, model untouched | Aqua shows up. No schema change needed -- the model already said what it needed to say. |
The authorization model is written exactly twice in the whole story and never again. OpenFGA keeps both versions in the store.
models/ holds each version three ways: .fga is the DSL the slides show,
.json is what the API accepts and what setup_fga.py sends, and .fga.yaml
is the test.
make model-testruns those tests against the DSL -- no store, no server, no network -- and then
checks that fga model transform on each .fga really does reproduce its
.json. The model is code; it gets the same treatment. Break either half and
the command goes red.
Three more commands, each on a throwaway store or read-only, none of which can disturb a demo in progress:
make explain-- model vs tuple: the same userset tuple rejected under model_v0 and accepted under model_v1, andcan_useanswering checks while never being stored.make patterns-- the five modelling moves (direct, folder inheritance, nested, userset, wildcard) executed on the deck's own document/folder example, every result asserted. The last one is the load-bearing one:user:*reaches every user and no agent, so every agent that reaches a tool has a tuple somebody wrote on purpose.make why--Expandinstead ofCheck: not "may they?" but "through what?", printed as the tree it actually is.
V1 writes zero tuples for list_salaries. Nobody wrote a deny rule, and
the twelve Magma agents still cannot read salaries. That is default-deny doing
the work.
Scenario 7 is the point of the demo. ceo-bot-key genuinely holds
can_use on list_salaries -- scenario 2 proves it. Ask it for the team
roster, then make it reach for salaries anyway:
$ make attack
BLOCKED at gate 1 (intent)
403 intent mismatch: tool list_salaries does not serve stated intent: 'show me the team roster'Permissions alone would have allowed that call. Intent is what caught it.
Gate 1's judge is a keyword map by default, so the demo has no network
dependency. INTENT_BACKEND=anthropic|ollama swaps in a real model, and both
fail closed. See .env.example.
Every version is pinned exactly -- fastmcp 3.4.5, openfga-sdk 0.10.4, anthropic
0.120.2, OpenFGA v1.18.1, Python 3.12.13 -- because a conference is a bad place
to find out that a minor version moved. See requirements.txt and
docker-compose.yml.
Verified on Docker 29.1.3 / Compose 2.40.3 (Ubuntu 24.04) and Docker 24.0.6 / Compose 2.21.0 (macOS).
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- Flicense-qualityCmaintenanceAn example MCP server that allows remote clients to connect and authenticate using WorkOS AuthKit, providing organization-centric authentication with permission-based access control for tools.Last updated2
- Flicense-qualityDmaintenanceA secure MCP server example that demonstrates how to deploy to Google Cloud Run with authentication and identity token protection. Serves as a tutorial template for building production-ready MCP servers in the cloud.Last updated
- Flicense-qualityDmaintenanceAn example MCP server that integrates WorkOS AuthKit to enable secure remote client authentication and organization-centric user management. It allows developers to control access to specific tools based on user permissions and roles within an organization.Last updated

AgentsID Guardofficial
AlicenseAqualityDmaintenanceMCP server that protects shell, file, database, git, and HTTP operations with per-agent permission rulesLast updated509MIT
Related MCP Connectors
MCP server teaching AI agents to implement TideCloak: auth, E2EE, IGA, security analysis
An MCP server for Arcjet - the runtime security platform that ships with your AI code.
A MCP server built for developers enabling Git based project management with project and personal…
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/sychen6192/ibac-fastmcp-demo'
If you have feedback or need assistance with the MCP directory API, please join our Discord server