@opengeni/api-router 0.12.5 → 0.12.12
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.
- package/dist/app.js +1 -1
- package/dist/{chunk-3BHOMOSD.js → chunk-5TULDPWX.js} +589 -153
- package/dist/chunk-5TULDPWX.js.map +1 -0
- package/dist/index.js +10 -4
- package/dist/index.js.map +1 -1
- package/package.json +10 -10
- package/src/app.ts +12 -0
- package/src/github-access.ts +1 -0
- package/src/index.ts +8 -3
- package/src/mcp/files.ts +77 -0
- package/src/mcp/server.ts +196 -86
- package/src/routes/files.ts +11 -0
- package/src/routes/github.ts +325 -20
- package/src/routes/scheduled-tasks.ts +1 -0
- package/src/routes/sessions.ts +12 -23
- package/src/routes/workspace-capture.ts +108 -8
- package/dist/chunk-3BHOMOSD.js.map +0 -1
package/src/mcp/server.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
CreateScheduledTaskRequest,
|
|
3
|
+
DEFAULT_FIRST_PARTY_MCP_TOOLS,
|
|
4
|
+
FIRST_PARTY_MCP_TOOL_NAMES,
|
|
3
5
|
defaultRepositoryMountPath,
|
|
4
6
|
SESSION_EVENT_RAW_DELTA_TYPES,
|
|
5
7
|
SessionEventLatestClass,
|
|
@@ -15,9 +17,11 @@ import {
|
|
|
15
17
|
VariableSetVariableName,
|
|
16
18
|
type AccessGrant,
|
|
17
19
|
type GitHubRepository,
|
|
20
|
+
type FirstPartyMcpToolName,
|
|
18
21
|
type Permission,
|
|
19
22
|
type ResourceRef,
|
|
20
23
|
type SessionAuthorizationOperation,
|
|
24
|
+
type SessionAuthorizationSurface,
|
|
21
25
|
type Session,
|
|
22
26
|
UpdateScheduledTaskRequest,
|
|
23
27
|
} from "@opengeni/contracts";
|
|
@@ -52,7 +56,6 @@ import {
|
|
|
52
56
|
MEMORY_CORRECT_TOOL_DESCRIPTION,
|
|
53
57
|
MEMORY_SAVE_TOOL_DESCRIPTION,
|
|
54
58
|
MEMORY_SEARCH_TOOL_DESCRIPTION,
|
|
55
|
-
requireFile,
|
|
56
59
|
requireScheduledTask,
|
|
57
60
|
requireSession,
|
|
58
61
|
saveWorkspaceMemory,
|
|
@@ -73,7 +76,13 @@ import {
|
|
|
73
76
|
GitHubAppConfigurationError,
|
|
74
77
|
githubAppMissingSettings,
|
|
75
78
|
} from "@opengeni/github";
|
|
76
|
-
import {
|
|
79
|
+
import {
|
|
80
|
+
McpServer,
|
|
81
|
+
type RegisteredTool,
|
|
82
|
+
type ToolCallback,
|
|
83
|
+
} from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
84
|
+
import type { AnySchema, ZodRawShapeCompat } from "@modelcontextprotocol/sdk/server/zod-compat.js";
|
|
85
|
+
import type { ToolAnnotations } from "@modelcontextprotocol/sdk/types.js";
|
|
77
86
|
import * as z4 from "zod/v4";
|
|
78
87
|
import {
|
|
79
88
|
hasPermission,
|
|
@@ -157,15 +166,153 @@ export type McpServerOptions = {
|
|
|
157
166
|
workspaceMemoryEnabled?: boolean | undefined;
|
|
158
167
|
};
|
|
159
168
|
|
|
169
|
+
type FirstPartyToolAuthorization = {
|
|
170
|
+
sessionRequired?: true;
|
|
171
|
+
allOf?: readonly Permission[];
|
|
172
|
+
anyOf?: readonly Permission[];
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Authorization is deliberately complete and separate from visibility. The
|
|
177
|
+
* `satisfies Record` check makes every catalog addition choose an explicit
|
|
178
|
+
* registration predicate before it can compile.
|
|
179
|
+
*/
|
|
180
|
+
const FIRST_PARTY_TOOL_AUTHORIZATION = {
|
|
181
|
+
set_session_title: { sessionRequired: true, allOf: ["sessions:control"] },
|
|
182
|
+
goal_set: { sessionRequired: true, allOf: ["goals:manage"] },
|
|
183
|
+
goal_update: { sessionRequired: true, allOf: ["goals:manage"] },
|
|
184
|
+
goal_complete: { sessionRequired: true, allOf: ["goals:manage"] },
|
|
185
|
+
goal_pause: { sessionRequired: true, allOf: ["goals:manage"] },
|
|
186
|
+
memory_search: { sessionRequired: true, allOf: ["documents:search"] },
|
|
187
|
+
memory_save: { sessionRequired: true, allOf: ["documents:search"] },
|
|
188
|
+
memory_correct: { sessionRequired: true, allOf: ["documents:search"] },
|
|
189
|
+
sandboxes_list: { sessionRequired: true, allOf: ["sessions:read"] },
|
|
190
|
+
sandbox_attach: { sessionRequired: true, allOf: ["sessions:control"] },
|
|
191
|
+
sandbox_swap: { sessionRequired: true, allOf: ["sessions:control"] },
|
|
192
|
+
run_on: { sessionRequired: true, allOf: ["sessions:control"] },
|
|
193
|
+
sandbox_provision: { sessionRequired: true, allOf: ["sessions:control"] },
|
|
194
|
+
rig_list: { allOf: ["rigs:use"] },
|
|
195
|
+
rig_get: { allOf: ["rigs:use"] },
|
|
196
|
+
rig_propose_change: { allOf: ["rigs:use"] },
|
|
197
|
+
rig_verify: { allOf: ["rigs:use"] },
|
|
198
|
+
rig_promote: { allOf: ["rigs:manage"] },
|
|
199
|
+
sessions_list: { allOf: ["sessions:read"] },
|
|
200
|
+
session_get: { allOf: ["sessions:read"] },
|
|
201
|
+
session_events: { allOf: ["sessions:read"] },
|
|
202
|
+
session_create: { allOf: ["sessions:create"] },
|
|
203
|
+
session_send_message: { allOf: ["sessions:control"] },
|
|
204
|
+
session_pause: { allOf: ["sessions:control"] },
|
|
205
|
+
session_resume: { allOf: ["sessions:control"] },
|
|
206
|
+
session_steer: { sessionRequired: true, allOf: ["sessions:control"] },
|
|
207
|
+
set_other_session_title: { allOf: ["sessions:control"] },
|
|
208
|
+
variable_set_list: { allOf: ["variable-sets:use"] },
|
|
209
|
+
environment_list: { allOf: ["variable-sets:use"] },
|
|
210
|
+
variable_set_set_variable: { allOf: ["variable-sets:manage"] },
|
|
211
|
+
environment_set_variable: { allOf: ["variable-sets:manage"] },
|
|
212
|
+
github_connect_link: { allOf: ["github:use"] },
|
|
213
|
+
github_token: { sessionRequired: true, allOf: ["github:use"] },
|
|
214
|
+
github_repositories_list: { allOf: ["github:use"] },
|
|
215
|
+
social_connections_list: { allOf: ["connections:read"] },
|
|
216
|
+
social_posts_recent: { allOf: ["connections:read"] },
|
|
217
|
+
social_daily_analysis_context: { allOf: ["connections:read"] },
|
|
218
|
+
scheduled_tasks_list: { anyOf: ["scheduled_tasks:manage", "scheduled_tasks:run"] },
|
|
219
|
+
scheduled_tasks_get: { anyOf: ["scheduled_tasks:manage", "scheduled_tasks:run"] },
|
|
220
|
+
scheduled_tasks_create: { allOf: ["scheduled_tasks:manage"] },
|
|
221
|
+
scheduled_tasks_update: { allOf: ["scheduled_tasks:manage"] },
|
|
222
|
+
scheduled_tasks_pause: { allOf: ["scheduled_tasks:manage"] },
|
|
223
|
+
scheduled_tasks_resume: { allOf: ["scheduled_tasks:manage"] },
|
|
224
|
+
scheduled_tasks_trigger: { allOf: ["scheduled_tasks:run"] },
|
|
225
|
+
scheduled_tasks_delete: { allOf: ["scheduled_tasks:manage"] },
|
|
226
|
+
scheduled_task_runs_list: { anyOf: ["scheduled_tasks:manage", "scheduled_tasks:run"] },
|
|
227
|
+
slack_bot_list_channels: { allOf: ["connections:read"] },
|
|
228
|
+
slack_bot_channel_history: { allOf: ["connections:read"] },
|
|
229
|
+
slack_bot_list_users: { allOf: ["connections:read"] },
|
|
230
|
+
slack_bot_post_message: { allOf: ["connections:read"] },
|
|
231
|
+
} satisfies Record<FirstPartyMcpToolName, FirstPartyToolAuthorization>;
|
|
232
|
+
|
|
233
|
+
const FIRST_PARTY_MCP_TOOL_NAME_SET = new Set<string>(FIRST_PARTY_MCP_TOOL_NAMES);
|
|
234
|
+
|
|
235
|
+
class PolicyMcpServer extends McpServer {
|
|
236
|
+
private registeredToolCount = 0;
|
|
237
|
+
|
|
238
|
+
constructor(
|
|
239
|
+
private readonly grant: AccessGrant,
|
|
240
|
+
private readonly sessionId: string | null,
|
|
241
|
+
private readonly selectedTools: ReadonlySet<FirstPartyMcpToolName> | null,
|
|
242
|
+
private readonly allowUncatalogued: boolean,
|
|
243
|
+
) {
|
|
244
|
+
super({ name: "opengeni", version: "1.0.0" });
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
override registerTool<
|
|
248
|
+
OutputArgs extends ZodRawShapeCompat | AnySchema,
|
|
249
|
+
InputArgs extends undefined | ZodRawShapeCompat | AnySchema = undefined,
|
|
250
|
+
>(
|
|
251
|
+
name: string,
|
|
252
|
+
config: {
|
|
253
|
+
title?: string;
|
|
254
|
+
description?: string;
|
|
255
|
+
inputSchema?: InputArgs;
|
|
256
|
+
outputSchema?: OutputArgs;
|
|
257
|
+
annotations?: ToolAnnotations;
|
|
258
|
+
_meta?: Record<string, unknown>;
|
|
259
|
+
},
|
|
260
|
+
cb: ToolCallback<InputArgs>,
|
|
261
|
+
): RegisteredTool {
|
|
262
|
+
const catalogued = FIRST_PARTY_MCP_TOOL_NAME_SET.has(name);
|
|
263
|
+
let admitted = this.allowUncatalogued && !catalogued;
|
|
264
|
+
if (catalogued) {
|
|
265
|
+
const toolName = name as FirstPartyMcpToolName;
|
|
266
|
+
const policy: FirstPartyToolAuthorization = FIRST_PARTY_TOOL_AUTHORIZATION[toolName];
|
|
267
|
+
const authorized =
|
|
268
|
+
(!policy.sessionRequired || this.sessionId !== null) &&
|
|
269
|
+
(policy.allOf?.every((permission) => hasPermission(this.grant.permissions, permission)) ??
|
|
270
|
+
true) &&
|
|
271
|
+
(policy.anyOf?.some((permission) => hasPermission(this.grant.permissions, permission)) ??
|
|
272
|
+
true);
|
|
273
|
+
const selected = this.selectedTools === null || this.selectedTools.has(toolName);
|
|
274
|
+
admitted = authorized && selected;
|
|
275
|
+
}
|
|
276
|
+
if (!admitted) {
|
|
277
|
+
return {
|
|
278
|
+
...(config.title ? { title: config.title } : {}),
|
|
279
|
+
...(config.description ? { description: config.description } : {}),
|
|
280
|
+
...(config.annotations ? { annotations: config.annotations } : {}),
|
|
281
|
+
...(config._meta ? { _meta: config._meta } : {}),
|
|
282
|
+
handler: cb as RegisteredTool["handler"],
|
|
283
|
+
enabled: false,
|
|
284
|
+
enable() {},
|
|
285
|
+
disable() {},
|
|
286
|
+
update() {},
|
|
287
|
+
remove() {},
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
this.registeredToolCount += 1;
|
|
291
|
+
return super.registerTool(name, config, cb);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
ensureToolsListHandler(): void {
|
|
295
|
+
if (this.registeredToolCount > 0) return;
|
|
296
|
+
super
|
|
297
|
+
.registerTool(
|
|
298
|
+
"__opengeni_empty_first_party_surface__",
|
|
299
|
+
{
|
|
300
|
+
description: "Internal disabled placeholder for an empty first-party surface.",
|
|
301
|
+
inputSchema: z4.object({}),
|
|
302
|
+
},
|
|
303
|
+
async () => ({
|
|
304
|
+
content: [{ type: "text" as const, text: '{"unavailable":true}' }],
|
|
305
|
+
}),
|
|
306
|
+
)
|
|
307
|
+
.disable();
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
160
311
|
export function buildOpenGeniMcpServer(
|
|
161
312
|
deps: ApiRouteDeps,
|
|
162
313
|
grant: AccessGrant,
|
|
163
314
|
options: McpServerOptions = {},
|
|
164
315
|
): McpServer {
|
|
165
|
-
const server = new McpServer({
|
|
166
|
-
name: "opengeni",
|
|
167
|
-
version: "1.0.0",
|
|
168
|
-
});
|
|
169
316
|
const json = (value: unknown) => ({
|
|
170
317
|
content: [{ type: "text" as const, text: JSON.stringify(value, null, 2) }],
|
|
171
318
|
});
|
|
@@ -178,6 +325,14 @@ export function buildOpenGeniMcpServer(
|
|
|
178
325
|
typeof grant.metadata?.["sessionId"] === "string"
|
|
179
326
|
? (grant.metadata["sessionId"] as string)
|
|
180
327
|
: null;
|
|
328
|
+
const selectedTools =
|
|
329
|
+
sessionId !== null && !toolspaceMode
|
|
330
|
+
? new Set(
|
|
331
|
+
(grant.metadata?.["firstPartyMcpTools"] as FirstPartyMcpToolName[] | undefined) ??
|
|
332
|
+
DEFAULT_FIRST_PARTY_MCP_TOOLS,
|
|
333
|
+
)
|
|
334
|
+
: null;
|
|
335
|
+
const server = new PolicyMcpServer(grant, sessionId, selectedTools, toolspaceMode);
|
|
181
336
|
// set_session_title names the agent's OWN session — pure session metadata,
|
|
182
337
|
// not a goal operation — so it is available on every session, gated only on
|
|
183
338
|
// the signed sessionId (NOT goals:manage, and NOT on a goal existing).
|
|
@@ -248,45 +403,6 @@ export function buildOpenGeniMcpServer(
|
|
|
248
403
|
}
|
|
249
404
|
}
|
|
250
405
|
|
|
251
|
-
if (!toolspaceMode || can("files:read")) {
|
|
252
|
-
server.registerTool(
|
|
253
|
-
"files_get_download_url",
|
|
254
|
-
{
|
|
255
|
-
description: "Create a short-lived download URL for a ready file asset.",
|
|
256
|
-
inputSchema: { fileId: z4.string().uuid() },
|
|
257
|
-
},
|
|
258
|
-
async ({ fileId }) => {
|
|
259
|
-
if (!deps.objectStorage) {
|
|
260
|
-
throw new Error("object storage is not configured");
|
|
261
|
-
}
|
|
262
|
-
const file = await requireFile(deps.db, grant.workspaceId, fileId);
|
|
263
|
-
if (file.status !== "ready") {
|
|
264
|
-
throw new Error(`file is ${file.status}`);
|
|
265
|
-
}
|
|
266
|
-
const signed = await deps.objectStorage.createGetUrl({
|
|
267
|
-
key: file.objectKey,
|
|
268
|
-
});
|
|
269
|
-
return json({
|
|
270
|
-
file: {
|
|
271
|
-
id: file.id,
|
|
272
|
-
filename: file.filename,
|
|
273
|
-
safeFilename: file.safeFilename,
|
|
274
|
-
contentType: file.contentType,
|
|
275
|
-
sizeBytes: file.sizeBytes,
|
|
276
|
-
sha256: file.sha256,
|
|
277
|
-
status: file.status,
|
|
278
|
-
createdAt: file.createdAt,
|
|
279
|
-
updatedAt: file.updatedAt,
|
|
280
|
-
},
|
|
281
|
-
downloadUrl: {
|
|
282
|
-
url: signed.url,
|
|
283
|
-
expiresAt: signed.expiresAt.toISOString(),
|
|
284
|
-
},
|
|
285
|
-
});
|
|
286
|
-
},
|
|
287
|
-
);
|
|
288
|
-
}
|
|
289
|
-
|
|
290
406
|
if (!toolspaceMode || can("github:use")) {
|
|
291
407
|
server.registerTool(
|
|
292
408
|
"github_repositories_list",
|
|
@@ -604,6 +720,7 @@ export function buildOpenGeniMcpServer(
|
|
|
604
720
|
task,
|
|
605
721
|
agentRunUsageIdempotencyKey,
|
|
606
722
|
triggerWorkflowId,
|
|
723
|
+
initiator: { kind: "subject", subjectId: grant.subjectId },
|
|
607
724
|
});
|
|
608
725
|
await recordWorkspaceUsage(deps, {
|
|
609
726
|
accountId: grant.accountId,
|
|
@@ -653,6 +770,7 @@ export function buildOpenGeniMcpServer(
|
|
|
653
770
|
}
|
|
654
771
|
|
|
655
772
|
registerToolspaceProxyTools(server, options.toolspace ?? null);
|
|
773
|
+
server.ensureToolsListHandler();
|
|
656
774
|
|
|
657
775
|
return server;
|
|
658
776
|
}
|
|
@@ -1450,6 +1568,7 @@ function registerRigTools(
|
|
|
1450
1568
|
function exactAgentCommandContext(
|
|
1451
1569
|
grant: AccessGrant,
|
|
1452
1570
|
callerSessionId: string,
|
|
1571
|
+
authorizationSurface?: SessionAuthorizationSurface,
|
|
1453
1572
|
): AgentSessionCommandContext {
|
|
1454
1573
|
const turnId = grant.metadata?.["turnId"];
|
|
1455
1574
|
const attemptId = grant.metadata?.["attemptId"];
|
|
@@ -1471,6 +1590,7 @@ function exactAgentCommandContext(
|
|
|
1471
1590
|
callerTurnId: turnId,
|
|
1472
1591
|
callerAttemptId: attemptId,
|
|
1473
1592
|
callerExecutionGeneration: executionGeneration,
|
|
1593
|
+
...(authorizationSurface ? { authorizationSurface } : {}),
|
|
1474
1594
|
};
|
|
1475
1595
|
}
|
|
1476
1596
|
|
|
@@ -1734,6 +1854,12 @@ function registerWorkspaceOrchestrationTools(
|
|
|
1734
1854
|
.describe(
|
|
1735
1855
|
"Optional first-party capability set for the child. Omit to inherit this session's effective permissions. An explicit set may only narrow capabilities held by this session. A goal-bearing child requires goals:manage in the resulting set; creation fails rather than adding it implicitly.",
|
|
1736
1856
|
),
|
|
1857
|
+
firstPartyMcpTools: z4
|
|
1858
|
+
.array(z4.enum(FIRST_PARTY_MCP_TOOL_NAMES))
|
|
1859
|
+
.optional()
|
|
1860
|
+
.describe(
|
|
1861
|
+
"Exact model-visible first-party tool selection for the child. Omit to inherit this session's effective selection. This does not grant permissions.",
|
|
1862
|
+
),
|
|
1737
1863
|
// Shared-sandbox placement (addendum 05 §D). OMIT (default) to SHARE the
|
|
1738
1864
|
// creator's box — one filesystem/repo/desktop, N independent conversations;
|
|
1739
1865
|
// this is the SAFE DEFAULT. Pass "new" for a fresh isolated box (a different
|
|
@@ -1792,7 +1918,7 @@ function registerWorkspaceOrchestrationTools(
|
|
|
1792
1918
|
"session_send_message",
|
|
1793
1919
|
{
|
|
1794
1920
|
description:
|
|
1795
|
-
"Send information to another session. From an OpenGeni worker this becomes a coalescible
|
|
1921
|
+
"Send information to another session. From an OpenGeni worker this becomes a canonical coalescible machine input: it appears in the target's compact incoming queue group, is durably added to model history when claimed, and remains visible in the timeline. A sessionless operator call appends one human/API prompt.",
|
|
1796
1922
|
inputSchema: {
|
|
1797
1923
|
sessionId: z4.string().uuid(),
|
|
1798
1924
|
text: z4.string().min(1),
|
|
@@ -1830,7 +1956,6 @@ function registerWorkspaceOrchestrationTools(
|
|
|
1830
1956
|
targetSessionId,
|
|
1831
1957
|
{
|
|
1832
1958
|
text,
|
|
1833
|
-
toolsProvided: false,
|
|
1834
1959
|
delivery: "send",
|
|
1835
1960
|
origin: "operator",
|
|
1836
1961
|
clientEventId: idempotencyKey,
|
|
@@ -1854,16 +1979,10 @@ function registerWorkspaceOrchestrationTools(
|
|
|
1854
1979
|
},
|
|
1855
1980
|
},
|
|
1856
1981
|
async ({ sessionId, idempotencyKey, reason }) => {
|
|
1857
|
-
const authorization = await authorizeFirstPartySession(
|
|
1858
|
-
deps,
|
|
1859
|
-
grant,
|
|
1860
|
-
sessionId,
|
|
1861
|
-
"session.control",
|
|
1862
|
-
);
|
|
1863
1982
|
if (callerSessionId !== null) {
|
|
1864
1983
|
const controlled = await controlAgentSessionWorkstream(
|
|
1865
1984
|
deps,
|
|
1866
|
-
exactAgentCommandContext(grant, callerSessionId),
|
|
1985
|
+
exactAgentCommandContext(grant, callerSessionId, "first_party_mcp"),
|
|
1867
1986
|
{
|
|
1868
1987
|
targetSessionId: sessionId,
|
|
1869
1988
|
action: "pause",
|
|
@@ -1876,7 +1995,7 @@ function registerWorkspaceOrchestrationTools(
|
|
|
1876
1995
|
effectiveControl: projectEffectiveControlForRelatedAccess(
|
|
1877
1996
|
serializeEffectiveSessionControl(controlled.control),
|
|
1878
1997
|
sessionId,
|
|
1879
|
-
authorization?.relatedSessionAccess ?? "root",
|
|
1998
|
+
controlled.authorization?.relatedSessionAccess ?? "root",
|
|
1880
1999
|
),
|
|
1881
2000
|
interruptionCount: controlled.interruptionCount,
|
|
1882
2001
|
replay: controlled.replay,
|
|
@@ -1889,6 +2008,7 @@ function registerWorkspaceOrchestrationTools(
|
|
|
1889
2008
|
workspaceId: grant.workspaceId,
|
|
1890
2009
|
sessionId,
|
|
1891
2010
|
subjectId: grant.subjectId,
|
|
2011
|
+
authorizationSurface: "first_party_mcp",
|
|
1892
2012
|
},
|
|
1893
2013
|
{
|
|
1894
2014
|
action: "pause",
|
|
@@ -1896,14 +2016,7 @@ function registerWorkspaceOrchestrationTools(
|
|
|
1896
2016
|
...(reason ? { reason } : {}),
|
|
1897
2017
|
},
|
|
1898
2018
|
);
|
|
1899
|
-
return json(
|
|
1900
|
-
...controlled,
|
|
1901
|
-
effectiveControl: projectEffectiveControlForRelatedAccess(
|
|
1902
|
-
controlled.effectiveControl,
|
|
1903
|
-
sessionId,
|
|
1904
|
-
authorization?.relatedSessionAccess ?? "root",
|
|
1905
|
-
),
|
|
1906
|
-
});
|
|
2019
|
+
return json(controlled);
|
|
1907
2020
|
},
|
|
1908
2021
|
);
|
|
1909
2022
|
|
|
@@ -1919,16 +2032,10 @@ function registerWorkspaceOrchestrationTools(
|
|
|
1919
2032
|
},
|
|
1920
2033
|
},
|
|
1921
2034
|
async ({ sessionId, idempotencyKey, reason }) => {
|
|
1922
|
-
const authorization = await authorizeFirstPartySession(
|
|
1923
|
-
deps,
|
|
1924
|
-
grant,
|
|
1925
|
-
sessionId,
|
|
1926
|
-
"session.control",
|
|
1927
|
-
);
|
|
1928
2035
|
if (callerSessionId !== null) {
|
|
1929
2036
|
const controlled = await controlAgentSessionWorkstream(
|
|
1930
2037
|
deps,
|
|
1931
|
-
exactAgentCommandContext(grant, callerSessionId),
|
|
2038
|
+
exactAgentCommandContext(grant, callerSessionId, "first_party_mcp"),
|
|
1932
2039
|
{
|
|
1933
2040
|
targetSessionId: sessionId,
|
|
1934
2041
|
action: "resume",
|
|
@@ -1941,7 +2048,7 @@ function registerWorkspaceOrchestrationTools(
|
|
|
1941
2048
|
effectiveControl: projectEffectiveControlForRelatedAccess(
|
|
1942
2049
|
serializeEffectiveSessionControl(controlled.control),
|
|
1943
2050
|
sessionId,
|
|
1944
|
-
authorization?.relatedSessionAccess ?? "root",
|
|
2051
|
+
controlled.authorization?.relatedSessionAccess ?? "root",
|
|
1945
2052
|
),
|
|
1946
2053
|
interruptionCount: controlled.interruptionCount,
|
|
1947
2054
|
replay: controlled.replay,
|
|
@@ -1954,6 +2061,7 @@ function registerWorkspaceOrchestrationTools(
|
|
|
1954
2061
|
workspaceId: grant.workspaceId,
|
|
1955
2062
|
sessionId,
|
|
1956
2063
|
subjectId: grant.subjectId,
|
|
2064
|
+
authorizationSurface: "first_party_mcp",
|
|
1957
2065
|
},
|
|
1958
2066
|
{
|
|
1959
2067
|
action: "resume",
|
|
@@ -1961,14 +2069,7 @@ function registerWorkspaceOrchestrationTools(
|
|
|
1961
2069
|
...(reason ? { reason } : {}),
|
|
1962
2070
|
},
|
|
1963
2071
|
);
|
|
1964
|
-
return json(
|
|
1965
|
-
...controlled,
|
|
1966
|
-
effectiveControl: projectEffectiveControlForRelatedAccess(
|
|
1967
|
-
controlled.effectiveControl,
|
|
1968
|
-
sessionId,
|
|
1969
|
-
authorization?.relatedSessionAccess ?? "root",
|
|
1970
|
-
),
|
|
1971
|
-
});
|
|
2072
|
+
return json(controlled);
|
|
1972
2073
|
},
|
|
1973
2074
|
);
|
|
1974
2075
|
|
|
@@ -1977,7 +2078,7 @@ function registerWorkspaceOrchestrationTools(
|
|
|
1977
2078
|
"session_steer",
|
|
1978
2079
|
{
|
|
1979
2080
|
description:
|
|
1980
|
-
"Atomically replace another session's current direction and resume it. The
|
|
2081
|
+
"Atomically replace another session's current direction and resume it. The Agent Steer is a typed machine input shown in the target queue/timeline and durably retained in model history; it never impersonates a human prompt.",
|
|
1981
2082
|
inputSchema: {
|
|
1982
2083
|
sessionId: z4.string().uuid(),
|
|
1983
2084
|
instruction: z4.string().min(1),
|
|
@@ -1985,10 +2086,9 @@ function registerWorkspaceOrchestrationTools(
|
|
|
1985
2086
|
},
|
|
1986
2087
|
},
|
|
1987
2088
|
async ({ sessionId, instruction, idempotencyKey }) => {
|
|
1988
|
-
await authorizeFirstPartySession(deps, grant, sessionId, "session.steer");
|
|
1989
2089
|
const result = await steerAgentSession(
|
|
1990
2090
|
deps,
|
|
1991
|
-
exactAgentCommandContext(grant, callerSessionId),
|
|
2091
|
+
exactAgentCommandContext(grant, callerSessionId, "first_party_mcp"),
|
|
1992
2092
|
{ targetSessionId: sessionId, instruction, idempotencyKey },
|
|
1993
2093
|
);
|
|
1994
2094
|
return json({
|
|
@@ -2194,14 +2294,16 @@ function registerGitHubConnectTool(
|
|
|
2194
2294
|
const { settings } = deps;
|
|
2195
2295
|
const missing = githubAppMissingSettings(settings);
|
|
2196
2296
|
const slug = settings.githubAppSlug?.trim() || null;
|
|
2297
|
+
const setupMode = settings.productAccessMode === "managed" ? "platform" : "operator";
|
|
2197
2298
|
if (missing.length > 0 || !slug) {
|
|
2198
2299
|
return json({
|
|
2199
2300
|
configured: false,
|
|
2200
2301
|
status: "disabled",
|
|
2201
|
-
|
|
2302
|
+
setupMode,
|
|
2303
|
+
appSlug: setupMode === "operator" ? slug : null,
|
|
2202
2304
|
installUrl: null,
|
|
2203
2305
|
linkUrl: null,
|
|
2204
|
-
missing,
|
|
2306
|
+
missing: setupMode === "operator" ? missing : [],
|
|
2205
2307
|
});
|
|
2206
2308
|
}
|
|
2207
2309
|
const installations = await listWorkspaceGitHubInstallationBindings(deps, grant.workspaceId);
|
|
@@ -2219,13 +2321,21 @@ function registerGitHubConnectTool(
|
|
|
2219
2321
|
const connectUrl = state
|
|
2220
2322
|
? `${baseUrl}/v1/workspaces/${grant.workspaceId}/github/connect?state=${encodeURIComponent(state)}`
|
|
2221
2323
|
: null;
|
|
2324
|
+
const installationViews = installations.map((installation) => ({
|
|
2325
|
+
...installation,
|
|
2326
|
+
configureUrl:
|
|
2327
|
+
state && baseUrl
|
|
2328
|
+
? `${baseUrl}/v1/workspaces/${grant.workspaceId}/github/installations/${installation.installationId}/configure?state=${encodeURIComponent(state)}`
|
|
2329
|
+
: null,
|
|
2330
|
+
}));
|
|
2222
2331
|
return json({
|
|
2223
2332
|
configured: true,
|
|
2224
2333
|
status,
|
|
2225
|
-
|
|
2334
|
+
setupMode,
|
|
2335
|
+
appSlug: setupMode === "operator" ? slug : null,
|
|
2226
2336
|
installUrl: connectUrl,
|
|
2227
2337
|
linkUrl: connectUrl,
|
|
2228
|
-
installations,
|
|
2338
|
+
installations: installationViews,
|
|
2229
2339
|
missing: [],
|
|
2230
2340
|
});
|
|
2231
2341
|
},
|
package/src/routes/files.ts
CHANGED
|
@@ -27,10 +27,21 @@ import { HTTPException } from "hono/http-exception";
|
|
|
27
27
|
import { requireAccessGrant } from "@opengeni/core";
|
|
28
28
|
import { recordWorkspaceUsage, requireLimit } from "@opengeni/core";
|
|
29
29
|
import type { ApiRouteDeps } from "@opengeni/core";
|
|
30
|
+
import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
|
|
31
|
+
import { buildFilesMcpServer } from "../mcp/files";
|
|
30
32
|
|
|
31
33
|
export function registerFileRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
32
34
|
const { db, objectStorage } = deps;
|
|
33
35
|
|
|
36
|
+
app.all("/v1/workspaces/:workspaceId/mcp/files", async (c) => {
|
|
37
|
+
const workspaceId = c.req.param("workspaceId");
|
|
38
|
+
const grant = await requireAccessGrant(c, deps, workspaceId, "files:read");
|
|
39
|
+
const transport = new WebStandardStreamableHTTPServerTransport({ enableJsonResponse: true });
|
|
40
|
+
const server = buildFilesMcpServer(deps, grant);
|
|
41
|
+
await server.connect(transport);
|
|
42
|
+
return await transport.handleRequest(c.req.raw);
|
|
43
|
+
});
|
|
44
|
+
|
|
34
45
|
app.post("/v1/workspaces/:workspaceId/files/uploads", async (c) => {
|
|
35
46
|
const workspaceId = c.req.param("workspaceId");
|
|
36
47
|
const grant = await requireAccessGrant(c, deps, workspaceId, "files:upload");
|