yungle-mcp 0.2.0 → 0.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/dist/index.mjs +26 -17
- package/dist/server.d.ts +2 -0
- package/dist/server.mjs +26 -17
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -74,7 +74,7 @@ async function uploadPath(endpoint, target, path, size, doFetch = fetch) {
|
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
// src/server.ts
|
|
77
|
-
var MCP_VERSION = true ? "0.2.
|
|
77
|
+
var MCP_VERSION = true ? "0.2.2" : "dev";
|
|
78
78
|
function createServer(client, opts = { canWrite: false, canEmail: false, local: false }) {
|
|
79
79
|
const server = new McpServer(
|
|
80
80
|
{ name: "yungle", version: MCP_VERSION },
|
|
@@ -95,7 +95,7 @@ function createServer(client, opts = { canWrite: false, canEmail: false, local:
|
|
|
95
95
|
title: "Account and storage",
|
|
96
96
|
description: "Answers: how much storage am I using? how much is left? what plan am I on? what can this connection do? Returns the workspace, its plan and quota, and the key's permissions.",
|
|
97
97
|
inputSchema: {},
|
|
98
|
-
annotations: { readOnlyHint: true }
|
|
98
|
+
annotations: { title: "Account and storage", readOnlyHint: true }
|
|
99
99
|
},
|
|
100
100
|
async () => ok(await client.me())
|
|
101
101
|
);
|
|
@@ -105,7 +105,7 @@ function createServer(client, opts = { canWrite: false, canEmail: false, local:
|
|
|
105
105
|
title: "List transfers",
|
|
106
106
|
description: "Answers: what have I sent recently? what is expiring soon? which deliveries has nobody picked up? who did I send that to? Returns recent transfers with size, recipients, download count, expiry date and share link. Start here for any question about sent files.",
|
|
107
107
|
inputSchema: {},
|
|
108
|
-
annotations: { readOnlyHint: true }
|
|
108
|
+
annotations: { title: "List transfers", readOnlyHint: true }
|
|
109
109
|
},
|
|
110
110
|
async () => {
|
|
111
111
|
const { transfers } = await client.listTransfers();
|
|
@@ -118,7 +118,7 @@ function createServer(client, opts = { canWrite: false, canEmail: false, local:
|
|
|
118
118
|
title: "Transfer detail",
|
|
119
119
|
description: "Answers: what is in this transfer? was it delivered? has a specific recipient opened it? is it safe to share? Returns the files with their malware-scan verdicts, plus per-recipient delivery and download status. Needs an id from list_transfers.",
|
|
120
120
|
inputSchema: { id: z.string().describe("Transfer id from list_transfers.") },
|
|
121
|
-
annotations: { readOnlyHint: true }
|
|
121
|
+
annotations: { title: "Transfer detail", readOnlyHint: true }
|
|
122
122
|
},
|
|
123
123
|
async ({ id }) => ok(wrapUntrusted(await client.getTransfer(id)))
|
|
124
124
|
);
|
|
@@ -128,7 +128,7 @@ function createServer(client, opts = { canWrite: false, canEmail: false, local:
|
|
|
128
128
|
title: "Download receipts",
|
|
129
129
|
description: "Answers: did the client download it? when? how many times? Returns download events with timestamps and per-recipient status. IMPORTANT when counting: one page visit is one download, not one per file \u2014 events sharing a sessionId are a single visit, so count distinct sessions or a single visitor looks like eight.",
|
|
130
130
|
inputSchema: { id: z.string() },
|
|
131
|
-
annotations: { readOnlyHint: true }
|
|
131
|
+
annotations: { title: "Download receipts", readOnlyHint: true }
|
|
132
132
|
},
|
|
133
133
|
async ({ id }) => ok(wrapUntrusted(await client.transferDownloads(id)))
|
|
134
134
|
);
|
|
@@ -138,7 +138,7 @@ function createServer(client, opts = { canWrite: false, canEmail: false, local:
|
|
|
138
138
|
title: "List collections",
|
|
139
139
|
description: "Answers: what collections do I have? which are empty? how big is each one? Returns collections with file counts, sizes and when each was last touched. A collection is a durable space clients are invited into, as opposed to a one-off transfer. Never includes the vault, which is unreadable to this server by design.",
|
|
140
140
|
inputSchema: {},
|
|
141
|
-
annotations: { readOnlyHint: true }
|
|
141
|
+
annotations: { title: "List collections", readOnlyHint: true }
|
|
142
142
|
},
|
|
143
143
|
async () => ok(wrapUntrusted(await client.listCollections()))
|
|
144
144
|
);
|
|
@@ -148,7 +148,7 @@ function createServer(client, opts = { canWrite: false, canEmail: false, local:
|
|
|
148
148
|
title: "Collection detail",
|
|
149
149
|
description: "Answers: what is the link for this collection? how much is in it? when does it expire? Returns one collection with its secret share link, file count, total size and expiry. Needs an id from list_collections.",
|
|
150
150
|
inputSchema: { id: z.string() },
|
|
151
|
-
annotations: { readOnlyHint: true }
|
|
151
|
+
annotations: { title: "Collection detail", readOnlyHint: true }
|
|
152
152
|
},
|
|
153
153
|
async ({ id }) => ok(wrapUntrusted(await client.getCollection(id)))
|
|
154
154
|
);
|
|
@@ -158,7 +158,7 @@ function createServer(client, opts = { canWrite: false, canEmail: false, local:
|
|
|
158
158
|
title: "Files in a collection",
|
|
159
159
|
description: 'Answers: what files are in this collection? what is in this folder? are the raws uploaded yet? Returns filenames, sizes and types. Omit folderId for every file; pass "root" for the top level only, or a folder id from list_folders. Filenames only \u2014 never file contents.',
|
|
160
160
|
inputSchema: { id: z.string(), folderId: z.string().optional() },
|
|
161
|
-
annotations: { readOnlyHint: true }
|
|
161
|
+
annotations: { title: "Files in a collection", readOnlyHint: true }
|
|
162
162
|
},
|
|
163
163
|
async ({ id, folderId }) => ok(wrapUntrusted(await client.listCollectionFiles(id, folderId)))
|
|
164
164
|
);
|
|
@@ -168,7 +168,7 @@ function createServer(client, opts = { canWrite: false, canEmail: false, local:
|
|
|
168
168
|
title: "Folders in a collection",
|
|
169
169
|
description: "Answers: how is this collection organised? what folders exist? Returns the folder tree. Ordered by depth then path, which is NOT a pre-order traversal \u2014 build the tree from parentId rather than trusting the order. Each folder also carries its full materialised path.",
|
|
170
170
|
inputSchema: { id: z.string() },
|
|
171
|
-
annotations: { readOnlyHint: true }
|
|
171
|
+
annotations: { title: "Folders in a collection", readOnlyHint: true }
|
|
172
172
|
},
|
|
173
173
|
async ({ id }) => ok(wrapUntrusted(await client.listFolders(id)))
|
|
174
174
|
);
|
|
@@ -178,7 +178,7 @@ function createServer(client, opts = { canWrite: false, canEmail: false, local:
|
|
|
178
178
|
title: "Guests on a collection",
|
|
179
179
|
description: "Answers: who has access to this collection? who did I invite? has someone accepted? Returns the invited guests and their status. Guests are not workspace members \u2014 they can view and download this one collection and nothing else.",
|
|
180
180
|
inputSchema: { id: z.string() },
|
|
181
|
-
annotations: { readOnlyHint: true }
|
|
181
|
+
annotations: { title: "Guests on a collection", readOnlyHint: true }
|
|
182
182
|
},
|
|
183
183
|
async ({ id }) => ok(wrapUntrusted(await client.listGuests(id)))
|
|
184
184
|
);
|
|
@@ -188,7 +188,7 @@ function createServer(client, opts = { canWrite: false, canEmail: false, local:
|
|
|
188
188
|
title: "Address book",
|
|
189
189
|
description: "Answers: what is this client's email address? who do I have saved? Returns the workspace address book. A contact grants no access on its own \u2014 it is a convenience, not a permission.",
|
|
190
190
|
inputSchema: {},
|
|
191
|
-
annotations: { readOnlyHint: true }
|
|
191
|
+
annotations: { title: "Address book", readOnlyHint: true }
|
|
192
192
|
},
|
|
193
193
|
async () => ok(wrapUntrusted(await client.listContacts()))
|
|
194
194
|
);
|
|
@@ -214,7 +214,7 @@ function createServer(client, opts = { canWrite: false, canEmail: false, local:
|
|
|
214
214
|
title: z.string().optional().describe("Label for the dashboard; never shown to recipients."),
|
|
215
215
|
expiresInDays: z.number().int().positive().optional()
|
|
216
216
|
},
|
|
217
|
-
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false }
|
|
217
|
+
annotations: { title: "Prepare a transfer", readOnlyHint: false, destructiveHint: false, idempotentHint: false }
|
|
218
218
|
},
|
|
219
219
|
async ({ files, title, expiresInDays }) => {
|
|
220
220
|
const created = await client.createTransfer({ files, title, expiresInDays });
|
|
@@ -250,7 +250,7 @@ function createServer(client, opts = { canWrite: false, canEmail: false, local:
|
|
|
250
250
|
title: z.string().optional().describe("Label for the dashboard; never shown to recipients."),
|
|
251
251
|
expiresInDays: z.number().int().positive().optional()
|
|
252
252
|
},
|
|
253
|
-
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }
|
|
253
|
+
annotations: { title: "Share content as a link", readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }
|
|
254
254
|
},
|
|
255
255
|
async ({ files, title, expiresInDays }) => {
|
|
256
256
|
const contents = files.map(
|
|
@@ -284,7 +284,7 @@ function createServer(client, opts = { canWrite: false, canEmail: false, local:
|
|
|
284
284
|
title: z.string().optional(),
|
|
285
285
|
expiresInDays: z.number().int().positive().optional()
|
|
286
286
|
},
|
|
287
|
-
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }
|
|
287
|
+
annotations: { title: "Share files from this computer as a link", readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }
|
|
288
288
|
},
|
|
289
289
|
async ({ paths: asked, title, expiresInDays }, extra) => {
|
|
290
290
|
const isHidden = (p) => p.split(/[\\/]/).some((seg) => seg.startsWith(".") && seg !== "." && seg !== "..");
|
|
@@ -332,7 +332,7 @@ function createServer(client, opts = { canWrite: false, canEmail: false, local:
|
|
|
332
332
|
recipients: z.array(z.string().email()).min(1).max(10),
|
|
333
333
|
message: z.string().max(2e3).optional().describe("A note shown in the email.")
|
|
334
334
|
},
|
|
335
|
-
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true }
|
|
335
|
+
annotations: { title: "Email a transfer to recipients", readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true }
|
|
336
336
|
},
|
|
337
337
|
async ({ transferId, recipients, message }, extra) => {
|
|
338
338
|
const { transfer } = await client.getTransfer(transferId);
|
|
@@ -369,9 +369,11 @@ async function startStdioServer() {
|
|
|
369
369
|
const apiKey = process.env.YUNGLE_API_KEY?.trim();
|
|
370
370
|
if (!apiKey) {
|
|
371
371
|
process.stderr.write(
|
|
372
|
-
"YUNGLE_API_KEY is not set. Create a key at https://yungle.co/dashboard/settings/api\n"
|
|
372
|
+
"YUNGLE_API_KEY is not set: listing tools only. Create a key at https://yungle.co/dashboard/settings/api\n"
|
|
373
373
|
);
|
|
374
|
-
|
|
374
|
+
const server2 = createServer(keylessClient(), { canWrite: false, canEmail: false, local: true });
|
|
375
|
+
await server2.connect(new StdioServerTransport());
|
|
376
|
+
return;
|
|
375
377
|
}
|
|
376
378
|
const client = new YungleClient({
|
|
377
379
|
apiKey,
|
|
@@ -397,6 +399,12 @@ async function startStdioServer() {
|
|
|
397
399
|
});
|
|
398
400
|
await server.connect(new StdioServerTransport());
|
|
399
401
|
}
|
|
402
|
+
function keylessClient() {
|
|
403
|
+
const message = "YUNGLE_API_KEY is not set, so this server cannot reach Yungle. Create a key at https://yungle.co/dashboard/settings/api and add it to this MCP server's environment, or connect to the hosted server at https://yungle.co/mcp instead.";
|
|
404
|
+
return new Proxy({}, {
|
|
405
|
+
get: () => () => Promise.reject(new Error(message))
|
|
406
|
+
});
|
|
407
|
+
}
|
|
400
408
|
function ok(payload) {
|
|
401
409
|
return { content: [{ type: "text", text: JSON.stringify(payload, null, 2) }] };
|
|
402
410
|
}
|
|
@@ -406,5 +414,6 @@ function fail(message) {
|
|
|
406
414
|
export {
|
|
407
415
|
MCP_VERSION,
|
|
408
416
|
createServer,
|
|
417
|
+
keylessClient,
|
|
409
418
|
startStdioServer
|
|
410
419
|
};
|
package/dist/server.d.ts
CHANGED
|
@@ -23,3 +23,5 @@ export declare function createServer(client: YungleClient, opts?: ServerOptions)
|
|
|
23
23
|
* emails nobody and needs only transfers:write.
|
|
24
24
|
*/
|
|
25
25
|
export declare function startStdioServer(): Promise<void>;
|
|
26
|
+
/** A client whose every call explains that there is no key. */
|
|
27
|
+
export declare function keylessClient(): YungleClient;
|
package/dist/server.mjs
CHANGED
|
@@ -345,7 +345,7 @@ async function uploadPath(endpoint, target, path, size, doFetch = fetch) {
|
|
|
345
345
|
}
|
|
346
346
|
|
|
347
347
|
// src/server.ts
|
|
348
|
-
var MCP_VERSION = true ? "0.2.
|
|
348
|
+
var MCP_VERSION = true ? "0.2.2" : "dev";
|
|
349
349
|
function createServer(client, opts = { canWrite: false, canEmail: false, local: false }) {
|
|
350
350
|
const server = new McpServer(
|
|
351
351
|
{ name: "yungle", version: MCP_VERSION },
|
|
@@ -366,7 +366,7 @@ function createServer(client, opts = { canWrite: false, canEmail: false, local:
|
|
|
366
366
|
title: "Account and storage",
|
|
367
367
|
description: "Answers: how much storage am I using? how much is left? what plan am I on? what can this connection do? Returns the workspace, its plan and quota, and the key's permissions.",
|
|
368
368
|
inputSchema: {},
|
|
369
|
-
annotations: { readOnlyHint: true }
|
|
369
|
+
annotations: { title: "Account and storage", readOnlyHint: true }
|
|
370
370
|
},
|
|
371
371
|
async () => ok(await client.me())
|
|
372
372
|
);
|
|
@@ -376,7 +376,7 @@ function createServer(client, opts = { canWrite: false, canEmail: false, local:
|
|
|
376
376
|
title: "List transfers",
|
|
377
377
|
description: "Answers: what have I sent recently? what is expiring soon? which deliveries has nobody picked up? who did I send that to? Returns recent transfers with size, recipients, download count, expiry date and share link. Start here for any question about sent files.",
|
|
378
378
|
inputSchema: {},
|
|
379
|
-
annotations: { readOnlyHint: true }
|
|
379
|
+
annotations: { title: "List transfers", readOnlyHint: true }
|
|
380
380
|
},
|
|
381
381
|
async () => {
|
|
382
382
|
const { transfers } = await client.listTransfers();
|
|
@@ -389,7 +389,7 @@ function createServer(client, opts = { canWrite: false, canEmail: false, local:
|
|
|
389
389
|
title: "Transfer detail",
|
|
390
390
|
description: "Answers: what is in this transfer? was it delivered? has a specific recipient opened it? is it safe to share? Returns the files with their malware-scan verdicts, plus per-recipient delivery and download status. Needs an id from list_transfers.",
|
|
391
391
|
inputSchema: { id: z.string().describe("Transfer id from list_transfers.") },
|
|
392
|
-
annotations: { readOnlyHint: true }
|
|
392
|
+
annotations: { title: "Transfer detail", readOnlyHint: true }
|
|
393
393
|
},
|
|
394
394
|
async ({ id }) => ok(wrapUntrusted(await client.getTransfer(id)))
|
|
395
395
|
);
|
|
@@ -399,7 +399,7 @@ function createServer(client, opts = { canWrite: false, canEmail: false, local:
|
|
|
399
399
|
title: "Download receipts",
|
|
400
400
|
description: "Answers: did the client download it? when? how many times? Returns download events with timestamps and per-recipient status. IMPORTANT when counting: one page visit is one download, not one per file \u2014 events sharing a sessionId are a single visit, so count distinct sessions or a single visitor looks like eight.",
|
|
401
401
|
inputSchema: { id: z.string() },
|
|
402
|
-
annotations: { readOnlyHint: true }
|
|
402
|
+
annotations: { title: "Download receipts", readOnlyHint: true }
|
|
403
403
|
},
|
|
404
404
|
async ({ id }) => ok(wrapUntrusted(await client.transferDownloads(id)))
|
|
405
405
|
);
|
|
@@ -409,7 +409,7 @@ function createServer(client, opts = { canWrite: false, canEmail: false, local:
|
|
|
409
409
|
title: "List collections",
|
|
410
410
|
description: "Answers: what collections do I have? which are empty? how big is each one? Returns collections with file counts, sizes and when each was last touched. A collection is a durable space clients are invited into, as opposed to a one-off transfer. Never includes the vault, which is unreadable to this server by design.",
|
|
411
411
|
inputSchema: {},
|
|
412
|
-
annotations: { readOnlyHint: true }
|
|
412
|
+
annotations: { title: "List collections", readOnlyHint: true }
|
|
413
413
|
},
|
|
414
414
|
async () => ok(wrapUntrusted(await client.listCollections()))
|
|
415
415
|
);
|
|
@@ -419,7 +419,7 @@ function createServer(client, opts = { canWrite: false, canEmail: false, local:
|
|
|
419
419
|
title: "Collection detail",
|
|
420
420
|
description: "Answers: what is the link for this collection? how much is in it? when does it expire? Returns one collection with its secret share link, file count, total size and expiry. Needs an id from list_collections.",
|
|
421
421
|
inputSchema: { id: z.string() },
|
|
422
|
-
annotations: { readOnlyHint: true }
|
|
422
|
+
annotations: { title: "Collection detail", readOnlyHint: true }
|
|
423
423
|
},
|
|
424
424
|
async ({ id }) => ok(wrapUntrusted(await client.getCollection(id)))
|
|
425
425
|
);
|
|
@@ -429,7 +429,7 @@ function createServer(client, opts = { canWrite: false, canEmail: false, local:
|
|
|
429
429
|
title: "Files in a collection",
|
|
430
430
|
description: 'Answers: what files are in this collection? what is in this folder? are the raws uploaded yet? Returns filenames, sizes and types. Omit folderId for every file; pass "root" for the top level only, or a folder id from list_folders. Filenames only \u2014 never file contents.',
|
|
431
431
|
inputSchema: { id: z.string(), folderId: z.string().optional() },
|
|
432
|
-
annotations: { readOnlyHint: true }
|
|
432
|
+
annotations: { title: "Files in a collection", readOnlyHint: true }
|
|
433
433
|
},
|
|
434
434
|
async ({ id, folderId }) => ok(wrapUntrusted(await client.listCollectionFiles(id, folderId)))
|
|
435
435
|
);
|
|
@@ -439,7 +439,7 @@ function createServer(client, opts = { canWrite: false, canEmail: false, local:
|
|
|
439
439
|
title: "Folders in a collection",
|
|
440
440
|
description: "Answers: how is this collection organised? what folders exist? Returns the folder tree. Ordered by depth then path, which is NOT a pre-order traversal \u2014 build the tree from parentId rather than trusting the order. Each folder also carries its full materialised path.",
|
|
441
441
|
inputSchema: { id: z.string() },
|
|
442
|
-
annotations: { readOnlyHint: true }
|
|
442
|
+
annotations: { title: "Folders in a collection", readOnlyHint: true }
|
|
443
443
|
},
|
|
444
444
|
async ({ id }) => ok(wrapUntrusted(await client.listFolders(id)))
|
|
445
445
|
);
|
|
@@ -449,7 +449,7 @@ function createServer(client, opts = { canWrite: false, canEmail: false, local:
|
|
|
449
449
|
title: "Guests on a collection",
|
|
450
450
|
description: "Answers: who has access to this collection? who did I invite? has someone accepted? Returns the invited guests and their status. Guests are not workspace members \u2014 they can view and download this one collection and nothing else.",
|
|
451
451
|
inputSchema: { id: z.string() },
|
|
452
|
-
annotations: { readOnlyHint: true }
|
|
452
|
+
annotations: { title: "Guests on a collection", readOnlyHint: true }
|
|
453
453
|
},
|
|
454
454
|
async ({ id }) => ok(wrapUntrusted(await client.listGuests(id)))
|
|
455
455
|
);
|
|
@@ -459,7 +459,7 @@ function createServer(client, opts = { canWrite: false, canEmail: false, local:
|
|
|
459
459
|
title: "Address book",
|
|
460
460
|
description: "Answers: what is this client's email address? who do I have saved? Returns the workspace address book. A contact grants no access on its own \u2014 it is a convenience, not a permission.",
|
|
461
461
|
inputSchema: {},
|
|
462
|
-
annotations: { readOnlyHint: true }
|
|
462
|
+
annotations: { title: "Address book", readOnlyHint: true }
|
|
463
463
|
},
|
|
464
464
|
async () => ok(wrapUntrusted(await client.listContacts()))
|
|
465
465
|
);
|
|
@@ -485,7 +485,7 @@ function createServer(client, opts = { canWrite: false, canEmail: false, local:
|
|
|
485
485
|
title: z.string().optional().describe("Label for the dashboard; never shown to recipients."),
|
|
486
486
|
expiresInDays: z.number().int().positive().optional()
|
|
487
487
|
},
|
|
488
|
-
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false }
|
|
488
|
+
annotations: { title: "Prepare a transfer", readOnlyHint: false, destructiveHint: false, idempotentHint: false }
|
|
489
489
|
},
|
|
490
490
|
async ({ files, title, expiresInDays }) => {
|
|
491
491
|
const created = await client.createTransfer({ files, title, expiresInDays });
|
|
@@ -521,7 +521,7 @@ function createServer(client, opts = { canWrite: false, canEmail: false, local:
|
|
|
521
521
|
title: z.string().optional().describe("Label for the dashboard; never shown to recipients."),
|
|
522
522
|
expiresInDays: z.number().int().positive().optional()
|
|
523
523
|
},
|
|
524
|
-
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }
|
|
524
|
+
annotations: { title: "Share content as a link", readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }
|
|
525
525
|
},
|
|
526
526
|
async ({ files, title, expiresInDays }) => {
|
|
527
527
|
const contents = files.map(
|
|
@@ -555,7 +555,7 @@ function createServer(client, opts = { canWrite: false, canEmail: false, local:
|
|
|
555
555
|
title: z.string().optional(),
|
|
556
556
|
expiresInDays: z.number().int().positive().optional()
|
|
557
557
|
},
|
|
558
|
-
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }
|
|
558
|
+
annotations: { title: "Share files from this computer as a link", readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }
|
|
559
559
|
},
|
|
560
560
|
async ({ paths: asked, title, expiresInDays }, extra) => {
|
|
561
561
|
const isHidden = (p) => p.split(/[\\/]/).some((seg) => seg.startsWith(".") && seg !== "." && seg !== "..");
|
|
@@ -603,7 +603,7 @@ function createServer(client, opts = { canWrite: false, canEmail: false, local:
|
|
|
603
603
|
recipients: z.array(z.string().email()).min(1).max(10),
|
|
604
604
|
message: z.string().max(2e3).optional().describe("A note shown in the email.")
|
|
605
605
|
},
|
|
606
|
-
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true }
|
|
606
|
+
annotations: { title: "Email a transfer to recipients", readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true }
|
|
607
607
|
},
|
|
608
608
|
async ({ transferId, recipients, message }, extra) => {
|
|
609
609
|
const { transfer } = await client.getTransfer(transferId);
|
|
@@ -640,9 +640,11 @@ async function startStdioServer() {
|
|
|
640
640
|
const apiKey = process.env.YUNGLE_API_KEY?.trim();
|
|
641
641
|
if (!apiKey) {
|
|
642
642
|
process.stderr.write(
|
|
643
|
-
"YUNGLE_API_KEY is not set. Create a key at https://yungle.co/dashboard/settings/api\n"
|
|
643
|
+
"YUNGLE_API_KEY is not set: listing tools only. Create a key at https://yungle.co/dashboard/settings/api\n"
|
|
644
644
|
);
|
|
645
|
-
|
|
645
|
+
const server2 = createServer(keylessClient(), { canWrite: false, canEmail: false, local: true });
|
|
646
|
+
await server2.connect(new StdioServerTransport());
|
|
647
|
+
return;
|
|
646
648
|
}
|
|
647
649
|
const client = new YungleClient({
|
|
648
650
|
apiKey,
|
|
@@ -668,6 +670,12 @@ async function startStdioServer() {
|
|
|
668
670
|
});
|
|
669
671
|
await server.connect(new StdioServerTransport());
|
|
670
672
|
}
|
|
673
|
+
function keylessClient() {
|
|
674
|
+
const message = "YUNGLE_API_KEY is not set, so this server cannot reach Yungle. Create a key at https://yungle.co/dashboard/settings/api and add it to this MCP server's environment, or connect to the hosted server at https://yungle.co/mcp instead.";
|
|
675
|
+
return new Proxy({}, {
|
|
676
|
+
get: () => () => Promise.reject(new Error(message))
|
|
677
|
+
});
|
|
678
|
+
}
|
|
671
679
|
function ok(payload) {
|
|
672
680
|
return { content: [{ type: "text", text: JSON.stringify(payload, null, 2) }] };
|
|
673
681
|
}
|
|
@@ -677,6 +685,7 @@ function fail(message) {
|
|
|
677
685
|
export {
|
|
678
686
|
MCP_VERSION,
|
|
679
687
|
createServer,
|
|
688
|
+
keylessClient,
|
|
680
689
|
startStdioServer
|
|
681
690
|
};
|
|
682
691
|
await startStdioServer();
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "yungle-mcp",
|
|
3
3
|
"mcpName": "co.yungle/yungle",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.2",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "MCP server for Yungle: let Claude, Cursor or any MCP client read your file transfers, share files and send them, with your approval.",
|
|
7
7
|
"bin": {
|