History

fix(v2): paginate tool catalog reads and expand default source set

Rhys Sullivan committed 8 months ago

Commit 4be71f41 parent 686206611 files · +384 −123Browse files

Unified · Split

11 files
  1. apps/convex/convex/_generated/api.d.ts +42 −0
  2. apps/convex/convex/control_plane/service.ts +2 −2
  3. apps/convex/convex/control_plane/tool_registry.ts +97 −23
  4. apps/convex/convex/control_plane/tools.ts +116 −87
  5. apps/convex/convex/convex.config.ts +2 −0
  6. apps/convex/convex/executor.test.ts +10 −9
  7. apps/convex/convex/source_tool_registry.ts +1 −1
  8. apps/convex/package.json +2 −1
  9. apps/web/components/tools/source-management-panel.tsx +48 −0
  10. bun.lock +3 −0
  11. legacy/executor/apps/web/src/lib/catalog-collections.ts +61 −0
apps/convex/convex/_generated/api.d.ts+42−0
@@ -101,6 +101,48 @@ export declare const internal: FilterApi< · 100 unmodified lines
101101 >;
102102
103103 export declare const components: {
104+ actionCache: {
105+ crons: {
106+ purge: FunctionReference<
107+ "mutation",
108+ "internal",
109+ { expiresAt?: number },
110+ null
111+ >;
112+ };
113+ lib: {
114+ get: FunctionReference<
115+ "query",
116+ "internal",
117+ { args: any; name: string; ttl: number | null },
118+ { kind: "hit"; value: any } | { expiredEntry?: string; kind: "miss" }
119+ >;
120+ put: FunctionReference<
121+ "mutation",
122+ "internal",
123+ {
124+ args: any;
125+ expiredEntry?: string;
126+ name: string;
127+ ttl: number | null;
128+ value: any;
129+ },
130+ { cacheHit: boolean; deletedExpiredEntry: boolean }
131+ >;
132+ remove: FunctionReference<
133+ "mutation",
134+ "internal",
135+ { args: any; name: string },
136+ null
137+ >;
138+ removeAll: FunctionReference<
139+ "mutation",
140+ "internal",
141+ { batchSize?: number; before?: number; name?: string },
142+ null
143+ >;
144+ };
145+ };
104146 workflow: {
105147 event: {
106148 create: FunctionReference<
apps/convex/convex/control_plane/service.ts+2−2
@@ -122,7 +122,7 @@ export const makeConvexControlPlaneService = ( · 121 unmodified lines
122122 listWorkspaceTools: (workspaceId) =>
123123 Effect.tryPromise({
124124 try: () =>
125− ctx.runQuery(api.controlPlane.listWorkspaceTools, {
125+ ctx.runAction(api.controlPlane.listWorkspaceTools, {
126126 workspaceId,
127127 }),
128128 catch: (cause) => toSourceStoreError("controlPlane.listWorkspaceTools", cause),
@@ -130,7 +130,7 @@ export const makeConvexControlPlaneService = ( · 1 unmodified line
130130 listSourceTools: (input) =>
131131 Effect.tryPromise({
132132 try: () =>
133− ctx.runQuery(api.controlPlane.listSourceTools, {
133+ ctx.runAction(api.controlPlane.listSourceTools, {
134134 workspaceId: input.workspaceId,
135135 sourceId: input.sourceId,
136136 }),
apps/convex/convex/control_plane/tool_registry.ts+97−23
@@ -1,12 +1,18 @@
1+import { ActionCache } from "@convex-dev/action-cache";
12 import { v } from "convex/values";
2−import { internal } from "../_generated/api";
3+import { components, internal } from "../_generated/api";
34
4−import { action, internalMutation, internalQuery } from "../_generated/server";
5+import { action, internalAction, internalMutation, internalQuery } from "../_generated/server";
56
67 const defaultSearchLimit = 50;
78 const maxSearchLimit = 5_000;
89 const writeBatchSize = 500;
910 const runtimeInternal = internal as any;
11+const listWorkspaceNamespacesActionCache = new ActionCache((components as any).actionCache, {
12+ action: runtimeInternal.control_plane.tool_registry.listWorkspaceNamespacesUncached,
13+ name: "workspace_namespaces_v1",
14+ ttl: 15_000,
15+});
1016
1117 const stripConvexSystemFields = (
1218 value: Record<string, unknown>,
@@ -1252,41 +1258,97 @@ export const listWorkspaceToolsByNormalizedPath = internalQuery({ · 1239 unmodified lines
12521258 },
12531259 });
12541260
1255−export const listWorkspaceNamespaces = internalQuery({
1261+export const listWorkspaceToolIndexPage = internalQuery({
12561262 args: {
12571263 workspaceId: v.string(),
1258− limit: v.optional(v.number()),
1264+ cursor: v.optional(v.union(v.string(), v.null())),
1265+ pageSize: v.optional(v.number()),
12591266 },
12601267 handler: async (ctx, args) => {
1261− const rows = await buildWorkspaceToolRows(ctx, {
1262− workspaceId: args.workspaceId,
1263− includeDisabled: false,
1264− });
1268+ const pageSize = Math.max(1, Math.min(500, Math.floor(args.pageSize ?? 200)));
1269+ const page = await ctx.db
1270+ .query("workspaceToolIndex")
1271+ .withIndex("by_workspaceId", (q) => q.eq("workspaceId", args.workspaceId))
1272+ .paginate({
1273+ cursor: args.cursor ?? null,
1274+ numItems: pageSize,
1275+ });
12651276
1277+ const rows = page.page
1278+ .filter((row) => row.status === "active")
1279+ .map((row) => stripConvexSystemFields(row as unknown as Record<string, unknown>));
1280+
1281+ return {
1282+ rows,
1283+ continueCursor: page.continueCursor,
1284+ isDone: page.isDone,
1285+ };
1286+ },
1287+});
1288+
1289+export const listWorkspaceNamespacesUncached = internalAction({
1290+ args: {
1291+ workspaceId: v.string(),
1292+ limit: v.optional(v.number()),
1293+ },
1294+ handler: async (ctx, args) => {
12661295 const grouped = new Map<
12671296 string,
12681297 {
12691298 sourceName: string;
12701299 sourceId: string;
12711300 sourceKind: string;
1301+ toolCount: number;
12721302 paths: Array<string>;
12731303 }
12741304 >();
12751305
1276− for (const row of rows) {
1277− const namespace = String(row.namespace);
1278− const existing = grouped.get(namespace);
1279− if (existing) {
1280− existing.paths.push(String(row.path));
1281− continue;
1306+ let cursor: string | null = null;
1307+ do {
1308+ const page: {
1309+ rows: Array<Record<string, unknown>>;
1310+ continueCursor: string | null;
1311+ isDone: boolean;
1312+ } = await ctx.runQuery(runtimeInternal.control_plane.tool_registry.listWorkspaceToolIndexPage, {
1313+ workspaceId: args.workspaceId,
1314+ cursor,
1315+ pageSize: 250,
1316+ });
1317+
1318+ for (const row of page.rows) {
1319+ const namespace = String(row.namespace);
1320+ const path = String(row.path);
1321+ const existing = grouped.get(namespace);
1322+ if (existing) {
1323+ existing.toolCount += 1;
1324+
1325+ if (existing.paths.length < 3) {
1326+ existing.paths.push(path);
1327+ existing.paths.sort((left, right) => left.localeCompare(right));
1328+ } else if (path.localeCompare(existing.paths[existing.paths.length - 1] ?? path) < 0) {
1329+ existing.paths[existing.paths.length - 1] = path;
1330+ existing.paths.sort((left, right) => left.localeCompare(right));
1331+ }
1332+
1333+ continue;
1334+ }
1335+
1336+ grouped.set(namespace, {
1337+ sourceName: String(row.sourceName),
1338+ sourceId: String(row.sourceId),
1339+ sourceKind: String(row.sourceKind),
1340+ toolCount: 1,
1341+ paths: [path],
1342+ });
12821343 }
12831344
1284− grouped.set(namespace, {
1285− sourceName: String(row.sourceName),
1286− sourceId: String(row.sourceId),
1287− sourceKind: String(row.sourceKind),
1288− paths: [String(row.path)],
1289− });
1345+ cursor = page.isDone ? null : (page.continueCursor as string | null);
1346+ } while (cursor);
1347+
1348+ const limit = normalizeLimit(args.limit);
1349+
1350+ if (limit <= 0 || grouped.size === 0) {
1351+ return [];
12901352 }
12911353
12921354 const namespaces = [...grouped.entries()]
@@ -1295,11 +1357,23 @@ export const listWorkspaceNamespaces = internalQuery({ · 2 unmodified lines
12951357 source: value.sourceName,
12961358 sourceId: value.sourceId,
12971359 sourceKind: value.sourceKind,
1298− toolCount: value.paths.length,
1299− samplePaths: value.paths.sort((left, right) => left.localeCompare(right)).slice(0, 3),
1360+ toolCount: value.toolCount,
1361+ samplePaths: value.paths,
13001362 }))
13011363 .sort((left, right) => left.namespace.localeCompare(right.namespace));
13021364
1303− return namespaces.slice(0, normalizeLimit(args.limit));
1365+ return namespaces.slice(0, limit);
13041366 },
13051367 });
1368+
1369+export const listWorkspaceNamespaces = internalAction({
1370+ args: {
1371+ workspaceId: v.string(),
1372+ limit: v.optional(v.number()),
1373+ },
1374+ handler: async (ctx, args) =>
1375+ listWorkspaceNamespacesActionCache.fetch(ctx, {
1376+ workspaceId: args.workspaceId,
1377+ limit: args.limit,
1378+ }),
1379+});
apps/convex/convex/control_plane/tools.ts+116−87
@@ -6,10 +6,12 @@ import { · 5 unmodified lines
66 } from "@executor-v2/schema";
77 import { v } from "convex/values";
88 import * as Schema from "effect/Schema";
9+import { internal } from "../_generated/api";
910
10−import { query } from "../_generated/server";
11+import { action, internalQuery, query } from "../_generated/server";
1112
1213 const decodeSource = Schema.decodeUnknownSync(SourceSchema);
14+const runtimeInternal = internal as any;
1315
1416 const stripConvexSystemFields = (
1517 value: Record<string, unknown>,
@@ -163,7 +165,7 @@ const resolveToolRefHintTableJson = async ( · 147 unmodified lines
163165
164166 const row = await ctx.db
165167 .query("artifactSchemaRefs")
166− .withIndex("by_artifactId_refKey", (q) =>
168+ .withIndex("by_artifactId_refKey", (q: any) =>
167169 q.eq("artifactId", artifactId).eq("refKey", refKey),
168170 )
169171 .unique();
@@ -215,6 +217,28 @@ const toSummary = ( · 45 unmodified lines
215217 };
216218 };
217219
220+const summaryFromIndexRow = (row: Record<string, unknown>): SourceToolSummary => {
221+ const method = asNullableString(row.method)?.toLowerCase() ?? null;
222+ const sourceKind = asNullableString(row.sourceKind);
223+ const normalizedSourceKind: Source["kind"] =
224+ sourceKind === "graphql" || sourceKind === "mcp" || sourceKind === "internal"
225+ ? sourceKind
226+ : "openapi";
227+
228+ return {
229+ sourceId: (asNullableString(row.sourceId) ?? "") as Source["id"],
230+ sourceName: asNullableString(row.sourceName) ?? "",
231+ sourceKind: normalizedSourceKind,
232+ toolId: asNullableString(row.toolId) ?? "unknown",
233+ name: asNullableString(row.name) ?? "Unnamed Tool",
234+ description: asNullableString(row.description),
235+ method: isMethod(method) ? method : "post",
236+ path: asNullableString(row.path) ?? "",
237+ operationPath: asNullableString(row.operationPath ?? null),
238+ operationHash: asNullableString(row.operationHash) ?? "",
239+ };
240+};
241+
218242 const sortTools = (tools: ReadonlyArray<SourceToolSummary>): Array<SourceToolSummary> =>
219243 [...tools].sort((left, right) => {
220244 const leftSource = left.sourceName.toLowerCase();
@@ -234,91 +258,97 @@ const sortTools = (tools: ReadonlyArray<SourceToolSummary>): Array<SourceToolSum · 13 unmodified lines
234258 return left.toolId.localeCompare(right.toolId);
235259 });
236260
237−export const listWorkspaceTools = query({
261+const workspaceToolsPageSize = 250;
262+
263+export const listWorkspaceToolSummaryPage = internalQuery({
238264 args: {
239265 workspaceId: v.string(),
266+ sourceId: v.optional(v.string()),
267+ cursor: v.optional(v.union(v.string(), v.null())),
268+ pageSize: v.optional(v.number()),
240269 },
241− handler: async (ctx, args): Promise<Array<SourceToolSummary>> => {
242− const sourceRows = await ctx.db
243− .query("sources")
244− .withIndex("by_workspaceId", (q) => q.eq("workspaceId", args.workspaceId))
245− .collect();
246−
247− const sources = sourceRows
248− .map((row) => toSource(row as unknown as Record<string, unknown>))
249− .filter((source) => source.enabled);
250− const bindings = await ctx.db
251− .query("sourceArtifactBindings")
252− .withIndex("by_workspaceId", (q) => q.eq("workspaceId", args.workspaceId))
253− .collect();
254− const bindingBySourceId = new Map<string, string>(
255− bindings.map((row) => [String(row.sourceId), String(row.artifactId)] as const),
256− );
257−
258− const summaries: Array<SourceToolSummary> = [];
270+ handler: async (ctx, args) => {
271+ const pageSize = Math.max(1, Math.min(500, Math.floor(args.pageSize ?? workspaceToolsPageSize)));
272+ const paginationOptions = {
273+ cursor: args.cursor ?? null,
274+ numItems: pageSize,
275+ };
259276
260− for (const source of sources) {
261− const artifactId = bindingBySourceId.get(source.id);
262− if (!artifactId) {
263− continue;
264− }
277+ const page = args.sourceId
278+ ? await ctx.db
279+ .query("workspaceToolIndex")
280+ .withIndex("by_workspaceId_sourceId", (q: any) =>
281+ q.eq("workspaceId", args.workspaceId).eq("sourceId", args.sourceId),
282+ )
283+ .paginate(paginationOptions)
284+ : await ctx.db
285+ .query("workspaceToolIndex")
286+ .withIndex("by_workspaceId", (q: any) => q.eq("workspaceId", args.workspaceId))
287+ .paginate(paginationOptions);
288+
289+ const summaries = page.page
290+ .filter((row) => row.status === "active")
291+ .map((row) => summaryFromIndexRow(stripConvexSystemFields(row as unknown as Record<string, unknown>)));
265292
266− const artifactTools = await ctx.db
267− .query("artifactTools")
268− .withIndex("by_artifactId", (q) => q.eq("artifactId", artifactId))
269− .collect();
293+ return {
294+ summaries,
295+ continueCursor: page.continueCursor,
296+ isDone: page.isDone,
297+ };
298+ },
299+});
270300
271− for (const artifactTool of artifactTools) {
272− const row = stripConvexSystemFields(artifactTool as unknown as Record<string, unknown>);
273− summaries.push(toSummary(source, row, asNullableString(row.metadataJson)));
274− }
275− }
301+export const listWorkspaceTools = action({
302+ args: {
303+ workspaceId: v.string(),
304+ },
305+ handler: async (ctx, args): Promise<Array<SourceToolSummary>> => {
306+ const summaries: Array<SourceToolSummary> = [];
307+ let cursor: string | null = null;
308+
309+ do {
310+ const page: {
311+ summaries: Array<SourceToolSummary>;
312+ continueCursor: string | null;
313+ isDone: boolean;
314+ } = await ctx.runQuery(runtimeInternal.control_plane.tools.listWorkspaceToolSummaryPage, {
315+ workspaceId: args.workspaceId,
316+ cursor,
317+ pageSize: workspaceToolsPageSize,
318+ });
319+
320+ summaries.push(...page.summaries);
321+ cursor = page.isDone ? null : page.continueCursor;
322+ } while (cursor);
276323
277324 return sortTools(summaries);
278325 },
279326 });
280327
281−export const listSourceTools = query({
328+export const listSourceTools = action({
282329 args: {
283330 workspaceId: v.string(),
284331 sourceId: v.string(),
285332 },
286333 handler: async (ctx, args): Promise<Array<SourceToolSummary>> => {
287− const sourceRow = await ctx.db
288− .query("sources")
289− .withIndex("by_domainId", (q) => q.eq("id", args.sourceId))
290− .unique();
291−
292− if (!sourceRow || sourceRow.workspaceId !== args.workspaceId) {
293− return [];
294− }
295−
296− const source = toSource(sourceRow as unknown as Record<string, unknown>);
297− if (!source.enabled) {
298− return [];
299− }
300−
301− const binding = await ctx.db
302− .query("sourceArtifactBindings")
303− .withIndex("by_workspaceId_sourceId", (q) =>
304− q.eq("workspaceId", args.workspaceId).eq("sourceId", args.sourceId),
305− )
306− .unique();
307− if (!binding) {
308− return [];
309− }
310−
311− const artifactTools = await ctx.db
312− .query("artifactTools")
313− .withIndex("by_artifactId", (q) => q.eq("artifactId", binding.artifactId))
314− .collect();
315−
316334 const summaries: Array<SourceToolSummary> = [];
317−
318− for (const artifactTool of artifactTools) {
319− const row = stripConvexSystemFields(artifactTool as unknown as Record<string, unknown>);
320− summaries.push(toSummary(source, row, asNullableString(row.metadataJson)));
321− }
335+ let cursor: string | null = null;
336+
337+ do {
338+ const page: {
339+ summaries: Array<SourceToolSummary>;
340+ continueCursor: string | null;
341+ isDone: boolean;
342+ } = await ctx.runQuery(runtimeInternal.control_plane.tools.listWorkspaceToolSummaryPage, {
343+ workspaceId: args.workspaceId,
344+ sourceId: args.sourceId,
345+ cursor,
346+ pageSize: workspaceToolsPageSize,
347+ });
348+
349+ summaries.push(...page.summaries);
350+ cursor = page.isDone ? null : page.continueCursor;
351+ } while (cursor);
322352
323353 return sortTools(summaries);
324354 },
@@ -341,44 +371,43 @@ export const getToolDetail = query({ · 16 unmodified lines
341371 }
342372
343373 const source = toSource(sourceRow as unknown as Record<string, unknown>);
344− const binding = await ctx.db
345− .query("sourceArtifactBindings")
346− .withIndex("by_workspaceId_sourceId", (q) =>
347− q.eq("workspaceId", args.workspaceId).eq("sourceId", args.sourceId),
374+ const indexedRows = await ctx.db
375+ .query("workspaceToolIndex")
376+ .withIndex("by_workspaceId_operationHash", (q: any) =>
377+ q.eq("workspaceId", args.workspaceId).eq("operationHash", args.operationHash),
348378 )
349− .unique();
350− if (!binding) {
379+ .collect();
380+ const indexedRow = indexedRows.find((row) => row.sourceId === args.sourceId) ?? null;
381+ if (!indexedRow) {
351382 return null;
352383 }
353384
354− const artifactRows = await ctx.db
385+ const artifactTool = await ctx.db
355386 .query("artifactTools")
356− .withIndex("by_artifactId", (q) => q.eq("artifactId", binding.artifactId))
357− .collect();
358−
359− const artifactTool = artifactRows.find((row) => row.operationHash === args.operationHash);
387+ .withIndex("by_artifactId_toolId", (q: any) =>
388+ q.eq("artifactId", indexedRow.artifactId).eq("toolId", indexedRow.toolId),
389+ )
390+ .unique();
360391 if (!artifactTool) {
361392 return null;
362393 }
363394
364395 const artifact = await ctx.db
365396 .query("artifacts")
366− .withIndex("by_domainId", (q) => q.eq("id", binding.artifactId))
397+ .withIndex("by_domainId", (q) => q.eq("id", indexedRow.artifactId))
367398 .unique();
368399
369400 const resolvedRefHintTableJson = source.kind === "openapi"
370401 ? await resolveToolRefHintTableJson(
371402 ctx,
372− binding.artifactId,
403+ indexedRow.artifactId,
373404 artifactTool.inputSchemaJson ?? null,
374405 artifactTool.outputSchemaJson ?? null,
375406 )
376407 : null;
377408
378− const summary = toSummary(
379− source,
380− stripConvexSystemFields(artifactTool as unknown as Record<string, unknown>),
381− artifactTool.metadataJson ?? null,
409+ const summary = summaryFromIndexRow(
410+ stripConvexSystemFields(indexedRow as unknown as Record<string, unknown>),
382411 );
383412
384413 return {
apps/convex/convex/convex.config.ts+2−0
@@ -1,7 +1,9 @@
1+import actionCache from "@convex-dev/action-cache/convex.config";
12 import workflow from "@convex-dev/workflow/convex.config.js";
23 import { defineApp } from "convex/server";
34
45 const app = defineApp();
6+app.use(actionCache);
57 app.use(workflow);
68
79 export default app;
apps/convex/convex/executor.test.ts+10−9
@@ -21,6 +21,7 @@ const setup = () => · 20 unmodified lines
2121 "./control_plane/credentials.ts": () => import("./control_plane/credentials"),
2222 "./control_plane/sources.ts": () => import("./control_plane/sources"),
2323 "./control_plane/tool_registry.ts": () => import("./control_plane/tool_registry"),
24+ "./control_plane/tools.ts": () => import("./control_plane/tools"),
2425 "./control_plane/openapi_ingest.ts": () => import("./control_plane/openapi_ingest"),
2526 "./workflow.ts": () => import("./workflow"),
2627 "./controlPlane.ts": () => import("./controlPlane"),
@@ -650,7 +651,7 @@ describe("Convex executor and control-plane", () => { · 623 unmodified lines
650651 );
651652
652653 const workspaceTools = (yield* Effect.tryPromise(() =>
653− t.query(api.controlPlane.listWorkspaceTools, {
654+ t.action(api.controlPlane.listWorkspaceTools, {
654655 workspaceId: "ws_1",
655656 }),
656657 )) as Array<{
@@ -661,7 +662,7 @@ describe("Convex executor and control-plane", () => { · 4 unmodified lines
661662 expect(workspaceTools[0]?.sourceId).toBe("src_1");
662663
663664 const sourceTools = (yield* Effect.tryPromise(() =>
664− t.query(api.controlPlane.listSourceTools, {
665+ t.action(api.controlPlane.listSourceTools, {
665666 workspaceId: "ws_1",
666667 sourceId: "src_1",
667668 }),
@@ -788,7 +789,7 @@ describe("Convex executor and control-plane", () => { · 120 unmodified lines
788789 );
789790
790791 const graphqlSourceTools = (yield* Effect.tryPromise(() =>
791− t.query(api.controlPlane.listSourceTools, {
792+ t.action(api.controlPlane.listSourceTools, {
792793 workspaceId: "ws_1",
793794 sourceId: "src_graphql_1",
794795 }),
@@ -803,7 +804,7 @@ describe("Convex executor and control-plane", () => { · 8 unmodified lines
803804 expect(graphqlSourceTools[0]?.method).toBe("post");
804805
805806 const workspaceToolsWithGraphql = (yield* Effect.tryPromise(() =>
806− t.query(api.controlPlane.listWorkspaceTools, {
807+ t.action(api.controlPlane.listWorkspaceTools, {
807808 workspaceId: "ws_1",
808809 }),
809810 )) as Array<{
@@ -906,7 +907,7 @@ describe("Convex executor and control-plane", () => { · 96 unmodified lines
906907 );
907908
908909 const mcpSourceTools = (yield* Effect.tryPromise(() =>
909− t.query(api.controlPlane.listSourceTools, {
910+ t.action(api.controlPlane.listSourceTools, {
910911 workspaceId: "ws_1",
911912 sourceId: "src_mcp_1",
912913 }),
@@ -920,7 +921,7 @@ describe("Convex executor and control-plane", () => { · 7 unmodified lines
920921 expect(mcpSourceTools[0]?.path).toContain("deepwiki.mcp.search_docs");
921922
922923 const workspaceToolsWithMcp = (yield* Effect.tryPromise(() =>
923− t.query(api.controlPlane.listWorkspaceTools, {
924+ t.action(api.controlPlane.listWorkspaceTools, {
924925 workspaceId: "ws_1",
925926 }),
926927 )) as Array<{
@@ -930,7 +931,7 @@ describe("Convex executor and control-plane", () => { · 3 unmodified lines
930931 expect(workspaceToolsWithMcp.some((tool) => tool.sourceId === "src_mcp_1")).toBe(true);
931932
932933 const sourceToolsFromWrongWorkspace = (yield* Effect.tryPromise(() =>
933− t.query(api.controlPlane.listSourceTools, {
934+ t.action(api.controlPlane.listSourceTools, {
934935 workspaceId: "ws_2",
935936 sourceId: "src_1",
936937 }),
@@ -1168,7 +1169,7 @@ describe("Convex executor and control-plane", () => { · 231 unmodified lines
11681169 expect(sources[0]?.sourceHash).toBeTypeOf("string");
11691170
11701171 const sourceTools = (yield* Effect.tryPromise(() =>
1171− t.query(api.controlPlane.listSourceTools, {
1172+ t.action(api.controlPlane.listSourceTools, {
11721173 workspaceId: "ws_cred",
11731174 sourceId: "src_graphql_cred_1",
11741175 }),
@@ -1361,7 +1362,7 @@ describe("Convex executor and control-plane", () => { · 186 unmodified lines
13611362 expect(sources[0]?.sourceHash).toBeTypeOf("string");
13621363
13631364 const sourceTools = (yield* Effect.tryPromise(() =>
1364− t.query(api.controlPlane.listSourceTools, {
1365+ t.action(api.controlPlane.listSourceTools, {
13651366 workspaceId: "ws_mcp_ingest",
13661367 sourceId: "src_mcp_ingest_1",
13671368 }),
apps/convex/convex/source_tool_registry.ts+1−1
@@ -1729,7 +1729,7 @@ export const createConvexSourceToolRegistry = ( · 1728 unmodified lines
17291729
17301730 const rows = yield* Effect.tryPromise({
17311731 try: async () =>
1732− (await ctx.runQuery(runtimeInternal.control_plane.tool_registry.listWorkspaceNamespaces, {
1732+ (await ctx.runAction(runtimeInternal.control_plane.tool_registry.listWorkspaceNamespaces, {
17331733 workspaceId,
17341734 limit,
17351735 })) as Array<{
apps/convex/package.json+2−1
@@ -9,8 +9,8 @@ · 8 unmodified lines
99 "convex:codegen": "bunx convex codegen"
1010 },
1111 "dependencies": {
12+ "@convex-dev/action-cache": "^0.3.0",
1213 "@convex-dev/workflow": "^0.3.4",
13− "@modelcontextprotocol/sdk": "^1.27.1",
1414 "@executor-v2/confect": "workspace:*",
1515 "@executor-v2/domain": "workspace:*",
1616 "@executor-v2/engine": "workspace:*",
@@ -20,6 +20,7 @@ · 3 unmodified lines
2020 "@executor-v2/runtime-cloudflare-worker-loader": "workspace:*",
2121 "@executor-v2/schema": "workspace:*",
2222 "@executor-v2/sdk": "workspace:*",
23+ "@modelcontextprotocol/sdk": "^1.27.1",
2324 "convex": "catalog:",
2425 "effect": "catalog:",
2526 "jose": "^6.1.0"
apps/web/components/tools/source-management-panel.tsx+48−0
@@ -84,6 +84,14 @@ const catalogTemplates: ReadonlyArray<CatalogTemplate> = [ · 83 unmodified lines
8484 type: "mcp",
8585 endpoint: "https://mcp.neon.tech/mcp",
8686 },
87+ {
88+ id: "axiom-mcp",
89+ name: "Axiom MCP",
90+ summary: "Query, analyze, and monitor your Axiom datasets and logs via MCP.",
91+ providerName: "Axiom",
92+ type: "mcp",
93+ endpoint: "https://mcp.axiom.co/mcp",
94+ },
8795 {
8896 id: "neon-openapi",
8997 name: "Neon API",
@@ -170,6 +178,46 @@ const catalogTemplates: ReadonlyArray<CatalogTemplate> = [ · 80 unmodified lines
170178 type: "openapi",
171179 endpoint: "https://developer.atlassian.com/cloud/jira/platform/swagger-v3.v3.json",
172180 },
181+ {
182+ id: "kubernetes-api",
183+ name: "Kubernetes API",
184+ summary: "Manage pods, deployments, services, and cluster resources.",
185+ providerName: "Kubernetes",
186+ type: "openapi",
187+ endpoint:
188+ "https://raw.githubusercontent.com/kubernetes/kubernetes/master/api/openapi-spec/swagger.json",
189+ faviconUrl: "https://kubernetes.io",
190+ },
191+ {
192+ id: "square-api",
193+ name: "Square API",
194+ summary: "Process payments, manage inventory, customers, and merchant operations.",
195+ providerName: "Square",
196+ type: "openapi",
197+ endpoint:
198+ "https://raw.githubusercontent.com/square/connect-api-specification/master/api.json",
199+ faviconUrl: "https://squareup.com",
200+ },
201+ {
202+ id: "twilio-api",
203+ name: "Twilio API",
204+ summary: "Send SMS, make calls, and manage communication workflows.",
205+ providerName: "Twilio",
206+ type: "openapi",
207+ endpoint:
208+ "https://raw.githubusercontent.com/twilio/twilio-oai/main/spec/json/twilio_api_v2010.json",
209+ faviconUrl: "https://twilio.com",
210+ },
211+ {
212+ id: "box-api",
213+ name: "Box Platform API",
214+ summary: "Manage files, folders, collaborations, and enterprise content workflows.",
215+ providerName: "Box",
216+ type: "openapi",
217+ endpoint:
218+ "https://raw.githubusercontent.com/box/box-openapi/main/openapi.json",
219+ faviconUrl: "https://box.com",
220+ },
173221 ];
174222
175223 // ---------------------------------------------------------------------------
bun.lock+3−0
@@ -35,6 +35,7 @@ · 34 unmodified lines
3535 "apps/convex": {
3636 "name": "@executor-v2/app-convex",
3737 "dependencies": {
38+ "@convex-dev/action-cache": "^0.3.0",
3839 "@convex-dev/workflow": "^0.3.4",
3940 "@executor-v2/confect": "workspace:*",
4041 "@executor-v2/domain": "workspace:*",
@@ -375,6 +376,8 @@ · 334 unmodified lines
375376
376377 "@cloudflare/workers-types": ["@cloudflare/workers-types@4.20260305.0", "", {}, "sha512-sCgPFnQ03SVpC2OVW8wysONLZW/A8hlp9Mq2ckG/h1oId4kr9NawA6vUiOmOjCWRn2hIohejBYVQ+Vu20rCdKA=="],
377378
379+ "@convex-dev/action-cache": ["@convex-dev/action-cache@0.3.0", "", { "peerDependencies": { "convex": "^1.24.8" } }, "sha512-B3GsOgwPv81whdu1mHaMKBwbIYHhNXsv3Fr5oMjynIOrRy1laGLUo9zRWDKWJgtKXfVnn8X/uxYD9RyWmPIbPQ=="],
380+
378381 "@convex-dev/workflow": ["@convex-dev/workflow@0.3.4", "", { "dependencies": { "async-channel": "^0.2.0" }, "peerDependencies": { "@convex-dev/workpool": "^0.3.0", "convex": "^1.24.8", "convex-helpers": "^0.1.99" } }, "sha512-uX9qhEbgkgt+touvgLnyKztussT3m5q4c2XH/iBNQxzk8TlL29F1+UeoCxXA6WibWEpOQLeuFA6/QMVP4tEX1w=="],
379382
380383 "@convex-dev/workpool": ["@convex-dev/workpool@0.3.2", "", { "peerDependencies": { "convex": "^1.31.7", "convex-helpers": "^0.1.94" } }, "sha512-jNPBHLCmZQXaztbBntZgIoM8JhKDmGwaIezZS/zPmX74PzON0Fgysgd9u+NOjJ04Dx2pN8ppBH9ElpsKxCOBpA=="],
legacy/executor/apps/web/src/lib/catalog-collections.ts+61−0
@@ -53,6 +53,19 @@ export const HARD_CODED_CATALOG_ITEMS: CatalogCollectionItem[] = [ · 52 unmodified lines
5353 rank: 0.2,
5454 addedAt: "2026-02-20",
5555 },
56+ {
57+ id: "axiom-mcp",
58+ name: "Axiom MCP",
59+ summary: "Query, analyze, and monitor your Axiom datasets and logs via MCP.",
60+ specUrl: "https://mcp.axiom.co/mcp",
61+ sourceType: "mcp",
62+ originUrl: "https://axiom.co",
63+ providerName: "Axiom",
64+ categories: "observability",
65+ version: "latest",
66+ rank: 0.25,
67+ addedAt: "2026-03-01",
68+ },
5669 {
5770 id: "neon-openapi",
5871 name: "Neon API",
@@ -175,4 +188,52 @@ export const HARD_CODED_CATALOG_ITEMS: CatalogCollectionItem[] = [ · 116 unmodified lines
175188 rank: 8,
176189 addedAt: "2025-11-15",
177190 },
191+ {
192+ id: "kubernetes-api",
193+ name: "Kubernetes API",
194+ summary: "Manage pods, deployments, services, and cluster resources.",
195+ specUrl: "https://raw.githubusercontent.com/kubernetes/kubernetes/master/api/openapi-spec/swagger.json",
196+ originUrl: "https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/",
197+ providerName: "Kubernetes",
198+ categories: "infrastructure",
199+ version: "latest",
200+ rank: 9,
201+ addedAt: "2026-03-01",
202+ },
203+ {
204+ id: "square-api",
205+ name: "Square API",
206+ summary: "Process payments, manage inventory, customers, and merchant operations.",
207+ specUrl: "https://raw.githubusercontent.com/square/connect-api-specification/master/api.json",
208+ originUrl: "https://developer.squareup.com/reference/square",
209+ providerName: "Square",
210+ categories: "payments",
211+ version: "latest",
212+ rank: 10,
213+ addedAt: "2026-03-01",
214+ },
215+ {
216+ id: "twilio-api",
217+ name: "Twilio API",
218+ summary: "Send SMS, make calls, and manage communication workflows.",
219+ specUrl: "https://raw.githubusercontent.com/twilio/twilio-oai/main/spec/json/twilio_api_v2010.json",
220+ originUrl: "https://www.twilio.com/docs/usage/api",
221+ providerName: "Twilio",
222+ categories: "communications",
223+ version: "v2010",
224+ rank: 11,
225+ addedAt: "2026-03-01",
226+ },
227+ {
228+ id: "box-api",
229+ name: "Box Platform API",
230+ summary: "Manage files, folders, collaborations, and enterprise content workflows.",
231+ specUrl: "https://raw.githubusercontent.com/box/box-openapi/main/openapi.json",
232+ originUrl: "https://developer.box.com/reference/",
233+ providerName: "Box",
234+ categories: "developer-tools",
235+ version: "latest",
236+ rank: 12,
237+ addedAt: "2026-03-01",
238+ },
178239 ];