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
xmcp already filters tools at startup with X_API_TOOL_ALLOWLIST (README). That’s a static allowlist.
Separately we ship npm affixio@1.3.3 with mcpToolGate — a per-call spend/policy check before an MCP tool runs. JS helper, not a Python dep ask. Not integrated with xmcp.
Ask
For agents hitting X tools (createPosts, DMs, etc.), is startup allowlist the only supported control, or is a runtime before-tool hook (deny/allow per call) something you’d want documented / accepted as a pattern next to xmcp?
Discovery only. Unique from our other MCP posts — this is specifically about X’s allowlist vs per-call gating.
A note on where a per-call gate could live in xmcp without adding a dependency: create_mcp() already builds the httpx.AsyncClient with event_hooks["request"] (normalize_query_params, sign_oauth1_request, log_request), and every tool call goes through that client. One more request hook could check each call against a policy before it leaves, and raise to stop it before the request is sent, so the tool call fails with a message saying why.
A small version could be driven by env vars, like the existing allowlist:
X_API_WRITE_LIMIT_PER_HOUR: cap the POST/PUT/DELETE calls (posts, DMs, follows) in a rolling hour.
X_API_BLOCK_WRITES=1: read-only mode without having to list every write operation in the denylist.
Static allow and deny lists still decide which tools exist. This would decide whether a given call may run right now, which is the case the issue raises for createPosts and DMs.
If the maintainers want this, I'm happy to open a PR with tests. Disclosure: we build Governor (https://governor.bsvkey.com), which does per-call caps and approvals for model API spend. This suggestion is only the small hook above, with no dependency on it.
Thanks for taking a look. That’s a useful suggestion, particularly reusing the existing request hooks rather than introducing another dependency.
The distinction between static tool availability and per-call authorisation is exactly what I was hoping to explore.
Rate limits and read-only mode would be a sensible starting point. Longer term, there’s also the question of action-specific approvals, budgets and verifiable decision evidence, but those don’t need to be part of an initial implementation.
I’d be interested to hear whether the maintainers consider the request-hook approach appropriate, especially around request retries and ensuring denied calls never reach the API.
On your two questions, from the httpx source (0.28.1, what xmcp installs today):
Denied calls never reach the API.AsyncClient._send_handling_redirects awaits every event_hooks["request"] hook before it calls _send_single_request, so a hook that raises stops the call before anything is sent. Putting the policy hook first in the list also means a denied call is never signed.
Retries. xmcp builds its AsyncClient without a custom transport, so httpx's default of retries=0 applies, and redirects aren't followed by default. xmcp never re-sends a request on its own. If the agent retries the tool, that's a new request and passes through the hook again, so it's checked and counted again.
One design choice for the maintainers: count a write against the hourly limit when the hook allows it, not when the API answers. That way a call that fails upstream still uses its slot, which errs on the safe side.
The existing httpx request hooks are an interesting place to enforce outbound API limits without introducing another dependency.
I think there’s one distinction worth preserving, though. A request hook can stop an HTTP call, but a runtime policy gate may also need to consider the agent’s intended action, who authorised it, and whether approval is required before execution.
For example, a post might be permitted while a DM requires approval, even when both are within the hourly limit.
That’s the gap we’re exploring with AffixIO: per-action decisions and verifiable evidence, rather than replacing xmcp’s existing allowlist or introducing a mandatory dependency.
Activity
EmbryoSpace commentedon Oct 26, 2026
A note on where a per-call gate could live in xmcp without adding a dependency:
create_mcp()already builds thehttpx.AsyncClientwithevent_hooks["request"](normalize_query_params,sign_oauth1_request,log_request), and every tool call goes through that client. One more request hook could check each call against a policy before it leaves, and raise to stop it before the request is sent, so the tool call fails with a message saying why.A small version could be driven by env vars, like the existing allowlist:
X_API_WRITE_LIMIT_PER_HOUR: cap the POST/PUT/DELETE calls (posts, DMs, follows) in a rolling hour.X_API_BLOCK_WRITES=1: read-only mode without having to list every write operation in the denylist.Static allow and deny lists still decide which tools exist. This would decide whether a given call may run right now, which is the case the issue raises for createPosts and DMs.
If the maintainers want this, I'm happy to open a PR with tests. Disclosure: we build Governor (https://governor.bsvkey.com), which does per-call caps and approvals for model API spend. This suggestion is only the small hook above, with no dependency on it.
AffixIO commentedon Oct 26, 2026
Thanks for taking a look. That’s a useful suggestion, particularly reusing the existing request hooks rather than introducing another dependency.
The distinction between static tool availability and per-call authorisation is exactly what I was hoping to explore.
Rate limits and read-only mode would be a sensible starting point. Longer term, there’s also the question of action-specific approvals, budgets and verifiable decision evidence, but those don’t need to be part of an initial implementation.
I’d be interested to hear whether the maintainers consider the request-hook approach appropriate, especially around request retries and ensuring denied calls never reach the API.
Appreciate you digging into it.
EmbryoSpace commentedon Oct 26, 2026
On your two questions, from the httpx source (0.28.1, what xmcp installs today):
AsyncClient._send_handling_redirectsawaits everyevent_hooks["request"]hook before it calls_send_single_request, so a hook that raises stops the call before anything is sent. Putting the policy hook first in the list also means a denied call is never signed.AsyncClientwithout a custom transport, so httpx's default ofretries=0applies, and redirects aren't followed by default. xmcp never re-sends a request on its own. If the agent retries the tool, that's a new request and passes through the hook again, so it's checked and counted again.One design choice for the maintainers: count a write against the hourly limit when the hook allows it, not when the API answers. That way a call that fails upstream still uses its slot, which errs on the safe side.
AffixIO commentedon Oct 26, 2026
The existing httpx request hooks are an interesting place to enforce outbound API limits without introducing another dependency.
I think there’s one distinction worth preserving, though. A request hook can stop an HTTP call, but a runtime policy gate may also need to consider the agent’s intended action, who authorised it, and whether approval is required before execution.
For example, a post might be permitted while a DM requires approval, even when both are within the hourly limit.
That’s the gap we’re exploring with AffixIO: per-action decisions and verifiable evidence, rather than replacing xmcp’s existing allowlist or introducing a mandatory dependency.
Best