77//
88// 1. MCP `execute` → `openapi.addSpec` registers the emulated Resend API
99// 2. MCP `execute` → `connections.createHandoff` returns the browser URL
10- // 3. The URL's origin must be THIS deployment (not a hardcoded host)
10+ // 3. The URL's origin must be THIS deployment (not a hardcoded host) AND it
11+ // must carry the bound org's slug (`/<slug>/integrations/…`), so a user in
12+ // several orgs lands in the exact org the agent is scoped to, not whatever
13+ // org the browser happened to last canonicalize onto
1114// 4. Playwright opens it: the Add connection modal must be open with a
1215// credential field, the emulator-minted API key is pasted and submitted
1316// 5. The saved connection is proven live: `execute` sends an email through
@@ -17,6 +20,7 @@ import { randomBytes } from "node:crypto";
1720
1821import { expect } from "@effect/vitest" ;
1922import { Effect } from "effect" ;
23+ import { AccountHttpApi } from "@executor-js/api" ;
2024import { composePluginApi } from "@executor-js/api/server" ;
2125import { connectEmulator } from "@executor-js/emulate" ;
2226import { openApiHttpPlugin } from "@executor-js/plugin-openapi/api" ;
@@ -126,6 +130,13 @@ scenario(
126130 const session = mcp . session ( identity ) ;
127131 const client = yield * makeApiClient ( api , identity ) ;
128132
133+ // The bound org's slug, read from the same account surface the console
134+ // shell reads — the handoff URL must canonicalize onto exactly this.
135+ const accountClient = yield * makeApiClient ( AccountHttpApi , identity ) ;
136+ const me = yield * accountClient . account . me ( ) ;
137+ const orgSlug = me . organization ?. slug ;
138+ expect ( orgSlug , "the bound organization advertises a URL slug" ) . toBeTruthy ( ) ;
139+
129140 yield * runScenario ( {
130141 target,
131142 browser,
@@ -134,6 +145,7 @@ scenario(
134145 integration,
135146 emailSubject,
136147 apiKey,
148+ orgSlug : orgSlug ! ,
137149 } ) . pipe (
138150 // Best-effort cleanup even on failure: drop the created connection(s)
139151 // over MCP, then the integration over the API.
@@ -155,9 +167,11 @@ const runScenario = (input: {
155167 readonly integration : string ;
156168 readonly emailSubject : string ;
157169 readonly apiKey : string ;
170+ readonly orgSlug : string ;
158171} ) =>
159172 Effect . gen ( function * ( ) {
160- const { target, browser, session, identity, integration, emailSubject, apiKey } = input ;
173+ const { target, browser, session, identity, integration, emailSubject, apiKey, orgSlug } =
174+ input ;
161175
162176 // 1. Agent registers the emulated provider over MCP.
163177 const added = yield * executeJson ( session , addSpecCode ( integration ) ) ;
@@ -168,13 +182,16 @@ const runScenario = (input: {
168182 expect ( handoff . ok , `createHandoff succeeded: ${ JSON . stringify ( handoff ) } ` ) . toBe ( true ) ;
169183 const handoffUrl = String ( handoff . url ) ;
170184
171- // 3. The URL must target THIS deployment. (Production returned a URL the
172- // user called "wrong/bad" — pin the contract here.)
185+ // 3. The URL must target THIS deployment AND carry the bound org's slug.
186+ // (Production returned a URL the user called "wrong/bad" — it had no slug,
187+ // so a multi-org user could land in the wrong workspace. Pin both here.)
173188 const parsed = new URL ( handoffUrl ) ;
174189 expect ( parsed . origin , `handoff URL (${ handoffUrl } ) targets this deployment` ) . toBe (
175190 new URL ( target . baseUrl ) . origin ,
176191 ) ;
177- expect ( parsed . pathname ) . toBe ( `/integrations/${ integration } ` ) ;
192+ expect ( parsed . pathname , `handoff URL (${ handoffUrl } ) carries the bound org slug` ) . toBe (
193+ `/${ orgSlug } /integrations/${ integration } ` ,
194+ ) ;
178195 expect ( parsed . searchParams . get ( "addAccount" ) ) . toBe ( "1" ) ;
179196
180197 // 4. The user opens the handoff URL and pastes the credential.
0 commit comments