History

match sketch

Rhys Sullivan committed 8 months ago

Commit 3934a4e1 parent d029a7917 files · +5667 −647Browse files

Unified · Split

17 files
  1. packages/control-plane/drizzle/20260307090915_blue_bullseye/migration.sql +70 −0
  2. packages/control-plane/drizzle/20260307090915_blue_bullseye/snapshot.json +3122 −0
  3. packages/control-plane/src/persistence/index.ts +2 −0
  4. packages/control-plane/src/persistence/repos/index.ts +1 −0
  5. packages/control-plane/src/persistence/repos/sources-repo.ts +61 −1
  6. packages/control-plane/src/persistence/repos/tool-artifacts-repo.ts +441 −0
  7. packages/control-plane/src/persistence/schema.ts +139 −0
  8. packages/control-plane/src/runtime/source-auth-service.ts +79 −14
  9. packages/control-plane/src/runtime/sources-operations.ts +92 −6
  10. packages/control-plane/src/runtime/tool-artifacts.ts +393 −0
  11. packages/control-plane/src/runtime/workspace-execution-environment.test.ts +410 −0
  12. packages/control-plane/src/runtime/workspace-execution-environment.ts +433 −216
  13. packages/control-plane/src/schema/index.ts +1 −0
  14. packages/control-plane/src/schema/models/tool-artifact.ts +159 −0
  15. packages/kitchen-sink/package.json +1 −0
  16. packages/kitchen-sink/src/source-runtime-sketch-types.ts +0 −128
  17. packages/kitchen-sink/src/source-runtime.test.ts +263 −282
packages/control-plane/drizzle/20260307090915_blue_bullseye/migration.sql+70−0
@@ -0,0 +1,70 @@
1+CREATE TABLE "tool_artifact_parameters" (
2+ "workspace_id" text,
3+ "path" text,
4+ "position" bigint,
5+ "name" text NOT NULL,
6+ "location" text NOT NULL,
7+ "required" boolean NOT NULL,
8+ CONSTRAINT "tool_artifact_parameters_pkey" PRIMARY KEY("workspace_id","path","position"),
9+ CONSTRAINT "tool_artifact_parameters_location_check" CHECK ("location" in ('path', 'query', 'header', 'cookie'))
10+);
11+--> statement-breakpoint
12+CREATE TABLE "tool_artifact_ref_hint_keys" (
13+ "workspace_id" text,
14+ "path" text,
15+ "position" bigint,
16+ "ref_hint_key" text NOT NULL,
17+ CONSTRAINT "tool_artifact_ref_hint_keys_pkey" PRIMARY KEY("workspace_id","path","position")
18+);
19+--> statement-breakpoint
20+CREATE TABLE "tool_artifact_request_body_content_types" (
21+ "workspace_id" text,
22+ "path" text,
23+ "position" bigint,
24+ "content_type" text NOT NULL,
25+ CONSTRAINT "tool_artifact_request_body_content_types_pkey" PRIMARY KEY("workspace_id","path","position")
26+);
27+--> statement-breakpoint
28+CREATE TABLE "tool_artifacts" (
29+ "workspace_id" text,
30+ "path" text,
31+ "tool_id" text NOT NULL,
32+ "source_id" text NOT NULL,
33+ "title" text,
34+ "description" text,
35+ "search_namespace" text NOT NULL,
36+ "search_text" text NOT NULL,
37+ "input_schema_json" text,
38+ "output_schema_json" text,
39+ "provider_kind" text NOT NULL,
40+ "mcp_tool_name" text,
41+ "openapi_method" text,
42+ "openapi_path_template" text,
43+ "openapi_operation_hash" text,
44+ "openapi_request_body_required" boolean,
45+ "created_at" bigint NOT NULL,
46+ "updated_at" bigint NOT NULL,
47+ CONSTRAINT "tool_artifacts_pkey" PRIMARY KEY("workspace_id","path"),
48+ CONSTRAINT "tool_artifacts_provider_kind_check" CHECK ("provider_kind" in ('mcp', 'openapi')),
49+ CONSTRAINT "tool_artifacts_mcp_shape_check" CHECK ("provider_kind" <> 'mcp'
50+ or (
51+ "mcp_tool_name" is not null
52+ and "openapi_method" is null
53+ and "openapi_path_template" is null
54+ and "openapi_operation_hash" is null
55+ and "openapi_request_body_required" is null
56+ )),
57+ CONSTRAINT "tool_artifacts_openapi_shape_check" CHECK ("provider_kind" <> 'openapi'
58+ or (
59+ "mcp_tool_name" is null
60+ and "openapi_method" in ('get', 'put', 'post', 'delete', 'patch', 'head', 'options', 'trace')
61+ and "openapi_path_template" is not null
62+ and "openapi_operation_hash" is not null
63+ ))
64+);
65+--> statement-breakpoint
66+CREATE INDEX "tool_artifact_parameters_lookup_idx" ON "tool_artifact_parameters" ("workspace_id","path","position");--> statement-breakpoint
67+CREATE INDEX "tool_artifact_ref_hint_keys_lookup_idx" ON "tool_artifact_ref_hint_keys" ("workspace_id","path","position");--> statement-breakpoint
68+CREATE INDEX "tool_artifact_request_body_content_types_lookup_idx" ON "tool_artifact_request_body_content_types" ("workspace_id","path","position");--> statement-breakpoint
69+CREATE INDEX "tool_artifacts_workspace_source_idx" ON "tool_artifacts" ("workspace_id","source_id","updated_at","path");--> statement-breakpoint
70+CREATE INDEX "tool_artifacts_workspace_namespace_idx" ON "tool_artifacts" ("workspace_id","search_namespace","path");
packages/control-plane/drizzle/20260307090915_blue_bullseye/snapshot.json+3122−0
@@ -0,0 +1,3122 @@
1+{
2+ "version": "8",
3+ "dialect": "postgres",
4+ "id": "3e9aa0ec-194c-4364-9e57-4539db8fb2ad",
5+ "prevIds": [
6+ "1141497b-c500-44ff-87db-6a440062becb"
7+ ],
8+ "ddl": [
9+ {
10+ "isRlsEnabled": false,
11+ "name": "accounts",
12+ "entityType": "tables",
13+ "schema": "public"
14+ },
15+ {
16+ "isRlsEnabled": false,
17+ "name": "execution_interactions",
18+ "entityType": "tables",
19+ "schema": "public"
20+ },
21+ {
22+ "isRlsEnabled": false,
23+ "name": "executions",
24+ "entityType": "tables",
25+ "schema": "public"
26+ },
27+ {
28+ "isRlsEnabled": false,
29+ "name": "local_installations",
30+ "entityType": "tables",
31+ "schema": "public"
32+ },
33+ {
34+ "isRlsEnabled": false,
35+ "name": "organization_memberships",
36+ "entityType": "tables",
37+ "schema": "public"
38+ },
39+ {
40+ "isRlsEnabled": false,
41+ "name": "organizations",
42+ "entityType": "tables",
43+ "schema": "public"
44+ },
45+ {
46+ "isRlsEnabled": false,
47+ "name": "policies",
48+ "entityType": "tables",
49+ "schema": "public"
50+ },
51+ {
52+ "isRlsEnabled": false,
53+ "name": "secret_materials",
54+ "entityType": "tables",
55+ "schema": "public"
56+ },
57+ {
58+ "isRlsEnabled": false,
59+ "name": "source_auth_sessions",
60+ "entityType": "tables",
61+ "schema": "public"
62+ },
63+ {
64+ "isRlsEnabled": false,
65+ "name": "source_credential_bindings",
66+ "entityType": "tables",
67+ "schema": "public"
68+ },
69+ {
70+ "isRlsEnabled": false,
71+ "name": "sources",
72+ "entityType": "tables",
73+ "schema": "public"
74+ },
75+ {
76+ "isRlsEnabled": false,
77+ "name": "tool_artifact_parameters",
78+ "entityType": "tables",
79+ "schema": "public"
80+ },
81+ {
82+ "isRlsEnabled": false,
83+ "name": "tool_artifact_ref_hint_keys",
84+ "entityType": "tables",
85+ "schema": "public"
86+ },
87+ {
88+ "isRlsEnabled": false,
89+ "name": "tool_artifact_request_body_content_types",
90+ "entityType": "tables",
91+ "schema": "public"
92+ },
93+ {
94+ "isRlsEnabled": false,
95+ "name": "tool_artifacts",
96+ "entityType": "tables",
97+ "schema": "public"
98+ },
99+ {
100+ "isRlsEnabled": false,
101+ "name": "workspaces",
102+ "entityType": "tables",
103+ "schema": "public"
104+ },
105+ {
106+ "type": "text",
107+ "typeSchema": null,
108+ "notNull": true,
109+ "dimensions": 0,
110+ "default": null,
111+ "generated": null,
112+ "identity": null,
113+ "name": "id",
114+ "entityType": "columns",
115+ "schema": "public",
116+ "table": "accounts"
117+ },
118+ {
119+ "type": "text",
120+ "typeSchema": null,
121+ "notNull": true,
122+ "dimensions": 0,
123+ "default": null,
124+ "generated": null,
125+ "identity": null,
126+ "name": "provider",
127+ "entityType": "columns",
128+ "schema": "public",
129+ "table": "accounts"
130+ },
131+ {
132+ "type": "text",
133+ "typeSchema": null,
134+ "notNull": true,
135+ "dimensions": 0,
136+ "default": null,
137+ "generated": null,
138+ "identity": null,
139+ "name": "subject",
140+ "entityType": "columns",
141+ "schema": "public",
142+ "table": "accounts"
143+ },
144+ {
145+ "type": "text",
146+ "typeSchema": null,
147+ "notNull": false,
148+ "dimensions": 0,
149+ "default": null,
150+ "generated": null,
151+ "identity": null,
152+ "name": "email",
153+ "entityType": "columns",
154+ "schema": "public",
155+ "table": "accounts"
156+ },
157+ {
158+ "type": "text",
159+ "typeSchema": null,
160+ "notNull": false,
161+ "dimensions": 0,
162+ "default": null,
163+ "generated": null,
164+ "identity": null,
165+ "name": "display_name",
166+ "entityType": "columns",
167+ "schema": "public",
168+ "table": "accounts"
169+ },
170+ {
171+ "type": "bigint",
172+ "typeSchema": null,
173+ "notNull": true,
174+ "dimensions": 0,
175+ "default": null,
176+ "generated": null,
177+ "identity": null,
178+ "name": "created_at",
179+ "entityType": "columns",
180+ "schema": "public",
181+ "table": "accounts"
182+ },
183+ {
184+ "type": "bigint",
185+ "typeSchema": null,
186+ "notNull": true,
187+ "dimensions": 0,
188+ "default": null,
189+ "generated": null,
190+ "identity": null,
191+ "name": "updated_at",
192+ "entityType": "columns",
193+ "schema": "public",
194+ "table": "accounts"
195+ },
196+ {
197+ "type": "text",
198+ "typeSchema": null,
199+ "notNull": true,
200+ "dimensions": 0,
201+ "default": null,
202+ "generated": null,
203+ "identity": null,
204+ "name": "id",
205+ "entityType": "columns",
206+ "schema": "public",
207+ "table": "execution_interactions"
208+ },
209+ {
210+ "type": "text",
211+ "typeSchema": null,
212+ "notNull": true,
213+ "dimensions": 0,
214+ "default": null,
215+ "generated": null,
216+ "identity": null,
217+ "name": "execution_id",
218+ "entityType": "columns",
219+ "schema": "public",
220+ "table": "execution_interactions"
221+ },
222+ {
223+ "type": "text",
224+ "typeSchema": null,
225+ "notNull": true,
226+ "dimensions": 0,
227+ "default": null,
228+ "generated": null,
229+ "identity": null,
230+ "name": "status",
231+ "entityType": "columns",
232+ "schema": "public",
233+ "table": "execution_interactions"
234+ },
235+ {
236+ "type": "text",
237+ "typeSchema": null,
238+ "notNull": true,
239+ "dimensions": 0,
240+ "default": null,
241+ "generated": null,
242+ "identity": null,
243+ "name": "kind",
244+ "entityType": "columns",
245+ "schema": "public",
246+ "table": "execution_interactions"
247+ },
248+ {
249+ "type": "text",
250+ "typeSchema": null,
251+ "notNull": true,
252+ "dimensions": 0,
253+ "default": null,
254+ "generated": null,
255+ "identity": null,
256+ "name": "payload_json",
257+ "entityType": "columns",
258+ "schema": "public",
259+ "table": "execution_interactions"
260+ },
261+ {
262+ "type": "text",
263+ "typeSchema": null,
264+ "notNull": false,
265+ "dimensions": 0,
266+ "default": null,
267+ "generated": null,
268+ "identity": null,
269+ "name": "response_json",
270+ "entityType": "columns",
271+ "schema": "public",
272+ "table": "execution_interactions"
273+ },
274+ {
275+ "type": "bigint",
276+ "typeSchema": null,
277+ "notNull": true,
278+ "dimensions": 0,
279+ "default": null,
280+ "generated": null,
281+ "identity": null,
282+ "name": "created_at",
283+ "entityType": "columns",
284+ "schema": "public",
285+ "table": "execution_interactions"
286+ },
287+ {
288+ "type": "bigint",
289+ "typeSchema": null,
290+ "notNull": true,
291+ "dimensions": 0,
292+ "default": null,
293+ "generated": null,
294+ "identity": null,
295+ "name": "updated_at",
296+ "entityType": "columns",
297+ "schema": "public",
298+ "table": "execution_interactions"
299+ },
300+ {
301+ "type": "text",
302+ "typeSchema": null,
303+ "notNull": true,
304+ "dimensions": 0,
305+ "default": null,
306+ "generated": null,
307+ "identity": null,
308+ "name": "id",
309+ "entityType": "columns",
310+ "schema": "public",
311+ "table": "executions"
312+ },
313+ {
314+ "type": "text",
315+ "typeSchema": null,
316+ "notNull": true,
317+ "dimensions": 0,
318+ "default": null,
319+ "generated": null,
320+ "identity": null,
321+ "name": "workspace_id",
322+ "entityType": "columns",
323+ "schema": "public",
324+ "table": "executions"
325+ },
326+ {
327+ "type": "text",
328+ "typeSchema": null,
329+ "notNull": true,
330+ "dimensions": 0,
331+ "default": null,
332+ "generated": null,
333+ "identity": null,
334+ "name": "created_by_account_id",
335+ "entityType": "columns",
336+ "schema": "public",
337+ "table": "executions"
338+ },
339+ {
340+ "type": "text",
341+ "typeSchema": null,
342+ "notNull": true,
343+ "dimensions": 0,
344+ "default": null,
345+ "generated": null,
346+ "identity": null,
347+ "name": "status",
348+ "entityType": "columns",
349+ "schema": "public",
350+ "table": "executions"
351+ },
352+ {
353+ "type": "text",
354+ "typeSchema": null,
355+ "notNull": true,
356+ "dimensions": 0,
357+ "default": null,
358+ "generated": null,
359+ "identity": null,
360+ "name": "code",
361+ "entityType": "columns",
362+ "schema": "public",
363+ "table": "executions"
364+ },
365+ {
366+ "type": "text",
367+ "typeSchema": null,
368+ "notNull": false,
369+ "dimensions": 0,
370+ "default": null,
371+ "generated": null,
372+ "identity": null,
373+ "name": "result_json",
374+ "entityType": "columns",
375+ "schema": "public",
376+ "table": "executions"
377+ },
378+ {
379+ "type": "text",
380+ "typeSchema": null,
381+ "notNull": false,
382+ "dimensions": 0,
383+ "default": null,
384+ "generated": null,
385+ "identity": null,
386+ "name": "error_text",
387+ "entityType": "columns",
388+ "schema": "public",
389+ "table": "executions"
390+ },
391+ {
392+ "type": "text",
393+ "typeSchema": null,
394+ "notNull": false,
395+ "dimensions": 0,
396+ "default": null,
397+ "generated": null,
398+ "identity": null,
399+ "name": "logs_json",
400+ "entityType": "columns",
401+ "schema": "public",
402+ "table": "executions"
403+ },
404+ {
405+ "type": "bigint",
406+ "typeSchema": null,
407+ "notNull": false,
408+ "dimensions": 0,
409+ "default": null,
410+ "generated": null,
411+ "identity": null,
412+ "name": "started_at",
413+ "entityType": "columns",
414+ "schema": "public",
415+ "table": "executions"
416+ },
417+ {
418+ "type": "bigint",
419+ "typeSchema": null,
420+ "notNull": false,
421+ "dimensions": 0,
422+ "default": null,
423+ "generated": null,
424+ "identity": null,
425+ "name": "completed_at",
426+ "entityType": "columns",
427+ "schema": "public",
428+ "table": "executions"
429+ },
430+ {
431+ "type": "bigint",
432+ "typeSchema": null,
433+ "notNull": true,
434+ "dimensions": 0,
435+ "default": null,
436+ "generated": null,
437+ "identity": null,
438+ "name": "created_at",
439+ "entityType": "columns",
440+ "schema": "public",
441+ "table": "executions"
442+ },
443+ {
444+ "type": "bigint",
445+ "typeSchema": null,
446+ "notNull": true,
447+ "dimensions": 0,
448+ "default": null,
449+ "generated": null,
450+ "identity": null,
451+ "name": "updated_at",
452+ "entityType": "columns",
453+ "schema": "public",
454+ "table": "executions"
455+ },
456+ {
457+ "type": "text",
458+ "typeSchema": null,
459+ "notNull": true,
460+ "dimensions": 0,
461+ "default": null,
462+ "generated": null,
463+ "identity": null,
464+ "name": "id",
465+ "entityType": "columns",
466+ "schema": "public",
467+ "table": "local_installations"
468+ },
469+ {
470+ "type": "text",
471+ "typeSchema": null,
472+ "notNull": true,
473+ "dimensions": 0,
474+ "default": null,
475+ "generated": null,
476+ "identity": null,
477+ "name": "account_id",
478+ "entityType": "columns",
479+ "schema": "public",
480+ "table": "local_installations"
481+ },
482+ {
483+ "type": "text",
484+ "typeSchema": null,
485+ "notNull": true,
486+ "dimensions": 0,
487+ "default": null,
488+ "generated": null,
489+ "identity": null,
490+ "name": "organization_id",
491+ "entityType": "columns",
492+ "schema": "public",
493+ "table": "local_installations"
494+ },
495+ {
496+ "type": "text",
497+ "typeSchema": null,
498+ "notNull": true,
499+ "dimensions": 0,
500+ "default": null,
501+ "generated": null,
502+ "identity": null,
503+ "name": "workspace_id",
504+ "entityType": "columns",
505+ "schema": "public",
506+ "table": "local_installations"
507+ },
508+ {
509+ "type": "bigint",
510+ "typeSchema": null,
511+ "notNull": true,
512+ "dimensions": 0,
513+ "default": null,
514+ "generated": null,
515+ "identity": null,
516+ "name": "created_at",
517+ "entityType": "columns",
518+ "schema": "public",
519+ "table": "local_installations"
520+ },
521+ {
522+ "type": "bigint",
523+ "typeSchema": null,
524+ "notNull": true,
525+ "dimensions": 0,
526+ "default": null,
527+ "generated": null,
528+ "identity": null,
529+ "name": "updated_at",
530+ "entityType": "columns",
531+ "schema": "public",
532+ "table": "local_installations"
533+ },
534+ {
535+ "type": "text",
536+ "typeSchema": null,
537+ "notNull": true,
538+ "dimensions": 0,
539+ "default": null,
540+ "generated": null,
541+ "identity": null,
542+ "name": "id",
543+ "entityType": "columns",
544+ "schema": "public",
545+ "table": "organization_memberships"
546+ },
547+ {
548+ "type": "text",
549+ "typeSchema": null,
550+ "notNull": true,
551+ "dimensions": 0,
552+ "default": null,
553+ "generated": null,
554+ "identity": null,
555+ "name": "organization_id",
556+ "entityType": "columns",
557+ "schema": "public",
558+ "table": "organization_memberships"
559+ },
560+ {
561+ "type": "text",
562+ "typeSchema": null,
563+ "notNull": true,
564+ "dimensions": 0,
565+ "default": null,
566+ "generated": null,
567+ "identity": null,
568+ "name": "account_id",
569+ "entityType": "columns",
570+ "schema": "public",
571+ "table": "organization_memberships"
572+ },
573+ {
574+ "type": "text",
575+ "typeSchema": null,
576+ "notNull": true,
577+ "dimensions": 0,
578+ "default": null,
579+ "generated": null,
580+ "identity": null,
581+ "name": "role",
582+ "entityType": "columns",
583+ "schema": "public",
584+ "table": "organization_memberships"
585+ },
586+ {
587+ "type": "text",
588+ "typeSchema": null,
589+ "notNull": true,
590+ "dimensions": 0,
591+ "default": null,
592+ "generated": null,
593+ "identity": null,
594+ "name": "status",
595+ "entityType": "columns",
596+ "schema": "public",
597+ "table": "organization_memberships"
598+ },
599+ {
600+ "type": "boolean",
601+ "typeSchema": null,
602+ "notNull": true,
603+ "dimensions": 0,
604+ "default": null,
605+ "generated": null,
606+ "identity": null,
607+ "name": "billable",
608+ "entityType": "columns",
609+ "schema": "public",
610+ "table": "organization_memberships"
611+ },
612+ {
613+ "type": "text",
614+ "typeSchema": null,
615+ "notNull": false,
616+ "dimensions": 0,
617+ "default": null,
618+ "generated": null,
619+ "identity": null,
620+ "name": "invited_by_account_id",
621+ "entityType": "columns",
622+ "schema": "public",
623+ "table": "organization_memberships"
624+ },
625+ {
626+ "type": "bigint",
627+ "typeSchema": null,
628+ "notNull": false,
629+ "dimensions": 0,
630+ "default": null,
631+ "generated": null,
632+ "identity": null,
633+ "name": "joined_at",
634+ "entityType": "columns",
635+ "schema": "public",
636+ "table": "organization_memberships"
637+ },
638+ {
639+ "type": "bigint",
640+ "typeSchema": null,
641+ "notNull": true,
642+ "dimensions": 0,
643+ "default": null,
644+ "generated": null,
645+ "identity": null,
646+ "name": "created_at",
647+ "entityType": "columns",
648+ "schema": "public",
649+ "table": "organization_memberships"
650+ },
651+ {
652+ "type": "bigint",
653+ "typeSchema": null,
654+ "notNull": true,
655+ "dimensions": 0,
656+ "default": null,
657+ "generated": null,
658+ "identity": null,
659+ "name": "updated_at",
660+ "entityType": "columns",
661+ "schema": "public",
662+ "table": "organization_memberships"
663+ },
664+ {
665+ "type": "text",
666+ "typeSchema": null,
667+ "notNull": true,
668+ "dimensions": 0,
669+ "default": null,
670+ "generated": null,
671+ "identity": null,
672+ "name": "id",
673+ "entityType": "columns",
674+ "schema": "public",
675+ "table": "organizations"
676+ },
677+ {
678+ "type": "text",
679+ "typeSchema": null,
680+ "notNull": true,
681+ "dimensions": 0,
682+ "default": null,
683+ "generated": null,
684+ "identity": null,
685+ "name": "slug",
686+ "entityType": "columns",
687+ "schema": "public",
688+ "table": "organizations"
689+ },
690+ {
691+ "type": "text",
692+ "typeSchema": null,
693+ "notNull": true,
694+ "dimensions": 0,
695+ "default": null,
696+ "generated": null,
697+ "identity": null,
698+ "name": "name",
699+ "entityType": "columns",
700+ "schema": "public",
701+ "table": "organizations"
702+ },
703+ {
704+ "type": "text",
705+ "typeSchema": null,
706+ "notNull": true,
707+ "dimensions": 0,
708+ "default": null,
709+ "generated": null,
710+ "identity": null,
711+ "name": "status",
712+ "entityType": "columns",
713+ "schema": "public",
714+ "table": "organizations"
715+ },
716+ {
717+ "type": "text",
718+ "typeSchema": null,
719+ "notNull": false,
720+ "dimensions": 0,
721+ "default": null,
722+ "generated": null,
723+ "identity": null,
724+ "name": "created_by_account_id",
725+ "entityType": "columns",
726+ "schema": "public",
727+ "table": "organizations"
728+ },
729+ {
730+ "type": "bigint",
731+ "typeSchema": null,
732+ "notNull": true,
733+ "dimensions": 0,
734+ "default": null,
735+ "generated": null,
736+ "identity": null,
737+ "name": "created_at",
738+ "entityType": "columns",
739+ "schema": "public",
740+ "table": "organizations"
741+ },
742+ {
743+ "type": "bigint",
744+ "typeSchema": null,
745+ "notNull": true,
746+ "dimensions": 0,
747+ "default": null,
748+ "generated": null,
749+ "identity": null,
750+ "name": "updated_at",
751+ "entityType": "columns",
752+ "schema": "public",
753+ "table": "organizations"
754+ },
755+ {
756+ "type": "text",
757+ "typeSchema": null,
758+ "notNull": true,
759+ "dimensions": 0,
760+ "default": null,
761+ "generated": null,
762+ "identity": null,
763+ "name": "id",
764+ "entityType": "columns",
765+ "schema": "public",
766+ "table": "policies"
767+ },
768+ {
769+ "type": "text",
770+ "typeSchema": null,
771+ "notNull": true,
772+ "dimensions": 0,
773+ "default": null,
774+ "generated": null,
775+ "identity": null,
776+ "name": "workspace_id",
777+ "entityType": "columns",
778+ "schema": "public",
779+ "table": "policies"
780+ },
781+ {
782+ "type": "text",
783+ "typeSchema": null,
784+ "notNull": false,
785+ "dimensions": 0,
786+ "default": null,
787+ "generated": null,
788+ "identity": null,
789+ "name": "target_account_id",
790+ "entityType": "columns",
791+ "schema": "public",
792+ "table": "policies"
793+ },
794+ {
795+ "type": "text",
796+ "typeSchema": null,
797+ "notNull": false,
798+ "dimensions": 0,
799+ "default": null,
800+ "generated": null,
801+ "identity": null,
802+ "name": "client_id",
803+ "entityType": "columns",
804+ "schema": "public",
805+ "table": "policies"
806+ },
807+ {
808+ "type": "text",
809+ "typeSchema": null,
810+ "notNull": true,
811+ "dimensions": 0,
812+ "default": null,
813+ "generated": null,
814+ "identity": null,
815+ "name": "resource_type",
816+ "entityType": "columns",
817+ "schema": "public",
818+ "table": "policies"
819+ },
820+ {
821+ "type": "text",
822+ "typeSchema": null,
823+ "notNull": true,
824+ "dimensions": 0,
825+ "default": null,
826+ "generated": null,
827+ "identity": null,
828+ "name": "resource_pattern",
829+ "entityType": "columns",
830+ "schema": "public",
831+ "table": "policies"
832+ },
833+ {
834+ "type": "text",
835+ "typeSchema": null,
836+ "notNull": true,
837+ "dimensions": 0,
838+ "default": null,
839+ "generated": null,
840+ "identity": null,
841+ "name": "match_type",
842+ "entityType": "columns",
843+ "schema": "public",
844+ "table": "policies"
845+ },
846+ {
847+ "type": "text",
848+ "typeSchema": null,
849+ "notNull": true,
850+ "dimensions": 0,
851+ "default": null,
852+ "generated": null,
853+ "identity": null,
854+ "name": "effect",
855+ "entityType": "columns",
856+ "schema": "public",
857+ "table": "policies"
858+ },
859+ {
860+ "type": "text",
861+ "typeSchema": null,
862+ "notNull": true,
863+ "dimensions": 0,
864+ "default": null,
865+ "generated": null,
866+ "identity": null,
867+ "name": "approval_mode",
868+ "entityType": "columns",
869+ "schema": "public",
870+ "table": "policies"
871+ },
872+ {
873+ "type": "text",
874+ "typeSchema": null,
875+ "notNull": false,
876+ "dimensions": 0,
877+ "default": null,
878+ "generated": null,
879+ "identity": null,
880+ "name": "argument_conditions_json",
881+ "entityType": "columns",
882+ "schema": "public",
883+ "table": "policies"
884+ },
885+ {
886+ "type": "bigint",
887+ "typeSchema": null,
888+ "notNull": true,
889+ "dimensions": 0,
890+ "default": null,
891+ "generated": null,
892+ "identity": null,
893+ "name": "priority",
894+ "entityType": "columns",
895+ "schema": "public",
896+ "table": "policies"
897+ },
898+ {
899+ "type": "boolean",
900+ "typeSchema": null,
901+ "notNull": true,
902+ "dimensions": 0,
903+ "default": null,
904+ "generated": null,
905+ "identity": null,
906+ "name": "enabled",
907+ "entityType": "columns",
908+ "schema": "public",
909+ "table": "policies"
910+ },
911+ {
912+ "type": "bigint",
913+ "typeSchema": null,
914+ "notNull": true,
915+ "dimensions": 0,
916+ "default": null,
917+ "generated": null,
918+ "identity": null,
919+ "name": "created_at",
920+ "entityType": "columns",
921+ "schema": "public",
922+ "table": "policies"
923+ },
924+ {
925+ "type": "bigint",
926+ "typeSchema": null,
927+ "notNull": true,
928+ "dimensions": 0,
929+ "default": null,
930+ "generated": null,
931+ "identity": null,
932+ "name": "updated_at",
933+ "entityType": "columns",
934+ "schema": "public",
935+ "table": "policies"
936+ },
937+ {
938+ "type": "text",
939+ "typeSchema": null,
940+ "notNull": true,
941+ "dimensions": 0,
942+ "default": null,
943+ "generated": null,
944+ "identity": null,
945+ "name": "id",
946+ "entityType": "columns",
947+ "schema": "public",
948+ "table": "secret_materials"
949+ },
950+ {
951+ "type": "text",
952+ "typeSchema": null,
953+ "notNull": true,
954+ "dimensions": 0,
955+ "default": null,
956+ "generated": null,
957+ "identity": null,
958+ "name": "purpose",
959+ "entityType": "columns",
960+ "schema": "public",
961+ "table": "secret_materials"
962+ },
963+ {
964+ "type": "text",
965+ "typeSchema": null,
966+ "notNull": true,
967+ "dimensions": 0,
968+ "default": null,
969+ "generated": null,
970+ "identity": null,
971+ "name": "value",
972+ "entityType": "columns",
973+ "schema": "public",
974+ "table": "secret_materials"
975+ },
976+ {
977+ "type": "bigint",
978+ "typeSchema": null,
979+ "notNull": true,
980+ "dimensions": 0,
981+ "default": null,
982+ "generated": null,
983+ "identity": null,
984+ "name": "created_at",
985+ "entityType": "columns",
986+ "schema": "public",
987+ "table": "secret_materials"
988+ },
989+ {
990+ "type": "bigint",
991+ "typeSchema": null,
992+ "notNull": true,
993+ "dimensions": 0,
994+ "default": null,
995+ "generated": null,
996+ "identity": null,
997+ "name": "updated_at",
998+ "entityType": "columns",
999+ "schema": "public",
1000+ "table": "secret_materials"
1001+ },
1002+ {
1003+ "type": "text",
1004+ "typeSchema": null,
1005+ "notNull": true,
1006+ "dimensions": 0,
1007+ "default": null,
1008+ "generated": null,
1009+ "identity": null,
1010+ "name": "id",
1011+ "entityType": "columns",
1012+ "schema": "public",
1013+ "table": "source_auth_sessions"
1014+ },
1015+ {
1016+ "type": "text",
1017+ "typeSchema": null,
1018+ "notNull": true,
1019+ "dimensions": 0,
1020+ "default": null,
1021+ "generated": null,
1022+ "identity": null,
1023+ "name": "workspace_id",
1024+ "entityType": "columns",
1025+ "schema": "public",
1026+ "table": "source_auth_sessions"
1027+ },
1028+ {
1029+ "type": "text",
1030+ "typeSchema": null,
1031+ "notNull": true,
1032+ "dimensions": 0,
1033+ "default": null,
1034+ "generated": null,
1035+ "identity": null,
1036+ "name": "source_id",
1037+ "entityType": "columns",
1038+ "schema": "public",
1039+ "table": "source_auth_sessions"
1040+ },
1041+ {
1042+ "type": "text",
1043+ "typeSchema": null,
1044+ "notNull": false,
1045+ "dimensions": 0,
1046+ "default": null,
1047+ "generated": null,
1048+ "identity": null,
1049+ "name": "execution_id",
1050+ "entityType": "columns",
1051+ "schema": "public",
1052+ "table": "source_auth_sessions"
1053+ },
1054+ {
1055+ "type": "text",
1056+ "typeSchema": null,
1057+ "notNull": false,
1058+ "dimensions": 0,
1059+ "default": null,
1060+ "generated": null,
1061+ "identity": null,
1062+ "name": "interaction_id",
1063+ "entityType": "columns",
1064+ "schema": "public",
1065+ "table": "source_auth_sessions"
1066+ },
1067+ {
1068+ "type": "text",
1069+ "typeSchema": null,
1070+ "notNull": true,
1071+ "dimensions": 0,
1072+ "default": null,
1073+ "generated": null,
1074+ "identity": null,
1075+ "name": "strategy",
1076+ "entityType": "columns",
1077+ "schema": "public",
1078+ "table": "source_auth_sessions"
1079+ },
1080+ {
1081+ "type": "text",
1082+ "typeSchema": null,
1083+ "notNull": true,
1084+ "dimensions": 0,
1085+ "default": null,
1086+ "generated": null,
1087+ "identity": null,
1088+ "name": "status",
1089+ "entityType": "columns",
1090+ "schema": "public",
1091+ "table": "source_auth_sessions"
1092+ },
1093+ {
1094+ "type": "text",
1095+ "typeSchema": null,
1096+ "notNull": true,
1097+ "dimensions": 0,
1098+ "default": null,
1099+ "generated": null,
1100+ "identity": null,
1101+ "name": "endpoint",
1102+ "entityType": "columns",
1103+ "schema": "public",
1104+ "table": "source_auth_sessions"
1105+ },
1106+ {
1107+ "type": "text",
1108+ "typeSchema": null,
1109+ "notNull": true,
1110+ "dimensions": 0,
1111+ "default": null,
1112+ "generated": null,
1113+ "identity": null,
1114+ "name": "state",
1115+ "entityType": "columns",
1116+ "schema": "public",
1117+ "table": "source_auth_sessions"
1118+ },
1119+ {
1120+ "type": "text",
1121+ "typeSchema": null,
1122+ "notNull": true,
1123+ "dimensions": 0,
1124+ "default": null,
1125+ "generated": null,
1126+ "identity": null,
1127+ "name": "redirect_uri",
1128+ "entityType": "columns",
1129+ "schema": "public",
1130+ "table": "source_auth_sessions"
1131+ },
1132+ {
1133+ "type": "text",
1134+ "typeSchema": null,
1135+ "notNull": false,
1136+ "dimensions": 0,
1137+ "default": null,
1138+ "generated": null,
1139+ "identity": null,
1140+ "name": "scope",
1141+ "entityType": "columns",
1142+ "schema": "public",
1143+ "table": "source_auth_sessions"
1144+ },
1145+ {
1146+ "type": "text",
1147+ "typeSchema": null,
1148+ "notNull": false,
1149+ "dimensions": 0,
1150+ "default": null,
1151+ "generated": null,
1152+ "identity": null,
1153+ "name": "resource_metadata_url",
1154+ "entityType": "columns",
1155+ "schema": "public",
1156+ "table": "source_auth_sessions"
1157+ },
1158+ {
1159+ "type": "text",
1160+ "typeSchema": null,
1161+ "notNull": false,
1162+ "dimensions": 0,
1163+ "default": null,
1164+ "generated": null,
1165+ "identity": null,
1166+ "name": "authorization_server_url",
1167+ "entityType": "columns",
1168+ "schema": "public",
1169+ "table": "source_auth_sessions"
1170+ },
1171+ {
1172+ "type": "text",
1173+ "typeSchema": null,
1174+ "notNull": false,
1175+ "dimensions": 0,
1176+ "default": null,
1177+ "generated": null,
1178+ "identity": null,
1179+ "name": "resource_metadata_json",
1180+ "entityType": "columns",
1181+ "schema": "public",
1182+ "table": "source_auth_sessions"
1183+ },
1184+ {
1185+ "type": "text",
1186+ "typeSchema": null,
1187+ "notNull": false,
1188+ "dimensions": 0,
1189+ "default": null,
1190+ "generated": null,
1191+ "identity": null,
1192+ "name": "authorization_server_metadata_json",
1193+ "entityType": "columns",
1194+ "schema": "public",
1195+ "table": "source_auth_sessions"
1196+ },
1197+ {
1198+ "type": "text",
1199+ "typeSchema": null,
1200+ "notNull": false,
1201+ "dimensions": 0,
1202+ "default": null,
1203+ "generated": null,
1204+ "identity": null,
1205+ "name": "client_information_json",
1206+ "entityType": "columns",
1207+ "schema": "public",
1208+ "table": "source_auth_sessions"
1209+ },
1210+ {
1211+ "type": "text",
1212+ "typeSchema": null,
1213+ "notNull": false,
1214+ "dimensions": 0,
1215+ "default": null,
1216+ "generated": null,
1217+ "identity": null,
1218+ "name": "code_verifier",
1219+ "entityType": "columns",
1220+ "schema": "public",
1221+ "table": "source_auth_sessions"
1222+ },
1223+ {
1224+ "type": "text",
1225+ "typeSchema": null,
1226+ "notNull": false,
1227+ "dimensions": 0,
1228+ "default": null,
1229+ "generated": null,
1230+ "identity": null,
1231+ "name": "authorization_url",
1232+ "entityType": "columns",
1233+ "schema": "public",
1234+ "table": "source_auth_sessions"
1235+ },
1236+ {
1237+ "type": "text",
1238+ "typeSchema": null,
1239+ "notNull": false,
1240+ "dimensions": 0,
1241+ "default": null,
1242+ "generated": null,
1243+ "identity": null,
1244+ "name": "error_text",
1245+ "entityType": "columns",
1246+ "schema": "public",
1247+ "table": "source_auth_sessions"
1248+ },
1249+ {
1250+ "type": "bigint",
1251+ "typeSchema": null,
1252+ "notNull": false,
1253+ "dimensions": 0,
1254+ "default": null,
1255+ "generated": null,
1256+ "identity": null,
1257+ "name": "completed_at",
1258+ "entityType": "columns",
1259+ "schema": "public",
1260+ "table": "source_auth_sessions"
1261+ },
1262+ {
1263+ "type": "bigint",
1264+ "typeSchema": null,
1265+ "notNull": true,
1266+ "dimensions": 0,
1267+ "default": null,
1268+ "generated": null,
1269+ "identity": null,
1270+ "name": "created_at",
1271+ "entityType": "columns",
1272+ "schema": "public",
1273+ "table": "source_auth_sessions"
1274+ },
1275+ {
1276+ "type": "bigint",
1277+ "typeSchema": null,
1278+ "notNull": true,
1279+ "dimensions": 0,
1280+ "default": null,
1281+ "generated": null,
1282+ "identity": null,
1283+ "name": "updated_at",
1284+ "entityType": "columns",
1285+ "schema": "public",
1286+ "table": "source_auth_sessions"
1287+ },
1288+ {
1289+ "type": "text",
1290+ "typeSchema": null,
1291+ "notNull": true,
1292+ "dimensions": 0,
1293+ "default": null,
1294+ "generated": null,
1295+ "identity": null,
1296+ "name": "workspace_id",
1297+ "entityType": "columns",
1298+ "schema": "public",
1299+ "table": "source_credential_bindings"
1300+ },
1301+ {
1302+ "type": "text",
1303+ "typeSchema": null,
1304+ "notNull": true,
1305+ "dimensions": 0,
1306+ "default": null,
1307+ "generated": null,
1308+ "identity": null,
1309+ "name": "source_id",
1310+ "entityType": "columns",
1311+ "schema": "public",
1312+ "table": "source_credential_bindings"
1313+ },
1314+ {
1315+ "type": "text",
1316+ "typeSchema": null,
1317+ "notNull": false,
1318+ "dimensions": 0,
1319+ "default": null,
1320+ "generated": null,
1321+ "identity": null,
1322+ "name": "token_provider_id",
1323+ "entityType": "columns",
1324+ "schema": "public",
1325+ "table": "source_credential_bindings"
1326+ },
1327+ {
1328+ "type": "text",
1329+ "typeSchema": null,
1330+ "notNull": false,
1331+ "dimensions": 0,
1332+ "default": null,
1333+ "generated": null,
1334+ "identity": null,
1335+ "name": "token_handle",
1336+ "entityType": "columns",
1337+ "schema": "public",
1338+ "table": "source_credential_bindings"
1339+ },
1340+ {
1341+ "type": "text",
1342+ "typeSchema": null,
1343+ "notNull": false,
1344+ "dimensions": 0,
1345+ "default": null,
1346+ "generated": null,
1347+ "identity": null,
1348+ "name": "refresh_token_provider_id",
1349+ "entityType": "columns",
1350+ "schema": "public",
1351+ "table": "source_credential_bindings"
1352+ },
1353+ {
1354+ "type": "text",
1355+ "typeSchema": null,
1356+ "notNull": false,
1357+ "dimensions": 0,
1358+ "default": null,
1359+ "generated": null,
1360+ "identity": null,
1361+ "name": "refresh_token_handle",
1362+ "entityType": "columns",
1363+ "schema": "public",
1364+ "table": "source_credential_bindings"
1365+ },
1366+ {
1367+ "type": "bigint",
1368+ "typeSchema": null,
1369+ "notNull": true,
1370+ "dimensions": 0,
1371+ "default": null,
1372+ "generated": null,
1373+ "identity": null,
1374+ "name": "created_at",
1375+ "entityType": "columns",
1376+ "schema": "public",
1377+ "table": "source_credential_bindings"
1378+ },
1379+ {
1380+ "type": "bigint",
1381+ "typeSchema": null,
1382+ "notNull": true,
1383+ "dimensions": 0,
1384+ "default": null,
1385+ "generated": null,
1386+ "identity": null,
1387+ "name": "updated_at",
1388+ "entityType": "columns",
1389+ "schema": "public",
1390+ "table": "source_credential_bindings"
1391+ },
1392+ {
1393+ "type": "text",
1394+ "typeSchema": null,
1395+ "notNull": true,
1396+ "dimensions": 0,
1397+ "default": null,
1398+ "generated": null,
1399+ "identity": null,
1400+ "name": "workspace_id",
1401+ "entityType": "columns",
1402+ "schema": "public",
1403+ "table": "sources"
1404+ },
1405+ {
1406+ "type": "text",
1407+ "typeSchema": null,
1408+ "notNull": true,
1409+ "dimensions": 0,
1410+ "default": null,
1411+ "generated": null,
1412+ "identity": null,
1413+ "name": "source_id",
1414+ "entityType": "columns",
1415+ "schema": "public",
1416+ "table": "sources"
1417+ },
1418+ {
1419+ "type": "text",
1420+ "typeSchema": null,
1421+ "notNull": true,
1422+ "dimensions": 0,
1423+ "default": null,
1424+ "generated": null,
1425+ "identity": null,
1426+ "name": "name",
1427+ "entityType": "columns",
1428+ "schema": "public",
1429+ "table": "sources"
1430+ },
1431+ {
1432+ "type": "text",
1433+ "typeSchema": null,
1434+ "notNull": true,
1435+ "dimensions": 0,
1436+ "default": null,
1437+ "generated": null,
1438+ "identity": null,
1439+ "name": "kind",
1440+ "entityType": "columns",
1441+ "schema": "public",
1442+ "table": "sources"
1443+ },
1444+ {
1445+ "type": "text",
1446+ "typeSchema": null,
1447+ "notNull": true,
1448+ "dimensions": 0,
1449+ "default": null,
1450+ "generated": null,
1451+ "identity": null,
1452+ "name": "endpoint",
1453+ "entityType": "columns",
1454+ "schema": "public",
1455+ "table": "sources"
1456+ },
1457+ {
1458+ "type": "text",
1459+ "typeSchema": null,
1460+ "notNull": true,
1461+ "dimensions": 0,
1462+ "default": null,
1463+ "generated": null,
1464+ "identity": null,
1465+ "name": "status",
1466+ "entityType": "columns",
1467+ "schema": "public",
1468+ "table": "sources"
1469+ },
1470+ {
1471+ "type": "boolean",
1472+ "typeSchema": null,
1473+ "notNull": true,
1474+ "dimensions": 0,
1475+ "default": null,
1476+ "generated": null,
1477+ "identity": null,
1478+ "name": "enabled",
1479+ "entityType": "columns",
1480+ "schema": "public",
1481+ "table": "sources"
1482+ },
1483+ {
1484+ "type": "text",
1485+ "typeSchema": null,
1486+ "notNull": false,
1487+ "dimensions": 0,
1488+ "default": null,
1489+ "generated": null,
1490+ "identity": null,
1491+ "name": "namespace",
1492+ "entityType": "columns",
1493+ "schema": "public",
1494+ "table": "sources"
1495+ },
1496+ {
1497+ "type": "text",
1498+ "typeSchema": null,
1499+ "notNull": false,
1500+ "dimensions": 0,
1501+ "default": null,
1502+ "generated": null,
1503+ "identity": null,
1504+ "name": "transport",
1505+ "entityType": "columns",
1506+ "schema": "public",
1507+ "table": "sources"
1508+ },
1509+ {
1510+ "type": "text",
1511+ "typeSchema": null,
1512+ "notNull": false,
1513+ "dimensions": 0,
1514+ "default": null,
1515+ "generated": null,
1516+ "identity": null,
1517+ "name": "query_params_json",
1518+ "entityType": "columns",
1519+ "schema": "public",
1520+ "table": "sources"
1521+ },
1522+ {
1523+ "type": "text",
1524+ "typeSchema": null,
1525+ "notNull": false,
1526+ "dimensions": 0,
1527+ "default": null,
1528+ "generated": null,
1529+ "identity": null,
1530+ "name": "headers_json",
1531+ "entityType": "columns",
1532+ "schema": "public",
1533+ "table": "sources"
1534+ },
1535+ {
1536+ "type": "text",
1537+ "typeSchema": null,
1538+ "notNull": false,
1539+ "dimensions": 0,
1540+ "default": null,
1541+ "generated": null,
1542+ "identity": null,
1543+ "name": "spec_url",
1544+ "entityType": "columns",
1545+ "schema": "public",
1546+ "table": "sources"
1547+ },
1548+ {
1549+ "type": "text",
1550+ "typeSchema": null,
1551+ "notNull": false,
1552+ "dimensions": 0,
1553+ "default": null,
1554+ "generated": null,
1555+ "identity": null,
1556+ "name": "default_headers_json",
1557+ "entityType": "columns",
1558+ "schema": "public",
1559+ "table": "sources"
1560+ },
1561+ {
1562+ "type": "text",
1563+ "typeSchema": null,
1564+ "notNull": true,
1565+ "dimensions": 0,
1566+ "default": null,
1567+ "generated": null,
1568+ "identity": null,
1569+ "name": "auth_kind",
1570+ "entityType": "columns",
1571+ "schema": "public",
1572+ "table": "sources"
1573+ },
1574+ {
1575+ "type": "text",
1576+ "typeSchema": null,
1577+ "notNull": false,
1578+ "dimensions": 0,
1579+ "default": null,
1580+ "generated": null,
1581+ "identity": null,
1582+ "name": "auth_header_name",
1583+ "entityType": "columns",
1584+ "schema": "public",
1585+ "table": "sources"
1586+ },
1587+ {
1588+ "type": "text",
1589+ "typeSchema": null,
1590+ "notNull": false,
1591+ "dimensions": 0,
1592+ "default": null,
1593+ "generated": null,
1594+ "identity": null,
1595+ "name": "auth_prefix",
1596+ "entityType": "columns",
1597+ "schema": "public",
1598+ "table": "sources"
1599+ },
1600+ {
1601+ "type": "text",
1602+ "typeSchema": null,
1603+ "notNull": false,
1604+ "dimensions": 0,
1605+ "default": null,
1606+ "generated": null,
1607+ "identity": null,
1608+ "name": "source_hash",
1609+ "entityType": "columns",
1610+ "schema": "public",
1611+ "table": "sources"
1612+ },
1613+ {
1614+ "type": "text",
1615+ "typeSchema": null,
1616+ "notNull": false,
1617+ "dimensions": 0,
1618+ "default": null,
1619+ "generated": null,
1620+ "identity": null,
1621+ "name": "last_error",
1622+ "entityType": "columns",
1623+ "schema": "public",
1624+ "table": "sources"
1625+ },
1626+ {
1627+ "type": "bigint",
1628+ "typeSchema": null,
1629+ "notNull": true,
1630+ "dimensions": 0,
1631+ "default": null,
1632+ "generated": null,
1633+ "identity": null,
1634+ "name": "created_at",
1635+ "entityType": "columns",
1636+ "schema": "public",
1637+ "table": "sources"
1638+ },
1639+ {
1640+ "type": "bigint",
1641+ "typeSchema": null,
1642+ "notNull": true,
1643+ "dimensions": 0,
1644+ "default": null,
1645+ "generated": null,
1646+ "identity": null,
1647+ "name": "updated_at",
1648+ "entityType": "columns",
1649+ "schema": "public",
1650+ "table": "sources"
1651+ },
1652+ {
1653+ "type": "text",
1654+ "typeSchema": null,
1655+ "notNull": true,
1656+ "dimensions": 0,
1657+ "default": null,
1658+ "generated": null,
1659+ "identity": null,
1660+ "name": "workspace_id",
1661+ "entityType": "columns",
1662+ "schema": "public",
1663+ "table": "tool_artifact_parameters"
1664+ },
1665+ {
1666+ "type": "text",
1667+ "typeSchema": null,
1668+ "notNull": true,
1669+ "dimensions": 0,
1670+ "default": null,
1671+ "generated": null,
1672+ "identity": null,
1673+ "name": "path",
1674+ "entityType": "columns",
1675+ "schema": "public",
1676+ "table": "tool_artifact_parameters"
1677+ },
1678+ {
1679+ "type": "bigint",
1680+ "typeSchema": null,
1681+ "notNull": true,
1682+ "dimensions": 0,
1683+ "default": null,
1684+ "generated": null,
1685+ "identity": null,
1686+ "name": "position",
1687+ "entityType": "columns",
1688+ "schema": "public",
1689+ "table": "tool_artifact_parameters"
1690+ },
1691+ {
1692+ "type": "text",
1693+ "typeSchema": null,
1694+ "notNull": true,
1695+ "dimensions": 0,
1696+ "default": null,
1697+ "generated": null,
1698+ "identity": null,
1699+ "name": "name",
1700+ "entityType": "columns",
1701+ "schema": "public",
1702+ "table": "tool_artifact_parameters"
1703+ },
1704+ {
1705+ "type": "text",
1706+ "typeSchema": null,
1707+ "notNull": true,
1708+ "dimensions": 0,
1709+ "default": null,
1710+ "generated": null,
1711+ "identity": null,
1712+ "name": "location",
1713+ "entityType": "columns",
1714+ "schema": "public",
1715+ "table": "tool_artifact_parameters"
1716+ },
1717+ {
1718+ "type": "boolean",
1719+ "typeSchema": null,
1720+ "notNull": true,
1721+ "dimensions": 0,
1722+ "default": null,
1723+ "generated": null,
1724+ "identity": null,
1725+ "name": "required",
1726+ "entityType": "columns",
1727+ "schema": "public",
1728+ "table": "tool_artifact_parameters"
1729+ },
1730+ {
1731+ "type": "text",
1732+ "typeSchema": null,
1733+ "notNull": true,
1734+ "dimensions": 0,
1735+ "default": null,
1736+ "generated": null,
1737+ "identity": null,
1738+ "name": "workspace_id",
1739+ "entityType": "columns",
1740+ "schema": "public",
1741+ "table": "tool_artifact_ref_hint_keys"
1742+ },
1743+ {
1744+ "type": "text",
1745+ "typeSchema": null,
1746+ "notNull": true,
1747+ "dimensions": 0,
1748+ "default": null,
1749+ "generated": null,
1750+ "identity": null,
1751+ "name": "path",
1752+ "entityType": "columns",
1753+ "schema": "public",
1754+ "table": "tool_artifact_ref_hint_keys"
1755+ },
1756+ {
1757+ "type": "bigint",
1758+ "typeSchema": null,
1759+ "notNull": true,
1760+ "dimensions": 0,
1761+ "default": null,
1762+ "generated": null,
1763+ "identity": null,
1764+ "name": "position",
1765+ "entityType": "columns",
1766+ "schema": "public",
1767+ "table": "tool_artifact_ref_hint_keys"
1768+ },
1769+ {
1770+ "type": "text",
1771+ "typeSchema": null,
1772+ "notNull": true,
1773+ "dimensions": 0,
1774+ "default": null,
1775+ "generated": null,
1776+ "identity": null,
1777+ "name": "ref_hint_key",
1778+ "entityType": "columns",
1779+ "schema": "public",
1780+ "table": "tool_artifact_ref_hint_keys"
1781+ },
1782+ {
1783+ "type": "text",
1784+ "typeSchema": null,
1785+ "notNull": true,
1786+ "dimensions": 0,
1787+ "default": null,
1788+ "generated": null,
1789+ "identity": null,
1790+ "name": "workspace_id",
1791+ "entityType": "columns",
1792+ "schema": "public",
1793+ "table": "tool_artifact_request_body_content_types"
1794+ },
1795+ {
1796+ "type": "text",
1797+ "typeSchema": null,
1798+ "notNull": true,
1799+ "dimensions": 0,
1800+ "default": null,
1801+ "generated": null,
1802+ "identity": null,
1803+ "name": "path",
1804+ "entityType": "columns",
1805+ "schema": "public",
1806+ "table": "tool_artifact_request_body_content_types"
1807+ },
1808+ {
1809+ "type": "bigint",
1810+ "typeSchema": null,
1811+ "notNull": true,
1812+ "dimensions": 0,
1813+ "default": null,
1814+ "generated": null,
1815+ "identity": null,
1816+ "name": "position",
1817+ "entityType": "columns",
1818+ "schema": "public",
1819+ "table": "tool_artifact_request_body_content_types"
1820+ },
1821+ {
1822+ "type": "text",
1823+ "typeSchema": null,
1824+ "notNull": true,
1825+ "dimensions": 0,
1826+ "default": null,
1827+ "generated": null,
1828+ "identity": null,
1829+ "name": "content_type",
1830+ "entityType": "columns",
1831+ "schema": "public",
1832+ "table": "tool_artifact_request_body_content_types"
1833+ },
1834+ {
1835+ "type": "text",
1836+ "typeSchema": null,
1837+ "notNull": true,
1838+ "dimensions": 0,
1839+ "default": null,
1840+ "generated": null,
1841+ "identity": null,
1842+ "name": "workspace_id",
1843+ "entityType": "columns",
1844+ "schema": "public",
1845+ "table": "tool_artifacts"
1846+ },
1847+ {
1848+ "type": "text",
1849+ "typeSchema": null,
1850+ "notNull": true,
1851+ "dimensions": 0,
1852+ "default": null,
1853+ "generated": null,
1854+ "identity": null,
1855+ "name": "path",
1856+ "entityType": "columns",
1857+ "schema": "public",
1858+ "table": "tool_artifacts"
1859+ },
1860+ {
1861+ "type": "text",
1862+ "typeSchema": null,
1863+ "notNull": true,
1864+ "dimensions": 0,
1865+ "default": null,
1866+ "generated": null,
1867+ "identity": null,
1868+ "name": "tool_id",
1869+ "entityType": "columns",
1870+ "schema": "public",
1871+ "table": "tool_artifacts"
1872+ },
1873+ {
1874+ "type": "text",
1875+ "typeSchema": null,
1876+ "notNull": true,
1877+ "dimensions": 0,
1878+ "default": null,
1879+ "generated": null,
1880+ "identity": null,
1881+ "name": "source_id",
1882+ "entityType": "columns",
1883+ "schema": "public",
1884+ "table": "tool_artifacts"
1885+ },
1886+ {
1887+ "type": "text",
1888+ "typeSchema": null,
1889+ "notNull": false,
1890+ "dimensions": 0,
1891+ "default": null,
1892+ "generated": null,
1893+ "identity": null,
1894+ "name": "title",
1895+ "entityType": "columns",
1896+ "schema": "public",
1897+ "table": "tool_artifacts"
1898+ },
1899+ {
1900+ "type": "text",
1901+ "typeSchema": null,
1902+ "notNull": false,
1903+ "dimensions": 0,
1904+ "default": null,
1905+ "generated": null,
1906+ "identity": null,
1907+ "name": "description",
1908+ "entityType": "columns",
1909+ "schema": "public",
1910+ "table": "tool_artifacts"
1911+ },
1912+ {
1913+ "type": "text",
1914+ "typeSchema": null,
1915+ "notNull": true,
1916+ "dimensions": 0,
1917+ "default": null,
1918+ "generated": null,
1919+ "identity": null,
1920+ "name": "search_namespace",
1921+ "entityType": "columns",
1922+ "schema": "public",
1923+ "table": "tool_artifacts"
1924+ },
1925+ {
1926+ "type": "text",
1927+ "typeSchema": null,
1928+ "notNull": true,
1929+ "dimensions": 0,
1930+ "default": null,
1931+ "generated": null,
1932+ "identity": null,
1933+ "name": "search_text",
1934+ "entityType": "columns",
1935+ "schema": "public",
1936+ "table": "tool_artifacts"
1937+ },
1938+ {
1939+ "type": "text",
1940+ "typeSchema": null,
1941+ "notNull": false,
1942+ "dimensions": 0,
1943+ "default": null,
1944+ "generated": null,
1945+ "identity": null,
1946+ "name": "input_schema_json",
1947+ "entityType": "columns",
1948+ "schema": "public",
1949+ "table": "tool_artifacts"
1950+ },
1951+ {
1952+ "type": "text",
1953+ "typeSchema": null,
1954+ "notNull": false,
1955+ "dimensions": 0,
1956+ "default": null,
1957+ "generated": null,
1958+ "identity": null,
1959+ "name": "output_schema_json",
1960+ "entityType": "columns",
1961+ "schema": "public",
1962+ "table": "tool_artifacts"
1963+ },
1964+ {
1965+ "type": "text",
1966+ "typeSchema": null,
1967+ "notNull": true,
1968+ "dimensions": 0,
1969+ "default": null,
1970+ "generated": null,
1971+ "identity": null,
1972+ "name": "provider_kind",
1973+ "entityType": "columns",
1974+ "schema": "public",
1975+ "table": "tool_artifacts"
1976+ },
1977+ {
1978+ "type": "text",
1979+ "typeSchema": null,
1980+ "notNull": false,
1981+ "dimensions": 0,
1982+ "default": null,
1983+ "generated": null,
1984+ "identity": null,
1985+ "name": "mcp_tool_name",
1986+ "entityType": "columns",
1987+ "schema": "public",
1988+ "table": "tool_artifacts"
1989+ },
1990+ {
1991+ "type": "text",
1992+ "typeSchema": null,
1993+ "notNull": false,
1994+ "dimensions": 0,
1995+ "default": null,
1996+ "generated": null,
1997+ "identity": null,
1998+ "name": "openapi_method",
1999+ "entityType": "columns",
2000+ "schema": "public",
2001+ "table": "tool_artifacts"
2002+ },
2003+ {
2004+ "type": "text",
2005+ "typeSchema": null,
2006+ "notNull": false,
2007+ "dimensions": 0,
2008+ "default": null,
2009+ "generated": null,
2010+ "identity": null,
2011+ "name": "openapi_path_template",
2012+ "entityType": "columns",
2013+ "schema": "public",
2014+ "table": "tool_artifacts"
2015+ },
2016+ {
2017+ "type": "text",
2018+ "typeSchema": null,
2019+ "notNull": false,
2020+ "dimensions": 0,
2021+ "default": null,
2022+ "generated": null,
2023+ "identity": null,
2024+ "name": "openapi_operation_hash",
2025+ "entityType": "columns",
2026+ "schema": "public",
2027+ "table": "tool_artifacts"
2028+ },
2029+ {
2030+ "type": "boolean",
2031+ "typeSchema": null,
2032+ "notNull": false,
2033+ "dimensions": 0,
2034+ "default": null,
2035+ "generated": null,
2036+ "identity": null,
2037+ "name": "openapi_request_body_required",
2038+ "entityType": "columns",
2039+ "schema": "public",
2040+ "table": "tool_artifacts"
2041+ },
2042+ {
2043+ "type": "bigint",
2044+ "typeSchema": null,
2045+ "notNull": true,
2046+ "dimensions": 0,
2047+ "default": null,
2048+ "generated": null,
2049+ "identity": null,
2050+ "name": "created_at",
2051+ "entityType": "columns",
2052+ "schema": "public",
2053+ "table": "tool_artifacts"
2054+ },
2055+ {
2056+ "type": "bigint",
2057+ "typeSchema": null,
2058+ "notNull": true,
2059+ "dimensions": 0,
2060+ "default": null,
2061+ "generated": null,
2062+ "identity": null,
2063+ "name": "updated_at",
2064+ "entityType": "columns",
2065+ "schema": "public",
2066+ "table": "tool_artifacts"
2067+ },
2068+ {
2069+ "type": "text",
2070+ "typeSchema": null,
2071+ "notNull": true,
2072+ "dimensions": 0,
2073+ "default": null,
2074+ "generated": null,
2075+ "identity": null,
2076+ "name": "id",
2077+ "entityType": "columns",
2078+ "schema": "public",
2079+ "table": "workspaces"
2080+ },
2081+ {
2082+ "type": "text",
2083+ "typeSchema": null,
2084+ "notNull": true,
2085+ "dimensions": 0,
2086+ "default": null,
2087+ "generated": null,
2088+ "identity": null,
2089+ "name": "organization_id",
2090+ "entityType": "columns",
2091+ "schema": "public",
2092+ "table": "workspaces"
2093+ },
2094+ {
2095+ "type": "text",
2096+ "typeSchema": null,
2097+ "notNull": true,
2098+ "dimensions": 0,
2099+ "default": null,
2100+ "generated": null,
2101+ "identity": null,
2102+ "name": "name",
2103+ "entityType": "columns",
2104+ "schema": "public",
2105+ "table": "workspaces"
2106+ },
2107+ {
2108+ "type": "text",
2109+ "typeSchema": null,
2110+ "notNull": false,
2111+ "dimensions": 0,
2112+ "default": null,
2113+ "generated": null,
2114+ "identity": null,
2115+ "name": "created_by_account_id",
2116+ "entityType": "columns",
2117+ "schema": "public",
2118+ "table": "workspaces"
2119+ },
2120+ {
2121+ "type": "bigint",
2122+ "typeSchema": null,
2123+ "notNull": true,
2124+ "dimensions": 0,
2125+ "default": null,
2126+ "generated": null,
2127+ "identity": null,
2128+ "name": "created_at",
2129+ "entityType": "columns",
2130+ "schema": "public",
2131+ "table": "workspaces"
2132+ },
2133+ {
2134+ "type": "bigint",
2135+ "typeSchema": null,
2136+ "notNull": true,
2137+ "dimensions": 0,
2138+ "default": null,
2139+ "generated": null,
2140+ "identity": null,
2141+ "name": "updated_at",
2142+ "entityType": "columns",
2143+ "schema": "public",
2144+ "table": "workspaces"
2145+ },
2146+ {
2147+ "nameExplicit": true,
2148+ "columns": [
2149+ {
2150+ "value": "provider",
2151+ "isExpression": false,
2152+ "asc": true,
2153+ "nullsFirst": false,
2154+ "opclass": null
2155+ },
2156+ {
2157+ "value": "subject",
2158+ "isExpression": false,
2159+ "asc": true,
2160+ "nullsFirst": false,
2161+ "opclass": null
2162+ }
2163+ ],
2164+ "isUnique": true,
2165+ "where": null,
2166+ "with": "",
2167+ "method": "btree",
2168+ "concurrently": false,
2169+ "name": "accounts_provider_subject_idx",
2170+ "entityType": "indexes",
2171+ "schema": "public",
2172+ "table": "accounts"
2173+ },
2174+ {
2175+ "nameExplicit": true,
2176+ "columns": [
2177+ {
2178+ "value": "updated_at",
2179+ "isExpression": false,
2180+ "asc": true,
2181+ "nullsFirst": false,
2182+ "opclass": null
2183+ },
2184+ {
2185+ "value": "id",
2186+ "isExpression": false,
2187+ "asc": true,
2188+ "nullsFirst": false,
2189+ "opclass": null
2190+ }
2191+ ],
2192+ "isUnique": false,
2193+ "where": null,
2194+ "with": "",
2195+ "method": "btree",
2196+ "concurrently": false,
2197+ "name": "accounts_updated_idx",
2198+ "entityType": "indexes",
2199+ "schema": "public",
2200+ "table": "accounts"
2201+ },
2202+ {
2203+ "nameExplicit": true,
2204+ "columns": [
2205+ {
2206+ "value": "execution_id",
2207+ "isExpression": false,
2208+ "asc": true,
2209+ "nullsFirst": false,
2210+ "opclass": null
2211+ },
2212+ {
2213+ "value": "updated_at",
2214+ "isExpression": false,
2215+ "asc": true,
2216+ "nullsFirst": false,
2217+ "opclass": null
2218+ },
2219+ {
2220+ "value": "id",
2221+ "isExpression": false,
2222+ "asc": true,
2223+ "nullsFirst": false,
2224+ "opclass": null
2225+ }
2226+ ],
2227+ "isUnique": false,
2228+ "where": null,
2229+ "with": "",
2230+ "method": "btree",
2231+ "concurrently": false,
2232+ "name": "execution_interactions_execution_idx",
2233+ "entityType": "indexes",
2234+ "schema": "public",
2235+ "table": "execution_interactions"
2236+ },
2237+ {
2238+ "nameExplicit": true,
2239+ "columns": [
2240+ {
2241+ "value": "workspace_id",
2242+ "isExpression": false,
2243+ "asc": true,
2244+ "nullsFirst": false,
2245+ "opclass": null
2246+ },
2247+ {
2248+ "value": "updated_at",
2249+ "isExpression": false,
2250+ "asc": true,
2251+ "nullsFirst": false,
2252+ "opclass": null
2253+ },
2254+ {
2255+ "value": "id",
2256+ "isExpression": false,
2257+ "asc": true,
2258+ "nullsFirst": false,
2259+ "opclass": null
2260+ }
2261+ ],
2262+ "isUnique": false,
2263+ "where": null,
2264+ "with": "",
2265+ "method": "btree",
2266+ "concurrently": false,
2267+ "name": "executions_workspace_idx",
2268+ "entityType": "indexes",
2269+ "schema": "public",
2270+ "table": "executions"
2271+ },
2272+ {
2273+ "nameExplicit": true,
2274+ "columns": [
2275+ {
2276+ "value": "organization_id",
2277+ "isExpression": false,
2278+ "asc": true,
2279+ "nullsFirst": false,
2280+ "opclass": null
2281+ }
2282+ ],
2283+ "isUnique": false,
2284+ "where": null,
2285+ "with": "",
2286+ "method": "btree",
2287+ "concurrently": false,
2288+ "name": "organization_memberships_org_idx",
2289+ "entityType": "indexes",
2290+ "schema": "public",
2291+ "table": "organization_memberships"
2292+ },
2293+ {
2294+ "nameExplicit": true,
2295+ "columns": [
2296+ {
2297+ "value": "account_id",
2298+ "isExpression": false,
2299+ "asc": true,
2300+ "nullsFirst": false,
2301+ "opclass": null
2302+ }
2303+ ],
2304+ "isUnique": false,
2305+ "where": null,
2306+ "with": "",
2307+ "method": "btree",
2308+ "concurrently": false,
2309+ "name": "organization_memberships_account_idx",
2310+ "entityType": "indexes",
2311+ "schema": "public",
2312+ "table": "organization_memberships"
2313+ },
2314+ {
2315+ "nameExplicit": true,
2316+ "columns": [
2317+ {
2318+ "value": "organization_id",
2319+ "isExpression": false,
2320+ "asc": true,
2321+ "nullsFirst": false,
2322+ "opclass": null
2323+ },
2324+ {
2325+ "value": "account_id",
2326+ "isExpression": false,
2327+ "asc": true,
2328+ "nullsFirst": false,
2329+ "opclass": null
2330+ }
2331+ ],
2332+ "isUnique": true,
2333+ "where": null,
2334+ "with": "",
2335+ "method": "btree",
2336+ "concurrently": false,
2337+ "name": "organization_memberships_org_account_idx",
2338+ "entityType": "indexes",
2339+ "schema": "public",
2340+ "table": "organization_memberships"
2341+ },
2342+ {
2343+ "nameExplicit": true,
2344+ "columns": [
2345+ {
2346+ "value": "slug",
2347+ "isExpression": false,
2348+ "asc": true,
2349+ "nullsFirst": false,
2350+ "opclass": null
2351+ }
2352+ ],
2353+ "isUnique": true,
2354+ "where": null,
2355+ "with": "",
2356+ "method": "btree",
2357+ "concurrently": false,
2358+ "name": "organizations_slug_idx",
2359+ "entityType": "indexes",
2360+ "schema": "public",
2361+ "table": "organizations"
2362+ },
2363+ {
2364+ "nameExplicit": true,
2365+ "columns": [
2366+ {
2367+ "value": "updated_at",
2368+ "isExpression": false,
2369+ "asc": true,
2370+ "nullsFirst": false,
2371+ "opclass": null
2372+ },
2373+ {
2374+ "value": "id",
2375+ "isExpression": false,
2376+ "asc": true,
2377+ "nullsFirst": false,
2378+ "opclass": null
2379+ }
2380+ ],
2381+ "isUnique": false,
2382+ "where": null,
2383+ "with": "",
2384+ "method": "btree",
2385+ "concurrently": false,
2386+ "name": "organizations_updated_idx",
2387+ "entityType": "indexes",
2388+ "schema": "public",
2389+ "table": "organizations"
2390+ },
2391+ {
2392+ "nameExplicit": true,
2393+ "columns": [
2394+ {
2395+ "value": "workspace_id",
2396+ "isExpression": false,
2397+ "asc": true,
2398+ "nullsFirst": false,
2399+ "opclass": null
2400+ },
2401+ {
2402+ "value": "updated_at",
2403+ "isExpression": false,
2404+ "asc": true,
2405+ "nullsFirst": false,
2406+ "opclass": null
2407+ },
2408+ {
2409+ "value": "id",
2410+ "isExpression": false,
2411+ "asc": true,
2412+ "nullsFirst": false,
2413+ "opclass": null
2414+ }
2415+ ],
2416+ "isUnique": false,
2417+ "where": null,
2418+ "with": "",
2419+ "method": "btree",
2420+ "concurrently": false,
2421+ "name": "policies_workspace_idx",
2422+ "entityType": "indexes",
2423+ "schema": "public",
2424+ "table": "policies"
2425+ },
2426+ {
2427+ "nameExplicit": true,
2428+ "columns": [
2429+ {
2430+ "value": "updated_at",
2431+ "isExpression": false,
2432+ "asc": true,
2433+ "nullsFirst": false,
2434+ "opclass": null
2435+ },
2436+ {
2437+ "value": "id",
2438+ "isExpression": false,
2439+ "asc": true,
2440+ "nullsFirst": false,
2441+ "opclass": null
2442+ }
2443+ ],
2444+ "isUnique": false,
2445+ "where": null,
2446+ "with": "",
2447+ "method": "btree",
2448+ "concurrently": false,
2449+ "name": "secret_materials_updated_idx",
2450+ "entityType": "indexes",
2451+ "schema": "public",
2452+ "table": "secret_materials"
2453+ },
2454+ {
2455+ "nameExplicit": true,
2456+ "columns": [
2457+ {
2458+ "value": "workspace_id",
2459+ "isExpression": false,
2460+ "asc": true,
2461+ "nullsFirst": false,
2462+ "opclass": null
2463+ },
2464+ {
2465+ "value": "updated_at",
2466+ "isExpression": false,
2467+ "asc": true,
2468+ "nullsFirst": false,
2469+ "opclass": null
2470+ },
2471+ {
2472+ "value": "id",
2473+ "isExpression": false,
2474+ "asc": true,
2475+ "nullsFirst": false,
2476+ "opclass": null
2477+ }
2478+ ],
2479+ "isUnique": false,
2480+ "where": null,
2481+ "with": "",
2482+ "method": "btree",
2483+ "concurrently": false,
2484+ "name": "source_auth_sessions_workspace_idx",
2485+ "entityType": "indexes",
2486+ "schema": "public",
2487+ "table": "source_auth_sessions"
2488+ },
2489+ {
2490+ "nameExplicit": true,
2491+ "columns": [
2492+ {
2493+ "value": "state",
2494+ "isExpression": false,
2495+ "asc": true,
2496+ "nullsFirst": false,
2497+ "opclass": null
2498+ }
2499+ ],
2500+ "isUnique": true,
2501+ "where": null,
2502+ "with": "",
2503+ "method": "btree",
2504+ "concurrently": false,
2505+ "name": "source_auth_sessions_state_idx",
2506+ "entityType": "indexes",
2507+ "schema": "public",
2508+ "table": "source_auth_sessions"
2509+ },
2510+ {
2511+ "nameExplicit": true,
2512+ "columns": [
2513+ {
2514+ "value": "workspace_id",
2515+ "isExpression": false,
2516+ "asc": true,
2517+ "nullsFirst": false,
2518+ "opclass": null
2519+ },
2520+ {
2521+ "value": "updated_at",
2522+ "isExpression": false,
2523+ "asc": true,
2524+ "nullsFirst": false,
2525+ "opclass": null
2526+ },
2527+ {
2528+ "value": "source_id",
2529+ "isExpression": false,
2530+ "asc": true,
2531+ "nullsFirst": false,
2532+ "opclass": null
2533+ }
2534+ ],
2535+ "isUnique": false,
2536+ "where": null,
2537+ "with": "",
2538+ "method": "btree",
2539+ "concurrently": false,
2540+ "name": "source_credential_bindings_workspace_idx",
2541+ "entityType": "indexes",
2542+ "schema": "public",
2543+ "table": "source_credential_bindings"
2544+ },
2545+ {
2546+ "nameExplicit": true,
2547+ "columns": [
2548+ {
2549+ "value": "workspace_id",
2550+ "isExpression": false,
2551+ "asc": true,
2552+ "nullsFirst": false,
2553+ "opclass": null
2554+ },
2555+ {
2556+ "value": "name",
2557+ "isExpression": false,
2558+ "asc": true,
2559+ "nullsFirst": false,
2560+ "opclass": null
2561+ }
2562+ ],
2563+ "isUnique": true,
2564+ "where": null,
2565+ "with": "",
2566+ "method": "btree",
2567+ "concurrently": false,
2568+ "name": "sources_workspace_name_idx",
2569+ "entityType": "indexes",
2570+ "schema": "public",
2571+ "table": "sources"
2572+ },
2573+ {
2574+ "nameExplicit": true,
2575+ "columns": [
2576+ {
2577+ "value": "workspace_id",
2578+ "isExpression": false,
2579+ "asc": true,
2580+ "nullsFirst": false,
2581+ "opclass": null
2582+ },
2583+ {
2584+ "value": "path",
2585+ "isExpression": false,
2586+ "asc": true,
2587+ "nullsFirst": false,
2588+ "opclass": null
2589+ },
2590+ {
2591+ "value": "position",
2592+ "isExpression": false,
2593+ "asc": true,
2594+ "nullsFirst": false,
2595+ "opclass": null
2596+ }
2597+ ],
2598+ "isUnique": false,
2599+ "where": null,
2600+ "with": "",
2601+ "method": "btree",
2602+ "concurrently": false,
2603+ "name": "tool_artifact_parameters_lookup_idx",
2604+ "entityType": "indexes",
2605+ "schema": "public",
2606+ "table": "tool_artifact_parameters"
2607+ },
2608+ {
2609+ "nameExplicit": true,
2610+ "columns": [
2611+ {
2612+ "value": "workspace_id",
2613+ "isExpression": false,
2614+ "asc": true,
2615+ "nullsFirst": false,
2616+ "opclass": null
2617+ },
2618+ {
2619+ "value": "path",
2620+ "isExpression": false,
2621+ "asc": true,
2622+ "nullsFirst": false,
2623+ "opclass": null
2624+ },
2625+ {
2626+ "value": "position",
2627+ "isExpression": false,
2628+ "asc": true,
2629+ "nullsFirst": false,
2630+ "opclass": null
2631+ }
2632+ ],
2633+ "isUnique": false,
2634+ "where": null,
2635+ "with": "",
2636+ "method": "btree",
2637+ "concurrently": false,
2638+ "name": "tool_artifact_ref_hint_keys_lookup_idx",
2639+ "entityType": "indexes",
2640+ "schema": "public",
2641+ "table": "tool_artifact_ref_hint_keys"
2642+ },
2643+ {
2644+ "nameExplicit": true,
2645+ "columns": [
2646+ {
2647+ "value": "workspace_id",
2648+ "isExpression": false,
2649+ "asc": true,
2650+ "nullsFirst": false,
2651+ "opclass": null
2652+ },
2653+ {
2654+ "value": "path",
2655+ "isExpression": false,
2656+ "asc": true,
2657+ "nullsFirst": false,
2658+ "opclass": null
2659+ },
2660+ {
2661+ "value": "position",
2662+ "isExpression": false,
2663+ "asc": true,
2664+ "nullsFirst": false,
2665+ "opclass": null
2666+ }
2667+ ],
2668+ "isUnique": false,
2669+ "where": null,
2670+ "with": "",
2671+ "method": "btree",
2672+ "concurrently": false,
2673+ "name": "tool_artifact_request_body_content_types_lookup_idx",
2674+ "entityType": "indexes",
2675+ "schema": "public",
2676+ "table": "tool_artifact_request_body_content_types"
2677+ },
2678+ {
2679+ "nameExplicit": true,
2680+ "columns": [
2681+ {
2682+ "value": "workspace_id",
2683+ "isExpression": false,
2684+ "asc": true,
2685+ "nullsFirst": false,
2686+ "opclass": null
2687+ },
2688+ {
2689+ "value": "source_id",
2690+ "isExpression": false,
2691+ "asc": true,
2692+ "nullsFirst": false,
2693+ "opclass": null
2694+ },
2695+ {
2696+ "value": "updated_at",
2697+ "isExpression": false,
2698+ "asc": true,
2699+ "nullsFirst": false,
2700+ "opclass": null
2701+ },
2702+ {
2703+ "value": "path",
2704+ "isExpression": false,
2705+ "asc": true,
2706+ "nullsFirst": false,
2707+ "opclass": null
2708+ }
2709+ ],
2710+ "isUnique": false,
2711+ "where": null,
2712+ "with": "",
2713+ "method": "btree",
2714+ "concurrently": false,
2715+ "name": "tool_artifacts_workspace_source_idx",
2716+ "entityType": "indexes",
2717+ "schema": "public",
2718+ "table": "tool_artifacts"
2719+ },
2720+ {
2721+ "nameExplicit": true,
2722+ "columns": [
2723+ {
2724+ "value": "workspace_id",
2725+ "isExpression": false,
2726+ "asc": true,
2727+ "nullsFirst": false,
2728+ "opclass": null
2729+ },
2730+ {
2731+ "value": "search_namespace",
2732+ "isExpression": false,
2733+ "asc": true,
2734+ "nullsFirst": false,
2735+ "opclass": null
2736+ },
2737+ {
2738+ "value": "path",
2739+ "isExpression": false,
2740+ "asc": true,
2741+ "nullsFirst": false,
2742+ "opclass": null
2743+ }
2744+ ],
2745+ "isUnique": false,
2746+ "where": null,
2747+ "with": "",
2748+ "method": "btree",
2749+ "concurrently": false,
2750+ "name": "tool_artifacts_workspace_namespace_idx",
2751+ "entityType": "indexes",
2752+ "schema": "public",
2753+ "table": "tool_artifacts"
2754+ },
2755+ {
2756+ "nameExplicit": true,
2757+ "columns": [
2758+ {
2759+ "value": "organization_id",
2760+ "isExpression": false,
2761+ "asc": true,
2762+ "nullsFirst": false,
2763+ "opclass": null
2764+ }
2765+ ],
2766+ "isUnique": false,
2767+ "where": null,
2768+ "with": "",
2769+ "method": "btree",
2770+ "concurrently": false,
2771+ "name": "workspaces_org_idx",
2772+ "entityType": "indexes",
2773+ "schema": "public",
2774+ "table": "workspaces"
2775+ },
2776+ {
2777+ "nameExplicit": true,
2778+ "columns": [
2779+ {
2780+ "value": "organization_id",
2781+ "isExpression": false,
2782+ "asc": true,
2783+ "nullsFirst": false,
2784+ "opclass": null
2785+ },
2786+ {
2787+ "value": "name",
2788+ "isExpression": false,
2789+ "asc": true,
2790+ "nullsFirst": false,
2791+ "opclass": null
2792+ }
2793+ ],
2794+ "isUnique": true,
2795+ "where": null,
2796+ "with": "",
2797+ "method": "btree",
2798+ "concurrently": false,
2799+ "name": "workspaces_org_name_idx",
2800+ "entityType": "indexes",
2801+ "schema": "public",
2802+ "table": "workspaces"
2803+ },
2804+ {
2805+ "columns": [
2806+ "workspace_id",
2807+ "source_id"
2808+ ],
2809+ "nameExplicit": false,
2810+ "name": "source_credential_bindings_pkey",
2811+ "entityType": "pks",
2812+ "schema": "public",
2813+ "table": "source_credential_bindings"
2814+ },
2815+ {
2816+ "columns": [
2817+ "workspace_id",
2818+ "source_id"
2819+ ],
2820+ "nameExplicit": false,
2821+ "name": "sources_pkey",
2822+ "entityType": "pks",
2823+ "schema": "public",
2824+ "table": "sources"
2825+ },
2826+ {
2827+ "columns": [
2828+ "workspace_id",
2829+ "path",
2830+ "position"
2831+ ],
2832+ "nameExplicit": false,
2833+ "name": "tool_artifact_parameters_pkey",
2834+ "entityType": "pks",
2835+ "schema": "public",
2836+ "table": "tool_artifact_parameters"
2837+ },
2838+ {
2839+ "columns": [
2840+ "workspace_id",
2841+ "path",
2842+ "position"
2843+ ],
2844+ "nameExplicit": false,
2845+ "name": "tool_artifact_ref_hint_keys_pkey",
2846+ "entityType": "pks",
2847+ "schema": "public",
2848+ "table": "tool_artifact_ref_hint_keys"
2849+ },
2850+ {
2851+ "columns": [
2852+ "workspace_id",
2853+ "path",
2854+ "position"
2855+ ],
2856+ "nameExplicit": false,
2857+ "name": "tool_artifact_request_body_content_types_pkey",
2858+ "entityType": "pks",
2859+ "schema": "public",
2860+ "table": "tool_artifact_request_body_content_types"
2861+ },
2862+ {
2863+ "columns": [
2864+ "workspace_id",
2865+ "path"
2866+ ],
2867+ "nameExplicit": false,
2868+ "name": "tool_artifacts_pkey",
2869+ "entityType": "pks",
2870+ "schema": "public",
2871+ "table": "tool_artifacts"
2872+ },
2873+ {
2874+ "columns": [
2875+ "id"
2876+ ],
2877+ "nameExplicit": false,
2878+ "name": "accounts_pkey",
2879+ "schema": "public",
2880+ "table": "accounts",
2881+ "entityType": "pks"
2882+ },
2883+ {
2884+ "columns": [
2885+ "id"
2886+ ],
2887+ "nameExplicit": false,
2888+ "name": "execution_interactions_pkey",
2889+ "schema": "public",
2890+ "table": "execution_interactions",
2891+ "entityType": "pks"
2892+ },
2893+ {
2894+ "columns": [
2895+ "id"
2896+ ],
2897+ "nameExplicit": false,
2898+ "name": "executions_pkey",
2899+ "schema": "public",
2900+ "table": "executions",
2901+ "entityType": "pks"
2902+ },
2903+ {
2904+ "columns": [
2905+ "id"
2906+ ],
2907+ "nameExplicit": false,
2908+ "name": "local_installations_pkey",
2909+ "schema": "public",
2910+ "table": "local_installations",
2911+ "entityType": "pks"
2912+ },
2913+ {
2914+ "columns": [
2915+ "id"
2916+ ],
2917+ "nameExplicit": false,
2918+ "name": "organization_memberships_pkey",
2919+ "schema": "public",
2920+ "table": "organization_memberships",
2921+ "entityType": "pks"
2922+ },
2923+ {
2924+ "columns": [
2925+ "id"
2926+ ],
2927+ "nameExplicit": false,
2928+ "name": "organizations_pkey",
2929+ "schema": "public",
2930+ "table": "organizations",
2931+ "entityType": "pks"
2932+ },
2933+ {
2934+ "columns": [
2935+ "id"
2936+ ],
2937+ "nameExplicit": false,
2938+ "name": "policies_pkey",
2939+ "schema": "public",
2940+ "table": "policies",
2941+ "entityType": "pks"
2942+ },
2943+ {
2944+ "columns": [
2945+ "id"
2946+ ],
2947+ "nameExplicit": false,
2948+ "name": "secret_materials_pkey",
2949+ "schema": "public",
2950+ "table": "secret_materials",
2951+ "entityType": "pks"
2952+ },
2953+ {
2954+ "columns": [
2955+ "id"
2956+ ],
2957+ "nameExplicit": false,
2958+ "name": "source_auth_sessions_pkey",
2959+ "schema": "public",
2960+ "table": "source_auth_sessions",
2961+ "entityType": "pks"
2962+ },
2963+ {
2964+ "columns": [
2965+ "id"
2966+ ],
2967+ "nameExplicit": false,
2968+ "name": "workspaces_pkey",
2969+ "schema": "public",
2970+ "table": "workspaces",
2971+ "entityType": "pks"
2972+ },
2973+ {
2974+ "value": "\"provider\" in ('local', 'workos', 'service')",
2975+ "name": "accounts_provider_check",
2976+ "entityType": "checks",
2977+ "schema": "public",
2978+ "table": "accounts"
2979+ },
2980+ {
2981+ "value": "\"status\" in ('pending', 'resolved', 'cancelled')",
2982+ "name": "execution_interactions_status_check",
2983+ "entityType": "checks",
2984+ "schema": "public",
2985+ "table": "execution_interactions"
2986+ },
2987+ {
2988+ "value": "\"status\" in ('pending', 'running', 'waiting_for_interaction', 'completed', 'failed', 'cancelled')",
2989+ "name": "executions_status_check",
2990+ "entityType": "checks",
2991+ "schema": "public",
2992+ "table": "executions"
2993+ },
2994+ {
2995+ "value": "\"role\" in ('viewer', 'editor', 'admin', 'owner')",
2996+ "name": "organization_memberships_role_check",
2997+ "entityType": "checks",
2998+ "schema": "public",
2999+ "table": "organization_memberships"
3000+ },
3001+ {
3002+ "value": "\"status\" in ('invited', 'active', 'suspended', 'removed')",
3003+ "name": "organization_memberships_status_check",
3004+ "entityType": "checks",
3005+ "schema": "public",
3006+ "table": "organization_memberships"
3007+ },
3008+ {
3009+ "value": "\"status\" in ('active', 'suspended', 'archived')",
3010+ "name": "organizations_status_check",
3011+ "entityType": "checks",
3012+ "schema": "public",
3013+ "table": "organizations"
3014+ },
3015+ {
3016+ "value": "\"resource_type\" in ('all_tools', 'source', 'namespace', 'tool_path')",
3017+ "name": "policies_resource_type_check",
3018+ "entityType": "checks",
3019+ "schema": "public",
3020+ "table": "policies"
3021+ },
3022+ {
3023+ "value": "\"match_type\" in ('glob', 'exact')",
3024+ "name": "policies_match_type_check",
3025+ "entityType": "checks",
3026+ "schema": "public",
3027+ "table": "policies"
3028+ },
3029+ {
3030+ "value": "\"effect\" in ('allow', 'deny')",
3031+ "name": "policies_effect_check",
3032+ "entityType": "checks",
3033+ "schema": "public",
3034+ "table": "policies"
3035+ },
3036+ {
3037+ "value": "\"approval_mode\" in ('auto', 'required')",
3038+ "name": "policies_approval_mode_check",
3039+ "entityType": "checks",
3040+ "schema": "public",
3041+ "table": "policies"
3042+ },
3043+ {
3044+ "value": "\"purpose\" in ('auth_material', 'oauth_access_token', 'oauth_refresh_token', 'oauth_client_info')",
3045+ "name": "secret_materials_purpose_check",
3046+ "entityType": "checks",
3047+ "schema": "public",
3048+ "table": "secret_materials"
3049+ },
3050+ {
3051+ "value": "\"strategy\" in ('oauth2_authorization_code')",
3052+ "name": "source_auth_sessions_strategy_check",
3053+ "entityType": "checks",
3054+ "schema": "public",
3055+ "table": "source_auth_sessions"
3056+ },
3057+ {
3058+ "value": "\"status\" in ('pending', 'completed', 'failed', 'cancelled')",
3059+ "name": "source_auth_sessions_status_check",
3060+ "entityType": "checks",
3061+ "schema": "public",
3062+ "table": "source_auth_sessions"
3063+ },
3064+ {
3065+ "value": "\"kind\" in ('mcp', 'openapi', 'graphql', 'internal')",
3066+ "name": "sources_kind_check",
3067+ "entityType": "checks",
3068+ "schema": "public",
3069+ "table": "sources"
3070+ },
3071+ {
3072+ "value": "\"status\" in ('draft', 'probing', 'auth_required', 'connected', 'error')",
3073+ "name": "sources_status_check",
3074+ "entityType": "checks",
3075+ "schema": "public",
3076+ "table": "sources"
3077+ },
3078+ {
3079+ "value": "\"transport\" is null or \"transport\" in ('auto', 'streamable-http', 'sse')",
3080+ "name": "sources_transport_check",
3081+ "entityType": "checks",
3082+ "schema": "public",
3083+ "table": "sources"
3084+ },
3085+ {
3086+ "value": "\"auth_kind\" in ('none', 'bearer', 'oauth2')",
3087+ "name": "sources_auth_kind_check",
3088+ "entityType": "checks",
3089+ "schema": "public",
3090+ "table": "sources"
3091+ },
3092+ {
3093+ "value": "\"location\" in ('path', 'query', 'header', 'cookie')",
3094+ "name": "tool_artifact_parameters_location_check",
3095+ "entityType": "checks",
3096+ "schema": "public",
3097+ "table": "tool_artifact_parameters"
3098+ },
3099+ {
3100+ "value": "\"provider_kind\" in ('mcp', 'openapi')",
3101+ "name": "tool_artifacts_provider_kind_check",
3102+ "entityType": "checks",
3103+ "schema": "public",
3104+ "table": "tool_artifacts"
3105+ },
3106+ {
3107+ "value": "\"provider_kind\" <> 'mcp'\n or (\n \"mcp_tool_name\" is not null\n and \"openapi_method\" is null\n and \"openapi_path_template\" is null\n and \"openapi_operation_hash\" is null\n and \"openapi_request_body_required\" is null\n )",
3108+ "name": "tool_artifacts_mcp_shape_check",
3109+ "entityType": "checks",
3110+ "schema": "public",
3111+ "table": "tool_artifacts"
3112+ },
3113+ {
3114+ "value": "\"provider_kind\" <> 'openapi'\n or (\n \"mcp_tool_name\" is null\n and \"openapi_method\" in ('get', 'put', 'post', 'delete', 'patch', 'head', 'options', 'trace')\n and \"openapi_path_template\" is not null\n and \"openapi_operation_hash\" is not null\n )",
3115+ "name": "tool_artifacts_openapi_shape_check",
3116+ "entityType": "checks",
3117+ "schema": "public",
3118+ "table": "tool_artifacts"
3119+ }
3120+ ],
3121+ "renames": []
3122+}
packages/control-plane/src/persistence/index.ts+2−0
@@ -16,6 +16,7 @@ import { · 15 unmodified lines
1616 createSourceAuthSessionsRepo,
1717 createSourceCredentialBindingsRepo,
1818 createSourcesRepo,
19+ createToolArtifactsRepo,
1920 createWorkspacesRepo,
2021 } from "./repos";
2122 import { drizzleSchema, tableNames, type DrizzleTables } from "./schema";
@@ -50,6 +51,7 @@ const createRows = (client: DrizzleClient, tables: DrizzleTables = drizzleSchema · 28 unmodified lines
5051 organizationMemberships: createOrganizationMembershipsRepo(client, tables),
5152 workspaces: createWorkspacesRepo(client, tables),
5253 sources: createSourcesRepo(client, tables),
54+ toolArtifacts: createToolArtifactsRepo(client, tables),
5355 sourceCredentialBindings: createSourceCredentialBindingsRepo(client, tables),
5456 secretMaterials: createSecretMaterialsRepo(client, tables),
5557 sourceAuthSessions: createSourceAuthSessionsRepo(client, tables),
packages/control-plane/src/persistence/repos/index.ts+1−0
@@ -3,6 +3,7 @@ export { createOrganizationsRepo } from "./organizations-repo"; · 2 unmodified lines
33 export { createOrganizationMembershipsRepo } from "./organization-memberships-repo";
44 export { createWorkspacesRepo } from "./workspaces-repo";
55 export { createSourcesRepo } from "./sources-repo";
6+export { createToolArtifactsRepo } from "./tool-artifacts-repo";
67 export { createSourceCredentialBindingsRepo } from "./source-credential-bindings-repo";
78 export { createSecretMaterialsRepo } from "./secret-materials-repo";
89 export { createSourceAuthSessionsRepo } from "./source-auth-sessions-repo";
packages/control-plane/src/persistence/repos/sources-repo.ts+61−1
@@ -4,7 +4,7 @@ import { · 3 unmodified lines
44 } from "#schema";
55 import * as Option from "effect/Option";
66 import { Schema } from "effect";
7−import { and, asc, eq } from "drizzle-orm";
7+import { and, asc, eq, or } from "drizzle-orm";
88
99 import type { DrizzleClient } from "../client";
1010 import type { DrizzleTables } from "../schema";
@@ -111,6 +111,66 @@ export const createSourcesRepo = ( · 100 unmodified lines
111111 sourceId: StoredSourceRecord["id"],
112112 ) =>
113113 client.useTx("rows.sources.remove", async (tx) => {
114+ const existingToolPaths = (
115+ await tx
116+ .select({
117+ path: tables.toolArtifactsTable.path,
118+ })
119+ .from(tables.toolArtifactsTable)
120+ .where(
121+ and(
122+ eq(tables.toolArtifactsTable.workspaceId, workspaceId),
123+ eq(tables.toolArtifactsTable.sourceId, sourceId),
124+ ),
125+ )
126+ ).map((row) => row.path);
127+
128+ if (existingToolPaths.length > 0) {
129+ await tx
130+ .delete(tables.toolArtifactParametersTable)
131+ .where(
132+ and(
133+ eq(tables.toolArtifactParametersTable.workspaceId, workspaceId),
134+ or(
135+ ...existingToolPaths.map((path) => eq(tables.toolArtifactParametersTable.path, path)),
136+ ),
137+ ),
138+ );
139+
140+ await tx
141+ .delete(tables.toolArtifactRequestBodyContentTypesTable)
142+ .where(
143+ and(
144+ eq(tables.toolArtifactRequestBodyContentTypesTable.workspaceId, workspaceId),
145+ or(
146+ ...existingToolPaths.map((path) =>
147+ eq(tables.toolArtifactRequestBodyContentTypesTable.path, path)
148+ ),
149+ ),
150+ ),
151+ );
152+
153+ await tx
154+ .delete(tables.toolArtifactRefHintKeysTable)
155+ .where(
156+ and(
157+ eq(tables.toolArtifactRefHintKeysTable.workspaceId, workspaceId),
158+ or(
159+ ...existingToolPaths.map((path) => eq(tables.toolArtifactRefHintKeysTable.path, path)),
160+ ),
161+ ),
162+ );
163+ }
164+
165+ await tx
166+ .delete(tables.toolArtifactsTable)
167+ .where(
168+ and(
169+ eq(tables.toolArtifactsTable.workspaceId, workspaceId),
170+ eq(tables.toolArtifactsTable.sourceId, sourceId),
171+ ),
172+ );
173+
114174 await tx
115175 .delete(tables.sourceCredentialBindingsTable)
116176 .where(
packages/control-plane/src/persistence/repos/tool-artifacts-repo.ts+441−0
@@ -0,0 +1,441 @@
1+import {
2+ type StoredToolArtifactParameterRecord,
3+ StoredToolArtifactParameterRecordSchema,
4+ type StoredToolArtifactRecord,
5+ StoredToolArtifactRecordSchema,
6+ type StoredToolArtifactRefHintKeyRecord,
7+ StoredToolArtifactRefHintKeyRecordSchema,
8+ type StoredToolArtifactRequestBodyContentTypeRecord,
9+ StoredToolArtifactRequestBodyContentTypeRecordSchema,
10+} from "#schema";
11+import * as Option from "effect/Option";
12+import { Schema } from "effect";
13+import {
14+ and,
15+ asc,
16+ count,
17+ eq,
18+ ilike,
19+ or,
20+} from "drizzle-orm";
21+
22+import type { DrizzleClient } from "../client";
23+import type { DrizzleTables } from "../schema";
24+import { firstOption } from "./shared";
25+
26+const decodeStoredToolArtifactRecord = Schema.decodeUnknownSync(
27+ StoredToolArtifactRecordSchema,
28+);
29+const encodeStoredToolArtifactRecord = Schema.encodeSync(
30+ StoredToolArtifactRecordSchema,
31+);
32+const decodeStoredToolArtifactParameterRecord = Schema.decodeUnknownSync(
33+ StoredToolArtifactParameterRecordSchema,
34+);
35+const encodeStoredToolArtifactParameterRecord = Schema.encodeSync(
36+ StoredToolArtifactParameterRecordSchema,
37+);
38+const decodeStoredToolArtifactRequestBodyContentTypeRecord = Schema.decodeUnknownSync(
39+ StoredToolArtifactRequestBodyContentTypeRecordSchema,
40+);
41+const encodeStoredToolArtifactRequestBodyContentTypeRecord = Schema.encodeSync(
42+ StoredToolArtifactRequestBodyContentTypeRecordSchema,
43+);
44+const decodeStoredToolArtifactRefHintKeyRecord = Schema.decodeUnknownSync(
45+ StoredToolArtifactRefHintKeyRecordSchema,
46+);
47+const encodeStoredToolArtifactRefHintKeyRecord = Schema.encodeSync(
48+ StoredToolArtifactRefHintKeyRecordSchema,
49+);
50+
51+type ReplaceableToolArtifactRecord = {
52+ artifact: StoredToolArtifactRecord;
53+ parameters?: readonly StoredToolArtifactParameterRecord[];
54+ requestBodyContentTypes?: readonly StoredToolArtifactRequestBodyContentTypeRecord[];
55+ refHintKeys?: readonly StoredToolArtifactRefHintKeyRecord[];
56+};
57+
58+const tokenizeQuery = (value: string | undefined): string[] =>
59+ value
60+ ?.trim()
61+ .toLowerCase()
62+ .split(/\s+/)
63+ .filter(Boolean)
64+ ?? [];
65+
66+const likePattern = (token: string): string =>
67+ `%${token.replaceAll("\\", "\\\\").replaceAll("%", "\\%").replaceAll("_", "\\_")}%`;
68+
69+const buildListWhereClause = (
70+ table: DrizzleTables["toolArtifactsTable"],
71+ input: {
72+ workspaceId: StoredToolArtifactRecord["workspaceId"];
73+ sourceId?: StoredToolArtifactRecord["sourceId"];
74+ namespace?: string;
75+ query?: string;
76+ },
77+) => {
78+ const queryTokens = tokenizeQuery(input.query);
79+
80+ return and(
81+ eq(table.workspaceId, input.workspaceId),
82+ input.sourceId ? eq(table.sourceId, input.sourceId) : undefined,
83+ input.namespace ? eq(table.searchNamespace, input.namespace) : undefined,
84+ ...queryTokens.map((token) =>
85+ ilike(table.searchText, likePattern(token)),
86+ ),
87+ );
88+};
89+
90+const buildSearchWhereClause = (
91+ table: DrizzleTables["toolArtifactsTable"],
92+ input: {
93+ workspaceId: StoredToolArtifactRecord["workspaceId"];
94+ namespace?: string;
95+ query: string;
96+ },
97+) => {
98+ const queryTokens = tokenizeQuery(input.query);
99+ if (queryTokens.length === 0) {
100+ return and(
101+ eq(table.workspaceId, input.workspaceId),
102+ input.namespace ? eq(table.searchNamespace, input.namespace) : undefined,
103+ eq(table.workspaceId, "__never__"),
104+ );
105+ }
106+
107+ return and(
108+ eq(table.workspaceId, input.workspaceId),
109+ input.namespace ? eq(table.searchNamespace, input.namespace) : undefined,
110+ or(...queryTokens.map((token) => ilike(table.searchText, likePattern(token)))),
111+ );
112+};
113+
114+export const createToolArtifactsRepo = (
115+ client: DrizzleClient,
116+ tables: DrizzleTables,
117+) => ({
118+ listByWorkspaceId: (
119+ workspaceId: StoredToolArtifactRecord["workspaceId"],
120+ input?: {
121+ sourceId?: StoredToolArtifactRecord["sourceId"];
122+ namespace?: string;
123+ query?: string;
124+ limit?: number;
125+ },
126+ ) =>
127+ client.use("rows.tool_artifacts.list_by_workspace", async (db) => {
128+ const rows = await db
129+ .select()
130+ .from(tables.toolArtifactsTable)
131+ .where(
132+ buildListWhereClause(tables.toolArtifactsTable, {
133+ workspaceId,
134+ sourceId: input?.sourceId,
135+ namespace: input?.namespace,
136+ query: input?.query,
137+ }),
138+ )
139+ .orderBy(
140+ asc(tables.toolArtifactsTable.searchNamespace),
141+ asc(tables.toolArtifactsTable.path),
142+ )
143+ .limit(input?.limit ?? 200);
144+
145+ return rows.map((row) => decodeStoredToolArtifactRecord(row));
146+ }),
147+
148+ listNamespacesByWorkspaceId: (
149+ workspaceId: StoredToolArtifactRecord["workspaceId"],
150+ input?: {
151+ limit?: number;
152+ },
153+ ) =>
154+ client.use("rows.tool_artifacts.list_namespaces_by_workspace", async (db) => {
155+ const rows = await db
156+ .select({
157+ namespace: tables.toolArtifactsTable.searchNamespace,
158+ toolCount: count(),
159+ })
160+ .from(tables.toolArtifactsTable)
161+ .where(eq(tables.toolArtifactsTable.workspaceId, workspaceId))
162+ .groupBy(tables.toolArtifactsTable.searchNamespace)
163+ .orderBy(asc(tables.toolArtifactsTable.searchNamespace))
164+ .limit(input?.limit ?? 200);
165+
166+ return rows.map((row) => ({
167+ namespace: row.namespace,
168+ toolCount: Number(row.toolCount),
169+ }));
170+ }),
171+
172+ searchByWorkspaceId: (
173+ workspaceId: StoredToolArtifactRecord["workspaceId"],
174+ input: {
175+ namespace?: string;
176+ query: string;
177+ limit?: number;
178+ },
179+ ) =>
180+ client.use("rows.tool_artifacts.search_by_workspace", async (db) => {
181+ const rows = await db
182+ .select()
183+ .from(tables.toolArtifactsTable)
184+ .where(
185+ buildSearchWhereClause(tables.toolArtifactsTable, {
186+ workspaceId,
187+ namespace: input.namespace,
188+ query: input.query,
189+ }),
190+ )
191+ .orderBy(
192+ asc(tables.toolArtifactsTable.searchNamespace),
193+ asc(tables.toolArtifactsTable.path),
194+ )
195+ .limit(input.limit ?? 500);
196+
197+ return rows.map((row) => decodeStoredToolArtifactRecord(row));
198+ }),
199+
200+ getByWorkspaceAndPath: (
201+ workspaceId: StoredToolArtifactRecord["workspaceId"],
202+ path: StoredToolArtifactRecord["path"],
203+ ) =>
204+ client.use("rows.tool_artifacts.get_by_workspace_and_path", async (db) => {
205+ const rows = await db
206+ .select()
207+ .from(tables.toolArtifactsTable)
208+ .where(
209+ and(
210+ eq(tables.toolArtifactsTable.workspaceId, workspaceId),
211+ eq(tables.toolArtifactsTable.path, path),
212+ ),
213+ )
214+ .limit(1);
215+
216+ const row = firstOption(rows);
217+ return Option.isSome(row)
218+ ? Option.some(decodeStoredToolArtifactRecord(row.value))
219+ : Option.none<StoredToolArtifactRecord>();
220+ }),
221+
222+ listParametersByWorkspaceAndPath: (
223+ workspaceId: StoredToolArtifactRecord["workspaceId"],
224+ path: StoredToolArtifactRecord["path"],
225+ ) =>
226+ client.use("rows.tool_artifacts.list_parameters_by_workspace_and_path", async (db) => {
227+ const rows = await db
228+ .select()
229+ .from(tables.toolArtifactParametersTable)
230+ .where(
231+ and(
232+ eq(tables.toolArtifactParametersTable.workspaceId, workspaceId),
233+ eq(tables.toolArtifactParametersTable.path, path),
234+ ),
235+ )
236+ .orderBy(asc(tables.toolArtifactParametersTable.position));
237+
238+ return rows.map((row) => decodeStoredToolArtifactParameterRecord(row));
239+ }),
240+
241+ listRequestBodyContentTypesByWorkspaceAndPath: (
242+ workspaceId: StoredToolArtifactRecord["workspaceId"],
243+ path: StoredToolArtifactRecord["path"],
244+ ) =>
245+ client.use(
246+ "rows.tool_artifacts.list_request_body_content_types_by_workspace_and_path",
247+ async (db) => {
248+ const rows = await db
249+ .select()
250+ .from(tables.toolArtifactRequestBodyContentTypesTable)
251+ .where(
252+ and(
253+ eq(tables.toolArtifactRequestBodyContentTypesTable.workspaceId, workspaceId),
254+ eq(tables.toolArtifactRequestBodyContentTypesTable.path, path),
255+ ),
256+ )
257+ .orderBy(asc(tables.toolArtifactRequestBodyContentTypesTable.position));
258+
259+ return rows.map((row) => decodeStoredToolArtifactRequestBodyContentTypeRecord(row));
260+ },
261+ ),
262+
263+ listRefHintKeysByWorkspaceAndPath: (
264+ workspaceId: StoredToolArtifactRecord["workspaceId"],
265+ path: StoredToolArtifactRecord["path"],
266+ ) =>
267+ client.use("rows.tool_artifacts.list_ref_hint_keys_by_workspace_and_path", async (db) => {
268+ const rows = await db
269+ .select()
270+ .from(tables.toolArtifactRefHintKeysTable)
271+ .where(
272+ and(
273+ eq(tables.toolArtifactRefHintKeysTable.workspaceId, workspaceId),
274+ eq(tables.toolArtifactRefHintKeysTable.path, path),
275+ ),
276+ )
277+ .orderBy(asc(tables.toolArtifactRefHintKeysTable.position));
278+
279+ return rows.map((row) => decodeStoredToolArtifactRefHintKeyRecord(row));
280+ }),
281+
282+ replaceForSource: (input: {
283+ workspaceId: StoredToolArtifactRecord["workspaceId"];
284+ sourceId: StoredToolArtifactRecord["sourceId"];
285+ artifacts: readonly ReplaceableToolArtifactRecord[];
286+ }) =>
287+ client.useTx("rows.tool_artifacts.replace_for_source", async (tx) => {
288+ const existingPaths = (
289+ await tx
290+ .select({
291+ path: tables.toolArtifactsTable.path,
292+ })
293+ .from(tables.toolArtifactsTable)
294+ .where(
295+ and(
296+ eq(tables.toolArtifactsTable.workspaceId, input.workspaceId),
297+ eq(tables.toolArtifactsTable.sourceId, input.sourceId),
298+ ),
299+ )
300+ ).map((row) => row.path);
301+
302+ if (existingPaths.length > 0) {
303+ await tx
304+ .delete(tables.toolArtifactParametersTable)
305+ .where(
306+ and(
307+ eq(tables.toolArtifactParametersTable.workspaceId, input.workspaceId),
308+ or(...existingPaths.map((path) => eq(tables.toolArtifactParametersTable.path, path))),
309+ ),
310+ );
311+ await tx
312+ .delete(tables.toolArtifactRequestBodyContentTypesTable)
313+ .where(
314+ and(
315+ eq(
316+ tables.toolArtifactRequestBodyContentTypesTable.workspaceId,
317+ input.workspaceId,
318+ ),
319+ or(
320+ ...existingPaths.map((path) =>
321+ eq(tables.toolArtifactRequestBodyContentTypesTable.path, path)
322+ ),
323+ ),
324+ ),
325+ );
326+ await tx
327+ .delete(tables.toolArtifactRefHintKeysTable)
328+ .where(
329+ and(
330+ eq(tables.toolArtifactRefHintKeysTable.workspaceId, input.workspaceId),
331+ or(...existingPaths.map((path) => eq(tables.toolArtifactRefHintKeysTable.path, path))),
332+ ),
333+ );
334+ }
335+
336+ await tx
337+ .delete(tables.toolArtifactsTable)
338+ .where(
339+ and(
340+ eq(tables.toolArtifactsTable.workspaceId, input.workspaceId),
341+ eq(tables.toolArtifactsTable.sourceId, input.sourceId),
342+ ),
343+ );
344+
345+ if (input.artifacts.length === 0) {
346+ return;
347+ }
348+
349+ await tx.insert(tables.toolArtifactsTable).values(
350+ input.artifacts.map(({ artifact }) => encodeStoredToolArtifactRecord(artifact)),
351+ );
352+
353+ const parameterRows = input.artifacts.flatMap(({ parameters = [] }) => parameters);
354+ if (parameterRows.length > 0) {
355+ await tx.insert(tables.toolArtifactParametersTable).values(
356+ parameterRows.map((record) => encodeStoredToolArtifactParameterRecord(record)),
357+ );
358+ }
359+
360+ const requestBodyContentTypeRows = input.artifacts.flatMap(
361+ ({ requestBodyContentTypes = [] }) => requestBodyContentTypes,
362+ );
363+ if (requestBodyContentTypeRows.length > 0) {
364+ await tx.insert(tables.toolArtifactRequestBodyContentTypesTable).values(
365+ requestBodyContentTypeRows.map((record) =>
366+ encodeStoredToolArtifactRequestBodyContentTypeRecord(record)
367+ ),
368+ );
369+ }
370+
371+ const refHintKeyRows = input.artifacts.flatMap(({ refHintKeys = [] }) => refHintKeys);
372+ if (refHintKeyRows.length > 0) {
373+ await tx.insert(tables.toolArtifactRefHintKeysTable).values(
374+ refHintKeyRows.map((record) => encodeStoredToolArtifactRefHintKeyRecord(record)),
375+ );
376+ }
377+ }),
378+
379+ removeByWorkspaceAndSourceId: (
380+ workspaceId: StoredToolArtifactRecord["workspaceId"],
381+ sourceId: StoredToolArtifactRecord["sourceId"],
382+ ) =>
383+ client.useTx("rows.tool_artifacts.remove_by_workspace_and_source_id", async (tx) => {
384+ const existingPaths = (
385+ await tx
386+ .select({
387+ path: tables.toolArtifactsTable.path,
388+ })
389+ .from(tables.toolArtifactsTable)
390+ .where(
391+ and(
392+ eq(tables.toolArtifactsTable.workspaceId, workspaceId),
393+ eq(tables.toolArtifactsTable.sourceId, sourceId),
394+ ),
395+ )
396+ ).map((row) => row.path);
397+
398+ if (existingPaths.length > 0) {
399+ await tx
400+ .delete(tables.toolArtifactParametersTable)
401+ .where(
402+ and(
403+ eq(tables.toolArtifactParametersTable.workspaceId, workspaceId),
404+ or(...existingPaths.map((path) => eq(tables.toolArtifactParametersTable.path, path))),
405+ ),
406+ );
407+ await tx
408+ .delete(tables.toolArtifactRequestBodyContentTypesTable)
409+ .where(
410+ and(
411+ eq(tables.toolArtifactRequestBodyContentTypesTable.workspaceId, workspaceId),
412+ or(
413+ ...existingPaths.map((path) =>
414+ eq(tables.toolArtifactRequestBodyContentTypesTable.path, path)
415+ ),
416+ ),
417+ ),
418+ );
419+ await tx
420+ .delete(tables.toolArtifactRefHintKeysTable)
421+ .where(
422+ and(
423+ eq(tables.toolArtifactRefHintKeysTable.workspaceId, workspaceId),
424+ or(...existingPaths.map((path) => eq(tables.toolArtifactRefHintKeysTable.path, path))),
425+ ),
426+ );
427+ }
428+
429+ const deleted = await tx
430+ .delete(tables.toolArtifactsTable)
431+ .where(
432+ and(
433+ eq(tables.toolArtifactsTable.workspaceId, workspaceId),
434+ eq(tables.toolArtifactsTable.sourceId, sourceId),
435+ ),
436+ )
437+ .returning();
438+
439+ return deleted.length;
440+ }),
441+});
packages/control-plane/src/persistence/schema.ts+139−0
@@ -16,6 +16,10 @@ export const tableNames = { · 15 unmodified lines
1616 organizationMemberships: "organization_memberships",
1717 workspaces: "workspaces",
1818 sources: "sources",
19+ toolArtifacts: "tool_artifacts",
20+ toolArtifactParameters: "tool_artifact_parameters",
21+ toolArtifactRequestBodyContentTypes: "tool_artifact_request_body_content_types",
22+ toolArtifactRefHintKeys: "tool_artifact_ref_hint_keys",
1923 sourceCredentialBindings: "source_credential_bindings",
2024 secretMaterials: "secret_materials",
2125 sourceAuthSessions: "source_auth_sessions",
@@ -183,6 +187,137 @@ export const sourceCredentialBindingsTable = pgTable( · 161 unmodified lines
183187 ],
184188 );
185189
190+export const toolArtifactsTable = pgTable(
191+ tableNames.toolArtifacts,
192+ {
193+ workspaceId: text("workspace_id").notNull(),
194+ path: text("path").notNull(),
195+ toolId: text("tool_id").notNull(),
196+ sourceId: text("source_id").notNull(),
197+ title: text("title"),
198+ description: text("description"),
199+ searchNamespace: text("search_namespace").notNull(),
200+ searchText: text("search_text").notNull(),
201+ inputSchemaJson: text("input_schema_json"),
202+ outputSchemaJson: text("output_schema_json"),
203+ providerKind: text("provider_kind").notNull(),
204+ mcpToolName: text("mcp_tool_name"),
205+ openApiMethod: text("openapi_method"),
206+ openApiPathTemplate: text("openapi_path_template"),
207+ openApiOperationHash: text("openapi_operation_hash"),
208+ openApiRequestBodyRequired: boolean("openapi_request_body_required"),
209+ createdAt: bigint("created_at", { mode: "number" }).notNull(),
210+ updatedAt: bigint("updated_at", { mode: "number" }).notNull(),
211+ },
212+ (table) => [
213+ primaryKey({
214+ columns: [table.workspaceId, table.path],
215+ }),
216+ index("tool_artifacts_workspace_source_idx").on(
217+ table.workspaceId,
218+ table.sourceId,
219+ table.updatedAt,
220+ table.path,
221+ ),
222+ index("tool_artifacts_workspace_namespace_idx").on(
223+ table.workspaceId,
224+ table.searchNamespace,
225+ table.path,
226+ ),
227+ check(
228+ "tool_artifacts_provider_kind_check",
229+ sql`${table.providerKind} in ('mcp', 'openapi')`,
230+ ),
231+ check(
232+ "tool_artifacts_mcp_shape_check",
233+ sql`${table.providerKind} <> 'mcp'
234+ or (
235+ ${table.mcpToolName} is not null
236+ and ${table.openApiMethod} is null
237+ and ${table.openApiPathTemplate} is null
238+ and ${table.openApiOperationHash} is null
239+ and ${table.openApiRequestBodyRequired} is null
240+ )`,
241+ ),
242+ check(
243+ "tool_artifacts_openapi_shape_check",
244+ sql`${table.providerKind} <> 'openapi'
245+ or (
246+ ${table.mcpToolName} is null
247+ and ${table.openApiMethod} in ('get', 'put', 'post', 'delete', 'patch', 'head', 'options', 'trace')
248+ and ${table.openApiPathTemplate} is not null
249+ and ${table.openApiOperationHash} is not null
250+ )`,
251+ ),
252+ ],
253+);
254+
255+export const toolArtifactParametersTable = pgTable(
256+ tableNames.toolArtifactParameters,
257+ {
258+ workspaceId: text("workspace_id").notNull(),
259+ path: text("path").notNull(),
260+ position: bigint("position", { mode: "number" }).notNull(),
261+ name: text("name").notNull(),
262+ location: text("location").notNull(),
263+ required: boolean("required").notNull(),
264+ },
265+ (table) => [
266+ primaryKey({
267+ columns: [table.workspaceId, table.path, table.position],
268+ }),
269+ index("tool_artifact_parameters_lookup_idx").on(
270+ table.workspaceId,
271+ table.path,
272+ table.position,
273+ ),
274+ check(
275+ "tool_artifact_parameters_location_check",
276+ sql`${table.location} in ('path', 'query', 'header', 'cookie')`,
277+ ),
278+ ],
279+);
280+
281+export const toolArtifactRequestBodyContentTypesTable = pgTable(
282+ tableNames.toolArtifactRequestBodyContentTypes,
283+ {
284+ workspaceId: text("workspace_id").notNull(),
285+ path: text("path").notNull(),
286+ position: bigint("position", { mode: "number" }).notNull(),
287+ contentType: text("content_type").notNull(),
288+ },
289+ (table) => [
290+ primaryKey({
291+ columns: [table.workspaceId, table.path, table.position],
292+ }),
293+ index("tool_artifact_request_body_content_types_lookup_idx").on(
294+ table.workspaceId,
295+ table.path,
296+ table.position,
297+ ),
298+ ],
299+);
300+
301+export const toolArtifactRefHintKeysTable = pgTable(
302+ tableNames.toolArtifactRefHintKeys,
303+ {
304+ workspaceId: text("workspace_id").notNull(),
305+ path: text("path").notNull(),
306+ position: bigint("position", { mode: "number" }).notNull(),
307+ refHintKey: text("ref_hint_key").notNull(),
308+ },
309+ (table) => [
310+ primaryKey({
311+ columns: [table.workspaceId, table.path, table.position],
312+ }),
313+ index("tool_artifact_ref_hint_keys_lookup_idx").on(
314+ table.workspaceId,
315+ table.path,
316+ table.position,
317+ ),
318+ ],
319+);
320+
186321 export const secretMaterialsTable = pgTable(
187322 tableNames.secretMaterials,
188323 {
@@ -349,6 +484,10 @@ export const drizzleSchema = { · 160 unmodified lines
349484 organizationMembershipsTable,
350485 workspacesTable,
351486 sourcesTable,
487+ toolArtifactsTable,
488+ toolArtifactParametersTable,
489+ toolArtifactRequestBodyContentTypesTable,
490+ toolArtifactRefHintKeysTable,
352491 sourceCredentialBindingsTable,
353492 secretMaterialsTable,
354493 sourceAuthSessionsTable,
packages/control-plane/src/runtime/source-auth-service.ts+79−14
@@ -27,6 +27,7 @@ import { · 26 unmodified lines
2727 type WorkspaceId,
2828 } from "#schema";
2929 import * as Context from "effect/Context";
30+import * as Either from "effect/Either";
3031 import * as Effect from "effect/Effect";
3132 import * as Layer from "effect/Layer";
3233 import * as Option from "effect/Option";
@@ -43,6 +44,11 @@ import { · 10 unmodified lines
4344 splitSourceForStorage,
4445 updateSourceFromPayload,
4546 } from "./source-definitions";
47+import {
48+ createEnvSecretMaterialResolver,
49+ persistMcpToolArtifactsFromManifest,
50+ syncSourceToolArtifacts,
51+} from "./tool-artifacts";
4652
4753 export const CONTROL_PLANE_SECRET_PROVIDER_ID = "control-plane";
4854 const OAUTH_CALLBACK_PATH = "/v1/local/oauth/callback";
@@ -505,7 +511,13 @@ export const createRuntimeSourceAuthService = (input: { · 456 unmodified lines
505511 rows: SqlControlPlaneRows;
506512 liveExecutionManager: LiveExecutionManager;
507513 getLocalServerBaseUrl?: () => string | undefined;
508−}) => ({
514+}) => {
515+ const resolveSecretMaterial = createDbBackedSecretMaterialResolver({
516+ rows: input.rows,
517+ fallback: createEnvSecretMaterialResolver(),
518+ });
519+
520+ return {
509521 getSourceById: ({ workspaceId, sourceId }) =>
510522 loadSourceById(input.rows, {
511523 workspaceId,
@@ -570,22 +582,52 @@ export const createRuntimeSourceAuthService = (input: { · 58 unmodified lines
570582 });
571583
572584 const persistedDraft = yield* persistSource(input.rows, draftSource);
585+ yield* syncSourceToolArtifacts({
586+ rows: input.rows,
587+ source: persistedDraft,
588+ resolveSecretMaterial,
589+ });
573590
574591 const discovered = yield* Effect.either(
575592 probeMcpSourceWithoutAuth(persistedDraft),
576593 );
577594
578− if (discovered._tag === "Right") {
579− const connected = yield* updateSourceStatus(input.rows, persistedDraft, {
580− status: "connected",
581− lastError: null,
582− auth: { kind: "none" },
583− });
595+ const connectedResult = yield* Either.match(discovered, {
596+ onLeft: () => Effect.succeed(null),
597+ onRight: (result) =>
598+ Effect.gen(function* () {
599+ const connected = yield* updateSourceStatus(input.rows, persistedDraft, {
600+ status: "connected",
601+ lastError: null,
602+ auth: { kind: "none" },
603+ });
604+ const indexed = yield* Effect.either(
605+ persistMcpToolArtifactsFromManifest({
606+ rows: input.rows,
607+ source: connected,
608+ manifestEntries: result.manifest.tools,
609+ }),
610+ );
611+
612+ return yield* Either.match(indexed, {
613+ onLeft: (error) =>
614+ updateSourceStatus(input.rows, connected, {
615+ status: "error",
616+ lastError: error.message,
617+ }).pipe(
618+ Effect.zipRight(Effect.fail(error)),
619+ ),
620+ onRight: () =>
621+ Effect.succeed({
622+ kind: "connected",
623+ source: connected,
624+ } satisfies ExecutorSourceAddResult),
625+ });
626+ }),
627+ });
584628
585− return {
586− kind: "connected",
587− source: connected,
588− } satisfies ExecutorSourceAddResult;
629+ if (connectedResult) {
630+ return connectedResult;
589631 }
590632
591633 const localServerBaseUrl = input.getLocalServerBaseUrl?.();
@@ -676,10 +718,15 @@ export const createRuntimeSourceAuthService = (input: { · 84 unmodified lines
676718 completedAt: failedAt,
677719 updatedAt: failedAt,
678720 });
679− yield* updateSourceStatus(input.rows, source, {
721+ const failedSource = yield* updateSourceStatus(input.rows, source, {
680722 status: "error",
681723 lastError: reason,
682724 });
725+ yield* syncSourceToolArtifacts({
726+ rows: input.rows,
727+ source: failedSource,
728+ resolveSecretMaterial,
729+ });
683730 yield* completeLiveInteraction({
684731 liveExecutionManager: input.liveExecutionManager,
685732 session,
@@ -743,9 +790,26 @@ export const createRuntimeSourceAuthService = (input: { · 57 unmodified lines
743790 : {
744791 providerId: CONTROL_PLANE_SECRET_PROVIDER_ID,
745792 handle: refreshTokenId,
746− },
793+ },
747794 },
748795 });
796+ const indexed = yield* Effect.either(
797+ syncSourceToolArtifacts({
798+ rows: input.rows,
799+ source: connectedSource,
800+ resolveSecretMaterial,
801+ }),
802+ );
803+ yield* Either.match(indexed, {
804+ onLeft: (error) =>
805+ updateSourceStatus(input.rows, connectedSource, {
806+ status: "error",
807+ lastError: error.message,
808+ }).pipe(
809+ Effect.zipRight(Effect.fail(error)),
810+ ),
811+ onRight: () => Effect.succeed(undefined),
812+ });
749813
750814 yield* input.rows.sourceAuthSessions.update(session.id, {
751815 status: "completed",
@@ -769,7 +833,8 @@ export const createRuntimeSourceAuthService = (input: { · 17 unmodified lines
769833
770834 return connectedSource;
771835 }),
772−} satisfies RuntimeSourceAuthServiceShape);
836+ } satisfies RuntimeSourceAuthServiceShape;
837+};
773838
774839 export type RuntimeSourceAuthService = ReturnType<
775840 typeof createRuntimeSourceAuthService
packages/control-plane/src/runtime/sources-operations.ts+92−6
@@ -4,9 +4,11 @@ import type { · 3 unmodified lines
44 } from "../api/sources/api";
55 import {
66 SourceIdSchema,
7+ type Source,
78 type SourceId,
89 type WorkspaceId,
910 } from "#schema";
11+import * as Either from "effect/Either";
1012 import * as Effect from "effect/Effect";
1113 import * as Option from "effect/Option";
1214
@@ -23,7 +25,14 @@ import { · 10 unmodified lines
2325 import {
2426 operationErrors,
2527 } from "./operation-errors";
26−import { ControlPlaneStore } from "./store";
28+import {
29+ createDbBackedSecretMaterialResolver,
30+} from "./source-auth-service";
31+import { ControlPlaneStore, type ControlPlaneStoreShape } from "./store";
32+import {
33+ createEnvSecretMaterialResolver,
34+ syncSourceToolArtifacts,
35+} from "./tool-artifacts";
2736
2837 const sourceOps = {
2938 list: operationErrors("sources.list"),
@@ -33,6 +42,67 @@ const sourceOps = { · 3 unmodified lines
3342 remove: operationErrors("sources.remove"),
3443 } as const;
3544
45+const syncArtifactsForSource = (input: {
46+ store: ControlPlaneStoreShape;
47+ source: Source;
48+ operation:
49+ | typeof sourceOps.create
50+ | typeof sourceOps.update;
51+}) =>
52+ Effect.gen(function* () {
53+ const resolveSecretMaterial = createDbBackedSecretMaterialResolver({
54+ rows: input.store,
55+ fallback: createEnvSecretMaterialResolver(),
56+ });
57+
58+ const synced = yield* Effect.either(
59+ syncSourceToolArtifacts({
60+ rows: input.store,
61+ source: input.source,
62+ resolveSecretMaterial,
63+ }),
64+ );
65+
66+ return yield* Either.match(synced, {
67+ onRight: () => Effect.succeed(input.source),
68+ onLeft: (error) =>
69+ Effect.gen(function* () {
70+ if (input.source.enabled && input.source.status === "connected") {
71+ const erroredSource = yield* updateSourceFromPayload({
72+ source: input.source,
73+ payload: {
74+ status: "error",
75+ lastError: error.message,
76+ },
77+ now: Date.now(),
78+ }).pipe(
79+ Effect.mapError((cause) =>
80+ input.operation.badRequest(
81+ "Failed indexing source tools",
82+ cause instanceof Error ? cause.message : String(cause),
83+ ),
84+ ),
85+ );
86+
87+ const { sourceRecord } = splitSourceForStorage({
88+ source: erroredSource,
89+ });
90+ yield* mapPersistenceError(
91+ input.operation.child("source_error"),
92+ input.store.sources.update(input.source.workspaceId, input.source.id, {
93+ ...sourceRecord,
94+ updatedAt: erroredSource.updatedAt,
95+ }),
96+ );
97+ }
98+
99+ return yield* Effect.fail(
100+ input.operation.unknownStorage(error, "Failed syncing source tools"),
101+ );
102+ }),
103+ });
104+ });
105+
36106 export const listSources = (workspaceId: WorkspaceId) =>
37107 Effect.flatMap(ControlPlaneStore, (store) =>
38108 Effect.gen(function* () {
@@ -93,7 +163,11 @@ export const createSource = (input: { · 54 unmodified lines
93163 );
94164 }
95165
96− return source;
166+ return yield* syncArtifactsForSource({
167+ store,
168+ source,
169+ operation: sourceOps.create,
170+ });
97171 }));
98172
99173 export const getSource = (input: {
@@ -222,7 +296,11 @@ export const updateSource = (input: { · 122 unmodified lines
222296 );
223297 }
224298
225− return updatedSource;
299+ return yield* syncArtifactsForSource({
300+ store,
301+ source: updatedSource,
302+ operation: sourceOps.update,
303+ });
226304 }));
227305
228306 export const removeSource = (input: {
@@ -230,7 +308,15 @@ export const removeSource = (input: { · 1 unmodified line
230308 sourceId: SourceId;
231309 }) =>
232310 Effect.flatMap(ControlPlaneStore, (store) =>
233− sourceOps.remove.mapStorage(
234− store.sources.removeByWorkspaceAndId(input.workspaceId, input.sourceId),
235− ).pipe(Effect.map((removed) => ({ removed })))
311+ Effect.gen(function* () {
312+ yield* sourceOps.remove.child("artifacts").mapStorage(
313+ store.toolArtifacts.removeByWorkspaceAndSourceId(input.workspaceId, input.sourceId),
314+ );
315+
316+ const removed = yield* sourceOps.remove.mapStorage(
317+ store.sources.removeByWorkspaceAndId(input.workspaceId, input.sourceId),
318+ );
319+
320+ return { removed };
321+ })
236322 );
packages/control-plane/src/runtime/tool-artifacts.ts+393−0
@@ -0,0 +1,393 @@
1+import {
2+ createSdkMcpConnector,
3+ discoverMcpToolsFromConnector,
4+ type McpToolManifestEntry,
5+} from "@executor-v3/codemode-mcp";
6+import {
7+ extractOpenApiManifest,
8+ fetchOpenApiDocument,
9+ type OpenApiExtractedTool,
10+} from "@executor-v3/codemode-openapi";
11+import type { SqlControlPlaneRows } from "#persistence";
12+import {
13+ type SecretRef,
14+ type Source,
15+ type StoredToolArtifactParameterRecord,
16+ type StoredToolArtifactRecord,
17+ type StoredToolArtifactRefHintKeyRecord,
18+ type StoredToolArtifactRequestBodyContentTypeRecord,
19+} from "#schema";
20+import * as Effect from "effect/Effect";
21+
22+export type ResolveSourceSecretMaterial = (
23+ ref: SecretRef,
24+) => Effect.Effect<string, Error, never>;
25+
26+export type ResolvedSourceAuthMaterial = {
27+ headers: Readonly<Record<string, string>>;
28+};
29+
30+const trim = (value: string | undefined): string | undefined => {
31+ const candidate = value?.trim();
32+ return candidate && candidate.length > 0 ? candidate : undefined;
33+};
34+
35+const joinToolPath = (namespace: string | null, toolId: string): string => {
36+ const normalizedNamespace = trim(namespace ?? undefined);
37+ return normalizedNamespace ? `${normalizedNamespace}.${toolId}` : toolId;
38+};
39+
40+const normalizeSearchText = (...parts: ReadonlyArray<string | null | undefined>): string =>
41+ parts
42+ .flatMap((part) => (part ? [part.trim()] : []))
43+ .filter(Boolean)
44+ .join(" ")
45+ .toLowerCase();
46+
47+const catalogNamespaceFromPath = (path: string): string => {
48+ const [first, second] = path.split(".");
49+ return second ? `${first}.${second}` : first;
50+};
51+
52+type IndexedToolArtifactRecord = {
53+ artifact: StoredToolArtifactRecord;
54+ parameters?: readonly StoredToolArtifactParameterRecord[];
55+ requestBodyContentTypes?: readonly StoredToolArtifactRequestBodyContentTypeRecord[];
56+ refHintKeys?: readonly StoredToolArtifactRefHintKeyRecord[];
57+};
58+
59+const toMcpToolArtifactRecord = (input: {
60+ workspaceId: Source["workspaceId"];
61+ source: Source;
62+ entry: {
63+ toolId: string;
64+ toolName: string;
65+ description: string | null;
66+ inputSchemaJson?: string;
67+ outputSchemaJson?: string;
68+ };
69+ now: number;
70+}): IndexedToolArtifactRecord => {
71+ const sourceNamespace = input.source.namespace ?? namespaceFromSourceName(input.source.name);
72+ const path = joinToolPath(sourceNamespace, input.entry.toolId);
73+ const searchNamespace = catalogNamespaceFromPath(path);
74+
75+ return {
76+ artifact: {
77+ workspaceId: input.workspaceId,
78+ path,
79+ toolId: input.entry.toolId,
80+ sourceId: input.source.id,
81+ title: input.entry.toolName,
82+ description: input.entry.description ?? null,
83+ searchNamespace,
84+ searchText: normalizeSearchText(
85+ path,
86+ searchNamespace,
87+ input.entry.toolName,
88+ input.entry.description ?? undefined,
89+ ),
90+ inputSchemaJson: input.entry.inputSchemaJson ?? null,
91+ outputSchemaJson: input.entry.outputSchemaJson ?? null,
92+ providerKind: "mcp",
93+ mcpToolName: input.entry.toolName,
94+ openApiMethod: null,
95+ openApiPathTemplate: null,
96+ openApiOperationHash: null,
97+ openApiRequestBodyRequired: null,
98+ createdAt: input.now,
99+ updatedAt: input.now,
100+ },
101+ };
102+};
103+
104+const toOpenApiToolArtifactRecord = (input: {
105+ workspaceId: Source["workspaceId"];
106+ source: Source;
107+ extracted: OpenApiExtractedTool;
108+ now: number;
109+}): IndexedToolArtifactRecord => {
110+ const sourceNamespace = input.source.namespace ?? namespaceFromSourceName(input.source.name);
111+ const path = joinToolPath(sourceNamespace, input.extracted.toolId);
112+ const searchNamespace = catalogNamespaceFromPath(path);
113+ const description =
114+ input.extracted.description
115+ ?? `${input.extracted.method.toUpperCase()} ${input.extracted.path}`;
116+
117+ return {
118+ artifact: {
119+ workspaceId: input.workspaceId,
120+ path,
121+ toolId: input.extracted.toolId,
122+ sourceId: input.source.id,
123+ title: input.extracted.name,
124+ description,
125+ searchNamespace,
126+ searchText: normalizeSearchText(
127+ path,
128+ searchNamespace,
129+ input.extracted.name,
130+ description,
131+ input.extracted.method.toUpperCase(),
132+ input.extracted.path,
133+ ),
134+ inputSchemaJson: input.extracted.typing?.inputSchemaJson ?? null,
135+ outputSchemaJson: input.extracted.typing?.outputSchemaJson ?? null,
136+ providerKind: "openapi",
137+ mcpToolName: null,
138+ openApiMethod: input.extracted.method,
139+ openApiPathTemplate: input.extracted.invocation.pathTemplate,
140+ openApiOperationHash: input.extracted.operationHash,
141+ openApiRequestBodyRequired: input.extracted.invocation.requestBody?.required ?? null,
142+ createdAt: input.now,
143+ updatedAt: input.now,
144+ },
145+ parameters: input.extracted.invocation.parameters.map((parameter, position) => ({
146+ workspaceId: input.workspaceId,
147+ path,
148+ position,
149+ name: parameter.name,
150+ location: parameter.location,
151+ required: parameter.required,
152+ })),
153+ requestBodyContentTypes:
154+ input.extracted.invocation.requestBody?.contentTypes.map((contentType, position) => ({
155+ workspaceId: input.workspaceId,
156+ path,
157+ position,
158+ contentType,
159+ })) ?? [],
160+ refHintKeys:
161+ input.extracted.typing?.refHintKeys?.map((refHintKey, position) => ({
162+ workspaceId: input.workspaceId,
163+ path,
164+ position,
165+ refHintKey,
166+ })) ?? [],
167+ };
168+};
169+
170+const shouldIndexSource = (source: Source): boolean =>
171+ source.enabled
172+ && source.status === "connected"
173+ && (source.kind === "mcp" || source.kind === "openapi");
174+
175+export const namespaceFromSourceName = (name: string): string => {
176+ const normalized = name
177+ .trim()
178+ .toLowerCase()
179+ .replace(/[^a-z0-9]+/g, ".")
180+ .replace(/^\.+|\.+$/g, "");
181+
182+ return normalized.length > 0 ? normalized : "source";
183+};
184+
185+export const createEnvSecretMaterialResolver = (): ResolveSourceSecretMaterial =>
186+ (ref) =>
187+ Effect.gen(function* () {
188+ if (ref.providerId !== "env") {
189+ return yield* Effect.fail(
190+ new Error(`Unsupported secret provider ${ref.providerId}`),
191+ );
192+ }
193+
194+ const value = process.env[ref.handle]?.trim();
195+ if (!value) {
196+ return yield* Effect.fail(
197+ new Error(`Environment variable ${ref.handle} is not set`),
198+ );
199+ }
200+
201+ return value;
202+ });
203+
204+export const resolveSourceAuthMaterial = (input: {
205+ source: Source;
206+ resolveSecretMaterial: ResolveSourceSecretMaterial;
207+}): Effect.Effect<ResolvedSourceAuthMaterial, Error, never> =>
208+ Effect.gen(function* () {
209+ if (input.source.auth.kind === "none") {
210+ return { headers: {} } satisfies ResolvedSourceAuthMaterial;
211+ }
212+
213+ const tokenRef =
214+ input.source.auth.kind === "bearer"
215+ ? input.source.auth.token
216+ : input.source.auth.accessToken;
217+
218+ const token = yield* input.resolveSecretMaterial(tokenRef);
219+
220+ return {
221+ headers: {
222+ [input.source.auth.headerName]: `${input.source.auth.prefix}${token}`,
223+ },
224+ } satisfies ResolvedSourceAuthMaterial;
225+ });
226+
227+const indexMcpSourceToolArtifacts = (input: {
228+ rows: SqlControlPlaneRows;
229+ source: Source;
230+ manifestEntries: readonly McpToolManifestEntry[];
231+}): Effect.Effect<void, Error, never> =>
232+ Effect.gen(function* () {
233+ const now = Date.now();
234+ yield* input.rows.toolArtifacts.replaceForSource({
235+ workspaceId: input.source.workspaceId,
236+ sourceId: input.source.id,
237+ artifacts: input.manifestEntries.map((entry) =>
238+ toMcpToolArtifactRecord({
239+ workspaceId: input.source.workspaceId,
240+ source: input.source,
241+ entry,
242+ now,
243+ })
244+ ),
245+ }).pipe(
246+ Effect.mapError((cause) =>
247+ cause instanceof Error ? cause : new Error(String(cause)),
248+ ),
249+ );
250+ });
251+
252+const discoverAndIndexMcpSourceToolArtifacts = (input: {
253+ rows: SqlControlPlaneRows;
254+ source: Source;
255+ auth: ResolvedSourceAuthMaterial;
256+}): Effect.Effect<void, Error, never> =>
257+ Effect.gen(function* () {
258+ const connector = yield* Effect.try({
259+ try: () =>
260+ createSdkMcpConnector({
261+ endpoint: input.source.endpoint,
262+ transport: input.source.transport ?? undefined,
263+ queryParams: input.source.queryParams ?? undefined,
264+ headers: {
265+ ...(input.source.headers ?? {}),
266+ ...input.auth.headers,
267+ },
268+ }),
269+ catch: (cause) =>
270+ cause instanceof Error ? cause : new Error(String(cause)),
271+ });
272+
273+ const discovered = yield* discoverMcpToolsFromConnector({
274+ connect: connector,
275+ namespace: input.source.namespace ?? namespaceFromSourceName(input.source.name),
276+ sourceKey: input.source.id,
277+ }).pipe(
278+ Effect.mapError(
279+ (cause) =>
280+ new Error(
281+ `Failed discovering MCP tools for ${input.source.id}: ${cause.message}`,
282+ ),
283+ ),
284+ );
285+
286+ return yield* indexMcpSourceToolArtifacts({
287+ rows: input.rows,
288+ source: input.source,
289+ manifestEntries: discovered.manifest.tools,
290+ });
291+ });
292+
293+const indexOpenApiSourceToolArtifacts = (input: {
294+ rows: SqlControlPlaneRows;
295+ source: Source;
296+}): Effect.Effect<void, Error, never> =>
297+ Effect.gen(function* () {
298+ if (!input.source.specUrl) {
299+ return yield* Effect.fail(
300+ new Error(`Missing OpenAPI specUrl for source ${input.source.id}`),
301+ );
302+ }
303+
304+ const openApiDocument = yield* Effect.tryPromise({
305+ try: () => fetchOpenApiDocument(input.source.specUrl!),
306+ catch: (cause) =>
307+ cause instanceof Error
308+ ? new Error(
309+ `Failed fetching OpenAPI spec for ${input.source.id}: ${cause.message}`,
310+ )
311+ : new Error(`Failed fetching OpenAPI spec for ${input.source.id}: ${String(cause)}`),
312+ });
313+
314+ const manifest = yield* extractOpenApiManifest(
315+ input.source.name,
316+ openApiDocument,
317+ ).pipe(
318+ Effect.mapError((cause) =>
319+ cause instanceof Error
320+ ? cause
321+ : new Error(String(cause)),
322+ ),
323+ );
324+
325+ const now = Date.now();
326+ yield* input.rows.toolArtifacts.replaceForSource({
327+ workspaceId: input.source.workspaceId,
328+ sourceId: input.source.id,
329+ artifacts: manifest.tools.map((extracted) =>
330+ toOpenApiToolArtifactRecord({
331+ workspaceId: input.source.workspaceId,
332+ source: input.source,
333+ extracted,
334+ now,
335+ })
336+ ),
337+ }).pipe(
338+ Effect.mapError((cause) =>
339+ cause instanceof Error ? cause : new Error(String(cause)),
340+ ),
341+ );
342+ });
343+
344+export const syncSourceToolArtifacts = (input: {
345+ rows: SqlControlPlaneRows;
346+ source: Source;
347+ resolveSecretMaterial: ResolveSourceSecretMaterial;
348+}): Effect.Effect<void, Error, never> =>
349+ Effect.gen(function* () {
350+ if (!shouldIndexSource(input.source)) {
351+ yield* input.rows.toolArtifacts.removeByWorkspaceAndSourceId(
352+ input.source.workspaceId,
353+ input.source.id,
354+ ).pipe(
355+ Effect.mapError((cause) =>
356+ cause instanceof Error ? cause : new Error(String(cause)),
357+ ),
358+ );
359+ return;
360+ }
361+
362+ const auth = yield* resolveSourceAuthMaterial({
363+ source: input.source,
364+ resolveSecretMaterial: input.resolveSecretMaterial,
365+ });
366+
367+ if (input.source.kind === "mcp") {
368+ return yield* discoverAndIndexMcpSourceToolArtifacts({
369+ rows: input.rows,
370+ source: input.source,
371+ auth,
372+ });
373+ }
374+
375+ if (input.source.kind === "openapi") {
376+ return yield* indexOpenApiSourceToolArtifacts({
377+ rows: input.rows,
378+ source: input.source,
379+ });
380+ }
381+
382+ return;
383+ });
384+
385+export const storedToolIdFromArtifact = (artifact: StoredToolArtifactRecord): string =>
386+ artifact.toolId;
387+
388+export const persistMcpToolArtifactsFromManifest = (input: {
389+ rows: SqlControlPlaneRows;
390+ source: Source;
391+ manifestEntries: readonly McpToolManifestEntry[];
392+}): Effect.Effect<void, Error, never> =>
393+ indexMcpSourceToolArtifacts(input);
packages/control-plane/src/runtime/workspace-execution-environment.test.ts+410−0
@@ -0,0 +1,410 @@
1+import { randomUUID } from "node:crypto";
2+
3+import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js";
4+import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5+import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
6+import { describe, expect, it } from "@effect/vitest";
7+import {
8+ AccountIdSchema,
9+ ExecutionIdSchema,
10+ SourceIdSchema,
11+ WorkspaceIdSchema,
12+} from "#schema";
13+import * as Effect from "effect/Effect";
14+import { z } from "zod/v4";
15+
16+import {
17+ createSqlControlPlanePersistence,
18+ type SqlControlPlanePersistence,
19+} from "#persistence";
20+import type { SourceId, WorkspaceId } from "#schema";
21+
22+import type { RuntimeSourceAuthService } from "./source-auth-service";
23+import { createWorkspaceExecutionEnvironmentResolver } from "./workspace-execution-environment";
24+
25+type CountedMcpServer = {
26+ endpoint: string;
27+ counts: {
28+ listTools: number;
29+ callTool: number;
30+ };
31+ close: () => Promise<void>;
32+};
33+
34+const makePersistence = Effect.acquireRelease(
35+ createSqlControlPlanePersistence({
36+ localDataDir: ":memory:",
37+ }),
38+ (persistence) =>
39+ Effect.tryPromise({
40+ try: () => persistence.close(),
41+ catch: (cause) => (cause instanceof Error ? cause : new Error(String(cause))),
42+ }).pipe(Effect.orDie),
43+);
44+
45+const makeCountedMcpServer = Effect.acquireRelease(
46+ Effect.promise<CountedMcpServer>(
47+ () =>
48+ new Promise<CountedMcpServer>((resolve, reject) => {
49+ const counts = {
50+ listTools: 0,
51+ callTool: 0,
52+ };
53+ let closed = false;
54+ const app = createMcpExpressApp({ host: "127.0.0.1" });
55+ const transports: Record<string, StreamableHTTPServerTransport> = {};
56+ const servers: Record<string, McpServer> = {};
57+
58+ const createServer = () => {
59+ const server = new McpServer(
60+ {
61+ name: "workspace-execution-environment-test-server",
62+ version: "1.0.0",
63+ },
64+ {
65+ capabilities: {
66+ tools: {},
67+ },
68+ },
69+ );
70+
71+ server.registerTool(
72+ "echo",
73+ {
74+ description: "Echo the provided string",
75+ inputSchema: {
76+ value: z.string(),
77+ },
78+ },
79+ async ({ value }: { value: string }) => ({
80+ content: [{ type: "text", text: `echo:${value}` }],
81+ }),
82+ );
83+
84+ return server;
85+ };
86+
87+ const countMethods = (body: unknown) => {
88+ const payloads = Array.isArray(body) ? body : [body];
89+ for (const payload of payloads) {
90+ if (!payload || typeof payload !== "object") {
91+ continue;
92+ }
93+
94+ const method = "method" in payload ? payload.method : undefined;
95+ if (method === "tools/list") {
96+ counts.listTools += 1;
97+ } else if (method === "tools/call") {
98+ counts.callTool += 1;
99+ }
100+ }
101+ };
102+
103+ app.post("/mcp", async (req: any, res: any) => {
104+ countMethods(req.body);
105+
106+ const sessionIdHeader = req.headers["mcp-session-id"];
107+ const sessionId =
108+ typeof sessionIdHeader === "string"
109+ ? sessionIdHeader
110+ : Array.isArray(sessionIdHeader)
111+ ? sessionIdHeader[0]
112+ : undefined;
113+
114+ try {
115+ let transport: StreamableHTTPServerTransport;
116+
117+ if (sessionId && transports[sessionId]) {
118+ transport = transports[sessionId];
119+ } else {
120+ transport = new StreamableHTTPServerTransport({
121+ sessionIdGenerator: () => randomUUID(),
122+ onsessioninitialized: (newSessionId) => {
123+ transports[newSessionId] = transport;
124+ },
125+ });
126+
127+ transport.onclose = () => {
128+ const closedSessionId = transport.sessionId;
129+ if (closedSessionId && transports[closedSessionId]) {
130+ delete transports[closedSessionId];
131+ }
132+ if (closedSessionId && servers[closedSessionId]) {
133+ void servers[closedSessionId].close().catch(() => undefined);
134+ delete servers[closedSessionId];
135+ }
136+ };
137+
138+ const server = createServer();
139+ await server.connect(transport);
140+ const newSessionId = transport.sessionId;
141+ if (newSessionId) {
142+ servers[newSessionId] = server;
143+ }
144+ }
145+
146+ await transport.handleRequest(req, res, req.body);
147+ } catch (error) {
148+ if (!res.headersSent) {
149+ res.status(500).json({
150+ jsonrpc: "2.0",
151+ error: {
152+ code: -32603,
153+ message:
154+ error instanceof Error ? error.message : "Internal server error",
155+ },
156+ id: null,
157+ });
158+ }
159+ }
160+ });
161+
162+ app.get("/mcp", async (req: any, res: any) => {
163+ const sessionIdHeader = req.headers["mcp-session-id"];
164+ const sessionId =
165+ typeof sessionIdHeader === "string"
166+ ? sessionIdHeader
167+ : Array.isArray(sessionIdHeader)
168+ ? sessionIdHeader[0]
169+ : undefined;
170+
171+ if (!sessionId || !transports[sessionId]) {
172+ res.status(400).send("Invalid or missing session ID");
173+ return;
174+ }
175+
176+ await transports[sessionId].handleRequest(req, res);
177+ });
178+
179+ app.delete("/mcp", async (req: any, res: any) => {
180+ const sessionIdHeader = req.headers["mcp-session-id"];
181+ const sessionId =
182+ typeof sessionIdHeader === "string"
183+ ? sessionIdHeader
184+ : Array.isArray(sessionIdHeader)
185+ ? sessionIdHeader[0]
186+ : undefined;
187+
188+ if (!sessionId || !transports[sessionId]) {
189+ res.status(400).send("Invalid or missing session ID");
190+ return;
191+ }
192+
193+ const transport = transports[sessionId];
194+ await transport.handleRequest(req, res, req.body);
195+ await transport.close();
196+ delete transports[sessionId];
197+
198+ if (servers[sessionId]) {
199+ await servers[sessionId].close().catch(() => undefined);
200+ delete servers[sessionId];
201+ }
202+ });
203+
204+ const listener = app.listen(0, "127.0.0.1", () => {
205+ const address = listener.address();
206+ if (!address || typeof address === "string") {
207+ reject(new Error("failed to resolve MCP test server address"));
208+ return;
209+ }
210+
211+ resolve({
212+ endpoint: `http://127.0.0.1:${address.port}/mcp`,
213+ counts,
214+ close: async () => {
215+ if (closed) {
216+ return;
217+ }
218+ closed = true;
219+
220+ for (const transport of Object.values(transports)) {
221+ await transport.close().catch(() => undefined);
222+ }
223+
224+ for (const server of Object.values(servers)) {
225+ await server.close().catch(() => undefined);
226+ }
227+
228+ await new Promise<void>((closeResolve, closeReject) => {
229+ listener.close((error: Error | undefined) => {
230+ if (error) {
231+ closeReject(error);
232+ return;
233+ }
234+ closeResolve();
235+ });
236+ });
237+ },
238+ });
239+ });
240+
241+ listener.once("error", reject);
242+ }),
243+ ),
244+ (server) =>
245+ Effect.tryPromise({
246+ try: () => server.close(),
247+ catch: (error: unknown) =>
248+ error instanceof Error ? error : new Error(String(error)),
249+ }).pipe(Effect.orDie),
250+);
251+
252+const persistConnectedEchoTool = (input: {
253+ persistence: SqlControlPlanePersistence;
254+ endpoint: string;
255+ workspaceId: WorkspaceId;
256+ sourceId: SourceId;
257+}) =>
258+ Effect.gen(function* () {
259+ const now = Date.now();
260+
261+ yield* input.persistence.rows.sources.insert({
262+ id: input.sourceId,
263+ workspaceId: input.workspaceId,
264+ name: "Counted MCP",
265+ kind: "mcp",
266+ endpoint: input.endpoint,
267+ status: "connected",
268+ enabled: true,
269+ namespace: "counted",
270+ transport: "auto",
271+ queryParamsJson: null,
272+ headersJson: null,
273+ specUrl: null,
274+ defaultHeadersJson: null,
275+ authKind: "none",
276+ authHeaderName: null,
277+ authPrefix: null,
278+ sourceHash: null,
279+ lastError: null,
280+ createdAt: now,
281+ updatedAt: now,
282+ });
283+
284+ yield* input.persistence.rows.toolArtifacts.replaceForSource({
285+ workspaceId: input.workspaceId,
286+ sourceId: input.sourceId,
287+ artifacts: [{
288+ artifact: {
289+ workspaceId: input.workspaceId,
290+ path: "counted.echo",
291+ toolId: "echo",
292+ sourceId: input.sourceId,
293+ title: "echo",
294+ description: "Echo the provided string",
295+ searchNamespace: "counted.echo",
296+ searchText: "counted.echo echo provided string",
297+ inputSchemaJson: JSON.stringify({
298+ type: "object",
299+ properties: {
300+ value: {
301+ type: "string",
302+ },
303+ },
304+ required: ["value"],
305+ additionalProperties: false,
306+ }),
307+ outputSchemaJson: null,
308+ providerKind: "mcp",
309+ mcpToolName: "echo",
310+ openApiMethod: null,
311+ openApiPathTemplate: null,
312+ openApiOperationHash: null,
313+ openApiRequestBodyRequired: null,
314+ createdAt: now,
315+ updatedAt: now,
316+ },
317+ }],
318+ });
319+ });
320+
321+const makeResolver = (persistence: SqlControlPlanePersistence) =>
322+ createWorkspaceExecutionEnvironmentResolver({
323+ rows: persistence.rows,
324+ sourceAuthService: {
325+ getSourceById: () => Effect.fail(new Error("not implemented in test")),
326+ addExecutorMcpSource: () => Effect.fail(new Error("not implemented in test")),
327+ completeSourceAuthCallback: () => Effect.fail(new Error("not implemented in test")),
328+ } as RuntimeSourceAuthService,
329+ });
330+
331+describe("workspace-execution-environment", () => {
332+ it.scoped("discovers persisted tools without live MCP listing", () =>
333+ Effect.gen(function* () {
334+ const server = yield* makeCountedMcpServer;
335+ const persistence = yield* makePersistence;
336+ const workspaceId = WorkspaceIdSchema.make("ws_catalog");
337+ const sourceId = SourceIdSchema.make("src_catalog");
338+ yield* persistConnectedEchoTool({
339+ persistence,
340+ endpoint: server.endpoint,
341+ workspaceId,
342+ sourceId,
343+ });
344+
345+ yield* Effect.tryPromise({
346+ try: () => server.close(),
347+ catch: (cause) => (cause instanceof Error ? cause : new Error(String(cause))),
348+ }).pipe(Effect.orDie);
349+
350+ const resolveEnvironment = makeResolver(persistence);
351+ const environment = yield* resolveEnvironment({
352+ workspaceId,
353+ accountId: AccountIdSchema.make("acc_catalog"),
354+ executionId: ExecutionIdSchema.make("exec_catalog"),
355+ });
356+
357+ const discovered = (yield* environment.toolInvoker.invoke({
358+ path: "discover",
359+ args: {
360+ query: "echo string",
361+ limit: 5,
362+ },
363+ })) as {
364+ bestPath: string | null;
365+ total: number;
366+ results: Array<{ path: string }>;
367+ };
368+
369+ expect(discovered.bestPath).toBe("counted.echo");
370+ expect(discovered.total).toBeGreaterThan(0);
371+ expect(server.counts.listTools).toBe(0);
372+ expect(server.counts.callTool).toBe(0);
373+ }),
374+ );
375+
376+ it.scoped("invokes persisted MCP tools on demand without re-listing", () =>
377+ Effect.gen(function* () {
378+ const server = yield* makeCountedMcpServer;
379+ const persistence = yield* makePersistence;
380+ const workspaceId = WorkspaceIdSchema.make("ws_invoke");
381+ const sourceId = SourceIdSchema.make("src_invoke");
382+ yield* persistConnectedEchoTool({
383+ persistence,
384+ endpoint: server.endpoint,
385+ workspaceId,
386+ sourceId,
387+ });
388+
389+ const resolveEnvironment = makeResolver(persistence);
390+ const environment = yield* resolveEnvironment({
391+ workspaceId,
392+ accountId: AccountIdSchema.make("acc_invoke"),
393+ executionId: ExecutionIdSchema.make("exec_invoke"),
394+ });
395+
396+ const result = (yield* environment.toolInvoker.invoke({
397+ path: "counted.echo",
398+ args: {
399+ value: "hello",
400+ },
401+ })) as {
402+ content?: Array<{ text?: string }>;
403+ };
404+
405+ expect(server.counts.listTools).toBe(0);
406+ expect(server.counts.callTool).toBe(1);
407+ expect(result.content?.[0]?.text).toBe("echo:hello");
408+ }),
409+ );
410+});
packages/control-plane/src/runtime/workspace-execution-environment.ts+433−216
@@ -1,17 +1,20 @@
11 import {
2− type ToolMap,
3− createToolCatalogFromTools,
42 createSystemToolMap,
3+ createToolCatalogFromTools,
54 makeToolInvokerFromTools,
6− mergeToolMaps,
5+ type SearchHit,
6+ type ToolCatalog,
7+ type ToolDescriptor,
8+ type ToolInvoker,
9+ type ToolNamespace,
10+ type ToolPath,
711 } from "@executor-v3/codemode-core";
812 import {
913 createSdkMcpConnector,
10− discoverMcpToolsFromConnector,
14+ createMcpToolsFromManifest,
1115 } from "@executor-v3/codemode-mcp";
1216 import {
13− createOpenApiToolsFromSpec,
14− fetchOpenApiDocument,
17+ createOpenApiToolsFromManifest,
1518 } from "@executor-v3/codemode-openapi";
1619 import { makeInProcessExecutor } from "@executor-v3/runtime-local-inproc";
1720 import {
@@ -20,192 +23,468 @@ import { · 2 unmodified lines
2023 } from "#persistence";
2124 import type {
2225 Source,
26+ StoredToolArtifactRecord,
2327 } from "#schema";
2428 import * as Context from "effect/Context";
2529 import * as Effect from "effect/Effect";
2630 import * as Layer from "effect/Layer";
31+import * as Option from "effect/Option";
2732
2833 import type {
2934 ExecutionEnvironment,
3035 ResolveExecutionEnvironment,
3136 } from "./execution-state";
3237 import { createExecutorToolMap } from "./executor-tools";
33−import { projectSourcesFromStorage } from "./source-definitions";
38+import { projectSourceFromStorage } from "./source-definitions";
3439 import {
3540 RuntimeSourceAuthServiceTag,
3641 createDbBackedSecretMaterialResolver,
3742 type ResolveSecretMaterial,
3843 type RuntimeSourceAuthService,
3944 } from "./source-auth-service";
45+import {
46+ createEnvSecretMaterialResolver,
47+ namespaceFromSourceName,
48+ resolveSourceAuthMaterial,
49+ storedToolIdFromArtifact,
50+} from "./tool-artifacts";
4051
41−export type ResolvedSourceAuthMaterial = {
42− headers: Readonly<Record<string, string>>;
43−};
52+const asToolPath = (value: string): ToolPath => value as ToolPath;
4453
45−const namespaceFromSourceName = (name: string): string => {
46− const normalized = name
54+const tokenize = (value: string): string[] =>
55+ value
4756 .trim()
4857 .toLowerCase()
49− .replace(/[^a-z0-9]+/g, ".")
50− .replace(/^\.+|\.+$/g, "");
58+ .split(/\s+/)
59+ .filter(Boolean);
60+
61+const scoreArtifact = (
62+ queryTokens: readonly string[],
63+ searchText: string,
64+): number =>
65+ queryTokens.reduce(
66+ (total, token) => total + (searchText.includes(token) ? 1 : 0),
67+ 0,
68+ );
69+
70+const toDescriptor = (input: {
71+ artifact: StoredToolArtifactRecord;
72+ includeSchemas: boolean;
73+ refHintKeys?: readonly string[];
74+}): ToolDescriptor => ({
75+ path: asToolPath(input.artifact.path),
76+ sourceKey: input.artifact.sourceId,
77+ description: input.artifact.description ?? input.artifact.title ?? undefined,
78+ interaction: "auto",
79+ inputHint: input.artifact.inputSchemaJson ? "object" : undefined,
80+ outputHint: input.artifact.outputSchemaJson ? "output" : undefined,
81+ inputSchemaJson: input.includeSchemas ? input.artifact.inputSchemaJson ?? undefined : undefined,
82+ outputSchemaJson: input.includeSchemas ? input.artifact.outputSchemaJson ?? undefined : undefined,
83+ refHintKeys: input.includeSchemas ? input.refHintKeys : undefined,
84+});
85+
86+const loadSourceById = (input: {
87+ rows: SqlControlPlaneRows;
88+ workspaceId: Source["workspaceId"];
89+ sourceId: Source["id"];
90+}): Effect.Effect<Source, Error, never> =>
91+ Effect.gen(function* () {
92+ const sourceRecord = yield* input.rows.sources.getByWorkspaceAndId(
93+ input.workspaceId,
94+ input.sourceId,
95+ ).pipe(
96+ Effect.mapError((cause) =>
97+ cause instanceof Error ? cause : new Error(String(cause)),
98+ ),
99+ );
51100
52− return normalized.length > 0 ? normalized : "source";
53−};
101+ if (Option.isNone(sourceRecord)) {
102+ return yield* Effect.fail(
103+ new Error(`Source not found: workspaceId=${input.workspaceId} sourceId=${input.sourceId}`),
104+ );
105+ }
106+
107+ const credentialBinding = yield* input.rows.sourceCredentialBindings
108+ .getByWorkspaceAndSourceId(input.workspaceId, input.sourceId)
109+ .pipe(
110+ Effect.mapError((cause) =>
111+ cause instanceof Error ? cause : new Error(String(cause)),
112+ ),
113+ );
114+
115+ return yield* projectSourceFromStorage({
116+ sourceRecord: sourceRecord.value,
117+ credentialBinding: Option.isSome(credentialBinding) ? credentialBinding.value : null,
118+ });
119+ });
54120
55−export const createEnvSecretMaterialResolver = (): ResolveSecretMaterial =>
56− (ref) =>
121+const createWorkspaceToolCatalog = (input: {
122+ workspaceId: Source["workspaceId"];
123+ rows: SqlControlPlaneRows;
124+ executorCatalog: ToolCatalog;
125+}): ToolCatalog => ({
126+ listNamespaces: ({ limit }) =>
57127 Effect.gen(function* () {
58− if (ref.providerId !== "env") {
59− return yield* Effect.fail(
60− new Error(`Unsupported secret provider ${ref.providerId}`),
61− );
62− }
128+ const [persisted, executor] = yield* Effect.all([
129+ input.rows.toolArtifacts.listNamespacesByWorkspaceId(input.workspaceId, {
130+ limit,
131+ }).pipe(
132+ Effect.mapError((cause) =>
133+ cause instanceof Error ? cause : new Error(String(cause)),
134+ ),
135+ ),
136+ input.executorCatalog.listNamespaces({ limit }),
137+ ]);
63138
64− const value = process.env[ref.handle]?.trim();
65− if (!value) {
66− return yield* Effect.fail(
67− new Error(`Environment variable ${ref.handle} is not set`),
68− );
139+ const merged = new Map<string, ToolNamespace>();
140+ for (const namespace of persisted) {
141+ merged.set(namespace.namespace, namespace);
142+ }
143+ for (const namespace of executor) {
144+ const existing = merged.get(namespace.namespace);
145+ merged.set(namespace.namespace, {
146+ namespace: namespace.namespace,
147+ displayName: namespace.displayName ?? existing?.displayName,
148+ toolCount:
149+ namespace.toolCount !== undefined || existing?.toolCount === undefined
150+ ? namespace.toolCount
151+ : existing.toolCount,
152+ });
69153 }
70154
71− return value;
72− });
73−
74−export const resolveSourceAuthMaterial = (input: {
75− source: Source;
76− resolveSecretMaterial: ResolveSecretMaterial;
77−}): Effect.Effect<ResolvedSourceAuthMaterial, Error, never> =>
78− Effect.gen(function* () {
79− if (input.source.auth.kind === "none") {
80− return { headers: {} } satisfies ResolvedSourceAuthMaterial;
81− }
155+ return [...merged.values()]
156+ .sort((left, right) => left.namespace.localeCompare(right.namespace))
157+ .slice(0, limit);
158+ }),
82159
83− const tokenRef =
84− input.source.auth.kind === "bearer"
85− ? input.source.auth.token
86− : input.source.auth.accessToken;
160+ listTools: ({ namespace, query, limit, includeSchemas = false }) =>
161+ Effect.gen(function* () {
162+ const [persisted, executor] = yield* Effect.all([
163+ namespace?.startsWith("executor")
164+ ? Effect.succeed([] as readonly StoredToolArtifactRecord[])
165+ : input.rows.toolArtifacts.listByWorkspaceId(input.workspaceId, {
166+ namespace,
167+ query,
168+ limit,
169+ }).pipe(
170+ Effect.mapError((cause) =>
171+ cause instanceof Error ? cause : new Error(String(cause)),
172+ ),
173+ ),
174+ input.executorCatalog.listTools({
175+ ...(namespace !== undefined ? { namespace } : {}),
176+ ...(query !== undefined ? { query } : {}),
177+ limit,
178+ includeSchemas,
179+ }),
180+ ]);
181+
182+ const persistedDescriptors = includeSchemas
183+ ? yield* Effect.forEach(
184+ persisted,
185+ (artifact) =>
186+ input.rows.toolArtifacts.listRefHintKeysByWorkspaceAndPath(
187+ input.workspaceId,
188+ artifact.path,
189+ ).pipe(
190+ Effect.map((rows) =>
191+ toDescriptor({
192+ artifact,
193+ includeSchemas,
194+ refHintKeys: rows.map((row) => row.refHintKey),
195+ })
196+ ),
197+ Effect.mapError((cause) =>
198+ cause instanceof Error ? cause : new Error(String(cause)),
199+ ),
200+ ),
201+ { concurrency: "unbounded" },
202+ )
203+ : persisted.map((artifact) =>
204+ toDescriptor({
205+ artifact,
206+ includeSchemas,
207+ })
208+ );
209+
210+ return [...persistedDescriptors, ...executor]
211+ .sort((left, right) => left.path.localeCompare(right.path))
212+ .slice(0, limit);
213+ }),
214+
215+ getToolByPath: ({ path, includeSchemas }) =>
216+ Effect.gen(function* () {
217+ const executor = yield* input.executorCatalog.getToolByPath({
218+ path,
219+ includeSchemas,
220+ });
221+ if (executor) {
222+ return executor;
223+ }
87224
88− const token = yield* input.resolveSecretMaterial(tokenRef);
225+ const artifact = yield* input.rows.toolArtifacts.getByWorkspaceAndPath(
226+ input.workspaceId,
227+ path,
228+ ).pipe(
229+ Effect.mapError((cause) =>
230+ cause instanceof Error ? cause : new Error(String(cause)),
231+ ),
232+ );
89233
90− return {
91− headers: {
92− [input.source.auth.headerName]: `${input.source.auth.prefix}${token}`,
93− },
94− } satisfies ResolvedSourceAuthMaterial;
95− });
234+ if (Option.isNone(artifact)) {
235+ return null;
236+ }
96237
97−const loadMcpSourceTools = (input: {
98− source: Source;
99− auth: ResolvedSourceAuthMaterial;
100−}): Effect.Effect<ToolMap, Error, never> =>
101− Effect.gen(function* () {
102− if (input.source.kind !== "mcp") {
103− return yield* Effect.fail(new Error(`Expected MCP source, received ${input.source.kind}`));
104− }
238+ const refHintKeys = includeSchemas
239+ ? yield* input.rows.toolArtifacts.listRefHintKeysByWorkspaceAndPath(
240+ input.workspaceId,
241+ path,
242+ ).pipe(
243+ Effect.map((rows) => rows.map((row) => row.refHintKey)),
244+ Effect.mapError((cause) =>
245+ cause instanceof Error ? cause : new Error(String(cause)),
246+ ),
247+ )
248+ : undefined;
249+
250+ return toDescriptor({
251+ artifact: artifact.value,
252+ includeSchemas,
253+ refHintKeys,
254+ });
255+ }),
105256
106− const connector = yield* Effect.try({
107− try: () =>
108− createSdkMcpConnector({
109− endpoint: input.source.endpoint,
110− transport: input.source.transport ?? undefined,
111− queryParams: input.source.queryParams ?? undefined,
112− headers: {
113− ...(input.source.headers ?? {}),
114− ...input.auth.headers,
115− },
257+ searchTools: ({ query, namespace, limit }) =>
258+ Effect.gen(function* () {
259+ const queryTokens = tokenize(query);
260+ const [persisted, executor] = yield* Effect.all([
261+ namespace?.startsWith("executor")
262+ ? Effect.succeed([] as readonly StoredToolArtifactRecord[])
263+ : input.rows.toolArtifacts.searchByWorkspaceId(input.workspaceId, {
264+ namespace,
265+ query,
266+ limit: Math.max(limit * 8, 50),
267+ }).pipe(
268+ Effect.mapError((cause) =>
269+ cause instanceof Error ? cause : new Error(String(cause)),
270+ ),
271+ ),
272+ input.executorCatalog.searchTools({
273+ query,
274+ ...(namespace !== undefined ? { namespace } : {}),
275+ limit,
116276 }),
117− catch: (cause) =>
118− cause instanceof Error
119− ? new Error(
120− `Failed creating MCP connector for ${input.source.id}: ${cause.message}`,
121− )
122− : new Error(`Failed creating MCP connector for ${input.source.id}: ${String(cause)}`),
123− });
277+ ]);
278+
279+ const persistedHits: SearchHit[] = persisted
280+ .map((artifact) => ({
281+ path: asToolPath(artifact.path),
282+ score: scoreArtifact(queryTokens, artifact.searchText),
283+ }))
284+ .filter((hit) => hit.score > 0);
285+
286+ return [...persistedHits, ...executor]
287+ .sort((left, right) =>
288+ right.score - left.score || left.path.localeCompare(right.path),
289+ )
290+ .slice(0, limit);
291+ }),
292+});
293+
294+const createWorkspaceToolInvoker = (input: {
295+ workspaceId: Source["workspaceId"];
296+ rows: SqlControlPlaneRows;
297+ resolveSecretMaterial: ResolveSecretMaterial;
298+ sourceAuthService: RuntimeSourceAuthService;
299+ onElicitation?: Parameters<typeof makeToolInvokerFromTools>[0]["onElicitation"];
300+}): {
301+ catalog: ToolCatalog;
302+ toolInvoker: ToolInvoker;
303+} => {
304+ const executorTools = createExecutorToolMap({
305+ workspaceId: input.workspaceId,
306+ sourceAuthService: input.sourceAuthService,
307+ });
308+ const executorCatalog = createToolCatalogFromTools({
309+ tools: executorTools,
310+ });
311+ const catalog = createWorkspaceToolCatalog({
312+ workspaceId: input.workspaceId,
313+ rows: input.rows,
314+ executorCatalog,
315+ });
316+ const systemTools = createSystemToolMap({ catalog });
317+ const systemToolPaths = new Set(Object.keys(systemTools));
318+ const executorToolPaths = new Set(Object.keys(executorTools));
319+ const systemInvoker = makeToolInvokerFromTools({
320+ tools: systemTools,
321+ onElicitation: input.onElicitation,
322+ });
323+ const executorInvoker = makeToolInvokerFromTools({
324+ tools: executorTools,
325+ onElicitation: input.onElicitation,
326+ });
124327
125− const discovered = yield* discoverMcpToolsFromConnector({
126− connect: connector,
127− namespace: input.source.namespace ?? namespaceFromSourceName(input.source.name),
128− sourceKey: input.source.id,
129− }).pipe(
130− Effect.mapError(
131− (cause) =>
132− new Error(
133− `Failed discovering MCP tools for ${input.source.id}: ${cause.message}`,
328+ const invokePersistedTool = (invocation: {
329+ path: string;
330+ args: unknown;
331+ context?: Record<string, unknown>;
332+ }) =>
333+ Effect.gen(function* () {
334+ const artifactOption = yield* input.rows.toolArtifacts
335+ .getByWorkspaceAndPath(input.workspaceId, invocation.path)
336+ .pipe(
337+ Effect.mapError((cause) =>
338+ cause instanceof Error ? cause : new Error(String(cause)),
134339 ),
135− ),
136− );
340+ );
137341
138− return discovered.tools;
139− });
342+ if (Option.isNone(artifactOption)) {
343+ return yield* Effect.fail(new Error(`Unknown tool path: ${invocation.path}`));
344+ }
140345
141−const loadOpenApiSourceTools = (input: {
142− source: Source;
143− auth: ResolvedSourceAuthMaterial;
144−}): Effect.Effect<ToolMap, Error, never> =>
145− Effect.gen(function* () {
146− if (input.source.kind !== "openapi") {
147− return yield* Effect.fail(
148− new Error(`Expected OpenAPI source, received ${input.source.kind}`),
149− );
150− }
346+ const artifact = artifactOption.value;
347+ const source = yield* loadSourceById({
348+ rows: input.rows,
349+ workspaceId: input.workspaceId,
350+ sourceId: artifact.sourceId,
351+ });
151352
152− if (!input.source.specUrl) {
153− return yield* Effect.fail(
154− new Error(`Missing OpenAPI specUrl for source ${input.source.id}`),
155− );
156− }
353+ if (!source.enabled || source.status !== "connected") {
354+ return yield* Effect.fail(
355+ new Error(`Source for tool path ${invocation.path} is not connected`),
356+ );
357+ }
157358
158− const openApiDocument = yield* Effect.tryPromise({
159− try: () => fetchOpenApiDocument(input.source.specUrl!),
160− catch: (cause) =>
161− cause instanceof Error
162− ? new Error(
163− `Failed fetching OpenAPI spec for ${input.source.id}: ${cause.message}`,
164− )
165− : new Error(`Failed fetching OpenAPI spec for ${input.source.id}: ${String(cause)}`),
166− });
359+ const auth = yield* resolveSourceAuthMaterial({
360+ source,
361+ resolveSecretMaterial: input.resolveSecretMaterial,
362+ });
167363
168− const extracted = yield* createOpenApiToolsFromSpec({
169− sourceName: input.source.name,
170− openApiSpec: openApiDocument,
171− baseUrl: input.source.endpoint,
172− namespace: input.source.namespace ?? namespaceFromSourceName(input.source.name),
173− sourceKey: input.source.id,
174− defaultHeaders: input.source.defaultHeaders ?? {},
175− credentialHeaders: input.auth.headers,
176− }).pipe(
177− Effect.mapError(
178− (cause: unknown) =>
179− new Error(
180− `Failed loading OpenAPI tools for ${input.source.id}: ${
181− cause instanceof Error ? cause.message : String(cause)
182− }`,
183− ),
184− ),
185− );
364+ if (artifact.providerKind === "mcp") {
365+ const tools = createMcpToolsFromManifest({
366+ manifest: {
367+ version: 1,
368+ tools: [{
369+ toolId: storedToolIdFromArtifact(artifact),
370+ toolName: artifact.mcpToolName ?? artifact.title ?? artifact.path,
371+ description: artifact.description ?? null,
372+ ...(artifact.inputSchemaJson ? { inputSchemaJson: artifact.inputSchemaJson } : {}),
373+ ...(artifact.outputSchemaJson ? { outputSchemaJson: artifact.outputSchemaJson } : {}),
374+ }],
375+ },
376+ connect: createSdkMcpConnector({
377+ endpoint: source.endpoint,
378+ transport: source.transport ?? undefined,
379+ queryParams: source.queryParams ?? undefined,
380+ headers: {
381+ ...(source.headers ?? {}),
382+ ...auth.headers,
383+ },
384+ }),
385+ namespace: source.namespace ?? namespaceFromSourceName(source.name),
386+ sourceKey: source.id,
387+ });
388+
389+ return yield* makeToolInvokerFromTools({
390+ tools,
391+ onElicitation: input.onElicitation,
392+ }).invoke({
393+ path: invocation.path,
394+ args: invocation.args,
395+ context: invocation.context,
396+ });
397+ }
186398
187− return extracted.tools;
188− });
399+ if (artifact.providerKind === "openapi") {
400+ const [parameters, requestBodyContentTypes, refHintKeys] = yield* Effect.all([
401+ input.rows.toolArtifacts.listParametersByWorkspaceAndPath(
402+ input.workspaceId,
403+ artifact.path,
404+ ),
405+ input.rows.toolArtifacts.listRequestBodyContentTypesByWorkspaceAndPath(
406+ input.workspaceId,
407+ artifact.path,
408+ ),
409+ input.rows.toolArtifacts.listRefHintKeysByWorkspaceAndPath(
410+ input.workspaceId,
411+ artifact.path,
412+ ),
413+ ]).pipe(
414+ Effect.mapError((cause) =>
415+ cause instanceof Error ? cause : new Error(String(cause)),
416+ ),
417+ );
189418
190−const loadSourceTools = (input: {
191− source: Source;
192− auth: ResolvedSourceAuthMaterial;
193−}): Effect.Effect<ToolMap, Error, never> => {
194− if (input.source.kind === "mcp") {
195− return loadMcpSourceTools({
196− source: input.source,
197− auth: input.auth,
198− });
199− }
419+ const tools = createOpenApiToolsFromManifest({
420+ manifest: {
421+ version: 1,
422+ sourceHash: source.sourceHash ?? "stored",
423+ tools: [{
424+ toolId: storedToolIdFromArtifact(artifact),
425+ name: artifact.title ?? storedToolIdFromArtifact(artifact),
426+ description: artifact.description ?? null,
427+ method: artifact.openApiMethod!,
428+ path: artifact.openApiPathTemplate!,
429+ invocation: {
430+ method: artifact.openApiMethod!,
431+ pathTemplate: artifact.openApiPathTemplate!,
432+ parameters: parameters.map((parameter) => ({
433+ name: parameter.name,
434+ location: parameter.location,
435+ required: parameter.required,
436+ })),
437+ requestBody:
438+ artifact.openApiRequestBodyRequired === null
439+ ? null
440+ : {
441+ required: artifact.openApiRequestBodyRequired,
442+ contentTypes: requestBodyContentTypes.map((row) => row.contentType),
443+ },
444+ },
445+ operationHash: artifact.openApiOperationHash!,
446+ typing: {
447+ ...(artifact.inputSchemaJson ? { inputSchemaJson: artifact.inputSchemaJson } : {}),
448+ ...(artifact.outputSchemaJson ? { outputSchemaJson: artifact.outputSchemaJson } : {}),
449+ ...(refHintKeys.length > 0
450+ ? { refHintKeys: refHintKeys.map((row) => row.refHintKey) }
451+ : {}),
452+ },
453+ }],
454+ },
455+ baseUrl: source.endpoint,
456+ namespace: source.namespace ?? namespaceFromSourceName(source.name),
457+ sourceKey: source.id,
458+ defaultHeaders: source.defaultHeaders ?? {},
459+ credentialHeaders: auth.headers,
460+ });
461+
462+ return yield* makeToolInvokerFromTools({
463+ tools,
464+ onElicitation: input.onElicitation,
465+ }).invoke({
466+ path: invocation.path,
467+ args: invocation.args,
468+ context: invocation.context,
469+ });
470+ }
200471
201− if (input.source.kind === "openapi") {
202− return loadOpenApiSourceTools({
203− source: input.source,
204− auth: input.auth,
472+ return yield* Effect.fail(
473+ new Error(`Unsupported stored tool provider for ${invocation.path}`),
474+ );
205475 });
206− }
207476
208− return Effect.succeed({});
477+ return {
478+ catalog,
479+ toolInvoker: {
480+ invoke: ({ path, args, context }) =>
481+ systemToolPaths.has(path)
482+ ? systemInvoker.invoke({ path, args, context })
483+ : executorToolPaths.has(path)
484+ ? executorInvoker.invoke({ path, args, context })
485+ : invokePersistedTool({ path, args, context }),
486+ },
487+ };
209488 };
210489
211490 export const createWorkspaceExecutionEnvironmentResolver = (input: {
@@ -221,80 +500,18 @@ export const createWorkspaceExecutionEnvironmentResolver = (input: { · 9 unmodified lines
221500 });
222501
223502 return ({ workspaceId, onElicitation }) =>
224− Effect.gen(function* () {
225− const sourceRecords = yield* input.rows.sources.listByWorkspaceId(workspaceId).pipe(
226− Effect.mapError((cause) =>
227− cause instanceof Error ? cause : new Error(String(cause)),
228− ),
229− );
230− const credentialBindings = yield* input.rows.sourceCredentialBindings
231− .listByWorkspaceId(workspaceId)
232− .pipe(
233− Effect.mapError((cause) =>
234− cause instanceof Error ? cause : new Error(String(cause)),
235− ),
236− );
237−
238− const sources = yield* projectSourcesFromStorage({
239− sourceRecords,
240− credentialBindings,
241− });
242− const enabledSources = sources.filter(
243− (source) => source.enabled && source.status === "connected",
244− );
245−
246− const discoveredToolMaps = yield* Effect.forEach(
247− enabledSources,
248− (source) =>
249− Effect.gen(function* () {
250− const auth = yield* resolveSourceAuthMaterial({
251− source,
252− resolveSecretMaterial,
253− });
254− return yield* loadSourceTools({
255− source,
256− auth,
257− });
258− }),
259− { concurrency: "unbounded" },
260− );
261−
262− const executorTools = createExecutorToolMap({
503+ Effect.sync(() => {
504+ const { catalog, toolInvoker } = createWorkspaceToolInvoker({
263505 workspaceId,
506+ rows: input.rows,
507+ resolveSecretMaterial,
264508 sourceAuthService: input.sourceAuthService,
265− });
266−
267− const sourceTools = yield* Effect.try({
268− try: () =>
269− mergeToolMaps([...discoveredToolMaps, executorTools], {
270− conflictMode: "throw",
271− }),
272− catch: (cause) =>
273− cause instanceof Error
274− ? new Error(`Failed merging discovered source tools: ${cause.message}`)
275− : new Error(`Failed merging discovered source tools: ${String(cause)}`),
276− });
277− const catalog = yield* Effect.try({
278− try: () => createToolCatalogFromTools({ tools: sourceTools }),
279− catch: (cause) =>
280− cause instanceof Error
281− ? new Error(`Failed creating tool catalog from source tools: ${cause.message}`)
282− : new Error(`Failed creating tool catalog from source tools: ${String(cause)}`),
283− });
284− const allTools = yield* Effect.try({
285− try: () => mergeToolMaps([sourceTools, createSystemToolMap({ catalog })]),
286− catch: (cause) =>
287− cause instanceof Error
288− ? new Error(`Failed creating source execution tool map: ${cause.message}`)
289− : new Error(`Failed creating source execution tool map: ${String(cause)}`),
509+ onElicitation,
290510 });
291511
292512 return {
293513 executor: makeInProcessExecutor(),
294− toolInvoker: makeToolInvokerFromTools({
295− tools: allTools,
296− onElicitation,
297− }),
514+ toolInvoker,
298515 catalog,
299516 } satisfies ExecutionEnvironment;
300517 });
packages/control-plane/src/schema/index.ts+1−0
@@ -5,6 +5,7 @@ export * from "./ids"; · 4 unmodified lines
55 export * from "./models/auth";
66 export * from "./models/workspace";
77 export * from "./models/source";
8+export * from "./models/tool-artifact";
89 export * from "./models/secret-material";
910 export * from "./models/source-auth-session";
1011 export * from "./models/policy";
packages/control-plane/src/schema/models/tool-artifact.ts+159−0
@@ -0,0 +1,159 @@
1+import { createSelectSchema } from "drizzle-orm/effect-schema";
2+import { Schema } from "effect";
3+
4+import {
5+ toolArtifactParametersTable,
6+ toolArtifactRefHintKeysTable,
7+ toolArtifactRequestBodyContentTypesTable,
8+ toolArtifactsTable,
9+} from "../../persistence/schema";
10+import { TimestampMsSchema } from "../common";
11+import { SourceIdSchema, WorkspaceIdSchema } from "../ids";
12+
13+export const ToolArtifactProviderKindSchema = Schema.Literal(
14+ "mcp",
15+ "openapi",
16+);
17+
18+export const ToolArtifactParameterLocationSchema = Schema.Literal(
19+ "path",
20+ "query",
21+ "header",
22+ "cookie",
23+);
24+
25+const toolArtifactRowSchemaOverrides = {
26+ workspaceId: WorkspaceIdSchema,
27+ sourceId: SourceIdSchema,
28+ providerKind: ToolArtifactProviderKindSchema,
29+ openApiMethod: Schema.NullOr(
30+ Schema.Literal("get", "put", "post", "delete", "patch", "head", "options", "trace"),
31+ ),
32+ createdAt: TimestampMsSchema,
33+ updatedAt: TimestampMsSchema,
34+} as const;
35+
36+const ToolArtifactStorageRowSchema = createSelectSchema(
37+ toolArtifactsTable,
38+ toolArtifactRowSchemaOverrides,
39+);
40+
41+export const StoredToolArtifactRecordSchema = Schema.Struct({
42+ workspaceId: WorkspaceIdSchema,
43+ path: Schema.String,
44+ toolId: Schema.String,
45+ sourceId: SourceIdSchema,
46+ title: Schema.NullOr(Schema.String),
47+ description: Schema.NullOr(Schema.String),
48+ searchNamespace: Schema.String,
49+ searchText: Schema.String,
50+ inputSchemaJson: Schema.NullOr(Schema.String),
51+ outputSchemaJson: Schema.NullOr(Schema.String),
52+ providerKind: ToolArtifactProviderKindSchema,
53+ mcpToolName: Schema.NullOr(Schema.String),
54+ openApiMethod: Schema.NullOr(
55+ Schema.Literal("get", "put", "post", "delete", "patch", "head", "options", "trace"),
56+ ),
57+ openApiPathTemplate: Schema.NullOr(Schema.String),
58+ openApiOperationHash: Schema.NullOr(Schema.String),
59+ openApiRequestBodyRequired: Schema.NullOr(Schema.Boolean),
60+ createdAt: TimestampMsSchema,
61+ updatedAt: TimestampMsSchema,
62+}).annotations({
63+ identifier: "StoredToolArtifactRecord",
64+});
65+
66+export const ToolArtifactStorageSchema = Schema.transform(
67+ ToolArtifactStorageRowSchema,
68+ StoredToolArtifactRecordSchema,
69+ {
70+ strict: false,
71+ decode: (row) => ({
72+ workspaceId: row.workspaceId,
73+ path: row.path,
74+ toolId: row.toolId,
75+ sourceId: row.sourceId,
76+ title: row.title,
77+ description: row.description,
78+ searchNamespace: row.searchNamespace,
79+ searchText: row.searchText,
80+ inputSchemaJson: row.inputSchemaJson,
81+ outputSchemaJson: row.outputSchemaJson,
82+ providerKind: row.providerKind,
83+ mcpToolName: row.mcpToolName,
84+ openApiMethod: row.openApiMethod,
85+ openApiPathTemplate: row.openApiPathTemplate,
86+ openApiOperationHash: row.openApiOperationHash,
87+ openApiRequestBodyRequired: row.openApiRequestBodyRequired,
88+ createdAt: row.createdAt,
89+ updatedAt: row.updatedAt,
90+ }),
91+ encode: (record) => ({
92+ workspaceId: record.workspaceId,
93+ path: record.path,
94+ toolId: record.toolId,
95+ sourceId: record.sourceId,
96+ title: record.title,
97+ description: record.description,
98+ searchNamespace: record.searchNamespace,
99+ searchText: record.searchText,
100+ inputSchemaJson: record.inputSchemaJson,
101+ outputSchemaJson: record.outputSchemaJson,
102+ providerKind: record.providerKind,
103+ mcpToolName: record.mcpToolName,
104+ openApiMethod: record.openApiMethod,
105+ openApiPathTemplate: record.openApiPathTemplate,
106+ openApiOperationHash: record.openApiOperationHash,
107+ openApiRequestBodyRequired: record.openApiRequestBodyRequired,
108+ createdAt: record.createdAt,
109+ updatedAt: record.updatedAt,
110+ }),
111+ },
112+);
113+
114+const toolArtifactParameterRowSchemaOverrides = {
115+ workspaceId: WorkspaceIdSchema,
116+ location: ToolArtifactParameterLocationSchema,
117+ position: Schema.Number,
118+} as const;
119+
120+export const StoredToolArtifactParameterRecordSchema = createSelectSchema(
121+ toolArtifactParametersTable,
122+ toolArtifactParameterRowSchemaOverrides,
123+).annotations({
124+ identifier: "StoredToolArtifactParameterRecord",
125+});
126+
127+const toolArtifactRequestBodyContentTypeRowSchemaOverrides = {
128+ workspaceId: WorkspaceIdSchema,
129+ position: Schema.Number,
130+} as const;
131+
132+export const StoredToolArtifactRequestBodyContentTypeRecordSchema = createSelectSchema(
133+ toolArtifactRequestBodyContentTypesTable,
134+ toolArtifactRequestBodyContentTypeRowSchemaOverrides,
135+).annotations({
136+ identifier: "StoredToolArtifactRequestBodyContentTypeRecord",
137+});
138+
139+const toolArtifactRefHintKeyRowSchemaOverrides = {
140+ workspaceId: WorkspaceIdSchema,
141+ position: Schema.Number,
142+} as const;
143+
144+export const StoredToolArtifactRefHintKeyRecordSchema = createSelectSchema(
145+ toolArtifactRefHintKeysTable,
146+ toolArtifactRefHintKeyRowSchemaOverrides,
147+).annotations({
148+ identifier: "StoredToolArtifactRefHintKeyRecord",
149+});
150+
151+export type ToolArtifactProviderKind = typeof ToolArtifactProviderKindSchema.Type;
152+export type ToolArtifactParameterLocation = typeof ToolArtifactParameterLocationSchema.Type;
153+export type StoredToolArtifactRecord = typeof StoredToolArtifactRecordSchema.Type;
154+export type StoredToolArtifactParameterRecord =
155+ typeof StoredToolArtifactParameterRecordSchema.Type;
156+export type StoredToolArtifactRequestBodyContentTypeRecord =
157+ typeof StoredToolArtifactRequestBodyContentTypeRecordSchema.Type;
158+export type StoredToolArtifactRefHintKeyRecord =
159+ typeof StoredToolArtifactRefHintKeyRecordSchema.Type;
packages/kitchen-sink/package.json+1−0
@@ -13,6 +13,7 @@ · 12 unmodified lines
1313 "@executor-v3/ai-sdk-adapter": "workspace:*",
1414 "@executor-v3/codemode-core": "workspace:*",
1515 "@executor-v3/codemode-openapi": "workspace:*",
16+ "@executor-v3/control-plane": "workspace:*",
1617 "@executor-v3/runtime-local-inproc": "workspace:*",
1718 "effect": "catalog:"
1819 },
packages/kitchen-sink/src/source-runtime-sketch-types.ts+0−128
@@ -1,128 +0,0 @@
1−import type { ToolInvocationContext, ToolPath } from "@executor-v3/codemode-core";
2−
3−export type SourceKey = string & { readonly __sourceKey: unique symbol };
4−
5−export type SourceAuthScheme =
6− | { kind: "none" }
7− | { kind: "apiKey"; in: "header" | "query"; name: string }
8− | { kind: "bearer" }
9− | { kind: "basic" }
10− | { kind: "oauth2" }
11− | { kind: "dynamic" };
12−
13−export type SourceDefinition =
14− | {
15− sourceKey: SourceKey;
16− displayName: string;
17− kind: "openapi";
18− enabled: boolean;
19− auth: SourceAuthScheme;
20− connection: {
21− specUrl?: string;
22− baseUrl: string;
23− };
24− }
25− | {
26− sourceKey: SourceKey;
27− displayName: string;
28− kind: "mcp";
29− enabled: boolean;
30− auth: SourceAuthScheme;
31− connection: {
32− endpoint: string;
33− transport?: "auto" | "streamable-http" | "sse";
34− };
35− }
36− | {
37− sourceKey: SourceKey;
38− displayName: string;
39− kind: "snippet";
40− enabled: boolean;
41− auth: SourceAuthScheme;
42− connection: {
43− snippetId: string;
44− entrypoint: string;
45− };
46− };
47−
48−export type ToolArtifact = {
49− path: ToolPath;
50− sourceKey: SourceKey;
51− title?: string;
52− description?: string;
53− inputSchemaJson?: string;
54− outputSchemaJson?: string;
55− search: {
56− namespace: string;
57− keywords: readonly string[];
58− };
59− invocation:
60− | {
61− provider: "openapi";
62− operationId: string;
63− method: "get" | "post" | "put" | "patch" | "delete";
64− pathTemplate: string;
65− }
66− | {
67− provider: "mcp";
68− toolName: string;
69− }
70− | {
71− provider: "snippet";
72− exportName: string;
73− };
74−};
75−
76−export type SecretRef = {
77− providerId: string;
78− handle: string;
79−};
80−
81−export type CredentialBinding = {
82− sourceKey: SourceKey;
83− authScheme: SourceAuthScheme;
84− materials: Record<string, SecretRef>;
85−};
86−
87−export type ResolvedAuthMaterial =
88− | { kind: "none" }
89− | { kind: "headers"; headers: Record<string, string> }
90− | { kind: "query"; queryParams: Record<string, string> }
91− | { kind: "composite"; values: Record<string, string> };
92−
93−export type SourceCallContext = {
94− auth: ResolvedAuthMaterial;
95−};
96−
97−export interface SecretMaterialProvider {
98− providerId: string;
99− get(input: {
100− handle: string;
101− }): Promise<string>;
102−}
103−
104−export interface SecretMaterialRegistry {
105− get(input: {
106− ref: SecretRef;
107− }): Promise<string>;
108−}
109−
110−export interface SourceRuntimeResolver {
111− resolveForCall(input: {
112− source: SourceDefinition;
113− artifact: ToolArtifact;
114− context?: ToolInvocationContext;
115− }): Promise<SourceCallContext>;
116−}
117−
118−export interface ProviderInvoker {
119− invoke(input: {
120− source: SourceDefinition;
121− artifact: ToolArtifact;
122− args: unknown;
123− runtime: SourceCallContext;
124− context?: ToolInvocationContext;
125− }): Promise<unknown>;
126−}
127−
128−export const asSourceKey = (value: string): SourceKey => value as SourceKey;
packages/kitchen-sink/src/source-runtime.test.ts+263−282
@@ -12,6 +12,13 @@ import * as Effect from "effect/Effect"; · 11 unmodified lines
1212 import * as Schema from "effect/Schema";
1313 import { Schema as EffectSchema } from "effect";
1414
15+import {
16+ type SecretRef,
17+ type Source,
18+ type SourceId,
19+ type StoredToolArtifactRecord,
20+ type WorkspaceId,
21+} from "@executor-v3/control-plane";
1522 import {
1623 createToolCatalogDiscovery,
1724 createToolCatalogFromTools,
@@ -24,100 +31,137 @@ import { · 6 unmodified lines
2431 type ToolInvocationContext,
2532 type ToolInvoker,
2633 } from "@executor-v3/codemode-core";
27−import {
28− type CredentialBinding,
29− type ProviderInvoker,
30− type SecretMaterialRegistry,
31− type SecretMaterialProvider,
32− type SourceCallContext,
33− type SourceDefinition,
34− type SourceKey,
35− type SourceRuntimeResolver,
36− type ToolArtifact,
37− asSourceKey,
38−} from "./source-runtime-sketch-types";
3934 import { createOpenApiToolsFromSpec } from "@executor-v3/codemode-openapi";
4035 import { makeInProcessExecutor } from "@executor-v3/runtime-local-inproc";
4136
4237 const asToolPath = (value: string): ToolPath => value as ToolPath;
38+const asSourceId = (value: string): SourceId => value as SourceId;
39+const asWorkspaceId = (value: string): WorkspaceId => value as WorkspaceId;
40+
41+const tokenize = (value: string): string[] =>
42+ value
43+ .trim()
44+ .toLowerCase()
45+ .split(/\s+/)
46+ .filter(Boolean);
47+
48+const normalizeSearchText = (...parts: ReadonlyArray<string | null | undefined>): string =>
49+ parts
50+ .flatMap((part) => {
51+ const normalized = part?.trim();
52+ return normalized ? [normalized] : [];
53+ })
54+ .join(" ")
55+ .toLowerCase();
56+
57+const catalogNamespaceFromPath = (path: string): string => {
58+ const [first, second] = path.split(".");
59+ return second ? `${first}.${second}` : first;
60+};
61+
62+const toDescriptor = (
63+ artifact: StoredToolArtifactRecord,
64+ includeSchemas: boolean,
65+) => ({
66+ path: asToolPath(artifact.path),
67+ sourceKey: artifact.sourceId,
68+ description: artifact.description ?? artifact.title ?? undefined,
69+ interaction: "auto" as const,
70+ inputHint: includeSchemas && artifact.inputSchemaJson ? "object" : undefined,
71+ outputHint: includeSchemas && artifact.outputSchemaJson ? "output" : undefined,
72+ inputSchemaJson: includeSchemas ? artifact.inputSchemaJson ?? undefined : undefined,
73+ outputSchemaJson: includeSchemas ? artifact.outputSchemaJson ?? undefined : undefined,
74+});
75+
76+type SourceCallContext = {
77+ auth:
78+ | { kind: "none" }
79+ | {
80+ kind: "headers";
81+ headers: Record<string, string>;
82+ };
83+};
84+
85+interface SecretMaterialProvider {
86+ providerId: string;
87+ get(input: {
88+ handle: string;
89+ }): Promise<string>;
90+}
91+
92+interface SecretMaterialRegistry {
93+ get(input: {
94+ ref: SecretRef;
95+ }): Promise<string>;
96+}
97+
98+interface ProviderInvoker {
99+ invoke(input: {
100+ source: Source;
101+ artifact: StoredToolArtifactRecord;
102+ args: unknown;
103+ runtime: SourceCallContext;
104+ context?: ToolInvocationContext;
105+ }): Promise<unknown>;
106+}
43107
44108 type WorkspaceScopedSourceStore = {
45109 registerSource(input: {
46− workspaceId: string;
47− source: SourceDefinition;
110+ workspaceId: WorkspaceId;
111+ source: Source;
48112 }): Promise<void>;
49− listSources(input: {
50− workspaceId: string;
51− limit?: number;
52− }): Promise<
53− readonly {
54− sourceKey: SourceKey;
55− displayName: string;
56− }[]
57− >;
58− getByKey(input: {
59− sourceKey: SourceKey;
60− }): Promise<SourceDefinition | null>;
113+ getById(input: {
114+ sourceId: SourceId;
115+ }): Promise<Source | null>;
61116 };
62117
63118 type WorkspaceScopedToolStore = {
64119 indexArtifacts(input: {
65− workspaceId: string;
66− sourceKey: SourceKey;
67− artifacts: readonly ToolArtifact[];
120+ workspaceId: WorkspaceId;
121+ artifacts: readonly StoredToolArtifactRecord[];
68122 }): Promise<void>;
123+ listNamespaces(input: {
124+ workspaceId: WorkspaceId;
125+ limit?: number;
126+ }): Promise<
127+ readonly {
128+ namespace: string;
129+ toolCount: number;
130+ }[]
131+ >;
69132 list(input: {
70− workspaceId: string;
71− sourceKey?: SourceKey;
133+ workspaceId: WorkspaceId;
72134 query?: string;
135+ namespace?: string;
73136 limit?: number;
74− }): Promise<readonly ToolArtifact[]>;
137+ }): Promise<readonly StoredToolArtifactRecord[]>;
75138 getByPath(input: {
76− workspaceId: string;
139+ workspaceId: WorkspaceId;
77140 path: ToolPath;
78− }): Promise<ToolArtifact | null>;
79−};
80−
81−type WorkspaceScopedBindingStore = {
82− put(input: {
83− workspaceId: string;
84− binding: CredentialBinding;
85− }): Promise<void>;
86− getBySourceKey(input: {
87− workspaceId: string;
88− sourceKey: SourceKey;
89− }): Promise<CredentialBinding | null>;
141+ }): Promise<StoredToolArtifactRecord | null>;
90142 };
91143
92144 const createInMemorySourceStore = (): WorkspaceScopedSourceStore => {
93− const byWorkspace = new Map<string, Map<string, SourceDefinition>>();
145+ const byWorkspace = new Map<string, Map<string, Source>>();
94146
95− const getWorkspaceMap = (workspaceId: string) => {
147+ const getWorkspaceMap = (workspaceId: WorkspaceId) => {
96148 const existing = byWorkspace.get(workspaceId);
97149 if (existing) {
98150 return existing;
99151 }
100152
101− const created = new Map<string, SourceDefinition>();
153+ const created = new Map<string, Source>();
102154 byWorkspace.set(workspaceId, created);
103155 return created;
104156 };
105157
106158 return {
107159 async registerSource({ workspaceId, source }) {
108− getWorkspaceMap(workspaceId).set(source.sourceKey, source);
160+ getWorkspaceMap(workspaceId).set(source.id, source);
109161 },
110− async listSources({ workspaceId, limit = 200 }) {
111− return [...getWorkspaceMap(workspaceId).values()]
112− .slice(0, limit)
113− .map((source) => ({
114− sourceKey: source.sourceKey,
115− displayName: source.displayName,
116− }));
117− },
118− async getByKey({ sourceKey }) {
162+ async getById({ sourceId }) {
119163 for (const workspace of byWorkspace.values()) {
120− const source = workspace.get(sourceKey);
164+ const source = workspace.get(sourceId);
121165 if (source) {
122166 return source;
123167 }
@@ -128,15 +172,15 @@ const createInMemorySourceStore = (): WorkspaceScopedSourceStore => { · 4 unmodified lines
128172 };
129173
130174 const createInMemoryToolStore = (): WorkspaceScopedToolStore => {
131− const byWorkspace = new Map<string, Map<string, ToolArtifact>>();
175+ const byWorkspace = new Map<string, Map<string, StoredToolArtifactRecord>>();
132176
133− const getWorkspaceMap = (workspaceId: string) => {
177+ const getWorkspaceMap = (workspaceId: WorkspaceId) => {
134178 const existing = byWorkspace.get(workspaceId);
135179 if (existing) {
136180 return existing;
137181 }
138182
139− const created = new Map<string, ToolArtifact>();
183+ const created = new Map<string, StoredToolArtifactRecord>();
140184 byWorkspace.set(workspaceId, created);
141185 return created;
142186 };
@@ -148,21 +192,28 @@ const createInMemoryToolStore = (): WorkspaceScopedToolStore => { · 5 unmodified lines
148192 workspace.set(artifact.path, artifact);
149193 }
150194 },
151− async list({ workspaceId, sourceKey, query, limit = 200 }) {
195+ async listNamespaces({ workspaceId, limit = 200 }) {
196+ const counts = new Map<string, number>();
197+ for (const artifact of getWorkspaceMap(workspaceId).values()) {
198+ counts.set(
199+ artifact.searchNamespace,
200+ (counts.get(artifact.searchNamespace) ?? 0) + 1,
201+ );
202+ }
203+
204+ return [...counts.entries()]
205+ .map(([namespace, toolCount]) => ({
206+ namespace,
207+ toolCount,
208+ }))
209+ .sort((left, right) => left.namespace.localeCompare(right.namespace))
210+ .slice(0, limit);
211+ },
212+ async list({ workspaceId, namespace, query, limit = 200 }) {
152213 return [...getWorkspaceMap(workspaceId).values()]
153− .filter((artifact) => !sourceKey || artifact.sourceKey === sourceKey)
214+ .filter((artifact) => !namespace || artifact.searchNamespace === namespace)
154215 .filter((artifact) =>
155− !query
156− || [
157− artifact.path,
158− artifact.title ?? "",
159− artifact.description ?? "",
160− artifact.search.namespace,
161− ...artifact.search.keywords,
162− ]
163− .join(" ")
164− .toLowerCase()
165− .includes(query.toLowerCase())
216+ !query || tokenize(query).every((token) => artifact.searchText.includes(token))
166217 )
167218 .slice(0, limit);
168219 },
@@ -172,32 +223,6 @@ const createInMemoryToolStore = (): WorkspaceScopedToolStore => { · 3 unmodified lines
172223 };
173224 };
174225
175−const createInMemoryBindingStore = (): WorkspaceScopedBindingStore => {
176− const byWorkspace = new Map<string, Map<string, CredentialBinding>>();
177−
178− const getWorkspaceMap = (workspaceId: string) => {
179− const existing = byWorkspace.get(workspaceId);
180− if (existing) {
181− return existing;
182− }
183−
184− const created = new Map<string, CredentialBinding>();
185− byWorkspace.set(workspaceId, created);
186− return created;
187− };
188−
189− return {
190− async put(input) {
191− const workspaceId = "workspaceId" in input ? input.workspaceId : "default";
192− getWorkspaceMap(workspaceId).set(input.binding.sourceKey, input.binding);
193− },
194− async getBySourceKey(input) {
195− const workspaceId = "workspaceId" in input ? input.workspaceId : "default";
196− return getWorkspaceMap(workspaceId).get(input.sourceKey) ?? null;
197− },
198− };
199−};
200−
201226 const createStaticSecretProvider = (
202227 providerId: string,
203228 values: Record<string, string>,
@@ -232,87 +257,46 @@ const createSecretRegistry = ( · 28 unmodified lines
232257 };
233258 };
234259
235−const createSourceRuntimeResolver = (input: {
236− bindingStore: WorkspaceScopedBindingStore;
260+const resolveSourceCallContext = (input: {
237261 secretRegistry: SecretMaterialRegistry;
238−}): SourceRuntimeResolver => ({
239− async resolveForCall({ source, context }) {
262+}) =>
263+ async (source: Source): Promise<SourceCallContext> => {
240264 if (source.auth.kind === "none") {
241265 return { auth: { kind: "none" } };
242266 }
243267
244− const workspaceId =
245− typeof context?.workspaceId === "string" ? context.workspaceId : "default";
246−
247− const binding = await input.bindingStore.getBySourceKey({
248− workspaceId,
249− sourceKey: source.sourceKey,
250− });
251− if (!binding) {
252− throw new Error(`Missing credential binding for source ${source.sourceKey}`);
253− }
254−
255− if (source.auth.kind === "bearer" || source.auth.kind === "oauth2") {
256− const ref = binding.materials.token ?? binding.materials.accessToken;
257− if (!ref) {
258− throw new Error(`Missing token material for source ${source.sourceKey}`);
259− }
260−
261− const token = await input.secretRegistry.get({ ref });
262− return {
263− auth: {
264− kind: "headers",
265− headers: {
266− Authorization: `Bearer ${token}`,
267− },
268− },
269− } satisfies SourceCallContext;
270− }
271−
272− if (source.auth.kind === "apiKey") {
273− const ref = binding.materials.apiKey;
274− if (!ref) {
275− throw new Error(`Missing apiKey material for source ${source.sourceKey}`);
276− }
277−
278− const apiKey = await input.secretRegistry.get({ ref });
279− return source.auth.in === "header"
280− ? {
281− auth: {
282− kind: "headers",
283− headers: {
284− [source.auth.name]: apiKey,
285− },
286− },
287− }
288− : {
289− auth: {
290− kind: "query",
291− queryParams: {
292− [source.auth.name]: apiKey,
293− },
294− },
295− };
296− }
268+ const tokenRef = source.auth.kind === "bearer"
269+ ? source.auth.token
270+ : source.auth.accessToken;
271+ const token = await input.secretRegistry.get({ ref: tokenRef });
297272
298273 return {
299274 auth: {
300− kind: "composite",
301− values: {
302− sourceKind: source.kind,
275+ kind: "headers",
276+ headers: {
277+ [source.auth.headerName]: `${source.auth.prefix}${token}`,
303278 },
304279 },
305280 };
306− },
307−});
281+ };
308282
309283 const createProviderInvoker = (): ProviderInvoker => ({
310284 async invoke({ source, artifact, args, runtime, context }) {
285+ const invocation = artifact.providerKind === "mcp"
286+ ? {
287+ toolName: artifact.mcpToolName ?? artifact.title ?? artifact.toolId,
288+ }
289+ : {
290+ method: artifact.openApiMethod,
291+ pathTemplate: artifact.openApiPathTemplate,
292+ operationHash: artifact.openApiOperationHash,
293+ };
294+
311295 return {
312− sourceKey: source.sourceKey,
296+ sourceId: source.id,
313297 path: artifact.path,
314− provider: artifact.invocation.provider,
315− invocation: artifact.invocation,
298+ provider: artifact.providerKind,
299+ invocation,
316300 args,
317301 auth: runtime.auth,
318302 workspaceId: context?.workspaceId ?? null,
@@ -322,20 +306,18 @@ const createProviderInvoker = (): ProviderInvoker => ({ · 3 unmodified lines
322306 });
323307
324308 const createWorkspaceToolCatalog = (input: {
325− workspaceId: string;
326− sourceStore: WorkspaceScopedSourceStore;
309+ workspaceId: WorkspaceId;
327310 toolStore: WorkspaceScopedToolStore;
328311 }): ToolCatalog => ({
329312 listNamespaces: ({ limit }) =>
330313 Effect.promise(() =>
331− input.sourceStore.listSources({
314+ input.toolStore.listNamespaces({
332315 workspaceId: input.workspaceId,
333316 limit,
334− }).then((sources) =>
335− sources.map((source) => ({
336− namespace: source.sourceKey as string,
337− displayName: source.displayName,
338− toolCount: undefined,
317+ }).then((namespaces) =>
318+ namespaces.map((namespace) => ({
319+ namespace: namespace.namespace,
320+ toolCount: namespace.toolCount,
339321 }))
340322 )
341323 ),
@@ -343,69 +325,37 @@ const createWorkspaceToolCatalog = (input: { · 1 unmodified line
343325 Effect.promise(() =>
344326 input.toolStore.list({
345327 workspaceId: input.workspaceId,
346− ...(namespace !== undefined ? { sourceKey: namespace as SourceKey } : {}),
328+ ...(namespace !== undefined ? { namespace } : {}),
347329 ...(query !== undefined ? { query } : {}),
348330 limit,
349− }).then((artifacts) =>
350− artifacts.map((artifact) => ({
351− path: artifact.path as any,
352− sourceKey: artifact.sourceKey,
353− description: artifact.description ?? artifact.title,
354− interaction: "auto" as const,
355− inputHint: includeSchemas && artifact.inputSchemaJson ? "object" : undefined,
356− outputHint: includeSchemas && artifact.outputSchemaJson ? "output" : undefined,
357− inputSchemaJson: includeSchemas ? artifact.inputSchemaJson : undefined,
358− outputSchemaJson: includeSchemas ? artifact.outputSchemaJson : undefined,
359− }))
360− )
331+ }).then((artifacts) => artifacts.map((artifact) => toDescriptor(artifact, includeSchemas)))
361332 ),
362333 getToolByPath: ({ path, includeSchemas }) =>
363334 Effect.promise(() =>
364335 input.toolStore.getByPath({
365336 workspaceId: input.workspaceId,
366− path: path as ToolPath,
367− }).then((artifact) =>
368− artifact
369− ? {
370− path: artifact.path as any,
371− sourceKey: artifact.sourceKey,
372− description: artifact.description ?? artifact.title,
373− interaction: "auto" as const,
374− inputHint: includeSchemas && artifact.inputSchemaJson ? "object" : undefined,
375− outputHint: includeSchemas && artifact.outputSchemaJson ? "output" : undefined,
376− inputSchemaJson: includeSchemas ? artifact.inputSchemaJson : undefined,
377− outputSchemaJson: includeSchemas ? artifact.outputSchemaJson : undefined,
378− }
379− : null
380− )
337+ path,
338+ }).then((artifact) => (artifact ? toDescriptor(artifact, includeSchemas) : null))
381339 ),
382340 searchTools: ({ query, namespace, limit }) =>
383341 Effect.promise(() =>
384342 input.toolStore.list({
385343 workspaceId: input.workspaceId,
386− ...(namespace !== undefined ? { sourceKey: namespace as SourceKey } : {}),
387− ...(query !== undefined ? { query } : {}),
344+ ...(namespace !== undefined ? { namespace } : {}),
345+ query,
388346 limit: 500,
389347 }).then((artifacts) => {
390− const queryTokens = query.toLowerCase().split(/\s+/).filter(Boolean);
348+ const queryTokens = tokenize(query);
391349
392350 return artifacts
393351 .map((artifact) => {
394− const haystack = [
395− artifact.path,
396− artifact.title ?? "",
397− artifact.description ?? "",
398− artifact.search.namespace,
399− ...artifact.search.keywords,
400− ].join(" ").toLowerCase();
401−
402352 const score = queryTokens.reduce(
403− (total, token) => total + (haystack.includes(token) ? 1 : 0),
353+ (total, token) => total + (artifact.searchText.includes(token) ? 1 : 0),
404354 0,
405355 );
406356
407357 return {
408− path: artifact.path as any,
358+ path: asToolPath(artifact.path),
409359 score,
410360 };
411361 })
@@ -417,16 +367,15 @@ const createWorkspaceToolCatalog = (input: { · 5 unmodified lines
417367 });
418368
419369 const createWorkspaceToolInvoker = (input: {
420− workspaceId: string;
370+ workspaceId: WorkspaceId;
421371 sourceStore: WorkspaceScopedSourceStore;
422372 toolStore: WorkspaceScopedToolStore;
423− runtimeResolver: SourceRuntimeResolver;
373+ resolveSourceCallContext: (source: Source) => Promise<SourceCallContext>;
424374 providerInvoker: ProviderInvoker;
425375 }): ToolInvoker => ({
426376 invoke: (() => {
427377 const catalog = createWorkspaceToolCatalog({
428378 workspaceId: input.workspaceId,
429− sourceStore: input.sourceStore,
430379 toolStore: input.toolStore,
431380 });
432381 const systemTools = createSystemToolMap({
@@ -457,18 +406,14 @@ const createWorkspaceToolInvoker = (input: { · 24 unmodified lines
457406 throw new Error(`Unknown tool path: ${path}`);
458407 }
459408
460− const source = await input.sourceStore.getByKey({
461− sourceKey: artifact.sourceKey,
409+ const source = await input.sourceStore.getById({
410+ sourceId: artifact.sourceId,
462411 });
463412 if (!source) {
464413 throw new Error(`Unknown source for tool path: ${path}`);
465414 }
466415
467− const runtime = await input.runtimeResolver.resolveForCall({
468− source,
469− artifact,
470− context: mergedContext,
471− });
416+ const runtime = await input.resolveSourceCallContext(source);
472417
473418 return input.providerInvoker.invoke({
474419 source,
@@ -484,10 +429,9 @@ const createWorkspaceToolInvoker = (input: { · 9 unmodified lines
484429 });
485430
486431 const toolInvokerFromWorkspace = (input: {
487− workspaceId: string;
432+ workspaceId: WorkspaceId;
488433 sourceStore: WorkspaceScopedSourceStore;
489434 toolStore: WorkspaceScopedToolStore;
490− bindingStore: WorkspaceScopedBindingStore;
491435 secretRegistry: SecretMaterialRegistry;
492436 providerInvoker?: ProviderInvoker;
493437 }): ToolInvoker =>
@@ -495,46 +439,100 @@ const toolInvokerFromWorkspace = (input: { · 1 unmodified line
495439 workspaceId: input.workspaceId,
496440 sourceStore: input.sourceStore,
497441 toolStore: input.toolStore,
498− runtimeResolver: createSourceRuntimeResolver({
499− bindingStore: input.bindingStore,
442+ resolveSourceCallContext: resolveSourceCallContext({
500443 secretRegistry: input.secretRegistry,
501444 }),
502445 providerInvoker: input.providerInvoker ?? createProviderInvoker(),
503446 });
504447
505−const bearerBinding = (input: {
506− sourceKey: SourceKey;
448+const bearerSourceAuth = (input: {
507449 providerId: string;
508450 handle: string;
509−}): CredentialBinding => ({
510− sourceKey: input.sourceKey,
511− authScheme: { kind: "bearer" },
512− materials: {
513− token: {
514− providerId: input.providerId,
515− handle: input.handle,
516− },
451+}): Source["auth"] => ({
452+ kind: "bearer",
453+ headerName: "Authorization",
454+ prefix: "Bearer ",
455+ token: {
456+ providerId: input.providerId,
457+ handle: input.handle,
517458 },
518459 });
519460
520461 const openApiSource = (input: {
521− sourceKey: SourceKey;
462+ workspaceId: WorkspaceId;
463+ sourceId: SourceId;
522464 displayName: string;
523465 baseUrl: string;
524466 specUrl?: string;
525− auth: SourceDefinition["auth"];
526−}): SourceDefinition => ({
527− sourceKey: input.sourceKey,
528− displayName: input.displayName,
467+ namespace?: string;
468+ auth: Source["auth"];
469+}): Source => ({
470+ id: input.sourceId,
471+ workspaceId: input.workspaceId,
472+ name: input.displayName,
529473 kind: "openapi",
474+ endpoint: input.baseUrl,
475+ status: "connected",
530476 enabled: true,
477+ namespace: input.namespace ?? null,
478+ transport: null,
479+ queryParams: null,
480+ headers: null,
481+ specUrl: input.specUrl ?? null,
482+ defaultHeaders: null,
531483 auth: input.auth,
532− connection: {
533− specUrl: input.specUrl,
534− baseUrl: input.baseUrl,
535− },
484+ sourceHash: null,
485+ lastError: null,
486+ createdAt: 0,
487+ updatedAt: 0,
536488 });
537489
490+const openApiArtifact = (input: {
491+ workspaceId: WorkspaceId;
492+ sourceId: SourceId;
493+ path: ToolPath;
494+ toolId: string;
495+ title: string;
496+ description?: string;
497+ method: NonNullable<StoredToolArtifactRecord["openApiMethod"]>;
498+ pathTemplate: string;
499+ operationHash?: string;
500+ inputSchemaJson?: string | null;
501+ outputSchemaJson?: string | null;
502+}): StoredToolArtifactRecord => {
503+ const path = input.path as string;
504+ const description =
505+ input.description ?? `${input.method.toUpperCase()} ${input.pathTemplate}`;
506+
507+ return {
508+ workspaceId: input.workspaceId,
509+ path,
510+ toolId: input.toolId,
511+ sourceId: input.sourceId,
512+ title: input.title,
513+ description,
514+ searchNamespace: catalogNamespaceFromPath(path),
515+ searchText: normalizeSearchText(
516+ path,
517+ catalogNamespaceFromPath(path),
518+ input.title,
519+ description,
520+ input.method.toUpperCase(),
521+ input.pathTemplate,
522+ ),
523+ inputSchemaJson: input.inputSchemaJson ?? null,
524+ outputSchemaJson: input.outputSchemaJson ?? null,
525+ providerKind: "openapi",
526+ mcpToolName: null,
527+ openApiMethod: input.method,
528+ openApiPathTemplate: input.pathTemplate,
529+ openApiOperationHash: input.operationHash ?? input.toolId,
530+ openApiRequestBodyRequired: null,
531+ createdAt: 0,
532+ updatedAt: 0,
533+ };
534+};
535+
538536 const numberPairInputSchema = Schema.standardSchemaV1(
539537 Schema.Struct({
540538 a: Schema.Number,
@@ -545,10 +543,9 @@ const numberPairInputSchema = Schema.standardSchemaV1( · 4 unmodified lines
545543 const createDiscoveryBackedToolMap = (input: {
546544 tools: ToolMap;
547545 namespace: string;
548− displayName?: string;
549546 sourceKey?: string;
550547 }) => {
551− const sourceKey = asSourceKey(input.sourceKey ?? "in_memory.tools");
548+ const sourceKey = input.sourceKey ?? "in_memory.tools";
552549 const catalog = createToolCatalogFromTools({
553550 tools: input.tools,
554551 defaultNamespace: input.namespace,
@@ -657,10 +654,9 @@ const makeOpenApiTestServer = Effect.acquireRelease( · 102 unmodified lines
657654 describe("source runtime", () => {
658655 it.effect("searches serialized workspace tools and calls one", () =>
659656 Effect.gen(function* () {
660− const workspaceId = "workspace_123";
657+ const workspaceId = asWorkspaceId("workspace_123");
661658 const sourceStore = createInMemorySourceStore();
662659 const toolStore = createInMemoryToolStore();
663− const bindingStore = createInMemoryBindingStore();
664660 const secretRegistry = createSecretRegistry([
665661 createStaticSecretProvider("postgres", {
666662 "github-db-token": "ghp_from_db",
@@ -668,11 +664,16 @@ describe("source runtime", () => { · 1 unmodified line
668664 ]);
669665
670666 const githubSource = openApiSource({
671− sourceKey: asSourceKey("github"),
667+ workspaceId,
668+ sourceId: asSourceId("github"),
672669 displayName: "GitHub API",
673670 baseUrl: "https://api.github.com",
674671 specUrl: "https://api.github.com/openapi.json",
675− auth: { kind: "bearer" },
672+ namespace: "github",
673+ auth: bearerSourceAuth({
674+ providerId: "postgres",
675+ handle: "github-db-token",
676+ }),
676677 });
677678
678679 yield* Effect.promise(() =>
@@ -681,37 +682,20 @@ describe("source runtime", () => { · 2 unmodified lines
681682 source: githubSource,
682683 })
683684 );
684− yield* Effect.promise(() =>
685− bindingStore.put({
686− workspaceId,
687− binding: bearerBinding({
688− sourceKey: githubSource.sourceKey,
689− providerId: "postgres",
690− handle: "github-db-token",
691− }),
692− })
693− );
694685 yield* Effect.promise(() =>
695686 toolStore.indexArtifacts({
696687 workspaceId,
697− sourceKey: githubSource.sourceKey,
698688 artifacts: [
699− {
689+ openApiArtifact({
690+ workspaceId,
691+ sourceId: githubSource.id,
700692 path: asToolPath("github.issues.list"),
701− sourceKey: githubSource.sourceKey,
693+ toolId: "issues.list",
702694 title: "List issues",
703695 description: "Serialized artifact loaded from a database row",
704− invocation: {
705− provider: "openapi",
706− operationId: "issues.list",
707− method: "get",
708− pathTemplate: "/repos/{owner}/{repo}/issues",
709− },
710− search: {
711− namespace: "github.issues",
712− keywords: ["github", "issues", "list", "serialized", "db"],
713− },
714− },
696+ method: "get",
697+ pathTemplate: "/repos/{owner}/{repo}/issues",
698+ }),
715699 ],
716700 })
717701 );
@@ -720,12 +704,10 @@ describe("source runtime", () => { · 2 unmodified lines
720704 workspaceId,
721705 sourceStore,
722706 toolStore,
723− bindingStore,
724707 secretRegistry,
725708 });
726709 const workspaceCatalog = createWorkspaceToolCatalog({
727710 workspaceId,
728− sourceStore,
729711 toolStore,
730712 });
731713 const executeDescription = yield* createToolCatalogDiscovery({
@@ -770,7 +752,7 @@ describe("source runtime", () => { · 38 unmodified lines
770752 [
771753 "Execute TypeScript in sandbox; call tools via discovery workflow.",
772754 "Available namespaces:",
773− "- GitHub API",
755+ "- github.issues",
774756 "Workflow:",
775757 '1) const matches = await tools.discover({ query: "<intent>", limit: 12 });',
776758 "2) const details = await tools.describe.tool({ path, includeSchemas: true });",
@@ -798,7 +780,6 @@ describe("source runtime", () => { · 21 unmodified lines
798780 const discoveryBacked = createDiscoveryBackedToolMap({
799781 tools: extracted.tools,
800782 namespace: "github",
801− displayName: "GitHub API",
802783 sourceKey: "github.openapi",
803784 });
804785