@opengeni/api-router 2.11.3 → 2.12.3-canary.0

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 (66) hide show
  1. package/dist/app.js +1 -1
  2. package/dist/{chunk-5X3XM6CE.js → chunk-4AQMKVQM.js} +17727 -14427
  3. package/dist/chunk-4AQMKVQM.js.map +1 -0
  4. package/dist/connection-ownership.d.ts +5 -1
  5. package/dist/index.js +3 -1
  6. package/dist/index.js.map +1 -1
  7. package/dist/integrations/atlassian.d.ts +4 -0
  8. package/dist/integrations/connect-authority.d.ts +1 -0
  9. package/dist/integrations/connect-callback-return.d.ts +6 -0
  10. package/dist/integrations/fiken.d.ts +7 -0
  11. package/dist/integrations/github-app-connect.d.ts +47 -0
  12. package/dist/integrations/github-installation-proof.d.ts +3 -0
  13. package/dist/integrations/github-lens-connect.d.ts +153 -0
  14. package/dist/integrations/google-drive.d.ts +4 -0
  15. package/dist/integrations/oauth-client.d.ts +4 -0
  16. package/dist/integrations/oauth-return-path.d.ts +1 -0
  17. package/dist/integrations/personal-github.d.ts +2 -0
  18. package/dist/integrations/provider-oauth.d.ts +11 -1
  19. package/dist/integrations/slack-install.d.ts +20 -0
  20. package/dist/integrations/social-oauth.d.ts +2 -0
  21. package/dist/mcp/scheduled-task-view.d.ts +3 -3
  22. package/dist/routes/api-integrations.d.ts +14 -0
  23. package/dist/routes/connect.d.ts +5 -0
  24. package/dist/routes/external-identity-links.d.ts +6 -0
  25. package/dist/routes/host-mcp-bindings.d.ts +5 -0
  26. package/dist/sandbox/rematerialize.d.ts +1 -1
  27. package/dist/workspace-tool-gateway.d.ts +1 -0
  28. package/package.json +19 -19
  29. package/src/app.ts +22 -2
  30. package/src/connection-ownership.ts +14 -1
  31. package/src/index.ts +2 -0
  32. package/src/integrations/atlassian.ts +143 -39
  33. package/src/integrations/connect-authority.ts +2 -0
  34. package/src/integrations/connect-callback-return.ts +86 -0
  35. package/src/integrations/fiken.ts +222 -40
  36. package/src/integrations/github-app-connect.ts +389 -0
  37. package/src/integrations/github-installation-proof.ts +60 -0
  38. package/src/integrations/github-lens-connect.ts +97 -0
  39. package/src/integrations/google-drive.ts +152 -47
  40. package/src/integrations/oauth-client.ts +180 -84
  41. package/src/integrations/oauth-return-path.ts +18 -0
  42. package/src/integrations/personal-github.ts +146 -52
  43. package/src/integrations/provider-oauth.ts +132 -21
  44. package/src/integrations/slack-install.ts +83 -0
  45. package/src/integrations/social-oauth.ts +142 -1
  46. package/src/mcp/documents.ts +1 -1
  47. package/src/mcp/server.ts +64 -17
  48. package/src/routes/api-integrations.ts +74 -64
  49. package/src/routes/connect.ts +1869 -0
  50. package/src/routes/connections.ts +263 -221
  51. package/src/routes/documents.ts +2 -2
  52. package/src/routes/external-identity-links.ts +200 -0
  53. package/src/routes/github.ts +39 -64
  54. package/src/routes/host-mcp-bindings.ts +122 -0
  55. package/src/routes/organization-memberships.ts +23 -0
  56. package/src/routes/organization-sessions.ts +2 -2
  57. package/src/routes/personal-github.ts +8 -1
  58. package/src/routes/pr-review-github.ts +30 -3
  59. package/src/routes/scheduled-tasks.ts +29 -4
  60. package/src/routes/sessions.ts +61 -32
  61. package/src/routes/social.ts +5 -1
  62. package/src/routes/supergrok.ts +90 -3
  63. package/src/routes/workspaces.ts +16 -0
  64. package/src/sandbox/rematerialize.ts +33 -1
  65. package/src/workspace-tool-gateway.ts +136 -24
  66. package/dist/chunk-5X3XM6CE.js.map +0 -1
@@ -21,6 +21,7 @@ import {
21
21
  ToolGatewayApprovalResponse,
22
22
  ToolGatewayDeclarationsResponse,
23
23
  type AccessGrant,
24
+ type McpGatewayCredentialAuthority,
24
25
  type ToolGatewayCatalog,
25
26
  type ToolGatewayIdentity,
26
27
  type ToolRef,
@@ -28,6 +29,9 @@ import {
28
29
  import {
29
30
  buildApiIntegrationMcpServers,
30
31
  hasPermission,
32
+ externalActorContinuationForAuthorization,
33
+ isVerifiedOrganizationServiceAuthorization,
34
+ externalContinuationCommitAuthorizer,
31
35
  requireResolvedAccessGrantAuthorization,
32
36
  resolveCodexAppsCredentialIdForRun,
33
37
  resolveWorkspaceCatalogSettings,
@@ -38,6 +42,10 @@ import {
38
42
  import {
39
43
  buildCodexTokenResolver,
40
44
  buildConnectionTokenResolver,
45
+ buildHostGatewayConnectionTokenResolver,
46
+ lockActiveExternalOrganizationKey,
47
+ withAccountRls,
48
+ requireWorkspace,
41
49
  withCodexAppsRequestAuthorization,
42
50
  consumeToolGatewayApproval,
43
51
  getWorkspaceArtifact,
@@ -80,6 +88,7 @@ export type PreparedWorkspaceToolGateway = Pick<
80
88
  > & {
81
89
  toolGateway: NonNullable<PreparedWorkspaceToolGatewayTools["toolGateway"]>;
82
90
  toolGatewayCatalog: NonNullable<PreparedWorkspaceToolGatewayTools["toolGatewayCatalog"]>;
91
+ reauthorize?: () => Promise<void>;
83
92
  };
84
93
 
85
94
  type WorkspaceSiteToolContext = {
@@ -118,8 +127,19 @@ export function requireWorkspaceToolGatewayAuthorization(
118
127
  authorization,
119
128
  authorization.grant.workspaceId,
120
129
  );
130
+ if (isVerifiedOrganizationServiceAuthorization(authorization)) {
131
+ if (grantUsesAttemptScopedMcp(grant))
132
+ throw new HTTPException(403, {
133
+ message: "service tool access cannot carry attempt authority",
134
+ });
135
+ return grant;
136
+ }
121
137
  requireWorkspaceToolGatewayGrant(grant);
122
- if (!authorization.canonicalManagedHumanSession && !authorization.canonicalLocalHumanSession) {
138
+ if (
139
+ !authorization.canonicalManagedHumanSession &&
140
+ !authorization.canonicalLocalHumanSession &&
141
+ !externalActorContinuationForAuthorization(authorization)
142
+ ) {
123
143
  throw new HTTPException(403, { message: "current-human tool access required" });
124
144
  }
125
145
  return grant;
@@ -130,7 +150,61 @@ export async function prepareWorkspaceToolGateway(
130
150
  authorization: AccessGrantAuthorization,
131
151
  ): Promise<PreparedWorkspaceToolGateway> {
132
152
  const grant = requireWorkspaceToolGatewayAuthorization(authorization);
133
- return await prepareWorkspaceToolGatewayForGrant(routeDeps, grant);
153
+ const external = externalActorContinuationForAuthorization(authorization);
154
+ const reauthorizeExternal = externalContinuationCommitAuthorizer(authorization);
155
+ const service = isVerifiedOrganizationServiceAuthorization(authorization);
156
+ const scope = {
157
+ accountId: grant.accountId,
158
+ workspaceId: grant.workspaceId,
159
+ subjectId: grant.subjectId,
160
+ };
161
+ const permissions = [...grant.permissions];
162
+ const reauthorize =
163
+ external || service
164
+ ? async () => {
165
+ await withAccountRls(routeDeps.db, scope.accountId, async (tx) => {
166
+ if (reauthorizeExternal) await reauthorizeExternal(tx);
167
+ else {
168
+ const live = await lockActiveExternalOrganizationKey(
169
+ tx,
170
+ scope.accountId,
171
+ scope.subjectId.slice("api_key:".length),
172
+ );
173
+ const workspace = await requireWorkspace(tx, scope.workspaceId);
174
+ if (
175
+ !live ||
176
+ permissions.some((permission) => !hasPermission(live, permission)) ||
177
+ workspace.accountId !== scope.accountId ||
178
+ workspace.kind !== "shared"
179
+ )
180
+ throw new HTTPException(403, { message: "service gateway authority changed" });
181
+ }
182
+ });
183
+ }
184
+ : undefined;
185
+ await reauthorize?.();
186
+ const prepared = await prepareWorkspaceToolGatewayForGrantInternal(
187
+ routeDeps,
188
+ grant,
189
+ undefined,
190
+ reauthorize
191
+ ? {
192
+ authority: {
193
+ kind: external ? "external_user" : "organization_service",
194
+ subjectId: scope.subjectId,
195
+ permissions,
196
+ },
197
+ reauthorize,
198
+ }
199
+ : undefined,
200
+ );
201
+ try {
202
+ await reauthorize?.();
203
+ } catch (error) {
204
+ await prepared.close();
205
+ throw error;
206
+ }
207
+ return { ...prepared, ...(reauthorize ? { reauthorize } : {}) };
134
208
  }
135
209
 
136
210
  export async function prepareMcpOAuthWorkspaceToolGateway(
@@ -148,6 +222,15 @@ export async function prepareWorkspaceToolGatewayForGrant(
148
222
  routeDeps: ApiRouteDeps,
149
223
  grant: AccessGrant,
150
224
  allowedIdentities?: readonly { serverId: string; toolName: string }[],
225
+ ): Promise<PreparedWorkspaceToolGateway> {
226
+ return await prepareWorkspaceToolGatewayForGrantInternal(routeDeps, grant, allowedIdentities);
227
+ }
228
+
229
+ async function prepareWorkspaceToolGatewayForGrantInternal(
230
+ routeDeps: ApiRouteDeps,
231
+ grant: AccessGrant,
232
+ allowedIdentities?: readonly { serverId: string; toolName: string }[],
233
+ hostContext?: { authority: McpGatewayCredentialAuthority; reauthorize: () => Promise<void> },
151
234
  ): Promise<PreparedWorkspaceToolGateway> {
152
235
  const catalogSourceSettings = routeDeps.catalogSourceSettings ?? routeDeps.settings;
153
236
  const resolvedCatalog = await resolveWorkspaceCatalogSettings(
@@ -173,7 +256,24 @@ export async function prepareWorkspaceToolGatewayForGrant(
173
256
  const gatewaySettings = workspaceToolGatewaySettingsForGrant(settings, grant, allowedIdentities);
174
257
  const gatewayServerIds = new Set(gatewaySettings.mcpServers.map((server) => server.id));
175
258
  const deps = { ...routeDeps, catalogSourceSettings, settings: gatewaySettings };
176
- const resolveConnection = buildConnectionTokenResolver(routeDeps.db, gatewaySettings);
259
+ const nativeResolveConnection = buildConnectionTokenResolver(routeDeps.db, gatewaySettings);
260
+ const hostPort = routeDeps.connectionCredentials?.mcpGatewayCredentials;
261
+ const hostResolveConnection =
262
+ hostContext && hostPort
263
+ ? buildHostGatewayConnectionTokenResolver(
264
+ hostPort,
265
+ {
266
+ accountId: grant.accountId,
267
+ workspaceId: grant.workspaceId,
268
+ authority: hostContext.authority,
269
+ },
270
+ hostContext.reauthorize,
271
+ )
272
+ : undefined;
273
+ const resolveConnection: typeof nativeResolveConnection = async (input) =>
274
+ input.connectionRef.authoritySource === "host" && hostResolveConnection
275
+ ? await hostResolveConnection(input)
276
+ : await nativeResolveConnection(input);
177
277
  const resolveCredential = async (
178
278
  input: ResolveConnectionCredentialInput,
179
279
  ): Promise<ResolveConnectionCredentialResult> =>
@@ -208,7 +308,8 @@ export async function prepareWorkspaceToolGatewayForGrant(
208
308
  routeDeps.db,
209
309
  grant.workspaceId,
210
310
  grant.metadata["sessionId"],
211
- )) ?? { mode: "off" as const, endUserSubjectId: null, rootSessionId: null })
311
+ grant.metadata,
312
+ )) ?? { mode: "off" as const, userSubjectId: null, rootSessionId: null })
212
313
  : null,
213
314
  },
214
315
  ),
@@ -355,24 +456,27 @@ export function buildWorkspaceToolGatewayMcpServer(
355
456
  { name: "opengeni-tool-gateway", version: "1.0.0" },
356
457
  { capabilities: { tools: {} } },
357
458
  );
358
- server.setRequestHandler(ListToolsRequestSchema, async () => ({
359
- tools: callableEntries.map((entry) => ({
360
- name: entry.modelName,
361
- ...(entry.title ? { title: entry.title } : {}),
362
- ...(entry.description ? { description: entry.description } : {}),
363
- inputSchema: entry.inputSchema,
364
- ...(entry.outputSchema ? { outputSchema: entry.outputSchema } : {}),
365
- ...(entry.annotations ? { annotations: entry.annotations } : {}),
366
- ...(entry.icons ? { icons: entry.icons } : {}),
367
- _meta: {
368
- "opengeni/identity": entry.identity,
369
- "opengeni/path": entry.codemodePath,
370
- "opengeni/source": entry.source,
371
- "opengeni/approval": entry.approval,
372
- "opengeni/catalogDigest": prepared.toolGatewayCatalog.digest,
373
- },
374
- })),
375
- }));
459
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
460
+ await prepared.reauthorize?.();
461
+ return {
462
+ tools: callableEntries.map((entry) => ({
463
+ name: entry.modelName,
464
+ ...(entry.title ? { title: entry.title } : {}),
465
+ ...(entry.description ? { description: entry.description } : {}),
466
+ inputSchema: entry.inputSchema,
467
+ ...(entry.outputSchema ? { outputSchema: entry.outputSchema } : {}),
468
+ ...(entry.annotations ? { annotations: entry.annotations } : {}),
469
+ ...(entry.icons ? { icons: entry.icons } : {}),
470
+ _meta: {
471
+ "opengeni/identity": entry.identity,
472
+ "opengeni/path": entry.codemodePath,
473
+ "opengeni/source": entry.source,
474
+ "opengeni/approval": entry.approval,
475
+ "opengeni/catalogDigest": prepared.toolGatewayCatalog.digest,
476
+ },
477
+ })),
478
+ };
479
+ });
376
480
  server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
377
481
  const entry = callableEntries.find((candidate) => candidate.modelName === request.params.name);
378
482
  if (!entry) throw new ToolGatewayToolNotFoundError();
@@ -382,7 +486,8 @@ export function buildWorkspaceToolGatewayMcpServer(
382
486
  source: entry.source,
383
487
  });
384
488
  try {
385
- const result = (await prepared.toolGateway.call(
489
+ await prepared.reauthorize?.();
490
+ const call = await prepared.toolGateway.prepareCall(
386
491
  {
387
492
  operationId: crypto.randomUUID(),
388
493
  catalogDigest: prepared.toolGatewayCatalog.digest,
@@ -391,7 +496,9 @@ export function buildWorkspaceToolGatewayMcpServer(
391
496
  caller: { kind: "mcp", subjectId: grant.subjectId },
392
497
  },
393
498
  { signal: extra.signal },
394
- )) as CallToolResult;
499
+ );
500
+ await prepared.reauthorize?.();
501
+ const result = (await call.execute()) as CallToolResult;
395
502
  observation.end(result.isError ? "tool_error" : "ok");
396
503
  return result;
397
504
  } catch (error) {
@@ -433,6 +540,7 @@ export async function callWorkspaceToolGateway(
433
540
  identity: request.identity,
434
541
  }
435
542
  : null;
543
+ await prepared.reauthorize?.();
436
544
  if (siteContext) {
437
545
  if (!db) throw new HTTPException(503, { message: "site_tool_authorization_unavailable" });
438
546
  await authorizeSiteTool(db, grant, siteContext);
@@ -450,6 +558,7 @@ export async function callWorkspaceToolGateway(
450
558
  },
451
559
  { transportMeta },
452
560
  );
561
+ await prepared.reauthorize?.();
453
562
  let approvalConfirmed = false;
454
563
  const approvalRequired = preparedCall.entry.approval === "human";
455
564
  if (approvalRequired && request.approvalToken && db) {
@@ -478,6 +587,7 @@ export async function callWorkspaceToolGateway(
478
587
  siteContext.siteVersionId,
479
588
  )
480
589
  : null;
590
+ await prepared.reauthorize?.();
481
591
  const result = await (origin
482
592
  ? withSiteSessionOrigin(origin, () => preparedCall.execute())
483
593
  : preparedCall.execute());
@@ -504,6 +614,7 @@ export async function approveWorkspaceToolGatewayCall(
504
614
  const request = ToolGatewayApprovalRequest.parse(input);
505
615
  let preparedCall: PreparedToolGatewayCall;
506
616
  try {
617
+ await prepared.reauthorize?.();
507
618
  preparedCall = await prepared.toolGateway.prepareCall(
508
619
  {
509
620
  operationId: request.operationId,
@@ -528,6 +639,7 @@ export async function approveWorkspaceToolGatewayCall(
528
639
  const approvalToken = `ogta_${randomBytes(32).toString("base64url")}`;
529
640
  const expiresAt = new Date(Date.now() + 5 * 60_000);
530
641
  try {
642
+ await prepared.reauthorize?.();
531
643
  await issueApproval(db, {
532
644
  tokenHash: hashOpaqueValue(approvalToken),
533
645
  accountId: grant.accountId,