@relvo/mcp 0.1.1 → 0.1.3
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/README.md +116 -5
- package/bin/relvo-mcp.mjs +1 -1
- package/dist/client/agent-run.js +681 -0
- package/dist/client/browser.js +650 -0
- package/dist/client/evm-wallet.js +214 -0
- package/dist/client/execution-context.js +6 -3
- package/dist/client/index.js +3 -342
- package/dist/client/local-input.js +18 -11
- package/dist/client/local-profile.js +22 -110
- package/dist/client/local-setup.js +0 -1
- package/dist/client/tool-name-hints.js +48 -0
- package/dist/client/wallet-access.js +207 -0
- package/dist/mcp/agent-server.js +237 -0
- package/dist/mcp/context.js +70 -0
- package/dist/mcp/host-schema.js +47 -0
- package/dist/mcp/instructions.js +17 -0
- package/dist/mcp/local-upload.js +12 -4
- package/dist/mcp/operations.js +30 -0
- package/dist/mcp/runtime.js +25 -5
- package/dist/mcp/server.js +293 -69
- package/dist/mcp/version.js +1 -0
- package/dist/schema/accountRequestMethods.js +1 -0
- package/dist/schema/cronValidator.js +150 -0
- package/dist/schema/platformApiKey.js +5 -5
- package/dist/schema/platformBillingConstants.js +3 -0
- package/dist/schema/platformCapabilityFamilies.js +217 -0
- package/dist/schema/platformConnectionCatalog.js +20 -3
- package/dist/schema/platformConnectionPolicy.js +26 -0
- package/dist/schema/platformCustomTools.js +113 -0
- package/dist/schema/platformFunding.js +9 -0
- package/dist/schema/platformHttpConstraints.js +7 -0
- package/dist/schema/platformMcpTools.js +7 -0
- package/dist/schema/platformMetaTools.js +10 -6
- package/dist/schema/platformMonitorSchedule.js +123 -0
- package/dist/schema/platformMonitors.js +159 -0
- package/dist/schema/platformOAuth.js +7 -0
- package/dist/schema/platformPublicExecution.js +72 -25
- package/dist/schema/platformRoutes.js +119 -0
- package/dist/schema/platformSkills.js +1 -1
- package/dist/schema/platformTeamSecretName.js +3 -0
- package/dist/schema/platformTeamSkills.js +49 -0
- package/dist/schema/platformToolAccess.js +13 -0
- package/dist/schema/platformTriggers.js +152 -0
- package/dist/schema/platformWorkflows.js +100 -0
- package/dist/schema/scheduleTimeZone.js +15 -0
- package/dist/schema/toolDiscovery.js +44 -6
- package/dist/schema/toolInputRepair.js +110 -0
- package/dist/schema/toolResultContext.js +33 -1
- package/package.json +1 -1
- package/dist/schema/amazonAds.js +0 -13
- package/dist/schema/oauth2.js +0 -64
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
const STRICT_CRON_RE = /^([\*0-9A-Za-z,\-\/]+)\s+([\*0-9A-Za-z,\-\/]+)\s+([\*0-9A-Za-z,\-\/]+)\s+([\*0-9A-Za-z,\-\/]+)\s+([\*0-9A-Za-z,\-\/]+)$/;
|
|
2
|
+
export function isStrictCron5Field(cron) {
|
|
3
|
+
if (typeof cron !== "string")
|
|
4
|
+
return false;
|
|
5
|
+
return STRICT_CRON_RE.test(cron.trim());
|
|
6
|
+
}
|
|
7
|
+
const RANGES = {
|
|
8
|
+
minute: { min: 0, max: 59 },
|
|
9
|
+
hour: { min: 0, max: 23 },
|
|
10
|
+
dayOfMonth: { min: 1, max: 31 },
|
|
11
|
+
month: { min: 1, max: 12 },
|
|
12
|
+
dayOfWeek: { min: 0, max: 6 },
|
|
13
|
+
};
|
|
14
|
+
const MONTH_NAMES = {
|
|
15
|
+
JAN: 1, FEB: 2, MAR: 3, APR: 4, MAY: 5, JUN: 6,
|
|
16
|
+
JUL: 7, AUG: 8, SEP: 9, OCT: 10, NOV: 11, DEC: 12,
|
|
17
|
+
};
|
|
18
|
+
const DOW_NAMES = {
|
|
19
|
+
SUN: 0, MON: 1, TUE: 2, WED: 3, THU: 4, FRI: 5, SAT: 6,
|
|
20
|
+
};
|
|
21
|
+
function normalizeToken(token, names) {
|
|
22
|
+
if (!names)
|
|
23
|
+
return token;
|
|
24
|
+
const upper = token.trim().toUpperCase();
|
|
25
|
+
return upper in names ? String(names[upper]) : token;
|
|
26
|
+
}
|
|
27
|
+
function expandField(field, range, names) {
|
|
28
|
+
const out = new Set();
|
|
29
|
+
for (const part of field.split(",")) {
|
|
30
|
+
const stepMatch = part.match(/^(.+)\/(\d+)$/);
|
|
31
|
+
let base = part;
|
|
32
|
+
let step = 1;
|
|
33
|
+
if (stepMatch) {
|
|
34
|
+
base = stepMatch[1];
|
|
35
|
+
step = Number.parseInt(stepMatch[2], 10);
|
|
36
|
+
if (!Number.isFinite(step) || step <= 0)
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
let lo;
|
|
40
|
+
let hi;
|
|
41
|
+
if (base === "*") {
|
|
42
|
+
lo = range.min;
|
|
43
|
+
hi = range.openMax ?? range.max;
|
|
44
|
+
}
|
|
45
|
+
else if (base.includes("-")) {
|
|
46
|
+
const [a, b] = base.split("-");
|
|
47
|
+
lo = Number.parseInt(normalizeToken(a ?? "", names), 10);
|
|
48
|
+
hi = Number.parseInt(normalizeToken(b ?? "", names), 10);
|
|
49
|
+
if (!Number.isFinite(lo) || !Number.isFinite(hi))
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
const v = Number.parseInt(normalizeToken(base, names), 10);
|
|
54
|
+
if (!Number.isFinite(v))
|
|
55
|
+
return null;
|
|
56
|
+
lo = v;
|
|
57
|
+
hi = stepMatch ? (range.openMax ?? range.max) : v;
|
|
58
|
+
}
|
|
59
|
+
if (lo < range.min || hi > range.max || lo > hi)
|
|
60
|
+
return null;
|
|
61
|
+
for (let i = lo; i <= hi; i += step)
|
|
62
|
+
out.add(i);
|
|
63
|
+
}
|
|
64
|
+
return out.size > 0 ? out : null;
|
|
65
|
+
}
|
|
66
|
+
export function parseCron(cron) {
|
|
67
|
+
if (!isStrictCron5Field(cron))
|
|
68
|
+
return null;
|
|
69
|
+
const [minute, hour, dom, month, dow] = cron.trim().split(/\s+/);
|
|
70
|
+
const minutes = expandField(minute, RANGES.minute);
|
|
71
|
+
const hours = expandField(hour, RANGES.hour);
|
|
72
|
+
const daysOfMonth = expandField(dom, RANGES.dayOfMonth);
|
|
73
|
+
const months = expandField(month, RANGES.month, MONTH_NAMES);
|
|
74
|
+
const dowRaw = expandField(dow, { min: RANGES.dayOfWeek.min, max: 7, openMax: RANGES.dayOfWeek.max }, DOW_NAMES);
|
|
75
|
+
const dowExpanded = dowRaw && new Set([...dowRaw].map((d) => (d === 7 ? 0 : d)));
|
|
76
|
+
if (!minutes || !hours || !daysOfMonth || !months || !dowExpanded)
|
|
77
|
+
return null;
|
|
78
|
+
return {
|
|
79
|
+
minutes,
|
|
80
|
+
hours,
|
|
81
|
+
daysOfMonth,
|
|
82
|
+
months,
|
|
83
|
+
daysOfWeek: dowExpanded,
|
|
84
|
+
dayRestrictionOr: dom !== "*" && dow !== "*",
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
export function isValidCron(cron) {
|
|
88
|
+
if (typeof cron !== "string")
|
|
89
|
+
return false;
|
|
90
|
+
return parseCron(cron) !== null;
|
|
91
|
+
}
|
|
92
|
+
export function isCronTooFrequent(cron, minIntervalMinutes) {
|
|
93
|
+
const fields = cron.trim().split(/\s+/);
|
|
94
|
+
if (fields.length !== 5)
|
|
95
|
+
return true;
|
|
96
|
+
const minutes = expandField(fields[0], RANGES.minute);
|
|
97
|
+
const hours = expandField(fields[1], RANGES.hour);
|
|
98
|
+
if (!minutes || !hours)
|
|
99
|
+
return true;
|
|
100
|
+
const slots = [];
|
|
101
|
+
for (const h of hours) {
|
|
102
|
+
for (const m of minutes)
|
|
103
|
+
slots.push(h * 60 + m);
|
|
104
|
+
}
|
|
105
|
+
slots.sort((a, b) => a - b);
|
|
106
|
+
for (let i = 1; i < slots.length; i++) {
|
|
107
|
+
if (slots[i] - slots[i - 1] < minIntervalMinutes)
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
return slots[0] + 1440 - slots[slots.length - 1] < minIntervalMinutes;
|
|
111
|
+
}
|
|
112
|
+
function matches(parsed, d) {
|
|
113
|
+
if (!parsed.minutes.has(d.getMinutes()))
|
|
114
|
+
return false;
|
|
115
|
+
if (!parsed.hours.has(d.getHours()))
|
|
116
|
+
return false;
|
|
117
|
+
if (!parsed.months.has(d.getMonth() + 1))
|
|
118
|
+
return false;
|
|
119
|
+
const domMatch = parsed.daysOfMonth.has(d.getDate());
|
|
120
|
+
const dowMatch = parsed.daysOfWeek.has(d.getDay());
|
|
121
|
+
if (parsed.dayRestrictionOr)
|
|
122
|
+
return domMatch || dowMatch;
|
|
123
|
+
return domMatch && dowMatch;
|
|
124
|
+
}
|
|
125
|
+
export function findNextFires(cron, from, deadline, max = 30) {
|
|
126
|
+
const parsed = parseCron(cron);
|
|
127
|
+
if (!parsed)
|
|
128
|
+
return [];
|
|
129
|
+
if (!(deadline.getTime() > from.getTime()))
|
|
130
|
+
return [];
|
|
131
|
+
const fires = [];
|
|
132
|
+
const cursor = new Date(from.getTime());
|
|
133
|
+
cursor.setSeconds(0, 0);
|
|
134
|
+
cursor.setTime(cursor.getTime() + 60_000);
|
|
135
|
+
const HARD_CAP_MINUTES = 370 * 24 * 60;
|
|
136
|
+
let ticks = 0;
|
|
137
|
+
while (cursor.getTime() <= deadline.getTime() && ticks < HARD_CAP_MINUTES) {
|
|
138
|
+
if (matches(parsed, cursor)) {
|
|
139
|
+
fires.push(new Date(cursor.getTime()));
|
|
140
|
+
if (fires.length >= max)
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
cursor.setTime(cursor.getTime() + 60_000);
|
|
144
|
+
ticks++;
|
|
145
|
+
}
|
|
146
|
+
return fires;
|
|
147
|
+
}
|
|
148
|
+
export function cronFiresBeforeDeadline(cron, deadline, from = new Date()) {
|
|
149
|
+
return findNextFires(cron, from, deadline, 1).length > 0;
|
|
150
|
+
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
export const PLATFORM_KEY_PREFIXES = { noimos: "nmp_", relvo: "rlv_"
|
|
2
|
-
export const PLATFORM_API_KEY_PATTERN = /^(?:nmp|rlv
|
|
3
|
-
export const PLATFORM_CLIENT_HEADERS = { noimos: "x-noimos-client", relvo: "x-relvo-client"
|
|
1
|
+
export const PLATFORM_KEY_PREFIXES = { noimos: "nmp_", relvo: "rlv_" };
|
|
2
|
+
export const PLATFORM_API_KEY_PATTERN = /^(?:nmp|rlv)_[A-Za-z0-9_-]{43}$/;
|
|
3
|
+
export const PLATFORM_CLIENT_HEADERS = { noimos: "x-noimos-client", relvo: "x-relvo-client" };
|
|
4
4
|
export const PLATFORM_PRODUCT_NAMES = { noimos: "NoimosAI Platform", relvo: "Relvo" };
|
|
5
5
|
export function isRelvoProduct(product) {
|
|
6
|
-
return product === "relvo"
|
|
6
|
+
return product === "relvo";
|
|
7
7
|
}
|
|
8
8
|
export function platformKeyProduct(key) {
|
|
9
9
|
for (const product of Object.keys(PLATFORM_KEY_PREFIXES))
|
|
10
10
|
if (key.startsWith(PLATFORM_KEY_PREFIXES[product]))
|
|
11
|
-
return product
|
|
11
|
+
return product;
|
|
12
12
|
return null;
|
|
13
13
|
}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
export const AUTO_CAPABILITY_VARIANTS = {
|
|
2
|
+
generate_video: ["generate_openrouter_video"],
|
|
3
|
+
generate_avatar_video: ["animate_kling_avatar", "generate_omnihuman_video"],
|
|
4
|
+
search_people: ["apollo_people_search", "anyapi_apollo_people_search", "anyapi_people_search_ai_ark", "anyapi_people_search_crustdata",
|
|
5
|
+
"anyapi_people_search_crustdata_v3", "anyapi_people_search_fullenrich", "anyapi_people_search_lusha", "anyapi_people_search_peopledatalabs",
|
|
6
|
+
"anyapi_people_search_prospeo", "anyapi_people_search_quickenrich", "pdl_people_search", "prospeo_people_search",
|
|
7
|
+
"firecrawl_fullenrich_people_search", "brightdata_business_people_search"],
|
|
8
|
+
search_organizations: ["apollo_company_search", "anyapi_apollo_organizations_search", "anyapi_company_search_ai_ark",
|
|
9
|
+
"anyapi_company_search_crustdata", "anyapi_company_search_crustdata_v3", "anyapi_company_search_fullenrich",
|
|
10
|
+
"anyapi_company_search_peopledatalabs", "anyapi_company_search_prospeo", "anyapi_company_search_quickenrich", "pdl_company_search",
|
|
11
|
+
"prospeo_company_search", "firecrawl_fullenrich_companies_search", "brightdata_business_company_search",
|
|
12
|
+
"predictleads_discover_companies", "predictleads_discover_extended_companies"],
|
|
13
|
+
find_companies: ["hunter_discover", "hunter_discover_people"],
|
|
14
|
+
enrich_organization: ["apollo_company_enrich", "apollo_companies_enrich", "anyapi_apollo_organization_enrich",
|
|
15
|
+
"anyapi_apollo_organizations_bulk_enrich", "anyapi_company_enrichment_crustdata", "anyapi_company_enrichment_crustdata_v3",
|
|
16
|
+
"anyapi_company_enrichment_lusha", "anyapi_company_enrichment_peopledatalabs", "anyapi_company_enrichment_prospeo", "pdl_company_enrich",
|
|
17
|
+
"pdl_companies_bulk_enrich", "prospeo_company_enrich", "prospeo_companies_enrich", "hunter_company_enrich", "findymail_company_enrich",
|
|
18
|
+
"firecrawl_datalegion_companies_enrich_base", "firecrawl_datalegion_companies_enrich_premium", "firecrawl_fullenrich_companies_lookup",
|
|
19
|
+
"predictleads_company", "predictleads_extended_company"],
|
|
20
|
+
get_organization_job_postings: ["apollo_company_jobs", "anyapi_apollo_organization_jobs"],
|
|
21
|
+
find_work_email: ["hunter_email_find", "findymail_email_find", "findymail_email_find_profile", "anyapi_email_find",
|
|
22
|
+
"anyapi_email_finding_dropleads", "anyapi_email_finding_icypeas", "anyapi_email_finding_quickenrich", "anyapi_email_finding_zerobounce",
|
|
23
|
+
"anyapi_linkedin_email", "anyapi_person_enrichment_bettercontact"],
|
|
24
|
+
find_company_emails: ["hunter_domain_search", "anyapi_email_finding_hunter_domain", "findymail_domain_contacts",
|
|
25
|
+
"anyapi_people_search_quickenrich_company"],
|
|
26
|
+
verify_email: ["hunter_email_verify", "debounce_email_verify", "findymail_email_verify", "anyapi_email_verify",
|
|
27
|
+
"anyapi_email_verification_allegrow", "anyapi_email_verification_bounceban", "anyapi_email_verification_icypeas",
|
|
28
|
+
"anyapi_email_verification_zerobounce"],
|
|
29
|
+
search_x_posts: ["anyapi_twitter_search", "tikhub_x_web_fetch_search_timeline"],
|
|
30
|
+
fetch_x_posts: ["tikhub_x_web_fetch_tweet_detail"],
|
|
31
|
+
fetch_x_posts_by_url: ["anyapi_twitter_tweet", "brightdata_twitter_posts_collect_by_url"],
|
|
32
|
+
get_x_post_comments: ["anyapi_twitter_replies", "tikhub_x_web_fetch_post_comments"],
|
|
33
|
+
get_x_profile: ["anyapi_twitter_profile", "tikhub_x_web_fetch_user_profile", "brightdata_twitter_profiles_collect_by_url"],
|
|
34
|
+
get_x_trends: ["anyapi_twitter_trends", "tikhub_x_web_fetch_trending"],
|
|
35
|
+
search_x_communities: ["anyapi_twitter_search_communities", "tikhub_x_web_fetch_search_communities"],
|
|
36
|
+
get_x_community_posts: ["anyapi_twitter_community_tweets", "tikhub_x_web_fetch_community_timeline"],
|
|
37
|
+
search_threads: ["anyapi_threads_search", "apify_threads_search", "tikhub_threads_web_search_posts", "tikhub_threads_web_search_top",
|
|
38
|
+
"tikhub_threads_web_search_recent"],
|
|
39
|
+
search_threads_profiles: ["anyapi_threads_search_users", "tikhub_threads_web_search_profiles"],
|
|
40
|
+
fetch_threads_post: ["anyapi_threads_post", "tikhub_threads_web_fetch_post_detail", "tikhub_threads_web_fetch_post_detail_v2",
|
|
41
|
+
"brightdata_threads_posts_collect_by_url"],
|
|
42
|
+
get_bluesky_profile: ["anyapi_bluesky_profile"],
|
|
43
|
+
get_instagram_profile: ["anyapi_instagram_profile", "anyapi_instagram_basic_profile", "tikhub_instagram_v1_fetch_user_info_by_username",
|
|
44
|
+
"tikhub_instagram_v1_fetch_user_info_by_username_v2", "tikhub_instagram_v1_fetch_user_info_by_username_v3",
|
|
45
|
+
"tikhub_instagram_v2_fetch_user_info", "tikhub_instagram_v3_get_user_profile", "apify_instagram_profile",
|
|
46
|
+
"brightdata_instagram_profiles_collect_by_url", "brightdata_instagram_profiles_discover_by_username", "searchapi_instagram_profile", "serpapi_instagram_profile",
|
|
47
|
+
"firecrawl_oriane_instagram_profile_lookup"],
|
|
48
|
+
search_instagram_posts: ["anyapi_instagram_user_posts", "tikhub_instagram_v1_fetch_user_posts", "tikhub_instagram_v1_fetch_user_posts_v2",
|
|
49
|
+
"tikhub_instagram_v2_fetch_user_posts", "tikhub_instagram_v3_get_user_posts", "apify_instagram_posts",
|
|
50
|
+
"brightdata_instagram_posts_discover_by_url"],
|
|
51
|
+
search_instagram_reels: ["anyapi_instagram_user_reels", "tikhub_instagram_v1_fetch_user_reels", "tikhub_instagram_v2_fetch_user_reels",
|
|
52
|
+
"tikhub_instagram_v3_get_user_reels", "apify_instagram_reels", "brightdata_instagram_reels_discover_by_url",
|
|
53
|
+
"brightdata_instagram_reels_discover_by_url_all_reels"],
|
|
54
|
+
search_instagram_by_keyword: ["anyapi_instagram_hashtag_recent_posts", "anyapi_instagram_hashtag_top_posts", "anyapi_instagram_search_hashtag",
|
|
55
|
+
"tikhub_instagram_v1_fetch_hashtag_posts", "tikhub_instagram_v2_fetch_hashtag_posts", "tikhub_instagram_v3_get_hashtag_posts",
|
|
56
|
+
"apify_instagram_hashtags"],
|
|
57
|
+
search_instagram_keyword: ["anyapi_instagram_search", "tikhub_instagram_v1_fetch_search", "tikhub_instagram_v2_search_users",
|
|
58
|
+
"tikhub_instagram_v2_search_hashtags", "tikhub_instagram_v3_search_users", "tikhub_instagram_v3_search_hashtags"],
|
|
59
|
+
search_instagram_phrase: ["tikhub_instagram_v2_general_search", "tikhub_instagram_v3_general_search"],
|
|
60
|
+
search_instagram_places: ["anyapi_instagram_search_locations", "tikhub_instagram_v2_search_locations",
|
|
61
|
+
"tikhub_instagram_v2_search_by_coordinates", "tikhub_instagram_v3_search_by_coordinates"],
|
|
62
|
+
search_instagram_audio: ["anyapi_instagram_audio_reels", "tikhub_instagram_v1_fetch_music_posts", "tikhub_instagram_v2_fetch_music_posts",
|
|
63
|
+
"tikhub_instagram_v3_get_music_posts", "tikhub_instagram_v2_search_music"],
|
|
64
|
+
get_instagram_post_comments: ["anyapi_instagram_post_comments", "tikhub_instagram_v1_fetch_post_comments_v2",
|
|
65
|
+
"tikhub_instagram_v2_fetch_post_comments", "tikhub_instagram_v3_get_post_comments", "brightdata_instagram_comments_collect_by_url",
|
|
66
|
+
"searchapi_instagram_comments"],
|
|
67
|
+
get_instagram_comment_replies: ["anyapi_instagram_comment_replies", "tikhub_instagram_v1_fetch_comment_replies",
|
|
68
|
+
"tikhub_instagram_v2_fetch_comment_replies", "tikhub_instagram_v3_get_comment_replies"],
|
|
69
|
+
get_instagram_stories: ["anyapi_instagram_stories_full", "anyapi_instagram_stories_thin", "tikhub_instagram_v2_fetch_user_stories",
|
|
70
|
+
"tikhub_instagram_v3_get_user_stories"],
|
|
71
|
+
get_instagram_highlights: ["anyapi_instagram_user_highlights", "tikhub_instagram_v2_fetch_user_highlights",
|
|
72
|
+
"tikhub_instagram_v3_get_user_highlights"],
|
|
73
|
+
get_instagram_tagged_posts: ["anyapi_instagram_tagged_posts", "tikhub_instagram_v1_fetch_user_tagged_posts",
|
|
74
|
+
"tikhub_instagram_v2_fetch_user_tagged_posts", "tikhub_instagram_v3_get_user_tagged_posts"],
|
|
75
|
+
get_instagram_former_usernames: ["tikhub_instagram_v3_get_user_former_usernames"],
|
|
76
|
+
get_instagram_account_network: ["anyapi_instagram_followers", "anyapi_instagram_following", "tikhub_instagram_v2_fetch_user_followers",
|
|
77
|
+
"tikhub_instagram_v2_fetch_user_following", "tikhub_instagram_v3_get_user_followers", "tikhub_instagram_v3_get_user_following"],
|
|
78
|
+
get_instagram_reposts: ["anyapi_instagram_user_reposts", "tikhub_instagram_v1_fetch_user_reposts"],
|
|
79
|
+
find_similar_instagram_accounts: ["anyapi_instagram_similar_profiles", "tikhub_instagram_v1_fetch_related_profiles",
|
|
80
|
+
"tikhub_instagram_v2_fetch_similar_users", "tikhub_instagram_v3_get_similar_users"],
|
|
81
|
+
scrape_facebook: ["anyapi_facebook_profile", "brightdata_facebook_pages_and_profiles_collect_by_url", "searchapi_facebook_business_page",
|
|
82
|
+
"serpapi_facebook_profile"],
|
|
83
|
+
scrape_facebook_posts: ["anyapi_facebook_profile_posts", "apify_facebook_posts", "brightdata_facebook_pages_posts_collect_by_url"],
|
|
84
|
+
search_facebook: ["anyapi_facebook_search_pages", "anyapi_facebook_page_lookup", "apify_facebook_search"],
|
|
85
|
+
search_facebook_posts: ["anyapi_facebook_search_posts", "apify_facebook_post_search"],
|
|
86
|
+
search_facebook_people: ["apify_facebook_people_search"],
|
|
87
|
+
fetch_facebook_post: ["anyapi_facebook_post", "brightdata_facebook_posts_collect_by_url"],
|
|
88
|
+
search_tiktok_posts: ["anyapi_tiktok_search_keyword", "anyapi_tiktok_search_top", "anyapi_tiktok_search_hashtag", "anyapi_tiktok_hashtag_videos",
|
|
89
|
+
"anyapi_tiktok_profile_videos", "tikhub_tiktok_app_v3_fetch_video_search_result", "tikhub_tiktok_web_fetch_search_video",
|
|
90
|
+
"tikhub_tiktok_app_v3_fetch_hashtag_video_list", "tikhub_tiktok_web_fetch_tag_post", "tikhub_tiktok_app_v3_fetch_user_post_videos",
|
|
91
|
+
"tikhub_tiktok_app_v3_fetch_user_post_videos_v2", "tikhub_tiktok_app_v3_fetch_user_post_videos_v3", "tikhub_tiktok_web_fetch_user_post",
|
|
92
|
+
"searchapi_tiktok_search", "searchapi_tiktok_profile_videos", "brightdata_tiktok_posts_discover_by_keyword",
|
|
93
|
+
"brightdata_tiktok_posts_discover_by_profile_url", "brightdata_tiktok_posts_by_profile_fast_api_collect_by_url",
|
|
94
|
+
"firecrawl_oriane_tiktok_posts_search", "apify_tiktok_scrape"],
|
|
95
|
+
get_tiktok_profile: ["anyapi_tiktok_profile", "tikhub_tiktok_web_fetch_user_profile", "tikhub_tiktok_app_v3_handler_user_profile",
|
|
96
|
+
"searchapi_tiktok_profile", "brightdata_tiktok_profiles_collect_by_url", "firecrawl_oriane_tiktok_profile_lookup"],
|
|
97
|
+
get_tiktok_comments: ["anyapi_tiktok_video_comments", "tiktok_comments", "tikhub_tiktok_web_fetch_post_comment",
|
|
98
|
+
"tikhub_tiktok_app_v3_fetch_video_comments", "searchapi_tiktok_comments", "brightdata_tiktok_comments_collect_by_url"],
|
|
99
|
+
get_tiktok_comment_replies: ["anyapi_tiktok_comment_replies", "tikhub_tiktok_web_fetch_post_comment_reply",
|
|
100
|
+
"tikhub_tiktok_app_v3_fetch_video_comment_replies"],
|
|
101
|
+
get_tiktok_video_metrics: ["tikhub_tiktok_analytics_fetch_video_metrics", "anyapi_tiktok_video", "tikhub_tiktok_app_v3_fetch_one_video",
|
|
102
|
+
"tikhub_tiktok_app_v3_fetch_one_video_v2", "tikhub_tiktok_app_v3_fetch_one_video_v3", "tikhub_tiktok_web_fetch_post_detail",
|
|
103
|
+
"tikhub_tiktok_web_fetch_post_detail_v2", "searchapi_tiktok_video", "brightdata_tiktok_posts_collect_by_url",
|
|
104
|
+
"firecrawl_oriane_tiktok_video_lookup"],
|
|
105
|
+
get_tiktok_video_transcript: ["anyapi_tiktok_video_transcript", "anyapi_tiktok_video_transcript_full", "searchapi_tiktok_transcripts",
|
|
106
|
+
"apify_tiktok_transcript"],
|
|
107
|
+
get_tiktok_sound_posts: ["anyapi_tiktok_song_videos", "tikhub_tiktok_app_v3_fetch_music_video_list"],
|
|
108
|
+
get_tiktok_sounds: ["tikhub_tiktok_app_v3_fetch_music_search_result", "tikhub_tiktok_app_v3_fetch_music_chart_list"],
|
|
109
|
+
get_tiktok_trends: ["apify_tiktok_trends", "anyapi_tiktok_trending_feed"],
|
|
110
|
+
get_tiktok_trending_hashtags: ["anyapi_tiktok_trending_hashtags", "tikhub_tiktok_ads_get_trends_hashtag_list"],
|
|
111
|
+
get_tiktok_hashtag_trend_detail: ["tikhub_tiktok_ads_get_trends_hashtag_detail"],
|
|
112
|
+
get_tiktok_search_insights: ["tikhub_tiktok_app_v3_fetch_creator_search_insights"],
|
|
113
|
+
get_tiktok_search_insight_detail: ["tikhub_tiktok_app_v3_fetch_creator_search_insights_detail"],
|
|
114
|
+
get_tiktok_comment_themes: ["tikhub_tiktok_analytics_fetch_comment_keywords"],
|
|
115
|
+
get_tiktok_video_article: ["tikhub_tiktok_web_fetch_post_ai_article"],
|
|
116
|
+
check_tiktok_video_authenticity: ["tikhub_tiktok_analytics_detect_fake_views"],
|
|
117
|
+
find_similar_tiktok_accounts: ["tikhub_tiktok_app_v3_fetch_similar_user_recommendations"],
|
|
118
|
+
search_tiktok_creators: ["anyapi_tiktok_search_users", "tikhub_tiktok_app_v3_fetch_user_search_result"],
|
|
119
|
+
get_tiktok_account_region: ["anyapi_tiktok_profile_region"],
|
|
120
|
+
get_tiktok_ad_detail: ["tikhub_tiktok_ads_get_ads_detail"],
|
|
121
|
+
search_tiktok_top_ads: ["anyapi_tiktok_top_ads_search", "tikhub_tiktok_ads_search_ads"],
|
|
122
|
+
search_tiktok_shop_products: ["anyapi_tiktok_shop_search", "tikhub_tiktok_shop_web_fetch_search_products_list",
|
|
123
|
+
"tikhub_tiktok_shop_web_fetch_search_products_list_v2", "brightdata_tiktok_shop_discover_by_keyword"],
|
|
124
|
+
get_tiktok_shop_product: ["anyapi_tiktok_shop_product", "tikhub_tiktok_shop_web_fetch_product_detail",
|
|
125
|
+
"tikhub_tiktok_shop_web_fetch_product_detail_v3"],
|
|
126
|
+
get_tiktok_shop_product_reviews: ["anyapi_tiktok_shop_product_reviews", "tikhub_tiktok_shop_web_fetch_product_reviews_v2"],
|
|
127
|
+
search_youtube: ["dataforseo_serp_youtube_organic_advanced", "anyapi_youtube_search", "tikhub_youtube_web_search_video", "tikhub_youtube_web_v2_get_general_search_v2",
|
|
128
|
+
"brightdata_youtube_videos_discover_by_keyword", "searchapi_youtube", "serpapi_youtube"],
|
|
129
|
+
search_youtube_channels: ["tikhub_youtube_web_search_channel", "tikhub_youtube_web_v2_search_channels",
|
|
130
|
+
"brightdata_youtube_channels_discover_by_keyword"],
|
|
131
|
+
search_youtube_shorts: ["anyapi_youtube_search_shorts", "tikhub_youtube_web_v2_get_shorts_search", "tikhub_youtube_web_v2_get_shorts_search_v2"],
|
|
132
|
+
get_youtube_video_info: ["dataforseo_serp_youtube_video_info_advanced", "anyapi_youtube_video", "tikhub_youtube_web_v2_get_video_info", "tikhub_youtube_web_v2_get_video_info_v2",
|
|
133
|
+
"brightdata_youtube_videos_collect_by_url", "searchapi_youtube_video", "serpapi_youtube_video"],
|
|
134
|
+
get_youtube_comments: ["dataforseo_serp_youtube_video_comments_advanced", "anyapi_youtube_video_comments", "tikhub_youtube_web_v2_get_video_comments", "searchapi_youtube_comments",
|
|
135
|
+
"brightdata_youtube_comments_collect_by_url"],
|
|
136
|
+
get_youtube_comment_replies: ["anyapi_youtube_comment_replies", "tikhub_youtube_web_v2_get_video_comment_replies",
|
|
137
|
+
"tikhub_youtube_web_v2_get_post_comment_replies"],
|
|
138
|
+
get_youtube_channel_about: ["tikhub_youtube_web_v2_get_channel_description", "tikhub_youtube_web_get_channel_info", "anyapi_youtube_channel",
|
|
139
|
+
"searchapi_youtube_channel", "serpapi_youtube_channel"],
|
|
140
|
+
get_youtube_channel_shorts: ["anyapi_youtube_channel_shorts", "tikhub_youtube_web_v2_get_channel_shorts",
|
|
141
|
+
"tikhub_youtube_web_get_channel_short_videos", "searchapi_youtube_channel_shorts"],
|
|
142
|
+
get_youtube_community_posts: ["anyapi_youtube_channel_community_posts", "tikhub_youtube_web_v2_get_channel_community_posts",
|
|
143
|
+
"searchapi_youtube_channel_posts"],
|
|
144
|
+
get_youtube_related_videos: ["tikhub_youtube_web_get_relate_video", "tikhub_youtube_web_v2_get_related_videos"],
|
|
145
|
+
get_youtube_transcript: ["dataforseo_serp_youtube_video_subtitles_advanced", "anyapi_youtube_video_transcript", "anyapi_youtube_video_transcript_full", "searchapi_youtube_transcripts",
|
|
146
|
+
"serpapi_youtube_video_transcript"],
|
|
147
|
+
get_youtube_trends: ["searchapi_youtube_trends"],
|
|
148
|
+
search_reddit: ["anyapi_reddit_search", "tikhub_reddit_app_fetch_dynamic_search", "brightdata_reddit_posts_discover_by_keyword",
|
|
149
|
+
"apify_reddit_scrape"],
|
|
150
|
+
fetch_reddit_post: ["anyapi_reddit_post", "tikhub_reddit_app_fetch_post_details", "brightdata_reddit_posts_collect_by_url"],
|
|
151
|
+
get_reddit_post_comments: ["anyapi_reddit_post_comments", "tikhub_reddit_app_fetch_post_comments", "brightdata_reddit_comments_collect_by_url"],
|
|
152
|
+
get_subreddit_posts: ["anyapi_reddit_subreddit_posts", "tikhub_reddit_app_fetch_subreddit_feed",
|
|
153
|
+
"brightdata_reddit_posts_discover_by_subreddit_url"],
|
|
154
|
+
get_subreddit_info: ["anyapi_reddit_subreddit_details", "tikhub_reddit_app_fetch_subreddit_info"],
|
|
155
|
+
get_reddit_popular_posts: ["tikhub_reddit_app_fetch_popular_feed", "anyapi_reddit_trending_posts"],
|
|
156
|
+
get_reddit_trending_searches: ["tikhub_reddit_app_fetch_trending_searches"],
|
|
157
|
+
get_reddit_user_profile: ["anyapi_reddit_profile", "tikhub_reddit_app_fetch_user_profile", "brightdata_reddit_profiles_1c7f260527ac"],
|
|
158
|
+
get_reddit_user_posts: ["anyapi_reddit_user_posts", "tikhub_reddit_app_fetch_user_posts"],
|
|
159
|
+
get_reddit_user_comments: ["anyapi_reddit_user_comments", "tikhub_reddit_app_fetch_user_comments"],
|
|
160
|
+
fetch_linkedin_post: ["anyapi_linkedin_post", "tikhub_linkedin_web_v2_get_post_detail", "brightdata_linkedin_posts_collect_by_url"],
|
|
161
|
+
get_linkedin_post_comments: ["anyapi_linkedin_post_comments", "tikhub_linkedin_web_v2_get_post_comments"],
|
|
162
|
+
get_linkedin_job_posting: ["anyapi_linkedin_job", "tikhub_linkedin_web_v2_get_job_detail", "searchapi_linkedin_job_details",
|
|
163
|
+
"brightdata_linkedin_jobs_collect_by_url"],
|
|
164
|
+
search_linkedin_job_postings: ["anyapi_linkedin_jobs", "anyapi_linkedin_jobs_thin", "tikhub_linkedin_web_v2_search_jobs",
|
|
165
|
+
"searchapi_linkedin_jobs", "brightdata_linkedin_jobs_discover_by_keyword"],
|
|
166
|
+
get_substack_post: ["firecrawl_substack_com_newsletters_post"],
|
|
167
|
+
list_substack_posts: ["firecrawl_substack_com_newsletters_archive", "anyapi_substack_posts"],
|
|
168
|
+
search_podcasts: ["firecrawl_particle_podcasts_search"],
|
|
169
|
+
search_podcast_episodes: ["firecrawl_particle_podcasts_episodes_search"],
|
|
170
|
+
get_podcast_show: ["firecrawl_particle_podcasts_show", "anyapi_spotify_podcast"],
|
|
171
|
+
get_podcast_episodes: ["firecrawl_particle_podcasts_show_episodes", "anyapi_spotify_podcast_episodes"],
|
|
172
|
+
web_search: ["search_web", "exa_search", "brave_web", "anyapi_brave_search", "crawl4ai_search"],
|
|
173
|
+
search_news: ["dataforseo_serp_google_news_advanced", "dataforseo_serp_google_news_html", "anyapi_google_news", "searchapi_google_news", "searchapi_google_news_light", "searchapi_bing_news", "brave_news",
|
|
174
|
+
"anyapi_hackernews_search", "serpapi_google_news", "serpapi_google_news_light", "serpapi_bing_news", "serpapi_duckduckgo_news", "serpapi_baidu_news"],
|
|
175
|
+
search_image: ["dataforseo_serp_google_images_advanced", "dataforseo_serp_google_images_html", "brave_images", "anyapi_google_images", "searchapi_google_images", "searchapi_google_images_light", "searchapi_bing_images",
|
|
176
|
+
"searchapi_duckduckgo_images", "serpapi_google_images", "serpapi_google_images_light", "serpapi_bing_images", "serpapi_yahoo_images", "serpapi_yandex_images"],
|
|
177
|
+
search_maps: ["dataforseo_serp_google_maps_advanced", "dataforseo_serp_google_local_finder_advanced", "dataforseo_serp_google_local_finder_html",
|
|
178
|
+
"dataforseo_business_business_listings_search", "anyapi_maps_search", "searchapi_google_maps", "firecrawl_firecrawl_maps_businesses_search", "serpapi_google_maps", "serpapi_google_local",
|
|
179
|
+
"serpapi_bing_maps", "serpapi_apple_maps", "serpapi_duckduckgo_maps", "serpapi_naver_maps"],
|
|
180
|
+
search_github: ["searchapi_github_search"],
|
|
181
|
+
serp_lookup: ["dataforseo_serp_google_organic_advanced", "dataforseo_serp_google_organic_regular", "dataforseo_serp_google_organic_html",
|
|
182
|
+
"dataforseo_serp_bing_organic_advanced", "dataforseo_serp_bing_organic_regular", "dataforseo_serp_bing_organic_html",
|
|
183
|
+
"dataforseo_serp_yahoo_organic_advanced", "dataforseo_serp_yahoo_organic_regular", "dataforseo_serp_yahoo_organic_html",
|
|
184
|
+
"dataforseo_serp_baidu_organic_advanced", "dataforseo_serp_baidu_organic_regular", "dataforseo_serp_baidu_organic_html",
|
|
185
|
+
"dataforseo_serp_naver_organic_advanced", "dataforseo_serp_naver_organic_regular", "dataforseo_serp_naver_organic_html",
|
|
186
|
+
"dataforseo_serp_seznam_organic_advanced", "dataforseo_serp_seznam_organic_regular", "dataforseo_serp_seznam_organic_html", "anyapi_google_search", "anyapi_google_search_100", "anyapi_bing_search", "searchapi_google", "searchapi_google_light",
|
|
187
|
+
"searchapi_google_rank_tracking", "searchapi_bing", "searchapi_naver", "serpapi_google", "serpapi_google_light", "serpapi_bing", "serpapi_naver", "crawl4ai_serp"],
|
|
188
|
+
google_ai_mode: ["dataforseo_serp_google_ai_mode_advanced", "dataforseo_serp_google_ai_mode_html", "anyapi_google_ai_mode", "searchapi_google_ai_mode", "serpapi_google_ai_mode"],
|
|
189
|
+
google_ai_overview: ["anyapi_google_ai_overview", "searchapi_google_ai_overview", "serpapi_google_ai_overview"],
|
|
190
|
+
google_trends: ["searchapi_google_trends_trending_now", "firecrawl_firecrawl_trends_trends_trending_now", "serpapi_google_trends_trending_now"],
|
|
191
|
+
google_trends_interest: ["dataforseo_keywords_google_trends_explore", "dataforseo_keywords_dataforseo_trends_explore", "anyapi_google_trends", "searchapi_google_trends", "firecrawl_firecrawl_trends_trends_interest_over_time", "serpapi_google_trends"],
|
|
192
|
+
seo_keyword_metrics: ["dataforseo_labs_google_keyword_overview", "dataforseo_keywords_google_ads_search_volume", "dataforseo_labs_google_bulk_keyword_difficulty"],
|
|
193
|
+
seo_domain_overview: ["dataforseo_labs_google_domain_rank_overview", "dataforseo_labs_google_bulk_traffic_estimation"],
|
|
194
|
+
seo_domain_keywords: ["dataforseo_labs_google_ranked_keywords", "dataforseo_labs_google_keywords_for_site"],
|
|
195
|
+
get_website_traffic: ["anyapi_similarweb_overview", "firecrawl_similarweb_web_traffic"],
|
|
196
|
+
search_meta_ads_library: ["anyapi_facebook_ads_search", "anyapi_facebook_company_ads", "searchapi_meta_ad_library",
|
|
197
|
+
"brightdata_meta_ad_library__fe525218deb2", "apify_facebook_ads"],
|
|
198
|
+
search_meta_ad_library_details: ["anyapi_facebook_ad_details", "anyapi_facebook_ad_details_full", "searchapi_meta_ad_library_ad_details"],
|
|
199
|
+
search_meta_ad_library_pages: ["anyapi_facebook_search_companies", "searchapi_meta_ad_library_page_search"],
|
|
200
|
+
search_google_ads_transparency: ["anyapi_google_ads_company_ads", "anyapi_google_ads_search", "searchapi_google_ads_transparency_center",
|
|
201
|
+
"serpapi_google_ads_transparency_center"],
|
|
202
|
+
search_google_ads_transparency_details: ["anyapi_google_ads_ad_details", "searchapi_google_ads_transparency_center_ad_details",
|
|
203
|
+
"serpapi_google_ads_transparency_center_ad_details"],
|
|
204
|
+
search_linkedin_ads_library: ["anyapi_linkedin_ads_search", "anyapi_linkedin_ad", "searchapi_linkedin_ad_library",
|
|
205
|
+
"searchapi_linkedin_ad_library_ad_details"],
|
|
206
|
+
search_tiktok_ads_library: ["anyapi_tiktok_ad_library_search", "searchapi_tiktok_ads_library"],
|
|
207
|
+
search_tiktok_ads_library_details: ["anyapi_tiktok_ad_library_ad", "searchapi_tiktok_ads_library_ad_details"],
|
|
208
|
+
search_mobile_apps: ["dataforseo_app_data_apple_app_searches_advanced", "dataforseo_app_data_google_app_searches_advanced", "dataforseo_app_data_google_app_searches_html",
|
|
209
|
+
"dataforseo_app_data_apple_app_listings_search", "dataforseo_app_data_google_app_listings_search", "anyapi_appstore_search", "anyapi_playstore_search", "searchapi_apple_app_store", "searchapi_google_play_store",
|
|
210
|
+
"firecrawl_apps_apple_com_apps_search", "firecrawl_play_google_com_media_app_search", "serpapi_apple_app_store", "serpapi_google_play"],
|
|
211
|
+
get_mobile_app: ["dataforseo_app_data_apple_app_info_advanced", "dataforseo_app_data_google_app_info_advanced", "dataforseo_app_data_google_app_info_html", "anyapi_appstore_app", "anyapi_playstore_app", "searchapi_google_play_product", "firecrawl_apps_apple_com_apps_app",
|
|
212
|
+
"firecrawl_play_google_com_media_app", "serpapi_google_play_product", "serpapi_apple_product"],
|
|
213
|
+
get_public_reviews: ["dataforseo_business_google_reviews", "dataforseo_business_google_extended_reviews", "dataforseo_business_trustpilot_reviews",
|
|
214
|
+
"dataforseo_business_tripadvisor_reviews", "anyapi_trustpilot_reviews", "anyapi_g2_reviews", "anyapi_maps_reviews", "brightdata_trustpilot_reviews_collect_by_url",
|
|
215
|
+
"brightdata_g2_reviews_collect_by_url", "brightdata_google_maps_reviews_collect_by_url", "searchapi_google_maps_reviews", "serpapi_google_maps_reviews",
|
|
216
|
+
"firecrawl_firecrawl_maps_businesses_reviews"],
|
|
217
|
+
};
|
|
@@ -1,20 +1,37 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { TOOL_RESULT_CONTEXT_MAX_BYTES } from "./toolResultContext.js";
|
|
3
|
+
import { platformConnectionPolicySchema } from "./platformConnectionPolicy.js";
|
|
4
|
+
import { platformAppApiPathSchema } from "./platformHttpConstraints.js";
|
|
5
|
+
export { platformOAuthScopeAlternativesSchema, platformOAuthPermissionSchema } from "./platformOAuth.js";
|
|
6
|
+
export const PLATFORM_APP_ID_PATTERN = /^[a-z0-9][a-z0-9_]{0,63}$/;
|
|
7
|
+
export const platformAppIdSchema = z.string().regex(PLATFORM_APP_ID_PATTERN);
|
|
8
|
+
export const platformAppConnectionProviderSchema = z.templateLiteral(["app:", platformAppIdSchema]);
|
|
3
9
|
export const platformConnectionProviderSchema = z.enum(["x", "gsc", "ga", "google", "github", "notion", "slack", "linear", "linkedin", "pinterest", "tiktok", "microsoftAds", "amazonAds"]);
|
|
10
|
+
export const platformAnyConnectionProviderSchema = z.union([platformConnectionProviderSchema, platformAppConnectionProviderSchema]);
|
|
4
11
|
export function platformConnectionOAuthProvider(provider) {
|
|
5
12
|
return provider === "gsc" || provider === "ga" ? "google" : provider === "microsoftAds" ? "microsoft" : provider === "amazonAds" ? "amazon" : provider;
|
|
6
13
|
}
|
|
7
|
-
export const PLATFORM_CONNECTION_CATALOG_LIMITS = { maxRows: 20, responseBytes: 65_536, metadataBytes: 60_000, contextBytes: TOOL_RESULT_CONTEXT_MAX_BYTES };
|
|
14
|
+
export const PLATFORM_CONNECTION_CATALOG_LIMITS = { maxRows: 20, responseBytes: 65_536, metadataBytes: 60_000, policyBytes: 8192, contextBytes: TOOL_RESULT_CONTEXT_MAX_BYTES };
|
|
8
15
|
export const PLATFORM_CONNECTION_PAGE_MAX = PLATFORM_CONNECTION_CATALOG_LIMITS.maxRows;
|
|
9
16
|
export const platformConnectionCatalogQuerySchema = z.object({
|
|
10
|
-
provider:
|
|
17
|
+
provider: platformAnyConnectionProviderSchema.optional(),
|
|
11
18
|
offset: z.number().int().min(0).max(Number.MAX_SAFE_INTEGER).default(0),
|
|
12
19
|
limit: z.number().int().min(1).max(PLATFORM_CONNECTION_PAGE_MAX).default(10),
|
|
13
20
|
}).strict();
|
|
21
|
+
export const platformConnectionAppSchema = z.object({ id: platformAppIdSchema, name: z.string(), docs: z.string(), baseUrl: z.string(),
|
|
22
|
+
baseUrls: z.array(z.string()).optional(),
|
|
23
|
+
apiPaths: z.array(platformAppApiPathSchema).optional(),
|
|
24
|
+
uploadUrls: z.array(z.object({ prefix: z.string(), credentials: z.boolean() })).optional(),
|
|
25
|
+
transport: z.literal("mcp").optional() });
|
|
14
26
|
export const platformConnectionCatalogSchema = z.object({
|
|
15
27
|
connections: z.array(z.object({
|
|
16
|
-
id: z.string(), provider:
|
|
28
|
+
id: z.string(), provider: platformAnyConnectionProviderSchema, label: z.string(),
|
|
17
29
|
status: z.enum(["connected", "reauth_required"]), scopes: z.array(z.string()),
|
|
30
|
+
scopeSyncedAt: z.iso.datetime().nullable().default(null),
|
|
31
|
+
personal: z.boolean().optional(),
|
|
32
|
+
policy: platformConnectionPolicySchema.nullable().optional(),
|
|
33
|
+
policyRestricted: z.boolean().optional(),
|
|
34
|
+
app: platformConnectionAppSchema.optional(),
|
|
18
35
|
})).max(PLATFORM_CONNECTION_PAGE_MAX),
|
|
19
36
|
total: z.number().int().nonnegative(), nextOffset: z.number().int().nonnegative().nullable(),
|
|
20
37
|
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { ACCOUNT_REQUEST_HTTP_METHODS } from "./accountRequestMethods.js";
|
|
3
|
+
export const PLATFORM_CONNECTION_POLICY_LIMITS = { rules: 64, subjects: 100, conditions: 16, pathChars: 2048, pointerChars: 256, valueChars: 2048, jsonChars: 12_000,
|
|
4
|
+
operations: 2_000, operationChars: 128, signInOperationsChars: 32_000 };
|
|
5
|
+
const limits = PLATFORM_CONNECTION_POLICY_LIMITS;
|
|
6
|
+
const ids = z.array(z.string().min(1).max(128)).min(1).max(limits.subjects);
|
|
7
|
+
const condition = z.object({ pointer: z.string().startsWith("/").max(limits.pointerChars),
|
|
8
|
+
operator: z.enum(["equals", "prefix"]).default("equals"), value: z.union([z.string().max(limits.valueChars), z.number().finite(), z.boolean()]) }).strict()
|
|
9
|
+
.refine(value => value.operator !== "prefix" || typeof value.value === "string", "A prefix is a string");
|
|
10
|
+
const common = { effect: z.enum(["allow", "deny"]).default("allow"), memberIds: ids.optional(), apiKeyIds: ids.optional(),
|
|
11
|
+
directoryGroupIds: ids.optional(),
|
|
12
|
+
conditions: z.array(condition).max(limits.conditions).default([]), approvalRequired: z.boolean().default(false) };
|
|
13
|
+
const path = z.string().startsWith("/").max(limits.pathChars).refine(value => !/[?#\\\u0000-\u001f\u007f]/.test(value) && !value.includes("//") && !value.split("/").some(part => part === "." || part === ".."));
|
|
14
|
+
export const platformConnectionPolicyRuleSchema = z.discriminatedUnion("kind", [
|
|
15
|
+
z.object({ ...common, kind: z.literal("http"), methods: z.array(z.enum(ACCOUNT_REQUEST_HTTP_METHODS)).min(1).max(ACCOUNT_REQUEST_HTTP_METHODS.length),
|
|
16
|
+
path, prefix: z.boolean().default(false), baseUrl: z.string().max(limits.pathChars).optional() }).strict(),
|
|
17
|
+
z.object({ ...common, kind: z.literal("mcp"), tool: z.string().min(1).max(256) }).strict(),
|
|
18
|
+
z.object({ ...common, kind: z.literal("database"), operations: z.array(z.enum(["schema", "query", "execute"])).min(1).max(3) }).strict(),
|
|
19
|
+
z.object({ ...common, kind: z.literal("operation"), action: z.string().min(1).max(128) }).strict(),
|
|
20
|
+
]).refine(rule => rule.effect !== "deny" || !rule.approvalRequired, "A refusal cannot be approved");
|
|
21
|
+
export const PLATFORM_CONNECTION_READ_OPERATIONS = "reads";
|
|
22
|
+
const operationNames = z.array(z.string().min(1).max(limits.operationChars)).max(limits.operations);
|
|
23
|
+
export const platformConnectionOperationsSchema = z.object({ allow: operationNames.optional(), deny: operationNames.default([]) }).strict();
|
|
24
|
+
export const platformConnectionPolicySchema = z.object({ version: z.literal(1), defaultEffect: z.enum(["allow", "deny"]).default("deny"),
|
|
25
|
+
rules: z.array(platformConnectionPolicyRuleSchema).max(limits.rules), operations: platformConnectionOperationsSchema.optional() }).strict()
|
|
26
|
+
.refine(value => JSON.stringify({ ...value, operations: undefined }).length <= limits.jsonChars, "Policy is too large");
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { projectScopeSchema, platformJsonObjectSchema, PLATFORM_SANDBOX_LIMITS } from "./platformPublicExecution.js";
|
|
3
|
+
import { PLATFORM_APP_HOST_PATTERN, PLATFORM_APP_RESERVED_HEADERS } from "./platformHttpConstraints.js";
|
|
4
|
+
import { platformTeamSecretNameSchema } from "./platformTeamSecretName.js";
|
|
5
|
+
export const PLATFORM_CUSTOM_TOOL_PREFIX = "custom__";
|
|
6
|
+
export const PLATFORM_CUSTOM_TOOL_LIMITS = {
|
|
7
|
+
perProject: 100,
|
|
8
|
+
versions: 50,
|
|
9
|
+
titleChars: 80,
|
|
10
|
+
descriptionChars: 1_000,
|
|
11
|
+
codeChars: 40_000,
|
|
12
|
+
schemaBytes: 16_384,
|
|
13
|
+
inputBytes: 16_384,
|
|
14
|
+
};
|
|
15
|
+
export const PLATFORM_CUSTOM_ENDPOINT_LIMITS = { bindings: 5, baseUrlChars: 2_048, nameChars: 64, templateChars: 256, secretChars: 4_096 };
|
|
16
|
+
export const platformCustomToolKindSchema = z.enum(["python", "http"]);
|
|
17
|
+
export const platformCustomEndpointLocationSchema = z.enum(["header", "query"]);
|
|
18
|
+
const HEADER_NAME = /^[A-Za-z0-9_-]{1,64}$/;
|
|
19
|
+
const QUERY_NAME = /^[A-Za-z0-9_.$-]{1,64}$/;
|
|
20
|
+
const FORBIDDEN_BINDING_HEADER = /^(?:keep-alive|trailer|proxy-.*|x-forwarded-.*|x-real-ip|set-cookie|accept-encoding|content-type|accept)$/i;
|
|
21
|
+
const CONTROL = /[\u0000-\u001f\u007f]/;
|
|
22
|
+
export const PLATFORM_CUSTOM_ENDPOINT_SECRET_PLACEHOLDER = "{secret}";
|
|
23
|
+
export function isPlatformCustomEndpointBaseUrl(value) {
|
|
24
|
+
try {
|
|
25
|
+
const url = new URL(value);
|
|
26
|
+
return url.protocol === "https:" && !url.username && !url.password && !url.hash && !url.search && !value.includes("?") && !value.includes("#")
|
|
27
|
+
&& PLATFORM_APP_HOST_PATTERN.test(url.host) && !/(?:^|\/)\.{1,2}(?:\/|$)/.test(url.pathname) && !/[\\\s]/.test(value) && !CONTROL.test(value);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export const platformCustomEndpointBaseUrlSchema = z.string().trim().min(1).max(PLATFORM_CUSTOM_ENDPOINT_LIMITS.baseUrlChars)
|
|
34
|
+
.refine(isPlatformCustomEndpointBaseUrl, "An https URL on a public host name, without credentials, a query or a fragment")
|
|
35
|
+
.describe("The API root, e.g. https://api.example.com or https://api.example.com/v1. Requests go to a path under it.");
|
|
36
|
+
const bindingTemplateSchema = z.string().min(1).max(PLATFORM_CUSTOM_ENDPOINT_LIMITS.templateChars)
|
|
37
|
+
.refine(value => value.includes(PLATFORM_CUSTOM_ENDPOINT_SECRET_PLACEHOLDER) && value.replaceAll(PLATFORM_CUSTOM_ENDPOINT_SECRET_PLACEHOLDER, "").search(/[{}]/) === -1 && !CONTROL.test(value), "The template contains {secret} and no other braces or control characters");
|
|
38
|
+
const bindingNameCheck = (binding) => binding.location === "header"
|
|
39
|
+
? HEADER_NAME.test(binding.name) && !PLATFORM_APP_RESERVED_HEADERS.has(binding.name.toLowerCase()) && !FORBIDDEN_BINDING_HEADER.test(binding.name)
|
|
40
|
+
: QUERY_NAME.test(binding.name);
|
|
41
|
+
const bindingLocation = platformCustomEndpointLocationSchema.describe("header or query: where Relvo puts the credential on every request.");
|
|
42
|
+
const bindingName = z.string().min(1).max(PLATFORM_CUSTOM_ENDPOINT_LIMITS.nameChars).describe("The header or query parameter name, e.g. Authorization or api_key.");
|
|
43
|
+
export const platformCustomEndpointBindingInputSchema = z.object({
|
|
44
|
+
id: z.string().regex(/^[a-z0-9]{8,32}$/).optional().describe("Only when editing: the id of a saved binding whose secret is kept when no new secret is sent."),
|
|
45
|
+
location: bindingLocation,
|
|
46
|
+
name: bindingName,
|
|
47
|
+
template: bindingTemplateSchema.default(PLATFORM_CUSTOM_ENDPOINT_SECRET_PLACEHOLDER).describe("The value sent, with {secret} where the secret goes, e.g. Bearer {secret}."),
|
|
48
|
+
secret: z.string().min(1).max(PLATFORM_CUSTOM_ENDPOINT_LIMITS.secretChars).refine(value => !CONTROL.test(value), "The secret has no control characters").optional()
|
|
49
|
+
.describe("The credential. Required for a new binding without secretRef; sent again only to replace it. Relvo never returns it."),
|
|
50
|
+
secretRef: platformTeamSecretNameSchema.optional().describe("The name of a saved team secret to use instead of an inline secret. Only the console may set it."),
|
|
51
|
+
}).strict().refine(bindingNameCheck, "A valid header or query parameter name that is not reserved")
|
|
52
|
+
.refine(binding => binding.id !== undefined || binding.secret !== undefined || binding.secretRef !== undefined, "A new binding needs its secret")
|
|
53
|
+
.refine(binding => binding.secret === undefined || binding.secretRef === undefined, "A binding takes a secret or a secretRef, not both");
|
|
54
|
+
export const platformCustomEndpointBindingSchema = z.object({ id: z.string(), location: bindingLocation, name: bindingName, template: bindingTemplateSchema,
|
|
55
|
+
maskedSecret: z.string().nullable(), secretRef: platformTeamSecretNameSchema.nullable().default(null) }).strict();
|
|
56
|
+
export const platformCustomEndpointSchema = z.object({ baseUrl: platformCustomEndpointBaseUrlSchema,
|
|
57
|
+
bindings: z.array(platformCustomEndpointBindingSchema).min(1).max(PLATFORM_CUSTOM_ENDPOINT_LIMITS.bindings) }).strict();
|
|
58
|
+
export const platformCustomToolSlugSchema = z.string().regex(/^[a-z][a-z0-9_]{1,39}$/)
|
|
59
|
+
.describe("Lowercase letters, digits and underscores, starting with a letter; the tool is named custom__<slug>.");
|
|
60
|
+
export const PLATFORM_CUSTOM_TOOL_NAME = /^custom__[a-z][a-z0-9_]{1,39}$/;
|
|
61
|
+
export const platformCustomToolNameSchema = z.string().regex(PLATFORM_CUSTOM_TOOL_NAME);
|
|
62
|
+
export const isPlatformCustomToolName = (name) => PLATFORM_CUSTOM_TOOL_NAME.test(name);
|
|
63
|
+
export const platformCustomToolInputSchemaSchema = platformJsonObjectSchema
|
|
64
|
+
.refine(schema => schema.type === "object" && schema.properties !== null && typeof schema.properties === "object" && !Array.isArray(schema.properties), "The input schema is an object schema with properties")
|
|
65
|
+
.refine(schema => new TextEncoder().encode(JSON.stringify(schema)).byteLength <= PLATFORM_CUSTOM_TOOL_LIMITS.schemaBytes, "The input schema is too large");
|
|
66
|
+
export const platformCustomToolDefinitionSchema = z.object({
|
|
67
|
+
kind: z.literal("python").default("python"),
|
|
68
|
+
title: z.string().trim().min(1).max(PLATFORM_CUSTOM_TOOL_LIMITS.titleChars),
|
|
69
|
+
description: z.string().trim().min(1).max(PLATFORM_CUSTOM_TOOL_LIMITS.descriptionChars).describe("What the tool does and returns; agents choose tools by it."),
|
|
70
|
+
inputSchema: platformCustomToolInputSchemaSchema.describe("JSON Schema of the input object, e.g. {\"type\":\"object\",\"properties\":{\"days\":{\"type\":\"integer\"}},\"required\":[\"days\"]}."),
|
|
71
|
+
code: z.string().min(1).max(PLATFORM_CUSTOM_TOOL_LIMITS.codeChars)
|
|
72
|
+
.describe("Python. The validated input is the dict `input`; print the result (JSON is easiest to read back). Files of sourceExecutionIds are listed in /home/user/tool-results/manifest.json. No credentials are present."),
|
|
73
|
+
timeoutMs: z.number().int().min(1000).max(PLATFORM_SANDBOX_LIMITS.timeoutMs).default(30_000),
|
|
74
|
+
enabled: z.boolean().default(true),
|
|
75
|
+
}).strict();
|
|
76
|
+
const uniqueBindings = (bindings) => new Set(bindings.map(binding => `${binding.location}:${binding.name.toLowerCase()}`)).size === bindings.length;
|
|
77
|
+
export const platformCustomEndpointDefinitionSchema = z.object({
|
|
78
|
+
kind: z.literal("http"),
|
|
79
|
+
title: z.string().trim().min(1).max(PLATFORM_CUSTOM_TOOL_LIMITS.titleChars),
|
|
80
|
+
description: z.string().trim().min(1).max(PLATFORM_CUSTOM_TOOL_LIMITS.descriptionChars).describe("What the API is and when to use it; agents choose tools by it."),
|
|
81
|
+
baseUrl: platformCustomEndpointBaseUrlSchema,
|
|
82
|
+
bindings: z.array(platformCustomEndpointBindingInputSchema).min(1).max(PLATFORM_CUSTOM_ENDPOINT_LIMITS.bindings).refine(uniqueBindings, "Each header or query parameter is bound once")
|
|
83
|
+
.describe("Credentials Relvo adds to every request, in the header or query parameter named. Agents never see them."),
|
|
84
|
+
enabled: z.boolean().default(true),
|
|
85
|
+
}).strict();
|
|
86
|
+
const slugged = { slug: platformCustomToolSlugSchema };
|
|
87
|
+
const versioned = { expectedVersion: z.number().int().min(1) };
|
|
88
|
+
export const platformCustomToolCreateSchema = z.union([platformCustomToolDefinitionSchema.extend(slugged).strict(), platformCustomEndpointDefinitionSchema.extend(slugged).strict()]);
|
|
89
|
+
export const platformCustomToolUpdateSchema = z.union([platformCustomToolDefinitionSchema.extend(versioned).strict(), platformCustomEndpointDefinitionSchema.extend(versioned).strict()]);
|
|
90
|
+
export const platformCustomToolRestoreSchema = z.object({ version: z.number().int().min(1), expectedVersion: z.number().int().min(1) }).strict();
|
|
91
|
+
export const platformCustomToolSchema = z.object({
|
|
92
|
+
kind: platformCustomToolKindSchema.default("python"), endpoint: platformCustomEndpointSchema.nullable().default(null),
|
|
93
|
+
name: platformCustomToolNameSchema, slug: platformCustomToolSlugSchema, title: z.string(), description: z.string(), inputSchema: platformJsonObjectSchema,
|
|
94
|
+
code: z.string(), timeoutMs: z.number().int(), enabled: z.boolean(), version: z.number().int().min(1), createdAt: z.string(), updatedAt: z.string(),
|
|
95
|
+
});
|
|
96
|
+
export const platformCustomToolSummarySchema = platformCustomToolSchema.omit({ code: true, inputSchema: true });
|
|
97
|
+
export const platformCustomToolVersionSchema = z.object({ version: z.number().int().min(1), title: z.string(), createdAt: z.string() });
|
|
98
|
+
export const platformVersionAuthorSchema = z.object({ email: z.string().nullable(), apiKey: z.string().nullable() }).strict();
|
|
99
|
+
export const platformCustomToolHistorySchema = platformCustomToolVersionSchema.extend({ author: platformVersionAuthorSchema });
|
|
100
|
+
export const platformCustomToolVersionDetailSchema = platformCustomToolSchema.pick({ kind: true, endpoint: true, title: true, description: true, inputSchema: true, code: true, timeoutMs: true, enabled: true })
|
|
101
|
+
.extend({ version: z.number().int().min(1), createdAt: z.string(), author: platformVersionAuthorSchema });
|
|
102
|
+
const toolInput = projectScopeSchema.extend({ name: platformCustomToolNameSchema });
|
|
103
|
+
const toolResult = z.object({ tool: platformCustomToolSchema }).strict();
|
|
104
|
+
export const platformCustomToolContracts = {
|
|
105
|
+
listDeveloperCustomTools: { input: projectScopeSchema.strict(), output: z.object({ tools: z.array(platformCustomToolSummarySchema) }).strict() },
|
|
106
|
+
getDeveloperCustomTool: { input: toolInput.strict(), output: toolResult },
|
|
107
|
+
createDeveloperCustomTool: { input: projectScopeSchema.extend({ tool: platformCustomToolCreateSchema }).strict(), output: toolResult },
|
|
108
|
+
updateDeveloperCustomTool: { input: toolInput.extend({ tool: platformCustomToolUpdateSchema }).strict(), output: toolResult },
|
|
109
|
+
restoreDeveloperCustomTool: { input: toolInput.merge(platformCustomToolRestoreSchema).strict(), output: toolResult },
|
|
110
|
+
deleteDeveloperCustomTool: { input: toolInput.strict(), output: z.object({ success: z.literal(true) }).strict() },
|
|
111
|
+
listDeveloperCustomToolVersions: { input: toolInput.strict(), output: z.object({ versions: z.array(platformCustomToolHistorySchema) }).strict() },
|
|
112
|
+
getDeveloperCustomToolVersion: { input: toolInput.extend({ version: z.number().int().min(1) }).strict(), output: z.object({ version: platformCustomToolVersionDetailSchema }).strict() },
|
|
113
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { PLATFORM_CHECKOUT_MIN_CENTS } from "./platformBillingConstants.js";
|
|
3
|
+
const micros = z.string().regex(/^\d{1,20}$/);
|
|
4
|
+
export const platformFundingShortfallSchema = z.object({
|
|
5
|
+
reason: z.enum(["balance", "organization_monthly_limit", "project_monthly_limit", "organization_daily_limit", "api_key_daily_limit"]),
|
|
6
|
+
requiredMicros: micros,
|
|
7
|
+
availableMicros: micros,
|
|
8
|
+
});
|
|
9
|
+
export const PLATFORM_WALLET_TOP_UP_MIN_CENTS = PLATFORM_CHECKOUT_MIN_CENTS;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { ACCOUNT_REQUEST_HTTP_METHODS } from "./accountRequestMethods.js";
|
|
3
|
+
export const PLATFORM_APP_HOST_PATTERN = /^(?=[^:]{4,253}(?::|$))(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z][a-z0-9-]{0,61}[a-z0-9](?::[1-9][0-9]{0,4})?$/;
|
|
4
|
+
export const PLATFORM_APP_RESERVED_HEADERS = new Set(["host", "cookie", "content-length", "transfer-encoding", "connection", "proxy-authorization", "forwarded", "te", "upgrade", "expect"]);
|
|
5
|
+
export const platformAppApiPathSchema = z.object({ baseUrl: z.number().int().min(0).max(8), method: z.enum(ACCOUNT_REQUEST_HTTP_METHODS),
|
|
6
|
+
path: z.string().startsWith("/").max(2048).refine(path => !/[?#\\\s\u0000-\u001f]/.test(path) && !path.includes("//")
|
|
7
|
+
&& path.split("/").every(segment => segment !== "." && segment !== ".." && (!/[{}]/.test(segment) || /^\{[A-Za-z_][A-Za-z0-9_]*\}$/.test(segment)))) }).strict();
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export const PLATFORM_MCP_DIRECT_TOOLS_MAX = 20;
|
|
2
|
+
export function platformMcpDirectToolNames(list) {
|
|
3
|
+
const names = [...new Set((list ?? "").split(",").map(name => name.trim()).filter(Boolean))];
|
|
4
|
+
if (names.length > PLATFORM_MCP_DIRECT_TOOLS_MAX || names.some(name => !/^[a-z][a-z0-9_]{1,63}$/u.test(name)))
|
|
5
|
+
throw new Error(`Name at most ${PLATFORM_MCP_DIRECT_TOOLS_MAX} catalog tools, separated by commas.`);
|
|
6
|
+
return names;
|
|
7
|
+
}
|