Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
55a8b5e
fix(openapi): fetch analyticsdata Discovery from the service host (#1…
smrht Sep 15, 2026
d873caf
Advertise refresh-token grant in OAuth client metadata (#1974)
baggiiiie Sep 15, 2026
61f71c5
fix(mcp): shut down scoped executor on session eviction (#1917) (#1971)
Adityakk9031 Sep 15, 2026
513426c
fix(mcp): bound discovery connection teardown (#1878)
aryasaatvik Sep 15, 2026
905e097
fix(react): honor prefers-reduced-motion in the shared stylesheet (#1…
ra-co88 Sep 15, 2026
de13821
Keep artifact deletion optimistic across navigation (#1901)
baggiiiie Sep 15, 2026
d64639b
Surface unreachable OpenAPI upstreams as network errors (#1960)
baggiiiie Sep 15, 2026
89b0f8d
fix(mcp): report completed connected tool (#1964)
dhairyashiil Sep 15, 2026
b961092
Return tool annotations from describe.tool (#1980)
daviesayo Sep 15, 2026
929b233
fix(oauth): request offline_access for Vercel MCP (#1951)
nidhi-singh02 Sep 15, 2026
5ecb881
fix(cloud): require CSRF state in the WorkOS login callback (#1886)
ra-co88 Sep 15, 2026
65d939e
Fix HubSpot optional scopes for workspace OAuth (#1898)
LloydVickeryASI Sep 15, 2026
1f67d83
Support POST health checks for HTTP RPC APIs (#1952)
Karavil Sep 15, 2026
0453a31
fix(e2e): honor the 503 restart envelope in the cap-eviction helper (…
ra-co88 Sep 15, 2026
bfc46e5
fix: use public origin for browser approval URLs (#1967)
utpalsinghdev Sep 15, 2026
3d9ac6a
fix(oauth): complete browser callbacks before tool sync (#1880)
aryasaatvik Sep 15, 2026
3eea03b
Fix hyphenated tool paths in artifacts (#1884)
GijungKim Sep 15, 2026
5c1d1b4
Bridge generated-UI target=_blank links through the host openLink cap…
baggiiiie Sep 15, 2026
0e9d800
fix(mcp): pause active timeout during elicitation (#1956)
mikemikimike Sep 15, 2026
3c263d7
Add search and invoke MCP mode (#1942)
RhysSullivan Sep 15, 2026
b5d53cb
Return artifact source from show-artifact (#1943)
baggiiiie Sep 15, 2026
40b2f2e
Carry an approval's persistence choice through elicitation (#1976)
SunkenInTime Sep 15, 2026
387d0b3
fix(host-cloudflare): route toolkit MCP endpoints (#1926)
donmasakayan Sep 15, 2026
ffa6dd2
Add account discovery and guides to search and invoke (#1997)
RhysSullivan Sep 15, 2026
7422219
Billing: update the payment card (#1992)
RhysSullivan Sep 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/artifact-delete-navigation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"executor": patch
---

Prevent deleted artifacts from briefly reappearing after returning to the artifact gallery.
5 changes: 5 additions & 0 deletions .changeset/artifact-external-links.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"executor": patch
---

**Fix: links in generated artifacts (`<a target="_blank">`) did nothing when clicked.** The sandbox iframe deliberately has no `allow-popups`, so the browser blocked the new browsing context and the click went nowhere. A trusted user click is now relayed across the frame boundary to the host's `openLink` capability — guarded by a per-render nonce so generated code cannot forge or observe it — and the host opens only `http`/`https` URLs.
5 changes: 5 additions & 0 deletions .changeset/artifact-hyphenated-tool-paths.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"executor": patch
---

Render artifacts that call integrations or tools with hyphenated slugs.
5 changes: 5 additions & 0 deletions .changeset/artifact-source-text.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"executor": patch
---

**Fix: `show-artifact` now returns the saved component source to MCP clients that cannot render Apps.** Agents can read the current source and make targeted edits instead of receiving only a link to the artifact.
10 changes: 10 additions & 0 deletions .changeset/cimd-refresh-token.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
"@executor-js/api": patch
---

Advertise refresh-token support in OAuth client ID metadata documents.

OAuth providers may reject the `offline_access` scope when the client's
metadata declares only the authorization-code grant. Hosted and local client
metadata now declare both `authorization_code` and `refresh_token`, matching
Executor's dynamic client registration behavior.
5 changes: 5 additions & 0 deletions .changeset/cli-approval-public-origin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"executor": patch
---

Pin CLI browser approval links to `EXECUTOR_WEB_BASE_URL` so a TLS reverse proxy no longer returns an unreachable `http://` URL.
5 changes: 5 additions & 0 deletions .changeset/completed-mcp-tool-name.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@executor-js/execution": patch
---

Completed MCP execute results now include `toolName` when a script successfully uses exactly one connected tool. Executions that use distinct tools remain unlabeled, and internal call provenance is not exposed in the MCP response.
14 changes: 14 additions & 0 deletions .changeset/computer-use-remembered-approvals.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
"@executor-js/sdk": patch
"@executor-js/execution": patch
"@executor-js/plugin-mcp": patch
"@executor-js/api": patch
"@executor-js/react": patch
"@executor-js/host-mcp": patch
"@executor-js/cloudflare": patch
"executor": patch
---

Carry an approval's persistence choice through elicitation, so Codex Computer Use stops asking to use the same app on every call.

Computer Use offers `persist: ["session", "always"]` in the prompt's terms and remembers the app only when the answer names one. Executor dropped the offer on the way in (the terms projection kept strings only) and the choice on the way out (every adapter rebuilt the reply from `action` and `content`), so each accept was one-time. `ElicitationResponse` now has `meta.persist`; the MCP plugin, the app-server bridge, and the MCP host pass it through; the model-mode `resume` tool and the browser approval page let the approver pick from the offered scopes. Nothing is chosen automatically: a bare accept still approves once.
5 changes: 5 additions & 0 deletions .changeset/describe-tool-annotations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"executor": patch
---

Return a tool's declared annotations from `tools.schema` and `describe.tool`. Code inside `execute` can now read `requiresApproval`, `approvalDescription` and `mayElicit` without parsing the tool's prose description.
9 changes: 9 additions & 0 deletions .changeset/great-hoops-repeat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@executor-js/plugin-openapi": patch
---

Fetch Google Analytics Data (`analyticsdata`) Discovery from the service's own
host. The central directory does not list the GA4 Data API, so the canonical
`https://www.googleapis.com/discovery/v1/apis/analyticsdata/v1beta/rest` answers
404 and the source fails to import. Same treatment `forms`, `keep` and
`photospicker` already get.
5 changes: 5 additions & 0 deletions .changeset/hubspot-workspace-optional-scopes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@executor-js/sdk": patch
---

Send HubSpot optional permissions in `optional_scope` for workspace OAuth clients so accounts can connect without optional product features.
5 changes: 5 additions & 0 deletions .changeset/mcp-elicitation-active-deadline.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@executor-js/plugin-mcp": patch
---

Exclude time spent waiting for elicitation from the MCP tool invocation deadline.
7 changes: 7 additions & 0 deletions .changeset/mcp-passthrough-mode.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@executor-js/sdk": minor
"@executor-js/execution": minor
"executor": minor
---

Add a search and invoke MCP mode (`?mode=passthrough`, `executor mcp --mode passthrough`). Search returns bounded pages of matching tool IDs and input schemas. Invoke validates arguments and runs the selected tool, with native client approval and workspace blocks enforced. The MCP catalog stays at two tools regardless of integration count.
7 changes: 7 additions & 0 deletions .changeset/mcp-session-executor-leak.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@executor-js/host-mcp": patch
"@executor-js/api": patch
"executor": patch
---

Shut down scoped executors and tool subprocess resources upon MCP session eviction and disposal in the in-process session store.
6 changes: 6 additions & 0 deletions .changeset/openapi-transport-unreachable.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"executor": patch
"@executor-js/plugin-openapi": patch
---

OpenAPI tools that cannot reach the upstream server now return an `upstream_unreachable` error instead of `Internal tool error [id]`. The message names the integration and origin that could not be reached, `details` carries the sanitized `host` and errno-style `code` (`ECONNREFUSED`, `ENOTFOUND`, …), and the failure is logged with the same classification.
6 changes: 6 additions & 0 deletions .changeset/post-health-checks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@executor-js/plugin-openapi": patch
"@executor-js/react": patch
---

Support POST health checks for APIs that expose reads through HTTP RPC. Warn that POST can change data, allow validated JSON request bodies, and display the reason when a configured probe cannot run.
10 changes: 10 additions & 0 deletions .changeset/reduced-motion.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
"@executor-js/react": patch
---

fix: honor prefers-reduced-motion in the shared stylesheet

Adds a `prefers-reduced-motion: reduce` block to the global stylesheet that
caps transition/animation durations to 0.01ms and disables smooth scrolling,
so motion-sensitive users get a stable UI. The loading spinner renders
statically under reduced motion (its meaning is preserved via `role="status"`).
17 changes: 17 additions & 0 deletions .changeset/tidy-login-state.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
"@executor-js/cloud": patch
---

fix: make login CSRF state mandatory in the WorkOS callback

The callback previously skipped its CSRF check whenever the redirect carried
no `state` value ("some WorkOS-initiated redirects don't include one"). That
bypass let an attacker complete their own OAuth round-trip and redirect a
victim's browser through the callback with the attacker's `code` and no
`state`, silently signing the victim into the attacker's account (login CSRF).

The check is now unconditional: a callback without a state matching the
`wos-login-state` cookie set on `/login` is rejected with 400. This is a
breaking change for any client relying on the undocumented no-state entry
path; server-initiated flows that cannot carry state must be redesigned with
a signed nonce instead of re-adding the bypass.
5 changes: 5 additions & 0 deletions .changeset/vercel-mcp-refresh-token.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@executor-js/sdk": patch
---

Keep Vercel MCP connections renewable by requesting the provider's `offline_access` lifecycle scope during registration and authorization.
33 changes: 31 additions & 2 deletions apps/cli/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1363,6 +1363,7 @@ const mcpUrlForActiveLocalServer = (input: {
readonly elicitationMode: "browser" | "model";
readonly artifacts: boolean;
readonly searchTools: boolean;
readonly toolMode: "codemode" | "passthrough";
}): URL => {
const url = new URL("/mcp", input.connection.origin);
if (input.elicitationMode === "browser") {
Expand All @@ -1378,6 +1379,10 @@ const mcpUrlForActiveLocalServer = (input: {
if (input.searchTools) {
url.searchParams.set("search_tools", "true");
}
// Passthrough is the non-default surface; only it is spelled out.
if (input.toolMode === "passthrough") {
url.searchParams.set("mode", "passthrough");
}
return url;
};

Expand All @@ -1394,6 +1399,7 @@ const runMcpHttpBridge = async (input: {
readonly elicitationMode: "browser" | "model";
readonly artifacts: boolean;
readonly searchTools: boolean;
readonly toolMode: "codemode" | "passthrough";
}): Promise<void> => {
const stdio = new StdioServerTransport();
const authorization = getExecutorServerAuthorizationHeader(input.manifest.connection);
Expand All @@ -1403,6 +1409,7 @@ const runMcpHttpBridge = async (input: {
elicitationMode: input.elicitationMode,
artifacts: input.artifacts,
searchTools: input.searchTools,
toolMode: input.toolMode,
}),
authorization ? { requestInit: { headers: { Authorization: authorization } } } : undefined,
);
Expand Down Expand Up @@ -1482,6 +1489,7 @@ const runStdioMcpSession = (input: {
readonly elicitationMode: "browser" | "model";
readonly artifacts: boolean;
readonly searchTools: boolean;
readonly toolMode: "codemode" | "passthrough";
}) =>
Effect.gen(function* () {
// `executor mcp` never owns the local database. If a local server is already
Expand All @@ -1499,6 +1507,7 @@ const runStdioMcpSession = (input: {
elicitationMode: input.elicitationMode,
artifacts: input.artifacts,
searchTools: input.searchTools,
toolMode: input.toolMode,
}),
);
return;
Expand Down Expand Up @@ -1526,6 +1535,7 @@ const runStdioMcpSession = (input: {
elicitationMode: input.elicitationMode,
artifacts: input.artifacts,
searchTools: input.searchTools,
toolMode: input.toolMode,
}),
);
});
Expand Down Expand Up @@ -2898,11 +2908,30 @@ const mcpCommand = Command.make(
"Serve one search_<integration> tool per connected integration. Off by default; each routes through the same flow as tools.search inside execute.",
),
),
toolMode: Options.choice("mode", ["codemode", "passthrough"] as const)
.pipe(Options.withDefault("codemode"))
.pipe(
Options.withDescription(
"codemode (default) serves the execute tool; passthrough serves search and invoke, with input schemas in search results and client approval for invoke.",
),
),
},
({ scope, elicitationMode, noArtifacts, searchTools }) =>
({ scope, elicitationMode, noArtifacts, searchTools, toolMode }) =>
Effect.gen(function* () {
applyScope(scope);
yield* runStdioMcpSession({ elicitationMode, artifacts: !noArtifacts, searchTools });
if (toolMode === "passthrough" && searchTools) {
return yield* Effect.fail(
new Error(
"--search-tools is a codemode option; passthrough already provides search. Drop --search-tools or --mode passthrough.",
),
);
}
yield* runStdioMcpSession({
elicitationMode,
artifacts: !noArtifacts,
searchTools,
toolMode,
});
}),
).pipe(Command.withDescription("Start an MCP server over stdio"));

Expand Down
1 change: 1 addition & 0 deletions apps/cloud/src/auth/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ const McpSessionExecutionParams = {
const ResumeMcpExecutionBody = Schema.Struct({
action: Schema.Literals(["accept", "decline", "cancel"]),
content: Schema.optional(Schema.Unknown),
persist: Schema.optional(Schema.String),
});

const McpPausedExecutionResponse = Schema.Struct({
Expand Down
27 changes: 15 additions & 12 deletions apps/cloud/src/auth/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,17 +189,17 @@ export const CloudAuthPublicHandlers = HttpApiBuilder.group(
const workos = yield* WorkOSClient;
const users = yield* UserStoreService;
const cookieState = request.cookies[STATE_COOKIE] ?? null;
// CSRF check is only enforced when the redirect carries a state
// value — some WorkOS-initiated redirects don't include one.
// When state is present, it MUST match the cookie we set on
// /login.
if (query.state !== undefined) {
if (!cookieState || !timingSafeEqual(cookieState, query.state)) {
return deleteResponseCookie(
HttpServerResponse.text("Invalid login state", { status: 400 }),
STATE_COOKIE,
);
}
// CSRF is unconditional: every callback must carry a state that
// matches the cookie set on /login. There is no legitimate
// no-state entry path — omitting state previously allowed an
// attacker to complete their own OAuth round-trip and redirect a
// victim's browser through this callback, signing the victim into
// the attacker's account (login CSRF).
if (!cookieState || !timingSafeEqual(cookieState, query.state ?? "")) {
return deleteResponseCookie(
HttpServerResponse.text("Invalid login state", { status: 400 }),
STATE_COOKIE,
);
}

const result = yield* workos.authenticateWithCode(query.code);
Expand All @@ -210,7 +210,7 @@ export const CloudAuthPublicHandlers = HttpApiBuilder.group(
let sealedSession = result.sealedSession;

// Resume where the SSR gate interrupted them. The state passed the
// CSRF check above whenever it's present, but it's still a
// CSRF check above, but it's still a
// round-tripped value, so the returnTo inside it is re-validated like
// any other untrusted path.
const returnTo = safeReturnTo(decodeLoginState(query.state)?.returnTo) ?? "/";
Expand Down Expand Up @@ -704,6 +704,9 @@ export const CloudSessionAuthHandlers = HttpApiBuilder.group(
{
action: payload.action,
content: payload.content as Record<string, unknown> | undefined,
...(payload.action === "accept" && payload.persist !== undefined
? { meta: { persist: payload.persist } }
: {}),
},
),
);
Expand Down
Loading
Loading