Skip to content

Commit 595bcab

Browse files
刘一cursoragent
andcommitted
fix(cursor-agent): inject --approve-mcps --force for headless MCP/tool access
The previous commit (1c55988) wired up ``-p --trust`` so the CLI launches in headless mode without the Workspace Trust prompt, but that alone is not enough to let ``specify workflow run`` drive a real speckit feature end-to-end with cursor-agent on Windows. Two more flags are required: * ``--approve-mcps``: without it, every MCP server configured in ``.cursor/mcp.json`` stays ``not loaded (needs approval)``, and any tool call against them is silently dropped. We hit this immediately trying to read a DingTalk PRD from a remote MCP server during the ``/speckit-specify`` step. * ``--force``: without it, the agent halts on the first tool-call approval prompt (the tool call gets rejected and the workflow exits non-zero with a misleading message). With ``--force`` cursor-agent matches the implicit "trusted environment" semantics that ``claude -p`` and ``codex --exec`` already have by default -- which is the right semantics for an unattended ``specify workflow run`` invocation. Verified end-to-end on Windows 10 + cursor-agent 2026.05.16-0338208: * ``cursor-agent -p --trust --approve-mcps --force --output-format text`` + a ``/speckit-specify`` prompt that included a DingTalk URL produced a full spec.md (31.5 KB) plus checklists/requirements.md in ~10.7 min, reading the source PRD through the ``dingtalk-doc`` remote MCP server, deciding the ``specs/`` subpath itself, and updating ``.specify/feature.json`` and ``specs/menu-dictionary.md`` along the way -- no human-in-the-loop, no source PRD ever touched the filesystem. * Without ``--approve-mcps`` the same prompt errors with the tool call rejected message; without ``--force`` the agent stops at the first non-MCP tool call. Tests: * ``test_build_exec_args_*`` updated to pin the new four-flag prefix. * New ``test_build_exec_args_contains_mandatory_headless_flags`` asserts the four flags are always present together. * ``test_dispatch_command_resolves_cmd_shim_for_subprocess`` updated to match the new argv layout. * All 43 cursor-agent tests pass; no other tests touched. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 454d3d0 commit 595bcab

2 files changed

Lines changed: 46 additions & 10 deletions

File tree

src/specify_cli/integrations/cursor_agent/__init__.py

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,14 +41,30 @@ def build_exec_args(
4141
) -> list[str] | None:
4242
"""Build CLI arguments for non-interactive ``cursor-agent`` execution.
4343
44-
Uses ``-p`` (print/headless mode, which gives access to all
45-
tools including write and shell) plus ``--trust`` (bypass the
46-
Workspace Trust prompt — mandatory for headless execution; the
47-
CLI exits non-zero without it).
44+
Mandatory headless flags:
45+
46+
* ``-p`` — print/headless mode (access to all tools)
47+
* ``--trust`` — bypass Workspace Trust prompt (CLI exits non-zero
48+
otherwise)
49+
* ``--approve-mcps`` — auto-approve MCP server loading (otherwise
50+
MCP servers stay ``not loaded (needs approval)`` and tool calls
51+
to them are silently dropped)
52+
* ``--force`` — auto-approve tool invocations (shell/write/MCP),
53+
matching the implicit "trusted environment" semantics that other
54+
integrations (``claude -p``, ``codex --exec``) get by default
55+
56+
Together these are the minimum set required to make
57+
``specify workflow run speckit --input integration=cursor-agent``
58+
behave the same way as it does for ``claude`` / ``codex``.
59+
Verified locally: with ``--approve-mcps --force`` the agent can
60+
call any configured MCP server (e.g. ``dingtalk-doc``) and write
61+
files during ``/speckit-*`` skill execution; without them the run
62+
either drops tool calls or exits non-zero on the first approval
63+
prompt.
4864
"""
4965
if not self.config or not self.config.get("requires_cli"):
5066
return None
51-
args = [self.key, "-p", "--trust", prompt]
67+
args = [self.key, "-p", "--trust", "--approve-mcps", "--force", prompt]
5268
if model:
5369
args.extend(["--model", model])
5470
if output_json:

tests/integrations/test_integration_cursor_agent.py

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -126,11 +126,14 @@ def test_install_url_is_set(self):
126126
assert url is not None
127127
assert "cursor.com" in url
128128

129-
def test_build_exec_args_default_includes_trust_and_json(self):
129+
def test_build_exec_args_default_includes_headless_flags_and_json(self):
130+
"""Default argv emits the full headless flag set: -p --trust
131+
--approve-mcps --force, then prompt, then --output-format json.
132+
"""
130133
i = get_integration("cursor-agent")
131134
args = i.build_exec_args("/speckit-specify some-feature")
132135
assert args == [
133-
"cursor-agent", "-p", "--trust",
136+
"cursor-agent", "-p", "--trust", "--approve-mcps", "--force",
134137
"/speckit-specify some-feature",
135138
"--output-format", "json",
136139
]
@@ -139,7 +142,8 @@ def test_build_exec_args_text_output_omits_format(self):
139142
i = get_integration("cursor-agent")
140143
args = i.build_exec_args("/speckit-plan", output_json=False)
141144
assert args == [
142-
"cursor-agent", "-p", "--trust", "/speckit-plan",
145+
"cursor-agent", "-p", "--trust", "--approve-mcps", "--force",
146+
"/speckit-plan",
143147
]
144148

145149
def test_build_exec_args_with_model(self):
@@ -148,10 +152,26 @@ def test_build_exec_args_with_model(self):
148152
"/speckit-specify", model="sonnet-4-thinking", output_json=False
149153
)
150154
assert args == [
151-
"cursor-agent", "-p", "--trust", "/speckit-specify",
155+
"cursor-agent", "-p", "--trust", "--approve-mcps", "--force",
156+
"/speckit-specify",
152157
"--model", "sonnet-4-thinking",
153158
]
154159

160+
def test_build_exec_args_contains_mandatory_headless_flags(self):
161+
"""The four headless flags must always appear together.
162+
163+
``--approve-mcps`` is required so MCP servers (e.g. dingtalk-doc)
164+
actually load in headless mode; ``--force`` is required so the
165+
agent doesn't block on tool-call approval prompts during the
166+
speckit workflow. Together with ``-p`` and ``--trust`` they
167+
bring cursor-agent's headless behaviour in line with
168+
``claude -p`` / ``codex --exec`` from spec-kit's perspective.
169+
"""
170+
i = get_integration("cursor-agent")
171+
args = i.build_exec_args("/speckit-implement", output_json=False)
172+
for flag in ("-p", "--trust", "--approve-mcps", "--force"):
173+
assert flag in args, f"missing mandatory headless flag: {flag}"
174+
155175
def test_build_command_invocation_uses_hyphenated_skill_name(self):
156176
"""SkillsIntegration: /speckit-plan (not /speckit.plan)."""
157177
i = get_integration("cursor-agent")
@@ -189,7 +209,7 @@ def test_dispatch_command_resolves_cmd_shim_for_subprocess(self):
189209
assert result["exit_code"] == 0
190210
argv = mock_run.call_args[0][0]
191211
assert argv[0] == fake_path, f"expected resolved .CMD path, got: {argv[0]!r}"
192-
assert argv[1:4] == ["-p", "--trust", "/speckit-plan feature-x"]
212+
assert argv[1:6] == ["-p", "--trust", "--approve-mcps", "--force", "/speckit-plan feature-x"]
193213

194214
def test_dispatch_command_passthrough_when_shutil_which_finds_nothing(self):
195215
"""If ``shutil.which`` returns ``None``, leave argv unchanged so the

0 commit comments

Comments
 (0)