@decocms/mesh-sdk 1.2.1 → 1.2.2
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 +6 -4
- package/src/context/index.ts +6 -1
- package/src/context/project-context.tsx +78 -29
- package/src/hooks/index.ts +7 -0
- package/src/hooks/use-collections.ts +160 -51
- package/src/hooks/use-connection.ts +39 -4
- package/src/hooks/use-mcp-client.ts +55 -2
- package/src/hooks/use-mcp-prompts.ts +16 -6
- package/src/hooks/use-mcp-resources.ts +15 -5
- package/src/index.ts +82 -3
- package/src/lib/bridge-transport.test.ts +368 -0
- package/src/lib/bridge-transport.ts +434 -0
- package/src/lib/constants.ts +113 -10
- package/src/lib/default-model.ts +96 -0
- package/src/lib/mcp-oauth.ts +80 -9
- package/src/lib/query-keys.ts +1 -0
- package/src/lib/server-client-bridge.ts +146 -0
- package/src/lib/usage.test.ts +163 -0
- package/src/lib/usage.ts +161 -0
- package/src/plugins/index.ts +15 -0
- package/src/plugins/plugin-context-provider.tsx +99 -0
- package/src/plugins/topbar-portal.tsx +118 -0
- package/src/types/ai-providers.ts +68 -0
- package/src/types/connection.ts +38 -20
- package/src/types/decopilot-events.ts +128 -0
- package/src/types/index.ts +30 -1
- package/src/types/virtual-mcp.ts +107 -109
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decocms/mesh-sdk",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.2",
|
|
4
4
|
"description": "SDK for building external apps that integrate with Mesh MCP servers",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -11,16 +11,18 @@
|
|
|
11
11
|
".": "./src/index.ts",
|
|
12
12
|
"./hooks": "./src/hooks/index.ts",
|
|
13
13
|
"./context": "./src/context/index.ts",
|
|
14
|
-
"./types": "./src/types/index.ts"
|
|
14
|
+
"./types": "./src/types/index.ts",
|
|
15
|
+
"./plugins": "./src/plugins/index.ts"
|
|
15
16
|
},
|
|
16
17
|
"dependencies": {
|
|
17
18
|
"@decocms/bindings": "^1.1.1",
|
|
18
|
-
"@modelcontextprotocol/sdk": "1.
|
|
19
|
+
"@modelcontextprotocol/sdk": "1.27.1",
|
|
19
20
|
"zod": "^4.0.0"
|
|
20
21
|
},
|
|
21
22
|
"peerDependencies": {
|
|
22
23
|
"@tanstack/react-query": ">=5.0.0",
|
|
23
24
|
"react": ">=18.0.0",
|
|
25
|
+
"react-dom": ">=18.0.0",
|
|
24
26
|
"sonner": ">=2.0.0"
|
|
25
27
|
},
|
|
26
28
|
"peerDependenciesMeta": {
|
|
@@ -41,7 +43,7 @@
|
|
|
41
43
|
],
|
|
42
44
|
"repository": {
|
|
43
45
|
"type": "git",
|
|
44
|
-
"url": "git+https://github.com/decocms/
|
|
46
|
+
"url": "git+https://github.com/decocms/studio.git",
|
|
45
47
|
"directory": "packages/mesh-sdk"
|
|
46
48
|
},
|
|
47
49
|
"license": "MIT",
|
package/src/context/index.ts
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
export {
|
|
2
2
|
ProjectContextProvider,
|
|
3
3
|
useProjectContext,
|
|
4
|
+
useOrg,
|
|
5
|
+
useCurrentProject,
|
|
6
|
+
useIsOrgAdmin,
|
|
4
7
|
Locator,
|
|
5
|
-
ORG_ADMIN_PROJECT_SLUG,
|
|
6
8
|
type ProjectContextProviderProps,
|
|
7
9
|
type ProjectLocator,
|
|
8
10
|
type LocatorStructured,
|
|
11
|
+
type OrganizationData,
|
|
12
|
+
type ProjectData,
|
|
13
|
+
type ProjectUI,
|
|
9
14
|
} from "./project-context";
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { createContext, useContext, type PropsWithChildren } from "react";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* A ProjectLocator is an ID-based string that identifies a project in an organization.
|
|
5
5
|
*
|
|
6
|
-
* format: <
|
|
6
|
+
* format: <orgId>/<projectId>
|
|
7
7
|
*/
|
|
8
8
|
export type ProjectLocator = `${string}/${string}`;
|
|
9
9
|
|
|
@@ -12,14 +12,8 @@ export type LocatorStructured = {
|
|
|
12
12
|
project: string;
|
|
13
13
|
};
|
|
14
14
|
|
|
15
|
-
export const ORG_ADMIN_PROJECT_SLUG = "org-admin";
|
|
16
|
-
|
|
17
15
|
export const Locator = {
|
|
18
16
|
from({ org, project }: LocatorStructured): ProjectLocator {
|
|
19
|
-
if (org?.includes("/") || project.includes("/")) {
|
|
20
|
-
throw new Error("Org or project cannot contain slashes");
|
|
21
|
-
}
|
|
22
|
-
|
|
23
17
|
return `${org}/${project}` as ProjectLocator;
|
|
24
18
|
},
|
|
25
19
|
parse(locator: ProjectLocator): LocatorStructured {
|
|
@@ -32,27 +26,60 @@ export const Locator = {
|
|
|
32
26
|
}
|
|
33
27
|
return { org, project };
|
|
34
28
|
},
|
|
35
|
-
isOrgAdminProject(locator: ProjectLocator): boolean {
|
|
36
|
-
return locator.split("/")[1] === ORG_ADMIN_PROJECT_SLUG;
|
|
37
|
-
},
|
|
38
|
-
adminProject(org: string): ProjectLocator {
|
|
39
|
-
return `${org}/${ORG_ADMIN_PROJECT_SLUG}`;
|
|
40
|
-
},
|
|
41
29
|
} as const;
|
|
42
30
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
31
|
+
/**
|
|
32
|
+
* Project UI customization
|
|
33
|
+
*/
|
|
34
|
+
export interface ProjectUI {
|
|
35
|
+
banner: string | null;
|
|
36
|
+
bannerColor: string | null;
|
|
37
|
+
icon: string | null;
|
|
38
|
+
themeColor: string | null;
|
|
39
|
+
pinnedViews?: Array<{
|
|
40
|
+
connectionId: string;
|
|
41
|
+
toolName: string;
|
|
42
|
+
label: string;
|
|
43
|
+
icon: string | null;
|
|
44
|
+
}> | null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Organization data in context
|
|
49
|
+
*/
|
|
50
|
+
export interface OrganizationData {
|
|
51
|
+
id: string;
|
|
52
|
+
name: string;
|
|
53
|
+
slug: string;
|
|
54
|
+
logo: string | null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Project data in context
|
|
59
|
+
* Includes full project info when loaded from storage
|
|
60
|
+
*/
|
|
61
|
+
export interface ProjectData {
|
|
62
|
+
/** Project ID */
|
|
63
|
+
id: string;
|
|
64
|
+
/** Organization ID (only available when loaded from storage) */
|
|
65
|
+
organizationId?: string;
|
|
66
|
+
/** Project slug */
|
|
67
|
+
slug: string;
|
|
68
|
+
/** Project display name */
|
|
69
|
+
name?: string;
|
|
70
|
+
/** Project description */
|
|
71
|
+
description?: string | null;
|
|
72
|
+
/** Enabled plugins */
|
|
73
|
+
enabledPlugins?: string[] | null;
|
|
74
|
+
/** UI customization */
|
|
75
|
+
ui?: ProjectUI | null;
|
|
76
|
+
/** Whether this is the org-admin project */
|
|
77
|
+
isOrgAdmin?: boolean;
|
|
78
|
+
}
|
|
55
79
|
|
|
80
|
+
interface ProjectContextType {
|
|
81
|
+
org: OrganizationData;
|
|
82
|
+
project: ProjectData;
|
|
56
83
|
locator: ProjectLocator;
|
|
57
84
|
}
|
|
58
85
|
|
|
@@ -69,9 +96,31 @@ export const useProjectContext = () => {
|
|
|
69
96
|
return context;
|
|
70
97
|
};
|
|
71
98
|
|
|
99
|
+
/**
|
|
100
|
+
* Convenience hook to get organization data
|
|
101
|
+
*/
|
|
102
|
+
export const useOrg = () => {
|
|
103
|
+
return useProjectContext().org;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Convenience hook to get current project data
|
|
108
|
+
*/
|
|
109
|
+
export const useCurrentProject = () => {
|
|
110
|
+
return useProjectContext().project;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Convenience hook to check if current project is org-admin
|
|
115
|
+
*/
|
|
116
|
+
export const useIsOrgAdmin = () => {
|
|
117
|
+
const project = useProjectContext().project;
|
|
118
|
+
return project.isOrgAdmin === true;
|
|
119
|
+
};
|
|
120
|
+
|
|
72
121
|
export type ProjectContextProviderProps = {
|
|
73
|
-
org:
|
|
74
|
-
project:
|
|
122
|
+
org: OrganizationData;
|
|
123
|
+
project: ProjectData;
|
|
75
124
|
};
|
|
76
125
|
|
|
77
126
|
export const ProjectContextProvider = ({
|
|
@@ -79,7 +128,7 @@ export const ProjectContextProvider = ({
|
|
|
79
128
|
org,
|
|
80
129
|
project,
|
|
81
130
|
}: PropsWithChildren<ProjectContextProviderProps>) => {
|
|
82
|
-
const locator = Locator.from({ org: org.
|
|
131
|
+
const locator = Locator.from({ org: org.id, project: project.id });
|
|
83
132
|
|
|
84
133
|
const value = { org, project, locator };
|
|
85
134
|
|
package/src/hooks/index.ts
CHANGED
|
@@ -3,9 +3,14 @@ export {
|
|
|
3
3
|
useCollectionItem,
|
|
4
4
|
useCollectionList,
|
|
5
5
|
useCollectionActions,
|
|
6
|
+
buildWhereExpression,
|
|
7
|
+
buildOrderByExpression,
|
|
8
|
+
buildCollectionQueryKey,
|
|
9
|
+
EMPTY_COLLECTION_LIST_RESULT,
|
|
6
10
|
type CollectionEntity,
|
|
7
11
|
type CollectionFilter,
|
|
8
12
|
type UseCollectionListOptions,
|
|
13
|
+
type CollectionQueryKey,
|
|
9
14
|
} from "./use-collections";
|
|
10
15
|
|
|
11
16
|
// Connection hooks
|
|
@@ -21,8 +26,10 @@ export {
|
|
|
21
26
|
export {
|
|
22
27
|
createMCPClient,
|
|
23
28
|
useMCPClient,
|
|
29
|
+
useMCPClientOptional,
|
|
24
30
|
type CreateMcpClientOptions,
|
|
25
31
|
type UseMcpClientOptions,
|
|
32
|
+
type UseMcpClientOptionalOptions,
|
|
26
33
|
} from "./use-mcp-client";
|
|
27
34
|
|
|
28
35
|
// MCP tools hooks
|
|
@@ -20,20 +20,22 @@ import {
|
|
|
20
20
|
type OrderByExpression,
|
|
21
21
|
type WhereExpression,
|
|
22
22
|
} from "@decocms/bindings/collections";
|
|
23
|
+
import type { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
23
24
|
import {
|
|
24
25
|
useMutation,
|
|
25
26
|
useQueryClient,
|
|
26
27
|
useSuspenseQuery,
|
|
27
28
|
} from "@tanstack/react-query";
|
|
28
29
|
import { toast } from "sonner";
|
|
29
|
-
import type { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
30
|
-
import { useMCPToolCall } from "./use-mcp-tools";
|
|
31
30
|
import { KEYS } from "../lib/query-keys";
|
|
32
31
|
|
|
33
32
|
/**
|
|
34
33
|
* Collection entity base type that matches the collection binding pattern
|
|
34
|
+
* Note: id can be nullable for synthetic entities like Decopilot agent
|
|
35
35
|
*/
|
|
36
|
-
export type CollectionEntity = BaseCollectionEntity
|
|
36
|
+
export type CollectionEntity = Omit<BaseCollectionEntity, "id"> & {
|
|
37
|
+
id: string | null;
|
|
38
|
+
};
|
|
37
39
|
|
|
38
40
|
/**
|
|
39
41
|
* Filter definition for collection queries (matches @deco/ui Filter shape)
|
|
@@ -63,12 +65,27 @@ export interface UseCollectionListOptions<T extends CollectionEntity> {
|
|
|
63
65
|
defaultSortKey?: keyof T;
|
|
64
66
|
/** Page size for pagination (default: 100) */
|
|
65
67
|
pageSize?: number;
|
|
68
|
+
/** Additional arguments forwarded to the collection tool call (e.g., binding, include_virtual) */
|
|
69
|
+
additionalToolArgs?: Record<string, unknown>;
|
|
66
70
|
}
|
|
67
71
|
|
|
72
|
+
/**
|
|
73
|
+
* Query key type for collection list queries
|
|
74
|
+
*/
|
|
75
|
+
export type CollectionQueryKey = readonly [
|
|
76
|
+
unknown,
|
|
77
|
+
string,
|
|
78
|
+
string,
|
|
79
|
+
"collection",
|
|
80
|
+
string,
|
|
81
|
+
"list",
|
|
82
|
+
string,
|
|
83
|
+
];
|
|
84
|
+
|
|
68
85
|
/**
|
|
69
86
|
* Build a where expression from search term and filters
|
|
70
87
|
*/
|
|
71
|
-
function buildWhereExpression<T extends CollectionEntity>(
|
|
88
|
+
export function buildWhereExpression<T extends CollectionEntity>(
|
|
72
89
|
searchTerm: string | undefined,
|
|
73
90
|
filters: CollectionFilter[] | undefined,
|
|
74
91
|
searchFields: (keyof T)[],
|
|
@@ -123,7 +140,7 @@ function buildWhereExpression<T extends CollectionEntity>(
|
|
|
123
140
|
/**
|
|
124
141
|
* Build orderBy expression from sort key and direction
|
|
125
142
|
*/
|
|
126
|
-
function buildOrderByExpression<T extends CollectionEntity>(
|
|
143
|
+
export function buildOrderByExpression<T extends CollectionEntity>(
|
|
127
144
|
sortKey: keyof T | undefined,
|
|
128
145
|
sortDirection: "asc" | "desc" | null | undefined,
|
|
129
146
|
defaultSortKey: keyof T,
|
|
@@ -143,11 +160,25 @@ function buildOrderByExpression<T extends CollectionEntity>(
|
|
|
143
160
|
* Extract payload from MCP tool result (handles structuredContent wrapper)
|
|
144
161
|
*/
|
|
145
162
|
function extractPayload<T>(result: unknown): T {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
163
|
+
if (!result || typeof result !== "object") {
|
|
164
|
+
throw new Error("Invalid result");
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if ("isError" in result && result.isError) {
|
|
168
|
+
throw new Error(
|
|
169
|
+
"content" in result &&
|
|
170
|
+
Array.isArray(result.content) &&
|
|
171
|
+
result.content[0]?.type === "text"
|
|
172
|
+
? result.content[0].text
|
|
173
|
+
: "Unknown error",
|
|
174
|
+
);
|
|
149
175
|
}
|
|
150
|
-
|
|
176
|
+
|
|
177
|
+
if ("structuredContent" in result) {
|
|
178
|
+
return result.structuredContent as T;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
throw new Error("No structured content found");
|
|
151
182
|
}
|
|
152
183
|
|
|
153
184
|
/**
|
|
@@ -165,23 +196,26 @@ export function useCollectionItem<T extends CollectionEntity>(
|
|
|
165
196
|
itemId: string | undefined,
|
|
166
197
|
client: Client,
|
|
167
198
|
) {
|
|
168
|
-
void scopeKey; // Reserved for future use (e.g., cache scoping)
|
|
169
199
|
const upperName = collectionName.toUpperCase();
|
|
170
200
|
const getToolName = `COLLECTION_${upperName}_GET`;
|
|
171
201
|
|
|
172
202
|
const { data } = useSuspenseQuery({
|
|
173
|
-
queryKey: KEYS.
|
|
203
|
+
queryKey: KEYS.collectionItem(
|
|
204
|
+
client,
|
|
205
|
+
scopeKey,
|
|
206
|
+
"",
|
|
207
|
+
upperName,
|
|
208
|
+
itemId ?? "",
|
|
209
|
+
),
|
|
174
210
|
queryFn: async () => {
|
|
175
211
|
if (!itemId) {
|
|
176
|
-
return { item: null }
|
|
212
|
+
return { item: null } satisfies CollectionGetOutput<T>;
|
|
177
213
|
}
|
|
178
214
|
|
|
179
|
-
const result =
|
|
215
|
+
const result = await client.callTool({
|
|
180
216
|
name: getToolName,
|
|
181
|
-
arguments: {
|
|
182
|
-
|
|
183
|
-
} as CollectionGetInput,
|
|
184
|
-
})) as { structuredContent?: unknown };
|
|
217
|
+
arguments: { id: itemId } satisfies CollectionGetInput,
|
|
218
|
+
});
|
|
185
219
|
|
|
186
220
|
return extractPayload<CollectionGetOutput<T>>(result);
|
|
187
221
|
},
|
|
@@ -191,30 +225,38 @@ export function useCollectionItem<T extends CollectionEntity>(
|
|
|
191
225
|
return data?.item ?? null;
|
|
192
226
|
}
|
|
193
227
|
|
|
228
|
+
/** Fake MCP result for empty collection list when client is skipped */
|
|
229
|
+
export const EMPTY_COLLECTION_LIST_RESULT = {
|
|
230
|
+
structuredContent: {
|
|
231
|
+
items: [],
|
|
232
|
+
} satisfies CollectionListOutput<CollectionEntity>,
|
|
233
|
+
isError: false,
|
|
234
|
+
} as const;
|
|
235
|
+
|
|
194
236
|
/**
|
|
195
237
|
* Get a paginated list of items from a collection
|
|
196
238
|
*
|
|
197
239
|
* @param scopeKey - The scope key (connectionId for connection-scoped, virtualMcpId for virtual-mcp-scoped, etc.)
|
|
198
240
|
* @param collectionName - The name of the collection (e.g., "CONNECTIONS", "AGENT")
|
|
199
|
-
* @param client - The MCP client used to call collection tools
|
|
241
|
+
* @param client - The MCP client used to call collection tools (null/undefined returns [] without MCP call)
|
|
200
242
|
* @param options - Filter and configuration options
|
|
201
243
|
* @returns Suspense query result with items array
|
|
202
244
|
*/
|
|
203
245
|
export function useCollectionList<T extends CollectionEntity>(
|
|
204
246
|
scopeKey: string,
|
|
205
247
|
collectionName: string,
|
|
206
|
-
client: Client,
|
|
248
|
+
client: Client | null | undefined,
|
|
207
249
|
options: UseCollectionListOptions<T> = {},
|
|
208
250
|
) {
|
|
209
|
-
void scopeKey; // Reserved for future use (e.g., cache scoping)
|
|
210
251
|
const {
|
|
211
252
|
searchTerm,
|
|
212
253
|
filters,
|
|
213
254
|
sortKey,
|
|
214
255
|
sortDirection,
|
|
215
|
-
searchFields = ["title", "description"]
|
|
216
|
-
defaultSortKey = "updated_at"
|
|
256
|
+
searchFields = ["title", "description"] satisfies (keyof T)[],
|
|
257
|
+
defaultSortKey = "updated_at" satisfies keyof T,
|
|
217
258
|
pageSize = 100,
|
|
259
|
+
additionalToolArgs,
|
|
218
260
|
} = options;
|
|
219
261
|
|
|
220
262
|
const upperName = collectionName.toUpperCase();
|
|
@@ -227,19 +269,39 @@ export function useCollectionList<T extends CollectionEntity>(
|
|
|
227
269
|
defaultSortKey,
|
|
228
270
|
);
|
|
229
271
|
|
|
230
|
-
const toolArguments: CollectionListInput = {
|
|
272
|
+
const toolArguments: CollectionListInput & Record<string, unknown> = {
|
|
231
273
|
...(where && { where }),
|
|
232
274
|
...(orderBy && { orderBy }),
|
|
233
275
|
limit: pageSize,
|
|
234
276
|
offset: 0,
|
|
277
|
+
...additionalToolArgs,
|
|
235
278
|
};
|
|
236
279
|
|
|
237
|
-
const
|
|
280
|
+
const argsKey = JSON.stringify(toolArguments);
|
|
281
|
+
const queryKey = KEYS.collectionList(
|
|
238
282
|
client,
|
|
239
|
-
|
|
240
|
-
|
|
283
|
+
scopeKey,
|
|
284
|
+
"",
|
|
285
|
+
upperName,
|
|
286
|
+
argsKey,
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
const { data } = useSuspenseQuery({
|
|
290
|
+
queryKey,
|
|
291
|
+
queryFn: async () => {
|
|
292
|
+
if (!client) {
|
|
293
|
+
return EMPTY_COLLECTION_LIST_RESULT;
|
|
294
|
+
}
|
|
295
|
+
const result = await client.callTool({
|
|
296
|
+
name: listToolName,
|
|
297
|
+
arguments: toolArguments,
|
|
298
|
+
});
|
|
299
|
+
return result;
|
|
300
|
+
},
|
|
301
|
+
staleTime: 30_000,
|
|
302
|
+
retry: false,
|
|
241
303
|
select: (result) => {
|
|
242
|
-
const payload = extractPayload<CollectionListOutput<T>>(result);
|
|
304
|
+
const payload = extractPayload<CollectionListOutput<T>>(result ?? {});
|
|
243
305
|
return payload?.items ?? [];
|
|
244
306
|
},
|
|
245
307
|
});
|
|
@@ -247,6 +309,54 @@ export function useCollectionList<T extends CollectionEntity>(
|
|
|
247
309
|
return data;
|
|
248
310
|
}
|
|
249
311
|
|
|
312
|
+
/**
|
|
313
|
+
* Builds a query key for a collection list query
|
|
314
|
+
* Matches the internal logic of useCollectionList exactly
|
|
315
|
+
*
|
|
316
|
+
* @param client - The MCP client used to call collection tools (null/undefined is valid for skip queries)
|
|
317
|
+
* @param collectionName - The name of the collection (e.g., "THREAD_MESSAGES", "CONNECTIONS")
|
|
318
|
+
* @param scopeKey - The scope key (connectionId for connection-scoped, virtualMcpId for virtual-mcp-scoped, etc.)
|
|
319
|
+
* @param options - Filter and configuration options
|
|
320
|
+
* @returns Query key array
|
|
321
|
+
*/
|
|
322
|
+
export function buildCollectionQueryKey<T extends CollectionEntity>(
|
|
323
|
+
client: Client | null | undefined,
|
|
324
|
+
collectionName: string,
|
|
325
|
+
scopeKey: string,
|
|
326
|
+
options: UseCollectionListOptions<T> = {},
|
|
327
|
+
): CollectionQueryKey {
|
|
328
|
+
const {
|
|
329
|
+
searchTerm,
|
|
330
|
+
filters,
|
|
331
|
+
sortKey,
|
|
332
|
+
sortDirection,
|
|
333
|
+
searchFields = ["title", "description"] satisfies (keyof T)[],
|
|
334
|
+
defaultSortKey = "updated_at" satisfies keyof T,
|
|
335
|
+
pageSize = 100,
|
|
336
|
+
additionalToolArgs,
|
|
337
|
+
} = options;
|
|
338
|
+
|
|
339
|
+
const upperName = collectionName.toUpperCase();
|
|
340
|
+
|
|
341
|
+
const where = buildWhereExpression(searchTerm, filters, searchFields);
|
|
342
|
+
const orderBy = buildOrderByExpression(
|
|
343
|
+
sortKey,
|
|
344
|
+
sortDirection,
|
|
345
|
+
defaultSortKey,
|
|
346
|
+
);
|
|
347
|
+
|
|
348
|
+
const toolArguments: CollectionListInput & Record<string, unknown> = {
|
|
349
|
+
...(where && { where }),
|
|
350
|
+
...(orderBy && { orderBy }),
|
|
351
|
+
limit: pageSize,
|
|
352
|
+
offset: 0,
|
|
353
|
+
...additionalToolArgs,
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
const argsKey = JSON.stringify(toolArguments);
|
|
357
|
+
return KEYS.collectionList(client, scopeKey, "", upperName, argsKey);
|
|
358
|
+
}
|
|
359
|
+
|
|
250
360
|
/**
|
|
251
361
|
* Get mutation actions for create, update, and delete operations
|
|
252
362
|
*
|
|
@@ -260,36 +370,40 @@ export function useCollectionActions<T extends CollectionEntity>(
|
|
|
260
370
|
collectionName: string,
|
|
261
371
|
client: Client,
|
|
262
372
|
) {
|
|
263
|
-
void scopeKey; // Reserved for future use (e.g., cache scoping)
|
|
264
373
|
const queryClient = useQueryClient();
|
|
265
374
|
const upperName = collectionName.toUpperCase();
|
|
266
375
|
const createToolName = `COLLECTION_${upperName}_CREATE`;
|
|
267
376
|
const updateToolName = `COLLECTION_${upperName}_UPDATE`;
|
|
268
377
|
const deleteToolName = `COLLECTION_${upperName}_DELETE`;
|
|
269
378
|
|
|
270
|
-
// Invalidate all
|
|
379
|
+
// Invalidate all collection queries for this scope and collection
|
|
271
380
|
const invalidateCollection = () => {
|
|
272
381
|
queryClient.invalidateQueries({
|
|
273
382
|
predicate: (query) => {
|
|
274
383
|
const key = query.queryKey;
|
|
275
|
-
// Match
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
const toolName = key[4] as string;
|
|
280
|
-
return toolName?.startsWith(`COLLECTION_${upperName}_`);
|
|
384
|
+
// Match collectionList/collectionItem keys: [client, scopeKey, "", "collection", collectionName, ...]
|
|
385
|
+
return (
|
|
386
|
+
key[1] === scopeKey && key[3] === "collection" && key[4] === upperName
|
|
387
|
+
);
|
|
281
388
|
},
|
|
282
389
|
});
|
|
283
390
|
};
|
|
284
391
|
|
|
285
392
|
const create = useMutation({
|
|
286
393
|
mutationFn: async (data: Partial<T>) => {
|
|
287
|
-
const result =
|
|
394
|
+
const result = await client.callTool({
|
|
288
395
|
name: createToolName,
|
|
289
|
-
arguments: {
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
396
|
+
arguments: { data } satisfies CollectionInsertInput<T>,
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
if (result.isError) {
|
|
400
|
+
throw new Error(
|
|
401
|
+
Array.isArray(result.content)
|
|
402
|
+
? result.content[0]?.text
|
|
403
|
+
: String(result.content),
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
|
|
293
407
|
const payload = extractPayload<CollectionInsertOutput<T>>(result);
|
|
294
408
|
|
|
295
409
|
return payload.item;
|
|
@@ -306,13 +420,10 @@ export function useCollectionActions<T extends CollectionEntity>(
|
|
|
306
420
|
|
|
307
421
|
const update = useMutation({
|
|
308
422
|
mutationFn: async ({ id, data }: { id: string; data: Partial<T> }) => {
|
|
309
|
-
const result =
|
|
423
|
+
const result = await client.callTool({
|
|
310
424
|
name: updateToolName,
|
|
311
|
-
arguments: {
|
|
312
|
-
|
|
313
|
-
data,
|
|
314
|
-
} as CollectionUpdateInput<T>,
|
|
315
|
-
})) as { structuredContent?: unknown };
|
|
425
|
+
arguments: { id, data } satisfies CollectionUpdateInput<T>,
|
|
426
|
+
});
|
|
316
427
|
const payload = extractPayload<CollectionUpdateOutput<T>>(result);
|
|
317
428
|
|
|
318
429
|
return payload.item;
|
|
@@ -329,12 +440,10 @@ export function useCollectionActions<T extends CollectionEntity>(
|
|
|
329
440
|
|
|
330
441
|
const remove = useMutation({
|
|
331
442
|
mutationFn: async (id: string) => {
|
|
332
|
-
const result =
|
|
443
|
+
const result = await client.callTool({
|
|
333
444
|
name: deleteToolName,
|
|
334
|
-
arguments: {
|
|
335
|
-
|
|
336
|
-
} as CollectionDeleteInput,
|
|
337
|
-
})) as { structuredContent?: unknown };
|
|
445
|
+
arguments: { id } satisfies CollectionDeleteInput,
|
|
446
|
+
});
|
|
338
447
|
const payload = extractPayload<CollectionDeleteOutput<T>>(result);
|
|
339
448
|
|
|
340
449
|
return payload.item.id;
|
|
@@ -25,15 +25,50 @@ export type ConnectionFilter = CollectionFilter;
|
|
|
25
25
|
/**
|
|
26
26
|
* Options for useConnections hook
|
|
27
27
|
*/
|
|
28
|
-
export
|
|
28
|
+
export interface UseConnectionsOptions
|
|
29
|
+
extends UseCollectionListOptions<ConnectionEntity> {
|
|
30
|
+
/**
|
|
31
|
+
* Server-side binding filter. Only returns connections whose tools satisfy the binding.
|
|
32
|
+
* Can be a well-known binding name (e.g., "LLM", "ASSISTANTS", "OBJECT_STORAGE")
|
|
33
|
+
* or a custom binding schema object.
|
|
34
|
+
*/
|
|
35
|
+
binding?: string | Record<string, unknown> | Record<string, unknown>[];
|
|
36
|
+
/**
|
|
37
|
+
* Whether to include VIRTUAL connections in results. Defaults to false (server default).
|
|
38
|
+
*/
|
|
39
|
+
includeVirtual?: boolean;
|
|
40
|
+
}
|
|
29
41
|
|
|
30
42
|
/**
|
|
31
|
-
* Hook to get
|
|
43
|
+
* Hook to get connections with server-side filtering.
|
|
32
44
|
*
|
|
33
|
-
* @param options - Filter and configuration options
|
|
45
|
+
* @param options - Filter and configuration options (binding, search, etc.)
|
|
34
46
|
* @returns Suspense query result with connections as ConnectionEntity[]
|
|
35
47
|
*/
|
|
36
48
|
export function useConnections(options: UseConnectionsOptions = {}) {
|
|
49
|
+
const { binding, includeVirtual, ...collectionOptions } = options;
|
|
50
|
+
|
|
51
|
+
// Build additional tool args for the COLLECTION_CONNECTIONS_LIST tool
|
|
52
|
+
const additionalToolArgs: Record<string, unknown> = {
|
|
53
|
+
...collectionOptions.additionalToolArgs,
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
if (binding !== undefined) {
|
|
57
|
+
additionalToolArgs.binding = binding;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (includeVirtual !== undefined) {
|
|
61
|
+
additionalToolArgs.include_virtual = includeVirtual;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const finalOptions: UseCollectionListOptions<ConnectionEntity> = {
|
|
65
|
+
...collectionOptions,
|
|
66
|
+
additionalToolArgs:
|
|
67
|
+
Object.keys(additionalToolArgs).length > 0
|
|
68
|
+
? additionalToolArgs
|
|
69
|
+
: undefined,
|
|
70
|
+
};
|
|
71
|
+
|
|
37
72
|
const { org } = useProjectContext();
|
|
38
73
|
const client = useMCPClient({
|
|
39
74
|
connectionId: SELF_MCP_ALIAS_ID,
|
|
@@ -43,7 +78,7 @@ export function useConnections(options: UseConnectionsOptions = {}) {
|
|
|
43
78
|
org.id,
|
|
44
79
|
"CONNECTIONS",
|
|
45
80
|
client,
|
|
46
|
-
|
|
81
|
+
finalOptions,
|
|
47
82
|
);
|
|
48
83
|
}
|
|
49
84
|
|