@opengeni/api-router 0.4.0 → 0.5.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.
- package/LICENSE +190 -0
- package/dist/app.js +1 -1
- package/dist/{chunk-I2EDWHVF.js → chunk-DQ5TIRDZ.js} +2229 -616
- package/dist/chunk-DQ5TIRDZ.js.map +1 -0
- package/dist/index.js +53 -8
- package/dist/index.js.map +1 -1
- package/package.json +12 -18
- package/src/app.ts +106 -7
- package/src/http/auth.ts +7 -1
- package/src/index.ts +9 -4
- package/src/integrations/oauth-client.ts +899 -0
- package/src/mcp/documents.ts +124 -20
- package/src/mcp/server.ts +37 -2
- package/src/mcp/toolspace.ts +627 -0
- package/src/observability.ts +31 -0
- package/src/routes/billing.ts +19 -1
- package/src/routes/connections.ts +179 -0
- package/src/routes/documents.ts +87 -3
- package/src/routes/enrollments.ts +1 -0
- package/src/sandbox/auth-callout.ts +5 -1
- package/src/sandbox/channel-a.ts +1 -1
- package/src/sandbox/machines.ts +2 -0
- package/src/sandbox/metrics-ingestion.ts +41 -10
- package/src/sandbox/viewer.ts +1 -1
- package/dist/chunk-I2EDWHVF.js.map +0 -1
package/src/mcp/documents.ts
CHANGED
|
@@ -4,11 +4,39 @@ import {
|
|
|
4
4
|
searchDocuments,
|
|
5
5
|
type DocumentServices,
|
|
6
6
|
} from "@opengeni/documents";
|
|
7
|
-
import
|
|
7
|
+
import {
|
|
8
|
+
createKnowledgeMemory,
|
|
9
|
+
listKnowledgeMemories,
|
|
10
|
+
type Database,
|
|
11
|
+
} from "@opengeni/db";
|
|
8
12
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
9
13
|
import * as z from "zod/v4";
|
|
10
14
|
|
|
11
|
-
|
|
15
|
+
const SearchInputSchema = {
|
|
16
|
+
query: z.string().min(1),
|
|
17
|
+
baseIds: z.array(z.string().uuid()).optional(),
|
|
18
|
+
limit: z.number().int().positive().max(50).optional(),
|
|
19
|
+
mode: z.enum(["hybrid", "vector", "keyword"]).optional(),
|
|
20
|
+
sourceKinds: z.array(z.enum(["manual_upload", "meeting_transcript", "repository", "email", "chat", "document", "web", "other"])).optional(),
|
|
21
|
+
aclTags: z.array(z.string().min(1)).optional(),
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const MemoryKindSchema = z.enum(["semantic", "episodic", "procedural", "decision", "preference"]);
|
|
25
|
+
const SourceRefSchema = z.object({
|
|
26
|
+
kind: z.enum(["document_chunk", "document", "session_event", "memory", "external"]),
|
|
27
|
+
id: z.string().min(1),
|
|
28
|
+
uri: z.string().min(1).optional(),
|
|
29
|
+
title: z.string().min(1).optional(),
|
|
30
|
+
metadata: z.record(z.string(), z.unknown()).optional(),
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
export function buildDocumentsMcpServer(
|
|
34
|
+
db: Database,
|
|
35
|
+
accountId: string,
|
|
36
|
+
workspaceId: string,
|
|
37
|
+
documentServices: DocumentServices,
|
|
38
|
+
options: { createdBySessionId?: string | undefined } = {},
|
|
39
|
+
): McpServer {
|
|
12
40
|
const server = new McpServer({
|
|
13
41
|
name: "opengeni-documents",
|
|
14
42
|
version: "1.0.0",
|
|
@@ -22,28 +50,19 @@ export function buildDocumentsMcpServer(db: Database, workspaceId: string, docum
|
|
|
22
50
|
}));
|
|
23
51
|
|
|
24
52
|
server.registerTool("search_documents", {
|
|
25
|
-
description: "Search indexed documents.",
|
|
26
|
-
inputSchema:
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
type: "text",
|
|
34
|
-
text: JSON.stringify(await searchDocuments(db, {
|
|
35
|
-
workspaceId,
|
|
36
|
-
query,
|
|
37
|
-
...(baseIds ? { baseIds } : {}),
|
|
38
|
-
...(limit ? { limit } : {}),
|
|
39
|
-
}, documentServices)),
|
|
40
|
-
}],
|
|
41
|
-
}));
|
|
53
|
+
description: "Search indexed documents with hybrid, vector, or keyword retrieval.",
|
|
54
|
+
inputSchema: SearchInputSchema,
|
|
55
|
+
}, async (input) => searchContent(db, workspaceId, documentServices, input));
|
|
56
|
+
|
|
57
|
+
server.registerTool("knowledge_search", {
|
|
58
|
+
description: "Search company knowledge sources with optional base, source-kind, ACL, and retrieval-mode filters.",
|
|
59
|
+
inputSchema: SearchInputSchema,
|
|
60
|
+
}, async (input) => searchContent(db, workspaceId, documentServices, input));
|
|
42
61
|
|
|
43
62
|
server.registerTool("fetch_document_chunk", {
|
|
44
63
|
description: "Fetch one indexed document chunk by id.",
|
|
45
64
|
inputSchema: {
|
|
46
|
-
chunkId: z.string(),
|
|
65
|
+
chunkId: z.string().uuid(),
|
|
47
66
|
},
|
|
48
67
|
}, async ({ chunkId }) => {
|
|
49
68
|
const found = await getDocumentChunk(db, workspaceId, chunkId);
|
|
@@ -53,5 +72,90 @@ export function buildDocumentsMcpServer(db: Database, workspaceId: string, docum
|
|
|
53
72
|
};
|
|
54
73
|
});
|
|
55
74
|
|
|
75
|
+
server.registerTool("knowledge_fetch", {
|
|
76
|
+
description: "Fetch one knowledge source chunk by id.",
|
|
77
|
+
inputSchema: {
|
|
78
|
+
chunkId: z.string().uuid(),
|
|
79
|
+
},
|
|
80
|
+
}, async ({ chunkId }) => {
|
|
81
|
+
const found = await getDocumentChunk(db, workspaceId, chunkId);
|
|
82
|
+
return {
|
|
83
|
+
content: [{ type: "text", text: found ? JSON.stringify(found) : `chunk not found: ${chunkId}` }],
|
|
84
|
+
isError: !found,
|
|
85
|
+
};
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
server.registerTool("memory_search", {
|
|
89
|
+
description: "Search approved company memory records.",
|
|
90
|
+
inputSchema: {
|
|
91
|
+
query: z.string().min(1).optional(),
|
|
92
|
+
kind: MemoryKindSchema.optional(),
|
|
93
|
+
scope: z.string().min(1).optional(),
|
|
94
|
+
limit: z.number().int().positive().max(100).optional(),
|
|
95
|
+
},
|
|
96
|
+
}, async ({ query, kind, scope, limit }) => ({
|
|
97
|
+
content: [{ type: "text", text: JSON.stringify(await listKnowledgeMemories(db, workspaceId, {
|
|
98
|
+
...(query ? { query } : {}),
|
|
99
|
+
status: "approved",
|
|
100
|
+
...(kind ? { kind } : {}),
|
|
101
|
+
...(scope ? { scope } : {}),
|
|
102
|
+
...(limit ? { limit } : {}),
|
|
103
|
+
})) }],
|
|
104
|
+
}));
|
|
105
|
+
|
|
106
|
+
server.registerTool("memory_propose", {
|
|
107
|
+
description: "Propose a company memory record for human review.",
|
|
108
|
+
inputSchema: {
|
|
109
|
+
text: z.string().min(1),
|
|
110
|
+
kind: MemoryKindSchema.optional(),
|
|
111
|
+
scope: z.string().min(1).optional(),
|
|
112
|
+
sourceRefs: z.array(SourceRefSchema).optional(),
|
|
113
|
+
confidence: z.number().min(0).max(1).optional(),
|
|
114
|
+
metadata: z.record(z.string(), z.unknown()).optional(),
|
|
115
|
+
},
|
|
116
|
+
}, async ({ text, kind, scope, sourceRefs, confidence, metadata }) => ({
|
|
117
|
+
content: [{ type: "text", text: JSON.stringify(await createKnowledgeMemory(db, {
|
|
118
|
+
accountId,
|
|
119
|
+
workspaceId,
|
|
120
|
+
status: "proposed",
|
|
121
|
+
kind: kind ?? "semantic",
|
|
122
|
+
scope: scope ?? "workspace",
|
|
123
|
+
text,
|
|
124
|
+
sourceRefs: sourceRefs?.map((sourceRef) => ({ ...sourceRef, metadata: sourceRef.metadata ?? {} })) ?? [],
|
|
125
|
+
confidence: confidence ?? 0.5,
|
|
126
|
+
metadata: metadata ?? {},
|
|
127
|
+
createdBySessionId: options.createdBySessionId,
|
|
128
|
+
})) }],
|
|
129
|
+
}));
|
|
130
|
+
|
|
56
131
|
return server;
|
|
57
132
|
}
|
|
133
|
+
|
|
134
|
+
async function searchContent(
|
|
135
|
+
db: Database,
|
|
136
|
+
workspaceId: string,
|
|
137
|
+
documentServices: DocumentServices,
|
|
138
|
+
input: {
|
|
139
|
+
query: string;
|
|
140
|
+
baseIds?: string[] | undefined;
|
|
141
|
+
limit?: number | undefined;
|
|
142
|
+
mode?: "hybrid" | "vector" | "keyword" | undefined;
|
|
143
|
+
sourceKinds?: Array<"manual_upload" | "meeting_transcript" | "repository" | "email" | "chat" | "document" | "web" | "other"> | undefined;
|
|
144
|
+
aclTags?: string[] | undefined;
|
|
145
|
+
},
|
|
146
|
+
) {
|
|
147
|
+
return {
|
|
148
|
+
content: [{
|
|
149
|
+
type: "text" as const,
|
|
150
|
+
text: JSON.stringify(await searchDocuments(db, {
|
|
151
|
+
workspaceId,
|
|
152
|
+
query: input.query,
|
|
153
|
+
...(input.baseIds ? { baseIds: input.baseIds } : {}),
|
|
154
|
+
...(input.limit ? { limit: input.limit } : {}),
|
|
155
|
+
...(input.mode ? { mode: input.mode } : {}),
|
|
156
|
+
...(input.sourceKinds ? { sourceKinds: input.sourceKinds } : {}),
|
|
157
|
+
...(input.aclTags ? { aclTags: input.aclTags } : {}),
|
|
158
|
+
}, documentServices)),
|
|
159
|
+
}],
|
|
160
|
+
};
|
|
161
|
+
}
|
package/src/mcp/server.ts
CHANGED
|
@@ -77,12 +77,14 @@ import {
|
|
|
77
77
|
type RunOnOp,
|
|
78
78
|
} from "@opengeni/core";
|
|
79
79
|
import { capEventPage, capSessionDetail } from "./session-view";
|
|
80
|
+
import type { ToolspaceMcpSurface } from "./toolspace";
|
|
80
81
|
|
|
81
82
|
export type McpServerOptions = {
|
|
82
83
|
// Origin of the HTTP request that reached the MCP route; last-resort base
|
|
83
84
|
// for links the server mints (github_connect_link) when neither
|
|
84
85
|
// OPENGENI_PUBLIC_BASE_URL nor the manifest base URL is configured.
|
|
85
86
|
requestOrigin?: string | null;
|
|
87
|
+
toolspace?: ToolspaceMcpSurface | null;
|
|
86
88
|
};
|
|
87
89
|
|
|
88
90
|
export function buildOpenGeniMcpServer(deps: ApiRouteDeps, grant: AccessGrant, options: McpServerOptions = {}): McpServer {
|
|
@@ -92,6 +94,7 @@ export function buildOpenGeniMcpServer(deps: ApiRouteDeps, grant: AccessGrant, o
|
|
|
92
94
|
});
|
|
93
95
|
const json = (value: unknown) => ({ content: [{ type: "text" as const, text: JSON.stringify(value, null, 2) }] });
|
|
94
96
|
const can = (permission: Permission) => hasPermission(grant.permissions, permission);
|
|
97
|
+
const toolspaceMode = options.toolspace != null;
|
|
95
98
|
|
|
96
99
|
// Session-scoped tools key off the worker-asserted sessionId claim (signed
|
|
97
100
|
// into the delegated token by the worker, never agent-controlled).
|
|
@@ -99,7 +102,7 @@ export function buildOpenGeniMcpServer(deps: ApiRouteDeps, grant: AccessGrant, o
|
|
|
99
102
|
// set_session_title names the agent's OWN session — pure session metadata,
|
|
100
103
|
// not a goal operation — so it is available on every session, gated only on
|
|
101
104
|
// the signed sessionId (NOT goals:manage, and NOT on a goal existing).
|
|
102
|
-
if (sessionId !== null) {
|
|
105
|
+
if (sessionId !== null && (!toolspaceMode || can("sessions:control"))) {
|
|
103
106
|
server.registerTool("set_session_title", {
|
|
104
107
|
description: "Set this session's display title to a concise 3-7 word summary. Call once early to name the session; calling again replaces it unless a human has manually set the title.",
|
|
105
108
|
inputSchema: { title: z4.string().min(1).max(200) },
|
|
@@ -119,7 +122,7 @@ export function buildOpenGeniMcpServer(deps: ApiRouteDeps, grant: AccessGrant, o
|
|
|
119
122
|
// so they register only when the grant carries the worker-signed sessionId claim
|
|
120
123
|
// (never agent-controlled). Gated on the selfhosted feature flag: the active
|
|
121
124
|
// pointer + swap are only meaningful when bring-your-own-compute is enabled.
|
|
122
|
-
if (sessionId !== null && deps.settings.sandboxSelfhostedEnabled) {
|
|
125
|
+
if (!toolspaceMode && sessionId !== null && deps.settings.sandboxSelfhostedEnabled) {
|
|
123
126
|
registerFleetTools(server, deps, grant, sessionId, json);
|
|
124
127
|
}
|
|
125
128
|
|
|
@@ -144,6 +147,7 @@ export function buildOpenGeniMcpServer(deps: ApiRouteDeps, grant: AccessGrant, o
|
|
|
144
147
|
}
|
|
145
148
|
}
|
|
146
149
|
|
|
150
|
+
if (!toolspaceMode || can("files:read")) {
|
|
147
151
|
server.registerTool("files_get_download_url", {
|
|
148
152
|
description: "Create a short-lived download URL for a ready file asset.",
|
|
149
153
|
inputSchema: { fileId: z4.string().uuid() },
|
|
@@ -174,7 +178,9 @@ export function buildOpenGeniMcpServer(deps: ApiRouteDeps, grant: AccessGrant, o
|
|
|
174
178
|
},
|
|
175
179
|
});
|
|
176
180
|
});
|
|
181
|
+
}
|
|
177
182
|
|
|
183
|
+
if (!toolspaceMode || can("github:use")) {
|
|
178
184
|
server.registerTool("github_repositories_list", {
|
|
179
185
|
description: "List GitHub App repositories available as scheduled task repository resources. Use the returned resource object in scheduled task agentConfig.resources.",
|
|
180
186
|
inputSchema: { limit: z4.number().int().positive().optional() },
|
|
@@ -191,7 +197,9 @@ export function buildOpenGeniMcpServer(deps: ApiRouteDeps, grant: AccessGrant, o
|
|
|
191
197
|
throw error;
|
|
192
198
|
}
|
|
193
199
|
});
|
|
200
|
+
}
|
|
194
201
|
|
|
202
|
+
if (!toolspaceMode || can("connections:read")) {
|
|
195
203
|
server.registerTool("social_connections_list", {
|
|
196
204
|
description: "List connected social media accounts available to social media analysis packs.",
|
|
197
205
|
inputSchema: { limit: z4.number().int().positive().optional() },
|
|
@@ -266,6 +274,9 @@ export function buildOpenGeniMcpServer(deps: ApiRouteDeps, grant: AccessGrant, o
|
|
|
266
274
|
});
|
|
267
275
|
});
|
|
268
276
|
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (!toolspaceMode || can("scheduled_tasks:manage") || can("scheduled_tasks:run")) {
|
|
269
280
|
server.registerTool("scheduled_tasks_list", {
|
|
270
281
|
description: "List scheduled tasks.",
|
|
271
282
|
inputSchema: { limit: z4.number().int().positive().optional() },
|
|
@@ -379,9 +390,33 @@ export function buildOpenGeniMcpServer(deps: ApiRouteDeps, grant: AccessGrant, o
|
|
|
379
390
|
inputSchema: { taskId: z4.string().uuid(), limit: z4.number().int().positive().optional() },
|
|
380
391
|
}, async ({ taskId, limit }) => json({ runs: await listScheduledTaskRuns(deps.db, grant.workspaceId, taskId, limit ?? 100) }));
|
|
381
392
|
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
registerToolspaceProxyTools(server, options.toolspace ?? null);
|
|
396
|
+
|
|
382
397
|
return server;
|
|
383
398
|
}
|
|
384
399
|
|
|
400
|
+
function registerToolspaceProxyTools(server: McpServer, surface: ToolspaceMcpSurface | null): void {
|
|
401
|
+
if (!surface) {
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
for (const tool of surface.tools) {
|
|
405
|
+
server.registerTool(tool.name, {
|
|
406
|
+
...(tool.description ? { description: tool.description } : {}),
|
|
407
|
+
inputSchema: z4.object({}).passthrough(),
|
|
408
|
+
_meta: {
|
|
409
|
+
opengeni: {
|
|
410
|
+
origin: "toolspace",
|
|
411
|
+
subjectId: surface.subjectId,
|
|
412
|
+
sessionId: surface.sessionId,
|
|
413
|
+
...(tool.inputSchema ? { inputSchema: tool.inputSchema } : {}),
|
|
414
|
+
},
|
|
415
|
+
},
|
|
416
|
+
}, async (args) => await tool.call(args));
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
385
420
|
function registerGoalTools(
|
|
386
421
|
server: McpServer,
|
|
387
422
|
deps: ApiRouteDeps,
|