Skip to content

feat(cursor): bridge native AgentService tools and MCP - #1261

Open
yansigit wants to merge 1834 commits into
1jehuang:masterfrom
yansigit:feat/cursor-native-tools-mcp
Open

yansigit wants to merge 1834 commits into
1jehuang:masterfrom
yansigit:feat/cursor-native-tools-mcp

Conversation

@yansigit

Copy link
Copy Markdown
Contributor

Summary

Implement the Cursor native AgentService HTTP/2 transport needed for reliable streaming and MCP tool execution through jcode.

Changes

  • Track current dated Cursor CLI framing metadata.
  • Decode Connect gzip/KV/context frames and streamed text/reasoning chunks.
  • Route native tool calls to jcode MCP and return NativeToolResult responses.
  • Sanitize and collision-proof Cursor tool names while preserving dispatch identity.
  • Bound transport operations and close streams cleanly.
  • Preserve model descriptor semantics for the separate model compatibility review in Cursor provider: most live-catalog models fail with ERROR_BAD_MODEL_NAME (Unknown model ID) #1226.

This is complementary to #1226 and #575. The branch is intentionally focused on native AgentService transport and tool bridging.

Validation

  • Rebasing onto current upstream/master completed successfully.
  • jcode-provider-cursor-runtime library tests passed.
  • jcode-provider-doctor library tests passed.
  • Public native Cursor streaming and tool acceptance passed with the installed dated CLI build.
  • Large-context acceptance passed for 80K and 200K inputs.

Closes #1258
Refs #1226
Refs #575

1jehuang and others added 30 commits August 26, 2026 12:39
…s-20260827

fix: resolve safe issues from open-issue triage
@yansigit

Copy link
Copy Markdown
Contributor Author

Eager MCP name blocker resolved

The final Cursor protobuf encoder now defensively sanitizes both name-bearing fields at the last protocol boundary, including callers that provide an alias directly. Names are ASCII-only, bounded to Cursor's maximum, and remain independent from the original registry dispatch key.

Added regression coverage for 20 eager MCP descriptors containing spaces, slashes, dots, and other unsafe characters. Focused validation now passes with 44 Cursor runtime tests, and bounded live acceptance with --tools all --mcp-tools eager completed successfully with CURSOR_EAGER_NAME_OK.

@yansigit
yansigit marked this pull request as ready for review September 15, 2026 06:52
@greptile-apps

greptile-apps Bot commented Sep 15, 2026

Copy link
Copy Markdown

RetriggerConfidence Score: 1/5

Not safe to merge until the outstanding correctness and memory-safety issues are fixed, and the repository logging requirement is satisfied.

Findings

  1. P1 Avoid Duplicate System Prompts
  2. P1 Propagate Tool Encoding Failures
  3. P1 Security Bound Response Memory Usage
  4. P2 Remove Debug Diagnostic
Fix with agent prompt
### Issue 1
crates/jcode-provider-cursor-runtime/src/lib.rs:563-573
`build_cli_prompt(system, messages)` already includes the system text, but this call passes both that assembled prompt and the original `system` to the transport. `routed_prompt` then prepends the system text again. Every non-empty system instruction is sent twice with different formatting, which changes instruction precedence. The extra copy and tool-routing directive are also added after the 120,000-character limit, so the final request is no longer bounded by `MAX_PROMPT_CHARS`.

### Issue 2
crates/jcode-provider-cursor-runtime/src/agent_transport.rs:328-331
If any advertised tool has a schema the bounded protobuf encoder cannot represent, such as more than 32 nested object or array levels, `encode_mcp_tools` returns an error that this branch silently discards. The request then proceeds without the entire tool payload. Because this provider reports that it handles tools internally, jcode has no fallback execution path, so the requested tools cannot run. Propagate the encoding error instead of sending a request without its tools.

### Issue 3
crates/jcode-provider-cursor-runtime/src/agent_transport.rs:378-389
The response path allocates from peer-controlled data without a byte limit. An incomplete frame can grow `pending` toward its declared 32-bit length, gzip decoding uses unbounded `read_to_end`, and decoded KV blobs are retained in an unbounded map. A configured or compromised AgentService peer can therefore send a large frame, compression bomb, or repeated unique blobs and exhaust the jcode process before the time deadline. Enforce limits for compressed payloads, decompressed payloads, the pending buffer, and aggregate blob storage.

**How this was verified:** A response reproduction retained 8,388,613 bytes for an incomplete 4 GiB frame, expanded 16,328 compressed bytes to 16,777,216 bytes, and retained 12 unique peer blobs totaling 12,582,960 bytes.

### Issue 4
crates/jcode-provider-cursor-runtime/src/agent_transport.rs:49-53
`stream_debug` commits an `eprintln!`-based transport diagnostic. The repository directive permits `eprintln!` only for throwaway diagnostics that are deleted before committing. Remove this path or route durable diagnostics through the supported logging mechanism. This repository requirement must be satisfied before merging.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Summary

This PR adds Cursor AgentService streaming, model discovery, native MCP tool bridging, and handling for streamed context, execution, terminal, key/value, and compressed frames. It also improves MCP cleanup and Bash optional-boolean handling.

The existing blocking correctness, memory-safety, and repository logging issues remain open.

Reviews (2) · Last reviewed commit: "fix(cursor): enforce safe eager MCP name..."

Comment on lines +563 to +573
let result = run_native_text_command(
client,
tx.clone(),
&prompt,
&model,
None,
resume_session_id.as_deref(),
&stream_uuid,
&tools,
&system,
tool_result_rx,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Avoid Duplicate System Prompts

build_cli_prompt(system, messages) already includes the system text, but this call passes both that assembled prompt and the original system to the transport. routed_prompt then prepends the system text again. Every non-empty system instruction is sent twice with different formatting, which changes instruction precedence. The extra copy and tool-routing directive are also added after the 120,000-character limit, so the final request is no longer bounded by MAX_PROMPT_CHARS.

Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/jcode-provider-cursor-runtime/src/lib.rs
Line: 563-573

Comment:
**Avoid Duplicate System Prompts**

`build_cli_prompt(system, messages)` already includes the system text, but this call passes both that assembled prompt and the original `system` to the transport. `routed_prompt` then prepends the system text again. Every non-empty system instruction is sent twice with different formatting, which changes instruction precedence. The extra copy and tool-routing directive are also added after the 120,000-character limit, so the final request is no longer bounded by `MAX_PROMPT_CHARS`.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Comment on lines +328 to +331
if !tools.is_empty() {
if let Ok(mcp_tools_bytes) = crate::wire::encode_mcp_tools(tools) {
req.extend(field_ld(4, &mcp_tools_bytes));
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Propagate Tool Encoding Failures

If any advertised tool has a schema the bounded protobuf encoder cannot represent, such as more than 32 nested object or array levels, encode_mcp_tools returns an error that this branch silently discards. The request then proceeds without the entire tool payload. Because this provider reports that it handles tools internally, jcode has no fallback execution path, so the requested tools cannot run. Propagate the encoding error instead of sending a request without its tools.

Artifacts

Evidence from the check

  • Copies the target crate outside the repository, appends a narrow runtime test, and runs both the valid and unencodable schema cases; it provides the exact executed source.

Command output from the check

  • Runs the valid-tool control against the copied target crate with exit code 0 and reports `baseline valid_tool_field4_present=true`; valid schemas are advertised.

Command output from the check

  • Runs the deeply nested tool schema case against the copied target crate with exit code 0 and reports the depth-limit error, one emitted request frame, and missing field 4; the error is silently converted into a tools-free request.

View artifacts

T-Rex Ran code and verified through T-Rex

Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/jcode-provider-cursor-runtime/src/agent_transport.rs
Line: 328-331

Comment:
**Propagate Tool Encoding Failures**

If any advertised tool has a schema the bounded protobuf encoder cannot represent, such as more than 32 nested object or array levels, `encode_mcp_tools` returns an error that this branch silently discards. The request then proceeds without the entire tool payload. Because this provider reports that it handles tools internally, jcode has no fallback execution path, so the requested tools cannot run. Propagate the encoding error instead of sending a request without its tools.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Comment on lines 378 to +389
let len = u32::from_be_bytes([buf[1], buf[2], buf[3], buf[4]]) as usize;
let end = 5 + len;
if buf.len() < end {
return None;
return Ok(None);
}
let mut payload = buf[5..end].to_vec();
if flag & 0x01 != 0 {
// gzip-compressed payload
let mut decoded = Vec::new();
if GzDecoder::new(&payload[..])
GzDecoder::new(&payload[..])
.read_to_end(&mut decoded)
.is_ok()
{
payload = decoded;
}
.context("Invalid gzip payload in Cursor agent stream")?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 security Bound Response Memory Usage

The response path allocates from peer-controlled data without a byte limit. An incomplete frame can grow pending toward its declared 32-bit length, gzip decoding uses unbounded read_to_end, and decoded KV blobs are retained in an unbounded map. A configured or compromised AgentService peer can therefore send a large frame, compression bomb, or repeated unique blobs and exhaust the jcode process before the time deadline. Enforce limits for compressed payloads, decompressed payloads, the pending buffer, and aggregate blob storage.

How this was verified: A response reproduction retained 8,388,613 bytes for an incomplete 4 GiB frame, expanded 16,328 compressed bytes to 16,777,216 bytes, and retained 12 unique peer blobs totaling 12,582,960 bytes.

Artifacts

Evidence from the check

  • This read-only script extracts and executes the exact pre-change and candidate `next_frame` implementations with incomplete-frame and gzip inputs, showing that both allocate without a bound.

Command output from the check

  • The executed pre-change decoder test retained 8,388,613 pending bytes for a 4 GiB declared frame and expanded 16,328 compressed bytes to 16,777,216 bytes, showing the framing and gzip issue already existed.

Command output from the check

  • The executed candidate decoder test produced the same unbounded pending-buffer and gzip-expansion result, showing the candidate retains those response allocation paths.

Evidence from the check

  • This read-only script compares pre-change source and runs a standalone protobuf harness matching the candidate KV set-blob response handling, showing unique peer blobs accumulate.

Command output from the check

  • The executed pre-change comparison found zero `blob_store.insert` and zero `set_blob` occurrences, showing this blob-retention behavior was not present before the candidate commit.

Command output from the check

  • The executed candidate KV handler reproduction stored 12 unique 1 MiB peer blobs as 12 entries and 12,582,960 retained bytes, showing unbounded aggregate retention.

View artifacts

T-Rex Ran code and verified through T-Rex

Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/jcode-provider-cursor-runtime/src/agent_transport.rs
Line: 378-389

Comment:
**Bound Response Memory Usage**

The response path allocates from peer-controlled data without a byte limit. An incomplete frame can grow `pending` toward its declared 32-bit length, gzip decoding uses unbounded `read_to_end`, and decoded KV blobs are retained in an unbounded map. A configured or compromised AgentService peer can therefore send a large frame, compression bomb, or repeated unique blobs and exhaust the jcode process before the time deadline. Enforce limits for compressed payloads, decompressed payloads, the pending buffer, and aggregate blob storage.

**How this was verified:** A response reproduction retained 8,388,613 bytes for an incomplete 4 GiB frame, expanded 16,328 compressed bytes to 16,777,216 bytes, and retained 12 unique peer blobs totaling 12,582,960 bytes.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Comment on lines +49 to +53
fn stream_debug(message: impl std::fmt::Display) {
if std::env::var_os("CURSOR_STREAM_DEBUG").is_some() {
eprintln!("cursor-stream: {message}");
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Remove Debug Diagnostic

stream_debug commits an eprintln!-based transport diagnostic. The repository directive permits eprintln! only for throwaway diagnostics that are deleted before committing. Remove this path or route durable diagnostics through the supported logging mechanism. This repository requirement must be satisfied before merging.

Context Used: AGENTS.md (source)

Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/jcode-provider-cursor-runtime/src/agent_transport.rs
Line: 49-53

Comment:
**Remove Debug Diagnostic**

`stream_debug` commits an `eprintln!`-based transport diagnostic. The repository directive permits `eprintln!` only for throwaway diagnostics that are deleted before committing. Remove this path or route durable diagnostics through the supported logging mechanism. This repository requirement must be satisfied before merging.

**Context Used:** AGENTS.md ([source](https://github.com/1jehuang/jcode/blob/master/AGENTS.md))

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@yansigit
yansigit force-pushed the feat/cursor-native-tools-mcp branch from b5cb0e5 to 48cdbd8 Compare September 16, 2026 01:47
@1jehuang 1jehuang added area: providers Model providers, API adapters, and provider authentication. area: tools Agent tools, integrations, and tool execution. security Security hardening or security-sensitive changes. type: feature Adds a new user-facing capability. and removed area: providers Model providers, API adapters, and provider authentication. area: tools Agent tools, integrations, and tool execution. type: feature Adds a new user-facing capability. labels Sep 19, 2026
@github-actions github-actions Bot added area: providers Model providers, API adapters, and provider authentication. area: tools Agent tools, integrations, and tool execution. type: feature Adds a new user-facing capability. labels Sep 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: providers Model providers, API adapters, and provider authentication. area: tools Agent tools, integrations, and tool execution. security Security hardening or security-sensitive changes. type: feature Adds a new user-facing capability.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Cursor AgentService native transport needs MCP tool bridging and resilient stream handling

3 participants