@decocms/runtime 1.2.11 → 1.2.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +9 -4
- package/src/bindings.ts +168 -12
- package/src/decopilot.ts +196 -0
- package/src/index.ts +3 -1
- package/src/tools.ts +151 -13
- package/src/workflows.ts +770 -0
package/src/tools.ts
CHANGED
|
@@ -13,14 +13,21 @@ import type {
|
|
|
13
13
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
14
14
|
import { z } from "zod";
|
|
15
15
|
import type { ZodRawShape, ZodSchema, ZodTypeAny } from "zod";
|
|
16
|
-
import { BindingRegistry } from "./bindings.ts";
|
|
16
|
+
import { BindingRegistry, injectBindingSchemas } from "./bindings.ts";
|
|
17
17
|
import { Event, type EventHandlers } from "./events.ts";
|
|
18
18
|
import type { DefaultEnv } from "./index.ts";
|
|
19
19
|
import { State } from "./state.ts";
|
|
20
|
+
import {
|
|
21
|
+
type WorkflowDefinition,
|
|
22
|
+
Workflow,
|
|
23
|
+
WORKFLOW_SCOPES,
|
|
24
|
+
workflowToolId,
|
|
25
|
+
} from "./workflows.ts";
|
|
20
26
|
|
|
21
27
|
// Re-export EventHandlers type and SELF constant for external use
|
|
22
28
|
export { SELF } from "./events.ts";
|
|
23
29
|
export type { EventHandlers } from "./events.ts";
|
|
30
|
+
export type { WorkflowDefinition } from "./workflows.ts";
|
|
24
31
|
|
|
25
32
|
export const createRuntimeContext = (prev?: AppContext) => {
|
|
26
33
|
const store = State.getStore();
|
|
@@ -42,13 +49,17 @@ export interface ToolExecutionContext<
|
|
|
42
49
|
|
|
43
50
|
/**
|
|
44
51
|
* Tool interface with generic schema types for type-safe tool creation.
|
|
52
|
+
*
|
|
53
|
+
* TId preserves the literal string type of `id` so consumers (e.g. the
|
|
54
|
+
* workflow builder) can derive union types of tool names without codegen.
|
|
45
55
|
*/
|
|
46
56
|
export interface Tool<
|
|
47
57
|
TSchemaIn extends ZodTypeAny = ZodTypeAny,
|
|
48
58
|
TSchemaOut extends ZodTypeAny | undefined = undefined,
|
|
59
|
+
TId extends string = string,
|
|
49
60
|
> {
|
|
50
61
|
_meta?: Record<string, unknown>;
|
|
51
|
-
id:
|
|
62
|
+
id: TId;
|
|
52
63
|
description?: string;
|
|
53
64
|
annotations?: ToolAnnotations;
|
|
54
65
|
inputSchema: TSchemaIn;
|
|
@@ -246,7 +257,8 @@ export function createStreamableTool<TSchemaIn extends ZodSchema = ZodSchema>(
|
|
|
246
257
|
export function createTool<
|
|
247
258
|
TSchemaIn extends ZodSchema = ZodSchema,
|
|
248
259
|
TSchemaOut extends ZodSchema | undefined = undefined,
|
|
249
|
-
|
|
260
|
+
TId extends string = string,
|
|
261
|
+
>(opts: Tool<TSchemaIn, TSchemaOut, TId>): Tool<TSchemaIn, TSchemaOut, TId> {
|
|
250
262
|
return {
|
|
251
263
|
...opts,
|
|
252
264
|
execute: (input: ToolExecutionContext<TSchemaIn>) => {
|
|
@@ -517,6 +529,9 @@ export interface CreateMCPServerOptions<
|
|
|
517
529
|
| Promise<CreatedResource[]>
|
|
518
530
|
>
|
|
519
531
|
| ((env: TEnv) => CreatedResource[] | Promise<CreatedResource[]>);
|
|
532
|
+
workflows?:
|
|
533
|
+
| WorkflowDefinition[]
|
|
534
|
+
| ((env: TEnv) => WorkflowDefinition[] | Promise<WorkflowDefinition[]>);
|
|
520
535
|
}
|
|
521
536
|
|
|
522
537
|
export type Fetch<TEnv = unknown> = (
|
|
@@ -541,16 +556,34 @@ const getEventBus = (
|
|
|
541
556
|
: env?.MESH_REQUEST_CONTEXT?.state?.[prop];
|
|
542
557
|
};
|
|
543
558
|
|
|
559
|
+
// TEnv is erased here because toolsFor() only reads events/workflows/configuration
|
|
560
|
+
// and doesn't need the full env type. Replacing `any` with a proper generic
|
|
561
|
+
// would require threading TEnv through toolsFor, which is a larger refactor.
|
|
562
|
+
type ResolvedMCPServerOptions<TSchema extends ZodTypeAny = never> = Omit<
|
|
563
|
+
CreateMCPServerOptions<any, TSchema>, // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
564
|
+
"workflows"
|
|
565
|
+
> & { workflows?: WorkflowDefinition[] };
|
|
566
|
+
|
|
567
|
+
const getMeshCtx = (input: { runtimeContext: AppContext }) => {
|
|
568
|
+
const ctx = input.runtimeContext.env.MESH_REQUEST_CONTEXT;
|
|
569
|
+
return {
|
|
570
|
+
connectionId: ctx?.connectionId,
|
|
571
|
+
meshUrl: ctx?.meshUrl,
|
|
572
|
+
token: ctx?.token,
|
|
573
|
+
};
|
|
574
|
+
};
|
|
575
|
+
|
|
544
576
|
const toolsFor = <TSchema extends ZodTypeAny = never>({
|
|
545
577
|
events,
|
|
578
|
+
workflows,
|
|
546
579
|
configuration: { state: schema, scopes, onChange } = {},
|
|
547
|
-
}:
|
|
580
|
+
}: ResolvedMCPServerOptions<TSchema> = {}): CreatedTool[] => {
|
|
548
581
|
const jsonSchema = schema
|
|
549
|
-
? z.toJSONSchema(schema)
|
|
582
|
+
? injectBindingSchemas(z.toJSONSchema(schema) as Record<string, unknown>)
|
|
550
583
|
: { type: "object", properties: {} };
|
|
551
584
|
const busProp = String(events?.bus ?? "EVENT_BUS");
|
|
552
585
|
return [
|
|
553
|
-
...(onChange || events
|
|
586
|
+
...(onChange || events || workflows?.length
|
|
554
587
|
? [
|
|
555
588
|
createTool({
|
|
556
589
|
id: "ON_MCP_CONFIGURATION",
|
|
@@ -573,9 +606,7 @@ const toolsFor = <TSchema extends ZodTypeAny = never>({
|
|
|
573
606
|
});
|
|
574
607
|
const bus = getEventBus(busProp, input.runtimeContext.env);
|
|
575
608
|
if (events && state && bus) {
|
|
576
|
-
|
|
577
|
-
const connectionId =
|
|
578
|
-
input.runtimeContext.env.MESH_REQUEST_CONTEXT?.connectionId;
|
|
609
|
+
const { connectionId } = getMeshCtx(input);
|
|
579
610
|
// Sync subscriptions - always call to handle deletions too
|
|
580
611
|
const subscriptions = Event.subscriptions(
|
|
581
612
|
events?.handlers ?? ({} as Record<string, never>),
|
|
@@ -607,6 +638,23 @@ const toolsFor = <TSchema extends ZodTypeAny = never>({
|
|
|
607
638
|
);
|
|
608
639
|
}
|
|
609
640
|
}
|
|
641
|
+
|
|
642
|
+
if (workflows?.length) {
|
|
643
|
+
const {
|
|
644
|
+
connectionId: wfConnectionId,
|
|
645
|
+
meshUrl,
|
|
646
|
+
token,
|
|
647
|
+
} = getMeshCtx(input);
|
|
648
|
+
if (wfConnectionId && meshUrl) {
|
|
649
|
+
await Workflow.sync(
|
|
650
|
+
workflows,
|
|
651
|
+
meshUrl,
|
|
652
|
+
wfConnectionId,
|
|
653
|
+
token,
|
|
654
|
+
);
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
|
|
610
658
|
return Promise.resolve({});
|
|
611
659
|
},
|
|
612
660
|
}),
|
|
@@ -623,10 +671,8 @@ const toolsFor = <TSchema extends ZodTypeAny = never>({
|
|
|
623
671
|
outputSchema: OnEventsOutputSchema,
|
|
624
672
|
execute: async (input) => {
|
|
625
673
|
const env = input.runtimeContext.env;
|
|
626
|
-
// Get state from MESH_REQUEST_CONTEXT - this has the binding values
|
|
627
674
|
const state = env.MESH_REQUEST_CONTEXT?.state as z.infer<TSchema>;
|
|
628
|
-
|
|
629
|
-
const connectionId = env.MESH_REQUEST_CONTEXT?.connectionId;
|
|
675
|
+
const { connectionId } = getMeshCtx(input);
|
|
630
676
|
return Event.execute(
|
|
631
677
|
events.handlers!,
|
|
632
678
|
input.context.events,
|
|
@@ -652,10 +698,90 @@ const toolsFor = <TSchema extends ZodTypeAny = never>({
|
|
|
652
698
|
scopes: [
|
|
653
699
|
...((scopes as string[]) ?? []),
|
|
654
700
|
...(events ? [`${busProp}::EVENT_SYNC_SUBSCRIPTIONS`] : []),
|
|
701
|
+
...(workflows?.length ? [...WORKFLOW_SCOPES] : []),
|
|
655
702
|
],
|
|
656
703
|
});
|
|
657
704
|
},
|
|
658
705
|
}),
|
|
706
|
+
|
|
707
|
+
// Auto-generated trigger tool for each declared workflow.
|
|
708
|
+
// Calls COLLECTION_WORKFLOW_EXECUTION_CREATE on the mesh and returns the
|
|
709
|
+
// execution ID immediately (fire-and-forget; poll with
|
|
710
|
+
// COLLECTION_WORKFLOW_EXECUTION_GET to track progress).
|
|
711
|
+
...(workflows?.length
|
|
712
|
+
? workflows.map((wf) => {
|
|
713
|
+
const id = wf.toolId ?? workflowToolId(wf.title);
|
|
714
|
+
return createTool({
|
|
715
|
+
id,
|
|
716
|
+
description: [
|
|
717
|
+
wf.description
|
|
718
|
+
? `Run workflow: ${wf.description}`
|
|
719
|
+
: `Start the "${wf.title}" workflow.`,
|
|
720
|
+
"Returns an execution_id immediately. Use COLLECTION_WORKFLOW_EXECUTION_GET to track progress.",
|
|
721
|
+
].join(" "),
|
|
722
|
+
inputSchema: z.object({
|
|
723
|
+
input: z
|
|
724
|
+
.record(z.string(), z.unknown())
|
|
725
|
+
.optional()
|
|
726
|
+
.describe(
|
|
727
|
+
"Input data for the workflow. Steps reference these values via @input.field.",
|
|
728
|
+
),
|
|
729
|
+
virtual_mcp_id: z
|
|
730
|
+
.string()
|
|
731
|
+
.optional()
|
|
732
|
+
.describe(
|
|
733
|
+
wf.virtual_mcp_id
|
|
734
|
+
? `Virtual MCP ID to use for execution (defaults to "${wf.virtual_mcp_id}").`
|
|
735
|
+
: "Virtual MCP ID that will execute the workflow steps.",
|
|
736
|
+
),
|
|
737
|
+
start_at_epoch_ms: z
|
|
738
|
+
.number()
|
|
739
|
+
.int()
|
|
740
|
+
.min(0)
|
|
741
|
+
.optional()
|
|
742
|
+
.describe(
|
|
743
|
+
"Unix timestamp (ms) for scheduled execution. Omit to start immediately.",
|
|
744
|
+
),
|
|
745
|
+
}),
|
|
746
|
+
outputSchema: z.object({
|
|
747
|
+
execution_id: z
|
|
748
|
+
.string()
|
|
749
|
+
.describe("ID of the created workflow execution."),
|
|
750
|
+
}),
|
|
751
|
+
execute: async (input) => {
|
|
752
|
+
const { connectionId, meshUrl, token } = getMeshCtx(input);
|
|
753
|
+
|
|
754
|
+
if (!connectionId || !meshUrl) {
|
|
755
|
+
throw new Error(
|
|
756
|
+
`[${id}] Missing MESH_REQUEST_CONTEXT (connectionId or meshUrl).`,
|
|
757
|
+
);
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
const ctx = input.context as {
|
|
761
|
+
input?: Record<string, unknown>;
|
|
762
|
+
virtual_mcp_id?: string;
|
|
763
|
+
start_at_epoch_ms?: number;
|
|
764
|
+
};
|
|
765
|
+
|
|
766
|
+
const virtualMcpId = ctx.virtual_mcp_id ?? wf.virtual_mcp_id;
|
|
767
|
+
|
|
768
|
+
const collectionId = Workflow.workflowId(connectionId, wf.title);
|
|
769
|
+
const executionId = await Workflow.createExecution(
|
|
770
|
+
meshUrl,
|
|
771
|
+
token,
|
|
772
|
+
{
|
|
773
|
+
workflow_collection_id: collectionId,
|
|
774
|
+
virtual_mcp_id: virtualMcpId,
|
|
775
|
+
input: ctx.input,
|
|
776
|
+
start_at_epoch_ms: ctx.start_at_epoch_ms,
|
|
777
|
+
},
|
|
778
|
+
);
|
|
779
|
+
|
|
780
|
+
return { execution_id: executionId };
|
|
781
|
+
},
|
|
782
|
+
});
|
|
783
|
+
})
|
|
784
|
+
: []),
|
|
659
785
|
];
|
|
660
786
|
};
|
|
661
787
|
|
|
@@ -710,7 +836,14 @@ export const createMCPServer = <
|
|
|
710
836
|
};
|
|
711
837
|
const tools = await toolsFn(bindings);
|
|
712
838
|
|
|
713
|
-
|
|
839
|
+
const resolvedWorkflows =
|
|
840
|
+
typeof options.workflows === "function"
|
|
841
|
+
? await options.workflows(bindings)
|
|
842
|
+
: options.workflows;
|
|
843
|
+
|
|
844
|
+
tools.push(
|
|
845
|
+
...toolsFor<TSchema>({ ...options, workflows: resolvedWorkflows }),
|
|
846
|
+
);
|
|
714
847
|
|
|
715
848
|
for (const tool of tools) {
|
|
716
849
|
server.registerTool(
|
|
@@ -858,6 +991,9 @@ export const createMCPServer = <
|
|
|
858
991
|
}
|
|
859
992
|
|
|
860
993
|
// MCP SDK expects either text or blob content, not both
|
|
994
|
+
const meta =
|
|
995
|
+
(result as { _meta?: Record<string, unknown> | null })._meta ??
|
|
996
|
+
undefined;
|
|
861
997
|
if (result.text !== undefined) {
|
|
862
998
|
return {
|
|
863
999
|
contents: [
|
|
@@ -865,6 +1001,7 @@ export const createMCPServer = <
|
|
|
865
1001
|
uri: result.uri,
|
|
866
1002
|
mimeType: result.mimeType,
|
|
867
1003
|
text: result.text,
|
|
1004
|
+
...(meta !== undefined ? { _meta: meta } : {}),
|
|
868
1005
|
},
|
|
869
1006
|
],
|
|
870
1007
|
};
|
|
@@ -875,6 +1012,7 @@ export const createMCPServer = <
|
|
|
875
1012
|
uri: result.uri,
|
|
876
1013
|
mimeType: result.mimeType,
|
|
877
1014
|
blob: result.blob,
|
|
1015
|
+
...(meta !== undefined ? { _meta: meta } : {}),
|
|
878
1016
|
},
|
|
879
1017
|
],
|
|
880
1018
|
};
|