Artifacts required a ?artifacts=true opt-in per MCP connection. Flip the default: a plain endpoint URL now gets the full artifact surface (tools, shell resource, skills), and a connection that does not want it says ?artifacts=false — or --no-artifacts on the stdio CLI. ?artifacts=true stays accepted as the explicit default. - readArtifactsEnabled: absent flag now reads true; any explicit value that is not a documented truthy form reads false (a user touching the flag was reaching for off). - tool-server config default and both Cloudflare DO restore defaults flip to true; sessions persisted without a value restore to enabled. - Install card: toggle defaults on, only the opt-out reaches the URL and the stdio command. - e2e: the comparative scenario now proves the opt-out (clean URL has the surface, artifacts=false loses it); artifact scenarios use plain sessions.
Unified · Split
| @@ -0,0 +1,5 @@ | |||
| 1 | --- | ||
| 2 | "executor": patch | ||
| 3 | --- | ||
| 4 | |||
| 5 | **Artifacts are now on by default for MCP connections.** A plain endpoint URL serves the full artifact surface — the artifact tools, the app shell resource, and the artifact skills. Connections that don't want it opt out with `?artifacts=false` (or `--no-artifacts` on the stdio CLI); `?artifacts=true` remains accepted as the explicit default. Previously the surface required a `?artifacts=true` opt-in. | ||
| @@ -1346,9 +1346,10 @@ const mcpUrlForActiveLocalServer = (input: { · 1345 unmodified lines | |||
| 1346 | if (input.elicitationMode === "browser") { | 1346 | if (input.elicitationMode === "browser") { |
| 1347 | url.searchParams.set("elicitation_mode", "browser"); | 1347 | url.searchParams.set("elicitation_mode", "browser"); |
| 1348 | } | 1348 | } |
| 1349 | // Only the opt-in is spelled out; the default endpoint stays clean. | 1349 | // Artifacts are on by default; only the opt-out is spelled out, so the |
| 1350 | if (input.artifacts) { | 1350 | // default endpoint stays clean. |
| 1351 | url.searchParams.set("artifacts", "true"); | 1351 | if (!input.artifacts) { |
| 1352 | url.searchParams.set("artifacts", "false"); | ||
| 1352 | } | 1353 | } |
| 1353 | return url; | 1354 | return url; |
| 1354 | }; | 1355 | }; |
| @@ -2802,18 +2803,18 @@ const mcpCommand = Command.make( · 1447 unmodified lines | |||
| 2802 | "Choose the stdio approval flow: browser approval or a CLI resume tool exposed to the model.", | 2803 | "Choose the stdio approval flow: browser approval or a CLI resume tool exposed to the model.", |
| 2803 | ), | 2804 | ), |
| 2804 | ), | 2805 | ), |
| 2805 | artifacts: Options.boolean("artifacts") | 2806 | noArtifacts: Options.boolean("no-artifacts") |
| 2806 | .pipe(Options.withDefault(false)) | 2807 | .pipe(Options.withDefault(false)) |
| 2807 | .pipe( | 2808 | .pipe( |
| 2808 | Options.withDescription( | 2809 | Options.withDescription( |
| 2809 | "Serve the artifact surface on this connection: the artifact tools, the app shell resource, and the artifact skills. Withheld by default.", | 2810 | "Withhold the artifact surface from this connection: the artifact tools, the app shell resource, and the artifact skills. Served by default.", |
| 2810 | ), | 2811 | ), |
| 2811 | ), | 2812 | ), |
| 2812 | }, | 2813 | }, |
| 2813 | ({ scope, elicitationMode, artifacts }) => | 2814 | ({ scope, elicitationMode, noArtifacts }) => |
| 2814 | Effect.gen(function* () { | 2815 | Effect.gen(function* () { |
| 2815 | applyScope(scope); | 2816 | applyScope(scope); |
| 2816 | yield* runStdioMcpSession({ elicitationMode, artifacts }); | 2817 | yield* runStdioMcpSession({ elicitationMode, artifacts: !noArtifacts }); |
| 2817 | }), | 2818 | }), |
| 2818 | ).pipe(Command.withDescription("Start an MCP server over stdio")); | 2819 | ).pipe(Command.withDescription("Start an MCP server over stdio")); |
| 2819 | 2820 | ||
| @@ -276,10 +276,10 @@ export class McpSessionDOSqlite extends McpAgentSessionDOBase<Env, CloudSessionD · 275 unmodified lines | |||
| 276 | description, | 276 | description, |
| 277 | artifacts: executor.artifacts, | 277 | artifacts: executor.artifacts, |
| 278 | connections: executor.connections, | 278 | connections: executor.connections, |
| 279 | // Artifacts are opt-in per connection. Sessions persisted before the | 279 | // Artifacts are on by default, opt-out per connection. A session |
| 280 | // flag existed carry no value; absent now means off, same as a fresh | 280 | // persisted without a value restores to the default, same as a fresh |
| 281 | // connection that did not ask for `?artifacts=true`. | 281 | // connection whose URL says nothing about `?artifacts=`. |
| 282 | artifactsEnabled: sessionMeta.artifactsEnabled ?? false, | 282 | artifactsEnabled: sessionMeta.artifactsEnabled ?? true, |
| 283 | // Cold restores rebuild this server with no `initialize` to replay, so | 283 | // Cold restores rebuild this server with no `initialize` to replay, so |
| 284 | // the negotiated apps support comes back from storage instead. | 284 | // the negotiated apps support comes back from storage instead. |
| 285 | restoredAppsEnabled: sessionMeta.appsEnabled ?? false, | 285 | restoredAppsEnabled: sessionMeta.appsEnabled ?? false, |
| @@ -145,10 +145,10 @@ export class McpSessionDO extends McpAgentSessionDOBase<CloudflareEnv, CfSession · 144 unmodified lines | |||
| 145 | engine, | 145 | engine, |
| 146 | artifacts: executor.artifacts, | 146 | artifacts: executor.artifacts, |
| 147 | connections: executor.connections, | 147 | connections: executor.connections, |
| 148 | // Artifacts are opt-in per connection. Sessions persisted before the | 148 | // Artifacts are on by default, opt-out per connection. A session |
| 149 | // flag existed carry no value; absent now means off, same as a fresh | 149 | // persisted without a value restores to the default, same as a fresh |
| 150 | // connection that did not ask for `?artifacts=true`. | 150 | // connection whose URL says nothing about `?artifacts=`. |
| 151 | artifactsEnabled: sessionMeta.artifactsEnabled ?? false, | 151 | artifactsEnabled: sessionMeta.artifactsEnabled ?? true, |
| 152 | // Cold restores rebuild this server with no `initialize` to replay, so | 152 | // Cold restores rebuild this server with no `initialize` to replay, so |
| 153 | // the negotiated apps support comes back from storage instead. | 153 | // the negotiated apps support comes back from storage instead. |
| 154 | restoredAppsEnabled: sessionMeta.appsEnabled ?? false, | 154 | restoredAppsEnabled: sessionMeta.appsEnabled ?? false, |
| @@ -92,8 +92,8 @@ scenario( · 91 unmodified lines | |||
| 92 | const mcp = yield* Mcp; | 92 | const mcp = yield* Mcp; |
| 93 | const identity = yield* target.newIdentity(); | 93 | const identity = yield* target.newIdentity(); |
| 94 | const bearer = yield* mcp.mintBearer(emailOf(identity)); | 94 | const bearer = yield* mcp.mintBearer(emailOf(identity)); |
| 95 | // Artifacts opt-in is what registers the shell resource on the session. | 95 | // Artifacts are on by default, which registers the shell resource. |
| 96 | const mcpUrl = `${target.mcpUrl}?artifacts=true`; | 96 | const mcpUrl = target.mcpUrl; |
| 97 | 97 | ||
| 98 | const sessionId = yield* Effect.promise(() => openSession(mcpUrl, bearer)); | 98 | const sessionId = yield* Effect.promise(() => openSession(mcpUrl, bearer)); |
| 99 | 99 | ||
| @@ -197,9 +197,9 @@ scenario( · 196 unmodified lines | |||
| 197 | 197 | ||
| 198 | const identity = yield* target.newIdentity(); | 198 | const identity = yield* target.newIdentity(); |
| 199 | const client = yield* apiClient(api, identity); | 199 | const client = yield* apiClient(api, identity); |
| 200 | // Artifacts are opt-in per MCP connection, so this session asks for the | 200 | // Artifacts are on by default; the plain session carries the surface |
| 201 | // surface it is here to exercise (`?artifacts=true`). | 201 | // this scenario exercises. |
| 202 | const session = mcp.session(identity, { artifacts: true }); | 202 | const session = mcp.session(identity); |
| 203 | 203 | ||
| 204 | const suffix = uniqueSuffix(); | 204 | const suffix = uniqueSuffix(); |
| 205 | const title = `Loading Surface ${suffix}`; | 205 | const title = `Loading Surface ${suffix}`; |
| @@ -145,9 +145,9 @@ scenario( · 144 unmodified lines | |||
| 145 | const browser = yield* Browser; | 145 | const browser = yield* Browser; |
| 146 | 146 | ||
| 147 | const identity = yield* target.newIdentity(); | 147 | const identity = yield* target.newIdentity(); |
| 148 | // Artifacts are opt-in per MCP connection, so this session asks for the | 148 | // Artifacts are on by default; the plain session carries the surface |
| 149 | // surface it is here to exercise (`?artifacts=true`). | 149 | // this scenario exercises. |
| 150 | const session = mcp.session(identity, { artifacts: true }); | 150 | const session = mcp.session(identity); |
| 151 | const suffix = uniqueSuffix(); | 151 | const suffix = uniqueSuffix(); |
| 152 | 152 | ||
| 153 | const seeded = [ | 153 | const seeded = [ |
| @@ -166,9 +166,9 @@ scenario( · 165 unmodified lines | |||
| 166 | 166 | ||
| 167 | const identity = yield* target.newIdentity(); | 167 | const identity = yield* target.newIdentity(); |
| 168 | const client = yield* apiClient(api, identity); | 168 | const client = yield* apiClient(api, identity); |
| 169 | // Artifacts are opt-in per MCP connection, so this session asks for the | 169 | // Artifacts are on by default; the plain session carries the surface |
| 170 | // surface it is here to exercise (`?artifacts=true`). | 170 | // this scenario exercises. |
| 171 | const session = mcp.session(identity, { artifacts: true }); | 171 | const session = mcp.session(identity); |
| 172 | 172 | ||
| 173 | const suffix = uniqueSuffix(); | 173 | const suffix = uniqueSuffix(); |
| 174 | const title = `Release Readiness ${suffix}`; | 174 | const title = `Release Readiness ${suffix}`; |
| @@ -556,9 +556,9 @@ scenario( · 381 unmodified lines | |||
| 556 | 556 | ||
| 557 | const identity = yield* target.newIdentity(); | 557 | const identity = yield* target.newIdentity(); |
| 558 | const client = yield* apiClient(api, identity); | 558 | const client = yield* apiClient(api, identity); |
| 559 | // Artifacts are opt-in per MCP connection, so this session asks for the | 559 | // Artifacts are on by default; the plain session carries the surface |
| 560 | // surface it is here to exercise (`?artifacts=true`). | 560 | // this scenario exercises. |
| 561 | const session = mcp.session(identity, { artifacts: true }); | 561 | const session = mcp.session(identity); |
| 562 | 562 | ||
| 563 | const suffix = uniqueSuffix(); | 563 | const suffix = uniqueSuffix(); |
| 564 | const originalTitle = `Draft Dashboard ${suffix}`; | 564 | const originalTitle = `Draft Dashboard ${suffix}`; |
| @@ -677,9 +677,9 @@ scenario( · 112 unmodified lines | |||
| 677 | 677 | ||
| 678 | const identity = yield* target.newIdentity(); | 678 | const identity = yield* target.newIdentity(); |
| 679 | const client = yield* apiClient(api, identity); | 679 | const client = yield* apiClient(api, identity); |
| 680 | // Artifacts are opt-in per MCP connection, so this session asks for the | 680 | // Artifacts are on by default; the plain session carries the surface |
| 681 | // surface it is here to exercise (`?artifacts=true`). | 681 | // this scenario exercises. |
| 682 | const session = mcp.session(identity, { artifacts: true }); | 682 | const session = mcp.session(identity); |
| 683 | 683 | ||
| 684 | const suffix = uniqueSuffix(); | 684 | const suffix = uniqueSuffix(); |
| 685 | const title = `Iterated Dashboard ${suffix}`; | 685 | const title = `Iterated Dashboard ${suffix}`; |
| @@ -773,14 +773,13 @@ function App() { · 87 unmodified lines | |||
| 773 | }), | 773 | }), |
| 774 | ); | 774 | ); |
| 775 | 775 | ||
| 776 | // The opt-in. Artifacts are off by default: a plain endpoint URL gets no | 776 | // The opt-out. Artifacts are on by default: a plain endpoint URL gets the full |
| 777 | // artifact surface at all — no artifact tools, and no artifact entries in the | 777 | // artifact surface. A connection that says `?artifacts=false` gets none of it — |
| 778 | // `skills` inventory to point a model at tools it does not have. A user who | 778 | // no artifact tools, and no artifact entries in the `skills` inventory to point |
| 779 | // wants generated UI says so on the URL (`?artifacts=true`) and that connection | 779 | // a model at tools it does not have. The proof is comparative: two sessions, |
| 780 | // gets the whole surface. The proof is comparative: two sessions, same | 780 | // same identity, same server, differing only in that query. |
| 781 | // identity, same server, differing only in that query. | ||
| 782 | scenario( | 781 | scenario( |
| 783 | "Artifacts · only a session connected with artifacts=true sees the artifact surface", | 782 | "Artifacts · a session connected with artifacts=false loses the artifact surface", |
| 784 | { timeout: 120_000 }, | 783 | { timeout: 120_000 }, |
| 785 | Effect.gen(function* () { | 784 | Effect.gen(function* () { |
| 786 | const target = yield* Target; | 785 | const target = yield* Target; |
| @@ -788,7 +787,7 @@ scenario( · 1 unmodified line | |||
| 788 | 787 | ||
| 789 | const identity = yield* target.newIdentity(); | 788 | const identity = yield* target.newIdentity(); |
| 790 | const defaultSession = mcp.session(identity); | 789 | const defaultSession = mcp.session(identity); |
| 791 | const optedIn = mcp.session(identity, { artifacts: true }); | 790 | const optedOut = mcp.session(identity, { artifacts: false }); |
| 792 | 791 | ||
| 793 | const ARTIFACT_TOOLS = [ | 792 | const ARTIFACT_TOOLS = [ |
| 794 | "create-artifact", | 793 | "create-artifact", |
| @@ -798,32 +797,32 @@ scenario( · 3 unmodified lines | |||
| 798 | "execute-action-resume", | 797 | "execute-action-resume", |
| 799 | ] as const; | 798 | ] as const; |
| 800 | 799 | ||
| 801 | const defaultTools = yield* defaultSession.listTools(); | 800 | const optedOutTools = yield* optedOut.listTools(); |
| 802 | for (const tool of ARTIFACT_TOOLS) { | 801 | for (const tool of ARTIFACT_TOOLS) { |
| 803 | expect(defaultTools, `${tool} is withheld from a default session`).not.toContain(tool); | 802 | expect(optedOutTools, `${tool} is withheld from an opted-out session`).not.toContain(tool); |
| 804 | } | 803 | } |
| 805 | // Only artifacts are withheld — the connection is otherwise a normal one. | 804 | // Only artifacts are withheld — the connection is otherwise a normal one. |
| 806 | expect(defaultTools, "execute still works on a default session").toContain("execute"); | 805 | expect(optedOutTools, "execute still works on an opted-out session").toContain("execute"); |
| 807 | expect(defaultTools, "skills still works on a default session").toContain("skills"); | 806 | expect(optedOutTools, "skills still works on an opted-out session").toContain("skills"); |
| 808 | 807 | ||
| 809 | const defaultSkills = yield* defaultSession.call("skills", {}); | 808 | const optedOutSkills = yield* optedOut.call("skills", {}); |
| 810 | expect(defaultSkills.ok, `the skills index came back: ${defaultSkills.text}`).toBe(true); | 809 | expect(optedOutSkills.ok, `the skills index came back: ${optedOutSkills.text}`).toBe(true); |
| 811 | expect(defaultSkills.text, "the index still offers the execute skill").toContain("`execute`"); | 810 | expect(optedOutSkills.text, "the index still offers the execute skill").toContain("`execute`"); |
| 812 | expect( | 811 | expect( |
| 813 | defaultSkills.text, | 812 | optedOutSkills.text, |
| 814 | "the index does not advertise a how-to for a tool this session lacks", | 813 | "the index does not advertise a how-to for a tool this session lacks", |
| 815 | ).not.toContain("`create-artifact`"); | 814 | ).not.toContain("`create-artifact`"); |
| 816 | expect(defaultSkills.text, "nor the artifact style guide").not.toContain("`artifact-style`"); | 815 | expect(optedOutSkills.text, "nor the artifact style guide").not.toContain("`artifact-style`"); |
| 817 | 816 | ||
| 818 | // The control: the same identity against the same server, with the query, | 817 | // The control: the same identity against the same server, with a clean |
| 819 | // gets everything. Without this the assertions above would also pass on a | 818 | // URL, gets everything by default. Without this the assertions above would |
| 820 | // server that had simply lost artifacts. | 819 | // also pass on a server that had simply lost artifacts. |
| 821 | const optedInTools = yield* optedIn.listTools(); | 820 | const defaultTools = yield* defaultSession.listTools(); |
| 822 | for (const tool of ["create-artifact", "list-artifacts", "show-artifact"] as const) { | 821 | for (const tool of ["create-artifact", "list-artifacts", "show-artifact"] as const) { |
| 823 | expect(optedInTools, `${tool} is present once the session opts in`).toContain(tool); | 822 | expect(defaultTools, `${tool} is present on a default session`).toContain(tool); |
| 824 | } | 823 | } |
| 825 | const optedInSkills = yield* optedIn.call("skills", {}); | 824 | const defaultSkills = yield* defaultSession.call("skills", {}); |
| 826 | expect(optedInSkills.text, "the opted-in index offers the create-artifact skill").toContain( | 825 | expect(defaultSkills.text, "the default index offers the create-artifact skill").toContain( |
| 827 | "`create-artifact`", | 826 | "`create-artifact`", |
| 828 | ); | 827 | ); |
| 829 | }), | 828 | }), |
| @@ -164,8 +164,9 @@ export interface McpSurface { · 163 unmodified lines | |||
| 164 | identity: Identity, | 164 | identity: Identity, |
| 165 | options?: { | 165 | options?: { |
| 166 | readonly elicitationMode?: McpElicitationMode; | 166 | readonly elicitationMode?: McpElicitationMode; |
| 167 | /** Opt this session in to artifacts (`?artifacts=true`). Omitted means | 167 | /** Set `false` to opt this session out of artifacts |
| 168 | * the product default: no artifact surface at all. */ | 168 | * (`?artifacts=false`). Omitted means the product default: the full |
| 169 | * artifact surface. */ | ||
| 169 | readonly artifacts?: boolean; | 170 | readonly artifacts?: boolean; |
| 170 | readonly url?: string; | 171 | readonly url?: string; |
| 171 | }, | 172 | }, |
| @@ -302,12 +303,12 @@ export const makeMcpSurface = (target: Target, runDir?: string): McpSurface => ( · 130 unmodified lines | |||
| 302 | const serverName = `${target.name}-${randomUUID().slice(0, 8)}`; | 303 | const serverName = `${target.name}-${randomUUID().slice(0, 8)}`; |
| 303 | // Per-connection settings ride the MCP endpoint query, the ecosystem | 304 | // Per-connection settings ride the MCP endpoint query, the ecosystem |
| 304 | // convention: `elicitation_mode` so a paused execution yields an approvalUrl | 305 | // convention: `elicitation_mode` so a paused execution yields an approvalUrl |
| 305 | // instead of letting the model resume inline, and `artifacts=true` to opt | 306 | // instead of letting the model resume inline, and `artifacts=false` to opt |
| 306 | // the session in to the artifact surface. Both are non-defaults, so a | 307 | // the session out of the artifact surface. Both are non-defaults, so a |
| 307 | // plain session's URL carries no query at all. | 308 | // plain session's URL carries no query at all. |
| 308 | const sessionQuery = [ | 309 | const sessionQuery = [ |
| 309 | ...(options?.elicitationMode ? [`elicitation_mode=${options.elicitationMode}`] : []), | 310 | ...(options?.elicitationMode ? [`elicitation_mode=${options.elicitationMode}`] : []), |
| 310 | ...(options?.artifacts === true ? ["artifacts=true"] : []), | 311 | ...(options?.artifacts === false ? ["artifacts=false"] : []), |
| 311 | ].join("&"); | 312 | ].join("&"); |
| 312 | const sessionUrl = sessionQuery ? `${mcpUrl}?${sessionQuery}` : mcpUrl; | 313 | const sessionUrl = sessionQuery ? `${mcpUrl}?${sessionQuery}` : mcpUrl; |
| 313 | 314 | ||
| @@ -609,7 +609,7 @@ export const SKILLS: readonly Skill[] = [ · 608 unmodified lines | |||
| 609 | ]; | 609 | ]; |
| 610 | 610 | ||
| 611 | /** The skills that only make sense when the session serves artifacts. A | 611 | /** The skills that only make sense when the session serves artifacts. A |
| 612 | * connection that did not opt in to artifacts (`?artifacts=true`) has no tool | 612 | * connection that opted out of artifacts (`?artifacts=false`) has no tool |
| 613 | * to apply them to, so they are dropped from its catalog rather than served as | 613 | * to apply them to, so they are dropped from its catalog rather than served as |
| 614 | * guidance for a surface it cannot reach. */ | 614 | * guidance for a surface it cannot reach. */ |
| 615 | const ARTIFACT_SKILLS: ReadonlySet<Skill> = new Set([CREATE_ARTIFACT_SKILL, ARTIFACT_STYLE_SKILL]); | 615 | const ARTIFACT_SKILLS: ReadonlySet<Skill> = new Set([CREATE_ARTIFACT_SKILL, ARTIFACT_STYLE_SKILL]); |
| @@ -41,7 +41,7 @@ export interface McpSessionInit { · 40 unmodified lines | |||
| 41 | readonly userId: string; | 41 | readonly userId: string; |
| 42 | readonly elicitationMode: McpElicitationMode; | 42 | readonly elicitationMode: McpElicitationMode; |
| 43 | /** Whether this session serves artifacts, read off `?artifacts=` at connect | 43 | /** Whether this session serves artifacts, read off `?artifacts=` at connect |
| 44 | * time. Absent means the default (disabled). */ | 44 | * time. Absent means the default (enabled). */ |
| 45 | readonly artifactsEnabled?: boolean; | 45 | readonly artifactsEnabled?: boolean; |
| 46 | /** The MCP resource the session was minted against (`/mcp` default vs a | 46 | /** The MCP resource the session was minted against (`/mcp` default vs a |
| 47 | * `/mcp/toolkits/<slug>` toolkit), so the tool catalog is scoped to it. */ | 47 | * `/mcp/toolkits/<slug>` toolkit), so the tool catalog is scoped to it. */ |
| @@ -103,7 +103,7 @@ export interface SessionMeta { · 55 unmodified lines | |||
| 103 | readonly elicitationMode?: McpElicitationMode; | 103 | readonly elicitationMode?: McpElicitationMode; |
| 104 | /** Whether the session serves artifacts (carried from {@link McpSessionInit}). | 104 | /** Whether the session serves artifacts (carried from {@link McpSessionInit}). |
| 105 | * Absent — including for sessions persisted before the flag existed — means | 105 | * Absent — including for sessions persisted before the flag existed — means |
| 106 | * disabled. */ | 106 | * the default (enabled). */ |
| 107 | readonly artifactsEnabled?: boolean; | 107 | readonly artifactsEnabled?: boolean; |
| 108 | /** The MCP resource the session serves (carried from {@link McpSessionInit}); | 108 | /** The MCP resource the session serves (carried from {@link McpSessionInit}); |
| 109 | * `buildMcpServer` scopes the tool catalog to it. */ | 109 | * `buildMcpServer` scopes the tool catalog to it. */ |
| @@ -1246,7 +1246,7 @@ const startMcpHarnessForEngine = async <E extends Cause.YieldableError>( · 1245 unmodified lines | |||
| 1246 | loadAppShellHtml: loadMcpAppsShellHtml, | 1246 | loadAppShellHtml: loadMcpAppsShellHtml, |
| 1247 | artifacts: makeInMemoryArtifacts(), | 1247 | artifacts: makeInMemoryArtifacts(), |
| 1248 | // Artifacts are opt-in per connection; this harness drives the artifact | 1248 | // Artifacts are opt-in per connection; this harness drives the artifact |
| 1249 | // tools, so it connects the way a `?artifacts=true` client does. | 1249 | // tools, the connection default. |
| 1250 | artifactsEnabled: true, | 1250 | artifactsEnabled: true, |
| 1251 | }), | 1251 | }), |
| 1252 | ); | 1252 | ); |
| @@ -140,11 +140,11 @@ const withClient = async <E extends Cause.YieldableError>( · 139 unmodified lines | |||
| 140 | createExecutorMcpServer({ | 140 | createExecutorMcpServer({ |
| 141 | engine, | 141 | engine, |
| 142 | loadAppShellHtml: () => Promise.resolve(SHELL_HTML), | 142 | loadAppShellHtml: () => Promise.resolve(SHELL_HTML), |
| 143 | // Artifacts are opt-in per connection, and nearly every test in this file | 143 | // Artifacts are on by default and nearly every test in this file |
| 144 | // exercises the artifact surface — so the helper connects the way a real | 144 | // exercises the artifact surface; spelled out here anyway so the cases |
| 145 | // `?artifacts=true` client does. The cases that assert the OFF surface | 145 | // that assert the OFF surface (`artifactsEnabled: false`) read as the |
| 146 | // pass `artifactsEnabled: false`, and the default (no flag at all) is | 146 | // deliberate contrast. The default (no flag at all) is proved directly |
| 147 | // proved by building the server directly below. | 147 | // in the opt-out describe block. |
| 148 | artifactsEnabled: true, | 148 | artifactsEnabled: true, |
| 149 | ...config, | 149 | ...config, |
| 150 | } as ExecutorMcpServerConfig<E>), | 150 | } as ExecutorMcpServerConfig<E>), |
| @@ -479,11 +479,11 @@ describe("MCP host — artifact tool visibility", () => { · 328 unmodified lines | |||
| 479 | await serverTransport.close(); | 479 | await serverTransport.close(); |
| 480 | }); | 480 | }); |
| 481 | 481 | ||
| 482 | // A session that did not opt in (`?artifacts=true`) gets no artifact surface | 482 | // A session that opted out (`?artifacts=false`) gets no artifact surface at |
| 483 | // at all. The host here is fully artifact-capable — shell loader, store, the | 483 | // all. The host here is fully artifact-capable — shell loader, store, the |
| 484 | // apps client capability — so what these assert is the connection default | 484 | // apps client capability — so what these assert is the opt-out itself, not |
| 485 | // itself, not a host that never had artifacts. | 485 | // a host that never had artifacts. |
| 486 | it("registers no artifact tools on a default connection", async () => { | 486 | it("registers no artifact tools on an opted-out connection", async () => { |
| 487 | const store = makeArtifactStore(); | 487 | const store = makeArtifactStore(); |
| 488 | await withClient( | 488 | await withClient( |
| 489 | makeStubEngine({}), | 489 | makeStubEngine({}), |
| @@ -500,9 +500,9 @@ describe("MCP host — artifact tool visibility", () => { · 10 unmodified lines | |||
| 500 | expect(names).toContain("skills"); | 500 | expect(names).toContain("skills"); |
| 501 | expect(names).toContain("resume"); | 501 | expect(names).toContain("resume"); |
| 502 | }, | 502 | }, |
| 503 | // No `artifactsEnabled` at all: the real shape of a client that connected | 503 | // The explicit opt-out: the shape of a client that connected with |
| 504 | // to a bare `/mcp` endpoint. | 504 | // `?artifacts=false`. |
| 505 | { artifacts: store.port, artifactsEnabled: undefined }, | 505 | { artifacts: store.port, artifactsEnabled: false }, |
| 506 | ); | 506 | ); |
| 507 | }); | 507 | }); |
| 508 | 508 | ||
| @@ -510,7 +510,7 @@ describe("MCP host — artifact tool visibility", () => { · 1 unmodified line | |||
| 510 | // The read is what proves it: with no resource registered at all, the SDK | 510 | // The read is what proves it: with no resource registered at all, the SDK |
| 511 | // does not install a resources handler either, so `resources/list` itself is | 511 | // does not install a resources handler either, so `resources/list` itself is |
| 512 | // unavailable — the same shape a host without a shell loader has always had. | 512 | // unavailable — the same shape a host without a shell loader has always had. |
| 513 | it("serves no shell resource on a default connection", async () => { | 513 | it("serves no shell resource on an opted-out connection", async () => { |
| 514 | const store = makeArtifactStore(); | 514 | const store = makeArtifactStore(); |
| 515 | await withClient( | 515 | await withClient( |
| 516 | makeStubEngine({}), | 516 | makeStubEngine({}), |
| @@ -518,11 +518,11 @@ describe("MCP host — artifact tool visibility", () => { · 1 unmodified line | |||
| 518 | async (client) => { | 518 | async (client) => { |
| 519 | await expect(client.readResource({ uri: MCP_APPS_SHELL_RESOURCE_URI })).rejects.toThrow(); | 519 | await expect(client.readResource({ uri: MCP_APPS_SHELL_RESOURCE_URI })).rejects.toThrow(); |
| 520 | }, | 520 | }, |
| 521 | { artifacts: store.port, artifactsEnabled: undefined }, | 521 | { artifacts: store.port, artifactsEnabled: false }, |
| 522 | ); | 522 | ); |
| 523 | }); | 523 | }); |
| 524 | 524 | ||
| 525 | it("drops the artifact skills from a default session's inventory", async () => { | 525 | it("drops the artifact skills from an opted-out session's inventory", async () => { |
| 526 | const store = makeArtifactStore(); | 526 | const store = makeArtifactStore(); |
| 527 | await withClient( | 527 | await withClient( |
| 528 | makeStubEngine({}), | 528 | makeStubEngine({}), |
| @@ -542,11 +542,11 @@ describe("MCP host — artifact tool visibility", () => { · 13 unmodified lines | |||
| 542 | expect(fetched.isError).toBe(true); | 542 | expect(fetched.isError).toBe(true); |
| 543 | expect(textOf(fetched)).toContain('No skill named "create-artifact"'); | 543 | expect(textOf(fetched)).toContain('No skill named "create-artifact"'); |
| 544 | }, | 544 | }, |
| 545 | { artifacts: store.port, artifactsEnabled: undefined }, | 545 | { artifacts: store.port, artifactsEnabled: false }, |
| 546 | ); | 546 | ); |
| 547 | }); | 547 | }); |
| 548 | 548 | ||
| 549 | it("serves the full artifact surface to a session that opted in", async () => { | 549 | it("serves the full artifact surface on the connection default (no flag at all)", async () => { |
| 550 | const store = makeArtifactStore(); | 550 | const store = makeArtifactStore(); |
| 551 | await withClient( | 551 | await withClient( |
| 552 | makeStubEngine({}), | 552 | makeStubEngine({}), |
| @@ -566,7 +566,9 @@ describe("MCP host — artifact tool visibility", () => { · 13 unmodified lines | |||
| 566 | expect(index).toContain("`create-artifact`"); | 566 | expect(index).toContain("`create-artifact`"); |
| 567 | expect(index).toContain("`artifact-style`"); | 567 | expect(index).toContain("`artifact-style`"); |
| 568 | }, | 568 | }, |
| 569 | { artifacts: store.port }, | 569 | // Overrides the helper's explicit `artifactsEnabled: true` with the real |
| 570 | // absent-flag shape, so this pins the DEFAULT, not an opt-in. | ||
| 571 | { artifacts: store.port, artifactsEnabled: undefined }, | ||
| 570 | ); | 572 | ); |
| 571 | }); | 573 | }); |
| 572 | 574 | ||
| @@ -2235,24 +2237,25 @@ describe("artifactUrlFor", () => { · 1662 unmodified lines | |||
| 2235 | // The `?artifacts=` endpoint query | 2237 | // The `?artifacts=` endpoint query |
| 2236 | // --------------------------------------------------------------------------- | 2238 | // --------------------------------------------------------------------------- |
| 2237 | 2239 | ||
| 2238 | describe("artifacts opt-in query", () => { | 2240 | describe("artifacts opt-out query", () => { |
| 2239 | const read = (query: string) => | 2241 | const read = (query: string) => |
| 2240 | readArtifactsEnabled(new Request(`https://executor.example/mcp${query}`)); | 2242 | readArtifactsEnabled(new Request(`https://executor.example/mcp${query}`)); |
| 2241 | 2243 | ||
| 2242 | it("keeps artifacts off for a clean endpoint", () => { | 2244 | it("serves artifacts on a clean endpoint", () => { |
| 2243 | expect(read("")).toBe(false); | 2245 | expect(read("")).toBe(true); |
| 2244 | expect(read("?elicitation_mode=browser")).toBe(false); | 2246 | expect(read("?elicitation_mode=browser")).toBe(true); |
| 2245 | }); | 2247 | }); |
| 2246 | 2248 | ||
| 2247 | it("turns artifacts on for the documented true values", () => { | 2249 | it("keeps artifacts on for the explicit true values", () => { |
| 2248 | for (const value of ["true", "TRUE", "1", "yes", "on"]) { | 2250 | for (const value of ["true", "TRUE", "1", "yes", "on"]) { |
| 2249 | expect(read(`?artifacts=${value}`)).toBe(true); | 2251 | expect(read(`?artifacts=${value}`)).toBe(true); |
| 2250 | } | 2252 | } |
| 2251 | }); | 2253 | }); |
| 2252 | 2254 | ||
| 2253 | // Anything else reads as "not an opt-in", so a typo cannot silently hand a | 2255 | // Once the flag is present, anything but a documented true value reads as |
| 2254 | // connection a surface the user never asked for. | 2256 | // the opt-out — a user who touched the flag at all was reaching for "off", |
| 2255 | it("keeps artifacts off for any other value", () => { | 2257 | // so a typo lands on the side they were steering toward. |
| 2258 | it("turns artifacts off for the opt-out and any other explicit value", () => { | ||
| 2256 | for (const value of ["false", "0", "no", "ture", ""]) { | 2259 | for (const value of ["false", "0", "no", "ture", ""]) { |
| 2257 | expect(read(`?artifacts=${value}`)).toBe(false); | 2260 | expect(read(`?artifacts=${value}`)).toBe(false); |
| 2258 | } | 2261 | } |
| @@ -52,16 +52,17 @@ export const readElicitationMode = (request: Request): McpElicitationMode => { · 51 unmodified lines | |||
| 52 | }; | 52 | }; |
| 53 | 53 | ||
| 54 | /** | 54 | /** |
| 55 | * Read the artifacts opt-in off an MCP request's `?artifacts=` query. | 55 | * Read the artifacts opt-OUT off an MCP request's `?artifacts=` query. |
| 56 | * Artifacts are OFF by default, so a clean endpoint URL serves no artifact | 56 | * Artifacts are ON by default: a clean endpoint URL gets the full artifact |
| 57 | * surface; only an explicit true value (`?artifacts=true`, and the same truthy | 57 | * surface, and only an explicit false value (`?artifacts=false`, or any |
| 58 | * spellings the rest of the query vocabulary accepts) turns them on. A session | 58 | * spelling that isn't one of the accepted truthy forms) withholds it. A |
| 59 | * without the opt-in gets no artifact tools, no `ui://` shell resource, and no | 59 | * session that opts out gets no artifact tools, no `ui://` shell resource, |
| 60 | * artifact skills — as if the host had never been configured for them. | 60 | * and no artifact skills — as if the host had never been configured for them. |
| 61 | * `?artifacts=true` remains accepted and is simply the default made explicit. | ||
| 61 | */ | 62 | */ |
| 62 | export const readArtifactsEnabled = (request: Request): boolean => { | 63 | export const readArtifactsEnabled = (request: Request): boolean => { |
| 63 | const value = new URL(request.url).searchParams.get("artifacts"); | 64 | const value = new URL(request.url).searchParams.get("artifacts"); |
| 64 | if (value === null) return false; | 65 | if (value === null) return true; |
| 65 | return TRUE_QUERY_VALUES.has(value.toLowerCase()); | 66 | return TRUE_QUERY_VALUES.has(value.toLowerCase()); |
| 66 | }; | 67 | }; |
| 67 | 68 | ||
| @@ -71,9 +71,9 @@ export interface McpBuildServerOptions { · 70 unmodified lines | |||
| 71 | | { readonly mode: "model" } | 71 | | { readonly mode: "model" } |
| 72 | | { readonly mode: "native" }; | 72 | | { readonly mode: "native" }; |
| 73 | readonly browserApprovalStore?: BrowserApprovalStore; | 73 | readonly browserApprovalStore?: BrowserApprovalStore; |
| 74 | /** Whether this session serves artifacts. True only when the client connected | 74 | /** Whether this session serves artifacts. True unless the client connected |
| 75 | * with `?artifacts=true`; otherwise the built server registers none of the | 75 | * with `?artifacts=false`; opted out, the built server registers none of |
| 76 | * artifact tools, resource, or skills. */ | 76 | * the artifact tools, resource, or skills. */ |
| 77 | readonly artifactsEnabled?: boolean; | 77 | readonly artifactsEnabled?: boolean; |
| 78 | } | 78 | } |
| 79 | 79 | ||
| @@ -156,8 +156,8 @@ type SharedMcpServerConfig = { · 155 unmodified lines | |||
| 156 | */ | 156 | */ |
| 157 | readonly loadAppShellHtml?: () => Promise<string>; | 157 | readonly loadAppShellHtml?: () => Promise<string>; |
| 158 | /** | 158 | /** |
| 159 | * Per-connection artifacts opt-in. Defaults to false. A client that connects | 159 | * Per-connection artifacts opt-out. Defaults to true. A client that connects |
| 160 | * without `?artifacts=true` gets NO artifact surface at all: none of the five | 160 | * with `?artifacts=false` gets NO artifact surface at all: none of the five |
| 161 | * artifact tools, no `ui://` shell resource, and no artifact entries in the | 161 | * artifact tools, no `ui://` shell resource, and no artifact entries in the |
| 162 | * `skills` inventory — the same shape a host without `loadAppShellHtml` | 162 | * `skills` inventory — the same shape a host without `loadAppShellHtml` |
| 163 | * serves. `execute`, `skills` and `resume` are untouched. | 163 | * serves. `execute`, `skills` and `resume` are untouched. |
| @@ -1037,10 +1037,10 @@ export const createExecutorMcpServer = <E extends Cause.YieldableError>( · 873 unmodified lines | |||
| 1037 | // The same live integration inventory the description carries, re-used by | 1037 | // The same live integration inventory the description carries, re-used by |
| 1038 | // the `skills` tool so the `execute` guide lists what is connected too. | 1038 | // the `skills` tool so the `execute` guide lists what is connected too. |
| 1039 | const executeInventory = extractInventory(description); | 1039 | const executeInventory = extractInventory(description); |
| 1040 | // Artifacts are off unless this connection opted in (`?artifacts=true`). | 1040 | // Artifacts are on unless this connection opted out (`?artifacts=false`). |
| 1041 | // One flag decides the whole surface: the tools, the shell resource, and | 1041 | // One flag decides the whole surface: the tools, the shell resource, and |
| 1042 | // the skills catalog below. | 1042 | // the skills catalog below. |
| 1043 | const artifactsEnabled = config.artifactsEnabled ?? false; | 1043 | const artifactsEnabled = config.artifactsEnabled ?? true; |
| 1044 | const skillCatalog: readonly Skill[] = skillCatalogFor({ artifacts: artifactsEnabled }); | 1044 | const skillCatalog: readonly Skill[] = skillCatalogFor({ artifacts: artifactsEnabled }); |
| 1045 | 1045 | ||
| 1046 | // Captured at construction time. SDK callbacks fire later (often | 1046 | // Captured at construction time. SDK callbacks fire later (often |
| @@ -1812,7 +1812,7 @@ export const createExecutorMcpServer = <E extends Cause.YieldableError>( · 765 unmodified lines | |||
| 1812 | ); | 1812 | ); |
| 1813 | 1813 | ||
| 1814 | // Two independent reasons to serve no artifact surface: the host cannot | 1814 | // Two independent reasons to serve no artifact surface: the host cannot |
| 1815 | // (no shell loader), or this connection did not ask for it (`?artifacts=true`). | 1815 | // (no shell loader), or this connection opted out (`?artifacts=false`). |
| 1816 | // Either way nothing below registers, so a disabled session is byte-for-byte | 1816 | // Either way nothing below registers, so a disabled session is byte-for-byte |
| 1817 | // a session on a host that never had artifacts. | 1817 | // a session on a host that never had artifacts. |
| 1818 | const loadAppShellHtml = artifactsEnabled ? config.loadAppShellHtml : undefined; | 1818 | const loadAppShellHtml = artifactsEnabled ? config.loadAppShellHtml : undefined; |
| @@ -74,15 +74,15 @@ describe("MCP install command rendering", () => { · 73 unmodified lines | |||
| 74 | ); | 74 | ); |
| 75 | }); | 75 | }); |
| 76 | 76 | ||
| 77 | // Artifacts are off by default, so only the opt-in is ever spelled out: a | 77 | // Artifacts are on by default, so only the opt-out is ever spelled out: a |
| 78 | // card left alone must still produce the bare endpoint every doc and test | 78 | // card left alone must still produce the bare endpoint every doc and test |
| 79 | // asserts. | 79 | // asserts. |
| 80 | it("emits the artifacts opt-in only when artifacts are enabled", () => { | 80 | it("emits the artifacts opt-out only when artifacts are disabled", () => { |
| 81 | expect( | 81 | expect( |
| 82 | buildMcpHttpEndpoint({ | 82 | buildMcpHttpEndpoint({ |
| 83 | origin: "https://executor.example", | 83 | origin: "https://executor.example", |
| 84 | desktop: null, | 84 | desktop: null, |
| 85 | artifacts: false, | 85 | artifacts: true, |
| 86 | }), | 86 | }), |
| 87 | ).toBe("https://executor.example/mcp"); | 87 | ).toBe("https://executor.example/mcp"); |
| 88 | 88 | ||
| @@ -90,9 +90,9 @@ describe("MCP install command rendering", () => { · 1 unmodified line | |||
| 90 | buildMcpHttpEndpoint({ | 90 | buildMcpHttpEndpoint({ |
| 91 | origin: "https://executor.example", | 91 | origin: "https://executor.example", |
| 92 | desktop: null, | 92 | desktop: null, |
| 93 | artifacts: true, | 93 | artifacts: false, |
| 94 | }), | 94 | }), |
| 95 | ).toBe("https://executor.example/mcp?artifacts=true"); | 95 | ).toBe("https://executor.example/mcp?artifacts=false"); |
| 96 | 96 | ||
| 97 | // Both non-defaults together, in the order the card renders them. | 97 | // Both non-defaults together, in the order the card renders them. |
| 98 | expect( | 98 | expect( |
| @@ -100,25 +100,25 @@ describe("MCP install command rendering", () => { · 1 unmodified line | |||
| 100 | origin: "https://executor.example", | 100 | origin: "https://executor.example", |
| 101 | desktop: null, | 101 | desktop: null, |
| 102 | elicitationMode: "browser", | 102 | elicitationMode: "browser", |
| 103 | artifacts: true, | 103 | artifacts: false, |
| 104 | }), | 104 | }), |
| 105 | ).toBe("https://executor.example/mcp?elicitation_mode=browser&artifacts=true"); | 105 | ).toBe("https://executor.example/mcp?elicitation_mode=browser&artifacts=false"); |
| 106 | 106 | ||
| 107 | // The unknown-origin placeholder is not a parsable URL, so it is | 107 | // The unknown-origin placeholder is not a parsable URL, so it is |
| 108 | // concatenated rather than round-tripped through `URL`. | 108 | // concatenated rather than round-tripped through `URL`. |
| 109 | expect(buildMcpHttpEndpoint({ origin: null, desktop: null, artifacts: true })).toBe( | 109 | expect(buildMcpHttpEndpoint({ origin: null, desktop: null, artifacts: false })).toBe( |
| 110 | "<this-server>/mcp?artifacts=true", | 110 | "<this-server>/mcp?artifacts=false", |
| 111 | ); | 111 | ); |
| 112 | }); | 112 | }); |
| 113 | 113 | ||
| 114 | it("passes the artifacts opt-in to the stdio CLI as a flag", () => { | 114 | it("passes the artifacts opt-out to the stdio CLI as a flag", () => { |
| 115 | expect( | 115 | expect( |
| 116 | buildMcpInstallCommand({ mode: "stdio", isDev: false, origin: null, artifacts: false }), | 116 | buildMcpInstallCommand({ mode: "stdio", isDev: false, origin: null, artifacts: true }), |
| 117 | ).toBe("npx add-mcp 'executor mcp' --name executor"); | 117 | ).toBe("npx add-mcp 'executor mcp' --name executor"); |
| 118 | 118 | ||
| 119 | expect( | 119 | expect( |
| 120 | buildMcpInstallCommand({ mode: "stdio", isDev: false, origin: null, artifacts: true }), | 120 | buildMcpInstallCommand({ mode: "stdio", isDev: false, origin: null, artifacts: false }), |
| 121 | ).toBe("npx add-mcp 'executor mcp --artifacts' --name executor"); | 121 | ).toBe("npx add-mcp 'executor mcp --no-artifacts' --name executor"); |
| 122 | }); | 122 | }); |
| 123 | 123 | ||
| 124 | it("passes model-managed resume through stdio install commands", () => { | 124 | it("passes model-managed resume through stdio install commands", () => { |
| @@ -49,8 +49,8 @@ export const buildMcpHttpEndpoint = (input: { · 48 unmodified lines | |||
| 49 | readonly port: number; | 49 | readonly port: number; |
| 50 | } | null; | 50 | } | null; |
| 51 | readonly elicitationMode?: McpElicitationMode; | 51 | readonly elicitationMode?: McpElicitationMode; |
| 52 | /** Artifacts are off by default, so only the opt-in is spelled out on the | 52 | /** Artifacts are on by default, so only the opt-out is spelled out on the |
| 53 | * URL (`&artifacts=true`) and a default endpoint stays clean. */ | 53 | * URL (`&artifacts=false`) and a default endpoint stays clean. */ |
| 54 | readonly artifacts?: boolean; | 54 | readonly artifacts?: boolean; |
| 55 | // Cloud only: pins the URL to `/<org-slug>/mcp` (the server also accepts the | 55 | // Cloud only: pins the URL to `/<org-slug>/mcp` (the server also accepts the |
| 56 | // legacy `/<org_id>/mcp` form). Desktop/local pass nothing and get the bare | 56 | // legacy `/<org_id>/mcp` form). Desktop/local pass nothing and get the bare |
| @@ -67,12 +67,12 @@ export const buildMcpHttpEndpoint = (input: { · 10 unmodified lines | |||
| 67 | ? `${input.origin}${mcpPath}` | 67 | ? `${input.origin}${mcpPath}` |
| 68 | : `<this-server>${mcpPath}`; | 68 | : `<this-server>${mcpPath}`; |
| 69 | // Only non-default choices reach the URL, so the common endpoint has no query | 69 | // Only non-default choices reach the URL, so the common endpoint has no query |
| 70 | // at all: `model` elicitation and artifacts-off are what the server assumes. | 70 | // at all: `model` elicitation and artifacts-on are what the server assumes. |
| 71 | const params: Array<readonly [string, string]> = []; | 71 | const params: Array<readonly [string, string]> = []; |
| 72 | if (input.elicitationMode && input.elicitationMode !== "model") { | 72 | if (input.elicitationMode && input.elicitationMode !== "model") { |
| 73 | params.push(["elicitation_mode", input.elicitationMode]); | 73 | params.push(["elicitation_mode", input.elicitationMode]); |
| 74 | } | 74 | } |
| 75 | if (input.artifacts === true) params.push(["artifacts", "true"]); | 75 | if (input.artifacts === false) params.push(["artifacts", "false"]); |
| 76 | if (params.length === 0) return endpoint; | 76 | if (params.length === 0) return endpoint; |
| 77 | 77 | ||
| 78 | const query = params.map(([key, value]) => `${key}=${value}`).join("&"); | 78 | const query = params.map(([key, value]) => `${key}=${value}`).join("&"); |
| @@ -127,8 +127,8 @@ export const buildMcpInstallCommand = (input: { · 48 unmodified lines | |||
| 127 | if (input.elicitationMode && input.elicitationMode !== "model") { | 127 | if (input.elicitationMode && input.elicitationMode !== "model") { |
| 128 | innerArgs.push("--elicitation-mode", input.elicitationMode); | 128 | innerArgs.push("--elicitation-mode", input.elicitationMode); |
| 129 | } | 129 | } |
| 130 | if (input.artifacts === true) { | 130 | if (input.artifacts === false) { |
| 131 | innerArgs.push("--artifacts"); | 131 | innerArgs.push("--no-artifacts"); |
| 132 | } | 132 | } |
| 133 | return `npx add-mcp ${shellQuoteWord(innerArgs.map(shellQuoteWord).join(" "))} --name executor`; | 133 | return `npx add-mcp ${shellQuoteWord(innerArgs.map(shellQuoteWord).join(" "))} --name executor`; |
| 134 | }; | 134 | }; |
| @@ -137,7 +137,7 @@ export function McpInstallCard(props: { className?: string }) { · 2 unmodified lines | |||
| 137 | const [mode, setMode] = useState<TransportMode>("http"); | 137 | const [mode, setMode] = useState<TransportMode>("http"); |
| 138 | const [advancedOpen, setAdvancedOpen] = useState(false); | 138 | const [advancedOpen, setAdvancedOpen] = useState(false); |
| 139 | const [httpElicitationMode, setHttpElicitationMode] = useState<McpElicitationMode>("model"); | 139 | const [httpElicitationMode, setHttpElicitationMode] = useState<McpElicitationMode>("model"); |
| 140 | const [artifacts, setArtifacts] = useState(false); | 140 | const [artifacts, setArtifacts] = useState(true); |
| 141 | const organizationSlug = useOrganizationSlug(); | 141 | const organizationSlug = useOrganizationSlug(); |
| 142 | const serverConnection = useExecutorServerConnection(); | 142 | const serverConnection = useExecutorServerConnection(); |
| 143 | // Desktop hosts ship Electron without putting an `executor` binary on | 143 | // Desktop hosts ship Electron without putting an `executor` binary on |
| @@ -208,7 +208,7 @@ export function McpInstallCard(props: { className?: string }) { · 64 unmodified lines | |||
| 208 | <div className="mt-0.5 text-xs leading-5 text-muted-foreground"> | 208 | <div className="mt-0.5 text-xs leading-5 text-muted-foreground"> |
| 209 | {artifacts | 209 | {artifacts |
| 210 | ? "Generated UI components are saved to your workspace." | 210 | ? "Generated UI components are saved to your workspace." |
| 211 | : "Enable to let agents create saved UI components."} | 211 | : "Disabled: this connection serves no artifact tools."} |
| 212 | </div> | 212 | </div> |
| 213 | </div> | 213 | </div> |
| 214 | <Switch | 214 | <Switch |