@socialneuron/mcp-server 1.7.1 → 1.7.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/http.js +195 -11
- package/dist/index.js +195 -11
- package/package.json +1 -1
package/dist/http.js
CHANGED
|
@@ -1985,7 +1985,24 @@ function registerContentTools(server) {
|
|
|
1985
1985
|
`Status: ${job.status}`
|
|
1986
1986
|
];
|
|
1987
1987
|
if (job.result_url) {
|
|
1988
|
-
|
|
1988
|
+
const isR2Key = !job.result_url.startsWith("http");
|
|
1989
|
+
if (isR2Key) {
|
|
1990
|
+
const segments = job.result_url.split("/");
|
|
1991
|
+
const filename = segments[segments.length - 1] || "media";
|
|
1992
|
+
lines.push(`Media ready: ${filename}`);
|
|
1993
|
+
lines.push(
|
|
1994
|
+
"(Pass job_id directly to schedule_post, or use get_media_url with job_id for a download link)"
|
|
1995
|
+
);
|
|
1996
|
+
} else {
|
|
1997
|
+
lines.push(`Result URL: ${job.result_url}`);
|
|
1998
|
+
}
|
|
1999
|
+
}
|
|
2000
|
+
const allUrls = job.result_metadata?.all_urls;
|
|
2001
|
+
if (allUrls && allUrls.length > 1) {
|
|
2002
|
+
lines.push(`Media files: ${allUrls.length} outputs available`);
|
|
2003
|
+
lines.push(
|
|
2004
|
+
"(Use job_id with schedule_post for carousel, or response_format=json for programmatic access)"
|
|
2005
|
+
);
|
|
1989
2006
|
}
|
|
1990
2007
|
if (job.error_message) {
|
|
1991
2008
|
lines.push(`Error: ${job.error_message}`);
|
|
@@ -2002,8 +2019,13 @@ function registerContentTools(server) {
|
|
|
2002
2019
|
details: { status: job.status, jobId: job.id }
|
|
2003
2020
|
});
|
|
2004
2021
|
if (format === "json") {
|
|
2022
|
+
const enriched = {
|
|
2023
|
+
...job,
|
|
2024
|
+
r2_key: job.result_url && !job.result_url.startsWith("http") ? job.result_url : null,
|
|
2025
|
+
all_urls: allUrls ?? null
|
|
2026
|
+
};
|
|
2005
2027
|
return {
|
|
2006
|
-
content: [{ type: "text", text: JSON.stringify(asEnvelope(
|
|
2028
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope(enriched), null, 2) }]
|
|
2007
2029
|
};
|
|
2008
2030
|
}
|
|
2009
2031
|
return {
|
|
@@ -2610,6 +2632,22 @@ function evaluateQuality(input) {
|
|
|
2610
2632
|
}
|
|
2611
2633
|
|
|
2612
2634
|
// src/tools/distribution.ts
|
|
2635
|
+
function snakeToCamel(obj) {
|
|
2636
|
+
const result = {};
|
|
2637
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
2638
|
+
const camelKey = key.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
|
|
2639
|
+
result[camelKey] = value;
|
|
2640
|
+
}
|
|
2641
|
+
return result;
|
|
2642
|
+
}
|
|
2643
|
+
function convertPlatformMetadata(meta) {
|
|
2644
|
+
if (!meta) return void 0;
|
|
2645
|
+
const converted = {};
|
|
2646
|
+
for (const [platform2, fields] of Object.entries(meta)) {
|
|
2647
|
+
converted[platform2] = snakeToCamel(fields);
|
|
2648
|
+
}
|
|
2649
|
+
return converted;
|
|
2650
|
+
}
|
|
2613
2651
|
var PLATFORM_CASE_MAP = {
|
|
2614
2652
|
youtube: "YouTube",
|
|
2615
2653
|
tiktok: "TikTok",
|
|
@@ -2652,8 +2690,53 @@ function registerDistributionTools(server) {
|
|
|
2652
2690
|
job_ids: z3.array(z3.string()).optional().describe(
|
|
2653
2691
|
"Array of async job IDs for carousel posts. Each resolved to its R2 key. Alternative to media_urls/r2_keys."
|
|
2654
2692
|
),
|
|
2655
|
-
platform_metadata: z3.
|
|
2656
|
-
|
|
2693
|
+
platform_metadata: z3.object({
|
|
2694
|
+
tiktok: z3.object({
|
|
2695
|
+
privacy_status: z3.enum([
|
|
2696
|
+
"PUBLIC_TO_EVERYONE",
|
|
2697
|
+
"MUTUAL_FOLLOW_FRIENDS",
|
|
2698
|
+
"FOLLOWER_OF_CREATOR",
|
|
2699
|
+
"SELF_ONLY"
|
|
2700
|
+
]).optional().describe("Required unless useInbox=true. Who can view the video."),
|
|
2701
|
+
enable_duet: z3.boolean().optional(),
|
|
2702
|
+
enable_comment: z3.boolean().optional(),
|
|
2703
|
+
enable_stitch: z3.boolean().optional(),
|
|
2704
|
+
is_ai_generated: z3.boolean().optional(),
|
|
2705
|
+
brand_content: z3.boolean().optional(),
|
|
2706
|
+
brand_organic: z3.boolean().optional(),
|
|
2707
|
+
use_inbox: z3.boolean().optional().describe("Post to TikTok inbox/draft instead of direct publish.")
|
|
2708
|
+
}).optional(),
|
|
2709
|
+
youtube: z3.object({
|
|
2710
|
+
title: z3.string().optional().describe("Video title (required for YouTube)."),
|
|
2711
|
+
description: z3.string().optional(),
|
|
2712
|
+
privacy_status: z3.enum(["public", "unlisted", "private"]).optional(),
|
|
2713
|
+
category_id: z3.string().optional(),
|
|
2714
|
+
tags: z3.array(z3.string()).optional(),
|
|
2715
|
+
made_for_kids: z3.boolean().optional(),
|
|
2716
|
+
notify_subscribers: z3.boolean().optional()
|
|
2717
|
+
}).optional(),
|
|
2718
|
+
facebook: z3.object({
|
|
2719
|
+
page_id: z3.string().optional().describe("Facebook Page ID to post to."),
|
|
2720
|
+
audience: z3.string().optional()
|
|
2721
|
+
}).optional(),
|
|
2722
|
+
instagram: z3.object({
|
|
2723
|
+
location: z3.string().optional(),
|
|
2724
|
+
collaborators: z3.array(z3.string()).optional(),
|
|
2725
|
+
cover_timestamp: z3.number().optional(),
|
|
2726
|
+
share_to_feed: z3.boolean().optional(),
|
|
2727
|
+
first_comment: z3.string().optional(),
|
|
2728
|
+
is_ai_generated: z3.boolean().optional()
|
|
2729
|
+
}).optional(),
|
|
2730
|
+
threads: z3.object({}).passthrough().optional(),
|
|
2731
|
+
bluesky: z3.object({
|
|
2732
|
+
content_labels: z3.array(z3.string()).optional()
|
|
2733
|
+
}).optional(),
|
|
2734
|
+
linkedin: z3.object({
|
|
2735
|
+
article_url: z3.string().optional()
|
|
2736
|
+
}).optional(),
|
|
2737
|
+
twitter: z3.object({}).passthrough().optional()
|
|
2738
|
+
}).optional().describe(
|
|
2739
|
+
'Platform-specific metadata. Example: {"tiktok":{"privacy_status":"PUBLIC_TO_EVERYONE"}, "youtube":{"title":"My Video"}}'
|
|
2657
2740
|
),
|
|
2658
2741
|
media_type: z3.enum(["IMAGE", "VIDEO", "CAROUSEL_ALBUM"]).optional().describe(
|
|
2659
2742
|
"Media type. Set to CAROUSEL_ALBUM with media_urls for Instagram carousels. Default: auto-detected from media_url."
|
|
@@ -2832,7 +2915,11 @@ Created with Social Neuron`;
|
|
|
2832
2915
|
hashtags,
|
|
2833
2916
|
scheduledAt: schedule_at,
|
|
2834
2917
|
projectId: project_id,
|
|
2835
|
-
...platform_metadata ? {
|
|
2918
|
+
...platform_metadata ? {
|
|
2919
|
+
platformMetadata: convertPlatformMetadata(
|
|
2920
|
+
platform_metadata
|
|
2921
|
+
)
|
|
2922
|
+
} : {}
|
|
2836
2923
|
},
|
|
2837
2924
|
{ timeoutMs: 3e4 }
|
|
2838
2925
|
);
|
|
@@ -3710,6 +3797,10 @@ import { readFile } from "node:fs/promises";
|
|
|
3710
3797
|
import { basename, extname } from "node:path";
|
|
3711
3798
|
init_supabase();
|
|
3712
3799
|
var MAX_BASE64_SIZE = 10 * 1024 * 1024;
|
|
3800
|
+
function maskR2Key(key) {
|
|
3801
|
+
const segments = key.split("/");
|
|
3802
|
+
return segments.length >= 3 ? `\u2026/${segments.slice(-2).join("/")}` : key;
|
|
3803
|
+
}
|
|
3713
3804
|
function inferContentType(filePath) {
|
|
3714
3805
|
const ext = extname(filePath).toLowerCase();
|
|
3715
3806
|
const map = {
|
|
@@ -3794,18 +3885,111 @@ function registerMediaTools(server) {
|
|
|
3794
3885
|
isError: true
|
|
3795
3886
|
};
|
|
3796
3887
|
}
|
|
3888
|
+
const ct = content_type || inferContentType(source);
|
|
3797
3889
|
if (fileBuffer.length > MAX_BASE64_SIZE) {
|
|
3890
|
+
const { data: putData, error: putError } = await callEdgeFunction(
|
|
3891
|
+
"get-signed-url",
|
|
3892
|
+
{
|
|
3893
|
+
operation: "put",
|
|
3894
|
+
contentType: ct,
|
|
3895
|
+
filename: basename(source),
|
|
3896
|
+
projectId: project_id
|
|
3897
|
+
},
|
|
3898
|
+
{ timeoutMs: 1e4 }
|
|
3899
|
+
);
|
|
3900
|
+
if (putError || !putData?.signedUrl) {
|
|
3901
|
+
return {
|
|
3902
|
+
content: [
|
|
3903
|
+
{
|
|
3904
|
+
type: "text",
|
|
3905
|
+
text: `Failed to get presigned upload URL: ${putError || "No URL returned"}`
|
|
3906
|
+
}
|
|
3907
|
+
],
|
|
3908
|
+
isError: true
|
|
3909
|
+
};
|
|
3910
|
+
}
|
|
3911
|
+
try {
|
|
3912
|
+
const putResp = await fetch(putData.signedUrl, {
|
|
3913
|
+
method: "PUT",
|
|
3914
|
+
headers: { "Content-Type": ct },
|
|
3915
|
+
body: fileBuffer
|
|
3916
|
+
});
|
|
3917
|
+
if (!putResp.ok) {
|
|
3918
|
+
return {
|
|
3919
|
+
content: [
|
|
3920
|
+
{
|
|
3921
|
+
type: "text",
|
|
3922
|
+
text: `R2 upload failed (HTTP ${putResp.status}): ${await putResp.text().catch(() => "Unknown error")}`
|
|
3923
|
+
}
|
|
3924
|
+
],
|
|
3925
|
+
isError: true
|
|
3926
|
+
};
|
|
3927
|
+
}
|
|
3928
|
+
} catch (uploadErr) {
|
|
3929
|
+
return {
|
|
3930
|
+
content: [
|
|
3931
|
+
{
|
|
3932
|
+
type: "text",
|
|
3933
|
+
text: `R2 upload failed: ${uploadErr instanceof Error ? uploadErr.message : String(uploadErr)}`
|
|
3934
|
+
}
|
|
3935
|
+
],
|
|
3936
|
+
isError: true
|
|
3937
|
+
};
|
|
3938
|
+
}
|
|
3939
|
+
const { data: signData } = await callEdgeFunction(
|
|
3940
|
+
"get-signed-url",
|
|
3941
|
+
{ key: putData.key, operation: "get" },
|
|
3942
|
+
{ timeoutMs: 1e4 }
|
|
3943
|
+
);
|
|
3944
|
+
await logMcpToolInvocation({
|
|
3945
|
+
toolName: "upload_media",
|
|
3946
|
+
status: "success",
|
|
3947
|
+
durationMs: Date.now() - startedAt,
|
|
3948
|
+
details: {
|
|
3949
|
+
source: "local-presigned-put",
|
|
3950
|
+
r2Key: putData.key,
|
|
3951
|
+
size: fileBuffer.length,
|
|
3952
|
+
contentType: ct
|
|
3953
|
+
}
|
|
3954
|
+
});
|
|
3955
|
+
if (format === "json") {
|
|
3956
|
+
return {
|
|
3957
|
+
content: [
|
|
3958
|
+
{
|
|
3959
|
+
type: "text",
|
|
3960
|
+
text: JSON.stringify(
|
|
3961
|
+
{
|
|
3962
|
+
r2_key: putData.key,
|
|
3963
|
+
signed_url: signData?.signedUrl ?? null,
|
|
3964
|
+
size: fileBuffer.length,
|
|
3965
|
+
content_type: ct
|
|
3966
|
+
},
|
|
3967
|
+
null,
|
|
3968
|
+
2
|
|
3969
|
+
)
|
|
3970
|
+
}
|
|
3971
|
+
],
|
|
3972
|
+
isError: false
|
|
3973
|
+
};
|
|
3974
|
+
}
|
|
3798
3975
|
return {
|
|
3799
3976
|
content: [
|
|
3800
3977
|
{
|
|
3801
3978
|
type: "text",
|
|
3802
|
-
text:
|
|
3979
|
+
text: [
|
|
3980
|
+
"Media uploaded successfully (presigned PUT).",
|
|
3981
|
+
`Media key: ${maskR2Key(putData.key)}`,
|
|
3982
|
+
signData?.signedUrl ? `Signed URL: ${signData.signedUrl}` : "",
|
|
3983
|
+
`Size: ${(fileBuffer.length / 1024 / 1024).toFixed(1)}MB`,
|
|
3984
|
+
`Type: ${ct}`,
|
|
3985
|
+
"",
|
|
3986
|
+
"Use job_id or response_format=json with schedule_post to post to any platform."
|
|
3987
|
+
].filter(Boolean).join("\n")
|
|
3803
3988
|
}
|
|
3804
3989
|
],
|
|
3805
|
-
isError:
|
|
3990
|
+
isError: false
|
|
3806
3991
|
};
|
|
3807
3992
|
}
|
|
3808
|
-
const ct = content_type || inferContentType(source);
|
|
3809
3993
|
const base64 = `data:${ct};base64,${fileBuffer.toString("base64")}`;
|
|
3810
3994
|
uploadBody = {
|
|
3811
3995
|
fileData: base64,
|
|
@@ -3870,12 +4054,12 @@ function registerMediaTools(server) {
|
|
|
3870
4054
|
type: "text",
|
|
3871
4055
|
text: [
|
|
3872
4056
|
"Media uploaded successfully.",
|
|
3873
|
-
`
|
|
4057
|
+
`Media key: ${maskR2Key(data.key)}`,
|
|
3874
4058
|
`Signed URL: ${data.url}`,
|
|
3875
4059
|
`Size: ${(data.size / 1024).toFixed(0)}KB`,
|
|
3876
4060
|
`Type: ${data.contentType}`,
|
|
3877
4061
|
"",
|
|
3878
|
-
"Use
|
|
4062
|
+
"Use job_id or response_format=json with schedule_post to post to any platform."
|
|
3879
4063
|
].join("\n")
|
|
3880
4064
|
}
|
|
3881
4065
|
],
|
|
@@ -3939,7 +4123,7 @@ function registerMediaTools(server) {
|
|
|
3939
4123
|
type: "text",
|
|
3940
4124
|
text: [
|
|
3941
4125
|
`Signed URL: ${data.signedUrl}`,
|
|
3942
|
-
`
|
|
4126
|
+
`Media key: ${maskR2Key(r2_key)}`,
|
|
3943
4127
|
`Expires in: ${data.expiresIn ?? 3600}s`
|
|
3944
4128
|
].join("\n")
|
|
3945
4129
|
}
|
package/dist/index.js
CHANGED
|
@@ -5135,7 +5135,24 @@ function registerContentTools(server2) {
|
|
|
5135
5135
|
`Status: ${job.status}`
|
|
5136
5136
|
];
|
|
5137
5137
|
if (job.result_url) {
|
|
5138
|
-
|
|
5138
|
+
const isR2Key = !job.result_url.startsWith("http");
|
|
5139
|
+
if (isR2Key) {
|
|
5140
|
+
const segments = job.result_url.split("/");
|
|
5141
|
+
const filename = segments[segments.length - 1] || "media";
|
|
5142
|
+
lines.push(`Media ready: ${filename}`);
|
|
5143
|
+
lines.push(
|
|
5144
|
+
"(Pass job_id directly to schedule_post, or use get_media_url with job_id for a download link)"
|
|
5145
|
+
);
|
|
5146
|
+
} else {
|
|
5147
|
+
lines.push(`Result URL: ${job.result_url}`);
|
|
5148
|
+
}
|
|
5149
|
+
}
|
|
5150
|
+
const allUrls = job.result_metadata?.all_urls;
|
|
5151
|
+
if (allUrls && allUrls.length > 1) {
|
|
5152
|
+
lines.push(`Media files: ${allUrls.length} outputs available`);
|
|
5153
|
+
lines.push(
|
|
5154
|
+
"(Use job_id with schedule_post for carousel, or response_format=json for programmatic access)"
|
|
5155
|
+
);
|
|
5139
5156
|
}
|
|
5140
5157
|
if (job.error_message) {
|
|
5141
5158
|
lines.push(`Error: ${job.error_message}`);
|
|
@@ -5152,8 +5169,13 @@ function registerContentTools(server2) {
|
|
|
5152
5169
|
details: { status: job.status, jobId: job.id }
|
|
5153
5170
|
});
|
|
5154
5171
|
if (format === "json") {
|
|
5172
|
+
const enriched = {
|
|
5173
|
+
...job,
|
|
5174
|
+
r2_key: job.result_url && !job.result_url.startsWith("http") ? job.result_url : null,
|
|
5175
|
+
all_urls: allUrls ?? null
|
|
5176
|
+
};
|
|
5155
5177
|
return {
|
|
5156
|
-
content: [{ type: "text", text: JSON.stringify(asEnvelope(
|
|
5178
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope(enriched), null, 2) }]
|
|
5157
5179
|
};
|
|
5158
5180
|
}
|
|
5159
5181
|
return {
|
|
@@ -5639,6 +5661,22 @@ import { createHash as createHash2 } from "node:crypto";
|
|
|
5639
5661
|
init_supabase();
|
|
5640
5662
|
init_quality();
|
|
5641
5663
|
init_version();
|
|
5664
|
+
function snakeToCamel(obj) {
|
|
5665
|
+
const result = {};
|
|
5666
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
5667
|
+
const camelKey = key.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
|
|
5668
|
+
result[camelKey] = value;
|
|
5669
|
+
}
|
|
5670
|
+
return result;
|
|
5671
|
+
}
|
|
5672
|
+
function convertPlatformMetadata(meta) {
|
|
5673
|
+
if (!meta) return void 0;
|
|
5674
|
+
const converted = {};
|
|
5675
|
+
for (const [platform3, fields] of Object.entries(meta)) {
|
|
5676
|
+
converted[platform3] = snakeToCamel(fields);
|
|
5677
|
+
}
|
|
5678
|
+
return converted;
|
|
5679
|
+
}
|
|
5642
5680
|
var PLATFORM_CASE_MAP = {
|
|
5643
5681
|
youtube: "YouTube",
|
|
5644
5682
|
tiktok: "TikTok",
|
|
@@ -5681,8 +5719,53 @@ function registerDistributionTools(server2) {
|
|
|
5681
5719
|
job_ids: z3.array(z3.string()).optional().describe(
|
|
5682
5720
|
"Array of async job IDs for carousel posts. Each resolved to its R2 key. Alternative to media_urls/r2_keys."
|
|
5683
5721
|
),
|
|
5684
|
-
platform_metadata: z3.
|
|
5685
|
-
|
|
5722
|
+
platform_metadata: z3.object({
|
|
5723
|
+
tiktok: z3.object({
|
|
5724
|
+
privacy_status: z3.enum([
|
|
5725
|
+
"PUBLIC_TO_EVERYONE",
|
|
5726
|
+
"MUTUAL_FOLLOW_FRIENDS",
|
|
5727
|
+
"FOLLOWER_OF_CREATOR",
|
|
5728
|
+
"SELF_ONLY"
|
|
5729
|
+
]).optional().describe("Required unless useInbox=true. Who can view the video."),
|
|
5730
|
+
enable_duet: z3.boolean().optional(),
|
|
5731
|
+
enable_comment: z3.boolean().optional(),
|
|
5732
|
+
enable_stitch: z3.boolean().optional(),
|
|
5733
|
+
is_ai_generated: z3.boolean().optional(),
|
|
5734
|
+
brand_content: z3.boolean().optional(),
|
|
5735
|
+
brand_organic: z3.boolean().optional(),
|
|
5736
|
+
use_inbox: z3.boolean().optional().describe("Post to TikTok inbox/draft instead of direct publish.")
|
|
5737
|
+
}).optional(),
|
|
5738
|
+
youtube: z3.object({
|
|
5739
|
+
title: z3.string().optional().describe("Video title (required for YouTube)."),
|
|
5740
|
+
description: z3.string().optional(),
|
|
5741
|
+
privacy_status: z3.enum(["public", "unlisted", "private"]).optional(),
|
|
5742
|
+
category_id: z3.string().optional(),
|
|
5743
|
+
tags: z3.array(z3.string()).optional(),
|
|
5744
|
+
made_for_kids: z3.boolean().optional(),
|
|
5745
|
+
notify_subscribers: z3.boolean().optional()
|
|
5746
|
+
}).optional(),
|
|
5747
|
+
facebook: z3.object({
|
|
5748
|
+
page_id: z3.string().optional().describe("Facebook Page ID to post to."),
|
|
5749
|
+
audience: z3.string().optional()
|
|
5750
|
+
}).optional(),
|
|
5751
|
+
instagram: z3.object({
|
|
5752
|
+
location: z3.string().optional(),
|
|
5753
|
+
collaborators: z3.array(z3.string()).optional(),
|
|
5754
|
+
cover_timestamp: z3.number().optional(),
|
|
5755
|
+
share_to_feed: z3.boolean().optional(),
|
|
5756
|
+
first_comment: z3.string().optional(),
|
|
5757
|
+
is_ai_generated: z3.boolean().optional()
|
|
5758
|
+
}).optional(),
|
|
5759
|
+
threads: z3.object({}).passthrough().optional(),
|
|
5760
|
+
bluesky: z3.object({
|
|
5761
|
+
content_labels: z3.array(z3.string()).optional()
|
|
5762
|
+
}).optional(),
|
|
5763
|
+
linkedin: z3.object({
|
|
5764
|
+
article_url: z3.string().optional()
|
|
5765
|
+
}).optional(),
|
|
5766
|
+
twitter: z3.object({}).passthrough().optional()
|
|
5767
|
+
}).optional().describe(
|
|
5768
|
+
'Platform-specific metadata. Example: {"tiktok":{"privacy_status":"PUBLIC_TO_EVERYONE"}, "youtube":{"title":"My Video"}}'
|
|
5686
5769
|
),
|
|
5687
5770
|
media_type: z3.enum(["IMAGE", "VIDEO", "CAROUSEL_ALBUM"]).optional().describe(
|
|
5688
5771
|
"Media type. Set to CAROUSEL_ALBUM with media_urls for Instagram carousels. Default: auto-detected from media_url."
|
|
@@ -5861,7 +5944,11 @@ Created with Social Neuron`;
|
|
|
5861
5944
|
hashtags,
|
|
5862
5945
|
scheduledAt: schedule_at,
|
|
5863
5946
|
projectId: project_id,
|
|
5864
|
-
...platform_metadata ? {
|
|
5947
|
+
...platform_metadata ? {
|
|
5948
|
+
platformMetadata: convertPlatformMetadata(
|
|
5949
|
+
platform_metadata
|
|
5950
|
+
)
|
|
5951
|
+
} : {}
|
|
5865
5952
|
},
|
|
5866
5953
|
{ timeoutMs: 3e4 }
|
|
5867
5954
|
);
|
|
@@ -6740,6 +6827,10 @@ import { readFile } from "node:fs/promises";
|
|
|
6740
6827
|
import { basename, extname } from "node:path";
|
|
6741
6828
|
init_supabase();
|
|
6742
6829
|
var MAX_BASE64_SIZE = 10 * 1024 * 1024;
|
|
6830
|
+
function maskR2Key(key) {
|
|
6831
|
+
const segments = key.split("/");
|
|
6832
|
+
return segments.length >= 3 ? `\u2026/${segments.slice(-2).join("/")}` : key;
|
|
6833
|
+
}
|
|
6743
6834
|
function inferContentType(filePath) {
|
|
6744
6835
|
const ext = extname(filePath).toLowerCase();
|
|
6745
6836
|
const map = {
|
|
@@ -6824,18 +6915,111 @@ function registerMediaTools(server2) {
|
|
|
6824
6915
|
isError: true
|
|
6825
6916
|
};
|
|
6826
6917
|
}
|
|
6918
|
+
const ct = content_type || inferContentType(source);
|
|
6827
6919
|
if (fileBuffer.length > MAX_BASE64_SIZE) {
|
|
6920
|
+
const { data: putData, error: putError } = await callEdgeFunction(
|
|
6921
|
+
"get-signed-url",
|
|
6922
|
+
{
|
|
6923
|
+
operation: "put",
|
|
6924
|
+
contentType: ct,
|
|
6925
|
+
filename: basename(source),
|
|
6926
|
+
projectId: project_id
|
|
6927
|
+
},
|
|
6928
|
+
{ timeoutMs: 1e4 }
|
|
6929
|
+
);
|
|
6930
|
+
if (putError || !putData?.signedUrl) {
|
|
6931
|
+
return {
|
|
6932
|
+
content: [
|
|
6933
|
+
{
|
|
6934
|
+
type: "text",
|
|
6935
|
+
text: `Failed to get presigned upload URL: ${putError || "No URL returned"}`
|
|
6936
|
+
}
|
|
6937
|
+
],
|
|
6938
|
+
isError: true
|
|
6939
|
+
};
|
|
6940
|
+
}
|
|
6941
|
+
try {
|
|
6942
|
+
const putResp = await fetch(putData.signedUrl, {
|
|
6943
|
+
method: "PUT",
|
|
6944
|
+
headers: { "Content-Type": ct },
|
|
6945
|
+
body: fileBuffer
|
|
6946
|
+
});
|
|
6947
|
+
if (!putResp.ok) {
|
|
6948
|
+
return {
|
|
6949
|
+
content: [
|
|
6950
|
+
{
|
|
6951
|
+
type: "text",
|
|
6952
|
+
text: `R2 upload failed (HTTP ${putResp.status}): ${await putResp.text().catch(() => "Unknown error")}`
|
|
6953
|
+
}
|
|
6954
|
+
],
|
|
6955
|
+
isError: true
|
|
6956
|
+
};
|
|
6957
|
+
}
|
|
6958
|
+
} catch (uploadErr) {
|
|
6959
|
+
return {
|
|
6960
|
+
content: [
|
|
6961
|
+
{
|
|
6962
|
+
type: "text",
|
|
6963
|
+
text: `R2 upload failed: ${uploadErr instanceof Error ? uploadErr.message : String(uploadErr)}`
|
|
6964
|
+
}
|
|
6965
|
+
],
|
|
6966
|
+
isError: true
|
|
6967
|
+
};
|
|
6968
|
+
}
|
|
6969
|
+
const { data: signData } = await callEdgeFunction(
|
|
6970
|
+
"get-signed-url",
|
|
6971
|
+
{ key: putData.key, operation: "get" },
|
|
6972
|
+
{ timeoutMs: 1e4 }
|
|
6973
|
+
);
|
|
6974
|
+
await logMcpToolInvocation({
|
|
6975
|
+
toolName: "upload_media",
|
|
6976
|
+
status: "success",
|
|
6977
|
+
durationMs: Date.now() - startedAt,
|
|
6978
|
+
details: {
|
|
6979
|
+
source: "local-presigned-put",
|
|
6980
|
+
r2Key: putData.key,
|
|
6981
|
+
size: fileBuffer.length,
|
|
6982
|
+
contentType: ct
|
|
6983
|
+
}
|
|
6984
|
+
});
|
|
6985
|
+
if (format === "json") {
|
|
6986
|
+
return {
|
|
6987
|
+
content: [
|
|
6988
|
+
{
|
|
6989
|
+
type: "text",
|
|
6990
|
+
text: JSON.stringify(
|
|
6991
|
+
{
|
|
6992
|
+
r2_key: putData.key,
|
|
6993
|
+
signed_url: signData?.signedUrl ?? null,
|
|
6994
|
+
size: fileBuffer.length,
|
|
6995
|
+
content_type: ct
|
|
6996
|
+
},
|
|
6997
|
+
null,
|
|
6998
|
+
2
|
|
6999
|
+
)
|
|
7000
|
+
}
|
|
7001
|
+
],
|
|
7002
|
+
isError: false
|
|
7003
|
+
};
|
|
7004
|
+
}
|
|
6828
7005
|
return {
|
|
6829
7006
|
content: [
|
|
6830
7007
|
{
|
|
6831
7008
|
type: "text",
|
|
6832
|
-
text:
|
|
7009
|
+
text: [
|
|
7010
|
+
"Media uploaded successfully (presigned PUT).",
|
|
7011
|
+
`Media key: ${maskR2Key(putData.key)}`,
|
|
7012
|
+
signData?.signedUrl ? `Signed URL: ${signData.signedUrl}` : "",
|
|
7013
|
+
`Size: ${(fileBuffer.length / 1024 / 1024).toFixed(1)}MB`,
|
|
7014
|
+
`Type: ${ct}`,
|
|
7015
|
+
"",
|
|
7016
|
+
"Use job_id or response_format=json with schedule_post to post to any platform."
|
|
7017
|
+
].filter(Boolean).join("\n")
|
|
6833
7018
|
}
|
|
6834
7019
|
],
|
|
6835
|
-
isError:
|
|
7020
|
+
isError: false
|
|
6836
7021
|
};
|
|
6837
7022
|
}
|
|
6838
|
-
const ct = content_type || inferContentType(source);
|
|
6839
7023
|
const base64 = `data:${ct};base64,${fileBuffer.toString("base64")}`;
|
|
6840
7024
|
uploadBody = {
|
|
6841
7025
|
fileData: base64,
|
|
@@ -6900,12 +7084,12 @@ function registerMediaTools(server2) {
|
|
|
6900
7084
|
type: "text",
|
|
6901
7085
|
text: [
|
|
6902
7086
|
"Media uploaded successfully.",
|
|
6903
|
-
`
|
|
7087
|
+
`Media key: ${maskR2Key(data.key)}`,
|
|
6904
7088
|
`Signed URL: ${data.url}`,
|
|
6905
7089
|
`Size: ${(data.size / 1024).toFixed(0)}KB`,
|
|
6906
7090
|
`Type: ${data.contentType}`,
|
|
6907
7091
|
"",
|
|
6908
|
-
"Use
|
|
7092
|
+
"Use job_id or response_format=json with schedule_post to post to any platform."
|
|
6909
7093
|
].join("\n")
|
|
6910
7094
|
}
|
|
6911
7095
|
],
|
|
@@ -6969,7 +7153,7 @@ function registerMediaTools(server2) {
|
|
|
6969
7153
|
type: "text",
|
|
6970
7154
|
text: [
|
|
6971
7155
|
`Signed URL: ${data.signedUrl}`,
|
|
6972
|
-
`
|
|
7156
|
+
`Media key: ${maskR2Key(r2_key)}`,
|
|
6973
7157
|
`Expires in: ${data.expiresIn ?? 3600}s`
|
|
6974
7158
|
].join("\n")
|
|
6975
7159
|
}
|