|
16 | 16 | // (an organization the mirror has never seen). Either is TRANSIENT, not |
17 | 17 | // evidence the org is gone, and must NOT reach the Forbidden/destroy |
18 | 18 | // path below. |
19 | | -// - no org / revoked org -> Forbidden ("No organization in session …", -32001). |
| 19 | +// - no org selected -> Forbidden (connect Executor v2's MCP URL, -32001). |
| 20 | +// - revoked org -> Forbidden ("No organization in session …", -32001). |
20 | 21 | // This requires a POSITIVE determination (the lookup SUCCEEDED and the org |
21 | 22 | // is absent), never a failed lookup. Because authenticate reads the |
22 | 23 | // mcp-session-id header to do the membership check, the envelope's |
@@ -74,6 +75,17 @@ const TOOLKIT_PROTECTED_RESOURCE_METADATA_PATH = `${PROTECTED_RESOURCE_METADATA_ |
74 | 75 |
|
75 | 76 | const NO_ORGANIZATION_MESSAGE = "No organization in session — log in via the web app first"; |
76 | 77 |
|
| 78 | +/** Executor v2's MCP endpoint, the one to connect instead of this server. */ |
| 79 | +const V2_MCP_URL = "https://mcp.executor.sh/mcp"; |
| 80 | + |
| 81 | +// A token that names no organization, on a URL that names none either. New |
| 82 | +// sign-ups happen on Executor v2, so nearly every such account was created by |
| 83 | +// an MCP client's sign-in here and has no v1 organization at all: the fix is |
| 84 | +// to connect v2's MCP server. The rarer account that joined a v1 organization |
| 85 | +// after signing in holds a token minted before that membership; signing in |
| 86 | +// again gives it one that names the organization. |
| 87 | +const NO_ORGANIZATION_SELECTED_MESSAGE = `Executor has moved. This account has no organization on executor.sh/mcp: connect ${V2_MCP_URL} instead. If you belong to an organization on executor.sh, reconnect this server and sign in again to choose it.`; |
| 88 | + |
77 | 89 | // A transient failure during the membership lookup must NOT masquerade as |
78 | 90 | // "org revoked". The lookup reads the local mirror, so in steady state its |
79 | 91 | // only failure is the database; the one path that still asks WorkOS (an |
@@ -201,7 +213,7 @@ export const cloudMcpAuthProviderLayer: Layer.Layer< |
201 | 213 | const organizationSelector = mcpOrganizationFromRequest(request) ?? token.organizationId; |
202 | 214 | if (!organizationSelector) { |
203 | 215 | yield* annotateMcpRequest(request, { token, parseBody }); |
204 | | - return forbidden(NO_ORGANIZATION_MESSAGE, -32001); |
| 216 | + return forbidden(NO_ORGANIZATION_SELECTED_MESSAGE, -32001); |
205 | 217 | } |
206 | 218 |
|
207 | 219 | // Capture success-vs-failure explicitly instead of collapsing both into |
|
0 commit comments