@agent-native/dispatch 0.40.40 → 0.40.41-beta-dispatch-embed-01a15dc9-20261027050558

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/dist/actions/create_embed_session.js +1 -0
  3. package/dist/actions/create_embed_session.js.map +1 -1
  4. package/dist/actions/delete-destination.d.ts +12 -12
  5. package/dist/actions/provider-api-register.d.ts +13 -13
  6. package/dist/components/layout/Layout.d.ts.map +1 -1
  7. package/dist/components/layout/Layout.js +8 -11
  8. package/dist/components/layout/Layout.js.map +1 -1
  9. package/dist/components/workspace-app-host.d.ts.map +1 -1
  10. package/dist/components/workspace-app-host.js +23 -10
  11. package/dist/components/workspace-app-host.js.map +1 -1
  12. package/dist/lib/workspace-apps.d.ts +2 -2
  13. package/dist/lib/workspace-apps.d.ts.map +1 -1
  14. package/dist/lib/workspace-apps.js +35 -5
  15. package/dist/lib/workspace-apps.js.map +1 -1
  16. package/dist/server/lib/app-creation-store.d.ts.map +1 -1
  17. package/dist/server/lib/app-creation-store.js +173 -48
  18. package/dist/server/lib/app-creation-store.js.map +1 -1
  19. package/dist/server/lib/community-apps.js +1 -1
  20. package/dist/server/lib/community-apps.js.map +1 -1
  21. package/dist/server/lib/mcp-gateway.d.ts +1 -0
  22. package/dist/server/lib/mcp-gateway.d.ts.map +1 -1
  23. package/dist/server/lib/mcp-gateway.js +170 -38
  24. package/dist/server/lib/mcp-gateway.js.map +1 -1
  25. package/dist/server/lib/provider-api.d.ts +2 -2
  26. package/dist/shared/workspace-app-route.d.ts +2 -0
  27. package/dist/shared/workspace-app-route.d.ts.map +1 -0
  28. package/dist/shared/workspace-app-route.js +4 -0
  29. package/dist/shared/workspace-app-route.js.map +1 -0
  30. package/package.json +4 -4
  31. package/src/actions/create_embed_session.ts +1 -0
  32. package/src/actions/index.spec.ts +3 -0
  33. package/src/components/layout/Layout.spec.tsx +39 -9
  34. package/src/components/layout/Layout.tsx +19 -40
  35. package/src/components/workspace-app-host.spec.tsx +88 -1
  36. package/src/components/workspace-app-host.tsx +30 -9
  37. package/src/lib/workspace-apps.spec.ts +64 -5
  38. package/src/lib/workspace-apps.ts +36 -6
  39. package/src/routes/pages/apps.$appId.spec.tsx +21 -3
  40. package/src/server/lib/app-creation-store.spec.ts +502 -11
  41. package/src/server/lib/app-creation-store.ts +198 -50
  42. package/src/server/lib/community-apps.ts +1 -1
  43. package/src/server/lib/mcp-gateway.spec.ts +396 -9
  44. package/src/server/lib/mcp-gateway.ts +209 -45
  45. package/src/shared/workspace-app-route.ts +3 -0
@@ -18,12 +18,14 @@ const mocks = vi.hoisted(() => ({
18
18
  return `/_agent-native/embed/start?ticket=${encodeURIComponent(ticket)}`;
19
19
  }),
20
20
  managerStart: vi.fn(),
21
+ managerGetConnectErrorStatus: vi.fn(),
21
22
  managerStop: vi.fn(),
22
23
  managerCallTool: vi.fn(),
23
24
  managerConstructor: vi.fn(),
24
25
  a2aConstructor: vi.fn(),
25
26
  a2aSend: vi.fn(),
26
27
  a2aGetTask: vi.fn(),
28
+ signA2AOrgMemberDelegationToken: vi.fn(),
27
29
  signA2AOrganizationToken: vi.fn(),
28
30
  signA2AToken: vi.fn(),
29
31
  canonicalA2AAudience: vi.fn((url: string) => url.replace(/\/+$/, "")),
@@ -88,6 +90,7 @@ vi.mock("@agent-native/core/a2a", async (importOriginal) => {
88
90
  }
89
91
  },
90
92
  signA2AOrganizationToken: mocks.signA2AOrganizationToken,
93
+ signA2AOrgMemberDelegationToken: mocks.signA2AOrgMemberDelegationToken,
91
94
  signA2AToken: mocks.signA2AToken,
92
95
  getGlobalA2ASecret: () => process.env.A2A_SECRET?.trim(),
93
96
  canonicalA2AAudience: mocks.canonicalA2AAudience,
@@ -111,6 +114,10 @@ vi.mock("@agent-native/core/mcp-client", () => ({
111
114
  return mocks.managerStart();
112
115
  }
113
116
 
117
+ getConnectErrorStatus(serverId: string) {
118
+ return mocks.managerGetConnectErrorStatus(serverId);
119
+ }
120
+
114
121
  stop() {
115
122
  return mocks.managerStop();
116
123
  }
@@ -165,6 +172,7 @@ beforeEach(() => {
165
172
  mocks.a2aSend.mockReset();
166
173
  mocks.a2aGetTask.mockReset();
167
174
  mocks.signA2AOrganizationToken.mockReset();
175
+ mocks.signA2AOrgMemberDelegationToken.mockReset();
168
176
  mocks.signA2AToken.mockReset();
169
177
  mocks.discoverAgents.mockResolvedValue([analyticsAgent]);
170
178
  mocks.getBuiltinAgents.mockReturnValue([]);
@@ -177,6 +185,7 @@ beforeEach(() => {
177
185
  expiresAt: 12345,
178
186
  });
179
187
  mocks.managerStart.mockResolvedValue(undefined);
188
+ mocks.managerGetConnectErrorStatus.mockReturnValue(undefined);
180
189
  mocks.managerStop.mockResolvedValue(undefined);
181
190
  mocks.managerCallTool.mockResolvedValue({
182
191
  structuredContent: {
@@ -194,9 +203,11 @@ beforeEach(() => {
194
203
  },
195
204
  });
196
205
  mocks.signA2AToken.mockResolvedValue("signed-token");
206
+ mocks.signA2AOrgMemberDelegationToken.mockResolvedValue("org-member-token");
197
207
  mocks.signA2AOrganizationToken.mockResolvedValue("signed-token");
198
208
  mocks.getOrgA2ASecret.mockResolvedValue(null);
199
209
  mocks.getOrgDomain.mockResolvedValue(null);
210
+ vi.stubEnv("A2A_SECRET", "");
200
211
  mocks.isFeatureFlagEnabled.mockResolvedValue(true);
201
212
  });
202
213
 
@@ -1575,6 +1586,228 @@ describe("createGrantedDispatchMcpEmbedSession", () => {
1575
1586
  expect(mocks.managerConstructor).toHaveBeenCalled();
1576
1587
  });
1577
1588
 
1589
+ it.each([
1590
+ [
1591
+ "workspace-sso",
1592
+ "gateway mount path",
1593
+ "https://beta.agent-workspace.builder.io/dispatch",
1594
+ "",
1595
+ ],
1596
+ [
1597
+ "dispatch-grant",
1598
+ "gateway mount path",
1599
+ "https://beta.agent-workspace.builder.io/dispatch",
1600
+ "",
1601
+ ],
1602
+ [
1603
+ "workspace-sso",
1604
+ "APP_BASE_PATH",
1605
+ "https://beta.agent-workspace.builder.io",
1606
+ "/dispatch",
1607
+ ],
1608
+ [
1609
+ "dispatch-grant",
1610
+ "APP_BASE_PATH",
1611
+ "https://beta.agent-workspace.builder.io",
1612
+ "/dispatch",
1613
+ ],
1614
+ ] as const)(
1615
+ "uses the mounted Workspace MCP endpoint and audience for %s embeds with %s",
1616
+ async (route, _mountConfig, gatewayUrl, appBasePath) => {
1617
+ vi.stubEnv("A2A_SECRET", "test-a2a-secret");
1618
+ vi.stubEnv("WORKSPACE_GATEWAY_URL", gatewayUrl);
1619
+ vi.stubEnv("APP_BASE_PATH", appBasePath);
1620
+ vi.stubEnv("VITE_APP_BASE_PATH", "");
1621
+ vi.stubEnv(
1622
+ "IDENTITY_SSO_APP_REGISTRY_JSON",
1623
+ JSON.stringify([
1624
+ {
1625
+ appId: "customer-ticket-triage",
1626
+ clientId: "workspace-client",
1627
+ origin: "https://beta.agent-workspace.builder.io",
1628
+ callbackPath: "/_agent-native/identity/callback",
1629
+ capabilities: ["identity-sso"],
1630
+ },
1631
+ ]),
1632
+ );
1633
+ const workspaceApp = {
1634
+ id: "customer-ticket-triage",
1635
+ name: "Customer Ticket Triage",
1636
+ description: "Triage customer tickets",
1637
+ path: "/customer-ticket-triage",
1638
+ url: "https://beta.agent-workspace.builder.io/dispatch/apps/customer-ticket-triage",
1639
+ isDispatch: false,
1640
+ };
1641
+ mocks.listWorkspaceApps.mockResolvedValue([workspaceApp]);
1642
+ mocks.discoverAgents.mockResolvedValue([
1643
+ {
1644
+ id: workspaceApp.id,
1645
+ name: workspaceApp.name,
1646
+ description: workspaceApp.description,
1647
+ url: workspaceApp.url,
1648
+ color: "#14B8A6",
1649
+ },
1650
+ ]);
1651
+ mocks.managerCallTool.mockResolvedValueOnce({
1652
+ structuredContent: {
1653
+ startUrl:
1654
+ "https://beta.agent-workspace.builder.io/dispatch/_agent-native/embed/start?ticket=remote",
1655
+ },
1656
+ });
1657
+
1658
+ const result = await runWithRequestContext(
1659
+ {
1660
+ userEmail: "owner@example.test",
1661
+ requestOrigin: "https://beta.dispatch.agent-native.com",
1662
+ },
1663
+ () =>
1664
+ route === "workspace-sso"
1665
+ ? createWorkspaceSsoEmbedSession({
1666
+ app: workspaceApp.id,
1667
+ path: "/triage",
1668
+ chrome: "minimal",
1669
+ })
1670
+ : createGrantedDispatchMcpEmbedSession({
1671
+ app: workspaceApp.id,
1672
+ path: "/triage",
1673
+ chrome: "minimal",
1674
+ }),
1675
+ );
1676
+
1677
+ expect(mocks.managerConstructor).toHaveBeenCalledWith(
1678
+ expect.objectContaining({
1679
+ servers: {
1680
+ target: expect.objectContaining({
1681
+ url: "https://beta.agent-workspace.builder.io/dispatch/mcp",
1682
+ headers: { Authorization: "Bearer signed-token" },
1683
+ }),
1684
+ },
1685
+ }),
1686
+ );
1687
+ expect(mocks.signA2AToken).toHaveBeenCalledWith(
1688
+ "owner@example.test",
1689
+ undefined,
1690
+ undefined,
1691
+ expect.objectContaining({
1692
+ audience: "https://beta.agent-workspace.builder.io/dispatch/mcp",
1693
+ preferGlobalSecret: true,
1694
+ }),
1695
+ );
1696
+ expect(mocks.managerCallTool).toHaveBeenCalledWith(
1697
+ "mcp__target__create_embed_session",
1698
+ {
1699
+ url: "https://beta.agent-workspace.builder.io/dispatch/apps/customer-ticket-triage/triage",
1700
+ chrome: "minimal",
1701
+ },
1702
+ );
1703
+ expect(result.startUrl).toBe(
1704
+ "https://beta.agent-workspace.builder.io/dispatch/_agent-native/embed/start?ticket=remote",
1705
+ );
1706
+
1707
+ mocks.managerCallTool.mockResolvedValueOnce({
1708
+ structuredContent: {
1709
+ startUrl:
1710
+ "https://beta.agent-workspace.builder.io/dispatch/apps/customer-ticket-triage/_agent-native/embed/start?ticket=sibling",
1711
+ },
1712
+ });
1713
+ await expect(
1714
+ runWithRequestContext(
1715
+ {
1716
+ userEmail: "owner@example.test",
1717
+ requestOrigin: "https://beta.dispatch.agent-native.com",
1718
+ },
1719
+ () =>
1720
+ route === "workspace-sso"
1721
+ ? createWorkspaceSsoEmbedSession({
1722
+ app: workspaceApp.id,
1723
+ path: "/",
1724
+ chrome: "minimal",
1725
+ })
1726
+ : createGrantedDispatchMcpEmbedSession({
1727
+ app: workspaceApp.id,
1728
+ path: "/",
1729
+ chrome: "minimal",
1730
+ }),
1731
+ ),
1732
+ ).rejects.toThrow(/invalid embed start URL/);
1733
+ },
1734
+ );
1735
+
1736
+ it("does not fall back when the configured Workspace gateway URL is invalid", async () => {
1737
+ vi.stubEnv("WORKSPACE_GATEWAY_URL", "not-a-url");
1738
+ mocks.listWorkspaceApps.mockResolvedValue([
1739
+ {
1740
+ id: "customer-ticket-triage",
1741
+ name: "Customer Ticket Triage",
1742
+ description: "Triage customer tickets",
1743
+ path: "/customer-ticket-triage",
1744
+ url: "https://beta.agent-workspace.builder.io/dispatch/apps/customer-ticket-triage",
1745
+ isDispatch: false,
1746
+ },
1747
+ ]);
1748
+
1749
+ await expect(
1750
+ runWithRequestContext(
1751
+ {
1752
+ userEmail: "owner@example.test",
1753
+ requestOrigin: "https://beta.dispatch.agent-native.com",
1754
+ },
1755
+ () =>
1756
+ createWorkspaceSsoEmbedSession({
1757
+ app: "customer-ticket-triage",
1758
+ path: "/",
1759
+ chrome: "minimal",
1760
+ }),
1761
+ ),
1762
+ ).rejects.toThrow();
1763
+ expect(mocks.managerConstructor).not.toHaveBeenCalled();
1764
+ });
1765
+
1766
+ it("rejects unsupported Workspace gateway schemes before beta projection", async () => {
1767
+ vi.stubEnv("A2A_SECRET", "test-a2a-secret");
1768
+ vi.stubEnv(
1769
+ "WORKSPACE_GATEWAY_URL",
1770
+ "ftp://agent-workspace.builder.io/dispatch",
1771
+ );
1772
+ vi.stubEnv("APP_BASE_PATH", "");
1773
+ vi.stubEnv("VITE_APP_BASE_PATH", "");
1774
+ const workspaceApp = {
1775
+ id: "customer-ticket-triage",
1776
+ name: "Customer Ticket Triage",
1777
+ description: "Triage customer tickets",
1778
+ path: "/customer-ticket-triage",
1779
+ url: "https://beta.agent-workspace.builder.io/dispatch/apps/customer-ticket-triage",
1780
+ isDispatch: false,
1781
+ };
1782
+ mocks.listWorkspaceApps.mockResolvedValue([workspaceApp]);
1783
+ mocks.discoverAgents.mockResolvedValue([
1784
+ {
1785
+ id: workspaceApp.id,
1786
+ name: workspaceApp.name,
1787
+ description: workspaceApp.description,
1788
+ url: workspaceApp.url,
1789
+ color: "#14B8A6",
1790
+ },
1791
+ ]);
1792
+
1793
+ await expect(
1794
+ runWithRequestContext(
1795
+ {
1796
+ userEmail: "owner@example.test",
1797
+ requestOrigin: "https://beta.dispatch.agent-native.com",
1798
+ },
1799
+ () =>
1800
+ createGrantedDispatchMcpEmbedSession({
1801
+ app: workspaceApp.id,
1802
+ path: "/",
1803
+ chrome: "minimal",
1804
+ }),
1805
+ ),
1806
+ ).rejects.toThrow("WORKSPACE_GATEWAY_URL must use http or https.");
1807
+ expect(mocks.signA2AToken).not.toHaveBeenCalled();
1808
+ expect(mocks.managerConstructor).not.toHaveBeenCalled();
1809
+ });
1810
+
1578
1811
  it("rejects beta workspace SSO apps on the production lane", async () => {
1579
1812
  mocks.discoverAgents.mockResolvedValue([]);
1580
1813
  mocks.listWorkspaceApps.mockResolvedValue([
@@ -2015,7 +2248,9 @@ describe("createGrantedDispatchMcpEmbedSession", () => {
2015
2248
  });
2016
2249
  });
2017
2250
 
2018
- it("binds the authenticated MCP caller token to the org ID", async () => {
2251
+ it("does not send the source app's local org ID to the target", async () => {
2252
+ mocks.getOrgDomain.mockResolvedValue("builder.io");
2253
+
2019
2254
  await runWithRequestContext(
2020
2255
  {
2021
2256
  userEmail: "owner@example.test",
@@ -2037,11 +2272,10 @@ describe("createGrantedDispatchMcpEmbedSession", () => {
2037
2272
  expiresIn: "5m",
2038
2273
  audience: "http://localhost:8086/mcp",
2039
2274
  preferGlobalSecret: true,
2040
- extraClaims: { org_id: "org-1" },
2041
2275
  },
2042
2276
  );
2043
2277
  expect(mocks.signA2AOrganizationToken).not.toHaveBeenCalled();
2044
- expect(mocks.getOrgDomain).not.toHaveBeenCalled();
2278
+ expect(mocks.getOrgDomain).toHaveBeenCalledWith("org-1");
2045
2279
  expect(mocks.getOrgA2ASecret).not.toHaveBeenCalled();
2046
2280
  expect(mocks.managerConstructor).toHaveBeenCalledWith(
2047
2281
  expect.objectContaining({
@@ -2054,11 +2288,139 @@ describe("createGrantedDispatchMcpEmbedSession", () => {
2054
2288
  );
2055
2289
  });
2056
2290
 
2057
- it("requires A2A_SECRET when an organization-only credential cannot assert user identity", async () => {
2291
+ it("uses a user delegation token when only the org secret is configured", async () => {
2058
2292
  vi.stubEnv("A2A_SECRET", "");
2059
2293
  mocks.getOrgDomain.mockResolvedValue("builder.io");
2060
2294
  mocks.getOrgA2ASecret.mockResolvedValue("org-specific-secret");
2061
2295
 
2296
+ const result = await runWithRequestContext(
2297
+ {
2298
+ userEmail: "owner@example.test",
2299
+ orgId: "org-1",
2300
+ requestOrigin: "http://localhost:8092",
2301
+ },
2302
+ () =>
2303
+ createGrantedDispatchMcpEmbedSession({
2304
+ app: "analytics",
2305
+ path: "/dashboards",
2306
+ }),
2307
+ );
2308
+
2309
+ expect(mocks.signA2AToken).not.toHaveBeenCalled();
2310
+ expect(mocks.signA2AOrgMemberDelegationToken).toHaveBeenCalledWith(
2311
+ "owner@example.test",
2312
+ "builder.io",
2313
+ "org-specific-secret",
2314
+ "http://localhost:8086/mcp",
2315
+ );
2316
+ expect(mocks.signA2AOrganizationToken).not.toHaveBeenCalled();
2317
+ expect(result.startUrl).toBe(
2318
+ "http://localhost:8086/_agent-native/embed/start?ticket=remote",
2319
+ );
2320
+ expect(mocks.managerConstructor).toHaveBeenCalledWith(
2321
+ expect.objectContaining({
2322
+ servers: {
2323
+ target: expect.objectContaining({
2324
+ headers: { Authorization: "Bearer org-member-token" },
2325
+ }),
2326
+ },
2327
+ }),
2328
+ );
2329
+ });
2330
+
2331
+ it("tries org-member delegation after a deployment-token auth rejection", async () => {
2332
+ mocks.getOrgDomain.mockResolvedValue("builder.io");
2333
+ mocks.getOrgA2ASecret.mockResolvedValue("org-specific-secret");
2334
+ mocks.signA2AToken.mockResolvedValue("deployment-user-token");
2335
+ mocks.signA2AOrgMemberDelegationToken.mockResolvedValue("org-member-token");
2336
+ mocks.managerGetConnectErrorStatus.mockReturnValueOnce(401);
2337
+
2338
+ const result = await runWithRequestContext(
2339
+ {
2340
+ userEmail: "owner@example.test",
2341
+ orgId: "org-1",
2342
+ requestOrigin: "http://localhost:8092",
2343
+ },
2344
+ () =>
2345
+ createGrantedDispatchMcpEmbedSession({
2346
+ app: "analytics",
2347
+ path: "/dashboards",
2348
+ }),
2349
+ );
2350
+
2351
+ expect(result.startUrl).toBe(
2352
+ "http://localhost:8086/_agent-native/embed/start?ticket=remote",
2353
+ );
2354
+ expect(mocks.managerConstructor).toHaveBeenNthCalledWith(
2355
+ 1,
2356
+ expect.objectContaining({
2357
+ servers: {
2358
+ target: expect.objectContaining({
2359
+ headers: { Authorization: "Bearer deployment-user-token" },
2360
+ }),
2361
+ },
2362
+ }),
2363
+ );
2364
+ expect(mocks.managerConstructor).toHaveBeenNthCalledWith(
2365
+ 2,
2366
+ expect.objectContaining({
2367
+ servers: {
2368
+ target: expect.objectContaining({
2369
+ headers: { Authorization: "Bearer org-member-token" },
2370
+ }),
2371
+ },
2372
+ }),
2373
+ );
2374
+ expect(mocks.managerCallTool).toHaveBeenCalledTimes(1);
2375
+ expect(mocks.signA2AOrgMemberDelegationToken).toHaveBeenCalledWith(
2376
+ "owner@example.test",
2377
+ "builder.io",
2378
+ "org-specific-secret",
2379
+ "http://localhost:8086/mcp",
2380
+ );
2381
+ });
2382
+
2383
+ it("uses member delegation when the org secret matches the source deployment secret", async () => {
2384
+ vi.stubEnv("A2A_SECRET", "source-deployment-secret");
2385
+ mocks.getOrgDomain.mockResolvedValue("builder.io");
2386
+ mocks.getOrgA2ASecret.mockResolvedValue("source-deployment-secret");
2387
+ mocks.managerGetConnectErrorStatus.mockReturnValueOnce(401);
2388
+
2389
+ const result = await runWithRequestContext(
2390
+ {
2391
+ userEmail: "owner@example.test",
2392
+ orgId: "org-1",
2393
+ requestOrigin: "http://localhost:8092",
2394
+ },
2395
+ () =>
2396
+ createGrantedDispatchMcpEmbedSession({
2397
+ app: "analytics",
2398
+ path: "/dashboards",
2399
+ }),
2400
+ );
2401
+
2402
+ expect(result.startUrl).toBe(
2403
+ "http://localhost:8086/_agent-native/embed/start?ticket=remote",
2404
+ );
2405
+ expect(mocks.managerConstructor).toHaveBeenCalledTimes(2);
2406
+ expect(mocks.signA2AOrgMemberDelegationToken).toHaveBeenCalledWith(
2407
+ "owner@example.test",
2408
+ "builder.io",
2409
+ "source-deployment-secret",
2410
+ "http://localhost:8086/mcp",
2411
+ );
2412
+ });
2413
+
2414
+ it("does not treat a bare status number in an error message as an auth refusal", async () => {
2415
+ vi.stubEnv("A2A_SECRET", "source-deployment-secret");
2416
+ mocks.getOrgDomain.mockResolvedValue("builder.io");
2417
+ mocks.getOrgA2ASecret.mockResolvedValue("org-specific-secret");
2418
+ mocks.managerStart.mockRejectedValueOnce(
2419
+ new Error(
2420
+ "MCP response body mentions reference 403 from an unrelated request",
2421
+ ),
2422
+ );
2423
+
2062
2424
  await expect(
2063
2425
  runWithRequestContext(
2064
2426
  {
@@ -2072,13 +2434,38 @@ describe("createGrantedDispatchMcpEmbedSession", () => {
2072
2434
  path: "/dashboards",
2073
2435
  }),
2074
2436
  ),
2075
- ).rejects.toThrow(
2076
- /require A2A_SECRET to preserve the authenticated user identity/,
2437
+ ).rejects.toThrow(/reference 403 from an unrelated request/);
2438
+
2439
+ expect(mocks.managerConstructor).toHaveBeenCalledTimes(1);
2440
+ expect(mocks.signA2AOrgMemberDelegationToken).not.toHaveBeenCalled();
2441
+ expect(mocks.getOrgA2ASecret).not.toHaveBeenCalled();
2442
+ });
2443
+
2444
+ it("does not retry an action-level authorization error with member delegation", async () => {
2445
+ mocks.getOrgDomain.mockResolvedValue("builder.io");
2446
+ mocks.getOrgA2ASecret.mockResolvedValue("org-specific-secret");
2447
+ mocks.managerCallTool.mockRejectedValueOnce(
2448
+ Object.assign(new Error("Forbidden"), { status: 403 }),
2077
2449
  );
2078
2450
 
2079
- expect(mocks.signA2AToken).not.toHaveBeenCalled();
2080
- expect(mocks.signA2AOrganizationToken).not.toHaveBeenCalled();
2081
- expect(mocks.managerConstructor).not.toHaveBeenCalled();
2451
+ await expect(
2452
+ runWithRequestContext(
2453
+ {
2454
+ userEmail: "owner@example.test",
2455
+ orgId: "org-1",
2456
+ requestOrigin: "http://localhost:8092",
2457
+ },
2458
+ () =>
2459
+ createGrantedDispatchMcpEmbedSession({
2460
+ app: "analytics",
2461
+ path: "/dashboards",
2462
+ }),
2463
+ ),
2464
+ ).rejects.toThrow("Forbidden");
2465
+
2466
+ expect(mocks.managerConstructor).toHaveBeenCalledTimes(1);
2467
+ expect(mocks.signA2AOrgMemberDelegationToken).not.toHaveBeenCalled();
2468
+ expect(mocks.getOrgA2ASecret).not.toHaveBeenCalled();
2082
2469
  });
2083
2470
 
2084
2471
  it("does not retry an authenticated-caller error with an org principal", async () => {