You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
feat(mcp-use): add HOL Guard tool middleware proof - #71
Adds one separately maintained HOL Guard proof against the documented Python mcp-use==1.7.0 client middleware contract.
MCPUseHOLGuardMiddleware attaches at Middleware.on_call_tool(...), evaluates the final tool name + structured arguments with local HOL Guard, and calls call_next(context) only after an explicit authoritative allow. Deny, review, malformed output, invalid input, oversized input/output, timeout, and provider failure return an MCP error result without sending the tool call downstream.
Contract
explicit allow -> downstream MCP handler executes exactly once
deny/review -> zero downstream executions
provider failure / invalid provider result -> zero executions
non-serializable or oversized input -> zero provider/downstream executions
warn is not treated as allow
local-only by default; no Guard Cloud dependency
fixed diagnostics do not echo tool arguments
The tests use mcp-use's real MiddlewareManager chain rather than a parallel execution path.
Scope
This is author-owned supporting proof, not mcp-use endorsement. The existing upstream architecture discussion is mcp-use/mcp-use#1257. The intended external conversion is the smallest docs/example or separately maintained middleware placement maintainers choose; this does not revive the broader built-in authorization subsystem.
External Lifecycle Points: 0 until a third-party acceptance/placement/merge/adoption event occurs.
• Introduce a local HOL Guard policy middleware gating tools/call at
Middleware.on_call_tool(...).
• Fail closed on deny/review/provider errors/invalid output/oversize/non-serializable inputs without
downstream execution.
• Add contract tests using the real mcp-useMiddlewareManager chain plus CI workflow coverage.
➕ Reduces ambiguity in _classify and improves long-term maintainability.
➕ Makes provider contract easier to document and validate.
➖ Less tolerant of existing/variant HOL Guard outputs.
➖ May reduce compatibility with harnesses emitting nested/legacy keys.
Recommendation: The current approach is the best fit for a “local-only, minimal integration proof” because it exercises the real CLI, enforces strict size/time bounds, and fails closed at the exact mcp-use middleware boundary. If this is ever promoted beyond proof-of-concept, consider (1) a well-defined, minimal decision schema to shrink _classify surface area and (2) an optional non-subprocess provider for environments where spawning processes is undesirable.
Files changed (6) +423 / -0
Enhancement (2) +242 / -0
__init__.pyExport middleware public API+3/-0
Export middleware public API
• Exposes 'GuardDecision', 'LocalHOLGuardProvider', and 'MCPUseHOLGuardMiddleware' as the package’s public surface via '__all__'.
middleware.pyImplement HOL Guard-gated 'tools/call' middleware with bounded local provider+239/-0
Implement HOL Guard-gated 'tools/call' middleware with bounded local provider
• Implements an mcp-use Middleware that constructs a PreToolUse-style payload, enforces JSON-serializability and request/output size limits, invokes a decision provider (defaulting to a local 'hol-guard' subprocess with timeout), and only calls downstream on explicit allow. All other conditions (deny/review, provider failure, invalid output) fail closed with fixed MCP error messages that do not echo tool arguments.
• Adds async pytest coverage using the real 'MiddlewareManager' chain to assert exactly-once downstream execution on allow and zero executions on deny/review, provider exceptions, invalid provider output, oversized inputs, and non-serializable arguments (without leaking repr/exception strings).
README.mdDocument the mcp-use middleware proof contract and usage+21/-0
Document the mcp-use middleware proof contract and usage
• Adds README describing the middleware attachment point, allow/deny/review semantics, local provider execution constraints (size, timeout), and the non-endorsement scope statement with upstream discussion link.
mcp-use-hol-guard.ymlAdd CI workflow to run the HOL Guard contract tests+32/-0
Add CI workflow to run the HOL Guard contract tests
• Introduces a GitHub Actions workflow that installs the integration package with test extras and runs pytest for the contract suite on PRs and main pushes affecting the integration.
pyproject.tomlDefine packaging and test dependencies for mcp-use-hol-guard+20/-0
Define packaging and test dependencies for mcp-use-hol-guard
• Adds a standalone Python package definition pinned to 'mcp-use==1.7.0', with pytest/pytest-asyncio test extras and wheel build configuration for the integration source package.
The new mcp-use integration copies the same HOL Guard payload encoding/parsing/classification and
bounded subprocess I/O helpers that already exist in other integrations, which increases the chance
of future security/bugfix drift across adapters. A fix to parsing/bounds behavior in one integration
will not automatically propagate to this one, creating inconsistent enforcement over time.
The mcp-use integration adds helper implementations for JSON encoding, JSON extraction from stdout,
decision classification, and bounded subprocess communication; the BeeAI integration already
contains substantially the same helper code. This demonstrates the PR is introducing another
independently maintained copy of the same security-sensitive logic.
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution
## Issue description
`mcp_use_hol_guard.middleware` introduces near-identical helper implementations (`_encode_payload`, `_parse_json_output`, `_classify`, `_terminate_process`, `_communicate_bounded`) that are already maintained in other `integrations/*-hol-guard` packages. This duplication makes it likely that future fixes (e.g., output parsing edge cases or I/O bounding behavior) will be applied to one adapter but missed in others.
## Issue Context
There is already an established pattern of these helpers living inside each integration package. Adding another copy increases the maintenance surface and the risk of inconsistent enforcement behavior.
## Fix Focus Areas
- Create a small shared internal module (e.g., `integrations/_hol_guard_common/` or a top-level `hol_guard_common/` package) containing the shared helper functions and constants.
- Update this new integration to import those helpers instead of redefining them.
- Optionally migrate existing integrations (at least the ones already using the same helper set) to the shared module to eliminate drift.
### References
- integrations/mcp-use-hol-guard/src/mcp_use_hol_guard/middleware.py[15-150]
- integrations/beeai-hol-guard/src/beeai_hol_guard/middleware.py[19-175]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
2. Private telemetry flag usage✓ Resolved🐞 Bug⚙ Maintainability
Description
The contract test mutates MiddlewareManager._record_telemetry, which is a private attribute and
may stop working (or raise) if mcp-use changes the implementation (e.g., adds __slots__ or renames
internals), breaking CI without any functional change to the middleware.
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution
## Issue description
The tests rely on `MiddlewareManager._record_telemetry` (a private/internal attribute). If mcp-use refactors this internal field, the tests can fail even though the middleware contract still holds.
### Issue Context
This integration pins `mcp-use==1.7.0` today, but the proof (and its CI workflow) will likely be re-run/maintained over time, and brittle coupling to private API increases maintenance cost.
### Fix Focus Areas
- integrations/mcp-use-hol-guard/tests/test_middleware.py[29-32]
### Suggested fix
- Prefer a documented/public toggle in mcp-use (if available) to disable telemetry.
- If no public toggle exists, guard the assignment:
- `if hasattr(manager, "_record_telemetry"): manager._record_telemetry = False`
- or wrap in `try/except AttributeError`.
- Add a short comment explaining why telemetry is being disabled (avoid noisy side effects in tests).
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
3. Private telemetry flag usage✓ Resolved🐞 Bug⚙ Maintainability
Description
The contract test mutates MiddlewareManager._record_telemetry, which is a private attribute and
may stop working (or raise) if mcp-use changes the implementation (e.g., adds __slots__ or renames
internals), breaking CI without any functional change to the middleware.
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution
## Issue description
The tests rely on `MiddlewareManager._record_telemetry` (a private/internal attribute). If mcp-use refactors this internal field, the tests can fail even though the middleware contract still holds.
### Issue Context
This integration pins `mcp-use==1.7.0` today, but the proof (and its CI workflow) will likely be re-run/maintained over time, and brittle coupling to private API increases maintenance cost.
### Fix Focus Areas
- integrations/mcp-use-hol-guard/tests/test_middleware.py[29-32]
### Suggested fix
- Prefer a documented/public toggle in mcp-use (if available) to disable telemetry.
- If no public toggle exists, guard the assignment:
- `if hasattr(manager, "_record_telemetry"): manager._record_telemetry = False`
- or wrap in `try/except AttributeError`.
- Add a short comment explaining why telemetry is being disabled (avoid noisy side effects in tests).
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
4. Provider failures are silent 🐞 Bug◔ Observability
Description
MCPUseHOLGuardMiddleware.on_call_tool catches all provider exceptions and fails closed without any
logging/telemetry, so a provider crash/misconfiguration can silently block all tool calls and be
hard to diagnose.
+ try:+ value = self._provider(payload)+ decision = await value if inspect.isawaitable(value) else value+ except Exception:+ decision = GuardDecision("deny")+ if not isinstance(decision, GuardDecision) or decision.kind not in {"allow", "deny", "review"}:+ decision = GuardDecision("deny")
Relevance
● Weak
Recent observability logging requests were explicitly rejected; fail-closed behavior and non-leakage
are the stated contract.
ⓘ Recommendations generated based on similar findings in past PRs
Evidence
The new middleware converts any provider exception into a deny decision with no side-channel
indicating that the provider errored, only that the call was blocked.
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution
## Issue description
The middleware intentionally fails closed on provider errors, but currently does so silently. This makes operational debugging difficult when the provider is broken/unavailable.
### Issue Context
The PR’s contract explicitly requires diagnostics to be fixed strings and not echo tool arguments; logging can still be safe if it is fixed and does not include request payloads.
### Fix Focus Areas
- integrations/mcp-use-hol-guard/src/mcp_use_hol_guard/middleware.py[228-239]
### Suggested fix
- Add an optional logger/callback (or use `logging.getLogger(__name__)`) and emit a fixed warning/error message when the provider raises.
- Ensure the log message does not include tool name/arguments/context IDs if those are considered sensitive for this proof (keep it constant/fixed).
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
5. Provider failures are silent 🐞 Bug◔ Observability
Description
MCPUseHOLGuardMiddleware.on_call_tool catches all provider exceptions and fails closed without any
logging/telemetry, so a provider crash/misconfiguration can silently block all tool calls and be
hard to diagnose.
+ try:+ value = self._provider(payload)+ decision = await value if inspect.isawaitable(value) else value+ except Exception:+ decision = GuardDecision("deny")+ if not isinstance(decision, GuardDecision) or decision.kind not in {"allow", "deny", "review"}:+ decision = GuardDecision("deny")
Relevance
● Weak
Recent observability logging requests were explicitly rejected; fail-closed behavior and non-leakage
are the stated contract.
ⓘ Recommendations generated based on similar findings in past PRs
Evidence
The new middleware converts any provider exception into a deny decision with no side-channel
indicating that the provider errored, only that the call was blocked.
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution
## Issue description
The middleware intentionally fails closed on provider errors, but currently does so silently. This makes operational debugging difficult when the provider is broken/unavailable.
### Issue Context
The PR’s contract explicitly requires diagnostics to be fixed strings and not echo tool arguments; logging can still be safe if it is fixed and does not include request payloads.
### Fix Focus Areas
- integrations/mcp-use-hol-guard/src/mcp_use_hol_guard/middleware.py[228-239]
### Suggested fix
- Add an optional logger/callback (or use `logging.getLogger(__name__)`) and emit a fixed warning/error message when the provider raises.
- Ensure the log message does not include tool name/arguments/context IDs if those are considered sensitive for this proof (keep it constant/fixed).
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
Tip of the day
💡 Did you know, you can commit Qodo's fix in one click with committable suggestions (GitHub & GitLab)
1. Private telemetry flag usage✓ Resolved🐞 Bug⚙ Maintainability
Description
The contract test mutates MiddlewareManager._record_telemetry, which is a private attribute and
may stop working (or raise) if mcp-use changes the implementation (e.g., adds __slots__ or renames
internals), breaking CI without any functional change to the middleware.
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution
### Issue description
The tests rely on `MiddlewareManager._record_telemetry` (a private/internal attribute). If mcp-use refactors this internal field, the tests can fail even though the middleware contract still holds.
### Issue Context
This integration pins `mcp-use==1.7.0` today, but the proof (and its CI workflow) will likely be re-run/maintained over time, and brittle coupling to private API increases maintenance cost.
### Fix Focus Areas
- integrations/mcp-use-hol-guard/tests/test_middleware.py[29-32]
### Suggested fix
- Prefer a documented/public toggle in mcp-use (if available) to disable telemetry.
- If no public toggle exists, guard the assignment:
- `if hasattr(manager, "_record_telemetry"): manager._record_telemetry = False`
- or wrap in `try/except AttributeError`.
- Add a short comment explaining why telemetry is being disabled (avoid noisy side effects in tests).
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
2. Provider failures are silent 🐞 Bug◔ Observability
Description
MCPUseHOLGuardMiddleware.on_call_tool catches all provider exceptions and fails closed without any
logging/telemetry, so a provider crash/misconfiguration can silently block all tool calls and be
hard to diagnose.
+ try:+ value = self._provider(payload)+ decision = await value if inspect.isawaitable(value) else value+ except Exception:+ decision = GuardDecision("deny")+ if not isinstance(decision, GuardDecision) or decision.kind not in {"allow", "deny", "review"}:+ decision = GuardDecision("deny")
Relevance
● Weak
Recent observability logging requests were explicitly rejected; fail-closed behavior and non-leakage
are the stated contract.
ⓘ Recommendations generated based on similar findings in past PRs
Evidence
The new middleware converts any provider exception into a deny decision with no side-channel
indicating that the provider errored, only that the call was blocked.
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution
### Issue description
The middleware intentionally fails closed on provider errors, but currently does so silently. This makes operational debugging difficult when the provider is broken/unavailable.
### Issue Context
The PR’s contract explicitly requires diagnostics to be fixed strings and not echo tool arguments; logging can still be safe if it is fixed and does not include request payloads.
### Fix Focus Areas
- integrations/mcp-use-hol-guard/src/mcp_use_hol_guard/middleware.py[228-239]
### Suggested fix
- Add an optional logger/callback (or use `logging.getLogger(__name__)`) and emit a fixed warning/error message when the provider raises.
- Ensure the log message does not include tool name/arguments/context IDs if those are considered sensitive for this proof (keep it constant/fixed).
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
Review mode: ⚖️ Balanced: Downgraded extended -> standard: change is below the extended eligibility bar (hunks 6/18, lines 423/200; both must reach the floor). Router rationale: This adds substantial security-sensitive middleware logic, subprocess/timeouts and output parsing, CI packaging, and integration tests across multiple independent paths where subtle fail-open or contract defects could be missed in one pass.
Tip of the day
💡 Did you know, you can commit Qodo's fix in one click with committable suggestions (GitHub & GitLab)
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
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.
Summary
Adds one separately maintained HOL Guard proof against the documented Python
mcp-use==1.7.0client middleware contract.MCPUseHOLGuardMiddlewareattaches atMiddleware.on_call_tool(...), evaluates the final tool name + structured arguments with local HOL Guard, and callscall_next(context)only after an explicit authoritative allow. Deny, review, malformed output, invalid input, oversized input/output, timeout, and provider failure return an MCP error result without sending the tool call downstream.Contract
warnis not treated as allowThe tests use
mcp-use's realMiddlewareManagerchain rather than a parallel execution path.Scope
This is author-owned supporting proof, not mcp-use endorsement. The existing upstream architecture discussion is mcp-use/mcp-use#1257. The intended external conversion is the smallest docs/example or separately maintained middleware placement maintainers choose; this does not revive the broader built-in authorization subsystem.
External Lifecycle Points: 0 until a third-party acceptance/placement/merge/adoption event occurs.