@mediagraph/mcp 1.0.2 → 1.0.4
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.js +247 -9
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -69,10 +69,11 @@ var OAuthHandler = class {
|
|
|
69
69
|
});
|
|
70
70
|
return `${this.config.oauthUrl}/oauth/authorize?${params.toString()}`;
|
|
71
71
|
}
|
|
72
|
+
callbackPromise = null;
|
|
72
73
|
/**
|
|
73
|
-
* Start
|
|
74
|
+
* Start the callback server and wait for it to be ready
|
|
74
75
|
*/
|
|
75
|
-
async
|
|
76
|
+
async startCallbackServer() {
|
|
76
77
|
return new Promise((resolve, reject) => {
|
|
77
78
|
this.callbackServer = createServer((req, res) => {
|
|
78
79
|
const url = new URL2(req.url || "/", `http://localhost:${this.config.redirectPort}`);
|
|
@@ -93,7 +94,8 @@ var OAuthHandler = class {
|
|
|
93
94
|
</html>
|
|
94
95
|
`);
|
|
95
96
|
this.stopCallbackServer();
|
|
96
|
-
reject(new Error(errorDescription || error));
|
|
97
|
+
this.callbackPromise?.reject(new Error(errorDescription || error));
|
|
98
|
+
this.callbackPromise = null;
|
|
97
99
|
return;
|
|
98
100
|
}
|
|
99
101
|
if (!code || !state) {
|
|
@@ -108,7 +110,8 @@ var OAuthHandler = class {
|
|
|
108
110
|
</html>
|
|
109
111
|
`);
|
|
110
112
|
this.stopCallbackServer();
|
|
111
|
-
reject(new Error("Missing authorization code or state"));
|
|
113
|
+
this.callbackPromise?.reject(new Error("Missing authorization code or state"));
|
|
114
|
+
this.callbackPromise = null;
|
|
112
115
|
return;
|
|
113
116
|
}
|
|
114
117
|
if (state !== this.state) {
|
|
@@ -123,7 +126,8 @@ var OAuthHandler = class {
|
|
|
123
126
|
</html>
|
|
124
127
|
`);
|
|
125
128
|
this.stopCallbackServer();
|
|
126
|
-
reject(new Error("State parameter mismatch"));
|
|
129
|
+
this.callbackPromise?.reject(new Error("State parameter mismatch"));
|
|
130
|
+
this.callbackPromise = null;
|
|
127
131
|
return;
|
|
128
132
|
}
|
|
129
133
|
res.writeHead(200, { "Content-Type": "text/html" });
|
|
@@ -137,20 +141,43 @@ var OAuthHandler = class {
|
|
|
137
141
|
</html>
|
|
138
142
|
`);
|
|
139
143
|
this.stopCallbackServer();
|
|
140
|
-
resolve({ code, state });
|
|
144
|
+
this.callbackPromise?.resolve({ code, state });
|
|
145
|
+
this.callbackPromise = null;
|
|
141
146
|
} else {
|
|
142
147
|
res.writeHead(404);
|
|
143
148
|
res.end("Not found");
|
|
144
149
|
}
|
|
145
150
|
});
|
|
151
|
+
this.callbackServer.on("error", (err) => {
|
|
152
|
+
reject(err);
|
|
153
|
+
});
|
|
146
154
|
this.callbackServer.listen(this.config.redirectPort, () => {
|
|
155
|
+
resolve();
|
|
147
156
|
});
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Wait for the OAuth callback (server must be started first)
|
|
161
|
+
*/
|
|
162
|
+
async waitForCallback() {
|
|
163
|
+
return new Promise((resolve, reject) => {
|
|
164
|
+
this.callbackPromise = { resolve, reject };
|
|
148
165
|
setTimeout(() => {
|
|
149
|
-
this.
|
|
150
|
-
|
|
166
|
+
if (this.callbackPromise) {
|
|
167
|
+
this.stopCallbackServer();
|
|
168
|
+
this.callbackPromise.reject(new Error("Authorization timed out"));
|
|
169
|
+
this.callbackPromise = null;
|
|
170
|
+
}
|
|
151
171
|
}, 5 * 60 * 1e3);
|
|
152
172
|
});
|
|
153
173
|
}
|
|
174
|
+
/**
|
|
175
|
+
* Start server and wait for callback (convenience method for CLI)
|
|
176
|
+
*/
|
|
177
|
+
async startAndWaitForCallback() {
|
|
178
|
+
await this.startCallbackServer();
|
|
179
|
+
return this.waitForCallback();
|
|
180
|
+
}
|
|
154
181
|
/**
|
|
155
182
|
* Stop the callback server
|
|
156
183
|
*/
|
|
@@ -984,6 +1011,36 @@ var MediagraphClient = class {
|
|
|
984
1011
|
async addAssetsToUpload(guid, assetIds) {
|
|
985
1012
|
await this.request("POST", `/api/uploads/${guid}/assets`, { body: { asset_ids: assetIds } });
|
|
986
1013
|
}
|
|
1014
|
+
/**
|
|
1015
|
+
* Prepare an asset for upload - returns a signed URL for direct S3 upload
|
|
1016
|
+
*/
|
|
1017
|
+
async prepareAssetUpload(uploadGuid, data) {
|
|
1018
|
+
return this.request("POST", `/api/uploads/${uploadGuid}/assets`, {
|
|
1019
|
+
body: { asset: data }
|
|
1020
|
+
});
|
|
1021
|
+
}
|
|
1022
|
+
/**
|
|
1023
|
+
* Mark an asset as uploaded (triggers processing)
|
|
1024
|
+
*/
|
|
1025
|
+
async setAssetUploaded(assetGuid, skipMeta) {
|
|
1026
|
+
const params = skipMeta ? { skip_meta: "true" } : void 0;
|
|
1027
|
+
return this.request("GET", `/api/assets/${assetGuid}/set_uploaded`, { params });
|
|
1028
|
+
}
|
|
1029
|
+
/**
|
|
1030
|
+
* Upload file data directly to a signed S3 URL
|
|
1031
|
+
*/
|
|
1032
|
+
async uploadToSignedUrl(signedUrl, fileData, contentType) {
|
|
1033
|
+
const response = await fetch(signedUrl, {
|
|
1034
|
+
method: "PUT",
|
|
1035
|
+
body: fileData,
|
|
1036
|
+
headers: {
|
|
1037
|
+
"Content-Type": contentType
|
|
1038
|
+
}
|
|
1039
|
+
});
|
|
1040
|
+
if (!response.ok) {
|
|
1041
|
+
throw new Error(`Failed to upload to S3: ${response.status} ${response.statusText}`);
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
987
1044
|
async setUploadDone(id) {
|
|
988
1045
|
return this.request("POST", `/api/uploads/${id}/set_done`);
|
|
989
1046
|
}
|
|
@@ -2441,6 +2498,181 @@ var downloadTools = {
|
|
|
2441
2498
|
}
|
|
2442
2499
|
};
|
|
2443
2500
|
|
|
2501
|
+
// src/tools/uploads.ts
|
|
2502
|
+
import { readFile, stat } from "fs/promises";
|
|
2503
|
+
import { basename, extname } from "path";
|
|
2504
|
+
var MIME_TYPES = {
|
|
2505
|
+
".jpg": "image/jpeg",
|
|
2506
|
+
".jpeg": "image/jpeg",
|
|
2507
|
+
".png": "image/png",
|
|
2508
|
+
".gif": "image/gif",
|
|
2509
|
+
".webp": "image/webp",
|
|
2510
|
+
".svg": "image/svg+xml",
|
|
2511
|
+
".bmp": "image/bmp",
|
|
2512
|
+
".tiff": "image/tiff",
|
|
2513
|
+
".tif": "image/tiff",
|
|
2514
|
+
".heic": "image/heic",
|
|
2515
|
+
".heif": "image/heif",
|
|
2516
|
+
".mp4": "video/mp4",
|
|
2517
|
+
".mov": "video/quicktime",
|
|
2518
|
+
".avi": "video/x-msvideo",
|
|
2519
|
+
".webm": "video/webm",
|
|
2520
|
+
".mkv": "video/x-matroska",
|
|
2521
|
+
".mp3": "audio/mpeg",
|
|
2522
|
+
".wav": "audio/wav",
|
|
2523
|
+
".aac": "audio/aac",
|
|
2524
|
+
".flac": "audio/flac",
|
|
2525
|
+
".pdf": "application/pdf",
|
|
2526
|
+
".doc": "application/msword",
|
|
2527
|
+
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
2528
|
+
".xls": "application/vnd.ms-excel",
|
|
2529
|
+
".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
2530
|
+
".ppt": "application/vnd.ms-powerpoint",
|
|
2531
|
+
".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
2532
|
+
".zip": "application/zip",
|
|
2533
|
+
".psd": "image/vnd.adobe.photoshop",
|
|
2534
|
+
".ai": "application/postscript",
|
|
2535
|
+
".eps": "application/postscript",
|
|
2536
|
+
".raw": "image/x-raw",
|
|
2537
|
+
".cr2": "image/x-canon-cr2",
|
|
2538
|
+
".nef": "image/x-nikon-nef",
|
|
2539
|
+
".arw": "image/x-sony-arw",
|
|
2540
|
+
".dng": "image/x-adobe-dng"
|
|
2541
|
+
};
|
|
2542
|
+
function getMimeType(filename) {
|
|
2543
|
+
const ext = extname(filename).toLowerCase();
|
|
2544
|
+
return MIME_TYPES[ext] || "application/octet-stream";
|
|
2545
|
+
}
|
|
2546
|
+
var uploadTools = {
|
|
2547
|
+
definitions: [
|
|
2548
|
+
{
|
|
2549
|
+
name: "upload_file",
|
|
2550
|
+
description: `Upload a file from the local filesystem to Mediagraph.
|
|
2551
|
+
This creates a new asset in the user's Mediagraph library.
|
|
2552
|
+
Supports images, videos, audio, documents, and other media files.
|
|
2553
|
+
The file will be processed and thumbnails/previews generated automatically.`,
|
|
2554
|
+
inputSchema: {
|
|
2555
|
+
type: "object",
|
|
2556
|
+
properties: {
|
|
2557
|
+
file_path: {
|
|
2558
|
+
type: "string",
|
|
2559
|
+
description: "Absolute path to the file on the local filesystem"
|
|
2560
|
+
},
|
|
2561
|
+
storage_folder_id: {
|
|
2562
|
+
type: "number",
|
|
2563
|
+
description: "Optional: ID of the storage folder to upload into"
|
|
2564
|
+
}
|
|
2565
|
+
},
|
|
2566
|
+
required: ["file_path"]
|
|
2567
|
+
}
|
|
2568
|
+
},
|
|
2569
|
+
{
|
|
2570
|
+
name: "upload_files",
|
|
2571
|
+
description: `Upload multiple files from the local filesystem to Mediagraph.
|
|
2572
|
+
Creates new assets for each file in the user's Mediagraph library.
|
|
2573
|
+
All files are uploaded in a single upload session.`,
|
|
2574
|
+
inputSchema: {
|
|
2575
|
+
type: "object",
|
|
2576
|
+
properties: {
|
|
2577
|
+
file_paths: {
|
|
2578
|
+
type: "array",
|
|
2579
|
+
items: { type: "string" },
|
|
2580
|
+
description: "Array of absolute paths to files on the local filesystem"
|
|
2581
|
+
},
|
|
2582
|
+
storage_folder_id: {
|
|
2583
|
+
type: "number",
|
|
2584
|
+
description: "Optional: ID of the storage folder to upload into"
|
|
2585
|
+
}
|
|
2586
|
+
},
|
|
2587
|
+
required: ["file_paths"]
|
|
2588
|
+
}
|
|
2589
|
+
}
|
|
2590
|
+
],
|
|
2591
|
+
handlers: {
|
|
2592
|
+
async upload_file(args, { client: client2 }) {
|
|
2593
|
+
const filePath = args.file_path;
|
|
2594
|
+
let fileStats;
|
|
2595
|
+
try {
|
|
2596
|
+
fileStats = await stat(filePath);
|
|
2597
|
+
} catch {
|
|
2598
|
+
return errorResult(`File not found: ${filePath}`);
|
|
2599
|
+
}
|
|
2600
|
+
if (!fileStats.isFile()) {
|
|
2601
|
+
return errorResult(`Not a file: ${filePath}`);
|
|
2602
|
+
}
|
|
2603
|
+
const fileData = await readFile(filePath);
|
|
2604
|
+
const filename = basename(filePath);
|
|
2605
|
+
const contentType = getMimeType(filename);
|
|
2606
|
+
const upload = await client2.createUpload();
|
|
2607
|
+
const preparedAsset = await client2.prepareAssetUpload(upload.guid, {
|
|
2608
|
+
filename,
|
|
2609
|
+
file_size: fileStats.size,
|
|
2610
|
+
created_via: "mcp"
|
|
2611
|
+
});
|
|
2612
|
+
await client2.uploadToSignedUrl(preparedAsset.signed_upload_url, fileData, contentType);
|
|
2613
|
+
const asset = await client2.setAssetUploaded(preparedAsset.guid);
|
|
2614
|
+
await client2.setUploadDone(upload.id);
|
|
2615
|
+
return successResult({
|
|
2616
|
+
message: `Successfully uploaded ${filename}`,
|
|
2617
|
+
asset: {
|
|
2618
|
+
id: asset.id,
|
|
2619
|
+
guid: asset.guid,
|
|
2620
|
+
filename: asset.filename,
|
|
2621
|
+
file_size: asset.file_size,
|
|
2622
|
+
content_type: asset.content_type
|
|
2623
|
+
},
|
|
2624
|
+
upload_guid: upload.guid
|
|
2625
|
+
});
|
|
2626
|
+
},
|
|
2627
|
+
async upload_files(args, { client: client2 }) {
|
|
2628
|
+
const filePaths = args.file_paths;
|
|
2629
|
+
if (!filePaths || filePaths.length === 0) {
|
|
2630
|
+
return errorResult("No files provided");
|
|
2631
|
+
}
|
|
2632
|
+
const upload = await client2.createUpload();
|
|
2633
|
+
const results = [];
|
|
2634
|
+
for (const filePath of filePaths) {
|
|
2635
|
+
try {
|
|
2636
|
+
const fileStats = await stat(filePath);
|
|
2637
|
+
if (!fileStats.isFile()) {
|
|
2638
|
+
results.push({ filename: filePath, success: false, error: "Not a file" });
|
|
2639
|
+
continue;
|
|
2640
|
+
}
|
|
2641
|
+
const fileData = await readFile(filePath);
|
|
2642
|
+
const filename = basename(filePath);
|
|
2643
|
+
const contentType = getMimeType(filename);
|
|
2644
|
+
const preparedAsset = await client2.prepareAssetUpload(upload.guid, {
|
|
2645
|
+
filename,
|
|
2646
|
+
file_size: fileStats.size,
|
|
2647
|
+
created_via: "mcp"
|
|
2648
|
+
});
|
|
2649
|
+
await client2.uploadToSignedUrl(preparedAsset.signed_upload_url, fileData, contentType);
|
|
2650
|
+
const asset = await client2.setAssetUploaded(preparedAsset.guid);
|
|
2651
|
+
results.push({
|
|
2652
|
+
filename,
|
|
2653
|
+
success: true,
|
|
2654
|
+
asset_id: asset.id,
|
|
2655
|
+
asset_guid: asset.guid
|
|
2656
|
+
});
|
|
2657
|
+
} catch (error) {
|
|
2658
|
+
results.push({
|
|
2659
|
+
filename: basename(filePath),
|
|
2660
|
+
success: false,
|
|
2661
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
2662
|
+
});
|
|
2663
|
+
}
|
|
2664
|
+
}
|
|
2665
|
+
await client2.setUploadDone(upload.id);
|
|
2666
|
+
const successCount = results.filter((r) => r.success).length;
|
|
2667
|
+
return successResult({
|
|
2668
|
+
message: `Uploaded ${successCount} of ${filePaths.length} files`,
|
|
2669
|
+
upload_guid: upload.guid,
|
|
2670
|
+
results
|
|
2671
|
+
});
|
|
2672
|
+
}
|
|
2673
|
+
}
|
|
2674
|
+
};
|
|
2675
|
+
|
|
2444
2676
|
// src/tools/webhooks.ts
|
|
2445
2677
|
var webhookTools = {
|
|
2446
2678
|
definitions: [
|
|
@@ -2754,6 +2986,7 @@ var allToolModules = [
|
|
|
2754
2986
|
workflowTools,
|
|
2755
2987
|
socialTools,
|
|
2756
2988
|
downloadTools,
|
|
2989
|
+
uploadTools,
|
|
2757
2990
|
webhookTools,
|
|
2758
2991
|
adminTools
|
|
2759
2992
|
];
|
|
@@ -2962,6 +3195,7 @@ async function runAutoAuth() {
|
|
|
2962
3195
|
isAuthInProgress = true;
|
|
2963
3196
|
try {
|
|
2964
3197
|
const authUrl = oauthHandler.getAuthorizationUrl();
|
|
3198
|
+
await oauthHandler.startCallbackServer();
|
|
2965
3199
|
openBrowser(authUrl);
|
|
2966
3200
|
const { code } = await oauthHandler.waitForCallback();
|
|
2967
3201
|
const tokens = await oauthHandler.exchangeCode(code);
|
|
@@ -2978,6 +3212,7 @@ async function runAutoAuth() {
|
|
|
2978
3212
|
return true;
|
|
2979
3213
|
} catch (error) {
|
|
2980
3214
|
console.error("Auto-auth failed:", error);
|
|
3215
|
+
oauthHandler.stopCallbackServer();
|
|
2981
3216
|
return false;
|
|
2982
3217
|
} finally {
|
|
2983
3218
|
isAuthInProgress = false;
|
|
@@ -3131,7 +3366,10 @@ async function runAuthorize() {
|
|
|
3131
3366
|
console.log("Starting Mediagraph OAuth authorization...");
|
|
3132
3367
|
console.log("");
|
|
3133
3368
|
const authUrl = oauthHandler.getAuthorizationUrl();
|
|
3134
|
-
|
|
3369
|
+
await oauthHandler.startCallbackServer();
|
|
3370
|
+
openBrowser(authUrl);
|
|
3371
|
+
console.log("Opening browser for authorization...");
|
|
3372
|
+
console.log("If the browser does not open, please visit:");
|
|
3135
3373
|
console.log("");
|
|
3136
3374
|
console.log(authUrl);
|
|
3137
3375
|
console.log("");
|