Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
},
"dependencies": {
"@anthropic-ai/claude-agent-sdk": "^0.3.276",
"@cursor/sdk": "1.0.38",
"@cursor/sdk": "1.2.0",
"@effect/platform-node": "catalog:",
"@effect/platform-node-shared": "catalog:",
"@ff-labs/fff-node": "0.11.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,9 @@ export function makeCursorAgentSdkReplayRunner(

const waitForRun = <Error>(
runId: string,
sendInput: CursorAgentSdk.CursorAgentSdkSendInput<Error>,
runInput: {
readonly onDelta?: (update: InteractionUpdate) => Effect.Effect<void, Error>;
},
): Effect.Effect<RunResult, CursorAgentSdkReplayError> =>
Effect.gen(function* () {
while (true) {
Expand Down Expand Up @@ -364,7 +366,7 @@ export function makeCursorAgentSdkReplayRunner(
);
}
advance();
yield* (sendInput.onDelta?.(frame.update) ?? Effect.void).pipe(
yield* (runInput.onDelta?.(frame.update) ?? Effect.void).pipe(
Effect.mapError((cause) =>
recordFailure(
new CursorReplayRuntimeError({
Expand Down Expand Up @@ -446,6 +448,34 @@ export function makeCursorAgentSdkReplayRunner(
},
catch: (cause) => replayRunnerError(transcript, cause, "replay.session.send"),
}),
summarize: (summarizeInput) =>
Effect.try({
try: () => {
assertOutbound({
type: "run.summarize",
});
const started = consumeInbound("run.started");
const run: CursorAgentSdk.CursorAgentSdkRun = {
runId: started.runId,
agentId: started.agentId,
wait: waitForRun(started.runId, summarizeInput ?? {}).pipe(
Effect.mapError((cause) =>
replayRunnerError(transcript, cause, "replay.run.wait"),
),
),
cancel: Effect.try({
try: () =>
assertOutbound({
type: "run.cancel",
runId: started.runId,
}),
catch: (cause) => replayRunnerError(transcript, cause, "replay.run.cancel"),
}),
};
return run;
},
catch: (cause) => replayRunnerError(transcript, cause, "replay.session.summarize"),
}),
listMessages: Effect.try({
try: () => {
assertOutbound({
Expand Down
7 changes: 5 additions & 2 deletions docs/orchestration-v2/orchestrator-mcp-server.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,11 @@ server headers or token.
### Cursor Agent SDK V2

Cursor receives the same authenticated HTTP MCP endpoint through the SDK's
`mcpServers` agent and send options. The adapter passes the authorization header
to the SDK but projects only redacted option metadata into protocol diagnostics.
`mcpServers` on `Agent.create` / `Agent.resume` and each `send`. With
`@cursor/sdk` 1.0.39+, send-level servers replace only the create-time inline
set; ambient servers from `settingSources` (`project`, `user`, `team`, `mdm`,
`plugins`) stay available. The adapter passes the authorization header to the
SDK but projects only redacted option metadata into protocol diagnostics.

### Grok ACP V2

Expand Down
16 changes: 14 additions & 2 deletions docs/user/cursor.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,17 @@ The adapter currently uses the SDK's local-agent runtime so runs operate in the
workspace. Cursor cloud agents need repository and cloud-environment configuration that T3 Code does
not expose yet.

Custom Cursor MCP servers and plugins are not configured in T3 Code. They load from the environment
machine's Cursor config when the agent opens: project `.cursor/mcp.json`, user `~/.cursor/mcp.json`,
and installed plugins under `~/.cursor/plugins`. Plugin tools
can appear without a login; OAuth MCP servers still need credentials. Run `agent mcp login <server>`
with the cwd set to the T3 project root (tokens land in
`~/.cursor/projects/<folder-slug>/mcp-auth.json`). Cursor desktop Connections auth does not write
that file, and the local SDK cannot complete interactive MCP OAuth itself—auth attempts fail with
messages such as "MCP authentication is not supported in local SDK runs" or that the auth server
does not support dynamic client registration. After you add or change skills, plugins, or MCP
servers, or after a successful CLI login, use **Restart agent session**.

## Sign in

Choose **Sign in**, then open the sign-in page and complete it in your browser. T3 Code
Expand All @@ -36,10 +47,11 @@ The adapter supports:
- creating and resuming local Cursor agent threads;
- changing the model and model parameters between turns;
- assistant text, reasoning, tool activity, plans, and todo streaming;
- thread-scoped T3 Code MCP tools;
- thread-scoped T3 Code MCP tools alongside ambient Cursor MCP/plugins from disk;
- image attachments;
- interruption, queued app messages, and orchestrator-owned interrupt/restart steering;
- provider conversation snapshots through `Agent.messages.list()`;
- native conversation compaction;
- provider conversation snapshots;
- Cursor `task` subagents, projected as read-only child app threads with their tool activity and
final result.

Expand Down
2 changes: 1 addition & 1 deletion packages/provider-cursor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
"test": "vp test run --config ../../vite.config.ts --dir ."
},
"dependencies": {
"@cursor/sdk": "1.0.38",
"@cursor/sdk": "1.2.0",
"@napi-rs/keyring": "^1.3.0",
"@t3tools/contracts": "workspace:*",
"@t3tools/provider-core": "workspace:*",
Expand Down
51 changes: 51 additions & 0 deletions packages/provider-cursor/src/server/CursorAgentSdk.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,17 +49,34 @@ const cursorSdkMock = vi.hoisted(() => {
agentId: "agent-cursor-agent-sdk-test",
wait: runWait,
cancel: runCancel,
stream: async function* () {},
};
},
);
const summarize = vi.fn(async () => {
runOperations.push("run.summarize");
return {
id: "run-cursor-agent-sdk-summarize",
agentId: "agent-cursor-agent-sdk-test",
wait: runWait,
cancel: runCancel,
stream: async function* () {
yield { type: "summary", status: "running" };
yield { type: "summary", status: "completed" };
yield { type: "summary", status: "error" };
},
};
});
const create = vi.fn(async () => ({
agentId: "agent-cursor-agent-sdk-test",
send,
summarize,
close: agentClose,
}));
const resume = vi.fn(async () => ({
agentId: "agent-cursor-agent-sdk-test",
send,
summarize,
close: agentClose,
}));
const listRuns = vi.fn(async () => {
Expand All @@ -84,6 +101,7 @@ const cursorSdkMock = vi.hoisted(() => {
runCancel,
runWait,
send,
summarize,
setAgentCloseFailure: (failure: unknown) => {
agentCloseFailure = failure;
},
Expand Down Expand Up @@ -172,4 +190,37 @@ describe("CursorAgentSdkRunner", () => {
]);
}).pipe(Effect.provide(layerTest)),
);

it.effect("pumps summarize stream updates without deadlocking wait", () =>
Effect.gen(function* () {
cursorSdkMock.runOperations.length = 0;
cursorSdkMock.summarize.mockClear();
cursorSdkMock.setStaleRunActive(false);

const updates: Array<unknown> = [];
const runner = yield* CursorAgentSdk.CursorAgentSdkRunner;
const session = yield* runner.open({
operation: "create",
options: { model: { id: "default" }, mode: "agent", local: { cwd: process.cwd() } },
threadId: ThreadId.make("thread-cursor-agent-sdk-summarize-test"),
providerSessionId: ProviderSessionId.make(
"provider-session-cursor-agent-sdk-summarize-test",
),
});
const run = yield* session.summarize({
onDelta: (update) =>
Effect.sync(() => {
updates.push(update);
}),
});
yield* run.wait;

assert.deepStrictEqual(cursorSdkMock.runOperations, ["run.summarize"]);
assert.deepStrictEqual(updates, [
{ type: "summary-started" },
{ type: "summary-completed" },
{ type: "summary-completed", failed: true },
]);
}).pipe(Effect.provide(layerTest)),
);
});
Loading
Loading