cursor-opencode-provider 0.7.3 → 0.7.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3406,7 +3406,7 @@ export function buildOpenCodeInteractionGuidance(tools, isCompaction, workspaceR
3406
3406
  instructions.push(shell
3407
3407
  ? `- OpenCode \`execute\` is Code Mode JavaScript (\`code\`); it is not a shell. For OS commands, call OpenCode ${shell}. Do not pass \`command\` to \`execute\`.`
3408
3408
  : "- OpenCode `execute` is Code Mode JavaScript (`code`); it is not a shell. Do not pass `command` to `execute`.");
3409
- instructions.push("- When the host's Code Mode catalog lists additional tools, including MCP server tools, call them inside `execute` through `tools`. Use only the exact paths and signatures in that catalog or returned by its `search` function. Call `execute` with `{ code }` to run them; do not request a Code Mode tool as a direct OpenCode tool call.");
3409
+ instructions.push("- Call tools named in the direct list by their own names, even when a server instruction says to reach them through `execute`. Use `execute` only for tools that appear in the host Code Mode catalog and are absent from that list. Use the exact paths and signatures from that catalog or its `search` function, and call `execute` with `{ code }`.");
3410
3410
  }
3411
3411
  if (names.has("task") || names.has("subagent")) {
3412
3412
  const target = names.has("task") ? "`task`" : "`subagent`";
@@ -0,0 +1,26 @@
1
+ import type { ToolDraft } from "./types.js";
2
+ /**
3
+ * OpenCode 2 puts an MCP server's tools in Code Mode unless that server's
4
+ * config sets `codemode: false` (`packages/schema/src/mcp.ts`, applied in
5
+ * `packages/core/src/tool/mcp.ts`). Only `options.codemode === false` joins
6
+ * the AI SDK catalog (`packages/core/src/tool.ts`). Cursor can call a tool
7
+ * only when it is in that catalog.
8
+ *
9
+ * `config.codemode` is also the remote-connection switch: while it is not
10
+ * false, OpenCode appends `?codemode=false` so servers that bundle their own
11
+ * Code Mode return individual tools (`packages/core/src/mcp/client.ts`).
12
+ * This module therefore leaves server config alone and clears the tool option
13
+ * instead. An explicit server `codemode: true` stays in Code Mode. OpenCode's
14
+ * own namespaced tools (the `opencode` namespace) are not MCP servers and are
15
+ * left alone.
16
+ *
17
+ * The tool registry is location-scoped, so this placement is shared by every
18
+ * provider in the process. `"codemode": true` on a server is the per-server
19
+ * way to keep that server inside `execute`.
20
+ */
21
+ export declare function mcpServerNamespace(server: string): string;
22
+ export declare function rememberDirectMcpNamespaces(target: Set<string>, servers: readonly (readonly [string, {
23
+ readonly codemode?: boolean;
24
+ }])[]): void;
25
+ /** Move tools from the recorded MCP namespaces onto the direct catalog. */
26
+ export declare function exposeDirectMcpTools(editor: ToolDraft, namespaces: ReadonlySet<string>): void;
@@ -0,0 +1,60 @@
1
+ /**
2
+ * OpenCode 2 puts an MCP server's tools in Code Mode unless that server's
3
+ * config sets `codemode: false` (`packages/schema/src/mcp.ts`, applied in
4
+ * `packages/core/src/tool/mcp.ts`). Only `options.codemode === false` joins
5
+ * the AI SDK catalog (`packages/core/src/tool.ts`). Cursor can call a tool
6
+ * only when it is in that catalog.
7
+ *
8
+ * `config.codemode` is also the remote-connection switch: while it is not
9
+ * false, OpenCode appends `?codemode=false` so servers that bundle their own
10
+ * Code Mode return individual tools (`packages/core/src/mcp/client.ts`).
11
+ * This module therefore leaves server config alone and clears the tool option
12
+ * instead. An explicit server `codemode: true` stays in Code Mode. OpenCode's
13
+ * own namespaced tools (the `opencode` namespace) are not MCP servers and are
14
+ * left alone.
15
+ *
16
+ * The tool registry is location-scoped, so this placement is shared by every
17
+ * provider in the process. `"codemode": true` on a server is the per-server
18
+ * way to keep that server inside `execute`.
19
+ */
20
+ export function mcpServerNamespace(server) {
21
+ return server.replace(/[^a-zA-Z0-9_-]/g, "_");
22
+ }
23
+ export function rememberDirectMcpNamespaces(target, servers) {
24
+ target.clear();
25
+ // The editor exposes normalized namespaces, not server ownership. Keep
26
+ // ambiguous namespaces in their host-selected placement: otherwise a server
27
+ // named `opencode` moves builtins too, or `my.docs` overrides an explicit
28
+ // Code Mode choice on `my_docs`. Exclusions must win regardless of order.
29
+ const excluded = new Set(["opencode"]);
30
+ for (const [name, config] of servers) {
31
+ const namespace = mcpServerNamespace(name);
32
+ if (config.codemode === true)
33
+ excluded.add(namespace);
34
+ else
35
+ target.add(namespace);
36
+ }
37
+ for (const namespace of excluded)
38
+ target.delete(namespace);
39
+ }
40
+ /** Move tools from the recorded MCP namespaces onto the direct catalog. */
41
+ export function exposeDirectMcpTools(editor, namespaces) {
42
+ if (namespaces.size === 0 || !editor.list || !editor.update)
43
+ return;
44
+ for (const tool of editor.list()) {
45
+ const namespace = tool.options?.namespace;
46
+ if (!namespace || !namespaces.has(namespace))
47
+ continue;
48
+ if (tool.options?.codemode === false)
49
+ continue;
50
+ editor.update(tool.id, (draft) => {
51
+ const options = {};
52
+ if (draft.options?.namespace !== undefined)
53
+ options.namespace = draft.options.namespace;
54
+ if (draft.options?.permission !== undefined)
55
+ options.permission = draft.options.permission;
56
+ options.codemode = false;
57
+ draft.options = options;
58
+ });
59
+ }
60
+ }
@@ -223,6 +223,15 @@ export type ToolDraft = {
223
223
  list?(): readonly (ToolDefinition & {
224
224
  readonly id: string;
225
225
  })[];
226
+ /** Host ToolEditor.update — missing ids are ignored. */
227
+ update?(id: string, update: (tool: {
228
+ options?: {
229
+ namespace?: string;
230
+ permission?: string;
231
+ codemode?: boolean;
232
+ pinned?: boolean;
233
+ };
234
+ }) => void): void;
226
235
  };
227
236
  export type ToolHookBaseFields = {
228
237
  readonly tool: string;
@@ -368,6 +377,20 @@ export type WebSearchDomain = {
368
377
  readonly transform: Transform<WebSearchEditor>;
369
378
  readonly reload: () => Promise<void>;
370
379
  };
380
+ /**
381
+ * MCP config reader this plugin uses. Server `codemode` is observed and not
382
+ * written: writing it also changes the remote raw-tool URL. The host
383
+ * `MCPEditor` is a superset.
384
+ */
385
+ export type McpServerConfig = {
386
+ readonly codemode?: boolean;
387
+ };
388
+ export type McpEditor = {
389
+ list(): readonly (readonly [string, McpServerConfig])[];
390
+ };
391
+ export type McpDomain = {
392
+ readonly transform: Transform<McpEditor>;
393
+ };
371
394
  export type PluginLocation = {
372
395
  readonly directory: string;
373
396
  };
@@ -381,6 +404,7 @@ export type PluginContext = {
381
404
  readonly location?: PluginLocation;
382
405
  readonly shell?: ShellDomain;
383
406
  readonly websearch?: WebSearchDomain;
407
+ readonly mcp?: McpDomain;
384
408
  };
385
409
  export type Cleanup = () => Promise<void> | void;
386
410
  export type Plugin2 = {
@@ -8,6 +8,7 @@ import { fetchOpenCodeWebSearchText, parseExaWebSearchResults, } from "./web-too
8
8
  import { captureCursorShellResult, cursorShellEnvForCommand, prepareCursorShellArgs, releaseCursorShellEnv, sanitizeRegisteredCursorShellOutput, } from "./shell-timeout.js";
9
9
  import { applyCursorProviderInventory, CURSOR_INTEGRATION_ID } from "./opencode2/catalog.js";
10
10
  import { applyCursorIntegration, resolveCursorAccessToken } from "./opencode2/integration.js";
11
+ import { exposeDirectMcpTools, rememberDirectMcpNamespaces } from "./opencode2/mcp-direct.js";
11
12
  import { registerTodoTools } from "./opencode2/todo-tools.js";
12
13
  import { OPENCODE_2_TOOL_DIALECT } from "./protocol/tools.js";
13
14
  import { clearSessionTodos } from "./todo-store.js";
@@ -250,12 +251,25 @@ const plugin = {
250
251
  });
251
252
  }));
252
253
  }
254
+ // Namespaces whose tools belong on the direct catalog. Filled by the MCP
255
+ // transform (config is not written) and read when tool transforms replay,
256
+ // including after a later MCP discovery reload.
257
+ const directMcpNamespaces = new Set();
258
+ if (ctx.mcp) {
259
+ await track(ctx.mcp.transform((editor) => {
260
+ rememberDirectMcpNamespaces(directMcpNamespaces, editor.list());
261
+ }));
262
+ }
253
263
  await track(ctx.tool.transform((draft) => {
254
264
  // OpenCode 2 dropped host todowrite/todoread. Off by default
255
265
  // (`CURSOR_OPENCODE2_TODOS=1`/`true` force-enables). When on, register
256
266
  // them as direct catalog tools (`codemode: false` + output schema) if
257
267
  // the host does not already own those names. When off, register none.
258
268
  registerTodoTools(draft);
269
+ // MCP tools default into Code Mode. Move every server that did not
270
+ // explicitly opt in onto the direct catalog so Cursor can call them
271
+ // by name. See `opencode2/mcp-direct.ts`.
272
+ exposeDirectMcpTools(draft, directMcpNamespaces);
259
273
  }));
260
274
  // ── Shell timeout wrapper ────────────────────────────────
261
275
  await track(ctx.tool.hook("execute.before", (event) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cursor-opencode-provider",
3
- "version": "0.7.3",
3
+ "version": "0.7.4",
4
4
  "description": "Use Cursor subscription models from OpenCode via Cursor's Connect-RPC agent protocol",
5
5
  "type": "module",
6
6
  "license": "MIT",