hermes - 💡(How to fix) Fix MCP `permissions_list_open` and `permissions_respond` are non-functional — `_pending_approvals` is never populated

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

Error Message

  • permissions_respond always returns {"error": "Approval not found: <id>"} return {"error": f"Approval not found: {approval_id}"}

Code Example

def respond_to_approval(self, approval_id: str, decision: str) -> dict:
    """Resolve a pending approval (best-effort without gateway IPC)."""
    with self._lock:
        approval = self._pending_approvals.pop(approval_id, None)
    if not approval:
        return {"error": f"Approval not found: {approval_id}"}
    self._enqueue(QueueEvent(
        cursor=0,
        type="approval_resolved",
        session_key=approval.get("session_key", ""),
        data={"approval_id": approval_id, "decision": decision},
    ))
    return {"resolved": True, "approval_id": approval_id, "decision": decision}
RAW_BUFFERClick to expand / collapse

Bug

The permissions_list_open and permissions_respond MCP tools are implemented but the underlying state they read from (EventBridge._pending_approvals) is never populated by any code path. As a result:

  • permissions_list_open always returns {"count": 0, "approvals": []}
  • permissions_respond always returns {"error": "Approval not found: <id>"}

Even if _pending_approvals were populated, respond_to_approval only emits a local approval_resolved event into the bridge's own in-memory queue. There is no IPC back to the gateway to relay the approval decision. So the gateway has no way to know that the MCP client "approved" anything.

Source

mcp_serve.py:

  • EventBridge.__init__ initializes self._pending_approvals: Dict[str, dict] = {} (around line 221)
  • _poll_once() only enqueues message-type events from SessionDB.get_messages() — never touches _pending_approvals
  • No other code path adds to _pending_approvals

respond_to_approval (around line 304-319):

def respond_to_approval(self, approval_id: str, decision: str) -> dict:
    """Resolve a pending approval (best-effort without gateway IPC)."""
    with self._lock:
        approval = self._pending_approvals.pop(approval_id, None)
    if not approval:
        return {"error": f"Approval not found: {approval_id}"}
    self._enqueue(QueueEvent(
        cursor=0,
        type="approval_resolved",
        session_key=approval.get("session_key", ""),
        data={"approval_id": approval_id, "decision": decision},
    ))
    return {"resolved": True, "approval_id": approval_id, "decision": decision}

The docstring acknowledges "best-effort without gateway IPC" but the surface tools' MCP schema does not communicate this to clients — they appear functional.

Impact

The MCP surface advertises an approvals workflow that does nothing:

  1. Agent calls permissions_list_open → sees no approvals → assumes nothing pending
  2. Real approval requests sit unresolved on the gateway
  3. Even if the agent did discover an approval (via some other channel) and called permissions_respond, the gateway would never see the response

Misleading API surface. Either the tools should work, or they should be removed.

Reproduce

  1. Trigger a real approval request on the gateway (e.g. via a plugin that requires allow-once / allow-always).
  2. Connect an MCP client and call permissions_list_open.
  3. Expected: the pending approval is listed. Actual: empty list.

Suggested fix

Option A: implement gateway IPC so the bridge can register approval requests as the gateway issues them, and relay decisions back. Principled fix but requires defining an IPC protocol (Unix socket, named pipe, file-watched JSON). Touches gateway lifecycle.

Option B: remove the tools from the MCP surface until the IPC layer exists. Cleaner short-term — no misleading affordance.

Option B is faster and unblocks honest expectations from MCP clients. Option A unlocks a genuinely useful pattern (e.g., Claude Code approving Hermes plugin actions) but is a real design conversation.

Affected versions

Confirmed on origin/main at 62c2f5d8d2a6a21adfdea2d8d1f28fd8f04b5dd7. Behavior unchanged at current HEAD 292f46836.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING