@decocms/mesh-sdk 1.1.0

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.
@@ -0,0 +1,357 @@
1
+ /**
2
+ * Collection Hooks using React Query
3
+ *
4
+ * Provides React hooks for working with collection-binding-compliant tools.
5
+ * Uses TanStack React Query for caching, loading states, and mutations.
6
+ */
7
+
8
+ import {
9
+ type BaseCollectionEntity,
10
+ type CollectionDeleteInput,
11
+ type CollectionDeleteOutput,
12
+ type CollectionGetInput,
13
+ type CollectionGetOutput,
14
+ type CollectionInsertInput,
15
+ type CollectionInsertOutput,
16
+ type CollectionListInput,
17
+ type CollectionListOutput,
18
+ type CollectionUpdateInput,
19
+ type CollectionUpdateOutput,
20
+ type OrderByExpression,
21
+ type WhereExpression,
22
+ } from "@decocms/bindings/collections";
23
+ import {
24
+ useMutation,
25
+ useQueryClient,
26
+ useSuspenseQuery,
27
+ } from "@tanstack/react-query";
28
+ import { toast } from "sonner";
29
+ import type { Client } from "@modelcontextprotocol/sdk/client/index.js";
30
+ import { useMCPToolCall } from "./use-mcp-tools";
31
+ import { KEYS } from "../lib/query-keys";
32
+
33
+ /**
34
+ * Collection entity base type that matches the collection binding pattern
35
+ */
36
+ export type CollectionEntity = BaseCollectionEntity;
37
+
38
+ /**
39
+ * Filter definition for collection queries (matches @deco/ui Filter shape)
40
+ */
41
+ export interface CollectionFilter {
42
+ /** Field to filter on (must match an entity property) */
43
+ column: string;
44
+ /** Value to match */
45
+ value: string | boolean | number;
46
+ }
47
+
48
+ /**
49
+ * Options for useCollectionList hook
50
+ */
51
+ export interface UseCollectionListOptions<T extends CollectionEntity> {
52
+ /** Text search term (searches configured searchable fields) */
53
+ searchTerm?: string;
54
+ /** Field filters */
55
+ filters?: CollectionFilter[];
56
+ /** Sort key (field to sort by) */
57
+ sortKey?: keyof T;
58
+ /** Sort direction */
59
+ sortDirection?: "asc" | "desc" | null;
60
+ /** Fields to search when searchTerm is provided (default: ["title", "description"]) */
61
+ searchFields?: (keyof T)[];
62
+ /** Default sort key when none provided */
63
+ defaultSortKey?: keyof T;
64
+ /** Page size for pagination (default: 100) */
65
+ pageSize?: number;
66
+ }
67
+
68
+ /**
69
+ * Build a where expression from search term and filters
70
+ */
71
+ function buildWhereExpression<T extends CollectionEntity>(
72
+ searchTerm: string | undefined,
73
+ filters: CollectionFilter[] | undefined,
74
+ searchFields: (keyof T)[],
75
+ ): WhereExpression | undefined {
76
+ const conditions: WhereExpression[] = [];
77
+
78
+ // Add search conditions (OR)
79
+ if (searchTerm?.trim()) {
80
+ const trimmedSearchTerm = searchTerm.trim();
81
+ const searchConditions = searchFields.map((field) => ({
82
+ field: [String(field)],
83
+ operator: "contains" as const,
84
+ value: trimmedSearchTerm,
85
+ }));
86
+
87
+ if (searchConditions.length === 1 && searchConditions[0]) {
88
+ conditions.push(searchConditions[0]);
89
+ } else if (searchConditions.length > 1) {
90
+ conditions.push({
91
+ operator: "or",
92
+ conditions: searchConditions,
93
+ });
94
+ }
95
+ }
96
+
97
+ // Add filter conditions (AND)
98
+ if (filters && filters.length > 0) {
99
+ for (const filter of filters) {
100
+ conditions.push({
101
+ field: [filter.column],
102
+ operator: "eq" as const,
103
+ value: filter.value,
104
+ });
105
+ }
106
+ }
107
+
108
+ if (conditions.length === 0) {
109
+ return undefined;
110
+ }
111
+
112
+ if (conditions.length === 1) {
113
+ return conditions[0];
114
+ }
115
+
116
+ // Combine all conditions with AND
117
+ return {
118
+ operator: "and",
119
+ conditions,
120
+ };
121
+ }
122
+
123
+ /**
124
+ * Build orderBy expression from sort key and direction
125
+ */
126
+ function buildOrderByExpression<T extends CollectionEntity>(
127
+ sortKey: keyof T | undefined,
128
+ sortDirection: "asc" | "desc" | null | undefined,
129
+ defaultSortKey: keyof T,
130
+ ): OrderByExpression[] | undefined {
131
+ const key = sortKey ?? defaultSortKey;
132
+ const direction = sortDirection ?? "asc";
133
+
134
+ return [
135
+ {
136
+ field: [String(key)],
137
+ direction,
138
+ },
139
+ ];
140
+ }
141
+
142
+ /**
143
+ * Extract payload from MCP tool result (handles structuredContent wrapper)
144
+ */
145
+ function extractPayload<T>(result: unknown): T {
146
+ const r = result as { structuredContent?: T } | T;
147
+ if (r && typeof r === "object" && "structuredContent" in r) {
148
+ return r.structuredContent as T;
149
+ }
150
+ return r as T;
151
+ }
152
+
153
+ /**
154
+ * Get a single item by ID from a collection
155
+ *
156
+ * @param scopeKey - The scope key (connectionId for connection-scoped, virtualMcpId for virtual-mcp-scoped, etc.)
157
+ * @param collectionName - The name of the collection (e.g., "CONNECTIONS", "AGENT")
158
+ * @param itemId - The ID of the item to fetch (undefined returns null without making an API call)
159
+ * @param client - The MCP client used to call collection tools
160
+ * @returns Suspense query result with the item, or null if itemId is undefined
161
+ */
162
+ export function useCollectionItem<T extends CollectionEntity>(
163
+ scopeKey: string,
164
+ collectionName: string,
165
+ itemId: string | undefined,
166
+ client: Client,
167
+ ) {
168
+ void scopeKey; // Reserved for future use (e.g., cache scoping)
169
+ const upperName = collectionName.toUpperCase();
170
+ const getToolName = `COLLECTION_${upperName}_GET`;
171
+
172
+ const { data } = useSuspenseQuery({
173
+ queryKey: KEYS.mcpToolCall(client, getToolName, itemId ?? ""),
174
+ queryFn: async () => {
175
+ if (!itemId) {
176
+ return { item: null } as CollectionGetOutput<T>;
177
+ }
178
+
179
+ const result = (await client.callTool({
180
+ name: getToolName,
181
+ arguments: {
182
+ id: itemId,
183
+ } as CollectionGetInput,
184
+ })) as { structuredContent?: unknown };
185
+
186
+ return extractPayload<CollectionGetOutput<T>>(result);
187
+ },
188
+ staleTime: 60_000,
189
+ });
190
+
191
+ return data?.item ?? null;
192
+ }
193
+
194
+ /**
195
+ * Get a paginated list of items from a collection
196
+ *
197
+ * @param scopeKey - The scope key (connectionId for connection-scoped, virtualMcpId for virtual-mcp-scoped, etc.)
198
+ * @param collectionName - The name of the collection (e.g., "CONNECTIONS", "AGENT")
199
+ * @param client - The MCP client used to call collection tools
200
+ * @param options - Filter and configuration options
201
+ * @returns Suspense query result with items array
202
+ */
203
+ export function useCollectionList<T extends CollectionEntity>(
204
+ scopeKey: string,
205
+ collectionName: string,
206
+ client: Client,
207
+ options: UseCollectionListOptions<T> = {},
208
+ ) {
209
+ void scopeKey; // Reserved for future use (e.g., cache scoping)
210
+ const {
211
+ searchTerm,
212
+ filters,
213
+ sortKey,
214
+ sortDirection,
215
+ searchFields = ["title", "description"] as (keyof T)[],
216
+ defaultSortKey = "updated_at" as keyof T,
217
+ pageSize = 100,
218
+ } = options;
219
+
220
+ const upperName = collectionName.toUpperCase();
221
+ const listToolName = `COLLECTION_${upperName}_LIST`;
222
+
223
+ const where = buildWhereExpression(searchTerm, filters, searchFields);
224
+ const orderBy = buildOrderByExpression(
225
+ sortKey,
226
+ sortDirection,
227
+ defaultSortKey,
228
+ );
229
+
230
+ const toolArguments: CollectionListInput = {
231
+ ...(where && { where }),
232
+ ...(orderBy && { orderBy }),
233
+ limit: pageSize,
234
+ offset: 0,
235
+ };
236
+
237
+ const { data } = useMCPToolCall({
238
+ client,
239
+ toolName: listToolName,
240
+ toolArguments,
241
+ select: (result) => {
242
+ const payload = extractPayload<CollectionListOutput<T>>(result);
243
+ return payload?.items ?? [];
244
+ },
245
+ });
246
+
247
+ return data;
248
+ }
249
+
250
+ /**
251
+ * Get mutation actions for create, update, and delete operations
252
+ *
253
+ * @param scopeKey - The scope key (connectionId for connection-scoped, virtualMcpId for virtual-mcp-scoped, etc.)
254
+ * @param collectionName - The name of the collection (e.g., "CONNECTIONS", "AGENT")
255
+ * @param client - The MCP client used to call collection tools
256
+ * @returns Object with create, update, and delete mutation hooks
257
+ */
258
+ export function useCollectionActions<T extends CollectionEntity>(
259
+ scopeKey: string,
260
+ collectionName: string,
261
+ client: Client,
262
+ ) {
263
+ void scopeKey; // Reserved for future use (e.g., cache scoping)
264
+ const queryClient = useQueryClient();
265
+ const upperName = collectionName.toUpperCase();
266
+ const createToolName = `COLLECTION_${upperName}_CREATE`;
267
+ const updateToolName = `COLLECTION_${upperName}_UPDATE`;
268
+ const deleteToolName = `COLLECTION_${upperName}_DELETE`;
269
+
270
+ // Invalidate all tool call queries for this collection
271
+ const invalidateCollection = () => {
272
+ queryClient.invalidateQueries({
273
+ predicate: (query) => {
274
+ const key = query.queryKey;
275
+ // Match mcpToolCall keys: ["mcp", "client", client, "tool-call", toolName, argsKey]
276
+ if (key[0] !== "mcp" || key[1] !== "client" || key[3] !== "tool-call") {
277
+ return false;
278
+ }
279
+ const toolName = key[4] as string;
280
+ return toolName?.startsWith(`COLLECTION_${upperName}_`);
281
+ },
282
+ });
283
+ };
284
+
285
+ const create = useMutation({
286
+ mutationFn: async (data: Partial<T>) => {
287
+ const result = (await client.callTool({
288
+ name: createToolName,
289
+ arguments: {
290
+ data,
291
+ } as CollectionInsertInput<T>,
292
+ })) as { structuredContent?: unknown };
293
+ const payload = extractPayload<CollectionInsertOutput<T>>(result);
294
+
295
+ return payload.item;
296
+ },
297
+ onSuccess: () => {
298
+ invalidateCollection();
299
+ toast.success("Item created successfully");
300
+ },
301
+ onError: (error: unknown) => {
302
+ const message = error instanceof Error ? error.message : String(error);
303
+ toast.error(`Failed to create item: ${message}`);
304
+ },
305
+ });
306
+
307
+ const update = useMutation({
308
+ mutationFn: async ({ id, data }: { id: string; data: Partial<T> }) => {
309
+ const result = (await client.callTool({
310
+ name: updateToolName,
311
+ arguments: {
312
+ id,
313
+ data,
314
+ } as CollectionUpdateInput<T>,
315
+ })) as { structuredContent?: unknown };
316
+ const payload = extractPayload<CollectionUpdateOutput<T>>(result);
317
+
318
+ return payload.item;
319
+ },
320
+ onSuccess: () => {
321
+ invalidateCollection();
322
+ toast.success("Item updated successfully");
323
+ },
324
+ onError: (error: unknown) => {
325
+ const message = error instanceof Error ? error.message : String(error);
326
+ toast.error(`Failed to update item: ${message}`);
327
+ },
328
+ });
329
+
330
+ const remove = useMutation({
331
+ mutationFn: async (id: string) => {
332
+ const result = (await client.callTool({
333
+ name: deleteToolName,
334
+ arguments: {
335
+ id,
336
+ } as CollectionDeleteInput,
337
+ })) as { structuredContent?: unknown };
338
+ const payload = extractPayload<CollectionDeleteOutput<T>>(result);
339
+
340
+ return payload.item.id;
341
+ },
342
+ onSuccess: () => {
343
+ invalidateCollection();
344
+ toast.success("Item deleted successfully");
345
+ },
346
+ onError: (error: unknown) => {
347
+ const message = error instanceof Error ? error.message : String(error);
348
+ toast.error(`Failed to delete item: ${message}`);
349
+ },
350
+ });
351
+
352
+ return {
353
+ create,
354
+ update,
355
+ delete: remove,
356
+ };
357
+ }
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Connection Collection Hooks
3
+ *
4
+ * Provides React hooks for working with connections using React Query.
5
+ * These hooks offer a reactive interface for accessing and manipulating connections.
6
+ */
7
+
8
+ import type { ConnectionEntity } from "../types/connection";
9
+ import { useProjectContext } from "../context/project-context";
10
+ import {
11
+ type CollectionFilter,
12
+ useCollectionActions,
13
+ useCollectionItem,
14
+ useCollectionList,
15
+ type UseCollectionListOptions,
16
+ } from "./use-collections";
17
+ import { useMCPClient } from "./use-mcp-client";
18
+ import { SELF_MCP_ALIAS_ID } from "../lib/constants";
19
+
20
+ /**
21
+ * Filter definition for connections (matches @deco/ui Filter shape)
22
+ */
23
+ export type ConnectionFilter = CollectionFilter;
24
+
25
+ /**
26
+ * Options for useConnections hook
27
+ */
28
+ export type UseConnectionsOptions = UseCollectionListOptions<ConnectionEntity>;
29
+
30
+ /**
31
+ * Hook to get all connections
32
+ *
33
+ * @param options - Filter and configuration options
34
+ * @returns Suspense query result with connections as ConnectionEntity[]
35
+ */
36
+ export function useConnections(options: UseConnectionsOptions = {}) {
37
+ const { org } = useProjectContext();
38
+ const client = useMCPClient({
39
+ connectionId: SELF_MCP_ALIAS_ID,
40
+ orgId: org.id,
41
+ });
42
+ return useCollectionList<ConnectionEntity>(
43
+ org.id,
44
+ "CONNECTIONS",
45
+ client,
46
+ options,
47
+ );
48
+ }
49
+
50
+ /**
51
+ * Hook to get a single connection by ID
52
+ *
53
+ * @param connectionId - The ID of the connection to fetch (undefined returns null without making an API call)
54
+ * @returns Suspense query result with the connection as ConnectionEntity | null
55
+ */
56
+ export function useConnection(connectionId: string | undefined) {
57
+ const { org } = useProjectContext();
58
+ const client = useMCPClient({
59
+ connectionId: SELF_MCP_ALIAS_ID,
60
+ orgId: org.id,
61
+ });
62
+ return useCollectionItem<ConnectionEntity>(
63
+ org.id,
64
+ "CONNECTIONS",
65
+ connectionId,
66
+ client,
67
+ );
68
+ }
69
+
70
+ /**
71
+ * Hook to get connection mutation actions (create, update, delete)
72
+ *
73
+ * @returns Object with create, update, and delete mutation hooks
74
+ */
75
+ export function useConnectionActions() {
76
+ const { org } = useProjectContext();
77
+ const client = useMCPClient({
78
+ connectionId: SELF_MCP_ALIAS_ID,
79
+ orgId: org.id,
80
+ });
81
+ return useCollectionActions<ConnectionEntity>(org.id, "CONNECTIONS", client);
82
+ }
@@ -0,0 +1,127 @@
1
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2
+ import { useSuspenseQuery } from "@tanstack/react-query";
3
+ import { KEYS } from "../lib/query-keys";
4
+ import { StreamableHTTPClientTransport } from "../lib/streamable-http-client-transport";
5
+
6
+ const DEFAULT_CLIENT_INFO = {
7
+ name: "mesh-sdk",
8
+ version: "1.0.0",
9
+ };
10
+
11
+ export interface CreateMcpClientOptions {
12
+ /** Connection ID - use SELF_MCP_ALIAS_ID for the self/management MCP (ALL_TOOLS), or any connectionId for other MCPs */
13
+ connectionId: string | null;
14
+ /** Organization ID - required, transforms to x-org-id header */
15
+ orgId: string;
16
+ /** Authorization token - optional */
17
+ token?: string | null;
18
+ /** Mesh server URL - optional, defaults to window.location.origin (for external apps, provide your Mesh server URL) */
19
+ meshUrl?: string;
20
+ }
21
+
22
+ export type UseMcpClientOptions = CreateMcpClientOptions;
23
+
24
+ /**
25
+ * Build the MCP URL from connectionId and optional meshUrl
26
+ * Uses /mcp/:connectionId for all servers
27
+ */
28
+ function buildMcpUrl(connectionId: string | null, meshUrl?: string): string {
29
+ const baseUrl =
30
+ meshUrl ??
31
+ (typeof window !== "undefined" ? window.location.origin : undefined);
32
+ if (!baseUrl) {
33
+ throw new Error(
34
+ "MCP client requires either meshUrl option or a browser environment.",
35
+ );
36
+ }
37
+
38
+ const path = connectionId ? `/mcp/${connectionId}` : "/mcp";
39
+ return new URL(path, baseUrl).href;
40
+ }
41
+
42
+ /**
43
+ * Create and connect an MCP client with Streamable HTTP transport.
44
+ * This is the low-level function for creating clients outside of React hooks.
45
+ *
46
+ * @param options - Configuration for the MCP client
47
+ * @returns Promise resolving to the connected MCP client
48
+ */
49
+ export async function createMCPClient({
50
+ connectionId,
51
+ orgId,
52
+ token,
53
+ meshUrl,
54
+ }: CreateMcpClientOptions): Promise<Client> {
55
+ const url = buildMcpUrl(connectionId, meshUrl);
56
+
57
+ const client = new Client(DEFAULT_CLIENT_INFO, {
58
+ capabilities: {
59
+ tasks: {
60
+ list: {},
61
+ cancel: {},
62
+ requests: {
63
+ tool: {
64
+ call: {},
65
+ },
66
+ },
67
+ },
68
+ },
69
+ });
70
+
71
+ const transport = new StreamableHTTPClientTransport(new URL(url), {
72
+ requestInit: {
73
+ headers: {
74
+ "Content-Type": "application/json",
75
+ Accept: "application/json, text/event-stream",
76
+ "x-org-id": orgId,
77
+ ...(token ? { Authorization: `Bearer ${token}` } : {}),
78
+ },
79
+ },
80
+ });
81
+
82
+ await client.connect(transport);
83
+
84
+ // Add toJSON method for query key serialization
85
+ // This allows the client to be used directly in query keys
86
+ const queryKey = KEYS.mcpClient(
87
+ orgId,
88
+ connectionId ?? "self",
89
+ token ?? "",
90
+ meshUrl ?? "",
91
+ );
92
+ (client as Client & { toJSON: () => string }).toJSON = () =>
93
+ `mcp-client:${queryKey.join(":")}`;
94
+
95
+ return client;
96
+ }
97
+
98
+ /**
99
+ * Hook to create and manage an MCP client with Streamable HTTP transport.
100
+ * Uses Suspense - must be used within a Suspense boundary.
101
+ *
102
+ * @param options - Configuration for the MCP client
103
+ * @returns The MCP client instance (never null - suspends until ready)
104
+ */
105
+ export function useMCPClient({
106
+ connectionId,
107
+ orgId,
108
+ token,
109
+ meshUrl,
110
+ }: UseMcpClientOptions): Client {
111
+ const queryKey = KEYS.mcpClient(
112
+ orgId,
113
+ connectionId ?? "",
114
+ token ?? "",
115
+ meshUrl ?? "",
116
+ );
117
+
118
+ const { data: client } = useSuspenseQuery({
119
+ queryKey,
120
+ queryFn: () => createMCPClient({ connectionId, orgId, token, meshUrl }),
121
+ staleTime: Infinity, // Keep client alive while query is active
122
+ gcTime: 0, // Clean up immediately when query is inactive
123
+ });
124
+
125
+ // useSuspenseQuery guarantees data is available (suspends until ready)
126
+ return client!;
127
+ }
@@ -0,0 +1,126 @@
1
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2
+ import {
3
+ useQuery,
4
+ UseQueryResult,
5
+ useSuspenseQuery,
6
+ UseSuspenseQueryResult,
7
+ type UseQueryOptions,
8
+ type UseSuspenseQueryOptions,
9
+ } from "@tanstack/react-query";
10
+ import type {
11
+ GetPromptRequest,
12
+ GetPromptResult,
13
+ ListPromptsResult,
14
+ } from "@modelcontextprotocol/sdk/types.js";
15
+ import { KEYS } from "../lib/query-keys";
16
+
17
+ /**
18
+ * List prompts from an MCP client.
19
+ * This is the raw async function that can be used outside of React hooks.
20
+ */
21
+ export async function listPrompts(client: Client): Promise<ListPromptsResult> {
22
+ const capabilities = client.getServerCapabilities();
23
+ if (!capabilities?.prompts) {
24
+ return { prompts: [] };
25
+ }
26
+ return await client.listPrompts();
27
+ }
28
+
29
+ /**
30
+ * Get a specific prompt from an MCP client.
31
+ * This is the raw async function that can be used outside of React hooks.
32
+ */
33
+ export async function getPrompt(
34
+ client: Client,
35
+ name: string,
36
+ args?: GetPromptRequest["params"]["arguments"],
37
+ ): Promise<GetPromptResult> {
38
+ const capabilities = client.getServerCapabilities();
39
+ if (!capabilities?.prompts) {
40
+ throw new Error("Prompts capability not supported");
41
+ }
42
+ return await client.getPrompt({ name, arguments: args ?? {} });
43
+ }
44
+
45
+ export interface UseMcpPromptsListOptions
46
+ extends Omit<
47
+ UseSuspenseQueryOptions<ListPromptsResult, Error>,
48
+ "queryKey" | "queryFn"
49
+ > {
50
+ /** The MCP client from useMCPClient */
51
+ client: Client;
52
+ }
53
+
54
+ /**
55
+ * Suspense hook to list prompts from an MCP client.
56
+ * Must be used within a Suspense boundary.
57
+ */
58
+ export function useMCPPromptsList({
59
+ client,
60
+ ...queryOptions
61
+ }: UseMcpPromptsListOptions): UseSuspenseQueryResult<ListPromptsResult, Error> {
62
+ return useSuspenseQuery<ListPromptsResult, Error>({
63
+ ...queryOptions,
64
+ queryKey: KEYS.mcpPromptsList(client),
65
+ queryFn: () => listPrompts(client),
66
+ staleTime: queryOptions.staleTime ?? 30000,
67
+ retry: false,
68
+ });
69
+ }
70
+
71
+ export interface UseMcpPromptsListQueryOptions
72
+ extends Omit<
73
+ UseQueryOptions<ListPromptsResult, Error>,
74
+ "queryKey" | "queryFn"
75
+ > {
76
+ /** The MCP client from useMCPClient */
77
+ client: Client;
78
+ }
79
+
80
+ /**
81
+ * Non-suspense hook to list prompts from an MCP client.
82
+ */
83
+ export function useMCPPromptsListQuery({
84
+ client,
85
+ ...queryOptions
86
+ }: UseMcpPromptsListQueryOptions): UseQueryResult<ListPromptsResult, Error> {
87
+ return useQuery<ListPromptsResult, Error>({
88
+ ...queryOptions,
89
+ queryKey: KEYS.mcpPromptsList(client),
90
+ queryFn: () => listPrompts(client),
91
+ staleTime: queryOptions.staleTime ?? 30000,
92
+ retry: false,
93
+ });
94
+ }
95
+
96
+ export interface UseMcpGetPromptOptions
97
+ extends Omit<
98
+ UseSuspenseQueryOptions<GetPromptResult, Error>,
99
+ "queryKey" | "queryFn"
100
+ > {
101
+ /** The MCP client from useMCPClient */
102
+ client: Client;
103
+ /** Prompt name */
104
+ name: string;
105
+ /** Optional prompt arguments */
106
+ arguments?: GetPromptRequest["params"]["arguments"];
107
+ }
108
+
109
+ /**
110
+ * Suspense hook to get a specific prompt from an MCP client.
111
+ * Must be used within a Suspense boundary.
112
+ */
113
+ export function useMCPGetPrompt({
114
+ client,
115
+ name,
116
+ arguments: args,
117
+ ...queryOptions
118
+ }: UseMcpGetPromptOptions): UseSuspenseQueryResult<GetPromptResult, Error> {
119
+ return useSuspenseQuery<GetPromptResult, Error>({
120
+ ...queryOptions,
121
+ queryKey: KEYS.mcpGetPrompt(client, name, JSON.stringify(args ?? {})),
122
+ queryFn: () => getPrompt(client, name, args),
123
+ staleTime: queryOptions.staleTime ?? 30000,
124
+ retry: false,
125
+ });
126
+ }