diff --git a/.changeset/cloudflare-mcp-codemode-opt-out.md b/.changeset/cloudflare-mcp-codemode-opt-out.md
deleted file mode 100644
index 780b98d435..0000000000
--- a/.changeset/cloudflare-mcp-codemode-opt-out.md
+++ /dev/null
@@ -1,8 +0,0 @@
----
-"@executor-js/plugin-mcp": patch
-"@executor-js/plugin-openapi": patch
----
-
-**Cloudflare ships as MCP-only, with code mode opted out**
-
-The Cloudflare OpenAPI preset is gone from the default catalog; the MCP preset is the one Cloudflare entry. Its endpoint now pins `?codemode=false` because Cloudflare's MCP server otherwise hides the tool catalog behind a single code-execution tool, and executor already provides the code-execution surface. Hand-entered `mcp.cloudflare.com` URLs missing the opt-out get an inline warning in the add flow telling the user to append `?codemode=false`.
diff --git a/.changeset/gmail-settings-access.md b/.changeset/gmail-settings-access.md
deleted file mode 100644
index 8fe9f8e43a..0000000000
--- a/.changeset/gmail-settings-access.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-"@executor-js/plugin-openapi": patch
----
-
-Request Gmail's basic-settings scope alongside full mailbox access so Google integrations can create and manage Gmail filters without including domain-admin-only sharing settings.
diff --git a/.changeset/lean-connection-health.md b/.changeset/lean-connection-health.md
deleted file mode 100644
index 02577c496f..0000000000
--- a/.changeset/lean-connection-health.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-"@executor-js/sdk": patch
----
-
-Keep `connections.list` health output compact unless callers opt into diagnostics with `verbose: true`. Default list responses now retain only the health status, identity, and check timestamp; verbose responses continue to include HTTP status, diagnostic detail, and bounded upstream response samples.
diff --git a/.changeset/oauth-popup-reserve-on-click.md b/.changeset/oauth-popup-reserve-on-click.md
deleted file mode 100644
index cf8d9430a5..0000000000
--- a/.changeset/oauth-popup-reserve-on-click.md
+++ /dev/null
@@ -1,9 +0,0 @@
----
-"@executor-js/react": patch
----
-
-**A slow OAuth discovery no longer kills the connect with no popup and no error**
-
-The transparent connect flows opened the sign-in window only after their setup round trips had answered: DCR after probe and dynamic registration, CIMD after minting the client, reconnect after starting the session. `window.open` needs transient user activation, which browsers expire a few seconds after the click, so once the API was slow enough the browser refused the window and the connect ended with nothing on screen but the button returning to "Connect". Every MCP integration takes that path.
-
-The window is now claimed on the click itself and navigated when the authorization URL arrives, however long that takes, and it is closed again on the paths that end without signing in (failed probe, no registration endpoint, rejected registration, failed client mint) as well as on cancel and unmount. A window the browser does refuse is now reported instead of swallowed: the flows stop before their round trips, and the sign-in error renders above the dialog footer, where the automatic flows can actually show it, rather than inside a method tab panel they never render.
diff --git a/.changeset/stdio-interrupt-child-cleanup.md b/.changeset/stdio-interrupt-child-cleanup.md
deleted file mode 100644
index 687598ade8..0000000000
--- a/.changeset/stdio-interrupt-child-cleanup.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-"@executor-js/plugin-mcp": patch
----
-
-**Interrupted stdio dials no longer strand the spawned child process**
-
-Cancelling an in-flight health check or tool discovery (a UI refresh aborting the request, or the 15s discovery timeout) abandoned the MCP connect handshake without closing the transport, leaving the spawned stdio child running indefinitely: for `docker run -i --rm` integrations, one stranded container per interrupted dial. The connect handshake now aborts on interruption (the SDK closes the transport, ending stdin and escalating to SIGTERM/SIGKILL), and tool discovery closes the connection even when the interrupt lands between the handshake completing and discovery starting.
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 2c340085a8..a9daf5378b 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -304,7 +304,9 @@ jobs:
if: matrix.target == 'cloud'
env:
MCP_SESSION_TIMEOUT_MS: "3000"
- MCP_PAUSED_SESSION_IDLE_TIMEOUT_MS: "6000"
+ # Still 18x shorter than production, but long enough for a cold Vite
+ # resume route to compile under a fully loaded CI runner.
+ MCP_PAUSED_SESSION_IDLE_TIMEOUT_MS: "30000"
run: bun scripts/run-ci-shard.ts cloud ${{ matrix['shard-index'] }}
working-directory: e2e
diff --git a/README.md b/README.md
index e66ea111da..cbf860f1bd 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,7 @@ and per-tool policies, then use that one catalog from any MCP-compatible agent.
[Website](https://executor.sh) · [Documentation](https://executor.sh/docs) · [Discord](https://discord.gg/eF29HBHwM6)
-[https://github.com/user-attachments/assets/11225f83-e848-42ba-99b2-a993bcc88dad](https://github.com/user-attachments/assets/11225f83-e848-42ba-99b2-a993bcc88dad)
+
## Why Executor
diff --git a/apps/cli/CHANGELOG.md b/apps/cli/CHANGELOG.md
index 4144daee42..246471d271 100644
--- a/apps/cli/CHANGELOG.md
+++ b/apps/cli/CHANGELOG.md
@@ -1,5 +1,17 @@
# executor
+## 1.6.0
+
+### Patch Changes
+
+- [#1737](https://github.com/UsefulSoftwareCo/executor/pull/1737) [`9296f36`](https://github.com/UsefulSoftwareCo/executor/commit/9296f36a8adbfdeec700ce33c37987127857b2fd) Thanks [@RhysSullivan](https://github.com/RhysSullivan)! - Scope the `skills` tool to Executor's own documentation. Its description, argument, index, and unknown-name error now state that it serves a fixed catalog of how-to docs for this server's tools, so an agent on a host without a skill tool of its own no longer reads it as a general reader for the harness's or the user's skills.
+
+- Updated dependencies [[`a2d1417`](https://github.com/UsefulSoftwareCo/executor/commit/a2d141758e478274813c8c24d354e1fd0f66af49)]:
+ - @executor-js/sdk@1.6.0
+ - @executor-js/local@1.6.0
+ - @executor-js/api@1.4.63
+ - @executor-js/runtime-quickjs@1.6.0
+
## 1.5.42
### Patch Changes
diff --git a/apps/cli/package.json b/apps/cli/package.json
index 8daeeb7e5f..a21ce368d8 100644
--- a/apps/cli/package.json
+++ b/apps/cli/package.json
@@ -1,6 +1,6 @@
{
"name": "executor",
- "version": "1.5.42",
+ "version": "1.6.0",
"private": true,
"bin": {
"executor": "./bin/executor.ts"
diff --git a/apps/cloud/CHANGELOG.md b/apps/cloud/CHANGELOG.md
index f7651fd1eb..69496f303b 100644
--- a/apps/cloud/CHANGELOG.md
+++ b/apps/cloud/CHANGELOG.md
@@ -1,5 +1,26 @@
# @executor-js/cloud
+## 1.4.61
+
+### Patch Changes
+
+- Updated dependencies [[`c11bef2`](https://github.com/UsefulSoftwareCo/executor/commit/c11bef2cd049db7bbf51b15e18761b14acccb534), [`46cea2c`](https://github.com/UsefulSoftwareCo/executor/commit/46cea2cbb1f414ae58ac876819a51b11967909a6), [`a2d1417`](https://github.com/UsefulSoftwareCo/executor/commit/a2d141758e478274813c8c24d354e1fd0f66af49), [`2bdbedf`](https://github.com/UsefulSoftwareCo/executor/commit/2bdbedf257f54d7c209e8c856c618174c10d6bb3), [`0b0b74f`](https://github.com/UsefulSoftwareCo/executor/commit/0b0b74f673b8098c5248159be36c648097f3c87b), [`256e25e`](https://github.com/UsefulSoftwareCo/executor/commit/256e25e7b291b0c023bc7547d092004b66781bba)]:
+ - @executor-js/plugin-mcp@1.6.0
+ - @executor-js/plugin-openapi@1.6.0
+ - @executor-js/sdk@1.6.0
+ - @executor-js/react@1.4.63
+ - @executor-js/runtime-dynamic-worker@1.4.4
+ - @executor-js/api@1.4.63
+ - @executor-js/execution@1.6.0
+ - @executor-js/vite-plugin@0.0.60
+ - @executor-js/cloudflare@0.0.42
+ - @executor-js/host-mcp@1.4.4
+ - @executor-js/mcp-apps-shell@1.4.11
+ - @executor-js/plugin-graphql@1.6.0
+ - @executor-js/plugin-toolkits@1.5.35
+ - @executor-js/plugin-workos-vault@0.0.2
+ - @executor-js/runtime-quickjs@1.6.0
+
## 1.4.60
### Patch Changes
diff --git a/apps/cloud/package.json b/apps/cloud/package.json
index df90fee0c0..a9add70284 100644
--- a/apps/cloud/package.json
+++ b/apps/cloud/package.json
@@ -1,6 +1,6 @@
{
"name": "@executor-js/cloud",
- "version": "1.4.60",
+ "version": "1.4.61",
"private": true,
"type": "module",
"scripts": {
diff --git a/apps/desktop/CHANGELOG.md b/apps/desktop/CHANGELOG.md
index 69f0c90c55..1eabb33d0d 100644
--- a/apps/desktop/CHANGELOG.md
+++ b/apps/desktop/CHANGELOG.md
@@ -1,5 +1,7 @@
# @executor-js/desktop
+## 1.6.0
+
## 1.5.42
## 1.5.41
diff --git a/apps/desktop/package.json b/apps/desktop/package.json
index 366b730be8..d068c07399 100644
--- a/apps/desktop/package.json
+++ b/apps/desktop/package.json
@@ -1,6 +1,6 @@
{
"name": "@executor-js/desktop",
- "version": "1.5.42",
+ "version": "1.6.0",
"private": true,
"homepage": "https://github.com/UsefulSoftwareCo/executor",
"license": "MIT",
diff --git a/apps/host-selfhost/CHANGELOG.md b/apps/host-selfhost/CHANGELOG.md
index cf5b60f9a6..09803bcb1a 100644
--- a/apps/host-selfhost/CHANGELOG.md
+++ b/apps/host-selfhost/CHANGELOG.md
@@ -1,5 +1,26 @@
# @executor-js/host-selfhost
+## 0.0.42
+
+### Patch Changes
+
+- Updated dependencies [[`c11bef2`](https://github.com/UsefulSoftwareCo/executor/commit/c11bef2cd049db7bbf51b15e18761b14acccb534), [`46cea2c`](https://github.com/UsefulSoftwareCo/executor/commit/46cea2cbb1f414ae58ac876819a51b11967909a6), [`a2d1417`](https://github.com/UsefulSoftwareCo/executor/commit/a2d141758e478274813c8c24d354e1fd0f66af49), [`2bdbedf`](https://github.com/UsefulSoftwareCo/executor/commit/2bdbedf257f54d7c209e8c856c618174c10d6bb3), [`0b0b74f`](https://github.com/UsefulSoftwareCo/executor/commit/0b0b74f673b8098c5248159be36c648097f3c87b), [`256e25e`](https://github.com/UsefulSoftwareCo/executor/commit/256e25e7b291b0c023bc7547d092004b66781bba)]:
+ - @executor-js/plugin-mcp@1.6.0
+ - @executor-js/plugin-openapi@1.6.0
+ - @executor-js/sdk@1.6.0
+ - @executor-js/react@1.4.63
+ - @executor-js/plugin-provider-service-split@0.0.14
+ - @executor-js/app@1.4.4
+ - @executor-js/analytics@0.1.7
+ - @executor-js/api@1.4.63
+ - @executor-js/execution@1.6.0
+ - @executor-js/host-mcp@1.4.4
+ - @executor-js/mcp-apps-shell@1.4.11
+ - @executor-js/plugin-encrypted-secrets@0.0.42
+ - @executor-js/plugin-graphql@1.6.0
+ - @executor-js/plugin-toolkits@1.5.35
+ - @executor-js/runtime-quickjs@1.6.0
+
## 0.0.41
### Patch Changes
diff --git a/apps/host-selfhost/package.json b/apps/host-selfhost/package.json
index 3b64081aa4..a78ce49f7f 100644
--- a/apps/host-selfhost/package.json
+++ b/apps/host-selfhost/package.json
@@ -1,6 +1,6 @@
{
"name": "@executor-js/host-selfhost",
- "version": "0.0.41",
+ "version": "0.0.42",
"private": true,
"type": "module",
"exports": {
diff --git a/apps/local/CHANGELOG.md b/apps/local/CHANGELOG.md
index 12d187e2fe..755369a8af 100644
--- a/apps/local/CHANGELOG.md
+++ b/apps/local/CHANGELOG.md
@@ -1,5 +1,32 @@
# @executor-js/local
+## 1.6.0
+
+### Patch Changes
+
+- Updated dependencies [[`c11bef2`](https://github.com/UsefulSoftwareCo/executor/commit/c11bef2cd049db7bbf51b15e18761b14acccb534), [`46cea2c`](https://github.com/UsefulSoftwareCo/executor/commit/46cea2cbb1f414ae58ac876819a51b11967909a6), [`a2d1417`](https://github.com/UsefulSoftwareCo/executor/commit/a2d141758e478274813c8c24d354e1fd0f66af49), [`2bdbedf`](https://github.com/UsefulSoftwareCo/executor/commit/2bdbedf257f54d7c209e8c856c618174c10d6bb3), [`0b0b74f`](https://github.com/UsefulSoftwareCo/executor/commit/0b0b74f673b8098c5248159be36c648097f3c87b), [`256e25e`](https://github.com/UsefulSoftwareCo/executor/commit/256e25e7b291b0c023bc7547d092004b66781bba)]:
+ - @executor-js/plugin-mcp@1.6.0
+ - @executor-js/plugin-openapi@1.6.0
+ - @executor-js/sdk@1.6.0
+ - @executor-js/react@1.4.63
+ - @executor-js/plugin-provider-service-split@0.0.14
+ - @executor-js/app@1.4.4
+ - @executor-js/analytics@0.1.7
+ - @executor-js/api@1.4.63
+ - @executor-js/config@1.6.0
+ - @executor-js/execution@1.6.0
+ - @executor-js/vite-plugin@0.0.60
+ - @executor-js/host-mcp@1.4.4
+ - @executor-js/mcp-apps-shell@1.4.11
+ - @executor-js/plugin-desktop-settings@1.6.0
+ - @executor-js/plugin-example@1.6.0
+ - @executor-js/plugin-file-secrets@1.6.0
+ - @executor-js/plugin-graphql@1.6.0
+ - @executor-js/plugin-keychain@1.6.0
+ - @executor-js/plugin-onepassword@1.6.0
+ - @executor-js/plugin-toolkits@1.5.35
+ - @executor-js/runtime-quickjs@1.6.0
+
## 1.5.42
### Patch Changes
diff --git a/apps/local/package.json b/apps/local/package.json
index 43b22005ff..e4ab63c2f4 100644
--- a/apps/local/package.json
+++ b/apps/local/package.json
@@ -1,6 +1,6 @@
{
"name": "@executor-js/local",
- "version": "1.5.42",
+ "version": "1.6.0",
"private": true,
"type": "module",
"exports": {
diff --git a/apps/marketing/src/pages/index.astro b/apps/marketing/src/pages/index.astro
index d147d171c5..cd0940b879 100644
--- a/apps/marketing/src/pages/index.astro
+++ b/apps/marketing/src/pages/index.astro
@@ -133,15 +133,14 @@ Source (and the place to start if something breaks): https://github.com/UsefulSo
- Executor is an integration platform and MCP gateway for AI agents.
- It lets you securely connect services such as Google Workspace,
- GitHub, and Slack, then read data and run the actions you request
- through one endpoint.
+ Executor is an MCP gateway. Anything that speaks MCP, like Claude
+ Code, Cursor, or Codex, points at one endpoint and reaches every
+ tool you connect.
diff --git a/assets/executor-demo.gif b/assets/executor-demo.gif
new file mode 100644
index 0000000000..4fe7d66ba3
Binary files /dev/null and b/assets/executor-demo.gif differ
diff --git a/bun.lock b/bun.lock
index 46d91d7cee..0ca43d8135 100644
--- a/bun.lock
+++ b/bun.lock
@@ -31,7 +31,7 @@
},
"apps/cli": {
"name": "executor",
- "version": "1.5.42",
+ "version": "1.6.0",
"bin": {
"executor": "./bin/executor.ts",
},
@@ -60,7 +60,7 @@
},
"apps/cloud": {
"name": "@executor-js/cloud",
- "version": "1.4.60",
+ "version": "1.4.61",
"dependencies": {
"@cloudflare/vite-plugin": "^1.31.1",
"@effect/atom-react": "catalog:",
@@ -133,7 +133,7 @@
},
"apps/desktop": {
"name": "@executor-js/desktop",
- "version": "1.5.42",
+ "version": "1.6.0",
"dependencies": {
"@sentry/bun": "^10.57.0",
"@sentry/electron": "^7.13.0",
@@ -220,7 +220,7 @@
},
"apps/host-selfhost": {
"name": "@executor-js/host-selfhost",
- "version": "0.0.41",
+ "version": "0.0.42",
"dependencies": {
"@better-auth/api-key": "^1.6.11",
"@cloudflare/worker-bundler": "0.2.1",
@@ -272,7 +272,7 @@
},
"apps/local": {
"name": "@executor-js/local",
- "version": "1.5.42",
+ "version": "1.6.0",
"dependencies": {
"@effect/atom-react": "catalog:",
"@effect/platform-node": "catalog:",
@@ -352,7 +352,7 @@
},
"e2e": {
"name": "@executor-js/e2e",
- "version": "0.0.39",
+ "version": "0.0.40",
"dependencies": {
"@executor-js/api": "workspace:*",
"@executor-js/emulate": "^0.13.9",
@@ -387,7 +387,7 @@
},
"examples/all-plugins": {
"name": "@executor-js/example-all-plugins",
- "version": "0.0.60",
+ "version": "0.0.61",
"dependencies": {
"@executor-js/plugin-file-secrets": "workspace:*",
"@executor-js/plugin-graphql": "workspace:*",
@@ -406,7 +406,7 @@
},
"examples/docs-sdk-quickstart": {
"name": "@executor-js/example-docs-sdk-quickstart",
- "version": "0.0.45",
+ "version": "0.0.46",
"dependencies": {
"@executor-js/plugin-openapi": "workspace:*",
"@executor-js/sdk": "workspace:*",
@@ -461,7 +461,7 @@
},
"packages/core/analytics": {
"name": "@executor-js/analytics",
- "version": "0.1.6",
+ "version": "0.1.7",
"dependencies": {
"@effect/platform-node": "catalog:",
"@executor-js/execution": "workspace:*",
@@ -477,7 +477,7 @@
},
"packages/core/api": {
"name": "@executor-js/api",
- "version": "1.4.62",
+ "version": "1.4.63",
"dependencies": {
"@executor-js/execution": "workspace:*",
"@executor-js/host-mcp": "workspace:*",
@@ -494,7 +494,7 @@
},
"packages/core/cli": {
"name": "@executor-js/cli",
- "version": "0.2.49",
+ "version": "0.2.50",
"bin": {
"executor-sdk": "./dist/index.js",
},
@@ -515,7 +515,7 @@
},
"packages/core/config": {
"name": "@executor-js/config",
- "version": "1.5.42",
+ "version": "1.6.0",
"dependencies": {
"@executor-js/sdk": "workspace:*",
"jiti": "^2.6.1",
@@ -536,7 +536,7 @@
},
"packages/core/execution": {
"name": "@executor-js/execution",
- "version": "1.5.42",
+ "version": "1.6.0",
"dependencies": {
"@executor-js/codemode-core": "workspace:*",
"@executor-js/sdk": "workspace:*",
@@ -602,7 +602,7 @@
},
"packages/core/sdk": {
"name": "@executor-js/sdk",
- "version": "1.5.42",
+ "version": "1.6.0",
"dependencies": {
"@executor-js/fumadb": "workspace:*",
"@standard-schema/spec": "^1.1.0",
@@ -655,7 +655,7 @@
},
"packages/core/vite-plugin": {
"name": "@executor-js/vite-plugin",
- "version": "0.0.59",
+ "version": "0.0.60",
"dependencies": {
"@executor-js/sdk": "workspace:*",
"jiti": "^2.6.1",
@@ -675,7 +675,7 @@
},
"packages/hosts/cloudflare": {
"name": "@executor-js/cloudflare",
- "version": "0.0.41",
+ "version": "0.0.42",
"dependencies": {
"@executor-js/api": "workspace:*",
"@executor-js/execution": "workspace:*",
@@ -716,12 +716,12 @@
},
"packages/hosts/mcp-apps-shell": {
"name": "@executor-js/mcp-apps-shell",
- "version": "1.4.10",
+ "version": "1.4.11",
"dependencies": {
"@executor-js/react": "workspace:*",
"@executor-js/runtime-quickjs": "workspace:*",
"@modelcontextprotocol/ext-apps": "^1.7.4",
- "@modelcontextprotocol/sdk": "^1.12.1",
+ "@modelcontextprotocol/sdk": "^1.29.0",
"@tanstack/react-query": "^5.99.0",
"effect": "catalog:",
"esbuild": "^0.27.7",
@@ -754,7 +754,7 @@
},
"packages/kernel/core": {
"name": "@executor-js/codemode-core",
- "version": "1.5.42",
+ "version": "1.6.0",
"dependencies": {
"@babel/parser": "^7.29.2",
"@standard-schema/spec": "^1.0.0",
@@ -827,7 +827,7 @@
},
"packages/kernel/runtime-quickjs": {
"name": "@executor-js/runtime-quickjs",
- "version": "1.5.42",
+ "version": "1.6.0",
"dependencies": {
"@executor-js/codemode-core": "workspace:*",
"quickjs-emscripten": "catalog:",
@@ -847,7 +847,7 @@
},
"packages/kernel/runtime-workerd-subprocess": {
"name": "@executor-js/runtime-workerd-subprocess",
- "version": "0.0.14",
+ "version": "0.0.15",
"dependencies": {
"@executor-js/codemode-core": "workspace:*",
"effect": "catalog:",
@@ -862,7 +862,7 @@
},
"packages/plugins/desktop-settings": {
"name": "@executor-js/plugin-desktop-settings",
- "version": "1.5.42",
+ "version": "1.6.0",
"dependencies": {
"@executor-js/sdk": "workspace:*",
"react": "catalog:",
@@ -875,7 +875,7 @@
},
"packages/plugins/encrypted-secrets": {
"name": "@executor-js/plugin-encrypted-secrets",
- "version": "0.0.41",
+ "version": "0.0.42",
"dependencies": {
"@executor-js/sdk": "workspace:*",
"effect": "catalog:",
@@ -890,7 +890,7 @@
},
"packages/plugins/example": {
"name": "@executor-js/plugin-example",
- "version": "1.5.42",
+ "version": "1.6.0",
"dependencies": {
"@executor-js/sdk": "workspace:*",
},
@@ -913,7 +913,7 @@
},
"packages/plugins/file-secrets": {
"name": "@executor-js/plugin-file-secrets",
- "version": "1.5.42",
+ "version": "1.6.0",
"dependencies": {
"@executor-js/sdk": "workspace:*",
},
@@ -930,7 +930,7 @@
},
"packages/plugins/graphql": {
"name": "@executor-js/plugin-graphql",
- "version": "1.5.42",
+ "version": "1.6.0",
"dependencies": {
"@effect/platform-node": "catalog:",
"@executor-js/config": "workspace:*",
@@ -969,7 +969,7 @@
},
"packages/plugins/keychain": {
"name": "@executor-js/plugin-keychain",
- "version": "1.5.42",
+ "version": "1.6.0",
"dependencies": {
"@executor-js/sdk": "workspace:*",
"@napi-rs/keyring": "^1.2.0",
@@ -988,12 +988,14 @@
},
"packages/plugins/mcp": {
"name": "@executor-js/plugin-mcp",
- "version": "1.5.42",
+ "version": "1.6.0",
"dependencies": {
"@cfworker/json-schema": "^4.1.1",
"@effect/platform-node": "catalog:",
"@executor-js/config": "workspace:*",
"@executor-js/sdk": "workspace:*",
+ "@modelcontextprotocol/client": "2.0.0",
+ "@modelcontextprotocol/core": "2.0.0",
"@modelcontextprotocol/sdk": "^1.29.0",
"zod": "4.3.6",
},
@@ -1002,6 +1004,7 @@
"@effect/vitest": "catalog:",
"@executor-js/api": "workspace:*",
"@executor-js/react": "workspace:*",
+ "@modelcontextprotocol/server": "2.0.0",
"@types/node": "catalog:",
"@types/react": "catalog:",
"bun-types": "catalog:",
@@ -1028,7 +1031,7 @@
},
"packages/plugins/onepassword": {
"name": "@executor-js/plugin-onepassword",
- "version": "1.5.42",
+ "version": "1.6.0",
"dependencies": {
"@1password/op-js": "^0.1.13",
"@1password/sdk": "^0.4.1-beta.1",
@@ -1062,7 +1065,7 @@
},
"packages/plugins/openapi": {
"name": "@executor-js/plugin-openapi",
- "version": "1.5.42",
+ "version": "1.6.0",
"dependencies": {
"@effect/platform-node": "catalog:",
"@executor-js/config": "workspace:*",
@@ -1103,7 +1106,7 @@
},
"packages/plugins/provider-service-split": {
"name": "@executor-js/plugin-provider-service-split",
- "version": "0.0.13",
+ "version": "0.0.14",
"dependencies": {
"@executor-js/plugin-openapi": "workspace:*",
"@executor-js/sdk": "workspace:*",
@@ -1120,7 +1123,7 @@
},
"packages/plugins/toolkits": {
"name": "@executor-js/plugin-toolkits",
- "version": "1.5.34",
+ "version": "1.5.35",
"dependencies": {
"@executor-js/sdk": "workspace:*",
},
@@ -1189,7 +1192,7 @@
},
"packages/react": {
"name": "@executor-js/react",
- "version": "1.4.62",
+ "version": "1.4.63",
"dependencies": {
"@base-ui/react": "^1.3.0",
"@effect/atom-react": "catalog:",
@@ -2120,10 +2123,16 @@
"@mishieck/ink-titled-box": ["@mishieck/ink-titled-box@0.3.0", "", { "peerDependencies": { "ink": "^6.0.0", "react": "^19.1.0", "typescript": "^5" } }, "sha512-ugzVH9hixp3hwKfQ8On/qnsrdAxS3y9rTu/aGOFed4zVUvtZyGZNIR4rxAwXult8HKI4vJEh0OM8wib9NPrwUg=="],
+ "@modelcontextprotocol/client": ["@modelcontextprotocol/client@2.0.0", "", { "dependencies": { "@modelcontextprotocol/core": "2.0.0", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "jose": "^6.1.3", "pkce-challenge": "^5.0.0", "zod": "^4.2.0" } }, "sha512-8f1OghQ2rjzIOfqgUCP+8GiUWqRs89njoWLNqAe8kWmDePv3s1fZXseej+QXemssEuuOvLLmLO/kqM3IQHtISw=="],
+
+ "@modelcontextprotocol/core": ["@modelcontextprotocol/core@2.0.0", "", { "dependencies": { "zod": "^4.2.0" } }, "sha512-pJCEwGG7Lfr/+PQp9ZTwKXNeO5wzbfKL7H3MYpCorM4oFBoQrdjnBgEoqG+RjhsvS1FKrDbKux+M1HhlnGWqcA=="],
+
"@modelcontextprotocol/ext-apps": ["@modelcontextprotocol/ext-apps@1.7.5", "", { "dependencies": { "@standard-schema/spec": "^1.1.0" }, "peerDependencies": { "@modelcontextprotocol/sdk": "^1.29.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0", "zod": "^3.25.0 || ^4.0.0" }, "optionalPeers": ["react", "react-dom"] }, "sha512-TjPH2S2y5UEGKhmI6+XGFuqfqOV4ppe1x6DA3txnUaEWkgtA4G5vo14jGKFZmegdkZ1H4QMLyujLvoU1BEdnAg=="],
"@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.29.0", "", { "dependencies": { "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.2.1", "express-rate-limit": "^8.2.1", "hono": "^4.11.4", "jose": "^6.1.3", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", "zod-to-json-schema": "^3.25.1" }, "peerDependencies": { "@cfworker/json-schema": "^4.1.1" }, "optionalPeers": ["@cfworker/json-schema"] }, "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ=="],
+ "@modelcontextprotocol/server": ["@modelcontextprotocol/server@2.0.0", "", { "dependencies": { "@modelcontextprotocol/core": "2.0.0", "zod": "^4.2.0" } }, "sha512-YhHWdHfpFMQfd0prsEnxKeS3Qz3ytIGmsS0sth4KDjnacIT7hxk6hXHkJ9KysxlkvTM+WZAtQbbcUhdoP4Hvtw=="],
+
"@msgpackr-extract/msgpackr-extract-darwin-arm64": ["@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw=="],
"@msgpackr-extract/msgpackr-extract-darwin-x64": ["@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw=="],
@@ -6106,8 +6115,16 @@
"@manypkg/get-packages/fs-extra": ["fs-extra@8.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g=="],
+ "@modelcontextprotocol/client/jose": ["jose@6.2.2", "", {}, "sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ=="],
+
+ "@modelcontextprotocol/client/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="],
+
+ "@modelcontextprotocol/core/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="],
+
"@modelcontextprotocol/sdk/jose": ["jose@6.2.2", "", {}, "sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ=="],
+ "@modelcontextprotocol/server/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="],
+
"@octokit/request/content-type": ["content-type@2.0.0", "", {}, "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ=="],
"@opentelemetry/exporter-logs-otlp-proto/@opentelemetry/resources": ["@opentelemetry/resources@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-lID/vxSuKWXM55XhAKNoYXu9Cutoq5hFdkbTdI/zDKQktXzcWBVhNsOkiZFTMU9UtEWuGRNe0HUgmsFldIdxVA=="],
diff --git a/e2e/CHANGELOG.md b/e2e/CHANGELOG.md
index cd520d27de..5481491dc5 100644
--- a/e2e/CHANGELOG.md
+++ b/e2e/CHANGELOG.md
@@ -1,5 +1,17 @@
# @executor-js/e2e
+## 0.0.40
+
+### Patch Changes
+
+- Updated dependencies [[`c11bef2`](https://github.com/UsefulSoftwareCo/executor/commit/c11bef2cd049db7bbf51b15e18761b14acccb534), [`46cea2c`](https://github.com/UsefulSoftwareCo/executor/commit/46cea2cbb1f414ae58ac876819a51b11967909a6), [`a2d1417`](https://github.com/UsefulSoftwareCo/executor/commit/a2d141758e478274813c8c24d354e1fd0f66af49), [`0b0b74f`](https://github.com/UsefulSoftwareCo/executor/commit/0b0b74f673b8098c5248159be36c648097f3c87b), [`256e25e`](https://github.com/UsefulSoftwareCo/executor/commit/256e25e7b291b0c023bc7547d092004b66781bba)]:
+ - @executor-js/plugin-mcp@1.6.0
+ - @executor-js/plugin-openapi@1.6.0
+ - @executor-js/sdk@1.6.0
+ - @executor-js/api@1.4.63
+ - @executor-js/plugin-graphql@1.6.0
+ - @executor-js/plugin-toolkits@1.5.35
+
## 0.0.39
### Patch Changes
diff --git a/e2e/cloud/mcp-browser-resume-page.test.ts b/e2e/cloud/mcp-browser-resume-page.test.ts
index 26be5910a4..5e82e5eb0d 100644
--- a/e2e/cloud/mcp-browser-resume-page.test.ts
+++ b/e2e/cloud/mcp-browser-resume-page.test.ts
@@ -18,7 +18,6 @@ import { scenario } from "../src/scenario";
import { Api, Browser, Mcp, Target } from "../src/services";
import { parseBrowserApproval } from "../src/surfaces/mcp";
import type { Identity } from "../src/target";
-import { visit } from "../src/surfaces/browser";
const coreApi = composePluginApi([] as const);
@@ -196,68 +195,75 @@ scenario(
openBrowserApprovalSession(target.mcpUrl, bearer),
);
yield* Effect.gen(function* () {
- const paused = yield* Effect.promise(() =>
- session.client.callTool({ name: "execute", arguments: { code: GATED_CODE } }),
- );
- const approval = parseBrowserApproval({
- raw: paused,
- text: textOf(paused),
- ok: paused.isError !== true,
- });
+ yield* browser.session(identity, async ({ page, step }) => {
+ // Open Chromium before pausing. The suite intentionally compresses
+ // the production nine-minute decision window to 30 seconds, and a
+ // cold browser launch should not consume the user's decision time.
+ const paused = await session.client.callTool({
+ name: "execute",
+ arguments: { code: GATED_CODE },
+ });
+ const approval = parseBrowserApproval({
+ raw: paused,
+ text: textOf(paused),
+ ok: paused.isError !== true,
+ });
- const approvalUrl = new URL(approval.approvalUrl);
- const mcpSessionId = approvalUrl.searchParams.get("mcp_session_id");
- expect(
- mcpSessionId,
- "approval URL carries the MCP session id that the browser page will query",
- ).toEqual(expect.any(String));
- expect(mcpSessionId, "approval URL points at the session that paused").toBe(
- session.transport.sessionId,
- );
-
- const [resumed] = yield* Effect.all(
- [
- Effect.promise(() =>
- session.client.callTool({
- name: "resume",
- arguments: { executionId: approval.executionId },
- }),
- ),
- browser.session(identity, async ({ page, step }) => {
- await step("Open the paused execution approval page", async () => {
- await visit(page, pathWithSearch(approval.approvalUrl));
- await page.getByText("User approval required").waitFor();
- });
+ const approvalUrl = new URL(approval.approvalUrl);
+ const mcpSessionId = approvalUrl.searchParams.get("mcp_session_id");
+ expect(
+ mcpSessionId,
+ "approval URL carries the MCP session id that the browser page will query",
+ ).toEqual(expect.any(String));
+ expect(mcpSessionId, "approval URL points at the session that paused").toBe(
+ session.transport.sessionId,
+ );
- await step("Review the paused tool call details", async () => {
- await page.getByText("Pending request").waitFor();
- await page.getByText(/Approve executor\.coreTools\.policies\.list\?/).waitFor();
+ await step("Approve the paused tool call through the browser page", async () => {
+ // The resume GET is this page's concrete readiness signal. The
+ // generic browser `visit` helper additionally waits up to five
+ // seconds for network-idle, which is inappropriate while this
+ // deliberately short approval lease is ticking.
+ const pausedExecutionLoaded = page.waitForResponse((response) => {
+ const responseUrl = new URL(response.url());
+ return (
+ response.request().method() === "GET" &&
+ responseUrl.pathname.endsWith(
+ `/api/mcp-sessions/${mcpSessionId}/executions/${approval.executionId}`,
+ ) &&
+ response.status() === 200
+ );
+ });
+ await page.goto(pathWithSearch(approval.approvalUrl), { waitUntil: "load" });
+ await pausedExecutionLoaded;
+ await page.getByText("User approval required").waitFor();
+ await page.getByText("Pending request").waitFor();
+ await page.getByText(/Approve executor\.coreTools\.policies\.list\?/).waitFor();
- const approve = page.getByRole("button", { name: "Approve" });
- await approve.waitFor();
- expect(
- await approve.isEnabled(),
- "the approve control is enabled for the paused execution",
- ).toBe(true);
- expect(
- await page.getByText(UNAVAILABLE_COPY).count(),
- "the resume page does not show the expired-session failure copy",
- ).toBe(0);
- });
+ const approve = page.getByRole("button", { name: "Approve" });
+ await approve.waitFor();
+ expect(
+ await approve.isEnabled(),
+ "the approve control is enabled for the paused execution",
+ ).toBe(true);
+ expect(
+ await page.getByText(UNAVAILABLE_COPY).count(),
+ "the resume page does not show the expired-session failure copy",
+ ).toBe(0);
+ await page.getByRole("button", { name: "Approve" }).click();
+ await page.getByText("Approve sent").waitFor();
+ });
- await step("Approve the paused tool call", async () => {
- await page.getByRole("button", { name: "Approve" }).click();
- await page.getByText("Approve sent").waitFor();
- });
- }),
- ],
- { concurrency: "unbounded" },
- );
+ const resumed = await session.client.callTool({
+ name: "resume",
+ arguments: { executionId: approval.executionId },
+ });
- expect(resumed.isError, "browser-mode resume completed after the UI approval").not.toBe(
- true,
- );
- expect(textOf(resumed), "the gated tool completed after approval").toContain(policy.id);
+ expect(resumed.isError, "browser-mode resume completed after the UI approval").not.toBe(
+ true,
+ );
+ expect(textOf(resumed), "the gated tool completed after approval").toContain(policy.id);
+ });
}).pipe(Effect.ensuring(closeQuietly(session)));
}).pipe(
Effect.ensuring(
diff --git a/e2e/local/stdio-mcp.test.ts b/e2e/local/stdio-mcp.test.ts
index f890122592..a8ece4caa3 100644
--- a/e2e/local/stdio-mcp.test.ts
+++ b/e2e/local/stdio-mcp.test.ts
@@ -146,6 +146,36 @@ scenario(
declTools.map((t) => t.name),
"connecting with the secret discovers the env-gated tool",
).toContain("whoami");
+
+ // --- versionNegotiation "auto" survives the API → config → connector
+ // path and still reaches a legacy server: the probe gets the fixture's
+ // method-not-found for `server/discover` (a definitive legacy verdict)
+ // and falls back to `initialize`. Modern-era acceptance against a real
+ // legacy-disabled SDK v2 server lives in the plugin's
+ // stdio-negotiation.test.ts. ---
+ const autoSlug = "e2e-stdio-auto";
+ yield* client.mcp.addServer({
+ payload: {
+ transport: "stdio",
+ name: "E2E Stdio Auto",
+ command: "node",
+ args: [FIXTURE],
+ versionNegotiation: "auto",
+ slug: autoSlug,
+ },
+ });
+
+ const autoStored = yield* client.mcp.getServer({ params: { slug: autoSlug } });
+ expect(
+ JSON.stringify(autoStored?.config ?? {}),
+ "the negotiation mode is persisted on the integration config",
+ ).toContain('"versionNegotiation":"auto"');
+
+ const autoTools = yield* client.tools.list({ query: { integration: autoSlug } });
+ expect(
+ autoTools.map((t) => t.name),
+ "auto negotiation falls back to legacy and still discovers tools",
+ ).toContain("echo_tool");
}),
);
}),
diff --git a/e2e/package.json b/e2e/package.json
index 040a042ebb..2f8bd94b0d 100644
--- a/e2e/package.json
+++ b/e2e/package.json
@@ -1,6 +1,6 @@
{
"name": "@executor-js/e2e",
- "version": "0.0.39",
+ "version": "0.0.40",
"private": true,
"type": "module",
"scripts": {
diff --git a/e2e/scenarios/graphql-introspection-health.test.ts b/e2e/scenarios/graphql-introspection-health.test.ts
index b0ca487fb9..aaa48979ec 100644
--- a/e2e/scenarios/graphql-introspection-health.test.ts
+++ b/e2e/scenarios/graphql-introspection-health.test.ts
@@ -1,14 +1,13 @@
import { randomBytes } from "node:crypto";
+import { createServer } from "node:http";
import { expect } from "@effect/vitest";
import { Effect } from "effect";
import { composePluginApi } from "@executor-js/api/server";
-import { connectEmulator } from "@executor-js/emulate";
import { graphqlHttpPlugin } from "@executor-js/plugin-graphql/api";
import { AuthTemplateSlug, ConnectionName, IntegrationSlug } from "@executor-js/sdk/shared";
import { variable } from "@executor-js/sdk/http-auth";
-import { createEmulatorInstance } from "../src/emulator-instance";
import { scenario } from "../src/scenario";
import { Api, Browser, Target } from "../src/services";
import { visit } from "../src/surfaces/browser";
@@ -16,125 +15,132 @@ import { visit } from "../src/surfaces/browser";
const api = composePluginApi([graphqlHttpPlugin()] as const);
const unique = (prefix: string): string => `${prefix}_${randomBytes(4).toString("hex")}`;
+const serveRejectingGraphql = () =>
+ Effect.acquireRelease(
+ Effect.callback<{ readonly url: string; readonly close: () => void }>((resume) => {
+ const server = createServer((request, response) => {
+ if (request.method === "POST" && request.url === "/graphql") {
+ response.writeHead(401, { "content-type": "application/json" });
+ response.end(JSON.stringify({ message: "Bad credentials" }));
+ return;
+ }
+ response.writeHead(404, { "content-type": "application/json" });
+ response.end(JSON.stringify({ message: "Not found" }));
+ });
+ server.listen(0, "127.0.0.1", () => {
+ const address = server.address();
+ const port = typeof address === "object" && address ? address.port : 0;
+ resume(
+ Effect.succeed({
+ url: `http://127.0.0.1:${port}`,
+ close: () => {
+ server.close();
+ server.closeAllConnections();
+ },
+ }),
+ );
+ });
+ }),
+ (server) => Effect.sync(server.close),
+ );
+
scenario(
"GraphQL · failed introspection blocks connection creation with an actionable error",
{},
- Effect.gen(function* () {
- const target = yield* Target;
- const browser = yield* Browser;
- const { client: makeApiClient } = yield* Api;
- const identity = yield* target.newIdentity();
- const client = yield* makeApiClient(api, identity);
- const slug = unique("graphql_health");
- const emulatorBaseUrl = yield* createEmulatorInstance("github", "graphql-health");
- const emulator = yield* Effect.promise(() =>
- connectEmulator({ baseUrl: emulatorBaseUrl, service: "github" }),
- );
+ Effect.scoped(
+ Effect.gen(function* () {
+ const target = yield* Target;
+ const browser = yield* Browser;
+ const { client: makeApiClient } = yield* Api;
+ const identity = yield* target.newIdentity();
+ const client = yield* makeApiClient(api, identity);
+ const slug = unique("graphql_health");
+ const upstream = yield* serveRejectingGraphql();
- // A budget, not a count: nothing in this scenario depends on how many
- // times the connect flow introspects, and the emulator's answer when the
- // budget runs out is not a neutral pass-through — an unauthenticated
- // GraphQL POST to the real handler is GitHub-shaped, so it comes back 403
- // "API rate limit exceeded". The UI then honestly reports HTTP 403 and the
- // assertion below fails on a message that has nothing to do with the
- // product. Arm enough that one connect attempt cannot exhaust it.
- yield* Effect.promise(() =>
- emulator.faults.arm({
- match: { method: "POST", pathPattern: "/graphql" },
- response: { status: 401, body: { message: "Bad credentials" } },
- times: 100,
- }),
- );
+ yield* client.graphql.addIntegration({
+ payload: {
+ endpoint: `${upstream.url}/graphql`,
+ slug,
+ name: "GraphQL health",
+ authenticationTemplate: [
+ {
+ slug: "header",
+ type: "apiKey",
+ headers: { Authorization: [variable("token")] },
+ },
+ ],
+ },
+ });
- yield* client.graphql.addIntegration({
- payload: {
- endpoint: `${emulatorBaseUrl}/graphql`,
- slug,
- name: "GraphQL health",
- authenticationTemplate: [
- {
- slug: "header",
- type: "apiKey",
- headers: { Authorization: [variable("token")] },
- },
- ],
- },
- });
+ yield* Effect.gen(function* () {
+ yield* browser.session(identity, async ({ page, step }) => {
+ await step("Open the connection flow", async () => {
+ await visit(page, `/integrations/${slug}?addAccount=1&owner=org&template=header`);
+ await page.getByRole("heading", { name: /Add connection · GraphQL health/ }).waitFor();
+ });
- yield* Effect.gen(function* () {
- yield* browser.session(identity, async ({ page, step }) => {
- await step("Open the connection flow", async () => {
- await visit(page, `/integrations/${slug}?addAccount=1&owner=org&template=header`);
- await page.getByRole("heading", { name: /Add connection · GraphQL health/ }).waitFor();
- });
+ await step("Submit a credential rejected during schema introspection", async () => {
+ const dialog = page.getByRole("dialog", {
+ name: /Add connection · GraphQL health/,
+ });
+ await dialog.getByRole("textbox", { name: "Authorization" }).fill("invalid-token");
+ await dialog.getByRole("button", { name: "Continue" }).click();
- await step("Submit a credential rejected during schema introspection", async () => {
- const dialog = page.getByRole("dialog", {
- name: /Add connection · GraphQL health/,
+ const alert = dialog.getByRole("alert");
+ await alert.waitFor();
+ const message = await alert.textContent();
+ expect(message).toContain("The endpoint rejected the credential with HTTP 401.");
+ expect(message).toContain("Check the credential and selected authentication method.");
+ await dialog.getByText("Step 1 of 2").waitFor();
+ expect(
+ await page.getByText("No connections yet").count(),
+ "the rejected credential is not saved",
+ ).toBe(1);
});
- await dialog.getByRole("textbox", { name: "Authorization" }).fill("invalid-token");
- await dialog.getByRole("button", { name: "Continue" }).click();
-
- const alert = dialog.getByRole("alert");
- await alert.waitFor();
- const message = await alert.textContent();
- expect(message).toContain("The endpoint rejected the credential with HTTP 401.");
- expect(message).toContain("Check the credential and selected authentication method.");
- await dialog.getByText("Step 1 of 2").waitFor();
- expect(
- await page.getByText("No connections yet").count(),
- "the rejected credential is not saved",
- ).toBe(1);
});
- });
-
- // The low-level API can still import an existing credential reference
- // without the browser's preflight. This models connections created before
- // the fix and proves their failed tool sync is no longer a silent zero.
- yield* client.connections.create({
- payload: {
- owner: "org",
- name: ConnectionName.make("legacy"),
- integration: IntegrationSlug.make(slug),
- template: AuthTemplateSlug.make("header"),
- value: "invalid-token",
- },
- });
- yield* browser.session(identity, async ({ page, step }) => {
- await step("A failed existing connection explains the empty tool catalogue", async () => {
- await visit(page, `/integrations/${slug}?tab=tools`);
- await page.getByText("Connection rejected", { exact: true }).first().waitFor();
- await page
- .getByText("The endpoint rejected the credential with HTTP 401.", {
- exact: false,
- })
- .waitFor();
- await page.getByRole("button", { name: "Check and sync tools" }).waitFor();
+ // The low-level API can still import an existing credential reference
+ // without the browser's preflight. This models connections created before
+ // the fix and proves their failed tool sync is no longer a silent zero.
+ yield* client.connections.create({
+ payload: {
+ owner: "org",
+ name: ConnectionName.make("legacy"),
+ integration: IntegrationSlug.make(slug),
+ template: AuthTemplateSlug.make("header"),
+ value: "invalid-token",
+ },
});
- await step("The account row carries the same actionable health verdict", async () => {
- await page.getByRole("tab", { name: "Accounts" }).click();
- await page.getByText("Expired", { exact: true }).waitFor();
- await page
- .getByText("Check the credential and selected authentication method.", {
- exact: false,
- })
- .waitFor();
+ yield* browser.session(identity, async ({ page, step }) => {
+ await step("A failed existing connection explains the empty tool catalogue", async () => {
+ await visit(page, `/integrations/${slug}?tab=tools`);
+ await page.getByText("Connection rejected", { exact: true }).first().waitFor();
+ await page
+ .getByText("The endpoint rejected the credential with HTTP 401.", {
+ exact: false,
+ })
+ .waitFor();
+ await page.getByRole("button", { name: "Check and sync tools" }).waitFor();
+ });
+
+ await step("The account row carries the same actionable health verdict", async () => {
+ await page.getByRole("tab", { name: "Accounts" }).click();
+ await page.getByText("Expired", { exact: true }).waitFor();
+ await page
+ .getByText("Check the credential and selected authentication method.", {
+ exact: false,
+ })
+ .waitFor();
+ });
});
- });
- }).pipe(
- Effect.ensuring(
- Effect.all(
- [
- client.integrations
- .remove({ params: { slug: IntegrationSlug.make(slug) } })
- .pipe(Effect.ignore),
- Effect.promise(() => emulator.faults.clear()).pipe(Effect.ignore),
- ],
- { concurrency: "unbounded" },
+ }).pipe(
+ Effect.ensuring(
+ client.integrations
+ .remove({ params: { slug: IntegrationSlug.make(slug) } })
+ .pipe(Effect.ignore),
),
- ),
- );
- }),
+ );
+ }),
+ ),
);
diff --git a/e2e/scenarios/policies-ui.test.ts b/e2e/scenarios/policies-ui.test.ts
index 6e42865ea2..ad45864429 100644
--- a/e2e/scenarios/policies-ui.test.ts
+++ b/e2e/scenarios/policies-ui.test.ts
@@ -27,7 +27,7 @@ import { AuthTemplateSlug, ConnectionName, IntegrationSlug } from "@executor-js/
import { scenario } from "../src/scenario";
import { Api, Browser, Target } from "../src/services";
-import { visit } from "../src/surfaces/browser";
+import { clickToReveal, visit } from "../src/surfaces/browser";
const api = composePluginApi([openApiHttpPlugin()] as const);
@@ -163,9 +163,15 @@ scenario(
await step("Open the integration's Tools tab", async () => {
await visit(page, `/integrations/${integration}`);
- await page.getByRole("tab", { name: "Tools" }).click();
- await sectionFor(alpha).waitFor();
- await sectionFor(beta).waitFor();
+ // The org-scoped redirect can replace the document between the tab
+ // becoming visible and React receiving the click. Reveal a node that
+ // exists only in the Tools panel so the Accounts panel's connection
+ // sections cannot satisfy the readiness check.
+ await clickToReveal(
+ page.getByRole("tab", { name: "Tools" }),
+ closedGroup(alpha, integration),
+ );
+ await closedGroup(beta, integration).waitFor();
});
await step("Expand the records category in the first account", async () => {
diff --git a/e2e/setup/mcp-session-timeouts.ts b/e2e/setup/mcp-session-timeouts.ts
index dfd4782978..3cf2846435 100644
--- a/e2e/setup/mcp-session-timeouts.ts
+++ b/e2e/setup/mcp-session-timeouts.ts
@@ -1,5 +1,7 @@
const DEFAULT_E2E_MCP_SESSION_TIMEOUT_MS = 3_000;
-const DEFAULT_E2E_MCP_PAUSED_SESSION_IDLE_TIMEOUT_MS = 6_000;
+// Keep expiry coverage fast while leaving enough room for a cold browser route
+// to compile under CI contention. Production allows nine minutes.
+const DEFAULT_E2E_MCP_PAUSED_SESSION_IDLE_TIMEOUT_MS = 30_000;
const PRODUCTION_MCP_SESSION_TIMEOUT_MS = 5 * 60 * 1000;
const PRODUCTION_MCP_PAUSED_SESSION_IDLE_TIMEOUT_MS = 9 * 60 * 1000;
diff --git a/examples/all-plugins/CHANGELOG.md b/examples/all-plugins/CHANGELOG.md
index e6bd92860b..c9ef985b41 100644
--- a/examples/all-plugins/CHANGELOG.md
+++ b/examples/all-plugins/CHANGELOG.md
@@ -1,5 +1,19 @@
# @executor-js/example-all-plugins
+## 0.0.61
+
+### Patch Changes
+
+- Updated dependencies [[`c11bef2`](https://github.com/UsefulSoftwareCo/executor/commit/c11bef2cd049db7bbf51b15e18761b14acccb534), [`46cea2c`](https://github.com/UsefulSoftwareCo/executor/commit/46cea2cbb1f414ae58ac876819a51b11967909a6), [`a2d1417`](https://github.com/UsefulSoftwareCo/executor/commit/a2d141758e478274813c8c24d354e1fd0f66af49), [`0b0b74f`](https://github.com/UsefulSoftwareCo/executor/commit/0b0b74f673b8098c5248159be36c648097f3c87b), [`256e25e`](https://github.com/UsefulSoftwareCo/executor/commit/256e25e7b291b0c023bc7547d092004b66781bba)]:
+ - @executor-js/plugin-mcp@1.6.0
+ - @executor-js/plugin-openapi@1.6.0
+ - @executor-js/sdk@1.6.0
+ - @executor-js/plugin-file-secrets@1.6.0
+ - @executor-js/plugin-graphql@1.6.0
+ - @executor-js/plugin-keychain@1.6.0
+ - @executor-js/plugin-onepassword@1.6.0
+ - @executor-js/plugin-workos-vault@0.0.2
+
## 0.0.60
### Patch Changes
diff --git a/examples/all-plugins/package.json b/examples/all-plugins/package.json
index da944c1166..42f5f94fa9 100644
--- a/examples/all-plugins/package.json
+++ b/examples/all-plugins/package.json
@@ -1,6 +1,6 @@
{
"name": "@executor-js/example-all-plugins",
- "version": "0.0.60",
+ "version": "0.0.61",
"private": true,
"type": "module",
"scripts": {
diff --git a/examples/docs-sdk-quickstart/CHANGELOG.md b/examples/docs-sdk-quickstart/CHANGELOG.md
index 5eec50eb1f..a287252536 100644
--- a/examples/docs-sdk-quickstart/CHANGELOG.md
+++ b/examples/docs-sdk-quickstart/CHANGELOG.md
@@ -1,5 +1,13 @@
# @executor-js/example-docs-sdk-quickstart
+## 0.0.46
+
+### Patch Changes
+
+- Updated dependencies [[`c11bef2`](https://github.com/UsefulSoftwareCo/executor/commit/c11bef2cd049db7bbf51b15e18761b14acccb534), [`46cea2c`](https://github.com/UsefulSoftwareCo/executor/commit/46cea2cbb1f414ae58ac876819a51b11967909a6), [`a2d1417`](https://github.com/UsefulSoftwareCo/executor/commit/a2d141758e478274813c8c24d354e1fd0f66af49)]:
+ - @executor-js/plugin-openapi@1.6.0
+ - @executor-js/sdk@1.6.0
+
## 0.0.45
### Patch Changes
diff --git a/examples/docs-sdk-quickstart/package.json b/examples/docs-sdk-quickstart/package.json
index e46fc18892..4a91d1354f 100644
--- a/examples/docs-sdk-quickstart/package.json
+++ b/examples/docs-sdk-quickstart/package.json
@@ -1,6 +1,6 @@
{
"name": "@executor-js/example-docs-sdk-quickstart",
- "version": "0.0.45",
+ "version": "0.0.46",
"private": true,
"type": "module",
"scripts": {
diff --git a/packages/core/analytics/CHANGELOG.md b/packages/core/analytics/CHANGELOG.md
index b26ac6af86..1c08c54c3d 100644
--- a/packages/core/analytics/CHANGELOG.md
+++ b/packages/core/analytics/CHANGELOG.md
@@ -1,5 +1,12 @@
# @executor-js/analytics
+## 0.1.7
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @executor-js/execution@1.6.0
+
## 0.1.6
### Patch Changes
diff --git a/packages/core/analytics/package.json b/packages/core/analytics/package.json
index ea638f0875..9e793a9b2b 100644
--- a/packages/core/analytics/package.json
+++ b/packages/core/analytics/package.json
@@ -1,6 +1,6 @@
{
"name": "@executor-js/analytics",
- "version": "0.1.6",
+ "version": "0.1.7",
"private": true,
"homepage": "https://github.com/UsefulSoftwareCo/executor/tree/main/packages/core/analytics",
"bugs": {
diff --git a/packages/core/api/CHANGELOG.md b/packages/core/api/CHANGELOG.md
index bbe48b574a..688eeee753 100644
--- a/packages/core/api/CHANGELOG.md
+++ b/packages/core/api/CHANGELOG.md
@@ -1,5 +1,14 @@
# @executor-js/api
+## 1.4.63
+
+### Patch Changes
+
+- Updated dependencies [[`a2d1417`](https://github.com/UsefulSoftwareCo/executor/commit/a2d141758e478274813c8c24d354e1fd0f66af49)]:
+ - @executor-js/sdk@1.6.0
+ - @executor-js/execution@1.6.0
+ - @executor-js/host-mcp@1.4.4
+
## 1.4.62
### Patch Changes
diff --git a/packages/core/api/package.json b/packages/core/api/package.json
index ada420e1b5..5eacfe461d 100644
--- a/packages/core/api/package.json
+++ b/packages/core/api/package.json
@@ -1,6 +1,6 @@
{
"name": "@executor-js/api",
- "version": "1.4.62",
+ "version": "1.4.63",
"private": true,
"type": "module",
"exports": {
diff --git a/packages/core/cli/CHANGELOG.md b/packages/core/cli/CHANGELOG.md
index e36cf1b6f5..0e0f3add3f 100644
--- a/packages/core/cli/CHANGELOG.md
+++ b/packages/core/cli/CHANGELOG.md
@@ -1,5 +1,12 @@
# @executor-js/cli
+## 0.2.50
+
+### Patch Changes
+
+- Updated dependencies [[`a2d1417`](https://github.com/UsefulSoftwareCo/executor/commit/a2d141758e478274813c8c24d354e1fd0f66af49)]:
+ - @executor-js/sdk@1.6.0
+
## 0.2.49
### Patch Changes
diff --git a/packages/core/cli/package.json b/packages/core/cli/package.json
index 682f5b7288..2bbe9b2668 100644
--- a/packages/core/cli/package.json
+++ b/packages/core/cli/package.json
@@ -1,6 +1,6 @@
{
"name": "@executor-js/cli",
- "version": "0.2.49",
+ "version": "0.2.50",
"description": "CLI for the executor SDK — schema generation, migrations",
"homepage": "https://github.com/UsefulSoftwareCo/executor/tree/main/packages/core/cli",
"bugs": {
diff --git a/packages/core/config/CHANGELOG.md b/packages/core/config/CHANGELOG.md
index 3cb6115960..46237be0ff 100644
--- a/packages/core/config/CHANGELOG.md
+++ b/packages/core/config/CHANGELOG.md
@@ -1,5 +1,12 @@
# @executor-js/config
+## 1.6.0
+
+### Patch Changes
+
+- Updated dependencies [[`a2d1417`](https://github.com/UsefulSoftwareCo/executor/commit/a2d141758e478274813c8c24d354e1fd0f66af49)]:
+ - @executor-js/sdk@1.6.0
+
## 1.5.42
### Patch Changes
diff --git a/packages/core/config/package.json b/packages/core/config/package.json
index 65fb56eca2..b4bce2cb9d 100644
--- a/packages/core/config/package.json
+++ b/packages/core/config/package.json
@@ -1,6 +1,6 @@
{
"name": "@executor-js/config",
- "version": "1.5.42",
+ "version": "1.6.0",
"homepage": "https://github.com/UsefulSoftwareCo/executor/tree/main/packages/core/config",
"bugs": {
"url": "https://github.com/UsefulSoftwareCo/executor/issues"
diff --git a/packages/core/execution/CHANGELOG.md b/packages/core/execution/CHANGELOG.md
index 2b54daa441..6b7241d164 100644
--- a/packages/core/execution/CHANGELOG.md
+++ b/packages/core/execution/CHANGELOG.md
@@ -1,5 +1,13 @@
# @executor-js/execution
+## 1.6.0
+
+### Patch Changes
+
+- Updated dependencies [[`a2d1417`](https://github.com/UsefulSoftwareCo/executor/commit/a2d141758e478274813c8c24d354e1fd0f66af49)]:
+ - @executor-js/sdk@1.6.0
+ - @executor-js/codemode-core@1.6.0
+
## 1.5.42
### Patch Changes
diff --git a/packages/core/execution/package.json b/packages/core/execution/package.json
index b344b472d5..20940d0c3d 100644
--- a/packages/core/execution/package.json
+++ b/packages/core/execution/package.json
@@ -1,6 +1,6 @@
{
"name": "@executor-js/execution",
- "version": "1.5.42",
+ "version": "1.6.0",
"homepage": "https://github.com/UsefulSoftwareCo/executor/tree/main/packages/core/execution",
"bugs": {
"url": "https://github.com/UsefulSoftwareCo/executor/issues"
diff --git a/packages/core/execution/src/skills.test.ts b/packages/core/execution/src/skills.test.ts
index 54a1c049b9..97e76e1062 100644
--- a/packages/core/execution/src/skills.test.ts
+++ b/packages/core/execution/src/skills.test.ts
@@ -29,6 +29,15 @@ describe("skills registry", () => {
}
});
+ // A host with no skill tool of its own reads `executor_skills` as the general
+ // one it is missing, so the index has to say what the catalog covers and that
+ // it is closed — otherwise the model asks it for the user's skills next.
+ it("frames the index as Executor's own closed catalog", () => {
+ const index = renderSkillsIndex();
+ expect(index).toContain("Executor's own tools");
+ expect(index).toContain("complete list");
+ });
+
// A connection that did not opt in to artifacts — the default — has no tool
// the artifact skills apply to, so they leave its catalog entirely rather
// than documenting a surface it cannot reach.
diff --git a/packages/core/execution/src/skills.ts b/packages/core/execution/src/skills.ts
index 1036ae697b..f503b26983 100644
--- a/packages/core/execution/src/skills.ts
+++ b/packages/core/execution/src/skills.ts
@@ -639,10 +639,17 @@ export const findSkill = (name: string, catalog: readonly Skill[] = SKILLS): Ski
/** The index the `skills` tool returns when called without a name (or with an
* unknown one): every skill's name and one-line summary, plus how to fetch
- * the body. */
+ * the body.
+ *
+ * It leads with what the catalog IS. A host without a skill tool of its own
+ * (pi, for one) reads `skills` as the general skill reader it is missing and
+ * asks it for the user's or the harness's skills; the index is where that
+ * model lands, so it has to say plainly that this list is the whole of it and
+ * covers only Executor's own tools. */
export const renderSkillsIndex = (catalog: readonly Skill[] = SKILLS): string =>
[
- 'Available skills. Fetch one with `skills({ name: "" })`.',
+ "How-to docs for Executor's own tools — this is the complete list, and there is nothing else to fetch.",
+ 'Fetch one with `skills({ name: "" })`. Names outside this list, file paths, and skills belonging to your harness or the user are not served here.',
"",
...catalog.map((skill) => `- \`${skill.name}\` — ${skill.summary}`),
].join("\n");
diff --git a/packages/core/sdk/CHANGELOG.md b/packages/core/sdk/CHANGELOG.md
index 4423d546a9..346be2436b 100644
--- a/packages/core/sdk/CHANGELOG.md
+++ b/packages/core/sdk/CHANGELOG.md
@@ -1,5 +1,11 @@
# @executor-js/sdk
+## 1.6.0
+
+### Patch Changes
+
+- [#1648](https://github.com/UsefulSoftwareCo/executor/pull/1648) [`a2d1417`](https://github.com/UsefulSoftwareCo/executor/commit/a2d141758e478274813c8c24d354e1fd0f66af49) Thanks [@baggiiiie](https://github.com/baggiiiie)! - Keep `connections.list` health output compact unless callers opt into diagnostics with `verbose: true`. Default list responses now retain only the health status, identity, and check timestamp; verbose responses continue to include HTTP status, diagnostic detail, and bounded upstream response samples.
+
## 1.5.42
### Patch Changes
diff --git a/packages/core/sdk/package.json b/packages/core/sdk/package.json
index 6545c8281f..17874f8d75 100644
--- a/packages/core/sdk/package.json
+++ b/packages/core/sdk/package.json
@@ -1,6 +1,6 @@
{
"name": "@executor-js/sdk",
- "version": "1.5.42",
+ "version": "1.6.0",
"homepage": "https://github.com/UsefulSoftwareCo/executor/tree/main/packages/core/sdk",
"bugs": {
"url": "https://github.com/UsefulSoftwareCo/executor/issues"
diff --git a/packages/core/vite-plugin/CHANGELOG.md b/packages/core/vite-plugin/CHANGELOG.md
index 277d898062..a2e26bf071 100644
--- a/packages/core/vite-plugin/CHANGELOG.md
+++ b/packages/core/vite-plugin/CHANGELOG.md
@@ -1,5 +1,12 @@
# @executor-js/vite-plugin
+## 0.0.60
+
+### Patch Changes
+
+- Updated dependencies [[`a2d1417`](https://github.com/UsefulSoftwareCo/executor/commit/a2d141758e478274813c8c24d354e1fd0f66af49)]:
+ - @executor-js/sdk@1.6.0
+
## 0.0.59
### Patch Changes
diff --git a/packages/core/vite-plugin/package.json b/packages/core/vite-plugin/package.json
index aba715a07b..4166fe4e64 100644
--- a/packages/core/vite-plugin/package.json
+++ b/packages/core/vite-plugin/package.json
@@ -1,6 +1,6 @@
{
"name": "@executor-js/vite-plugin",
- "version": "0.0.59",
+ "version": "0.0.60",
"homepage": "https://github.com/UsefulSoftwareCo/executor/tree/main/packages/core/vite-plugin",
"bugs": {
"url": "https://github.com/UsefulSoftwareCo/executor/issues"
diff --git a/packages/hosts/cloudflare/CHANGELOG.md b/packages/hosts/cloudflare/CHANGELOG.md
index ea7df00ec2..93a6ae71ce 100644
--- a/packages/hosts/cloudflare/CHANGELOG.md
+++ b/packages/hosts/cloudflare/CHANGELOG.md
@@ -1,5 +1,15 @@
# @executor-js/cloudflare
+## 0.0.42
+
+### Patch Changes
+
+- Updated dependencies [[`a2d1417`](https://github.com/UsefulSoftwareCo/executor/commit/a2d141758e478274813c8c24d354e1fd0f66af49)]:
+ - @executor-js/sdk@1.6.0
+ - @executor-js/api@1.4.63
+ - @executor-js/execution@1.6.0
+ - @executor-js/host-mcp@1.4.4
+
## 0.0.41
### Patch Changes
diff --git a/packages/hosts/cloudflare/package.json b/packages/hosts/cloudflare/package.json
index 79eba80ac5..f6a2927597 100644
--- a/packages/hosts/cloudflare/package.json
+++ b/packages/hosts/cloudflare/package.json
@@ -1,6 +1,6 @@
{
"name": "@executor-js/cloudflare",
- "version": "0.0.41",
+ "version": "0.0.42",
"private": true,
"type": "module",
"exports": {
diff --git a/packages/hosts/mcp-apps-shell/CHANGELOG.md b/packages/hosts/mcp-apps-shell/CHANGELOG.md
index 0c5100cebd..5d5ae0197e 100644
--- a/packages/hosts/mcp-apps-shell/CHANGELOG.md
+++ b/packages/hosts/mcp-apps-shell/CHANGELOG.md
@@ -1,5 +1,13 @@
# @executor-js/mcp-apps-shell
+## 1.4.11
+
+### Patch Changes
+
+- Updated dependencies [[`2bdbedf`](https://github.com/UsefulSoftwareCo/executor/commit/2bdbedf257f54d7c209e8c856c618174c10d6bb3)]:
+ - @executor-js/react@1.4.63
+ - @executor-js/runtime-quickjs@1.6.0
+
## 1.4.10
### Patch Changes
diff --git a/packages/hosts/mcp-apps-shell/package.json b/packages/hosts/mcp-apps-shell/package.json
index 8047a241ce..14f6cab9fc 100644
--- a/packages/hosts/mcp-apps-shell/package.json
+++ b/packages/hosts/mcp-apps-shell/package.json
@@ -1,6 +1,6 @@
{
"name": "@executor-js/mcp-apps-shell",
- "version": "1.4.10",
+ "version": "1.4.11",
"private": true,
"type": "module",
"exports": {
@@ -69,7 +69,7 @@
"@executor-js/react": "workspace:*",
"@executor-js/runtime-quickjs": "workspace:*",
"@modelcontextprotocol/ext-apps": "^1.7.4",
- "@modelcontextprotocol/sdk": "^1.12.1",
+ "@modelcontextprotocol/sdk": "^1.29.0",
"@tanstack/react-query": "^5.99.0",
"effect": "catalog:",
"esbuild": "^0.27.7",
diff --git a/packages/hosts/mcp/src/tool-server.test.ts b/packages/hosts/mcp/src/tool-server.test.ts
index 80118f37a3..813faeb485 100644
--- a/packages/hosts/mcp/src/tool-server.test.ts
+++ b/packages/hosts/mcp/src/tool-server.test.ts
@@ -1807,6 +1807,19 @@ describe("MCP host server — skills tool", () => {
});
});
+ // pi and other hosts that ship no skill tool of their own read
+ // `executor_skills` as the general skill reader they are missing, so the
+ // description has to scope itself to this server before a model tries to
+ // read a SKILL.md through it.
+ it("scopes the skills tool description to this server's own docs", async () => {
+ await withClient(makeStubEngine({}), NO_CAPS, async (client) => {
+ const { tools } = await client.listTools();
+ const description = tools.find((t) => t.name === "skills")?.description ?? "";
+ expect(description).toContain("Not a general skill reader");
+ expect(description).toContain("SKILL.md");
+ });
+ });
+
it("returns the execute skill body by name", async () => {
await withClient(makeStubEngine({}), NO_CAPS, async (client) => {
const result = await client.callTool({
@@ -1866,6 +1879,9 @@ describe("MCP host server — skills tool", () => {
});
expect(result.isError).toBe(true);
expect(textOf(result)).toContain('No skill named "nope"');
+ // The miss is where a model that asked for an outside skill lands, so the
+ // note names the boundary instead of only reporting the bad name.
+ expect(textOf(result)).toContain("only Executor's own docs");
expect(textOf(result)).toContain("`execute`");
expect(result.structuredContent).toBeUndefined();
});
diff --git a/packages/hosts/mcp/src/tool-server.ts b/packages/hosts/mcp/src/tool-server.ts
index 41c61bcd0a..26014e5637 100644
--- a/packages/hosts/mcp/src/tool-server.ts
+++ b/packages/hosts/mcp/src/tool-server.ts
@@ -771,6 +771,11 @@ const fallbackOutcomeResult = (
// skill's body; an unknown name -> the index plus a not-found note so the model
// retries with a listed name instead of the same miss.
//
+// The miss is also where a model that mistook this for a general skill reader
+// arrives — a host with no skill tool of its own reads `executor_skills` as the
+// one it is missing and asks it for the harness's or the user's skills — so the
+// note names the boundary rather than only reporting the bad name.
+//
// The skill body IS the payload, returned as plain text content. We do NOT
// attach `structuredContent`: a client that prefers structured output (Claude
// Code does) will surface only that and drop the text, so the long-form guide
@@ -797,7 +802,10 @@ const skillsResult = (
if (!skill) {
return {
content: [
- { type: "text", text: `No skill named "${trimmed}".\n\n${renderSkillsIndex(catalog)}` },
+ {
+ type: "text",
+ text: `No skill named "${trimmed}". This tool serves only Executor's own docs, listed below — a skill from your harness or the user's project is not reachable from here.\n\n${renderSkillsIndex(catalog)}`,
+ },
],
isError: true,
};
@@ -1505,15 +1513,18 @@ export const createExecutorMcpServer = (
"skills",
{
description: [
- "Fetch a named how-to skill. Skills hold the long-form guidance that would otherwise bloat another tool's always-loaded description.",
+ "Documentation for THIS server's own tools. Not a general skill reader: it serves a short, fixed set of how-to docs about using `execute` and artifacts here, and it cannot reach your harness's skills, a SKILL.md on disk, or any user- or project-authored skill. The argument is a name from its own catalog, never a path or an outside skill's id.",
+ "These docs hold the long-form guidance that would otherwise bloat another tool's always-loaded description.",
'Call `skills({ name: "execute" })` for the full guide to writing code for the `execute` tool (search the catalog, call tools, emit results, resume paused runs).',
- "Call with no name to list the available skills.",
+ "Call with no name to list the few docs available.",
].join("\n"),
inputSchema: {
name: z
.string()
.optional()
- .describe('The skill to fetch, e.g. "execute". Omit to list available skills.'),
+ .describe(
+ 'A doc from this server\'s own catalog, e.g. "execute" — not a path or an outside skill name. Omit to list the catalog.',
+ ),
},
},
({ name }) =>
diff --git a/packages/kernel/core/CHANGELOG.md b/packages/kernel/core/CHANGELOG.md
index fbcb33457f..091a003204 100644
--- a/packages/kernel/core/CHANGELOG.md
+++ b/packages/kernel/core/CHANGELOG.md
@@ -1,5 +1,7 @@
# @executor-js/codemode-core
+## 1.6.0
+
## 1.5.42
## 1.5.41
diff --git a/packages/kernel/core/package.json b/packages/kernel/core/package.json
index 657dacd8b2..ec8701de58 100644
--- a/packages/kernel/core/package.json
+++ b/packages/kernel/core/package.json
@@ -1,6 +1,6 @@
{
"name": "@executor-js/codemode-core",
- "version": "1.5.42",
+ "version": "1.6.0",
"homepage": "https://github.com/UsefulSoftwareCo/executor/tree/main/packages/kernel/core",
"bugs": {
"url": "https://github.com/UsefulSoftwareCo/executor/issues"
diff --git a/packages/kernel/runtime-quickjs/CHANGELOG.md b/packages/kernel/runtime-quickjs/CHANGELOG.md
index cd9579188e..46bac973ea 100644
--- a/packages/kernel/runtime-quickjs/CHANGELOG.md
+++ b/packages/kernel/runtime-quickjs/CHANGELOG.md
@@ -1,5 +1,12 @@
# @executor-js/runtime-quickjs
+## 1.6.0
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @executor-js/codemode-core@1.6.0
+
## 1.5.42
### Patch Changes
diff --git a/packages/kernel/runtime-quickjs/package.json b/packages/kernel/runtime-quickjs/package.json
index 1479dfd8b3..8e9d8dba77 100644
--- a/packages/kernel/runtime-quickjs/package.json
+++ b/packages/kernel/runtime-quickjs/package.json
@@ -1,6 +1,6 @@
{
"name": "@executor-js/runtime-quickjs",
- "version": "1.5.42",
+ "version": "1.6.0",
"homepage": "https://github.com/UsefulSoftwareCo/executor/tree/main/packages/kernel/runtime-quickjs",
"bugs": {
"url": "https://github.com/UsefulSoftwareCo/executor/issues"
diff --git a/packages/kernel/runtime-workerd-subprocess/CHANGELOG.md b/packages/kernel/runtime-workerd-subprocess/CHANGELOG.md
index e7bca976d3..462125d3e7 100644
--- a/packages/kernel/runtime-workerd-subprocess/CHANGELOG.md
+++ b/packages/kernel/runtime-workerd-subprocess/CHANGELOG.md
@@ -1,5 +1,12 @@
# @executor-js/runtime-workerd-subprocess
+## 0.0.15
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @executor-js/codemode-core@1.6.0
+
## 0.0.14
### Patch Changes
diff --git a/packages/kernel/runtime-workerd-subprocess/package.json b/packages/kernel/runtime-workerd-subprocess/package.json
index a72abff29b..66b32fd78a 100644
--- a/packages/kernel/runtime-workerd-subprocess/package.json
+++ b/packages/kernel/runtime-workerd-subprocess/package.json
@@ -1,6 +1,6 @@
{
"name": "@executor-js/runtime-workerd-subprocess",
- "version": "0.0.14",
+ "version": "0.0.15",
"private": true,
"type": "module",
"exports": {
diff --git a/packages/plugins/desktop-settings/CHANGELOG.md b/packages/plugins/desktop-settings/CHANGELOG.md
index 9f2be893e6..7931f1c09b 100644
--- a/packages/plugins/desktop-settings/CHANGELOG.md
+++ b/packages/plugins/desktop-settings/CHANGELOG.md
@@ -1,5 +1,12 @@
# @executor-js/plugin-desktop-settings
+## 1.6.0
+
+### Patch Changes
+
+- Updated dependencies [[`a2d1417`](https://github.com/UsefulSoftwareCo/executor/commit/a2d141758e478274813c8c24d354e1fd0f66af49)]:
+ - @executor-js/sdk@1.6.0
+
## 1.5.42
### Patch Changes
diff --git a/packages/plugins/desktop-settings/package.json b/packages/plugins/desktop-settings/package.json
index f4fa0ae7bf..fd1016ab6b 100644
--- a/packages/plugins/desktop-settings/package.json
+++ b/packages/plugins/desktop-settings/package.json
@@ -1,6 +1,6 @@
{
"name": "@executor-js/plugin-desktop-settings",
- "version": "1.5.42",
+ "version": "1.6.0",
"homepage": "https://github.com/UsefulSoftwareCo/executor/tree/main/packages/plugins/desktop-settings",
"bugs": {
"url": "https://github.com/UsefulSoftwareCo/executor/issues"
diff --git a/packages/plugins/encrypted-secrets/CHANGELOG.md b/packages/plugins/encrypted-secrets/CHANGELOG.md
index 4e55a78b43..18abe13c9c 100644
--- a/packages/plugins/encrypted-secrets/CHANGELOG.md
+++ b/packages/plugins/encrypted-secrets/CHANGELOG.md
@@ -1,5 +1,12 @@
# @executor-js/plugin-encrypted-secrets
+## 0.0.42
+
+### Patch Changes
+
+- Updated dependencies [[`a2d1417`](https://github.com/UsefulSoftwareCo/executor/commit/a2d141758e478274813c8c24d354e1fd0f66af49)]:
+ - @executor-js/sdk@1.6.0
+
## 0.0.41
### Patch Changes
diff --git a/packages/plugins/encrypted-secrets/package.json b/packages/plugins/encrypted-secrets/package.json
index f78aee5a1c..f2272cdc9f 100644
--- a/packages/plugins/encrypted-secrets/package.json
+++ b/packages/plugins/encrypted-secrets/package.json
@@ -1,6 +1,6 @@
{
"name": "@executor-js/plugin-encrypted-secrets",
- "version": "0.0.41",
+ "version": "0.0.42",
"private": true,
"type": "module",
"exports": {
diff --git a/packages/plugins/example/CHANGELOG.md b/packages/plugins/example/CHANGELOG.md
index 8f68c06d39..578bffb513 100644
--- a/packages/plugins/example/CHANGELOG.md
+++ b/packages/plugins/example/CHANGELOG.md
@@ -1,5 +1,12 @@
# @executor-js/plugin-example
+## 1.6.0
+
+### Patch Changes
+
+- Updated dependencies [[`a2d1417`](https://github.com/UsefulSoftwareCo/executor/commit/a2d141758e478274813c8c24d354e1fd0f66af49)]:
+ - @executor-js/sdk@1.6.0
+
## 1.5.42
### Patch Changes
diff --git a/packages/plugins/example/package.json b/packages/plugins/example/package.json
index b6afab9661..498bd20205 100644
--- a/packages/plugins/example/package.json
+++ b/packages/plugins/example/package.json
@@ -1,6 +1,6 @@
{
"name": "@executor-js/plugin-example",
- "version": "1.5.42",
+ "version": "1.6.0",
"homepage": "https://github.com/UsefulSoftwareCo/executor/tree/main/packages/plugins/example",
"bugs": {
"url": "https://github.com/UsefulSoftwareCo/executor/issues"
diff --git a/packages/plugins/file-secrets/CHANGELOG.md b/packages/plugins/file-secrets/CHANGELOG.md
index 2118d5a4fc..a0539f41e4 100644
--- a/packages/plugins/file-secrets/CHANGELOG.md
+++ b/packages/plugins/file-secrets/CHANGELOG.md
@@ -1,5 +1,12 @@
# @executor-js/plugin-file-secrets
+## 1.6.0
+
+### Patch Changes
+
+- Updated dependencies [[`a2d1417`](https://github.com/UsefulSoftwareCo/executor/commit/a2d141758e478274813c8c24d354e1fd0f66af49)]:
+ - @executor-js/sdk@1.6.0
+
## 1.5.42
### Patch Changes
diff --git a/packages/plugins/file-secrets/package.json b/packages/plugins/file-secrets/package.json
index 0808aaf07b..ece5b36650 100644
--- a/packages/plugins/file-secrets/package.json
+++ b/packages/plugins/file-secrets/package.json
@@ -1,6 +1,6 @@
{
"name": "@executor-js/plugin-file-secrets",
- "version": "1.5.42",
+ "version": "1.6.0",
"homepage": "https://github.com/UsefulSoftwareCo/executor/tree/main/packages/plugins/file-secrets",
"bugs": {
"url": "https://github.com/UsefulSoftwareCo/executor/issues"
diff --git a/packages/plugins/graphql/CHANGELOG.md b/packages/plugins/graphql/CHANGELOG.md
index 9635c2251b..7c05160406 100644
--- a/packages/plugins/graphql/CHANGELOG.md
+++ b/packages/plugins/graphql/CHANGELOG.md
@@ -1,5 +1,15 @@
# @executor-js/plugin-graphql
+## 1.6.0
+
+### Patch Changes
+
+- Updated dependencies [[`a2d1417`](https://github.com/UsefulSoftwareCo/executor/commit/a2d141758e478274813c8c24d354e1fd0f66af49), [`2bdbedf`](https://github.com/UsefulSoftwareCo/executor/commit/2bdbedf257f54d7c209e8c856c618174c10d6bb3)]:
+ - @executor-js/sdk@1.6.0
+ - @executor-js/react@1.4.63
+ - @executor-js/api@1.4.63
+ - @executor-js/config@1.6.0
+
## 1.5.42
### Patch Changes
diff --git a/packages/plugins/graphql/package.json b/packages/plugins/graphql/package.json
index 327582cb51..eaf8f691e5 100644
--- a/packages/plugins/graphql/package.json
+++ b/packages/plugins/graphql/package.json
@@ -1,6 +1,6 @@
{
"name": "@executor-js/plugin-graphql",
- "version": "1.5.42",
+ "version": "1.6.0",
"homepage": "https://github.com/UsefulSoftwareCo/executor/tree/main/packages/plugins/graphql",
"bugs": {
"url": "https://github.com/UsefulSoftwareCo/executor/issues"
diff --git a/packages/plugins/keychain/CHANGELOG.md b/packages/plugins/keychain/CHANGELOG.md
index 7e95a55057..680709d847 100644
--- a/packages/plugins/keychain/CHANGELOG.md
+++ b/packages/plugins/keychain/CHANGELOG.md
@@ -1,5 +1,12 @@
# @executor-js/plugin-keychain
+## 1.6.0
+
+### Patch Changes
+
+- Updated dependencies [[`a2d1417`](https://github.com/UsefulSoftwareCo/executor/commit/a2d141758e478274813c8c24d354e1fd0f66af49)]:
+ - @executor-js/sdk@1.6.0
+
## 1.5.42
### Patch Changes
diff --git a/packages/plugins/keychain/package.json b/packages/plugins/keychain/package.json
index cdd4e1e8e6..e0a2850034 100644
--- a/packages/plugins/keychain/package.json
+++ b/packages/plugins/keychain/package.json
@@ -1,6 +1,6 @@
{
"name": "@executor-js/plugin-keychain",
- "version": "1.5.42",
+ "version": "1.6.0",
"homepage": "https://github.com/UsefulSoftwareCo/executor/tree/main/packages/plugins/keychain",
"bugs": {
"url": "https://github.com/UsefulSoftwareCo/executor/issues"
diff --git a/packages/plugins/mcp/CHANGELOG.md b/packages/plugins/mcp/CHANGELOG.md
index 6acf7ea2a7..ac60bf0e78 100644
--- a/packages/plugins/mcp/CHANGELOG.md
+++ b/packages/plugins/mcp/CHANGELOG.md
@@ -1,5 +1,27 @@
# @executor-js/plugin-mcp
+## 1.6.0
+
+### Minor Changes
+
+- [#1733](https://github.com/UsefulSoftwareCo/executor/pull/1733) [`0b0b74f`](https://github.com/UsefulSoftwareCo/executor/commit/0b0b74f673b8098c5248159be36c648097f3c87b) Thanks [@RhysSullivan](https://github.com/RhysSullivan)! - Restore MCP spec 2026-07-28 negotiation for outbound MCP connections. Remote Streamable HTTP connections auto-negotiate the modern protocol era again, stdio servers can opt in per integration, and the negotiated era is recorded on connection handshake traces.
+
+### Patch Changes
+
+- [#1660](https://github.com/UsefulSoftwareCo/executor/pull/1660) [`c11bef2`](https://github.com/UsefulSoftwareCo/executor/commit/c11bef2cd049db7bbf51b15e18761b14acccb534) Thanks [@RhysSullivan](https://github.com/RhysSullivan)! - **Cloudflare ships as MCP-only, with code mode opted out**
+
+ The Cloudflare OpenAPI preset is gone from the default catalog; the MCP preset is the one Cloudflare entry. Its endpoint now pins `?codemode=false` because Cloudflare's MCP server otherwise hides the tool catalog behind a single code-execution tool, and executor already provides the code-execution surface. Hand-entered `mcp.cloudflare.com` URLs missing the opt-out get an inline warning in the add flow telling the user to append `?codemode=false`.
+
+- [#1654](https://github.com/UsefulSoftwareCo/executor/pull/1654) [`256e25e`](https://github.com/UsefulSoftwareCo/executor/commit/256e25e7b291b0c023bc7547d092004b66781bba) Thanks [@RhysSullivan](https://github.com/RhysSullivan)! - **Interrupted stdio dials no longer strand the spawned child process**
+
+ Cancelling an in-flight health check or tool discovery (a UI refresh aborting the request, or the 15s discovery timeout) abandoned the MCP connect handshake without closing the transport, leaving the spawned stdio child running indefinitely: for `docker run -i --rm` integrations, one stranded container per interrupted dial. The connect handshake now aborts on interruption (the SDK closes the transport, ending stdin and escalating to SIGTERM/SIGKILL), and tool discovery closes the connection even when the interrupt lands between the handshake completing and discovery starting.
+
+- Updated dependencies [[`a2d1417`](https://github.com/UsefulSoftwareCo/executor/commit/a2d141758e478274813c8c24d354e1fd0f66af49), [`2bdbedf`](https://github.com/UsefulSoftwareCo/executor/commit/2bdbedf257f54d7c209e8c856c618174c10d6bb3)]:
+ - @executor-js/sdk@1.6.0
+ - @executor-js/react@1.4.63
+ - @executor-js/api@1.4.63
+ - @executor-js/config@1.6.0
+
## 1.5.42
### Patch Changes
diff --git a/packages/plugins/mcp/package.json b/packages/plugins/mcp/package.json
index fac079160a..79deee56fd 100644
--- a/packages/plugins/mcp/package.json
+++ b/packages/plugins/mcp/package.json
@@ -1,6 +1,6 @@
{
"name": "@executor-js/plugin-mcp",
- "version": "1.5.42",
+ "version": "1.6.0",
"homepage": "https://github.com/UsefulSoftwareCo/executor/tree/main/packages/plugins/mcp",
"bugs": {
"url": "https://github.com/UsefulSoftwareCo/executor/issues"
@@ -65,6 +65,8 @@
"@effect/platform-node": "catalog:",
"@executor-js/config": "workspace:*",
"@executor-js/sdk": "workspace:*",
+ "@modelcontextprotocol/client": "2.0.0",
+ "@modelcontextprotocol/core": "2.0.0",
"@modelcontextprotocol/sdk": "^1.29.0",
"zod": "4.3.6"
},
@@ -73,6 +75,7 @@
"@effect/vitest": "catalog:",
"@executor-js/api": "workspace:*",
"@executor-js/react": "workspace:*",
+ "@modelcontextprotocol/server": "2.0.0",
"@types/node": "catalog:",
"@types/react": "catalog:",
"bun-types": "catalog:",
diff --git a/packages/plugins/mcp/src/api/group.ts b/packages/plugins/mcp/src/api/group.ts
index 324b9d6841..2de2fb1222 100644
--- a/packages/plugins/mcp/src/api/group.ts
+++ b/packages/plugins/mcp/src/api/group.ts
@@ -57,6 +57,10 @@ const AddStdioServerPayload = Schema.Struct({
/** One-shot secret env values (programmatic). The UI sends `envVars`. */
env: Schema.optional(StringMap),
cwd: Schema.optional(Schema.String),
+ /** Protocol negotiation at connect: `auto` probes `server/discover` (spec
+ * 2026-07-28) for modern-only servers; default is the legacy `initialize`
+ * handshake. */
+ versionNegotiation: Schema.optional(Schema.Literals(["legacy", "auto"])),
slug: Schema.optional(Schema.String),
});
diff --git a/packages/plugins/mcp/src/api/handlers.ts b/packages/plugins/mcp/src/api/handlers.ts
index 6ca5c3188a..2b05275ad3 100644
--- a/packages/plugins/mcp/src/api/handlers.ts
+++ b/packages/plugins/mcp/src/api/handlers.ts
@@ -39,6 +39,7 @@ const toServerInput = (
envVars?: readonly string[];
env?: Record;
cwd?: string;
+ versionNegotiation?: "legacy" | "auto";
slug?: string;
};
return {
@@ -50,6 +51,7 @@ const toServerInput = (
envVars: p.envVars ? [...p.envVars] : undefined,
env: p.env,
cwd: p.cwd,
+ versionNegotiation: p.versionNegotiation,
slug: p.slug,
};
}
diff --git a/packages/plugins/mcp/src/sdk/connection-pool.ts b/packages/plugins/mcp/src/sdk/connection-pool.ts
index 6b732caf78..bf25c866a2 100644
--- a/packages/plugins/mcp/src/sdk/connection-pool.ts
+++ b/packages/plugins/mcp/src/sdk/connection-pool.ts
@@ -3,6 +3,10 @@ import { Cause, Effect, Exit, Predicate } from "effect";
import type { McpConnection, McpConnector } from "./connection";
import type { McpInvocationError } from "./errors";
+// The pool preserves sessions for sessionful legacy servers. Stateless
+// 2026-07-28 servers do not need it, but retaining a cheap idle client is
+// harmless and keeps one lifecycle for both protocol eras.
+
const IDLE_TTL_MS = 5 * 60 * 1_000;
type IdleConnection = {
diff --git a/packages/plugins/mcp/src/sdk/connection.ts b/packages/plugins/mcp/src/sdk/connection.ts
index 5ac15669c6..3035716858 100644
--- a/packages/plugins/mcp/src/sdk/connection.ts
+++ b/packages/plugins/mcp/src/sdk/connection.ts
@@ -1,16 +1,18 @@
-import type { OAuthClientProvider } from "@modelcontextprotocol/sdk/client/auth.js";
-import { Client } from "@modelcontextprotocol/sdk/client/index.js";
-import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
-import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
-import type { FetchLike } from "@modelcontextprotocol/sdk/shared/transport.js";
-import { CfWorkerJsonSchemaValidator } from "@modelcontextprotocol/sdk/validation/cfworker";
+import {
+ Client,
+ SSEClientTransport,
+ StreamableHTTPClientTransport,
+ type FetchLike,
+ type OAuthClientProvider,
+} from "@modelcontextprotocol/client";
+import { CfWorkerJsonSchemaValidator } from "@modelcontextprotocol/client/validators/cf-worker";
import { Effect, Layer, Predicate, Stream } from "effect";
import { HttpClient, HttpClientRequest } from "effect/unstable/http";
// NOTE: `StdioClientTransport` is NOT imported eagerly. The upstream module
-// (`@modelcontextprotocol/sdk/client/stdio.js`) touches `node:child_process`
-// at evaluation time, which crashes workerd (incl. vitest-pool-workers) at
-// SIGSEGV on module instantiation. Cloud callers set
+// (`@modelcontextprotocol/client/stdio`) still imports Node process/stream and
+// `cross-spawn` eagerly at evaluation time, which crashes workerd (including
+// vitest-pool-workers) with SIGSEGV on module instantiation. Cloud callers set
// `dangerouslyAllowStdioMCP: false` and never reach the stdio branch below;
// prod bundles that DO use stdio load it via a dynamic import inside the
// stdio branch of `createMcpConnector`.
@@ -201,12 +203,13 @@ const fetchFromHttpClientLayer = (
// MCP plugin runs inside a Cloudflare Worker (executor.sh). The
// cfworker validator does not use code generation and works in every
// runtime we ship to.
-const createClient = (): Client =>
+const createClient = (versionNegotiation?: { readonly mode: "auto" }): Client =>
new Client(
{ name: "executor-mcp", version: "0.1.0" },
{
capabilities: { elicitation: { form: {}, url: {} } },
jsonSchemaValidator: new CfWorkerJsonSchemaValidator(),
+ ...(versionNegotiation === undefined ? {} : { versionNegotiation }),
},
);
@@ -247,9 +250,10 @@ const connectionFailure = (
const connectClient = (input: {
transport: string;
createTransport: () => Parameters[0];
+ versionNegotiation?: { readonly mode: "auto" };
}): Effect.Effect =>
Effect.gen(function* () {
- const client = createClient();
+ const client = createClient(input.versionNegotiation);
const transportInstance = input.createTransport();
yield* Effect.tryPromise({
@@ -262,6 +266,15 @@ const connectClient = (input: {
catch: (cause) =>
connectionFailure(input.transport, `Failed connecting via ${input.transport}`, cause),
}).pipe(
+ // The negotiated era ("modern" = 2026-07-28 server/discover, "legacy" =
+ // 2025 initialize) is otherwise invisible: both eras list and call tools
+ // identically, so traces are the one place an integration author can
+ // verify which handshake a connection actually used.
+ Effect.tap(() =>
+ Effect.annotateCurrentSpan({
+ "plugin.mcp.protocol_era": client.getProtocolEra() ?? "unknown",
+ }),
+ ),
Effect.withSpan("plugin.mcp.connection.handshake", {
attributes: { "plugin.mcp.transport": input.transport },
}),
@@ -300,6 +313,12 @@ export const createMcpConnector = (input: ConnectorInput): McpConnector => {
return yield* connectClient({
transport: "stdio",
+ // Opt-in per integration (default legacy) — see
+ // `McpStdioVersionNegotiation` for why stdio does not follow the
+ // remote transport's unconditional auto.
+ ...(input.versionNegotiation === "auto"
+ ? { versionNegotiation: { mode: "auto" as const } }
+ : {}),
createTransport: () =>
createStdioTransport({
command,
@@ -319,8 +338,13 @@ export const createMcpConnector = (input: ConnectorInput): McpConnector => {
const endpoint = buildEndpointUrl(input.endpoint, input.queryParams ?? {});
+ // Auto-negotiate the 2026-07-28 era unconditionally only on Streamable
+ // HTTP. SSE is a legacy-only transport; stdio negotiates per the
+ // integration's `versionNegotiation` (default legacy — see the stdio
+ // branch above).
const connectStreamableHttp = connectClient({
transport: "streamable-http",
+ versionNegotiation: { mode: "auto" },
createTransport: () =>
new StreamableHTTPClientTransport(endpoint, {
requestInit,
diff --git a/packages/plugins/mcp/src/sdk/elicitation.test.ts b/packages/plugins/mcp/src/sdk/elicitation.test.ts
index 4ca64f32a2..cc2bed354f 100644
--- a/packages/plugins/mcp/src/sdk/elicitation.test.ts
+++ b/packages/plugins/mcp/src/sdk/elicitation.test.ts
@@ -1,7 +1,7 @@
import { describe, expect, it } from "@effect/vitest";
import { Effect, Predicate, Schema, Semaphore } from "effect";
-import { CfWorkerJsonSchemaValidator } from "@modelcontextprotocol/sdk/validation/cfworker";
-import type { JsonSchemaType } from "@modelcontextprotocol/sdk/validation/types";
+import type { JsonSchemaType } from "@modelcontextprotocol/client";
+import { CfWorkerJsonSchemaValidator } from "@modelcontextprotocol/client/validators/cf-worker";
import {
AuthTemplateSlug,
@@ -226,7 +226,7 @@ describe("MCP elicitation (end-to-end)", () => {
]),
);
expect(schema?.outputTypeScript).toContain('type: "text"');
- expect(schema?.outputTypeScript).toContain("structuredContent?: { [k: string]: unknown; }");
+ expect(schema?.outputTypeScript).toContain("structuredContent?: unknown;");
const result = yield* executor.execute(
simpleEcho.address,
diff --git a/packages/plugins/mcp/src/sdk/http-status.test.ts b/packages/plugins/mcp/src/sdk/http-status.test.ts
index 2276509f7e..0344a71986 100644
--- a/packages/plugins/mcp/src/sdk/http-status.test.ts
+++ b/packages/plugins/mcp/src/sdk/http-status.test.ts
@@ -1,4 +1,5 @@
import { describe, expect, it } from "@effect/vitest";
+import { InsufficientScopeError, SdkErrorCode, SdkHttpError } from "@modelcontextprotocol/client";
// oxlint-disable executor/no-error-constructor -- boundary: these tests reproduce the MCP SDK's own transport rejections, which are built-in Errors
import { insufficientScopeFromCause } from "./http-status";
@@ -9,7 +10,8 @@ import { insufficientScopeFromCause } from "./http-status";
// - with an authProvider (the production OAuth path): the StreamableHTTP
// transport consumes the insufficient_scope challenge itself, retries
// with the broader scope, and only when THAT fails throws the fixed
-// "Server returned 403 after trying upscoping" message.
+// typed `InsufficientScopeError`, or after retry exhaustion the fixed
+// `SdkHttpError` step-up message.
describe("insufficientScopeFromCause", () => {
it("detects the OAuth error body embedded in a transport message", () => {
expect(
@@ -31,9 +33,21 @@ describe("insufficientScopeFromCause", () => {
).toBe(true);
});
- it("detects the SDK's exhausted-upscoping failure (the authProvider path)", () => {
+ it("detects the SDK's typed insufficient-scope failure", () => {
expect(
- insufficientScopeFromCause(new Error("Server returned 403 after trying upscoping")),
+ insufficientScopeFromCause(new InsufficientScopeError({ requiredScope: "files.read" })),
+ ).toBe(true);
+ });
+
+ it("detects the SDK's exhausted step-up failure (the authProvider path)", () => {
+ expect(
+ insufficientScopeFromCause(
+ new SdkHttpError(
+ SdkErrorCode.ClientHttpForbidden,
+ "Server returned 403 insufficient_scope after step-up re-authorization (retry limit 2 reached)",
+ { status: 403 },
+ ),
+ ),
).toBe(true);
});
diff --git a/packages/plugins/mcp/src/sdk/http-status.ts b/packages/plugins/mcp/src/sdk/http-status.ts
index a4442f8d23..541c631f34 100644
--- a/packages/plugins/mcp/src/sdk/http-status.ts
+++ b/packages/plugins/mcp/src/sdk/http-status.ts
@@ -1,7 +1,8 @@
// ---------------------------------------------------------------------------
// Extract the HTTP status from an MCP SDK transport error. The SDK surfaces
-// transport failures two ways: a `StreamableHTTPError` subclass carrying a
-// numeric `code`, and an SSE POST failure whose message embeds `(HTTP nnn)`.
+// transport failures two ways: an `SdkHttpError` carrying a numeric `status`,
+// and an `SseError` carrying a numeric `code`. The SSE transport also retains
+// its historic POST-failure message for errors created below EventSource.
// Shared by the invoke path (classifies tool-call failures) and the connect
// path (so a 401/403 during the handshake reaches the liveness health check).
// ---------------------------------------------------------------------------
@@ -9,13 +10,13 @@
import { Option, Schema } from "effect";
import { insufficientScopeFromEmbeddedJson } from "@executor-js/sdk/core";
-import { StreamableHTTPError } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
+import { InsufficientScopeError, SdkHttpError, SseError } from "@modelcontextprotocol/client";
const SsePostErrorCause = Schema.Struct({ message: Schema.String });
const decodeSsePostErrorCause = Schema.decodeUnknownOption(SsePostErrorCause);
-// Matches the SDK's SSEClientTransport POST-failure message (sse.js); re-verify
-// on SDK bumps. A format drift just yields undefined (generic error, no crash).
+// V2 still constructs this exact message in SSEClientTransport._send. A format
+// drift just yields undefined (generic error, no crash).
const statusFromSsePostError = (cause: unknown): number | undefined =>
Option.match(decodeSsePostErrorCause(cause), {
onNone: () => undefined,
@@ -26,32 +27,36 @@ const statusFromSsePostError = (cause: unknown): number | undefined =>
},
});
-const statusFromStreamableHttpError = (cause: unknown): number | undefined => {
- // oxlint-disable-next-line executor/no-instanceof-tagged-error -- boundary: MCP SDK exposes transport HTTP failures as this Error subclass; protocol errors can carry the same numeric code
- if (!(cause instanceof StreamableHTTPError)) return undefined;
- const code = cause.code;
- return code !== undefined && code >= 100 && code <= 599 ? code : undefined;
+const statusFromTypedTransportError = (cause: unknown): number | undefined => {
+ if (SdkHttpError.isInstance(cause)) return cause.status;
+ if (SseError.isInstance(cause)) {
+ const code = cause.code;
+ return code !== undefined && code >= 100 && code <= 599 ? code : undefined;
+ }
+ return undefined;
};
export const httpStatusFromCause = (cause: unknown): number | undefined =>
- statusFromStreamableHttpError(cause) ?? statusFromSsePostError(cause);
+ statusFromTypedTransportError(cause) ?? statusFromSsePostError(cause);
// The SDK embeds the upstream response text in the transport error message
// ("Error POSTing to endpoint: "), which is the only place a 403's body
// survives for connections without an authProvider. For OAuth connections the
-// StreamableHTTP transport consumes the insufficient_scope challenge ITSELF:
-// it re-runs auth requesting the broader scope, and only when that upscoped
-// retry still 403s does it throw — with the fixed message matched below
-// (verified against @modelcontextprotocol/sdk streamableHttp.js; re-verify on
-// SDK bumps). Both paths mean the same thing: the grant does not cover the
-// operation, and re-running the identical flow cannot help. Strict matching
+// StreamableHTTP transport consumes the insufficient_scope challenge itself.
+// V2 throws `InsufficientScopeError` when configured not to reauthorize; after
+// exhausting its step-up retries it throws `SdkHttpError` with the exact fixed
+// message matched below (verified against the installed v2 transport source).
+// Both paths mean the same thing: the grant does not cover the operation, and
+// re-running the identical flow cannot help. Strict matching
// (exact serialized field forms via the shared core detector, or the SDK's
-// exact upscoping message) — a miss stays on the generic auth path.
-const SDK_UPSCOPING_EXHAUSTED_RE = /Server returned 403 after trying upscoping/;
+// exact step-up message) — a miss stays on the generic auth path.
+const SDK_STEP_UP_EXHAUSTED_RE =
+ /^Server returned 403 insufficient_scope after step-up re-authorization \(retry limit \d+ reached\)$/;
export const insufficientScopeFromCause = (cause: unknown): boolean =>
+ InsufficientScopeError.isInstance(cause) ||
Option.match(decodeSsePostErrorCause(cause), {
onNone: () => false,
onSome: ({ message }) =>
- insufficientScopeFromEmbeddedJson(message) || SDK_UPSCOPING_EXHAUSTED_RE.test(message),
+ insufficientScopeFromEmbeddedJson(message) || SDK_STEP_UP_EXHAUSTED_RE.test(message),
});
diff --git a/packages/plugins/mcp/src/sdk/invoke.test.ts b/packages/plugins/mcp/src/sdk/invoke.test.ts
index 3b5aaae66f..ce133bfd5f 100644
--- a/packages/plugins/mcp/src/sdk/invoke.test.ts
+++ b/packages/plugins/mcp/src/sdk/invoke.test.ts
@@ -2,9 +2,12 @@ import { describe, expect, it } from "@effect/vitest";
import { Effect, Predicate } from "effect";
import { HttpServerResponse } from "effect/unstable/http";
-import type { OAuthClientProvider } from "@modelcontextprotocol/sdk/client/auth.js";
-import { StreamableHTTPError } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
-import { McpError } from "@modelcontextprotocol/sdk/types.js";
+import {
+ ProtocolError,
+ SdkErrorCode,
+ SdkHttpError,
+ type OAuthClientProvider,
+} from "@modelcontextprotocol/client";
import { ElicitationResponse } from "@executor-js/sdk";
import { serveTestHttpApp } from "@executor-js/sdk/testing";
@@ -108,14 +111,16 @@ const invocationRejectionCases = [
name: "wraps callTool rejection with a stable message and status",
toolId: "blocked",
transport: "streamable-http",
- cause: new StreamableHTTPError(401, "token=do-not-leak"),
+ cause: new SdkHttpError(SdkErrorCode.ClientHttpAuthentication, "token=do-not-leak", {
+ status: 401,
+ }),
expectedStatus: 401 as number | undefined,
},
{
name: "does not treat MCP protocol error codes as HTTP statuses",
toolId: "protocol_error",
transport: "streamable-http",
- cause: new McpError(401, "application-level do-not-leak"),
+ cause: new ProtocolError(401, "application-level do-not-leak"),
expectedStatus: undefined,
},
{
diff --git a/packages/plugins/mcp/src/sdk/invoke.ts b/packages/plugins/mcp/src/sdk/invoke.ts
index 3e5aa7695f..46a08bc663 100644
--- a/packages/plugins/mcp/src/sdk/invoke.ts
+++ b/packages/plugins/mcp/src/sdk/invoke.ts
@@ -8,7 +8,7 @@
// legitimately retain state in that session. The pool keeps one idle
// connection per resolved identity and leases it exclusively per invoke;
// stdio and callers without a pool remain strictly per-call.
-// 2. Installing a per-invocation `ElicitRequestSchema` handler that bridges
+// 2. Installing a per-invocation `elicitation/create` handler that bridges
// MCP's elicit capability into the host's elicit function threaded via
// `InvokeToolInput.elicit`.
// 3. Calling `client.callTool({ name, arguments })`.
@@ -16,12 +16,7 @@
import { Cause, Effect, Exit, Option, Predicate, Schema } from "effect";
-import {
- ElicitRequestSchema,
- ErrorCode,
- McpError,
- ToolListChangedNotificationSchema,
-} from "@modelcontextprotocol/sdk/types.js";
+import { ProtocolError, ProtocolErrorCode } from "@modelcontextprotocol/client";
import {
ElicitationId,
@@ -66,9 +61,10 @@ export const isUnknownToolMessage = (message: string, toolName: string): boolean
const isUnknownToolCause = (cause: unknown, toolName: string): boolean =>
// oxlint-disable-next-line executor/no-instanceof-tagged-error -- boundary: MCP SDK surfaces JSON-RPC protocol errors as this Error subclass
- cause instanceof McpError &&
- (cause.code === ErrorCode.InvalidParams || cause.code === ErrorCode.MethodNotFound) &&
- // oxlint-disable-next-line executor/no-unknown-error-message -- boundary: instanceof narrows to the SDK's McpError, whose message carries the only unknown-tool discriminator the protocol provides
+ cause instanceof ProtocolError &&
+ (cause.code === ProtocolErrorCode.InvalidParams ||
+ cause.code === ProtocolErrorCode.MethodNotFound) &&
+ // oxlint-disable-next-line executor/no-unknown-error-message -- boundary: instanceof narrows to the SDK's ProtocolError, whose message carries the only unknown-tool discriminator the protocol provides
isUnknownToolMessage(cause.message, toolName);
// ---------------------------------------------------------------------------
@@ -93,6 +89,17 @@ const McpElicitParams = Schema.Union([
type McpElicitParams = typeof McpElicitParams.Type;
const decodeElicitParams = Schema.decodeUnknownSync(McpElicitParams);
+const decodeElicitContent = Schema.decodeUnknownSync(
+ Schema.Record(
+ Schema.String,
+ Schema.Union([
+ Schema.String,
+ Schema.Number,
+ Schema.Boolean,
+ Schema.mutable(Schema.Array(Schema.String)),
+ ]),
+ ),
+);
const toElicitationRequest = (params: McpElicitParams): ElicitationRequest =>
params.mode === "url"
@@ -107,7 +114,7 @@ const toElicitationRequest = (params: McpElicitParams): ElicitationRequest =>
});
const installElicitationHandler = (client: McpConnection["client"], elicit: Elicit): void => {
- client.setRequestHandler(ElicitRequestSchema, async (request: { params: unknown }) => {
+ client.setRequestHandler("elicitation/create", async (request: { params: unknown }) => {
const params = decodeElicitParams(request.params);
const req = toElicitationRequest(params);
// Use runPromiseExit so we can inspect typed failures — `elicit`
@@ -119,7 +126,9 @@ const installElicitationHandler = (client: McpConnection["client"], elicit: Elic
const response = exit.value;
return {
action: response.action,
- ...(response.action === "accept" && response.content ? { content: response.content } : {}),
+ ...(response.action === "accept" && response.content
+ ? { content: decodeElicitContent(response.content) }
+ : {}),
};
}
const failure = exit.cause.reasons.find(Cause.isFailReason);
@@ -149,7 +158,7 @@ const installToolListChangedHandler = (
onToolListChanged: (() => void) | undefined,
): void => {
if (!onToolListChanged) return;
- client.setNotificationHandler(ToolListChangedNotificationSchema, () => {
+ client.setNotificationHandler("notifications/tools/list_changed", () => {
onToolListChanged();
});
};
@@ -189,8 +198,8 @@ const useConnection = (
});
}
const status = httpStatusFromCause(cause);
- // oxlint-disable-next-line executor/no-instanceof-tagged-error -- boundary: MCP SDK protocol failures are its McpError subclass; transport failures use other error shapes
- const protocolFailure = cause instanceof McpError;
+ // oxlint-disable-next-line executor/no-instanceof-tagged-error -- boundary: MCP SDK protocol failures are its ProtocolError subclass; transport failures use other error shapes
+ const protocolFailure = cause instanceof ProtocolError;
return new McpInvocationError({
toolName,
message: `MCP tool call failed for ${toolName}`,
diff --git a/packages/plugins/mcp/src/sdk/plugin.ts b/packages/plugins/mcp/src/sdk/plugin.ts
index 01f5ad7bc6..375e4e2493 100644
--- a/packages/plugins/mcp/src/sdk/plugin.ts
+++ b/packages/plugins/mcp/src/sdk/plugin.ts
@@ -1,9 +1,8 @@
import { Effect, Layer, Option, Result, Schema } from "effect";
import type { HttpClient } from "effect/unstable/http";
-import type { OAuthClientProvider } from "@modelcontextprotocol/sdk/client/auth.js";
-import { CallToolResultSchema } from "@modelcontextprotocol/sdk/types.js";
-import * as z from "zod/v4";
+import type { OAuthClientProvider } from "@modelcontextprotocol/client";
+import { CallToolResultSchema } from "@modelcontextprotocol/core";
import {
authToolFailure,
@@ -62,6 +61,7 @@ import {
expandMcpAuthMethodInputs,
mcpAuthMethodFromShorthand,
normalizeMcpAuthMethods,
+ McpStdioVersionNegotiation,
parseMcpIntegrationConfig,
type McpIntegrationConfig as McpIntegrationConfigType,
type McpStdioEnvMethod,
@@ -208,6 +208,11 @@ const McpStdioServerInputSchema = Schema.Struct({
* instead and leaves the values to the connect step. */
env: Schema.optional(Schema.Record(Schema.String, Schema.String)),
cwd: Schema.optional(Schema.String),
+ /** Protocol negotiation at connect: `auto` probes `server/discover` (spec
+ * 2026-07-28) for modern-only servers. Defaults to the legacy `initialize`
+ * handshake — the right call for spawn-per-call servers, where the auto
+ * probe costs an extra child process per connect. */
+ versionNegotiation: Schema.optional(McpStdioVersionNegotiation),
slug: Schema.optional(Schema.String),
});
@@ -371,6 +376,7 @@ const toIntegrationConfig = (input: McpServerInput): McpIntegrationConfigType =>
command: input.command,
args: input.args ? [...input.args] : undefined,
cwd: input.cwd,
+ versionNegotiation: input.versionNegotiation,
authenticationTemplate:
vars.length > 0
? [{ slug: STDIO_ENV_TEMPLATE, kind: "stdio_env", vars }]
@@ -393,7 +399,7 @@ type JsonSchemaObject = Record & {
readonly properties?: Record;
};
-const McpCallToolResultJsonSchema = z.toJSONSchema(CallToolResultSchema) as JsonSchemaObject;
+const McpCallToolResultJsonSchema: JsonSchemaObject = CallToolResultSchema.toJSONSchema();
const mcpCallToolResultOutputSchema = (structuredContentSchema?: unknown): JsonSchemaObject => {
const defaultStructuredContentSchema =
@@ -494,7 +500,9 @@ export const userFacingProbeMessage = (
// MCP-SDK OAuth provider adapter — wraps a pre-resolved access token so the
// transport sends it as a Bearer header. Refresh is core's responsibility
// (the connection row carries the OAuth grant); this adapter never initiates
-// a new flow and fails loudly if the SDK tries to.
+// a new flow and fails loudly if the SDK tries to. V2 stamps stored credentials
+// with the authorization-server issuer and offers scoped invalidation; this
+// single-token boundary intentionally persists neither.
// ---------------------------------------------------------------------------
const makeOAuthProvider = (accessToken: string): OAuthClientProvider => ({
@@ -587,6 +595,7 @@ const buildConnectorInput = (
args: config.args,
env: Object.keys(env).length > 0 ? env : undefined,
cwd: config.cwd,
+ versionNegotiation: config.versionNegotiation,
} satisfies McpStdioIntegrationConfig);
}
@@ -1045,6 +1054,7 @@ export const mcpPlugin = definePlugin((options?: McpPluginOptions) => {
command: config.command,
args: config.args,
cwd: config.cwd,
+ versionNegotiation: config.versionNegotiation,
authenticationTemplate: hasEnv
? [{ slug: STDIO_ENV_TEMPLATE, kind: "stdio_env", vars: envVars }]
: [{ slug: "none", kind: "none" }],
diff --git a/packages/plugins/mcp/src/sdk/probe-shape.test.ts b/packages/plugins/mcp/src/sdk/probe-shape.test.ts
index 7eb124ebaf..8a61f40b47 100644
--- a/packages/plugins/mcp/src/sdk/probe-shape.test.ts
+++ b/packages/plugins/mcp/src/sdk/probe-shape.test.ts
@@ -1,6 +1,12 @@
import { describe, expect, it } from "@effect/vitest";
-import { Effect, Ref } from "effect";
-import { HttpServerResponse } from "effect/unstable/http";
+import { Effect, Layer, Ref } from "effect";
+import {
+ HttpClient,
+ HttpClientError,
+ HttpClientRequest,
+ HttpClientResponse,
+ HttpServerResponse,
+} from "effect/unstable/http";
import { serveTestHttpApp } from "@executor-js/sdk/testing";
import { probeMcpEndpointShape } from "./probe-shape";
@@ -320,6 +326,88 @@ describe("probeMcpEndpointShape", () => {
),
);
+ it.effect("falls through a wrong-shape legacy GET retry to modern server discovery", () =>
+ Effect.scoped(
+ Effect.gen(function* () {
+ const server = yield* serveProbeEndpoint((request) => {
+ if (request.body.includes('"method":"server/discover"')) {
+ return HttpServerResponse.jsonUnsafe({
+ jsonrpc: "2.0",
+ id: 2,
+ error: { code: -32601, message: "Method not found" },
+ });
+ }
+ if (request.method === "GET") {
+ return HttpServerResponse.jsonUnsafe({ error: "legacy SSE is unsupported" });
+ }
+ return HttpServerResponse.empty({ status: 405 });
+ });
+
+ const result = yield* probeMcpEndpointShape(server.endpoint);
+ expect(result).toEqual({ kind: "mcp", requiresAuth: false });
+
+ const requests = yield* server.requests;
+ expect(requests).toHaveLength(3);
+ expect(requests[0]?.body).toContain('"protocolVersion":"2025-11-25"');
+ expect(requests[1]?.method).toBe("GET");
+ expect(requests[2]?.body).toBe(
+ JSON.stringify({
+ jsonrpc: "2.0",
+ id: 2,
+ method: "server/discover",
+ params: {
+ _meta: { "io.modelcontextprotocol/protocolVersion": "2026-07-28" },
+ },
+ }),
+ );
+ expect(requests[2]?.headers["mcp-protocol-version"]).toBe("2026-07-28");
+ }),
+ ),
+ );
+
+ // First request (initialize) answers 200 HTML; second (the discover
+ // fallback) dies at the transport. The endpoint already proved reachable,
+ // so the verdict must stay the initialize classification, not "unreachable".
+ it.effect("keeps the initialize verdict when the discover fallback fails at the transport", () =>
+ Effect.gen(function* () {
+ let requestCount = 0;
+ const httpClientLayer = Layer.succeed(HttpClient.HttpClient)(
+ HttpClient.make((request: HttpClientRequest.HttpClientRequest) => {
+ requestCount += 1;
+ if (requestCount > 1) {
+ return Effect.fail(
+ new HttpClientError.HttpClientError({
+ reason: new HttpClientError.TransportError({
+ request,
+ description: "connection reset by peer",
+ }),
+ }),
+ );
+ }
+ return Effect.succeed(
+ HttpClientResponse.fromWeb(
+ request,
+ new Response("not mcp", {
+ status: 200,
+ headers: { "content-type": "text/html" },
+ }),
+ ),
+ );
+ }),
+ );
+
+ const result = yield* probeMcpEndpointShape("https://internal.example/mcp", {
+ httpClientLayer,
+ });
+ expect(result).toEqual({
+ kind: "not-mcp",
+ category: "wrong-shape",
+ reason: "2xx POST body is not a JSON-RPC envelope",
+ });
+ expect(requestCount).toBe(2);
+ }),
+ );
+
it.effect("rejects 2xx with HTML body as wrong-shape", () =>
withServer(
() =>
diff --git a/packages/plugins/mcp/src/sdk/probe-shape.ts b/packages/plugins/mcp/src/sdk/probe-shape.ts
index 3b3be94297..1049fb9f22 100644
--- a/packages/plugins/mcp/src/sdk/probe-shape.ts
+++ b/packages/plugins/mcp/src/sdk/probe-shape.ts
@@ -13,7 +13,7 @@
// and (b) plenty of real MCP servers authenticate with static API
// keys and publish no OAuth metadata at all (e.g. cubic.dev).
//
-// The probe issues an unauth JSON-RPC `initialize` POST and accepts
+// The primary probe issues an unauth JSON-RPC `initialize` POST and accepts
// only the wire shapes a real MCP server can return:
//
// - 2xx with `Content-Type: text/event-stream` — streamable HTTP
@@ -30,8 +30,14 @@
// only accepts 2xx with `text/event-stream` or the same 401+Bearer
// shape.
//
-// One `fetch` (occasionally two), no MCP-SDK session state, no OAuth
-// round-trip, no DCR — every non-MCP endpoint exits here.
+// If initialize ultimately has the wrong shape, a second JSON-RPC POST probes
+// `server/discover` using the 2026-07-28 envelope and protocol header. This
+// catches modern-only servers that reject initialize with a non-JSON-RPC
+// response. Authentication outcomes remain terminal because they do not vary
+// by transport era.
+//
+// One primary request (occasionally plus legacy GET and modern discover), no
+// MCP-SDK session state, no OAuth round-trip, no DCR.
// ---------------------------------------------------------------------------
import { Data, Duration, Effect, Layer, Option, Schema } from "effect";
@@ -48,12 +54,21 @@ const INITIALIZE_BODY = JSON.stringify({
id: 1,
method: "initialize",
params: {
- protocolVersion: "2025-06-18",
+ protocolVersion: "2025-11-25",
capabilities: {},
clientInfo: { name: "executor-probe", version: "0" },
},
});
+const DISCOVER_BODY = JSON.stringify({
+ jsonrpc: "2.0",
+ id: 2,
+ method: "server/discover",
+ params: {
+ _meta: { "io.modelcontextprotocol/protocolVersion": "2026-07-28" },
+ },
+});
+
/** Header-name lookup is case-insensitive per RFC 7230. `fetch`'s
* `Response.headers` already lower-cases, but we normalise explicitly
* to stay robust against test mocks that construct `Headers` loosely. */
@@ -385,10 +400,9 @@ export const probeMcpEndpointShape = (
.execute(postRequest)
.pipe(Effect.timeout(Duration.millis(timeoutMs)));
- const postResult = yield* classify(postResponse, "POST");
- if (postResult) return postResult;
+ let initializeResult = yield* classify(postResponse, "POST");
- if ([404, 405, 406, 415].includes(postResponse.status)) {
+ if (initializeResult === null && [404, 405, 406, 415].includes(postResponse.status)) {
let getRequest = HttpClientRequest.get(url.toString()).pipe(
HttpClientRequest.setHeader("accept", "text/event-stream"),
);
@@ -398,15 +412,42 @@ export const probeMcpEndpointShape = (
const getResponse = yield* client
.execute(getRequest)
.pipe(Effect.timeout(Duration.millis(timeoutMs)));
- const getResult = yield* classify(getResponse, "GET");
- if (getResult) return getResult;
+ initializeResult = yield* classify(getResponse, "GET");
}
- return {
+ initializeResult ??= {
kind: "not-mcp",
category: "wrong-shape",
reason: `unexpected status ${postResponse.status} for initialize`,
} as const;
+
+ if (initializeResult.kind !== "not-mcp" || initializeResult.category !== "wrong-shape") {
+ return initializeResult;
+ }
+
+ let discoverRequest = HttpClientRequest.post(url.toString()).pipe(
+ HttpClientRequest.setHeader("content-type", "application/json"),
+ HttpClientRequest.setHeader("accept", "application/json, text/event-stream"),
+ HttpClientRequest.bodyText(DISCOVER_BODY, "application/json"),
+ );
+ for (const [name, value] of Object.entries(options.headers ?? {})) {
+ discoverRequest = HttpClientRequest.setHeader(discoverRequest, name, value);
+ }
+ discoverRequest = HttpClientRequest.setHeader(
+ discoverRequest,
+ "MCP-Protocol-Version",
+ "2026-07-28",
+ );
+
+ // The endpoint already answered the primary probe, so a transport
+ // failure on this secondary request must not overwrite that verdict
+ // with "unreachable" — keep the initialize classification instead.
+ const discoverResult = yield* client.execute(discoverRequest).pipe(
+ Effect.timeout(Duration.millis(timeoutMs)),
+ Effect.flatMap((discoverResponse) => classify(discoverResponse, "POST")),
+ Effect.catch(() => Effect.succeed(null)),
+ );
+ return discoverResult ?? initializeResult;
}).pipe(
Effect.provide(options.httpClientLayer ?? FetchHttpClient.layer),
Effect.mapError(
diff --git a/packages/plugins/mcp/src/sdk/stdio-connector.ts b/packages/plugins/mcp/src/sdk/stdio-connector.ts
index 99a0f72e37..6fec6f0617 100644
--- a/packages/plugins/mcp/src/sdk/stdio-connector.ts
+++ b/packages/plugins/mcp/src/sdk/stdio-connector.ts
@@ -3,8 +3,9 @@
// ---------------------------------------------------------------------------
//
// Kept in its own module so `connection.ts` never imports it eagerly at
-// module load. `@modelcontextprotocol/sdk/client/stdio.js` pulls in
-// `node:child_process` at evaluation time; under `@cloudflare/vitest-pool-workers`
+// module load. The v2 `@modelcontextprotocol/client/stdio` entry still eagerly
+// evaluates Node-only process/stream imports and `cross-spawn` (which loads
+// `node:child_process`); under `@cloudflare/vitest-pool-workers`
// that crashes workerd at module instantiation with SIGSEGV (prod bundles
// tree-shake it away when `dangerouslyAllowStdioMCP: false`, tests do not).
//
@@ -13,7 +14,7 @@
// the import and therefore never touch `node:child_process`.
// ---------------------------------------------------------------------------
-import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
+import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
export type StdioTransportConfig = {
readonly command: string;
diff --git a/packages/plugins/mcp/src/sdk/stdio-negotiation-test-server.ts b/packages/plugins/mcp/src/sdk/stdio-negotiation-test-server.ts
new file mode 100644
index 0000000000..c31bd2e29a
--- /dev/null
+++ b/packages/plugins/mcp/src/sdk/stdio-negotiation-test-server.ts
@@ -0,0 +1,24 @@
+// Stdio MCP fixture for stdio-negotiation.test.ts, spawned as a child process
+// with `bun run `. Serves both protocol eras by default;
+// `--legacy-reject` refuses the 2025 `initialize` opening so only a client
+// probing `server/discover` (spec 2026-07-28) can connect — the shape of an
+// SDK v2 server running with its legacy compatibility lane disabled.
+import { McpServer } from "@modelcontextprotocol/server";
+import { serveStdio } from "@modelcontextprotocol/server/stdio";
+import * as z from "zod/v4";
+
+serveStdio(
+ () => {
+ const server = new McpServer(
+ { name: "stdio-negotiation-fixture", version: "1.0.0" },
+ { capabilities: { tools: {} } },
+ );
+ server.registerTool(
+ "add",
+ { description: "Add two numbers", inputSchema: z.object({ a: z.number(), b: z.number() }) },
+ async ({ a, b }) => ({ content: [{ type: "text", text: String(a + b) }] }),
+ );
+ return server;
+ },
+ { legacy: process.argv.includes("--legacy-reject") ? "reject" : "serve" },
+);
diff --git a/packages/plugins/mcp/src/sdk/stdio-negotiation.test.ts b/packages/plugins/mcp/src/sdk/stdio-negotiation.test.ts
new file mode 100644
index 0000000000..d3400dc8a9
--- /dev/null
+++ b/packages/plugins/mcp/src/sdk/stdio-negotiation.test.ts
@@ -0,0 +1,64 @@
+import { describe, expect, it } from "@effect/vitest";
+import { Effect, Predicate } from "effect";
+import { fileURLToPath } from "node:url";
+
+import { createMcpConnector, type StdioConnectorInput } from "./connection";
+
+const fixture = fileURLToPath(new URL("./stdio-negotiation-test-server.ts", import.meta.url));
+
+const stdioInput = (
+ overrides: Partial> & {
+ readonly args: readonly string[];
+ },
+): StdioConnectorInput => ({
+ transport: "stdio",
+ command: "bun",
+ ...overrides,
+});
+
+const withConnection = (input: StdioConnectorInput) =>
+ Effect.acquireRelease(createMcpConnector(input).pipe(Effect.orDie), (connection) =>
+ Effect.promise(connection.close),
+ );
+
+describe("stdio version negotiation", () => {
+ it.effect("auto negotiation connects modern to a server with legacy support disabled", () =>
+ Effect.scoped(
+ Effect.gen(function* () {
+ const connection = yield* withConnection(
+ stdioInput({ args: ["run", fixture, "--legacy-reject"], versionNegotiation: "auto" }),
+ );
+
+ expect(connection.client.getProtocolEra()).toBe("modern");
+ const tools = yield* Effect.promise(() => connection.client.listTools());
+ expect(tools.tools.map(({ name }) => name)).toContain("add");
+ const result = yield* Effect.promise(() =>
+ connection.client.callTool({ name: "add", arguments: { a: 2, b: 2 } }),
+ );
+ expect(result.content).toEqual([{ type: "text", text: "4" }]);
+ }),
+ ),
+ );
+
+ it.effect("the default handshake surfaces a connection error on that same server", () =>
+ Effect.gen(function* () {
+ const error = yield* createMcpConnector(
+ stdioInput({ args: ["run", fixture, "--legacy-reject"] }),
+ ).pipe(Effect.flip);
+
+ expect(Predicate.isTagged(error, "McpConnectionError")).toBe(true);
+ }),
+ );
+
+ it.effect("absent config keeps the legacy handshake against a both-era server", () =>
+ Effect.scoped(
+ Effect.gen(function* () {
+ const connection = yield* withConnection(stdioInput({ args: ["run", fixture] }));
+
+ expect(connection.client.getProtocolEra()).toBe("legacy");
+ const tools = yield* Effect.promise(() => connection.client.listTools());
+ expect(tools.tools.map(({ name }) => name)).toContain("add");
+ }),
+ ),
+ );
+});
diff --git a/packages/plugins/mcp/src/sdk/testing-fixtures.test.ts b/packages/plugins/mcp/src/sdk/testing-fixtures.test.ts
index b804335399..02b911abc4 100644
--- a/packages/plugins/mcp/src/sdk/testing-fixtures.test.ts
+++ b/packages/plugins/mcp/src/sdk/testing-fixtures.test.ts
@@ -1,7 +1,6 @@
import { expect, layer } from "@effect/vitest";
import { Effect } from "effect";
-import { Client } from "@modelcontextprotocol/sdk/client/index.js";
-import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
+import { Client, StreamableHTTPClientTransport } from "@modelcontextprotocol/client";
import { OAuthTestServer } from "@executor-js/sdk/testing";
import { makeEchoMcpServer, serveMcpServerWithOAuth } from "../testing";
@@ -16,7 +15,10 @@ const createGreetingMcpServer = () =>
});
const makeClient = (endpoint: string, accessToken: string) => {
- const client = new Client({ name: "executor-test-client", version: "1.0.0" });
+ const client = new Client(
+ { name: "executor-test-client", version: "1.0.0" },
+ { versionNegotiation: { mode: "auto" } },
+ );
const transport = new StreamableHTTPClientTransport(new URL(endpoint), {
requestInit: {
headers: { authorization: `Bearer ${accessToken}` },
diff --git a/packages/plugins/mcp/src/sdk/types.ts b/packages/plugins/mcp/src/sdk/types.ts
index 838e81e0ac..a9ebcd427f 100644
--- a/packages/plugins/mcp/src/sdk/types.ts
+++ b/packages/plugins/mcp/src/sdk/types.ts
@@ -31,6 +31,20 @@ export type McpRemoteTransport = typeof McpRemoteTransport.Type;
export const McpTransport = Schema.Literals(["streamable-http", "sse", "stdio", "auto"]);
export type McpTransport = typeof McpTransport.Type;
+/** Protocol-version negotiation for a stdio server, mirroring the client
+ * SDK's `versionNegotiation` modes. `legacy` (the default when absent) opens
+ * with the 2025 `initialize` handshake; `auto` probes `server/discover`
+ * (spec 2026-07-28) and falls back to `initialize` on legacy servers.
+ *
+ * Deliberately opt-in, unlike remote streamable HTTP where `auto` is
+ * unconditional: the SDK's stdio probe runs on a short-lived sibling
+ * process, and a legacy server that never answers the probe stalls connect
+ * for the full probe timeout — the wrong default for spawn-per-call CLI
+ * servers, and exactly the case the SDK's own guidance says to keep on the
+ * legacy handshake unless the server is known-modern. */
+export const McpStdioVersionNegotiation = Schema.Literals(["legacy", "auto"]);
+export type McpStdioVersionNegotiation = typeof McpStdioVersionNegotiation.Type;
+
// ---------------------------------------------------------------------------
// Auth methods — the shared placements vocabulary (`@executor-js/sdk/http-auth`)
// plus MCP's own oauth variant. An integration declares zero or more methods,
@@ -208,6 +222,9 @@ export const McpStdioIntegrationConfig = Schema.Struct({
env: Schema.optional(StringMap),
/** Working directory */
cwd: Schema.optional(Schema.String),
+ /** Protocol negotiation at connect. Absent means `legacy` (see
+ * `McpStdioVersionNegotiation` for why that stays the default). */
+ versionNegotiation: Schema.optional(McpStdioVersionNegotiation),
/** Declared auth methods — a single `stdio_env` method naming the secret env
* vars, or `none`. A connection's `template` picks one by slug, exactly as
* for remote servers. Optional so pre-revamp stdio configs (which had no
diff --git a/packages/plugins/mcp/src/testing/server.ts b/packages/plugins/mcp/src/testing/server.ts
index 0b6edf0f04..dceac0db2a 100644
--- a/packages/plugins/mcp/src/testing/server.ts
+++ b/packages/plugins/mcp/src/testing/server.ts
@@ -1,7 +1,8 @@
-import { Context, Data, Effect, Layer, Ref, Scope } from "effect";
+import { Context, Data, Effect, Layer, Option, Ref, Schema, Scope } from "effect";
import * as http from "node:http";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
+import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
import { OAuthTestServer } from "@executor-js/sdk/testing";
import z from "zod";
@@ -57,6 +58,22 @@ const writeText = (response: http.ServerResponse, status: number, body: string)
response.end(body);
};
+const readRequestBody = (
+ request: http.IncomingMessage,
+): Effect.Effect =>
+ Effect.tryPromise({
+ try: () =>
+ new Promise((resolve, reject) => {
+ const chunks: Buffer[] = [];
+ request.on("data", (chunk: Buffer) => chunks.push(chunk));
+ request.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
+ request.on("error", reject);
+ }),
+ catch: (cause) => new McpTestServerError({ cause }),
+ });
+
+const decodeJsonBody = Schema.decodeUnknownOption(Schema.fromJsonString(Schema.Unknown));
+
const isMcpPath = (url: string, path: string): boolean => {
const parsed = new URL(url, "http://executor.test");
return parsed.pathname === path;
@@ -157,6 +174,24 @@ export const serveMcpServer = (factory: () => McpServer, options: McpTestServerO
return;
}
+ // Mirror the real v1 transport's stateful contract: only an
+ // `initialize` POST opens a session; any other sessionless POST
+ // (e.g. a v2 client's `server/discover` era probe) is rejected
+ // with 400 + a JSON-RPC error and no session is minted.
+ let parsedBody: unknown;
+ if (request.method === "POST") {
+ const body = yield* readRequestBody(request);
+ parsedBody = Option.getOrUndefined(decodeJsonBody(body));
+ if (!isInitializeRequest(parsedBody)) {
+ writeJson(response, 400, {
+ jsonrpc: "2.0",
+ error: { code: -32000, message: "Bad Request: Server not initialized" },
+ id: null,
+ });
+ return;
+ }
+ }
+
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: () => crypto.randomUUID(),
onsessioninitialized: (sid) => {
@@ -172,7 +207,7 @@ export const serveMcpServer = (factory: () => McpServer, options: McpTestServerO
catch: (cause) => new McpTestServerError({ cause }),
});
yield* Effect.tryPromise({
- try: () => transport.handleRequest(request, response),
+ try: () => transport.handleRequest(request, response, parsedBody),
catch: (cause) => new McpTestServerError({ cause }),
});
}).pipe(
diff --git a/packages/plugins/onepassword/CHANGELOG.md b/packages/plugins/onepassword/CHANGELOG.md
index 1e216ca206..4f23ed82ae 100644
--- a/packages/plugins/onepassword/CHANGELOG.md
+++ b/packages/plugins/onepassword/CHANGELOG.md
@@ -1,5 +1,14 @@
# @executor-js/plugin-onepassword
+## 1.6.0
+
+### Patch Changes
+
+- Updated dependencies [[`a2d1417`](https://github.com/UsefulSoftwareCo/executor/commit/a2d141758e478274813c8c24d354e1fd0f66af49), [`2bdbedf`](https://github.com/UsefulSoftwareCo/executor/commit/2bdbedf257f54d7c209e8c856c618174c10d6bb3)]:
+ - @executor-js/sdk@1.6.0
+ - @executor-js/react@1.4.63
+ - @executor-js/api@1.4.63
+
## 1.5.42
### Patch Changes
diff --git a/packages/plugins/onepassword/package.json b/packages/plugins/onepassword/package.json
index ee98c9ed65..d64929a9a4 100644
--- a/packages/plugins/onepassword/package.json
+++ b/packages/plugins/onepassword/package.json
@@ -1,6 +1,6 @@
{
"name": "@executor-js/plugin-onepassword",
- "version": "1.5.42",
+ "version": "1.6.0",
"homepage": "https://github.com/UsefulSoftwareCo/executor/tree/main/packages/plugins/onepassword",
"bugs": {
"url": "https://github.com/UsefulSoftwareCo/executor/issues"
diff --git a/packages/plugins/openapi/CHANGELOG.md b/packages/plugins/openapi/CHANGELOG.md
index b930d0bab7..4730a2ffbd 100644
--- a/packages/plugins/openapi/CHANGELOG.md
+++ b/packages/plugins/openapi/CHANGELOG.md
@@ -1,5 +1,21 @@
# @executor-js/plugin-openapi
+## 1.6.0
+
+### Patch Changes
+
+- [#1660](https://github.com/UsefulSoftwareCo/executor/pull/1660) [`c11bef2`](https://github.com/UsefulSoftwareCo/executor/commit/c11bef2cd049db7bbf51b15e18761b14acccb534) Thanks [@RhysSullivan](https://github.com/RhysSullivan)! - **Cloudflare ships as MCP-only, with code mode opted out**
+
+ The Cloudflare OpenAPI preset is gone from the default catalog; the MCP preset is the one Cloudflare entry. Its endpoint now pins `?codemode=false` because Cloudflare's MCP server otherwise hides the tool catalog behind a single code-execution tool, and executor already provides the code-execution surface. Hand-entered `mcp.cloudflare.com` URLs missing the opt-out get an inline warning in the add flow telling the user to append `?codemode=false`.
+
+- [#1669](https://github.com/UsefulSoftwareCo/executor/pull/1669) [`46cea2c`](https://github.com/UsefulSoftwareCo/executor/commit/46cea2cbb1f414ae58ac876819a51b11967909a6) Thanks [@RhysSullivan](https://github.com/RhysSullivan)! - Request Gmail's basic-settings scope alongside full mailbox access so Google integrations can create and manage Gmail filters without including domain-admin-only sharing settings.
+
+- Updated dependencies [[`a2d1417`](https://github.com/UsefulSoftwareCo/executor/commit/a2d141758e478274813c8c24d354e1fd0f66af49), [`2bdbedf`](https://github.com/UsefulSoftwareCo/executor/commit/2bdbedf257f54d7c209e8c856c618174c10d6bb3)]:
+ - @executor-js/sdk@1.6.0
+ - @executor-js/react@1.4.63
+ - @executor-js/api@1.4.63
+ - @executor-js/config@1.6.0
+
## 1.5.42
### Patch Changes
diff --git a/packages/plugins/openapi/package.json b/packages/plugins/openapi/package.json
index 887cb2ad3e..fc7a75aaeb 100644
--- a/packages/plugins/openapi/package.json
+++ b/packages/plugins/openapi/package.json
@@ -1,6 +1,6 @@
{
"name": "@executor-js/plugin-openapi",
- "version": "1.5.42",
+ "version": "1.6.0",
"homepage": "https://github.com/UsefulSoftwareCo/executor/tree/main/packages/plugins/openapi",
"bugs": {
"url": "https://github.com/UsefulSoftwareCo/executor/issues"
diff --git a/packages/plugins/provider-service-split/CHANGELOG.md b/packages/plugins/provider-service-split/CHANGELOG.md
index 4162c2a4d7..fa2dbf91d3 100644
--- a/packages/plugins/provider-service-split/CHANGELOG.md
+++ b/packages/plugins/provider-service-split/CHANGELOG.md
@@ -1,5 +1,13 @@
# @executor-js/plugin-provider-service-split
+## 0.0.14
+
+### Patch Changes
+
+- Updated dependencies [[`c11bef2`](https://github.com/UsefulSoftwareCo/executor/commit/c11bef2cd049db7bbf51b15e18761b14acccb534), [`46cea2c`](https://github.com/UsefulSoftwareCo/executor/commit/46cea2cbb1f414ae58ac876819a51b11967909a6), [`a2d1417`](https://github.com/UsefulSoftwareCo/executor/commit/a2d141758e478274813c8c24d354e1fd0f66af49)]:
+ - @executor-js/plugin-openapi@1.6.0
+ - @executor-js/sdk@1.6.0
+
## 0.0.13
### Patch Changes
diff --git a/packages/plugins/provider-service-split/package.json b/packages/plugins/provider-service-split/package.json
index 78943a9d73..fd603b0155 100644
--- a/packages/plugins/provider-service-split/package.json
+++ b/packages/plugins/provider-service-split/package.json
@@ -1,6 +1,6 @@
{
"name": "@executor-js/plugin-provider-service-split",
- "version": "0.0.13",
+ "version": "0.0.14",
"private": true,
"type": "module",
"exports": {
diff --git a/packages/plugins/toolkits/CHANGELOG.md b/packages/plugins/toolkits/CHANGELOG.md
index 7b2286ccc3..87371b3249 100644
--- a/packages/plugins/toolkits/CHANGELOG.md
+++ b/packages/plugins/toolkits/CHANGELOG.md
@@ -1,5 +1,14 @@
# @executor-js/plugin-toolkits
+## 1.5.35
+
+### Patch Changes
+
+- Updated dependencies [[`a2d1417`](https://github.com/UsefulSoftwareCo/executor/commit/a2d141758e478274813c8c24d354e1fd0f66af49), [`2bdbedf`](https://github.com/UsefulSoftwareCo/executor/commit/2bdbedf257f54d7c209e8c856c618174c10d6bb3)]:
+ - @executor-js/sdk@1.6.0
+ - @executor-js/react@1.4.63
+ - @executor-js/api@1.4.63
+
## 1.5.34
### Patch Changes
diff --git a/packages/plugins/toolkits/package.json b/packages/plugins/toolkits/package.json
index 92db77f814..702f94f5a1 100644
--- a/packages/plugins/toolkits/package.json
+++ b/packages/plugins/toolkits/package.json
@@ -1,6 +1,6 @@
{
"name": "@executor-js/plugin-toolkits",
- "version": "1.5.34",
+ "version": "1.5.35",
"homepage": "https://github.com/UsefulSoftwareCo/executor/tree/main/packages/plugins/toolkits",
"bugs": {
"url": "https://github.com/UsefulSoftwareCo/executor/issues"
diff --git a/packages/react/CHANGELOG.md b/packages/react/CHANGELOG.md
index f85d103023..2686df2249 100644
--- a/packages/react/CHANGELOG.md
+++ b/packages/react/CHANGELOG.md
@@ -1,5 +1,19 @@
# @executor-js/react
+## 1.4.63
+
+### Patch Changes
+
+- [#1703](https://github.com/UsefulSoftwareCo/executor/pull/1703) [`2bdbedf`](https://github.com/UsefulSoftwareCo/executor/commit/2bdbedf257f54d7c209e8c856c618174c10d6bb3) Thanks [@RhysSullivan](https://github.com/RhysSullivan)! - **A slow OAuth discovery no longer kills the connect with no popup and no error**
+
+ The transparent connect flows opened the sign-in window only after their setup round trips had answered: DCR after probe and dynamic registration, CIMD after minting the client, reconnect after starting the session. `window.open` needs transient user activation, which browsers expire a few seconds after the click, so once the API was slow enough the browser refused the window and the connect ended with nothing on screen but the button returning to "Connect". Every MCP integration takes that path.
+
+ The window is now claimed on the click itself and navigated when the authorization URL arrives, however long that takes, and it is closed again on the paths that end without signing in (failed probe, no registration endpoint, rejected registration, failed client mint) as well as on cancel and unmount. A window the browser does refuse is now reported instead of swallowed: the flows stop before their round trips, and the sign-in error renders above the dialog footer, where the automatic flows can actually show it, rather than inside a method tab panel they never render.
+
+- Updated dependencies [[`a2d1417`](https://github.com/UsefulSoftwareCo/executor/commit/a2d141758e478274813c8c24d354e1fd0f66af49)]:
+ - @executor-js/sdk@1.6.0
+ - @executor-js/api@1.4.63
+
## 1.4.62
### Patch Changes
diff --git a/packages/react/package.json b/packages/react/package.json
index 171d7032b0..b85800ce76 100644
--- a/packages/react/package.json
+++ b/packages/react/package.json
@@ -1,6 +1,6 @@
{
"name": "@executor-js/react",
- "version": "1.4.62",
+ "version": "1.4.63",
"private": true,
"type": "module",
"exports": {