@trops/dash-core 0.1.75 → 0.1.77

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,220 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * gdrive-server.mjs
4
+ *
5
+ * Local Google Drive MCP server wrapper that fixes the upstream OAuth2 bug.
6
+ *
7
+ * The upstream @modelcontextprotocol/server-gdrive creates OAuth2 without
8
+ * client_id/client_secret, which prevents token refresh after ~1 hour.
9
+ * This wrapper reads both the credentials file AND the OAuth keys file
10
+ * to properly initialize OAuth2 with client credentials.
11
+ *
12
+ * Env vars:
13
+ * GDRIVE_CREDENTIALS_PATH - path to saved credentials (access_token, refresh_token)
14
+ * GDRIVE_OAUTH_KEYS_PATH - path to gcp-oauth.keys.json (client_id, client_secret)
15
+ */
16
+
17
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
18
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
19
+ import {
20
+ CallToolRequestSchema,
21
+ ListResourcesRequestSchema,
22
+ ListToolsRequestSchema,
23
+ ReadResourceRequestSchema,
24
+ } from "@modelcontextprotocol/sdk/types.js";
25
+ import fs from "fs";
26
+ import { google } from "googleapis";
27
+
28
+ const drive = google.drive("v3");
29
+
30
+ const server = new Server(
31
+ {
32
+ name: "dash/gdrive",
33
+ version: "1.0.0",
34
+ },
35
+ {
36
+ capabilities: {
37
+ resources: {},
38
+ tools: {},
39
+ },
40
+ }
41
+ );
42
+
43
+ // --- List Resources ---
44
+ server.setRequestHandler(ListResourcesRequestSchema, async (request) => {
45
+ const pageSize = 10;
46
+ const params = {
47
+ pageSize,
48
+ fields: "nextPageToken, files(id, name, mimeType)",
49
+ };
50
+ if (request.params?.cursor) {
51
+ params.pageToken = request.params.cursor;
52
+ }
53
+ const res = await drive.files.list(params);
54
+ const files = res.data.files;
55
+ return {
56
+ resources: files.map((file) => ({
57
+ uri: `gdrive:///${file.id}`,
58
+ mimeType: file.mimeType,
59
+ name: file.name,
60
+ })),
61
+ nextCursor: res.data.nextPageToken,
62
+ };
63
+ });
64
+
65
+ // --- Read Resource ---
66
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
67
+ const fileId = request.params.uri.replace("gdrive:///", "");
68
+ const file = await drive.files.get({
69
+ fileId,
70
+ fields: "mimeType",
71
+ });
72
+
73
+ // Google Docs/Sheets/etc need export
74
+ if (file.data.mimeType?.startsWith("application/vnd.google-apps")) {
75
+ let exportMimeType;
76
+ switch (file.data.mimeType) {
77
+ case "application/vnd.google-apps.document":
78
+ exportMimeType = "text/markdown";
79
+ break;
80
+ case "application/vnd.google-apps.spreadsheet":
81
+ exportMimeType = "text/csv";
82
+ break;
83
+ case "application/vnd.google-apps.presentation":
84
+ exportMimeType = "text/plain";
85
+ break;
86
+ case "application/vnd.google-apps.drawing":
87
+ exportMimeType = "image/png";
88
+ break;
89
+ default:
90
+ exportMimeType = "text/plain";
91
+ }
92
+ const res = await drive.files.export(
93
+ { fileId, mimeType: exportMimeType },
94
+ { responseType: "text" }
95
+ );
96
+ return {
97
+ contents: [
98
+ {
99
+ uri: request.params.uri,
100
+ mimeType: exportMimeType,
101
+ text: res.data,
102
+ },
103
+ ],
104
+ };
105
+ }
106
+
107
+ // Regular files — download content
108
+ const res = await drive.files.get(
109
+ { fileId, alt: "media" },
110
+ { responseType: "arraybuffer" }
111
+ );
112
+ const mimeType = file.data.mimeType || "application/octet-stream";
113
+ if (mimeType.startsWith("text/") || mimeType === "application/json") {
114
+ return {
115
+ contents: [
116
+ {
117
+ uri: request.params.uri,
118
+ mimeType,
119
+ text: Buffer.from(res.data).toString("utf-8"),
120
+ },
121
+ ],
122
+ };
123
+ }
124
+ return {
125
+ contents: [
126
+ {
127
+ uri: request.params.uri,
128
+ mimeType,
129
+ blob: Buffer.from(res.data).toString("base64"),
130
+ },
131
+ ],
132
+ };
133
+ });
134
+
135
+ // --- List Tools ---
136
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
137
+ return {
138
+ tools: [
139
+ {
140
+ name: "search",
141
+ description: "Search for files in Google Drive",
142
+ inputSchema: {
143
+ type: "object",
144
+ properties: {
145
+ query: {
146
+ type: "string",
147
+ description: "Search query",
148
+ },
149
+ },
150
+ required: ["query"],
151
+ },
152
+ },
153
+ ],
154
+ };
155
+ });
156
+
157
+ // --- Call Tool ---
158
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
159
+ if (request.params.name === "search") {
160
+ const userQuery = request.params.arguments?.query;
161
+ const escapedQuery = userQuery
162
+ .replace(/\\/g, "\\\\")
163
+ .replace(/'/g, "\\'");
164
+ const formattedQuery = `fullText contains '${escapedQuery}'`;
165
+ const res = await drive.files.list({
166
+ q: formattedQuery,
167
+ pageSize: 10,
168
+ fields: "files(id, name, mimeType, modifiedTime, size)",
169
+ });
170
+ const fileList = res.data.files
171
+ ?.map((file) => `${file.name} (${file.mimeType})`)
172
+ .join("\n");
173
+ return {
174
+ content: [
175
+ {
176
+ type: "text",
177
+ text: `Found ${res.data.files?.length ?? 0} files:\n${fileList}`,
178
+ },
179
+ ],
180
+ isError: false,
181
+ };
182
+ }
183
+ throw new Error("Tool not found");
184
+ });
185
+
186
+ // --- Load credentials and start server ---
187
+ const credentialsPath = process.env.GDRIVE_CREDENTIALS_PATH;
188
+ const oauthKeysPath = process.env.GDRIVE_OAUTH_KEYS_PATH;
189
+
190
+ if (!credentialsPath || !fs.existsSync(credentialsPath)) {
191
+ console.error(
192
+ "Credentials not found. Please run OAuth auth flow first."
193
+ );
194
+ process.exit(1);
195
+ }
196
+
197
+ const credentials = JSON.parse(fs.readFileSync(credentialsPath, "utf-8"));
198
+
199
+ // THE FIX: Read client_id and client_secret from the OAuth keys file
200
+ // so that googleapis can refresh the access_token when it expires.
201
+ let clientId, clientSecret;
202
+ if (oauthKeysPath && fs.existsSync(oauthKeysPath)) {
203
+ const keysFile = JSON.parse(fs.readFileSync(oauthKeysPath, "utf-8"));
204
+ const keyData = keysFile.installed || keysFile.web;
205
+ if (keyData) {
206
+ clientId = keyData.client_id;
207
+ clientSecret = keyData.client_secret;
208
+ }
209
+ }
210
+
211
+ const auth = new google.auth.OAuth2(clientId, clientSecret);
212
+ auth.setCredentials(credentials);
213
+ google.options({ auth });
214
+
215
+ console.error(
216
+ `Credentials loaded (refresh_token: ${credentials.refresh_token ? "present" : "missing"}). Starting server.`
217
+ );
218
+
219
+ const transport = new StdioServerTransport();
220
+ await server.connect(transport);
@@ -201,12 +201,13 @@
201
201
  "mcpConfig": {
202
202
  "transport": "stdio",
203
203
  "command": "npx",
204
- "args": ["-y", "@modelcontextprotocol/server-gdrive"],
204
+ "args": ["-y", "-p", "@modelcontextprotocol/server-gdrive", "node", "{{MCP_DIR}}/gdrive-server.mjs"],
205
205
  "envMapping": {
206
206
  "GDRIVE_OAUTH_PATH": "oauthKeysPath"
207
207
  },
208
208
  "staticEnv": {
209
- "GDRIVE_CREDENTIALS_PATH": "~/.gdrive-mcp/credentials.json"
209
+ "GDRIVE_CREDENTIALS_PATH": "~/.gdrive-mcp/credentials.json",
210
+ "GDRIVE_OAUTH_KEYS_PATH": "~/.gdrive-mcp/gcp-oauth.keys.json"
210
211
  }
211
212
  },
212
213
  "authCommand": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trops/dash-core",
3
- "version": "0.1.75",
3
+ "version": "0.1.77",
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/",
22
+ "build:electron": "rollup -c rollup.config.electron.mjs && mkdir -p dist/mcp && cp electron/mcp/mcpServerCatalog.json electron/mcp/gdrive-server.mjs dist/mcp/",
23
23
  "clean": "rm -rf dist",
24
24
  "prepublishOnly": "npm run clean && npm run build",
25
25
  "prettify": "prettier --write \"src/**/*.{js,jsx,ts,tsx}\" \"electron/**/*.js\""