Python: Sample for using WorkIQ MCP server using a gateway for labelling and IFC policy evaluation #6860
Conversation
…ecurity, and change context label only using the labels of unhidden result from tools
…nto pr/fides-mcp-autolabel-clean
…nto pr/fides-mcp-autolabel-clean
… instead of /insiders - Switch MCP_URL from /mcp/insiders to /mcp/ in github_mcp_example.py - Add MCP_HEADERS constant with X-MCP-Features: ifc_labels to opt-in to server-side IFC label emission in _meta payloads - Fix SecureMCPToolProxy to pass headers via httpx.AsyncClient so they are included on session.initialize(), not just on tool calls (was causing 401 to silently surface as anyio cancel-scope CancelledError) - Update README, FIDES_DEVELOPER_GUIDE, FIDES_IMPLEMENTATION_SUMMARY, and 0024-prompt-injection-defense.md to remove all /insiders references
…ntConfig quarantine client global behavior
…nto pr/fides-mcp-autolabel-clean
1. Can connect to workiq via gateway
2. Can read the top-level label specified under "$", {"ifc": {"$" : {...}}}
3. ConfidentialityLabel supports readers list. Context aggreation happens correctly on readers (only checked for basic-ifc).
TODO:
1. Add support for policy enforcement on the gateway.
2. Add / Confirm that FIDES works with the readers
…nto add/gateway-integration-for-workiq
There was a problem hiding this comment.
Pull request overview
This PR extends the Python FIDES security layer to support a “PRIVATE with readers” confidentiality lattice and adds gateway-driven policy enforcement for MCP tools (via a Fides Gateway eval_policy call). It also introduces a runnable WorkIQ email/teams sample and updates the security sample README and core security tests accordingly.
Changes:
- Add
ConfidentialityLabelreader restrictions (list/frozenset readers) and update label combine/serialization and policy checks accordingly. - Add optional gateway policy enforcement (
SecureMCPToolProxy(gateway_policy=True)) that attaches a per-tool_gateway_policy_fnand routes enforcement througheval_policy. - Add a WorkIQ gateway sample script plus README entry and update unit tests for the new confidentiality semantics.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 6 comments.
| File | Description |
|---|---|
| python/samples/02-agents/security/workiq-email-example.py | New runnable sample demonstrating WorkIQ MCP usage via a local Fides Gateway plus approval flow handling. |
| python/samples/02-agents/security/README.md | Documents the new WorkIQ gateway sample, setup steps, and expected behavior. |
| python/packages/core/tests/test_security.py | Updates unit tests to reflect the new “PRIVATE with readers” confidentiality model. |
| python/packages/core/agent_framework/security.py | Implements reader-aware confidentiality labels and gateway-driven policy enforcement hooks for MCP tools. |
| PUBLIC: ConfidentialityLabel # set after class body | ||
| PRIVATE: ConfidentialityLabel # set after class body | ||
|
|
||
| def __init__(self, value: str | list[str] = "public", *, readers: frozenset[str] | None = None) -> None: |
There was a problem hiding this comment.
The literal will tell users that they made a mistake (misspelling or a non-list name) and the Sequence is to allow covariant typing.
| def __init__(self, value: str | list[str] = "public", *, readers: frozenset[str] | None = None) -> None: | |
| def __init__(self, value: Literal['public' | 'private'] | Sequence[str] = "public", *, readers: frozenset[str] | None = None) -> None: |
| ValueError: If ``value`` is not ``"public"``, ``"private"``, or a list. | ||
| """ | ||
| if isinstance(value, list): | ||
| self._level = "private" |
There was a problem hiding this comment.
| self._level = "private" | |
| self._level: Literal['public' | 'private'] = "private" |
| client=main_client, | ||
| name="WorkIQSecureAgent", | ||
| instructions=AGENT_INSTRUCTIONS, | ||
| tools=all_tools, |
There was a problem hiding this comment.
it is not necessary to unpack the tools list for both, you can just pass the two MCP servers in:
| tools=all_tools, | |
| tools=[secure_mcp_1, secure_mcp_2], |
| if mode == "cli": | ||
| asyncio.run(run_cli_async(debug=debug, gateway_port=gateway_port, auto_approve=auto_approve)) | ||
| else: | ||
| asyncio.run(run_devui_async(debug=debug, gateway_port=gateway_port)) |
There was a problem hiding this comment.
again, this complicates showing the exact setup for this feature, and adds a lot of overhead on the because of the two interfaces, remember that samples are not for dev purposes, it is to show how something works, less is more!
| print(f"[auto-approve] gateway policy flagged '{tool_name}' -> approving") | ||
| else: | ||
| prompt = f"Approve tool '{tool_name}' flagged by gateway policy? [y/N] " | ||
| answer = (await asyncio.to_thread(input, prompt)).strip().lower() |
There was a problem hiding this comment.
this is also not needed for a sample, just use answer = input(prompt)
| ) | ||
|
|
||
|
|
||
| async def run_cli_async(*, debug: bool = False, gateway_port: int = 9090, auto_approve: bool = False) -> None: |
There was a problem hiding this comment.
wiring the auto-approval should a rule in the ToolApprovalMiddleware, that makes the other code much simpler as well
|
|
||
| @experimental(feature_id=ExperimentalFeature.FIDES) | ||
| class ConfidentialityLabel(str, Enum): | ||
| class ConfidentialityLabel: |
There was a problem hiding this comment.
I'm never a big fan of this kind of enum-looking but not quite it features... and I'm thinking if we maybe could do Literal['public' | 'private'] | Sequence[str] as the value for a confidentiality param, with helpers functions for combining and comparison, that might simplify things a bit and it saves a additonial class concept that users then do not need to know
Motivation & Context
FIDES enforces information-flow-control policy locally today. This change lets a FIDES-secured agent delegate policy decisions to an external FIDES Gateway fronting its MCP servers, while still tracking labels and surfacing approvals locally. It also adds a runnable WorkIQ email/teams sample as a reference.
Description & Review Guide
SecureMCPToolProxy(gateway_policy=True) delegates per-call policy checks to the gateway's eval_policy (allow/deny/ask).
ConfidentialityLabel gains a readers lattice [PRIVATE] with a readers frozenset, intersected on combine via ConfidentialityLabel.combine, and parses gateway _meta IFC labels including the top-level "$" scope.
New [workiq-email-example.py] sample ([--cli] using ToolApprovalMiddleware, plus README and tests.