Repository navigation
feat: added MCP Gateway examples #73
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
5c6bef5
feat: added MCP Gateway examples
aaguiarz 3e667fe
chore: adding experimental flag clarification
aaguiarz f7b72ad
Fix typo in mcp-gateway.fga.yaml comment
aaguiarz 8784410
Fix comments in mcp-gateway.fga.yaml
aaguiarz 8d63fab
chore: applied feedback
aaguiarz 96267ea
chore: clarified experimental feature behavior
aaguiarz fff6686
chore: improved clarity
aaguiarz 14600e4
Update stores/mcp-gateway/mcp-gateway.fga.yaml
aaguiarz 939557f
Update stores/mcp-gateway/mcp-gateway.fga.yaml
aaguiarz 214062d
Update stores/mcp-gateway/mcp-gateway.fga.yaml
aaguiarz 02164c9
Update stores/mcp-gateway/mcp-gateway.fga.yaml
aaguiarz c090bec
Update stores/mcp-gateway/mcp-gateway.fga.yaml
aaguiarz 8452c55
Update stores/mcp-gateway/mcp-gateway.fga.yaml
aaguiarz 2538d5d
chore: simplified readonly scenario
aaguiarz File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| # OpenFGA for MCP Gateways | ||
|
|
||
| ## Use Case | ||
|
|
||
| This folder demonstrates three different models for MCP Gateway use cases: | ||
|
|
||
| * **An employee-facing MCP Gateway:** Control which MCP server tools users can call through a gateway. Permissions are based on user identity and role membership. | ||
| * **A customer-facing multi-tenant MCP Gateway:** Where you write your own agents and want to control what the agents can do when interacting with each tenant's tools. Permissions are based on agent identity. | ||
| * **An intent-based authorization scenario:** An iteration of the previous one showing how to apply the same ideas for authorizing calls based on inferred intent. | ||
|
|
||
| These examples depend on the experimental **Dynamic Conditions** feature, whose syntax, behavior, API, and tuple format may change. The FGA CLI enables inline expressions automatically when running model tests. Testing with an OpenFGA server requires OpenFGA v1.21.0 or later with the experimental feature enabled explicitly: | ||
|
rhamzeh marked this conversation as resolved.
|
||
|
|
||
| ```bash | ||
| openfga run --experimentals inline_expressions | ||
| ``` | ||
|
|
||
| ## Employee Authorization | ||
|
|
||
| **Requirements:** | ||
|
|
||
| * **Gateway-level policies:** Allow definition of policies that are well-known to the gateway developer so they can be defined with static conditions. In the example, we add an IP restriction policy. | ||
| * **Broad tool access:** Allow access to all read-only or read-write tools per MCP server, user, or role. | ||
| * **Specific tool access:** Allow access to specific tools per user or role. | ||
| * **Parameter-based access:** Allow calling tools with specific parameter values per user or role. This is implemented using Dynamic Conditions. | ||
|
|
||
| You can view the [mcp-gateway.fga.yaml](mcp-gateway.fga.yaml) example configuration. | ||
|
|
||
| ## Multi-Tenant Agent Authorization | ||
|
|
||
| **Requirements:** | ||
|
|
||
| * Agents operate on behalf of an organization. | ||
| * Agents can be granted permissions to call tools, including calls with specific parameter values. | ||
|
|
||
| For example, you can configure that the `triage-bot` can only send Slack messages to `#product-announcements` for the organization `Acme`. | ||
|
|
||
| You can view the [multi-tenant-mcp-gateway.fga.yaml](multi-tenant-mcp-gateway.fga.yaml) example configuration. | ||
|
|
||
| ## Intent-Based Authorization | ||
|
|
||
| This builds on the previous example. Organization membership and tool ownership are persisted, but tool-call grants are supplied at request time instead of being persisted. | ||
|
|
||
| **Requirements:** | ||
|
|
||
| * Agents operate on behalf of an organization. | ||
| * Agents begin with no persisted tool-call grants. | ||
| * An intent inference engine (out of scope for this example) will infer which tools the agent should call and with which parameters. This information is sent to OpenFGA as contextual tuples. | ||
|
|
||
| This pattern also applies to intent-based authorization. Permissions can come from an intent-inference engine or a token that grants the agent fine-grained contextual permissions. | ||
|
|
||
| The model test writes its test-local `caller` tuples to a temporary datastore. A production application would instead include those grants as contextual tuples in its authorization check. | ||
|
|
||
| You can view the [multi-tenant-mcp-gateway-intent.fga.yaml](multi-tenant-mcp-gateway-intent.fga.yaml) example configuration. | ||
|
|
||
| ## Try It Out | ||
|
|
||
| 1. Make sure you have the [FGA CLI v0.81 or later](https://github.com/openfga/cli/?tab=readme-ov-file#installation) installed. | ||
|
|
||
| 2. In the `mcp-gateway` directory, run `fga model test --tests <store-file>.fga.yaml` | ||
|
aaguiarz marked this conversation as resolved.
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,158 @@ | ||
| name: MCP Gateway for Workforce use cases | ||
|
rhamzeh marked this conversation as resolved.
|
||
| model: | | ||
| model | ||
| schema 1.1 | ||
|
|
||
| type gateway | ||
| relations | ||
| # Gateway-level policies. | ||
| # If you write a user:* tuple, no gateway-level restrictions are applied | ||
| # This example restricts gateway access by IP using a condition defined in the model | ||
| define allow_policies : [user:*, user:* with in_company_network] | ||
|
|
||
| type user | ||
|
|
||
| type role | ||
| relations | ||
| define assignee : [user] | ||
|
|
||
| type mcp_server | ||
| relations | ||
| # Adds the MCP server to the gateway instance so it inherits its policies | ||
| define gateway : [gateway] | ||
|
|
||
| # Allows assigning access to all read-only or read-write tools | ||
| define readonly_access : [user, role#assignee] | ||
|
rhamzeh marked this conversation as resolved.
|
||
| define readwrite_access : [user, role#assignee] | ||
|
|
||
| define can_call_readonly : (readonly_access or readwrite_access) and passes_gateway_policies | ||
| define can_call_readwrite : readwrite_access and passes_gateway_policies | ||
| define passes_gateway_policies: allow_policies from gateway | ||
|
|
||
| type tool | ||
| relations | ||
| define mcp_server : [mcp_server] | ||
| # Tools without the read-only flag are treated as read-write | ||
| define is_readonly : [user:*] | ||
|
|
||
| define caller : [user, role#assignee, role#assignee with $expression, user with $expression] | ||
|
|
||
| define can_call : (caller and passes_gateway_policies from mcp_server) or (is_readonly and can_call_readonly from mcp_server) or can_call_readwrite from mcp_server | ||
|
|
||
| condition in_company_network(user_ip: ipaddress, cidr: string) { | ||
| user_ip.in_cidr(cidr) | ||
| } | ||
|
|
||
| tuples: | ||
|
|
||
| ### Gateway level tuples | ||
|
|
||
| # If you write this tuple, there won't be any network restriction conditions. | ||
| #- user: user:* | ||
| # relation: allow_policies | ||
| # object: gateway:mcp-gateway | ||
|
|
||
| - user: user:* | ||
| relation: allow_policies | ||
| object: gateway:mcp-gateway | ||
| condition: | ||
| name: in_company_network | ||
| context: | ||
| cidr: '192.168.0.0/24' | ||
|
|
||
| ### MCP configuration | ||
| - user: gateway:mcp-gateway | ||
| relation: gateway | ||
| object: mcp_server:slack | ||
|
|
||
| ## These tuples are created from MCP server tool definitions | ||
|
|
||
| # slack_read_channel has the 'readOnlyHint' hint in the MCP Tool definition | ||
| - user: user:* | ||
| relation: is_readonly | ||
| object: tool:slack_read_channel | ||
|
|
||
| # The Slack MCP server owns the slack_send_message tool | ||
| - user: mcp_server:slack | ||
| relation: mcp_server | ||
| object: tool:slack_send_message | ||
|
|
||
| # The Slack MCP server owns the slack_read_channel tool | ||
| - user: mcp_server:slack | ||
| relation: mcp_server | ||
| object: tool:slack_read_channel | ||
|
|
||
| ### Users/Roles tuples | ||
| - user: user:anne | ||
| relation: assignee | ||
| object: role:devops | ||
|
|
||
| - user: user:bob | ||
| relation: assignee | ||
| object: role:product | ||
|
|
||
| - user: role:devops#assignee | ||
| relation: readwrite_access | ||
| object: mcp_server:slack | ||
|
|
||
| # Assignees to the product role are the only ones that can post in product announcements | ||
| # Note that the developer building the MCP Gateway will need to allow users to enter | ||
| # these kinds of policies | ||
|
|
||
| - user: role:product#assignee | ||
| relation: caller | ||
| object: tool:slack_send_message | ||
| condition: | ||
| name: $expression | ||
| context: | ||
| expression: "channel_name == '#product-announcements'" | ||
| parameters: | ||
| channel_name: string | ||
|
|
||
| tests: | ||
| - name: dynamic-rbac-example | ||
| check: | ||
| # Anne has access as the devops team has write access to all channels | ||
| - user: user:anne | ||
| object: tool:slack_send_message | ||
| context: | ||
| channel_name: "#product-announcements" | ||
| user_ip: 192.168.0.1 | ||
| assertions: | ||
| can_call: true | ||
|
|
||
| # Anne's read-write access also grants access to read-only tools | ||
| - user: user:anne | ||
| object: tool:slack_read_channel | ||
| context: | ||
| user_ip: 192.168.0.1 | ||
| assertions: | ||
| can_call: true | ||
|
|
||
| # Bob has access to this channel as he is assigned to the product role | ||
| - user: user:bob | ||
| object: tool:slack_send_message | ||
| context: | ||
| channel_name: "#product-announcements" | ||
| user_ip: 192.168.0.1 | ||
| assertions: | ||
| can_call: true | ||
|
|
||
| # Bob does not have access when using another IP | ||
|
rhamzeh marked this conversation as resolved.
|
||
| - user: user:bob | ||
| object: tool:slack_send_message | ||
| context: | ||
| channel_name: "#product-announcements" | ||
| user_ip: 200.168.0.1 | ||
| assertions: | ||
| can_call: false | ||
|
|
||
| # Bob does not have access to channels outside the product announcements policy, even when on the correct IP | ||
| - user: user:bob | ||
| object: tool:slack_send_message | ||
| context: | ||
| channel_name: "#general" | ||
| user_ip: 192.168.0.1 | ||
| assertions: | ||
| can_call: false | ||
|
|
||
105 changes: 105 additions & 0 deletions
105
stores/mcp-gateway/multi-tenant-mcp-gateway-intent.fga.yaml
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,105 @@ | ||
| name: Customer-facing multi-tenant MCP Gateway with intent-based authorization | ||
|
rhamzeh marked this conversation as resolved.
|
||
| model: | | ||
| model | ||
| schema 1.1 | ||
|
|
||
| type agent | ||
|
|
||
| type organization | ||
| relations | ||
| define agent : [agent] | ||
|
|
||
| type tool | ||
| relations | ||
| define organization : [organization] | ||
| define caller: [agent, agent with $expression] | ||
| define can_call: caller and agent from organization | ||
|
|
||
| tuples: | ||
| - user: agent:triage-bot | ||
| relation: agent | ||
| object: organization:acme | ||
|
|
||
| - user: agent:assistant | ||
| relation: agent | ||
| object: organization:acme | ||
|
|
||
| # The Slack tools are enabled for the organization | ||
| - user: organization:acme | ||
| relation: organization | ||
| object: tool:acme_slack_send_message | ||
|
|
||
| - user: organization:acme | ||
| relation: organization | ||
| object: tool:acme_slack_read_channel | ||
|
|
||
| - user: organization:acme | ||
| relation: organization | ||
| object: tool:acme_slack_search_channels | ||
|
|
||
| - user: organization:acme | ||
| relation: organization | ||
| object: tool:acme_slack_schedule_message | ||
|
|
||
| # Agents start with zero permissions | ||
|
|
||
| tests: | ||
| # We assume that the intent engine inferred the agent needs to find a specific | ||
| # Slack channel and post a message there. | ||
| # This is sent by the Intent engine as contextual tuples | ||
|
aaguiarz marked this conversation as resolved.
|
||
|
|
||
| - name: test | ||
| tuples: | ||
| - user: agent:triage-bot | ||
|
rhamzeh marked this conversation as resolved.
|
||
| relation: caller | ||
| object: tool:acme_slack_send_message | ||
| condition: | ||
| name: $expression | ||
| context: | ||
| expression: "channel_name == '#product-announcements'" | ||
| parameters: | ||
| channel_name: string | ||
|
|
||
| - user: agent:triage-bot | ||
| relation: caller | ||
| object: tool:acme_slack_search_channels | ||
| condition: | ||
| name: $expression | ||
| context: | ||
| expression: "query == 'product-announcements'" | ||
| parameters: | ||
| query: string | ||
|
|
||
| check: | ||
| - user: agent:triage-bot | ||
| object: tool:acme_slack_search_channels | ||
| context: | ||
| query: "product-announcements" | ||
| assertions: | ||
| can_call: true | ||
|
|
||
| - user: agent:triage-bot | ||
| object: tool:acme_slack_send_message | ||
| context: | ||
| channel_name: "#product-announcements" | ||
| assertions: | ||
| can_call: true | ||
|
|
||
| - user: agent:triage-bot | ||
| object: tool:acme_slack_send_message | ||
| context: | ||
| channel_name: "#general" | ||
| assertions: | ||
| can_call: false | ||
|
|
||
| - user: agent:triage-bot | ||
| object: tool:acme_slack_search_channels | ||
| context: | ||
| query: "general" | ||
| assertions: | ||
| can_call: false | ||
|
|
||
| - user: agent:triage-bot | ||
| object: tool:acme_slack_schedule_message | ||
| assertions: | ||
| can_call: false | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.