@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
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
import type { JSONRPCMessage } from "@modelcontextprotocol/sdk/types.js";
|
|
2
|
+
import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Bridge MCP Transport
|
|
6
|
+
*
|
|
7
|
+
* High-performance bridge transport for MCP communication within the same process.
|
|
8
|
+
* Uses direct callbacks with microtask scheduling to avoid serialization overhead
|
|
9
|
+
* and minimize event loop impact.
|
|
10
|
+
*
|
|
11
|
+
* ## Design
|
|
12
|
+
*
|
|
13
|
+
* - **Zero serialization**: Messages are passed as JavaScript objects by reference
|
|
14
|
+
* - **Microtask scheduling**: Uses `queueMicrotask` to avoid deep recursion while
|
|
15
|
+
* maintaining message ordering
|
|
16
|
+
* - **Direct callbacks**: No Web API overhead (EventTarget, MessageChannel, etc.)
|
|
17
|
+
*
|
|
18
|
+
* ## Usage
|
|
19
|
+
*
|
|
20
|
+
* ```ts
|
|
21
|
+
* import { createBridgeTransportPair } from "@decocms/mesh-sdk";
|
|
22
|
+
* import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
23
|
+
* import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
24
|
+
*
|
|
25
|
+
* const { client: clientTransport, server: serverTransport } =
|
|
26
|
+
* createBridgeTransportPair();
|
|
27
|
+
*
|
|
28
|
+
* const client = new Client({ name: "test-client", version: "1.0.0" });
|
|
29
|
+
* const server = new Server({ name: "test-server", version: "1.0.0" });
|
|
30
|
+
*
|
|
31
|
+
* await server.connect(serverTransport);
|
|
32
|
+
* await client.connect(clientTransport);
|
|
33
|
+
*
|
|
34
|
+
* // Now client and server can communicate via bridge
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
type TransportSide = "client" | "server";
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Maximum number of messages that can be queued before throwing an error.
|
|
42
|
+
* This prevents unbounded memory growth if one side sends faster than the other processes.
|
|
43
|
+
*/
|
|
44
|
+
const MAX_QUEUE_SIZE = 10_000;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Internal channel that manages bidirectional message queues between client and server.
|
|
48
|
+
*/
|
|
49
|
+
class BridgeChannel {
|
|
50
|
+
private clientQueue: JSONRPCMessage[] = [];
|
|
51
|
+
private serverQueue: JSONRPCMessage[] = [];
|
|
52
|
+
private clientClosed = false;
|
|
53
|
+
private serverClosed = false;
|
|
54
|
+
private clientFlushScheduled = false;
|
|
55
|
+
private serverFlushScheduled = false;
|
|
56
|
+
|
|
57
|
+
// Use type-only forward reference to avoid circular dependency
|
|
58
|
+
private clientTransport?: { deliverMessage(message: JSONRPCMessage): void };
|
|
59
|
+
private serverTransport?: { deliverMessage(message: JSONRPCMessage): void };
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Register transports with the channel and link them to each other.
|
|
63
|
+
* This sets up both message delivery and close notifications.
|
|
64
|
+
*/
|
|
65
|
+
registerTransports(
|
|
66
|
+
client: BridgeClientTransport,
|
|
67
|
+
server: BridgeServerTransport,
|
|
68
|
+
): void {
|
|
69
|
+
this.clientTransport = client;
|
|
70
|
+
this.serverTransport = server;
|
|
71
|
+
// Link transports to each other for close notifications
|
|
72
|
+
client.setOppositeTransport(server);
|
|
73
|
+
server.setOppositeTransport(client);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Get the queue for a specific side (opposite of sender)
|
|
78
|
+
*/
|
|
79
|
+
private getQueue(side: TransportSide): JSONRPCMessage[] {
|
|
80
|
+
return side === "client" ? this.clientQueue : this.serverQueue;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Check if the target side is closed
|
|
85
|
+
*/
|
|
86
|
+
isClosed(side: TransportSide): boolean {
|
|
87
|
+
return side === "client" ? this.clientClosed : this.serverClosed;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Mark a side as closed
|
|
92
|
+
*/
|
|
93
|
+
close(side: TransportSide): void {
|
|
94
|
+
if (side === "client") {
|
|
95
|
+
this.clientClosed = true;
|
|
96
|
+
this.clientQueue = [];
|
|
97
|
+
} else {
|
|
98
|
+
this.serverClosed = true;
|
|
99
|
+
this.serverQueue = [];
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Enqueue a message to the target side's queue
|
|
105
|
+
* @throws Error if queue size exceeds MAX_QUEUE_SIZE
|
|
106
|
+
*/
|
|
107
|
+
enqueue(side: TransportSide, message: JSONRPCMessage): void {
|
|
108
|
+
if (this.isClosed(side)) {
|
|
109
|
+
// Silent no-op when target is closed (matches stdio transport behavior)
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const queue = this.getQueue(side);
|
|
114
|
+
|
|
115
|
+
// Prevent unbounded memory growth
|
|
116
|
+
if (queue.length >= MAX_QUEUE_SIZE) {
|
|
117
|
+
throw new Error(
|
|
118
|
+
`BridgeTransport: ${side} queue overflow (max ${MAX_QUEUE_SIZE} messages). ` +
|
|
119
|
+
"The receiver may not be processing messages fast enough.",
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
queue.push(message);
|
|
124
|
+
|
|
125
|
+
// Schedule flush if not already scheduled
|
|
126
|
+
if (side === "client" && !this.clientFlushScheduled) {
|
|
127
|
+
this.scheduleFlush("client");
|
|
128
|
+
} else if (side === "server" && !this.serverFlushScheduled) {
|
|
129
|
+
this.scheduleFlush("server");
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Schedule a flush operation for the given side using microtask scheduling.
|
|
135
|
+
* This frees the event loop and prevents deep recursion.
|
|
136
|
+
*/
|
|
137
|
+
private scheduleFlush(side: TransportSide): void {
|
|
138
|
+
if (side === "client") {
|
|
139
|
+
this.clientFlushScheduled = true;
|
|
140
|
+
} else {
|
|
141
|
+
this.serverFlushScheduled = true;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
queueMicrotask(() => {
|
|
145
|
+
this.flush(side);
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Flush all messages from the queue for a specific side
|
|
151
|
+
* and deliver them to the appropriate transport
|
|
152
|
+
*/
|
|
153
|
+
flush(side: TransportSide): void {
|
|
154
|
+
const queue = this.getQueue(side);
|
|
155
|
+
|
|
156
|
+
// Reset scheduled flag
|
|
157
|
+
if (side === "client") {
|
|
158
|
+
this.clientFlushScheduled = false;
|
|
159
|
+
} else {
|
|
160
|
+
this.serverFlushScheduled = false;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// If closed, clear queue and return
|
|
164
|
+
if (this.isClosed(side)) {
|
|
165
|
+
queue.length = 0;
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Get the transport for this side
|
|
170
|
+
const transport =
|
|
171
|
+
side === "client" ? this.clientTransport : this.serverTransport;
|
|
172
|
+
|
|
173
|
+
if (!transport) {
|
|
174
|
+
// Transport not registered yet, messages will be processed when it starts
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Drain queue in FIFO order and deliver to transport
|
|
179
|
+
// Continue draining even if transport isn't ready yet - deliverMessage will check
|
|
180
|
+
while (queue.length > 0) {
|
|
181
|
+
const message = queue.shift()!;
|
|
182
|
+
transport.deliverMessage(message);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Close both sides of the channel
|
|
188
|
+
*/
|
|
189
|
+
closeBoth(): void {
|
|
190
|
+
this.close("client");
|
|
191
|
+
this.close("server");
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Base transport implementation shared by client and server transports
|
|
197
|
+
*/
|
|
198
|
+
abstract class BaseBridgeTransport implements Transport {
|
|
199
|
+
protected channel: BridgeChannel;
|
|
200
|
+
protected side: TransportSide;
|
|
201
|
+
protected started = false;
|
|
202
|
+
protected closed = false;
|
|
203
|
+
private _onmessage?: (message: JSONRPCMessage) => void;
|
|
204
|
+
private _onerror?: (error: Error) => void;
|
|
205
|
+
private _onclose?: () => void;
|
|
206
|
+
|
|
207
|
+
constructor(channel: BridgeChannel, side: TransportSide) {
|
|
208
|
+
this.channel = channel;
|
|
209
|
+
this.side = side;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
get onmessage(): ((message: JSONRPCMessage) => void) | undefined {
|
|
213
|
+
return this._onmessage;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
set onmessage(fn: ((message: JSONRPCMessage) => void) | undefined) {
|
|
217
|
+
this._onmessage = fn;
|
|
218
|
+
// If transport is started and onmessage is set, flush any queued messages
|
|
219
|
+
if (fn && this.started && !this.closed) {
|
|
220
|
+
this.channel.flush(this.side);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
get onerror(): ((error: Error) => void) | undefined {
|
|
225
|
+
return this._onerror;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
set onerror(fn: ((error: Error) => void) | undefined) {
|
|
229
|
+
this._onerror = fn;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
get onclose(): (() => void) | undefined {
|
|
233
|
+
return this._onclose;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
set onclose(fn: (() => void) | undefined) {
|
|
237
|
+
this._onclose = fn;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Start the transport. For bridge transports, this is a no-op
|
|
242
|
+
* but required by the Transport interface.
|
|
243
|
+
*/
|
|
244
|
+
async start(): Promise<void> {
|
|
245
|
+
if (this.started) {
|
|
246
|
+
throw new Error(
|
|
247
|
+
`${this.side === "client" ? "BridgeClientTransport" : "BridgeServerTransport"} already started! If using Client/Server class, note that connect() calls start() automatically.`,
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
this.started = true;
|
|
251
|
+
// Process any messages that were queued before start
|
|
252
|
+
// If onmessage is already set, flush immediately; otherwise it will flush when onmessage is set
|
|
253
|
+
if (this._onmessage && !this.closed) {
|
|
254
|
+
this.channel.flush(this.side);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Send a message to the opposite side
|
|
260
|
+
*/
|
|
261
|
+
async send(message: JSONRPCMessage): Promise<void> {
|
|
262
|
+
if (this.closed) {
|
|
263
|
+
// Silent no-op when transport is closed (matches stdio transport behavior)
|
|
264
|
+
return Promise.resolve();
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const targetSide: TransportSide =
|
|
268
|
+
this.side === "client" ? "server" : "client";
|
|
269
|
+
this.channel.enqueue(targetSide, message);
|
|
270
|
+
|
|
271
|
+
// Resolve immediately - message delivery is async via microtask
|
|
272
|
+
return Promise.resolve();
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Close the transport
|
|
277
|
+
*/
|
|
278
|
+
async close(): Promise<void> {
|
|
279
|
+
if (!this.started || this.closed) {
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
this.closed = true;
|
|
284
|
+
this.channel.close(this.side);
|
|
285
|
+
|
|
286
|
+
// Notify opposite side that we closed
|
|
287
|
+
const oppositeTransport = this.getOppositeTransport();
|
|
288
|
+
if (oppositeTransport && !oppositeTransport.closed) {
|
|
289
|
+
oppositeTransport._onclose?.();
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
this._onclose?.();
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Get reference to the opposite transport (set by factory)
|
|
297
|
+
*/
|
|
298
|
+
protected abstract getOppositeTransport(): BaseBridgeTransport | undefined;
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Set reference to opposite transport (called by factory)
|
|
302
|
+
*/
|
|
303
|
+
abstract setOppositeTransport(transport: BaseBridgeTransport): void;
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Internal method to deliver a message to this transport
|
|
307
|
+
* Called by the channel during flush operations
|
|
308
|
+
*/
|
|
309
|
+
deliverMessage(message: JSONRPCMessage): void {
|
|
310
|
+
if (!this.started || this.channel.isClosed(this.side)) {
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
try {
|
|
315
|
+
this._onmessage?.(message);
|
|
316
|
+
} catch (error) {
|
|
317
|
+
this._onerror?.(error as Error);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Client-side bridge transport
|
|
324
|
+
*/
|
|
325
|
+
export class BridgeClientTransport extends BaseBridgeTransport {
|
|
326
|
+
private oppositeTransport?: BridgeServerTransport;
|
|
327
|
+
|
|
328
|
+
constructor(channel: BridgeChannel) {
|
|
329
|
+
super(channel, "client");
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
protected getOppositeTransport(): BaseBridgeTransport | undefined {
|
|
333
|
+
return this.oppositeTransport;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
setOppositeTransport(transport: BaseBridgeTransport): void {
|
|
337
|
+
if (!(transport instanceof BridgeServerTransport)) {
|
|
338
|
+
throw new Error("Opposite transport must be BridgeServerTransport");
|
|
339
|
+
}
|
|
340
|
+
this.oppositeTransport = transport;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
override async start(): Promise<void> {
|
|
344
|
+
await super.start();
|
|
345
|
+
// Callbacks will be set by MCP SDK after start()
|
|
346
|
+
// We use property setters to sync them with internal handlers
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
override async send(message: JSONRPCMessage): Promise<void> {
|
|
350
|
+
await super.send(message);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Server-side bridge transport
|
|
356
|
+
*/
|
|
357
|
+
export class BridgeServerTransport extends BaseBridgeTransport {
|
|
358
|
+
private oppositeTransport?: BridgeClientTransport;
|
|
359
|
+
|
|
360
|
+
constructor(channel: BridgeChannel) {
|
|
361
|
+
super(channel, "server");
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
protected getOppositeTransport(): BaseBridgeTransport | undefined {
|
|
365
|
+
return this.oppositeTransport;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
setOppositeTransport(transport: BaseBridgeTransport): void {
|
|
369
|
+
if (!(transport instanceof BridgeClientTransport)) {
|
|
370
|
+
throw new Error("Opposite transport must be BridgeClientTransport");
|
|
371
|
+
}
|
|
372
|
+
this.oppositeTransport = transport;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
override async start(): Promise<void> {
|
|
376
|
+
await super.start();
|
|
377
|
+
// Callbacks will be set by MCP SDK after start()
|
|
378
|
+
// We use property setters to sync them with internal handlers
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
override async send(message: JSONRPCMessage): Promise<void> {
|
|
382
|
+
await super.send(message);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Result of creating a bridge transport pair
|
|
388
|
+
*/
|
|
389
|
+
export interface BridgeTransportPair {
|
|
390
|
+
/**
|
|
391
|
+
* Client-side transport (for MCP Client)
|
|
392
|
+
*/
|
|
393
|
+
client: BridgeClientTransport;
|
|
394
|
+
/**
|
|
395
|
+
* Server-side transport (for MCP Server)
|
|
396
|
+
*/
|
|
397
|
+
server: BridgeServerTransport;
|
|
398
|
+
/**
|
|
399
|
+
* Internal channel (for advanced use cases)
|
|
400
|
+
*/
|
|
401
|
+
channel: BridgeChannel;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Create a pair of bridge transports for client-server communication.
|
|
406
|
+
*
|
|
407
|
+
* Uses microtask scheduling for message delivery, which frees the event loop
|
|
408
|
+
* and prevents deep recursion while maintaining message ordering.
|
|
409
|
+
*
|
|
410
|
+
* @returns A pair of transports connected via a bridge channel
|
|
411
|
+
*
|
|
412
|
+
* @example
|
|
413
|
+
* ```ts
|
|
414
|
+
* const { client, server } = createBridgeTransportPair();
|
|
415
|
+
*
|
|
416
|
+
* const mcpClient = new Client({ name: "test", version: "1.0.0" });
|
|
417
|
+
* const mcpServer = new Server({ name: "test", version: "1.0.0" });
|
|
418
|
+
*
|
|
419
|
+
* await mcpServer.connect(server);
|
|
420
|
+
* await mcpClient.connect(client);
|
|
421
|
+
*
|
|
422
|
+
* // Now client and server can communicate via bridge
|
|
423
|
+
* ```
|
|
424
|
+
*/
|
|
425
|
+
export function createBridgeTransportPair(): BridgeTransportPair {
|
|
426
|
+
const channel = new BridgeChannel();
|
|
427
|
+
const client = new BridgeClientTransport(channel);
|
|
428
|
+
const server = new BridgeServerTransport(channel);
|
|
429
|
+
|
|
430
|
+
// Register transports with channel (also links them for close notifications)
|
|
431
|
+
channel.registerTransports(client, server);
|
|
432
|
+
|
|
433
|
+
return { client, server, channel };
|
|
434
|
+
}
|
package/src/lib/constants.ts
CHANGED
|
@@ -5,7 +5,10 @@
|
|
|
5
5
|
* This module provides constants and factory functions for creating standard MCP connections.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import type {
|
|
8
|
+
import type {
|
|
9
|
+
ConnectionCreateData,
|
|
10
|
+
ConnectionEntity,
|
|
11
|
+
} from "../types/connection";
|
|
9
12
|
import type { VirtualMCPEntity } from "../types/virtual-mcp";
|
|
10
13
|
|
|
11
14
|
/**
|
|
@@ -21,6 +24,8 @@ export const WellKnownOrgMCPId = {
|
|
|
21
24
|
REGISTRY: (org: string) => `${org}_registry`,
|
|
22
25
|
/** Community MCP registry */
|
|
23
26
|
COMMUNITY_REGISTRY: (org: string) => `${org}_community-registry`,
|
|
27
|
+
/** Dev Assets MCP - local file storage for development */
|
|
28
|
+
DEV_ASSETS: (org: string) => `${org}_dev-assets`,
|
|
24
29
|
};
|
|
25
30
|
|
|
26
31
|
/**
|
|
@@ -30,6 +35,13 @@ export const WellKnownOrgMCPId = {
|
|
|
30
35
|
*/
|
|
31
36
|
export const SELF_MCP_ALIAS_ID = "self";
|
|
32
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Frontend connection ID for the dev-assets MCP endpoint.
|
|
40
|
+
* Use this constant when calling object storage tools from the frontend in dev mode.
|
|
41
|
+
* The endpoint is exposed at /mcp/dev-assets.
|
|
42
|
+
*/
|
|
43
|
+
export const DEV_ASSETS_MCP_ALIAS_ID = "dev-assets";
|
|
44
|
+
|
|
33
45
|
/**
|
|
34
46
|
* Get well-known connection definition for the Deco Store registry.
|
|
35
47
|
* This can be used by both frontend and backend to create registry connections.
|
|
@@ -44,7 +56,7 @@ export function getWellKnownRegistryConnection(
|
|
|
44
56
|
title: "Deco Store",
|
|
45
57
|
description: "Official deco MCP registry with curated integrations",
|
|
46
58
|
connection_type: "HTTP",
|
|
47
|
-
connection_url: "https://
|
|
59
|
+
connection_url: "https://studio.decocms.com/org/deco/registry/mcp",
|
|
48
60
|
icon: "https://assets.decocache.com/decocms/00ccf6c3-9e13-4517-83b0-75ab84554bb9/596364c63320075ca58483660156b6d9de9b526e.png",
|
|
49
61
|
app_name: "deco-registry",
|
|
50
62
|
app_id: null,
|
|
@@ -101,8 +113,8 @@ export function getWellKnownSelfConnection(
|
|
|
101
113
|
): ConnectionCreateData {
|
|
102
114
|
return {
|
|
103
115
|
id: WellKnownOrgMCPId.SELF(orgId),
|
|
104
|
-
title: "
|
|
105
|
-
description: "The MCP for the
|
|
116
|
+
title: "Deco CMS",
|
|
117
|
+
description: "The MCP for the CMS API",
|
|
106
118
|
connection_type: "HTTP",
|
|
107
119
|
// Custom url for targeting this mcp. It's a standalone endpoint that exposes all management tools.
|
|
108
120
|
connection_url: `${baseUrl}/mcp/${SELF_MCP_ALIAS_ID}`,
|
|
@@ -120,6 +132,43 @@ export function getWellKnownSelfConnection(
|
|
|
120
132
|
};
|
|
121
133
|
}
|
|
122
134
|
|
|
135
|
+
/**
|
|
136
|
+
* Get well-known connection definition for Dev Assets MCP.
|
|
137
|
+
* This is a dev-only MCP that provides local file storage at /data/assets/<org_id>/.
|
|
138
|
+
* It implements the OBJECT_STORAGE_BINDING interface.
|
|
139
|
+
*
|
|
140
|
+
* @param baseUrl - The base URL for the MCP server (e.g., "http://localhost:3000")
|
|
141
|
+
* @param orgId - The organization ID
|
|
142
|
+
* @returns ConnectionCreateData for the Dev Assets MCP
|
|
143
|
+
*/
|
|
144
|
+
export function getWellKnownDevAssetsConnection(
|
|
145
|
+
baseUrl: string,
|
|
146
|
+
orgId: string,
|
|
147
|
+
): ConnectionCreateData {
|
|
148
|
+
return {
|
|
149
|
+
id: WellKnownOrgMCPId.DEV_ASSETS(orgId),
|
|
150
|
+
title: "Local Files",
|
|
151
|
+
description:
|
|
152
|
+
"Local file storage for development. Files are stored in /data/assets/.",
|
|
153
|
+
connection_type: "HTTP",
|
|
154
|
+
connection_url: `${baseUrl}/mcp/${DEV_ASSETS_MCP_ALIAS_ID}`,
|
|
155
|
+
// Folder icon
|
|
156
|
+
icon: "https://api.iconify.design/lucide:folder.svg?color=%23888",
|
|
157
|
+
app_name: "@deco/dev-assets-mcp",
|
|
158
|
+
app_id: null,
|
|
159
|
+
connection_token: null,
|
|
160
|
+
connection_headers: null,
|
|
161
|
+
oauth_config: null,
|
|
162
|
+
configuration_state: null,
|
|
163
|
+
configuration_scopes: null,
|
|
164
|
+
metadata: {
|
|
165
|
+
isFixed: true,
|
|
166
|
+
devOnly: true,
|
|
167
|
+
type: "dev-assets",
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
123
172
|
/**
|
|
124
173
|
* Get well-known connection definition for OpenRouter.
|
|
125
174
|
* Used by the chat UI to offer a one-click install when no model provider is connected.
|
|
@@ -131,7 +180,7 @@ export function getWellKnownOpenRouterConnection(
|
|
|
131
180
|
id: opts.id,
|
|
132
181
|
title: "OpenRouter",
|
|
133
182
|
description: "Access hundreds of LLM models from a single API",
|
|
134
|
-
icon: "https://
|
|
183
|
+
icon: "https://assets.decocache.com/decocms/b2e2f64f-6025-45f7-9e8c-3b3ebdd073d8/openrouter_logojpg.jpg",
|
|
135
184
|
app_name: "openrouter",
|
|
136
185
|
app_id: "openrouter",
|
|
137
186
|
connection_type: "HTTP",
|
|
@@ -178,27 +227,81 @@ export function getWellKnownMcpStudioConnection(): ConnectionCreateData {
|
|
|
178
227
|
}
|
|
179
228
|
|
|
180
229
|
/**
|
|
181
|
-
* Get well-known Decopilot
|
|
230
|
+
* Get well-known Decopilot Virtual MCP entity.
|
|
182
231
|
* This is the default agent that aggregates ALL org connections.
|
|
183
232
|
*
|
|
184
233
|
* @param organizationId - Organization ID
|
|
185
234
|
* @returns VirtualMCPEntity representing the Decopilot agent
|
|
186
235
|
*/
|
|
187
|
-
export function
|
|
236
|
+
export function getWellKnownDecopilotVirtualMCP(
|
|
188
237
|
organizationId: string,
|
|
189
238
|
): VirtualMCPEntity {
|
|
190
239
|
return {
|
|
191
|
-
id:
|
|
240
|
+
id: getDecopilotId(organizationId),
|
|
192
241
|
organization_id: organizationId,
|
|
193
242
|
title: "Decopilot",
|
|
194
243
|
description: "Default agent that aggregates all organization connections",
|
|
195
244
|
icon: "https://assets.decocache.com/decocms/fd07a578-6b1c-40f1-bc05-88a3b981695d/f7fc4ffa81aec04e37ae670c3cd4936643a7b269.png",
|
|
196
|
-
tool_selection_mode: "exclusion",
|
|
197
245
|
status: "active",
|
|
198
246
|
created_at: new Date().toISOString(),
|
|
199
247
|
updated_at: new Date().toISOString(),
|
|
200
248
|
created_by: "system",
|
|
201
249
|
updated_by: undefined,
|
|
202
|
-
|
|
250
|
+
metadata: { instructions: null },
|
|
251
|
+
subtype: null,
|
|
252
|
+
connections: [], // Empty connections array - gateway.ts will populate with all org connections
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Decopilot ID prefix constant
|
|
258
|
+
*/
|
|
259
|
+
const DECOPILOT_PREFIX = "decopilot_";
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Check if a connection or virtual MCP ID is the Decopilot agent.
|
|
263
|
+
*
|
|
264
|
+
* @param id - Connection or virtual MCP ID to check
|
|
265
|
+
* @returns The organization ID if the ID matches the Decopilot pattern (decopilot_{orgId}), null otherwise
|
|
266
|
+
*/
|
|
267
|
+
export function isDecopilot(id: string | null | undefined): string | null {
|
|
268
|
+
if (!id) return null;
|
|
269
|
+
if (!id.startsWith(DECOPILOT_PREFIX)) return null;
|
|
270
|
+
return id.slice(DECOPILOT_PREFIX.length) || null;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Get the Decopilot ID for a given organization.
|
|
275
|
+
*
|
|
276
|
+
* @param organizationId - Organization ID
|
|
277
|
+
* @returns The Decopilot ID in the format `decopilot_{organizationId}`
|
|
278
|
+
*/
|
|
279
|
+
export function getDecopilotId(organizationId: string): string {
|
|
280
|
+
return `${DECOPILOT_PREFIX}${organizationId}`;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
export function getWellKnownDecopilotConnection(
|
|
284
|
+
organizationId: string,
|
|
285
|
+
): ConnectionEntity {
|
|
286
|
+
const virtual = getWellKnownDecopilotVirtualMCP(organizationId);
|
|
287
|
+
|
|
288
|
+
return {
|
|
289
|
+
...virtual,
|
|
290
|
+
id: virtual.id!,
|
|
291
|
+
connection_type: "VIRTUAL",
|
|
292
|
+
connection_url: `virtual://${virtual.id}`,
|
|
293
|
+
app_name: "decopilot",
|
|
294
|
+
app_id: "decopilot",
|
|
295
|
+
connection_token: null,
|
|
296
|
+
connection_headers: null,
|
|
297
|
+
oauth_config: null,
|
|
298
|
+
configuration_state: null,
|
|
299
|
+
configuration_scopes: null,
|
|
300
|
+
metadata: {
|
|
301
|
+
isDefault: true,
|
|
302
|
+
type: "decopilot",
|
|
303
|
+
},
|
|
304
|
+
tools: [],
|
|
305
|
+
bindings: [],
|
|
203
306
|
};
|
|
204
307
|
}
|