- cloud: resolves userId from the authenticated session and builds a [userOrgScope, orgScope] stack via createScopedExecutor(userId, orgId, orgName). userId threaded through execution-stack, mcp-session, and MCP dispatch init. - cloud env refactor: deletes the lazy Proxy apps/cloud/src/env.ts in favor of cloudflare:workers env imported at call sites; adds env-augment.d.ts to type secrets not present in generated wrangler types. - cloud/server.ts: env-guarded OTEL wiring (skips instrument() when AXIOM_TOKEN absent); captures rawFetch before instrument() mutates it so dev requests still land on the Hono app. - cloud/services/db.ts: prefers DATABASE_URL over the hyperdrive proxy so local dev works without the tunnel. - vite.config.ts: devCrashGuard plugin that prints uncaught Worker errors with a stable exit. - hosts/mcp: optional debug flag surfaces verbose session logging, wired up from cloud mcp-session. - local/examples/marketing: migrated to scopes: [scope].
Showing the changes to packages (1 of 25 files). Show all
Unified · Split
| @@ -66,6 +66,10 @@ type SharedMcpServerConfig = { · 65 unmodified lines | |||
| 66 | * under whichever request triggered it; see the Cloud DO). | 66 | * under whichever request triggered it; see the Cloud DO). |
| 67 | */ | 67 | */ |
| 68 | readonly parentSpan?: Tracer.AnySpan | (() => Tracer.AnySpan | undefined); | 68 | readonly parentSpan?: Tracer.AnySpan | (() => Tracer.AnySpan | undefined); |
| 69 | /** | ||
| 70 | * Enable verbose MCP capability / elicitation debug logging. | ||
| 71 | */ | ||
| 72 | readonly debug?: boolean; | ||
| 69 | }; | 73 | }; |
| 70 | 74 | ||
| 71 | export type ExecutorMcpServerConfig<E extends Cause.YieldableError = Cause.YieldableError> = | 75 | export type ExecutorMcpServerConfig<E extends Cause.YieldableError = Cause.YieldableError> = |
| @@ -85,9 +89,21 @@ const getElicitationSupport = (server: McpServer): { form: boolean; url: boolean · 13 unmodified lines | |||
| 85 | return { form: Boolean(elicitation.form), url: Boolean(elicitation.url) }; | 89 | return { form: Boolean(elicitation.form), url: Boolean(elicitation.url) }; |
| 86 | }; | 90 | }; |
| 87 | 91 | ||
| 92 | const readDebugDefault = (): boolean => { | ||
| 93 | if (typeof process === "undefined" || !process.env) return false; | ||
| 94 | const value = process.env.EXECUTOR_MCP_DEBUG; | ||
| 95 | return value === "1" || value === "true"; | ||
| 96 | }; | ||
| 97 | |||
| 88 | const supportsManagedElicitation = (server: McpServer): boolean => | 98 | const supportsManagedElicitation = (server: McpServer): boolean => |
| 89 | getElicitationSupport(server).form; | 99 | getElicitationSupport(server).form; |
| 90 | 100 | ||
| 101 | const capabilitySnapshot = (server: McpServer) => ({ | ||
| 102 | clientCapabilities: server.server.getClientCapabilities() ?? null, | ||
| 103 | elicitationSupport: getElicitationSupport(server), | ||
| 104 | managedElicitation: supportsManagedElicitation(server), | ||
| 105 | }); | ||
| 106 | |||
| 91 | type ElicitInputParams = | 107 | type ElicitInputParams = |
| 92 | | { | 108 | | { |
| 93 | mode?: "form"; | 109 | mode?: "form"; |
| @@ -118,7 +134,10 @@ const elicitationRequestToParams: (request: ElicitationRequest) => ElicitInputPa · 24 unmodified lines | |||
| 118 | ); | 134 | ); |
| 119 | 135 | ||
| 120 | const makeMcpElicitationHandler = | 136 | const makeMcpElicitationHandler = |
| 121 | (server: McpServer): ElicitationHandler => | 137 | ( |
| 138 | server: McpServer, | ||
| 139 | debugLog?: (event: string, data: Record<string, unknown>) => void, | ||
| 140 | ): ElicitationHandler => | ||
| 122 | (ctx: ElicitationContext): Effect.Effect<typeof ElicitationResponse.Type> => { | 141 | (ctx: ElicitationContext): Effect.Effect<typeof ElicitationResponse.Type> => { |
| 123 | const { url: supportsUrl } = getElicitationSupport(server); | 142 | const { url: supportsUrl } = getElicitationSupport(server); |
| 124 | 143 | ||
| @@ -133,19 +152,51 @@ const makeMcpElicitationHandler = · 8 unmodified lines | |||
| 133 | : elicitationRequestToParams(ctx.request); | 152 | : elicitationRequestToParams(ctx.request); |
| 134 | 153 | ||
| 135 | return Effect.promise(async (): Promise<typeof ElicitationResponse.Type> => { | 154 | return Effect.promise(async (): Promise<typeof ElicitationResponse.Type> => { |
| 155 | debugLog?.("elicitation.request", { | ||
| 156 | requestTag: ctx.request._tag, | ||
| 157 | supportsUrl, | ||
| 158 | message: ctx.request.message, | ||
| 159 | hasRequestedSchema: | ||
| 160 | ctx.request._tag === "FormElicitation" | ||
| 161 | ? Object.keys(ctx.request.requestedSchema).length > 0 | ||
| 162 | : false, | ||
| 163 | url: ctx.request._tag === "UrlElicitation" ? ctx.request.url : undefined, | ||
| 164 | clientCapabilities: server.server.getClientCapabilities() ?? null, | ||
| 165 | }); | ||
| 136 | try { | 166 | try { |
| 137 | const response = await server.server.elicitInput( | 167 | const response = await server.server.elicitInput( |
| 138 | params as Parameters<typeof server.server.elicitInput>[0], | 168 | params as Parameters<typeof server.server.elicitInput>[0], |
| 139 | ); | 169 | ); |
| 140 | 170 | ||
| 171 | debugLog?.("elicitation.response", { | ||
| 172 | requestTag: ctx.request._tag, | ||
| 173 | action: response.action, | ||
| 174 | hasContent: | ||
| 175 | typeof response.content === "object" && | ||
| 176 | response.content !== null && | ||
| 177 | Object.keys(response.content).length > 0, | ||
| 178 | }); | ||
| 179 | |||
| 141 | return { | 180 | return { |
| 142 | action: response.action, | 181 | action: response.action, |
| 143 | content: response.content, | 182 | content: response.content, |
| 144 | }; | 183 | }; |
| 145 | } catch (err) { | 184 | } catch (err) { |
| 185 | debugLog?.("elicitation.error", { | ||
| 186 | requestTag: ctx.request._tag, | ||
| 187 | error: | ||
| 188 | err instanceof Error | ||
| 189 | ? { name: err.name, message: err.message, stack: err.stack } | ||
| 190 | : { message: String(err) }, | ||
| 191 | clientCapabilities: server.server.getClientCapabilities() ?? null, | ||
| 192 | }); | ||
| 146 | console.error( | 193 | console.error( |
| 147 | "[executor] elicitInput failed — falling back to cancel.", | 194 | "[executor] elicitInput failed — falling back to cancel.", |
| 148 | err instanceof Error ? err.message : err, | 195 | JSON.stringify({ |
| 196 | error: err instanceof Error ? err.message : String(err), | ||
| 197 | requestTag: ctx.request._tag, | ||
| 198 | ...capabilitySnapshot(server), | ||
| 199 | }), | ||
| 149 | ); | 200 | ); |
| 150 | return { action: "cancel" }; | 201 | return { action: "cancel" }; |
| 151 | } | 202 | } |
| @@ -205,6 +256,15 @@ export const createExecutorMcpServer = <E extends Cause.YieldableError>( · 53 unmodified lines | |||
| 205 | // deferred past the outer Effect's await), so we use the runtime to | 256 | // deferred past the outer Effect's await), so we use the runtime to |
| 206 | // re-enter Effect-land at each callback edge. | 257 | // re-enter Effect-land at each callback edge. |
| 207 | const runtime = yield* Effect.runtime<never>(); | 258 | const runtime = yield* Effect.runtime<never>(); |
| 259 | const debugEnabled = config.debug ?? readDebugDefault(); | ||
| 260 | const debugLog = (event: string, data: Record<string, unknown>) => { | ||
| 261 | if (!debugEnabled) return; | ||
| 262 | try { | ||
| 263 | console.error(`[executor:mcp] ${event} ${JSON.stringify(data)}`); | ||
| 264 | } catch { | ||
| 265 | console.error(`[executor:mcp] ${event}`, data); | ||
| 266 | } | ||
| 267 | }; | ||
| 208 | 268 | ||
| 209 | const resolveParentSpan = (): Tracer.AnySpan | undefined => { | 269 | const resolveParentSpan = (): Tracer.AnySpan | undefined => { |
| 210 | const ps = config.parentSpan; | 270 | const ps = config.parentSpan; |
| @@ -228,13 +288,27 @@ export const createExecutorMcpServer = <E extends Cause.YieldableError>( · 17 unmodified lines | |||
| 228 | 288 | ||
| 229 | const executeCode = (code: string): Effect.Effect<McpToolResult, E> => | 289 | const executeCode = (code: string): Effect.Effect<McpToolResult, E> => |
| 230 | Effect.gen(function* () { | 290 | Effect.gen(function* () { |
| 291 | debugLog("execute.call", { | ||
| 292 | managedElicitation: supportsManagedElicitation(server), | ||
| 293 | elicitationSupport: getElicitationSupport(server), | ||
| 294 | clientCapabilities: server.server.getClientCapabilities() ?? null, | ||
| 295 | codeLength: code.length, | ||
| 296 | }); | ||
| 231 | if (supportsManagedElicitation(server)) { | 297 | if (supportsManagedElicitation(server)) { |
| 232 | const result = yield* engine.execute(code, { | 298 | const result = yield* engine.execute(code, { |
| 233 | onElicitation: makeMcpElicitationHandler(server), | 299 | onElicitation: makeMcpElicitationHandler(server, debugLog), |
| 234 | }); | 300 | }); |
| 235 | return toMcpResult(formatExecuteResult(result)); | 301 | return toMcpResult(formatExecuteResult(result)); |
| 236 | } | 302 | } |
| 237 | const outcome = yield* engine.executeWithPause(code); | 303 | const outcome = yield* engine.executeWithPause(code); |
| 304 | debugLog("execute.paused_flow_result", { | ||
| 305 | status: outcome.status, | ||
| 306 | executionId: outcome.status === "paused" ? outcome.execution.id : undefined, | ||
| 307 | interactionKind: | ||
| 308 | outcome.status === "paused" | ||
| 309 | ? outcome.execution.elicitationContext.request._tag | ||
| 310 | : undefined, | ||
| 311 | }); | ||
| 238 | return outcome.status === "completed" | 312 | return outcome.status === "completed" |
| 239 | ? toMcpResult(formatExecuteResult(outcome.result)) | 313 | ? toMcpResult(formatExecuteResult(outcome.result)) |
| 240 | : toMcpPausedResult(formatPausedExecution(outcome.execution)); | 314 | : toMcpPausedResult(formatPausedExecution(outcome.execution)); |
| @@ -253,8 +327,15 @@ export const createExecutorMcpServer = <E extends Cause.YieldableError>( · 12 unmodified lines | |||
| 253 | content: Record<string, unknown> | undefined, | 327 | content: Record<string, unknown> | undefined, |
| 254 | ): Effect.Effect<McpToolResult, E> => | 328 | ): Effect.Effect<McpToolResult, E> => |
| 255 | Effect.gen(function* () { | 329 | Effect.gen(function* () { |
| 330 | debugLog("resume.call", { | ||
| 331 | executionId, | ||
| 332 | action, | ||
| 333 | hasContent: content !== undefined, | ||
| 334 | clientCapabilities: server.server.getClientCapabilities() ?? null, | ||
| 335 | }); | ||
| 256 | const outcome = yield* engine.resume(executionId, { action, content }); | 336 | const outcome = yield* engine.resume(executionId, { action, content }); |
| 257 | if (!outcome) { | 337 | if (!outcome) { |
| 338 | debugLog("resume.missing_execution", { executionId }); | ||
| 258 | return { | 339 | return { |
| 259 | content: [ | 340 | content: [ |
| 260 | { type: "text" as const, text: `No paused execution: ${executionId}` }, | 341 | { type: "text" as const, text: `No paused execution: ${executionId}` }, |
| @@ -262,6 +343,15 @@ export const createExecutorMcpServer = <E extends Cause.YieldableError>( · 1 unmodified line | |||
| 262 | isError: true, | 343 | isError: true, |
| 263 | } satisfies McpToolResult; | 344 | } satisfies McpToolResult; |
| 264 | } | 345 | } |
| 346 | debugLog("resume.result", { | ||
| 347 | executionId, | ||
| 348 | status: outcome.status, | ||
| 349 | nextExecutionId: outcome.status === "paused" ? outcome.execution.id : undefined, | ||
| 350 | interactionKind: | ||
| 351 | outcome.status === "paused" | ||
| 352 | ? outcome.execution.elicitationContext.request._tag | ||
| 353 | : undefined, | ||
| 354 | }); | ||
| 265 | return outcome.status === "completed" | 355 | return outcome.status === "completed" |
| 266 | ? toMcpResult(formatExecuteResult(outcome.result)) | 356 | ? toMcpResult(formatExecuteResult(outcome.result)) |
| 267 | : toMcpPausedResult(formatPausedExecution(outcome.execution)); | 357 | : toMcpPausedResult(formatPausedExecution(outcome.execution)); |
| @@ -331,6 +421,19 @@ export const createExecutorMcpServer = <E extends Cause.YieldableError>( · 63 unmodified lines | |||
| 331 | } else { | 421 | } else { |
| 332 | resumeTool.enable(); | 422 | resumeTool.enable(); |
| 333 | } | 423 | } |
| 424 | console.error( | ||
| 425 | "[executor] MCP capability snapshot", | ||
| 426 | JSON.stringify({ | ||
| 427 | ...capabilitySnapshot(server), | ||
| 428 | resumeEnabled: !supportsManagedElicitation(server), | ||
| 429 | }), | ||
| 430 | ); | ||
| 431 | debugLog("tool.visibility", { | ||
| 432 | clientCapabilities: server.server.getClientCapabilities() ?? null, | ||
| 433 | elicitationSupport: getElicitationSupport(server), | ||
| 434 | managedElicitation: supportsManagedElicitation(server), | ||
| 435 | resumeEnabled: !supportsManagedElicitation(server), | ||
| 436 | }); | ||
| 334 | }; | 437 | }; |
| 335 | 438 | ||
| 336 | yield* Effect.sync(() => { | 439 | yield* Effect.sync(() => { |