feat: add Cursor SDK backend with MCP bridge and tool hooks#8
Merged
Conversation
Port CursorBackend into the upstream pluggable backend architecture alongside ClaudeAgentBackend. Cursor registers in REGISTRY, uses the v1.2.0 AgentBackend protocol (__init__, drop_keys wizard, num_turns), and adds cursor_api_key/cursor_model config fields. Co-authored-by: Aleksandar Hadzibabic <hadzija7@users.noreply.github.com>
Expose TOOL_DEFINITIONS through an ephemeral in-process HTTP MCP server (EphemeralHttpMcpBridge) and pass mcp_servers to Cursor AgentOptions/ SendOptions. Images are sent via SDKImage/UserMessage. Adds shared mcp_tools builder and tests. Co-authored-by: Aleksandar Hadzibabic <hadzija7@users.noreply.github.com>
Wrap bridge listen wait in try/except so a timeout triggers the same uvicorn shutdown as __aexit__, avoiding a leaked server task. Cap Cursor agentic tool loops by cancelling the run after max_turns tool invocations and clamp reported num_turns accordingly.
Use AsyncAgent.close() instead of aclose(), concatenate streamed text blocks, and prefer the longer run.wait() result to avoid truncated replies. Co-authored-by: Cursor <cursoragent@cursor.com>
…cpServerConfig Avoid awaiting run.wait() after cancel() when the tool-step cap fires, since wait() may not complete promptly on a cancelled run and would block agent cleanup. Import HttpMcpServerConfig inside __aenter__ and under TYPE_CHECKING only so loading memclaw.backends does not pull in cursor_sdk until the bridge runs.
…close - Use tool_steps >= max_turns so the Cursor backend matches Claude max_turns. - Add MemclawAgent.aclose() to await optional backend.close() (Cursor bridge). - MemclawAgent.close() runs aclose via asyncio.run when no loop is active. - Telegram/Slack shutdown and interactive CLI await aclose; async tests updated.
End users installing memclaw no longer pull the large native wheel unless they use pip install memclaw[cursor]. Keep cursor-sdk in the dev extra so CI and contributors running tests still resolve the dependency.
Keep the Cursor backend integration protocol-only by restoring the original close() lifecycle instead of threading aclose() through core code. Co-authored-by: Cursor <cursoragent@cursor.com>
…rializable _log_tool_call used json.dumps on SDK message args without handling TypeError/ValueError; fall back to str() so logging cannot abort run_turn.
…atomically Stop using str(result) in _extract_run_text so RunResult repr cannot replace valid assistant text when wait() text is chosen by length. Bind and listen on 127.0.0.1:0, pass the socket fd to uvicorn, and close the listener after shutdown to remove the ephemeral-port TOCTOU window.
Ship and verify ~/.memclaw/.cursor hooks that allow only Memclaw MCP tools, mirroring Claude disallowed_tools guardrail for Cursor backend. Co-authored-by: Cursor <cursoragent@cursor.com>
…--memory-dir Cursor requires a top-level numeric version in project hooks.json; the packaged template now includes version 1 so Cursor loads preToolUse hooks. Plumb the active memory directory from run_setup into wizard_setup so the Cursor wizard installs hooks under the same tree as AGENT_BACKEND=cursor, not only the default ~/.memclaw path.
Start the local MCP server with agent.start() on a configurable port and reuse it across Cursor turns, stopping cleanly on shutdown. Co-authored-by: Cursor <cursoragent@cursor.com>
MemclawAgent.close() now tears down the local HTTP MCP server when the Cursor backend still has it running, using asyncio.run when no event loop is active and scheduling stop on the running loop otherwise. HttpMcpServer.start() calls stop() before re-raising bind errors so partial executor/session state is cleared.
Recognize MCP:tool shorthand and CallMcpTool bridge payloads, broaden local MCP URL matching, and add deny audit logging plus incomplete-tool warnings for easier diagnosis. Co-authored-by: Cursor <cursoragent@cursor.com>
The Cursor backend counted each tool_call running event toward max_turns and cancelled the run, unlike Claude which uses max_turns for agent iterations. Remove that cancellation and derive num_turns from the SDK wait result only.
preToolUse previously treated any MCP bridge payload with providerIdentifier/server memclaw as allowed without checking toolName against MEMCLAW_MCP_TOOL_NAMES, and is_allowed_tool short-circuited the same way. Align bridge checks with the allowlist and update the mcp_auth parametrized expectation.
Cursor MCP shutdown belongs in aclose(); close() stays sync-only for index and background sync cleanup. Co-authored-by: Cursor <cursoragent@cursor.com>
Add AgentBackend start/shutdown hooks so MemclawAgent stays SDK-agnostic, move MCP ownership into the Cursor backend, and generate hook allowlists from a single tool_policy module. Co-authored-by: Cursor <cursoragent@cursor.com>
Derive the memory directory from the hook install path and set MEMCLAW_MEMORY_DIR when launching the Cursor backend so custom --memory-dir paths are respected. Co-authored-by: Cursor <cursoragent@cursor.com>
MemclawAgent.close() now awaits backend on_agent_shutdown via asyncio.run when no event loop is running, and aclose() calls shared _close_sync_only() after async shutdown. Consolidation CLI uses await aclose() so shutdown runs inside the async context. Cursor beforeMCPExecution hook matches URL port to policy/env MCP port; hook policy and bundled defaults bump to v9 with mcp_http_port.
Consolidation called _maybe_consolidate without starting the agent, so CursorAgentBackend.on_agent_start never ran and ensure_cursor_hooks was skipped. Match interactive mode by syncing the index and invoking on_agent_start before the one-shot consolidation run.
feat: add Cursor SDK agent backend layer
Integrate upstream platform picker, doctor command, and curl-cffi link fetching while preserving Cursor backend config, MCP bridge, and hooks. Co-authored-by: Cursor <cursoragent@cursor.com>
Use handlers.aclose() instead of close() so MemclawAgent runs backend.on_agent_shutdown() while the asyncio loop is still active (e.g. Cursor MCP HTTP server cleanup).
Sync upstream/main and resolve merge conflicts
Revert ruff-driven line wrapping in agent.py, cli.py, and setup.py while keeping the Cursor backend functional changes from PR emanuilo#8. Co-authored-by: Cursor <cursoragent@cursor.com>
Consolidation only needs index sync and run_one_shot, so starting the MCP server is unnecessary and fails when another memclaw instance is already running. Co-authored-by: Cursor <cursoragent@cursor.com>
Make the model field optional with a visible default hint, escape Rich markup in the prompt, and drop the noisy hooks-ready info log during configure. Co-authored-by: Cursor <cursoragent@cursor.com>
Enable SendOptions.on_delta so turn-ended usage reaches Memclaw, normalize token fields into TurnResult, and add Composer pricing fallback when cost is missing. Co-authored-by: Cursor <cursoragent@cursor.com>
Skip backend startup during consolidate
The Cursor SDK emits cache creation tokens as cacheWriteTokens in the turn-ended usage payload. The lookup list previously only checked Anthropic-style names (cache_creation_input_tokens, ...), so the cache_create telemetry was silently always zero. Add the camelCase Cursor key (and a snake_case variant as a defensive fallback) at the front of the lookup.
Add an Agent Backend section right after Messaging Platforms with a subsection per backend, move the existing Claude/Cursor auth details there, and drop the duplicated Configuration subsections. Neutralize a few Claude-specific mentions elsewhere (How It Works, Agent Layer, Direct Commands, Architecture). Add a Cursor SDK badge, a billing note for Cursor, and a dedicated Link row in the message-type table.
Owner
|
Looks good! Thanks! |
emanuilo
approved these changes
May 25, 2026
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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
AGENT_BACKEND=cursor) alongside Claude, selectable in the setup wizard.memory_save, reminders, etc.) into Cursor via a local HTTP MCP bridge, with process-scoped MCP lifecycle management.main(platform picker,memclaw doctor, curl-cffi link fetching, masked user IDs in logs).Test plan
uv run pytest— 173 tests passmemclaw configure, select Cursor backend, and verify keys are savedmemclawin terminal mode withAGENT_BACKEND=cursorand confirm memory save/search worksAGENT_BACKEND=claude)Made with Cursor