@trops/dash-core 0.1.336 → 0.1.340
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.
|
@@ -195,47 +195,25 @@
|
|
|
195
195
|
{
|
|
196
196
|
"id": "google-drive",
|
|
197
197
|
"name": "Google Drive",
|
|
198
|
-
"description": "
|
|
198
|
+
"description": "Search, read, and write files in Google Drive. Supports folder listing, file creation, and path resolution.",
|
|
199
199
|
"icon": "google-drive",
|
|
200
200
|
"tags": ["google", "files", "cloud-storage"],
|
|
201
201
|
"mcpConfig": {
|
|
202
202
|
"transport": "stdio",
|
|
203
203
|
"command": "node",
|
|
204
204
|
"args": ["{{MCP_DIR}}/servers/google-drive.js"],
|
|
205
|
-
"envMapping": {
|
|
206
|
-
"GDRIVE_OAUTH_PATH": "oauthKeysPath"
|
|
207
|
-
},
|
|
208
205
|
"staticEnv": {
|
|
209
206
|
"GDRIVE_CREDENTIALS_PATH": "~/.gdrive-mcp/credentials.json"
|
|
210
|
-
},
|
|
211
|
-
"tokenRefresh": {
|
|
212
|
-
"credentialsPath": "~/.gdrive-mcp/credentials.json",
|
|
213
|
-
"oauthKeysPath": "~/.gdrive-mcp/gcp-oauth.keys.json"
|
|
214
207
|
}
|
|
215
208
|
},
|
|
216
209
|
"authCommand": {
|
|
217
210
|
"command": "node",
|
|
218
211
|
"args": ["{{MCP_DIR}}/servers/google-drive.js", "auth"],
|
|
219
|
-
"setup": {
|
|
220
|
-
"copyCredential": {
|
|
221
|
-
"from": "oauthKeysPath",
|
|
222
|
-
"to": "~/.gdrive-mcp/gcp-oauth.keys.json"
|
|
223
|
-
}
|
|
224
|
-
},
|
|
225
212
|
"staticEnv": {
|
|
226
|
-
"GDRIVE_CREDENTIALS_PATH": "~/.gdrive-mcp/credentials.json"
|
|
227
|
-
"GDRIVE_OAUTH_PATH": "~/.gdrive-mcp/gcp-oauth.keys.json"
|
|
213
|
+
"GDRIVE_CREDENTIALS_PATH": "~/.gdrive-mcp/credentials.json"
|
|
228
214
|
}
|
|
229
215
|
},
|
|
230
|
-
"credentialSchema": {
|
|
231
|
-
"oauthKeysPath": {
|
|
232
|
-
"type": "file",
|
|
233
|
-
"displayName": "OAuth Keys File",
|
|
234
|
-
"required": false,
|
|
235
|
-
"secret": false,
|
|
236
|
-
"instructions": "Path to your Google OAuth keys file (gcp-oauth.keys.json). Create one at console.cloud.google.com > APIs & Services > Credentials > OAuth 2.0 Client IDs."
|
|
237
|
-
}
|
|
238
|
-
}
|
|
216
|
+
"credentialSchema": {}
|
|
239
217
|
},
|
|
240
218
|
{
|
|
241
219
|
"id": "gmail",
|
|
@@ -2,12 +2,11 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Custom Google Drive MCP server.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
* fundamental bug: it creates OAuth2 clients without client_id/client_secret,
|
|
7
|
-
* so it can never refresh tokens.
|
|
5
|
+
* Tools: search, list_folder, create_folder, read_file, write_file, resolve_path
|
|
8
6
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
* OAuth uses PKCE with bundled app credentials (client_id + obfuscated
|
|
8
|
+
* client_secret). No per-user GCP project setup — users just click
|
|
9
|
+
* "Connect Google Drive" to grant access via browser.
|
|
11
10
|
*
|
|
12
11
|
* Usage:
|
|
13
12
|
* MCP server: node google-drive.js (stdio transport)
|
|
@@ -15,7 +14,6 @@
|
|
|
15
14
|
*
|
|
16
15
|
* Environment variables:
|
|
17
16
|
* GDRIVE_CREDENTIALS_PATH — path to stored OAuth credentials (access/refresh tokens)
|
|
18
|
-
* GDRIVE_OAUTH_PATH — path to Google OAuth client keys file
|
|
19
17
|
*/
|
|
20
18
|
const { Server } = require("@modelcontextprotocol/sdk/server/index.js");
|
|
21
19
|
const {
|
|
@@ -28,26 +26,30 @@ const {
|
|
|
28
26
|
const fs = require("fs");
|
|
29
27
|
const https = require("https");
|
|
30
28
|
const path = require("path");
|
|
29
|
+
const crypto = require("crypto");
|
|
31
30
|
|
|
32
31
|
const credentialsPath = (process.env.GDRIVE_CREDENTIALS_PATH || "").replace(
|
|
33
32
|
/^~/,
|
|
34
33
|
process.env.HOME || "",
|
|
35
34
|
);
|
|
36
|
-
const oauthKeysPath = (process.env.GDRIVE_OAUTH_PATH || "").replace(
|
|
37
|
-
/^~/,
|
|
38
|
-
process.env.HOME || "",
|
|
39
|
-
);
|
|
40
35
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
36
|
+
// Bundled OAuth credentials for the Dash platform's GCP project.
|
|
37
|
+
// client_id is public (identifier, not a secret).
|
|
38
|
+
// client_secret is injected at build time from GitHub Secrets — the
|
|
39
|
+
// placeholder below is replaced in dist/ during `npm run build`.
|
|
40
|
+
// Desktop OAuth client_secrets are not confidential per Google's docs —
|
|
41
|
+
// the consent screen is the security boundary, not this value.
|
|
42
|
+
const BUNDLED_CLIENT_ID =
|
|
43
|
+
"785070273499-mr9b0vup4u24he8duh3c6j5gpk7qj54j.apps.googleusercontent.com";
|
|
44
|
+
const BUNDLED_CLIENT_SECRET =
|
|
45
|
+
process.env.GDRIVE_CLIENT_SECRET || "__GDRIVE_CLIENT_SECRET__";
|
|
46
|
+
|
|
47
|
+
function getClientId() {
|
|
48
|
+
return BUNDLED_CLIENT_ID;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function getClientSecret() {
|
|
52
|
+
return BUNDLED_CLIENT_SECRET;
|
|
51
53
|
}
|
|
52
54
|
|
|
53
55
|
/**
|
|
@@ -62,17 +64,17 @@ function readCredentials() {
|
|
|
62
64
|
*/
|
|
63
65
|
async function getAccessToken() {
|
|
64
66
|
let creds = readCredentials();
|
|
65
|
-
const
|
|
67
|
+
const clientId = getClientId();
|
|
66
68
|
|
|
67
69
|
// Still valid (>60s remaining)?
|
|
68
70
|
if (creds.expiry_date && creds.expiry_date > Date.now() + 60 * 1000) {
|
|
69
71
|
return creds.access_token;
|
|
70
72
|
}
|
|
71
73
|
|
|
72
|
-
// Refresh
|
|
74
|
+
// Refresh token — Google requires client_secret even for desktop apps
|
|
73
75
|
const postData = [
|
|
74
|
-
`client_id=${encodeURIComponent(
|
|
75
|
-
`client_secret=${encodeURIComponent(
|
|
76
|
+
`client_id=${encodeURIComponent(clientId)}`,
|
|
77
|
+
`client_secret=${encodeURIComponent(getClientSecret())}`,
|
|
76
78
|
`refresh_token=${encodeURIComponent(creds.refresh_token)}`,
|
|
77
79
|
"grant_type=refresh_token",
|
|
78
80
|
].join("&");
|
|
@@ -117,43 +119,194 @@ async function getAccessToken() {
|
|
|
117
119
|
}
|
|
118
120
|
|
|
119
121
|
/**
|
|
120
|
-
* Make a Google Drive API request.
|
|
122
|
+
* Make a Google Drive API request (GET, POST, PATCH, etc.).
|
|
121
123
|
*/
|
|
122
|
-
function driveRequest(
|
|
124
|
+
function driveRequest(apiPath, token, method = "GET", body = null) {
|
|
123
125
|
return new Promise((resolve, reject) => {
|
|
126
|
+
const headers = { Authorization: `Bearer ${token}` };
|
|
127
|
+
if (body) {
|
|
128
|
+
headers["Content-Type"] = "application/json";
|
|
129
|
+
headers["Content-Length"] = Buffer.byteLength(body);
|
|
130
|
+
}
|
|
131
|
+
const req = https.request(
|
|
132
|
+
{ hostname: "www.googleapis.com", path: apiPath, method, headers },
|
|
133
|
+
(res) => {
|
|
134
|
+
let data = "";
|
|
135
|
+
res.on("data", (chunk) => (data += chunk));
|
|
136
|
+
res.on("end", () => {
|
|
137
|
+
if (res.statusCode >= 200 && res.statusCode < 300) {
|
|
138
|
+
try {
|
|
139
|
+
resolve(JSON.parse(data));
|
|
140
|
+
} catch {
|
|
141
|
+
resolve(data);
|
|
142
|
+
}
|
|
143
|
+
} else {
|
|
144
|
+
reject(
|
|
145
|
+
new Error(`Drive API ${method} (${res.statusCode}): ${data}`),
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
},
|
|
150
|
+
);
|
|
151
|
+
req.on("error", reject);
|
|
152
|
+
if (body) req.write(body);
|
|
153
|
+
req.end();
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Multipart upload to Google Drive (for creating/updating file content).
|
|
159
|
+
*/
|
|
160
|
+
function driveUploadRequest(
|
|
161
|
+
apiPath,
|
|
162
|
+
token,
|
|
163
|
+
method,
|
|
164
|
+
metadata,
|
|
165
|
+
content,
|
|
166
|
+
mimeType,
|
|
167
|
+
) {
|
|
168
|
+
return new Promise((resolve, reject) => {
|
|
169
|
+
const boundary = "dash_boundary_" + Date.now().toString(36);
|
|
170
|
+
const body =
|
|
171
|
+
`--${boundary}\r\nContent-Type: application/json; charset=UTF-8\r\n\r\n` +
|
|
172
|
+
`${JSON.stringify(metadata)}\r\n` +
|
|
173
|
+
`--${boundary}\r\nContent-Type: ${mimeType}\r\n\r\n` +
|
|
174
|
+
`${content}\r\n` +
|
|
175
|
+
`--${boundary}--`;
|
|
176
|
+
|
|
124
177
|
const req = https.request(
|
|
125
178
|
{
|
|
126
179
|
hostname: "www.googleapis.com",
|
|
127
|
-
path,
|
|
128
|
-
method
|
|
129
|
-
headers: {
|
|
180
|
+
path: apiPath,
|
|
181
|
+
method,
|
|
182
|
+
headers: {
|
|
183
|
+
Authorization: `Bearer ${token}`,
|
|
184
|
+
"Content-Type": `multipart/related; boundary=${boundary}`,
|
|
185
|
+
"Content-Length": Buffer.byteLength(body),
|
|
186
|
+
},
|
|
130
187
|
},
|
|
131
188
|
(res) => {
|
|
132
189
|
let data = "";
|
|
133
|
-
res.on("data", (
|
|
190
|
+
res.on("data", (c) => (data += c));
|
|
134
191
|
res.on("end", () => {
|
|
135
|
-
if (res.statusCode
|
|
136
|
-
|
|
192
|
+
if (res.statusCode >= 200 && res.statusCode < 300) {
|
|
193
|
+
try {
|
|
194
|
+
resolve(JSON.parse(data));
|
|
195
|
+
} catch {
|
|
196
|
+
resolve(data);
|
|
197
|
+
}
|
|
137
198
|
} else {
|
|
138
|
-
reject(
|
|
199
|
+
reject(
|
|
200
|
+
new Error(`Drive upload ${method} (${res.statusCode}): ${data}`),
|
|
201
|
+
);
|
|
139
202
|
}
|
|
140
203
|
});
|
|
141
204
|
},
|
|
142
205
|
);
|
|
143
206
|
req.on("error", reject);
|
|
207
|
+
req.write(body);
|
|
144
208
|
req.end();
|
|
145
209
|
});
|
|
146
210
|
}
|
|
147
211
|
|
|
212
|
+
// ── Tool helper functions ────────────────────────────────────────────
|
|
213
|
+
|
|
214
|
+
async function listFolder(token, folderId) {
|
|
215
|
+
const q = encodeURIComponent(`'${folderId}' in parents and trashed=false`);
|
|
216
|
+
const fields = encodeURIComponent("files(id,name,mimeType)");
|
|
217
|
+
const result = await driveRequest(
|
|
218
|
+
`/drive/v3/files?q=${q}&fields=${fields}&pageSize=200`,
|
|
219
|
+
token,
|
|
220
|
+
);
|
|
221
|
+
return result.files || [];
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
async function createFolder(token, parentId, name) {
|
|
225
|
+
const body = JSON.stringify({
|
|
226
|
+
name,
|
|
227
|
+
mimeType: "application/vnd.google-apps.folder",
|
|
228
|
+
parents: [parentId],
|
|
229
|
+
});
|
|
230
|
+
return await driveRequest(
|
|
231
|
+
"/drive/v3/files?fields=id,name",
|
|
232
|
+
token,
|
|
233
|
+
"POST",
|
|
234
|
+
body,
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
async function readFile(token, fileId) {
|
|
239
|
+
return await driveRequest(`/drive/v3/files/${fileId}?alt=media`, token);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
async function writeFile(token, parentId, name, content, mimeType) {
|
|
243
|
+
mimeType = mimeType || "text/markdown";
|
|
244
|
+
// Upsert: check if file with this name already exists in parent
|
|
245
|
+
const escapedName = name.replace(/'/g, "\\'");
|
|
246
|
+
const q = encodeURIComponent(
|
|
247
|
+
`name='${escapedName}' and '${parentId}' in parents and trashed=false`,
|
|
248
|
+
);
|
|
249
|
+
const existing = await driveRequest(
|
|
250
|
+
`/drive/v3/files?q=${q}&fields=files(id)`,
|
|
251
|
+
token,
|
|
252
|
+
);
|
|
253
|
+
const existingId = existing.files?.[0]?.id;
|
|
254
|
+
|
|
255
|
+
if (existingId) {
|
|
256
|
+
const result = await driveUploadRequest(
|
|
257
|
+
`/upload/drive/v3/files/${existingId}?uploadType=multipart&fields=id,name`,
|
|
258
|
+
token,
|
|
259
|
+
"PATCH",
|
|
260
|
+
{},
|
|
261
|
+
content,
|
|
262
|
+
mimeType,
|
|
263
|
+
);
|
|
264
|
+
return { ...result, _action: "updated" };
|
|
265
|
+
} else {
|
|
266
|
+
const result = await driveUploadRequest(
|
|
267
|
+
`/upload/drive/v3/files?uploadType=multipart&fields=id,name`,
|
|
268
|
+
token,
|
|
269
|
+
"POST",
|
|
270
|
+
{ name, parents: [parentId], mimeType },
|
|
271
|
+
content,
|
|
272
|
+
mimeType,
|
|
273
|
+
);
|
|
274
|
+
return { ...result, _action: "created" };
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
async function resolvePath(token, pathStr) {
|
|
279
|
+
const segments = pathStr
|
|
280
|
+
.split("/")
|
|
281
|
+
.map((s) => s.trim())
|
|
282
|
+
.filter(Boolean);
|
|
283
|
+
let currentId = "root";
|
|
284
|
+
for (const segment of segments) {
|
|
285
|
+
const children = await listFolder(token, currentId);
|
|
286
|
+
const match = children.find((c) => c.name === segment);
|
|
287
|
+
if (!match) return null;
|
|
288
|
+
currentId = match.id;
|
|
289
|
+
}
|
|
290
|
+
return currentId;
|
|
291
|
+
}
|
|
292
|
+
|
|
148
293
|
// ── Auth subcommand ──────────────────────────────────────────────────
|
|
149
294
|
if (process.argv[2] === "auth") {
|
|
150
295
|
(async () => {
|
|
151
296
|
try {
|
|
152
297
|
const http = require("http");
|
|
153
298
|
const { URL } = require("url");
|
|
154
|
-
const
|
|
299
|
+
const clientId = getClientId();
|
|
300
|
+
|
|
301
|
+
const scopes = ["https://www.googleapis.com/auth/drive"];
|
|
302
|
+
|
|
303
|
+
// PKCE: generate code verifier + challenge (additional security layer)
|
|
304
|
+
const codeVerifier = crypto.randomBytes(32).toString("base64url");
|
|
305
|
+
const codeChallenge = crypto
|
|
306
|
+
.createHash("sha256")
|
|
307
|
+
.update(codeVerifier)
|
|
308
|
+
.digest("base64url");
|
|
155
309
|
|
|
156
|
-
const scopes = ["https://www.googleapis.com/auth/drive.readonly"];
|
|
157
310
|
let redirectUri;
|
|
158
311
|
|
|
159
312
|
// Start local server to catch the callback
|
|
@@ -166,11 +319,12 @@ if (process.argv[2] === "auth") {
|
|
|
166
319
|
return;
|
|
167
320
|
}
|
|
168
321
|
|
|
169
|
-
// Exchange code for tokens
|
|
322
|
+
// Exchange code for tokens (PKCE code_verifier + client_secret)
|
|
170
323
|
const postData = [
|
|
171
324
|
`code=${encodeURIComponent(code)}`,
|
|
172
|
-
`client_id=${encodeURIComponent(
|
|
173
|
-
`client_secret=${encodeURIComponent(
|
|
325
|
+
`client_id=${encodeURIComponent(clientId)}`,
|
|
326
|
+
`client_secret=${encodeURIComponent(getClientSecret())}`,
|
|
327
|
+
`code_verifier=${encodeURIComponent(codeVerifier)}`,
|
|
174
328
|
`redirect_uri=${encodeURIComponent(redirectUri)}`,
|
|
175
329
|
`grant_type=authorization_code`,
|
|
176
330
|
].join("&");
|
|
@@ -246,12 +400,14 @@ if (process.argv[2] === "auth") {
|
|
|
246
400
|
|
|
247
401
|
const authUrl =
|
|
248
402
|
`https://accounts.google.com/o/oauth2/v2/auth?` +
|
|
249
|
-
`client_id=${encodeURIComponent(
|
|
403
|
+
`client_id=${encodeURIComponent(clientId)}` +
|
|
250
404
|
`&redirect_uri=${encodeURIComponent(redirectUri)}` +
|
|
251
405
|
`&response_type=code` +
|
|
252
406
|
`&scope=${encodeURIComponent(scopes.join(" "))}` +
|
|
253
407
|
`&access_type=offline` +
|
|
254
|
-
`&prompt=consent
|
|
408
|
+
`&prompt=consent` +
|
|
409
|
+
`&code_challenge=${encodeURIComponent(codeChallenge)}` +
|
|
410
|
+
`&code_challenge_method=S256`;
|
|
255
411
|
|
|
256
412
|
const { exec } = require("child_process");
|
|
257
413
|
exec(`open "${authUrl}"`);
|
|
@@ -280,72 +436,269 @@ if (process.argv[2] === "auth") {
|
|
|
280
436
|
inputSchema: {
|
|
281
437
|
type: "object",
|
|
282
438
|
properties: {
|
|
283
|
-
query: {
|
|
439
|
+
query: { type: "string", description: "Search query" },
|
|
440
|
+
},
|
|
441
|
+
required: ["query"],
|
|
442
|
+
},
|
|
443
|
+
},
|
|
444
|
+
{
|
|
445
|
+
name: "list_folder",
|
|
446
|
+
description:
|
|
447
|
+
"List children of a Google Drive folder by ID. Use 'root' for My Drive.",
|
|
448
|
+
inputSchema: {
|
|
449
|
+
type: "object",
|
|
450
|
+
properties: {
|
|
451
|
+
folderId: {
|
|
284
452
|
type: "string",
|
|
285
|
-
description: "
|
|
453
|
+
description: "Folder ID, or 'root' for My Drive",
|
|
286
454
|
},
|
|
287
455
|
},
|
|
288
|
-
required: ["
|
|
456
|
+
required: ["folderId"],
|
|
457
|
+
},
|
|
458
|
+
},
|
|
459
|
+
{
|
|
460
|
+
name: "create_folder",
|
|
461
|
+
description:
|
|
462
|
+
"Create a new folder inside a parent folder. Returns the new folder's ID.",
|
|
463
|
+
inputSchema: {
|
|
464
|
+
type: "object",
|
|
465
|
+
properties: {
|
|
466
|
+
parentId: { type: "string", description: "Parent folder ID" },
|
|
467
|
+
name: { type: "string", description: "New folder name" },
|
|
468
|
+
},
|
|
469
|
+
required: ["parentId", "name"],
|
|
470
|
+
},
|
|
471
|
+
},
|
|
472
|
+
{
|
|
473
|
+
name: "read_file",
|
|
474
|
+
description:
|
|
475
|
+
"Read the text content of a Drive file by ID. Plain text files only.",
|
|
476
|
+
inputSchema: {
|
|
477
|
+
type: "object",
|
|
478
|
+
properties: {
|
|
479
|
+
fileId: { type: "string", description: "File ID" },
|
|
480
|
+
},
|
|
481
|
+
required: ["fileId"],
|
|
482
|
+
},
|
|
483
|
+
},
|
|
484
|
+
{
|
|
485
|
+
name: "write_file",
|
|
486
|
+
description:
|
|
487
|
+
"Create or update a text file in a folder (upsert by name).",
|
|
488
|
+
inputSchema: {
|
|
489
|
+
type: "object",
|
|
490
|
+
properties: {
|
|
491
|
+
parentId: { type: "string", description: "Parent folder ID" },
|
|
492
|
+
name: { type: "string", description: "File name" },
|
|
493
|
+
content: { type: "string", description: "File content" },
|
|
494
|
+
mimeType: {
|
|
495
|
+
type: "string",
|
|
496
|
+
description: "Optional MIME type (default: text/markdown)",
|
|
497
|
+
},
|
|
498
|
+
},
|
|
499
|
+
required: ["parentId", "name", "content"],
|
|
500
|
+
},
|
|
501
|
+
},
|
|
502
|
+
{
|
|
503
|
+
name: "resolve_path",
|
|
504
|
+
description:
|
|
505
|
+
"Walk a slash-separated path from My Drive root and return the final file/folder ID, or null if any segment is missing.",
|
|
506
|
+
inputSchema: {
|
|
507
|
+
type: "object",
|
|
508
|
+
properties: {
|
|
509
|
+
path: {
|
|
510
|
+
type: "string",
|
|
511
|
+
description:
|
|
512
|
+
"Slash-separated path, e.g. 'Sales Pipeline/AMER/ENT/Acme Corp'",
|
|
513
|
+
},
|
|
514
|
+
},
|
|
515
|
+
required: ["path"],
|
|
289
516
|
},
|
|
290
517
|
},
|
|
291
518
|
],
|
|
292
519
|
}));
|
|
293
520
|
|
|
294
521
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
content: [
|
|
298
|
-
{
|
|
299
|
-
type: "text",
|
|
300
|
-
text: `Unknown tool: ${request.params.name}`,
|
|
301
|
-
},
|
|
302
|
-
],
|
|
303
|
-
isError: true,
|
|
304
|
-
};
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
const query = request.params.arguments?.query;
|
|
308
|
-
if (!query) {
|
|
309
|
-
return {
|
|
310
|
-
content: [{ type: "text", text: "Missing required argument: query" }],
|
|
311
|
-
isError: true,
|
|
312
|
-
};
|
|
313
|
-
}
|
|
522
|
+
const toolName = request.params.name;
|
|
523
|
+
const args = request.params.arguments || {};
|
|
314
524
|
|
|
315
525
|
try {
|
|
316
526
|
const token = await getAccessToken();
|
|
317
|
-
const encodedQuery = encodeURIComponent(
|
|
318
|
-
`fullText contains '${query.replace(/'/g, "\\'")}'`,
|
|
319
|
-
);
|
|
320
|
-
const result = await driveRequest(
|
|
321
|
-
`/drive/v3/files?q=${encodedQuery}&fields=files(id,name,mimeType,modifiedTime,webViewLink)&pageSize=20`,
|
|
322
|
-
token,
|
|
323
|
-
);
|
|
324
527
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
{
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
528
|
+
switch (toolName) {
|
|
529
|
+
case "search": {
|
|
530
|
+
const query = args.query;
|
|
531
|
+
if (!query) {
|
|
532
|
+
return {
|
|
533
|
+
content: [
|
|
534
|
+
{ type: "text", text: "Missing required argument: query" },
|
|
535
|
+
],
|
|
536
|
+
isError: true,
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
const encodedQuery = encodeURIComponent(
|
|
540
|
+
`fullText contains '${query.replace(/'/g, "\\'")}'`,
|
|
541
|
+
);
|
|
542
|
+
const result = await driveRequest(
|
|
543
|
+
`/drive/v3/files?q=${encodedQuery}&fields=files(id,name,mimeType,modifiedTime,webViewLink)&pageSize=20`,
|
|
544
|
+
token,
|
|
545
|
+
);
|
|
546
|
+
const files = result.files || [];
|
|
547
|
+
if (files.length === 0) {
|
|
548
|
+
return {
|
|
549
|
+
content: [
|
|
550
|
+
{ type: "text", text: `No files found for query: ${query}` },
|
|
551
|
+
],
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
const lines = files.map(
|
|
555
|
+
(f) =>
|
|
556
|
+
`${f.name} (${f.mimeType})${f.webViewLink ? ` - ${f.webViewLink}` : ""}`,
|
|
557
|
+
);
|
|
558
|
+
return {
|
|
559
|
+
content: [
|
|
560
|
+
{
|
|
561
|
+
type: "text",
|
|
562
|
+
text: `Found ${files.length} files:\n${lines.join("\n")}`,
|
|
563
|
+
},
|
|
564
|
+
],
|
|
565
|
+
};
|
|
566
|
+
}
|
|
336
567
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
568
|
+
case "list_folder": {
|
|
569
|
+
if (!args.folderId) {
|
|
570
|
+
return {
|
|
571
|
+
content: [
|
|
572
|
+
{
|
|
573
|
+
type: "text",
|
|
574
|
+
text: "Missing required argument: folderId",
|
|
575
|
+
},
|
|
576
|
+
],
|
|
577
|
+
isError: true,
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
const children = await listFolder(token, args.folderId);
|
|
581
|
+
if (children.length === 0) {
|
|
582
|
+
return {
|
|
583
|
+
content: [{ type: "text", text: "Folder is empty." }],
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
const childLines = children.map(
|
|
587
|
+
(f) => `${f.name} (${f.mimeType}) [${f.id}]`,
|
|
588
|
+
);
|
|
589
|
+
return {
|
|
590
|
+
content: [
|
|
591
|
+
{
|
|
592
|
+
type: "text",
|
|
593
|
+
text: `${children.length} children:\n${childLines.join("\n")}`,
|
|
594
|
+
},
|
|
595
|
+
],
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
case "create_folder": {
|
|
600
|
+
if (!args.parentId || !args.name) {
|
|
601
|
+
return {
|
|
602
|
+
content: [
|
|
603
|
+
{
|
|
604
|
+
type: "text",
|
|
605
|
+
text: "Missing required arguments: parentId, name",
|
|
606
|
+
},
|
|
607
|
+
],
|
|
608
|
+
isError: true,
|
|
609
|
+
};
|
|
610
|
+
}
|
|
611
|
+
const folder = await createFolder(token, args.parentId, args.name);
|
|
612
|
+
return {
|
|
613
|
+
content: [
|
|
614
|
+
{
|
|
615
|
+
type: "text",
|
|
616
|
+
text: `Created folder "${folder.name}" [${folder.id}]`,
|
|
617
|
+
},
|
|
618
|
+
],
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
case "read_file": {
|
|
623
|
+
if (!args.fileId) {
|
|
624
|
+
return {
|
|
625
|
+
content: [
|
|
626
|
+
{ type: "text", text: "Missing required argument: fileId" },
|
|
627
|
+
],
|
|
628
|
+
isError: true,
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
const content = await readFile(token, args.fileId);
|
|
632
|
+
return {
|
|
633
|
+
content: [
|
|
634
|
+
{
|
|
635
|
+
type: "text",
|
|
636
|
+
text:
|
|
637
|
+
typeof content === "string"
|
|
638
|
+
? content
|
|
639
|
+
: JSON.stringify(content),
|
|
640
|
+
},
|
|
641
|
+
],
|
|
642
|
+
};
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
case "write_file": {
|
|
646
|
+
if (!args.parentId || !args.name || args.content == null) {
|
|
647
|
+
return {
|
|
648
|
+
content: [
|
|
649
|
+
{
|
|
650
|
+
type: "text",
|
|
651
|
+
text: "Missing required arguments: parentId, name, content",
|
|
652
|
+
},
|
|
653
|
+
],
|
|
654
|
+
isError: true,
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
const writeResult = await writeFile(
|
|
658
|
+
token,
|
|
659
|
+
args.parentId,
|
|
660
|
+
args.name,
|
|
661
|
+
args.content,
|
|
662
|
+
args.mimeType,
|
|
663
|
+
);
|
|
664
|
+
return {
|
|
665
|
+
content: [
|
|
666
|
+
{
|
|
667
|
+
type: "text",
|
|
668
|
+
text: `${writeResult._action} "${writeResult.name}" [${writeResult.id}]`,
|
|
669
|
+
},
|
|
670
|
+
],
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
case "resolve_path": {
|
|
675
|
+
if (!args.path) {
|
|
676
|
+
return {
|
|
677
|
+
content: [
|
|
678
|
+
{ type: "text", text: "Missing required argument: path" },
|
|
679
|
+
],
|
|
680
|
+
isError: true,
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
const resolvedId = await resolvePath(token, args.path);
|
|
684
|
+
if (resolvedId) {
|
|
685
|
+
return {
|
|
686
|
+
content: [
|
|
687
|
+
{ type: "text", text: `Resolved to ID: ${resolvedId}` },
|
|
688
|
+
],
|
|
689
|
+
};
|
|
690
|
+
}
|
|
691
|
+
return {
|
|
692
|
+
content: [{ type: "text", text: `Path not found: ${args.path}` }],
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
default:
|
|
697
|
+
return {
|
|
698
|
+
content: [{ type: "text", text: `Unknown tool: ${toolName}` }],
|
|
699
|
+
isError: true,
|
|
700
|
+
};
|
|
701
|
+
}
|
|
349
702
|
} catch (err) {
|
|
350
703
|
return {
|
|
351
704
|
content: [{ type: "text", text: `Error: ${err.message}` }],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trops/dash-core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.340",
|
|
4
4
|
"description": "Core framework for Dash dashboard applications",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.esm.js",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"scripts": {
|
|
20
20
|
"build": "npm run build:renderer && npm run build:electron",
|
|
21
21
|
"build:renderer": "rollup -c rollup.config.renderer.mjs",
|
|
22
|
-
"build:electron": "rollup -c rollup.config.electron.mjs && mkdir -p dist/mcp && cp electron/mcp/mcpServerCatalog.json dist/mcp/ && rm -rf dist/mcp/servers && cp -r electron/mcp/servers dist/mcp/",
|
|
22
|
+
"build:electron": "rollup -c rollup.config.electron.mjs && mkdir -p dist/mcp && cp electron/mcp/mcpServerCatalog.json dist/mcp/ && rm -rf dist/mcp/servers && cp -r electron/mcp/servers dist/mcp/ && node scripts/inject-secrets.js",
|
|
23
23
|
"clean": "rm -rf dist",
|
|
24
24
|
"prepublishOnly": "npm run clean && npm run build",
|
|
25
25
|
"test:mcp": "node --test electron/controller/mcpController.test.js electron/mcp/mcpServerCatalog.test.js",
|