@decocms/bindings 1.0.8 → 1.0.9
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 +7 -3
- package/src/core/binder.ts +43 -0
- package/src/core/plugin-context-provider.tsx +94 -0
- package/src/core/plugin-context.ts +183 -0
- package/src/core/plugin-router.tsx +226 -0
- package/src/core/plugins.ts +81 -0
- package/src/index.ts +38 -0
- package/src/well-known/assistant.ts +7 -5
- package/src/well-known/event-bus.ts +4 -4
- package/src/well-known/object-storage.ts +257 -0
- package/src/well-known/workflow.ts +4 -2
package/package.json
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decocms/bindings",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.9",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"check": "tsc --noEmit",
|
|
7
7
|
"test": "bun test"
|
|
8
8
|
},
|
|
9
9
|
"dependencies": {
|
|
10
|
-
"@modelcontextprotocol/sdk": "1.25.
|
|
10
|
+
"@modelcontextprotocol/sdk": "1.25.2",
|
|
11
|
+
"@tanstack/react-router": "1.139.7",
|
|
12
|
+
"react": "^19.2.0",
|
|
11
13
|
"zod": "^4.0.0",
|
|
12
14
|
"zod-from-json-schema": "^0.5.2"
|
|
13
15
|
},
|
|
@@ -20,7 +22,9 @@
|
|
|
20
22
|
"./mcp": "./src/well-known/mcp.ts",
|
|
21
23
|
"./assistant": "./src/well-known/assistant.ts",
|
|
22
24
|
"./prompt": "./src/well-known/prompt.ts",
|
|
23
|
-
"./workflow": "./src/well-known/workflow.ts"
|
|
25
|
+
"./workflow": "./src/well-known/workflow.ts",
|
|
26
|
+
"./plugins": "./src/core/plugins.ts",
|
|
27
|
+
"./plugin-router": "./src/core/plugin-router.tsx"
|
|
24
28
|
},
|
|
25
29
|
"engines": {
|
|
26
30
|
"node": ">=24.0.0"
|
package/src/core/binder.ts
CHANGED
|
@@ -177,3 +177,46 @@ export function createBindingChecker<TDefinition extends readonly ToolBinder[]>(
|
|
|
177
177
|
},
|
|
178
178
|
};
|
|
179
179
|
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Generic connection type for binding checking.
|
|
183
|
+
* Any connection object with a tools array can be checked.
|
|
184
|
+
*/
|
|
185
|
+
export interface ConnectionForBinding {
|
|
186
|
+
tools?: Array<{
|
|
187
|
+
name: string;
|
|
188
|
+
inputSchema?: Record<string, unknown>;
|
|
189
|
+
outputSchema?: Record<string, unknown>;
|
|
190
|
+
}> | null;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Checks if a connection implements a binding by validating its tools.
|
|
195
|
+
* Used by plugin layouts to filter connections by binding.
|
|
196
|
+
*/
|
|
197
|
+
export function connectionImplementsBinding(
|
|
198
|
+
connection: ConnectionForBinding,
|
|
199
|
+
binding: Binder,
|
|
200
|
+
): boolean {
|
|
201
|
+
const tools = connection.tools;
|
|
202
|
+
|
|
203
|
+
if (!tools || tools.length === 0) {
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Prepare tools for checker (only input schema, skip output for detection)
|
|
208
|
+
const toolsForChecker = tools.map((t) => ({
|
|
209
|
+
name: t.name,
|
|
210
|
+
inputSchema: t.inputSchema,
|
|
211
|
+
}));
|
|
212
|
+
|
|
213
|
+
// Create binding checker without output schemas
|
|
214
|
+
const bindingForChecker = binding.map((b) => ({
|
|
215
|
+
name: b.name,
|
|
216
|
+
inputSchema: b.inputSchema,
|
|
217
|
+
opt: b.opt,
|
|
218
|
+
}));
|
|
219
|
+
|
|
220
|
+
const checker = createBindingChecker(bindingForChecker);
|
|
221
|
+
return checker.isImplementedBy(toolsForChecker);
|
|
222
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin Context Provider
|
|
3
|
+
*
|
|
4
|
+
* React context provider and hook for accessing plugin context.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { createContext, useContext, type ReactNode } from "react";
|
|
8
|
+
import type { Binder } from "./binder";
|
|
9
|
+
import type { PluginContext, PluginContextPartial } from "./plugin-context";
|
|
10
|
+
|
|
11
|
+
// Internal context stores the partial version (nullable connection fields)
|
|
12
|
+
// The hook return type depends on the options passed
|
|
13
|
+
const PluginContextInternal = createContext<PluginContextPartial | null>(null);
|
|
14
|
+
|
|
15
|
+
export interface PluginContextProviderProps<TBinding extends Binder> {
|
|
16
|
+
value: PluginContext<TBinding> | PluginContextPartial<TBinding>;
|
|
17
|
+
children: ReactNode;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Provider component for plugin context.
|
|
22
|
+
* Used by the mesh app layout to provide context to plugin routes.
|
|
23
|
+
*/
|
|
24
|
+
export function PluginContextProvider<TBinding extends Binder>({
|
|
25
|
+
value,
|
|
26
|
+
children,
|
|
27
|
+
}: PluginContextProviderProps<TBinding>) {
|
|
28
|
+
return (
|
|
29
|
+
<PluginContextInternal.Provider value={value as PluginContextPartial}>
|
|
30
|
+
{children}
|
|
31
|
+
</PluginContextInternal.Provider>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Options for usePluginContext hook.
|
|
37
|
+
*/
|
|
38
|
+
export interface UsePluginContextOptions {
|
|
39
|
+
/**
|
|
40
|
+
* Set to true when calling from an empty state component.
|
|
41
|
+
* This returns nullable connection fields since no valid connection exists.
|
|
42
|
+
*/
|
|
43
|
+
partial?: boolean;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Hook to access the plugin context with typed tool caller.
|
|
48
|
+
*
|
|
49
|
+
* @template TBinding - The binding type for typed tool calls
|
|
50
|
+
* @param options - Optional settings
|
|
51
|
+
* @param options.partial - Set to true in empty state components where connection may not exist
|
|
52
|
+
* @throws Error if used outside of PluginContextProvider
|
|
53
|
+
* @throws Error if connection is null but partial option is not set
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* ```tsx
|
|
57
|
+
* // In route component (connection guaranteed by layout)
|
|
58
|
+
* const { toolCaller, connection } = usePluginContext<typeof REGISTRY_APP_BINDING>();
|
|
59
|
+
* const result = await toolCaller("COLLECTION_REGISTRY_APP_LIST", { limit: 20 });
|
|
60
|
+
*
|
|
61
|
+
* // In empty state component (no connection available)
|
|
62
|
+
* const { session, org } = usePluginContext<typeof REGISTRY_APP_BINDING>({ partial: true });
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
export function usePluginContext<TBinding extends Binder = Binder>(options: {
|
|
66
|
+
partial: true;
|
|
67
|
+
}): PluginContextPartial<TBinding>;
|
|
68
|
+
export function usePluginContext<
|
|
69
|
+
TBinding extends Binder = Binder,
|
|
70
|
+
>(): PluginContext<TBinding>;
|
|
71
|
+
export function usePluginContext<TBinding extends Binder = Binder>(
|
|
72
|
+
options?: UsePluginContextOptions,
|
|
73
|
+
): PluginContext<TBinding> | PluginContextPartial<TBinding> {
|
|
74
|
+
const context = useContext(PluginContextInternal);
|
|
75
|
+
if (!context) {
|
|
76
|
+
throw new Error(
|
|
77
|
+
"usePluginContext must be used within a PluginContextProvider",
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// If partial mode, return as-is with nullable fields
|
|
82
|
+
if (options?.partial) {
|
|
83
|
+
return context as PluginContextPartial<TBinding>;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Otherwise, assert that connection exists (routes should always have one)
|
|
87
|
+
if (!context.connectionId || !context.connection || !context.toolCaller) {
|
|
88
|
+
throw new Error(
|
|
89
|
+
"usePluginContext requires a valid connection. Use { partial: true } in empty state components.",
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return context as PluginContext<TBinding>;
|
|
94
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin Context Types
|
|
3
|
+
*
|
|
4
|
+
* Provides typed context for plugins to access their selected connection
|
|
5
|
+
* and call tools with full type safety based on the plugin's binding.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { Binder, ToolBinder } from "./binder";
|
|
9
|
+
import type { z } from "zod";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Connection entity shape provided by the layout.
|
|
13
|
+
*/
|
|
14
|
+
export interface PluginConnectionEntity {
|
|
15
|
+
id: string;
|
|
16
|
+
title: string;
|
|
17
|
+
icon: string | null;
|
|
18
|
+
description: string | null;
|
|
19
|
+
app_name: string | null;
|
|
20
|
+
app_id: string | null;
|
|
21
|
+
tools: Array<{
|
|
22
|
+
name: string;
|
|
23
|
+
description?: string;
|
|
24
|
+
inputSchema?: Record<string, unknown>;
|
|
25
|
+
outputSchema?: Record<string, unknown>;
|
|
26
|
+
}> | null;
|
|
27
|
+
metadata: Record<string, unknown> | null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Organization context.
|
|
32
|
+
*/
|
|
33
|
+
export interface PluginOrgContext {
|
|
34
|
+
id: string;
|
|
35
|
+
slug: string;
|
|
36
|
+
name: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* User session provided to plugins.
|
|
41
|
+
*/
|
|
42
|
+
export interface PluginSession {
|
|
43
|
+
user: {
|
|
44
|
+
id: string;
|
|
45
|
+
name: string;
|
|
46
|
+
email: string;
|
|
47
|
+
image?: string | null;
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Helper type to extract tool by name from a binding.
|
|
53
|
+
*/
|
|
54
|
+
type ExtractToolByName<TBinding extends Binder, TName extends string> = Extract<
|
|
55
|
+
TBinding[number],
|
|
56
|
+
{ name: TName }
|
|
57
|
+
>;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Typed tool caller for a specific binding.
|
|
61
|
+
* Provides type-safe tool calls based on the binding definition.
|
|
62
|
+
*
|
|
63
|
+
* @template TBinding - The binding type to derive tool types from
|
|
64
|
+
*/
|
|
65
|
+
export type TypedToolCaller<TBinding extends Binder> = <
|
|
66
|
+
TName extends TBinding[number]["name"] & string,
|
|
67
|
+
>(
|
|
68
|
+
toolName: TName,
|
|
69
|
+
args: ExtractToolByName<TBinding, TName> extends ToolBinder<
|
|
70
|
+
TName,
|
|
71
|
+
infer TInput,
|
|
72
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
73
|
+
infer _TOutput
|
|
74
|
+
>
|
|
75
|
+
? TInput extends z.ZodType
|
|
76
|
+
? z.infer<TInput>
|
|
77
|
+
: TInput
|
|
78
|
+
: unknown,
|
|
79
|
+
) => Promise<
|
|
80
|
+
ExtractToolByName<TBinding, TName> extends ToolBinder<
|
|
81
|
+
TName,
|
|
82
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
83
|
+
infer _TInput,
|
|
84
|
+
infer TOutput
|
|
85
|
+
>
|
|
86
|
+
? TOutput extends z.ZodType
|
|
87
|
+
? z.infer<TOutput>
|
|
88
|
+
: TOutput
|
|
89
|
+
: unknown
|
|
90
|
+
>;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Base plugin context with connection fields always available.
|
|
94
|
+
* Used by plugin routes where layout guarantees a valid connection.
|
|
95
|
+
*
|
|
96
|
+
* @template TBinding - The binding type the plugin requires
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* ```tsx
|
|
100
|
+
* // In plugin route component (connection guaranteed)
|
|
101
|
+
* const { toolCaller, connection } = usePluginContext<typeof REGISTRY_APP_BINDING>();
|
|
102
|
+
*
|
|
103
|
+
* // toolCaller and connection are non-null
|
|
104
|
+
* const result = await toolCaller("COLLECTION_REGISTRY_APP_LIST", { limit: 20 });
|
|
105
|
+
* ```
|
|
106
|
+
*/
|
|
107
|
+
export interface PluginContext<TBinding extends Binder = Binder> {
|
|
108
|
+
/**
|
|
109
|
+
* The selected connection ID.
|
|
110
|
+
* Always defined in routes (layout handles empty state separately).
|
|
111
|
+
*/
|
|
112
|
+
connectionId: string;
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* The selected connection entity.
|
|
116
|
+
* Always defined in routes.
|
|
117
|
+
*/
|
|
118
|
+
connection: PluginConnectionEntity;
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Typed tool caller for the selected connection.
|
|
122
|
+
* Call MCP tools with full type safety based on the plugin's binding.
|
|
123
|
+
*/
|
|
124
|
+
toolCaller: TypedToolCaller<TBinding>;
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Organization context.
|
|
128
|
+
* Always available.
|
|
129
|
+
*/
|
|
130
|
+
org: PluginOrgContext;
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Current user session.
|
|
134
|
+
* Available when user is authenticated.
|
|
135
|
+
*/
|
|
136
|
+
session: PluginSession | null;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Partial plugin context with nullable connection fields.
|
|
141
|
+
* Used by empty state components where no valid connection exists.
|
|
142
|
+
*
|
|
143
|
+
* @template TBinding - The binding type the plugin requires
|
|
144
|
+
*
|
|
145
|
+
* @example
|
|
146
|
+
* ```tsx
|
|
147
|
+
* // In empty state component (no connection)
|
|
148
|
+
* const { session, org } = usePluginContext<typeof REGISTRY_APP_BINDING>({ partial: true });
|
|
149
|
+
*
|
|
150
|
+
* // connection, connectionId, toolCaller are null
|
|
151
|
+
* ```
|
|
152
|
+
*/
|
|
153
|
+
export interface PluginContextPartial<TBinding extends Binder = Binder> {
|
|
154
|
+
/**
|
|
155
|
+
* The selected connection ID.
|
|
156
|
+
* Null when no valid connection is available.
|
|
157
|
+
*/
|
|
158
|
+
connectionId: string | null;
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* The selected connection entity.
|
|
162
|
+
* Null when no valid connection is available.
|
|
163
|
+
*/
|
|
164
|
+
connection: PluginConnectionEntity | null;
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Typed tool caller for the selected connection.
|
|
168
|
+
* Null when no valid connection is available.
|
|
169
|
+
*/
|
|
170
|
+
toolCaller: TypedToolCaller<TBinding> | null;
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Organization context.
|
|
174
|
+
* Always available.
|
|
175
|
+
*/
|
|
176
|
+
org: PluginOrgContext;
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Current user session.
|
|
180
|
+
* Available when user is authenticated.
|
|
181
|
+
*/
|
|
182
|
+
session: PluginSession | null;
|
|
183
|
+
}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import {
|
|
3
|
+
createRoute,
|
|
4
|
+
lazyRouteComponent,
|
|
5
|
+
Route,
|
|
6
|
+
useNavigate,
|
|
7
|
+
useParams,
|
|
8
|
+
useSearch,
|
|
9
|
+
useLocation,
|
|
10
|
+
Link as TanStackLink,
|
|
11
|
+
type AnyRoute,
|
|
12
|
+
type RouteIds,
|
|
13
|
+
type RouteById,
|
|
14
|
+
type LinkProps,
|
|
15
|
+
type NavigateOptions,
|
|
16
|
+
} from "@tanstack/react-router";
|
|
17
|
+
import type { PluginSetupContext } from "./plugins";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Prepends the plugin base path (/$org/$pluginId) to a route path.
|
|
21
|
+
* Handles both absolute plugin paths (starting with /) and relative paths.
|
|
22
|
+
*/
|
|
23
|
+
function prependBasePath(
|
|
24
|
+
to: string | undefined,
|
|
25
|
+
org: string,
|
|
26
|
+
pluginId: string,
|
|
27
|
+
): string {
|
|
28
|
+
if (!to) return `/${org}/${pluginId}`;
|
|
29
|
+
|
|
30
|
+
// If path starts with /, it's relative to the plugin root
|
|
31
|
+
if (to.startsWith("/")) {
|
|
32
|
+
return `/${org}/${pluginId}${to}`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Otherwise, it's already a full path or relative
|
|
36
|
+
return to;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Creates a typed plugin router from a route factory function.
|
|
41
|
+
*
|
|
42
|
+
* Routes are registered directly under ctx.parentRoute (which already has
|
|
43
|
+
* component: Outlet). Return an array of sibling routes for multiple pages.
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```tsx
|
|
47
|
+
* export const storeRouter = createPluginRouter((ctx) => {
|
|
48
|
+
* const indexRoute = ctx.routing.createRoute({
|
|
49
|
+
* getParentRoute: () => ctx.parentRoute,
|
|
50
|
+
* path: "/",
|
|
51
|
+
* component: ctx.routing.lazyRouteComponent(() => import("./routes/page.tsx")),
|
|
52
|
+
* });
|
|
53
|
+
*
|
|
54
|
+
* const detailRoute = ctx.routing.createRoute({
|
|
55
|
+
* getParentRoute: () => ctx.parentRoute,
|
|
56
|
+
* path: "/$appName",
|
|
57
|
+
* component: ctx.routing.lazyRouteComponent(() => import("./routes/detail.tsx")),
|
|
58
|
+
* validateSearch: z.object({ tab: z.string().optional() }),
|
|
59
|
+
* });
|
|
60
|
+
*
|
|
61
|
+
* return [indexRoute, detailRoute];
|
|
62
|
+
* });
|
|
63
|
+
*
|
|
64
|
+
* // In plugin setup - register each route
|
|
65
|
+
* export const storePlugin: AnyPlugin = {
|
|
66
|
+
* id: "store",
|
|
67
|
+
* setup: (ctx) => {
|
|
68
|
+
* const routes = storeRouter.createRoutes(ctx);
|
|
69
|
+
* for (const route of routes) {
|
|
70
|
+
* ctx.registerRootPluginRoute(route);
|
|
71
|
+
* }
|
|
72
|
+
* },
|
|
73
|
+
* };
|
|
74
|
+
*
|
|
75
|
+
* // In components
|
|
76
|
+
* function DetailPage() {
|
|
77
|
+
* const { appName } = storeRouter.useParams({ from: "/$appName" });
|
|
78
|
+
* const { tab } = storeRouter.useSearch({ from: "/$appName" });
|
|
79
|
+
*
|
|
80
|
+
* const navigate = storeRouter.useNavigate();
|
|
81
|
+
* navigate({ to: "/$appName", params: { appName: "other" } });
|
|
82
|
+
* }
|
|
83
|
+
* ```
|
|
84
|
+
*/
|
|
85
|
+
export function createPluginRouter<TRoutes extends AnyRoute | AnyRoute[]>(
|
|
86
|
+
createRoutes: (ctx: PluginSetupContext) => TRoutes,
|
|
87
|
+
) {
|
|
88
|
+
// Extract route type from array or single route
|
|
89
|
+
type TRoute = TRoutes extends (infer R)[] ? R : TRoutes;
|
|
90
|
+
type TRouteId = TRoute extends AnyRoute ? RouteIds<TRoute> : never;
|
|
91
|
+
type TRouteById<TId extends TRouteId> = TRoute extends AnyRoute
|
|
92
|
+
? RouteById<TRoute, TId>
|
|
93
|
+
: never;
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
/**
|
|
97
|
+
* Create the route tree. Call this in your plugin's setup().
|
|
98
|
+
*/
|
|
99
|
+
createRoutes,
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Get route params with TanStack's type inference.
|
|
103
|
+
*/
|
|
104
|
+
useParams: <TFrom extends TRouteId>(_options: { from: TFrom }) => {
|
|
105
|
+
return useParams({
|
|
106
|
+
strict: false,
|
|
107
|
+
}) as TRouteById<TFrom>["types"]["allParams"];
|
|
108
|
+
},
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Get search params with TanStack's type inference.
|
|
112
|
+
*/
|
|
113
|
+
useSearch: <TFrom extends TRouteId>(_options: { from: TFrom }) => {
|
|
114
|
+
return useSearch({
|
|
115
|
+
strict: false,
|
|
116
|
+
}) as TRouteById<TFrom>["types"]["fullSearchSchema"];
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Navigate within the plugin.
|
|
121
|
+
* Automatically prepends /$org/$pluginId to the path.
|
|
122
|
+
*/
|
|
123
|
+
useNavigate: () => {
|
|
124
|
+
const navigate = useNavigate();
|
|
125
|
+
const { org, pluginId } = useParams({ strict: false }) as {
|
|
126
|
+
org: string;
|
|
127
|
+
pluginId: string;
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
return <TTo extends TRouteId>(
|
|
131
|
+
options: Omit<NavigateOptions, "to" | "params"> & {
|
|
132
|
+
to: TTo;
|
|
133
|
+
params?: TRouteById<TTo>["types"]["allParams"];
|
|
134
|
+
search?: TRouteById<TTo>["types"]["fullSearchSchema"];
|
|
135
|
+
},
|
|
136
|
+
) => {
|
|
137
|
+
const to = prependBasePath(options.to, org, pluginId);
|
|
138
|
+
|
|
139
|
+
return navigate({
|
|
140
|
+
...options,
|
|
141
|
+
to,
|
|
142
|
+
params: {
|
|
143
|
+
org,
|
|
144
|
+
pluginId,
|
|
145
|
+
...(options.params as Record<string, string>),
|
|
146
|
+
},
|
|
147
|
+
} as NavigateOptions);
|
|
148
|
+
};
|
|
149
|
+
},
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Get the current location.
|
|
153
|
+
*/
|
|
154
|
+
useLocation: () => {
|
|
155
|
+
return useLocation();
|
|
156
|
+
},
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Link component for plugin navigation.
|
|
160
|
+
* Automatically prepends /$org/$pluginId to the path.
|
|
161
|
+
*/
|
|
162
|
+
Link: function PluginLink<TTo extends TRouteId>(
|
|
163
|
+
props: Omit<LinkProps, "to" | "params" | "search"> & {
|
|
164
|
+
to: TTo;
|
|
165
|
+
params?: TRouteById<TTo>["types"]["allParams"];
|
|
166
|
+
search?: TRouteById<TTo>["types"]["fullSearchSchema"];
|
|
167
|
+
className?: string;
|
|
168
|
+
children?: ReactNode;
|
|
169
|
+
},
|
|
170
|
+
) {
|
|
171
|
+
const { org, pluginId } = useParams({ strict: false }) as {
|
|
172
|
+
org: string;
|
|
173
|
+
pluginId: string;
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const to = prependBasePath(props.to as string, org, pluginId);
|
|
177
|
+
|
|
178
|
+
return (
|
|
179
|
+
<TanStackLink
|
|
180
|
+
{...(props as LinkProps)}
|
|
181
|
+
to={to}
|
|
182
|
+
params={{
|
|
183
|
+
org,
|
|
184
|
+
pluginId,
|
|
185
|
+
...props.params,
|
|
186
|
+
}}
|
|
187
|
+
/>
|
|
188
|
+
);
|
|
189
|
+
},
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Type helpers
|
|
193
|
+
*/
|
|
194
|
+
_types: {
|
|
195
|
+
routes: undefined as unknown as TRoutes,
|
|
196
|
+
routeIds: undefined as unknown as TRouteId,
|
|
197
|
+
},
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Type helper to extract route IDs from a plugin router
|
|
203
|
+
*/
|
|
204
|
+
export type PluginRouteIds<
|
|
205
|
+
TRouter extends ReturnType<typeof createPluginRouter>,
|
|
206
|
+
> = TRouter["_types"]["routeIds"];
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Type helper to extract routes from a plugin router
|
|
210
|
+
*/
|
|
211
|
+
export type PluginRoutes<
|
|
212
|
+
TRouter extends ReturnType<typeof createPluginRouter>,
|
|
213
|
+
> = TRouter["_types"]["routes"];
|
|
214
|
+
|
|
215
|
+
// Re-export TanStack utilities for plugins
|
|
216
|
+
export {
|
|
217
|
+
createRoute,
|
|
218
|
+
lazyRouteComponent,
|
|
219
|
+
Route,
|
|
220
|
+
TanStackLink as Link,
|
|
221
|
+
useNavigate,
|
|
222
|
+
useParams,
|
|
223
|
+
useSearch,
|
|
224
|
+
useLocation,
|
|
225
|
+
};
|
|
226
|
+
export type { AnyRoute, RouteIds, RouteById };
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createRoute,
|
|
3
|
+
lazyRouteComponent,
|
|
4
|
+
type AnyRoute,
|
|
5
|
+
} from "@tanstack/react-router";
|
|
6
|
+
import { Binder } from "./binder";
|
|
7
|
+
import type { ReactNode } from "react";
|
|
8
|
+
import type { PluginConnectionEntity } from "./plugin-context";
|
|
9
|
+
|
|
10
|
+
export interface ToolViewItem {
|
|
11
|
+
toolName: string;
|
|
12
|
+
label: string;
|
|
13
|
+
icon: ReactNode;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface RegisterRootSidebarItemParams {
|
|
17
|
+
icon: ReactNode;
|
|
18
|
+
label: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface RegisterEmptyStateParams {
|
|
22
|
+
component: ReactNode;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface PluginSetupContext {
|
|
26
|
+
parentRoute: AnyRoute;
|
|
27
|
+
routing: {
|
|
28
|
+
createRoute: typeof createRoute;
|
|
29
|
+
lazyRouteComponent: typeof lazyRouteComponent;
|
|
30
|
+
};
|
|
31
|
+
registerRootSidebarItem: (params: RegisterRootSidebarItemParams) => void;
|
|
32
|
+
registerPluginRoutes: (route: AnyRoute[]) => void;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export type PluginSetup = (context: PluginSetupContext) => void;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Props passed to plugin's renderHeader function.
|
|
39
|
+
*/
|
|
40
|
+
export interface PluginRenderHeaderProps {
|
|
41
|
+
connections: PluginConnectionEntity[];
|
|
42
|
+
selectedConnectionId: string;
|
|
43
|
+
onConnectionChange: (connectionId: string) => void;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface Plugin<TBinding extends Binder> {
|
|
47
|
+
id: string;
|
|
48
|
+
/**
|
|
49
|
+
* Short description of the plugin shown in the settings UI.
|
|
50
|
+
*/
|
|
51
|
+
description?: string;
|
|
52
|
+
binding: TBinding;
|
|
53
|
+
setup: PluginSetup;
|
|
54
|
+
/**
|
|
55
|
+
* Optional custom layout component for this plugin.
|
|
56
|
+
* If not provided, a default layout with connection selector will be used.
|
|
57
|
+
* @deprecated Use renderHeader and renderEmptyState instead.
|
|
58
|
+
*/
|
|
59
|
+
LayoutComponent?: React.ComponentType;
|
|
60
|
+
/**
|
|
61
|
+
* Render the header with connection selector.
|
|
62
|
+
* Receives the list of valid connections and current selection handlers.
|
|
63
|
+
*/
|
|
64
|
+
renderHeader?: (props: PluginRenderHeaderProps) => ReactNode;
|
|
65
|
+
/**
|
|
66
|
+
* Render the empty state when no valid connections are available.
|
|
67
|
+
*/
|
|
68
|
+
renderEmptyState?: () => ReactNode;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export type AnyPlugin = Plugin<any>;
|
|
72
|
+
|
|
73
|
+
// Re-export plugin router utilities
|
|
74
|
+
export {
|
|
75
|
+
createPluginRouter,
|
|
76
|
+
type PluginRouteIds,
|
|
77
|
+
type PluginRoutes,
|
|
78
|
+
type AnyRoute,
|
|
79
|
+
type RouteIds,
|
|
80
|
+
type RouteById,
|
|
81
|
+
} from "./plugin-router";
|
package/src/index.ts
CHANGED
|
@@ -8,12 +8,32 @@
|
|
|
8
8
|
// Re-export core binder types and utilities
|
|
9
9
|
export {
|
|
10
10
|
createBindingChecker,
|
|
11
|
+
bindingClient,
|
|
12
|
+
connectionImplementsBinding,
|
|
11
13
|
type Binder,
|
|
12
14
|
type BindingChecker,
|
|
13
15
|
type ToolBinder,
|
|
14
16
|
type ToolWithSchemas,
|
|
17
|
+
type ConnectionForBinding,
|
|
15
18
|
} from "./core/binder";
|
|
16
19
|
|
|
20
|
+
// Re-export plugin context types and provider
|
|
21
|
+
export {
|
|
22
|
+
type PluginContext,
|
|
23
|
+
type PluginContextPartial,
|
|
24
|
+
type PluginConnectionEntity,
|
|
25
|
+
type PluginOrgContext,
|
|
26
|
+
type PluginSession,
|
|
27
|
+
type TypedToolCaller,
|
|
28
|
+
} from "./core/plugin-context";
|
|
29
|
+
|
|
30
|
+
export {
|
|
31
|
+
PluginContextProvider,
|
|
32
|
+
usePluginContext,
|
|
33
|
+
type PluginContextProviderProps,
|
|
34
|
+
type UsePluginContextOptions,
|
|
35
|
+
} from "./core/plugin-context-provider";
|
|
36
|
+
|
|
17
37
|
// Re-export registry binding types
|
|
18
38
|
export {
|
|
19
39
|
MCPRegistryServerSchema,
|
|
@@ -70,3 +90,21 @@ export {
|
|
|
70
90
|
EventBusBinding,
|
|
71
91
|
type EventBusBindingClient,
|
|
72
92
|
} from "./well-known/event-bus";
|
|
93
|
+
|
|
94
|
+
// Re-export object storage binding types
|
|
95
|
+
export {
|
|
96
|
+
OBJECT_STORAGE_BINDING,
|
|
97
|
+
type ObjectStorageBinding,
|
|
98
|
+
type ListObjectsInput,
|
|
99
|
+
type ListObjectsOutput,
|
|
100
|
+
type GetObjectMetadataInput,
|
|
101
|
+
type GetObjectMetadataOutput,
|
|
102
|
+
type GetPresignedUrlInput,
|
|
103
|
+
type GetPresignedUrlOutput,
|
|
104
|
+
type PutPresignedUrlInput,
|
|
105
|
+
type PutPresignedUrlOutput,
|
|
106
|
+
type DeleteObjectInput,
|
|
107
|
+
type DeleteObjectOutput,
|
|
108
|
+
type DeleteObjectsInput,
|
|
109
|
+
type DeleteObjectsOutput,
|
|
110
|
+
} from "./well-known/object-storage";
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Defines the interface for AI assistant providers.
|
|
5
5
|
* Any MCP that implements this binding can provide configurable AI assistants
|
|
6
|
-
* with a system prompt and runtime configuration (
|
|
6
|
+
* with a system prompt and runtime configuration (virtual MCP + model).
|
|
7
7
|
*
|
|
8
8
|
* This binding uses collection bindings for full CRUD operations.
|
|
9
9
|
*/
|
|
@@ -37,10 +37,12 @@ export const AssistantSchema = BaseCollectionEntitySchema.extend({
|
|
|
37
37
|
.describe("System prompt that defines the assistant's behavior"),
|
|
38
38
|
|
|
39
39
|
/**
|
|
40
|
-
* Selected
|
|
41
|
-
* This
|
|
40
|
+
* Selected virtual MCP (agent) for this assistant.
|
|
41
|
+
* This virtual MCP determines which MCP tools are exposed to chat.
|
|
42
42
|
*/
|
|
43
|
-
|
|
43
|
+
virtual_mcp_id: z
|
|
44
|
+
.string()
|
|
45
|
+
.describe("Virtual MCP ID to use for this assistant"),
|
|
44
46
|
|
|
45
47
|
/**
|
|
46
48
|
* Selected model for this assistant (model id + the connection where it lives).
|
|
@@ -75,7 +77,7 @@ export const ASSISTANTS_COLLECTION_BINDING = createCollectionBindings(
|
|
|
75
77
|
*
|
|
76
78
|
* Required tools:
|
|
77
79
|
* - COLLECTION_ASSISTANT_LIST: List available AI assistants with their configurations
|
|
78
|
-
* - COLLECTION_ASSISTANT_GET: Get a single assistant by ID (includes system_prompt,
|
|
80
|
+
* - COLLECTION_ASSISTANT_GET: Get a single assistant by ID (includes system_prompt, virtual_mcp_id, model)
|
|
79
81
|
*
|
|
80
82
|
* Optional tools:
|
|
81
83
|
* - COLLECTION_ASSISTANT_CREATE: Create a new assistant
|
|
@@ -150,10 +150,10 @@ export const EventSubscribeOutputSchema = z.object({
|
|
|
150
150
|
enabled: z.boolean().describe("Whether subscription is enabled"),
|
|
151
151
|
|
|
152
152
|
/** Created timestamp */
|
|
153
|
-
createdAt: z.
|
|
153
|
+
createdAt: z.string().datetime().describe("Created timestamp (ISO 8601)"),
|
|
154
154
|
|
|
155
155
|
/** Updated timestamp */
|
|
156
|
-
updatedAt: z.
|
|
156
|
+
updatedAt: z.string().datetime().describe("Updated timestamp (ISO 8601)"),
|
|
157
157
|
}),
|
|
158
158
|
});
|
|
159
159
|
|
|
@@ -209,10 +209,10 @@ export const SubscriptionDetailSchema = z.object({
|
|
|
209
209
|
enabled: z.boolean().describe("Whether subscription is enabled"),
|
|
210
210
|
|
|
211
211
|
/** Created timestamp */
|
|
212
|
-
createdAt: z.
|
|
212
|
+
createdAt: z.string().datetime().describe("Created timestamp (ISO 8601)"),
|
|
213
213
|
|
|
214
214
|
/** Updated timestamp */
|
|
215
|
-
updatedAt: z.
|
|
215
|
+
updatedAt: z.string().datetime().describe("Updated timestamp (ISO 8601)"),
|
|
216
216
|
});
|
|
217
217
|
|
|
218
218
|
export type SubscriptionDetail = z.infer<typeof SubscriptionDetailSchema>;
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Object Storage Well-Known Binding
|
|
3
|
+
*
|
|
4
|
+
* Defines the interface for S3-compatible object storage operations.
|
|
5
|
+
* Any MCP that implements this binding can provide file/object management
|
|
6
|
+
* for buckets and objects.
|
|
7
|
+
*
|
|
8
|
+
* This binding includes:
|
|
9
|
+
* - LIST_OBJECTS: List objects with pagination and prefix filtering
|
|
10
|
+
* - GET_OBJECT_METADATA: Get object metadata (HEAD operation)
|
|
11
|
+
* - GET_PRESIGNED_URL: Generate presigned URL for downloading
|
|
12
|
+
* - PUT_PRESIGNED_URL: Generate presigned URL for uploading
|
|
13
|
+
* - DELETE_OBJECT: Delete a single object
|
|
14
|
+
* - DELETE_OBJECTS: Batch delete multiple objects
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { z } from "zod";
|
|
18
|
+
import type { Binder, ToolBinder } from "../core/binder";
|
|
19
|
+
|
|
20
|
+
// ============================================================================
|
|
21
|
+
// Tool Schemas
|
|
22
|
+
// ============================================================================
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* LIST_OBJECTS - List objects in the bucket with pagination support
|
|
26
|
+
*/
|
|
27
|
+
const ListObjectsInputSchema = z.object({
|
|
28
|
+
prefix: z
|
|
29
|
+
.string()
|
|
30
|
+
.optional()
|
|
31
|
+
.describe("Filter objects by prefix (e.g., 'folder/' for folder contents)"),
|
|
32
|
+
maxKeys: z
|
|
33
|
+
.number()
|
|
34
|
+
.optional()
|
|
35
|
+
.default(1000)
|
|
36
|
+
.describe("Maximum number of keys to return (default: 1000)"),
|
|
37
|
+
continuationToken: z
|
|
38
|
+
.string()
|
|
39
|
+
.optional()
|
|
40
|
+
.describe("Token for pagination from previous response"),
|
|
41
|
+
delimiter: z
|
|
42
|
+
.string()
|
|
43
|
+
.optional()
|
|
44
|
+
.describe(
|
|
45
|
+
"Delimiter for grouping keys (typically '/'). When set, commonPrefixes returns folder paths.",
|
|
46
|
+
),
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const ListObjectsOutputSchema = z.object({
|
|
50
|
+
objects: z.array(
|
|
51
|
+
z.object({
|
|
52
|
+
key: z.string().describe("Object key/path"),
|
|
53
|
+
size: z.number().describe("Object size in bytes"),
|
|
54
|
+
lastModified: z.string().describe("Last modified timestamp"),
|
|
55
|
+
etag: z.string().describe("Entity tag for the object"),
|
|
56
|
+
}),
|
|
57
|
+
),
|
|
58
|
+
nextContinuationToken: z
|
|
59
|
+
.string()
|
|
60
|
+
.optional()
|
|
61
|
+
.describe("Token for fetching next page of results"),
|
|
62
|
+
isTruncated: z.boolean().describe("Whether there are more results available"),
|
|
63
|
+
commonPrefixes: z
|
|
64
|
+
.array(z.string())
|
|
65
|
+
.optional()
|
|
66
|
+
.describe(
|
|
67
|
+
"Folder paths when delimiter is used (e.g., ['photos/2024/', 'photos/2025/'])",
|
|
68
|
+
),
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
export type ListObjectsInput = z.infer<typeof ListObjectsInputSchema>;
|
|
72
|
+
export type ListObjectsOutput = z.infer<typeof ListObjectsOutputSchema>;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* GET_OBJECT_METADATA - Get object metadata using HEAD operation
|
|
76
|
+
*/
|
|
77
|
+
const GetObjectMetadataInputSchema = z.object({
|
|
78
|
+
key: z.string().describe("Object key/path to get metadata for"),
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const GetObjectMetadataOutputSchema = z.object({
|
|
82
|
+
contentType: z.string().optional().describe("MIME type of the object"),
|
|
83
|
+
contentLength: z.number().describe("Size of the object in bytes"),
|
|
84
|
+
lastModified: z.string().describe("Last modified timestamp"),
|
|
85
|
+
etag: z.string().describe("Entity tag for the object"),
|
|
86
|
+
metadata: z
|
|
87
|
+
.record(z.string(), z.string())
|
|
88
|
+
.optional()
|
|
89
|
+
.describe("Custom metadata key-value pairs"),
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
export type GetObjectMetadataInput = z.infer<
|
|
93
|
+
typeof GetObjectMetadataInputSchema
|
|
94
|
+
>;
|
|
95
|
+
export type GetObjectMetadataOutput = z.infer<
|
|
96
|
+
typeof GetObjectMetadataOutputSchema
|
|
97
|
+
>;
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* GET_PRESIGNED_URL - Generate a presigned URL for downloading an object
|
|
101
|
+
*/
|
|
102
|
+
const GetPresignedUrlInputSchema = z.object({
|
|
103
|
+
key: z.string().describe("Object key/path to generate URL for"),
|
|
104
|
+
expiresIn: z
|
|
105
|
+
.number()
|
|
106
|
+
.optional()
|
|
107
|
+
.describe(
|
|
108
|
+
"URL expiration time in seconds (default: from state config or 3600)",
|
|
109
|
+
),
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const GetPresignedUrlOutputSchema = z.object({
|
|
113
|
+
url: z.string().describe("Presigned URL for downloading the object"),
|
|
114
|
+
expiresIn: z.number().describe("Expiration time in seconds that was used"),
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
export type GetPresignedUrlInput = z.infer<typeof GetPresignedUrlInputSchema>;
|
|
118
|
+
export type GetPresignedUrlOutput = z.infer<typeof GetPresignedUrlOutputSchema>;
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* PUT_PRESIGNED_URL - Generate a presigned URL for uploading an object
|
|
122
|
+
*/
|
|
123
|
+
const PutPresignedUrlInputSchema = z.object({
|
|
124
|
+
key: z.string().describe("Object key/path for the upload"),
|
|
125
|
+
expiresIn: z
|
|
126
|
+
.number()
|
|
127
|
+
.optional()
|
|
128
|
+
.describe(
|
|
129
|
+
"URL expiration time in seconds (default: from state config or 3600)",
|
|
130
|
+
),
|
|
131
|
+
contentType: z
|
|
132
|
+
.string()
|
|
133
|
+
.optional()
|
|
134
|
+
.describe("MIME type for the object being uploaded"),
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
const PutPresignedUrlOutputSchema = z.object({
|
|
138
|
+
url: z.string().describe("Presigned URL for uploading the object"),
|
|
139
|
+
expiresIn: z.number().describe("Expiration time in seconds that was used"),
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
export type PutPresignedUrlInput = z.infer<typeof PutPresignedUrlInputSchema>;
|
|
143
|
+
export type PutPresignedUrlOutput = z.infer<typeof PutPresignedUrlOutputSchema>;
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* DELETE_OBJECT - Delete a single object
|
|
147
|
+
*/
|
|
148
|
+
const DeleteObjectInputSchema = z.object({
|
|
149
|
+
key: z.string().describe("Object key/path to delete"),
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
const DeleteObjectOutputSchema = z.object({
|
|
153
|
+
success: z.boolean().describe("Whether the deletion was successful"),
|
|
154
|
+
key: z.string().describe("The key that was deleted"),
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
export type DeleteObjectInput = z.infer<typeof DeleteObjectInputSchema>;
|
|
158
|
+
export type DeleteObjectOutput = z.infer<typeof DeleteObjectOutputSchema>;
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* DELETE_OBJECTS - Delete multiple objects in batch
|
|
162
|
+
*/
|
|
163
|
+
const DeleteObjectsInputSchema = z.object({
|
|
164
|
+
keys: z
|
|
165
|
+
.array(z.string())
|
|
166
|
+
.max(1000)
|
|
167
|
+
.describe("Array of object keys/paths to delete (max 1000)"),
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
const DeleteObjectsOutputSchema = z.object({
|
|
171
|
+
deleted: z.array(z.string()).describe("Array of successfully deleted keys"),
|
|
172
|
+
errors: z
|
|
173
|
+
.array(
|
|
174
|
+
z.object({
|
|
175
|
+
key: z.string(),
|
|
176
|
+
message: z.string(),
|
|
177
|
+
}),
|
|
178
|
+
)
|
|
179
|
+
.describe("Array of errors for failed deletions"),
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
export type DeleteObjectsInput = z.infer<typeof DeleteObjectsInputSchema>;
|
|
183
|
+
export type DeleteObjectsOutput = z.infer<typeof DeleteObjectsOutputSchema>;
|
|
184
|
+
|
|
185
|
+
// ============================================================================
|
|
186
|
+
// Binding Definition
|
|
187
|
+
// ============================================================================
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Object Storage Binding
|
|
191
|
+
*
|
|
192
|
+
* Defines the interface for S3-compatible object storage operations.
|
|
193
|
+
* Any MCP that implements this binding can be used with the Object Storage plugin
|
|
194
|
+
* to provide a file browser UI.
|
|
195
|
+
*
|
|
196
|
+
* Required tools:
|
|
197
|
+
* - LIST_OBJECTS: List objects with prefix filtering and pagination
|
|
198
|
+
* - GET_OBJECT_METADATA: Get object metadata (HEAD)
|
|
199
|
+
* - GET_PRESIGNED_URL: Generate download URL
|
|
200
|
+
* - PUT_PRESIGNED_URL: Generate upload URL
|
|
201
|
+
* - DELETE_OBJECT: Delete single object
|
|
202
|
+
* - DELETE_OBJECTS: Batch delete objects
|
|
203
|
+
*/
|
|
204
|
+
export const OBJECT_STORAGE_BINDING = [
|
|
205
|
+
{
|
|
206
|
+
name: "LIST_OBJECTS" as const,
|
|
207
|
+
inputSchema: ListObjectsInputSchema,
|
|
208
|
+
outputSchema: ListObjectsOutputSchema,
|
|
209
|
+
} satisfies ToolBinder<"LIST_OBJECTS", ListObjectsInput, ListObjectsOutput>,
|
|
210
|
+
{
|
|
211
|
+
name: "GET_OBJECT_METADATA" as const,
|
|
212
|
+
inputSchema: GetObjectMetadataInputSchema,
|
|
213
|
+
outputSchema: GetObjectMetadataOutputSchema,
|
|
214
|
+
} satisfies ToolBinder<
|
|
215
|
+
"GET_OBJECT_METADATA",
|
|
216
|
+
GetObjectMetadataInput,
|
|
217
|
+
GetObjectMetadataOutput
|
|
218
|
+
>,
|
|
219
|
+
{
|
|
220
|
+
name: "GET_PRESIGNED_URL" as const,
|
|
221
|
+
inputSchema: GetPresignedUrlInputSchema,
|
|
222
|
+
outputSchema: GetPresignedUrlOutputSchema,
|
|
223
|
+
} satisfies ToolBinder<
|
|
224
|
+
"GET_PRESIGNED_URL",
|
|
225
|
+
GetPresignedUrlInput,
|
|
226
|
+
GetPresignedUrlOutput
|
|
227
|
+
>,
|
|
228
|
+
{
|
|
229
|
+
name: "PUT_PRESIGNED_URL" as const,
|
|
230
|
+
inputSchema: PutPresignedUrlInputSchema,
|
|
231
|
+
outputSchema: PutPresignedUrlOutputSchema,
|
|
232
|
+
} satisfies ToolBinder<
|
|
233
|
+
"PUT_PRESIGNED_URL",
|
|
234
|
+
PutPresignedUrlInput,
|
|
235
|
+
PutPresignedUrlOutput
|
|
236
|
+
>,
|
|
237
|
+
{
|
|
238
|
+
name: "DELETE_OBJECT" as const,
|
|
239
|
+
inputSchema: DeleteObjectInputSchema,
|
|
240
|
+
outputSchema: DeleteObjectOutputSchema,
|
|
241
|
+
} satisfies ToolBinder<
|
|
242
|
+
"DELETE_OBJECT",
|
|
243
|
+
DeleteObjectInput,
|
|
244
|
+
DeleteObjectOutput
|
|
245
|
+
>,
|
|
246
|
+
{
|
|
247
|
+
name: "DELETE_OBJECTS" as const,
|
|
248
|
+
inputSchema: DeleteObjectsInputSchema,
|
|
249
|
+
outputSchema: DeleteObjectsOutputSchema,
|
|
250
|
+
} satisfies ToolBinder<
|
|
251
|
+
"DELETE_OBJECTS",
|
|
252
|
+
DeleteObjectsInput,
|
|
253
|
+
DeleteObjectsOutput
|
|
254
|
+
>,
|
|
255
|
+
] as const satisfies Binder;
|
|
256
|
+
|
|
257
|
+
export type ObjectStorageBinding = typeof OBJECT_STORAGE_BINDING;
|
|
@@ -164,9 +164,11 @@ export type WorkflowExecutionStatus = z.infer<
|
|
|
164
164
|
* Includes lock columns and retry tracking.
|
|
165
165
|
*/
|
|
166
166
|
export const WorkflowExecutionSchema = BaseCollectionEntitySchema.extend({
|
|
167
|
-
|
|
167
|
+
virtual_mcp_id: z
|
|
168
168
|
.string()
|
|
169
|
-
.describe(
|
|
169
|
+
.describe(
|
|
170
|
+
"ID of the virtual MCP (agent) that will be used to execute the workflow",
|
|
171
|
+
),
|
|
170
172
|
status: WorkflowExecutionStatusEnum.describe(
|
|
171
173
|
"Current status of the workflow execution",
|
|
172
174
|
),
|