@scavio/mcp-server 0.13.0 → 0.14.0
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 +1 -1
- package/dist/lib/trim-response.d.ts +6 -0
- package/dist/lib/trim-response.js +26 -0
- package/dist/lib/trim-response.js.map +1 -0
- package/dist/tools/airbnb.js +30 -49
- package/dist/tools/airbnb.js.map +1 -1
- package/dist/tools/amazon.js +11 -18
- package/dist/tools/amazon.js.map +1 -1
- package/dist/tools/appstore.js +16 -15
- package/dist/tools/appstore.js.map +1 -1
- package/dist/tools/booking.js +28 -45
- package/dist/tools/booking.js.map +1 -1
- package/dist/tools/capterra.js +16 -15
- package/dist/tools/capterra.js.map +1 -1
- package/dist/tools/companieshouse.js +11 -10
- package/dist/tools/companieshouse.js.map +1 -1
- package/dist/tools/ebay.js +21 -30
- package/dist/tools/ebay.js.map +1 -1
- package/dist/tools/extract.js +6 -5
- package/dist/tools/extract.js.map +1 -1
- package/dist/tools/g2.js +23 -22
- package/dist/tools/g2.js.map +1 -1
- package/dist/tools/glassdoor.js +18 -17
- package/dist/tools/glassdoor.js.map +1 -1
- package/dist/tools/google.js +154 -153
- package/dist/tools/google.js.map +1 -1
- package/dist/tools/googleads.js +20 -19
- package/dist/tools/googleads.js.map +1 -1
- package/dist/tools/googleplay.js +14 -13
- package/dist/tools/googleplay.js.map +1 -1
- package/dist/tools/homedepot.js +15 -28
- package/dist/tools/homedepot.js.map +1 -1
- package/dist/tools/indeed.js +20 -39
- package/dist/tools/indeed.js.map +1 -1
- package/dist/tools/instagram.js +59 -58
- package/dist/tools/instagram.js.map +1 -1
- package/dist/tools/kuaishou.js +49 -48
- package/dist/tools/kuaishou.js.map +1 -1
- package/dist/tools/linkedin.js +26 -25
- package/dist/tools/linkedin.js.map +1 -1
- package/dist/tools/metaads.js +21 -20
- package/dist/tools/metaads.js.map +1 -1
- package/dist/tools/reddit.js +48 -47
- package/dist/tools/reddit.js.map +1 -1
- package/dist/tools/redfin.js +29 -28
- package/dist/tools/redfin.js.map +1 -1
- package/dist/tools/sec.js +36 -35
- package/dist/tools/sec.js.map +1 -1
- package/dist/tools/target.js +19 -40
- package/dist/tools/target.js.map +1 -1
- package/dist/tools/threads.js +20 -19
- package/dist/tools/threads.js.map +1 -1
- package/dist/tools/tiktok-shop.js +38 -37
- package/dist/tools/tiktok-shop.js.map +1 -1
- package/dist/tools/tiktok.js +54 -53
- package/dist/tools/tiktok.js.map +1 -1
- package/dist/tools/tripadvisor.js +20 -43
- package/dist/tools/tripadvisor.js.map +1 -1
- package/dist/tools/usage.js +3 -2
- package/dist/tools/usage.js.map +1 -1
- package/dist/tools/walmart.js +35 -34
- package/dist/tools/walmart.js.map +1 -1
- package/dist/tools/x.js +44 -43
- package/dist/tools/x.js.map +1 -1
- package/dist/tools/yelp.js +21 -20
- package/dist/tools/yelp.js.map +1 -1
- package/dist/tools/youtube.js +77 -76
- package/dist/tools/youtube.js.map +1 -1
- package/dist/tools/zillow.js +38 -53
- package/dist/tools/zillow.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|

|
|
4
4
|

|
|
5
5
|
|
|
6
|
-
[Scavio](https://scavio.dev) is a unified [Web Search API](https://scavio.dev/docs/search-api) and MCP server that gives AI agents web search, page extraction, and structured data from e-commerce, social, travel, jobs, real-estate, app-store, ad-library and company-filing sources. 191 tools across
|
|
6
|
+
[Scavio](https://scavio.dev) is a unified [Web Search API](https://scavio.dev/docs/search-api) and MCP server that gives AI agents web search, page extraction, and structured data from e-commerce, social, travel, jobs, real-estate, app-store, ad-library and company-filing sources. 191 tools across 31 platforms plus Extract, one API key.
|
|
7
7
|
|
|
8
8
|
**The 22 platforms added in 0.13.0 are opt-in.** Registering all 191 tools puts 262KB of tool definitions — roughly 70k tokens — into every session before you type anything. So the default is the surface 0.12.x already had, plus Extract: 106 tools, 102KB. Upgrading never removes a tool you were using. Everything else is one env var away: see [Choosing which tools load](#choosing-which-tools-load).
|
|
9
9
|
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
function stripEmpty(val) {
|
|
2
|
+
if (val === null || val === undefined || val === "")
|
|
3
|
+
return undefined;
|
|
4
|
+
if (Array.isArray(val)) {
|
|
5
|
+
const filtered = val.map(stripEmpty).filter((v) => v !== undefined);
|
|
6
|
+
return filtered.length ? filtered : undefined;
|
|
7
|
+
}
|
|
8
|
+
if (typeof val === "object") {
|
|
9
|
+
const out = {};
|
|
10
|
+
let hasKey = false;
|
|
11
|
+
for (const [k, v] of Object.entries(val)) {
|
|
12
|
+
const clean = stripEmpty(v);
|
|
13
|
+
if (clean !== undefined) {
|
|
14
|
+
out[k] = clean;
|
|
15
|
+
hasKey = true;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return hasKey ? out : undefined;
|
|
19
|
+
}
|
|
20
|
+
return val;
|
|
21
|
+
}
|
|
22
|
+
export function trimResponse(data) {
|
|
23
|
+
const trimmed = stripEmpty(data) ?? {};
|
|
24
|
+
return { content: [{ type: "text", text: JSON.stringify(trimmed) }] };
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=trim-response.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trim-response.js","sourceRoot":"","sources":["../../src/lib/trim-response.ts"],"names":[],"mappings":"AAEA,SAAS,UAAU,CAAC,GAAc;IAChC,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,KAAK,SAAS,IAAI,GAAG,KAAK,EAAE;QAAE,OAAO,SAAS,CAAC;IAEtE,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;QACvB,MAAM,QAAQ,GAAG,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,SAAS,CAAgB,CAAC;QACnF,OAAO,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC;IAChD,CAAC;IAED,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QAC5B,MAAM,GAAG,GAA8B,EAAE,CAAC;QAC1C,IAAI,MAAM,GAAG,KAAK,CAAC;QACnB,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;YACzC,MAAM,KAAK,GAAG,UAAU,CAAC,CAAc,CAAC,CAAC;YACzC,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;gBACxB,GAAG,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC;gBACf,MAAM,GAAG,IAAI,CAAC;YAChB,CAAC;QACH,CAAC;QACD,OAAO,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC;IAClC,CAAC;IAED,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,IAAa;IACxC,MAAM,OAAO,GAAG,UAAU,CAAC,IAAiB,CAAC,IAAI,EAAE,CAAC;IACpD,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,CAAC;AACxE,CAAC"}
|
package/dist/tools/airbnb.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { handleApiError } from "../lib/tool-error.js";
|
|
3
|
+
import { trimResponse } from "../lib/trim-response.js";
|
|
3
4
|
// No zod `.default()` anywhere in this file, on purpose. The MCP SDK applies a
|
|
4
5
|
// zod default BEFORE the handler runs, so a defaulted field is posted on every
|
|
5
6
|
// single call whether the model set it or not — which is exactly how the
|
|
@@ -11,107 +12,87 @@ const ISO_DATE = /^\d{4}-\d{2}-\d{2}$/;
|
|
|
11
12
|
// tool schemas share an instance.
|
|
12
13
|
const stayFields = () => ({
|
|
13
14
|
check_in: z.string().regex(ISO_DATE, "check_in must be YYYY-MM-DD").optional()
|
|
14
|
-
.describe("
|
|
15
|
+
.describe("YYYY-MM-DD. Must pair with check_out."),
|
|
15
16
|
check_out: z.string().regex(ISO_DATE, "check_out must be YYYY-MM-DD").optional()
|
|
16
|
-
.describe("
|
|
17
|
+
.describe("YYYY-MM-DD. Must pair with check_in."),
|
|
17
18
|
adults: z.number().int().min(1).optional()
|
|
18
|
-
.describe("Adults
|
|
19
|
+
.describe("Adults."),
|
|
19
20
|
children: z.number().int().min(0).optional()
|
|
20
|
-
.describe("Children
|
|
21
|
+
.describe("Children 2-12."),
|
|
21
22
|
infants: z.number().int().min(0).optional()
|
|
22
23
|
.describe("Infants under 2."),
|
|
23
24
|
pets: z.number().int().min(0).optional()
|
|
24
|
-
.describe("Pets
|
|
25
|
+
.describe("Pets."),
|
|
25
26
|
});
|
|
26
27
|
const currencyField = () => z.string().regex(/^[A-Za-z]{3}$/, "currency must be a 3-letter ISO 4217 code").optional()
|
|
27
|
-
.describe("
|
|
28
|
+
.describe("Currency, e.g. 'USD'. Default USD.");
|
|
28
29
|
const listingIdField = () => z.string().min(1).max(500)
|
|
29
|
-
.describe("
|
|
30
|
+
.describe("Listing id or airbnb.com/rooms/ URL.");
|
|
30
31
|
export function registerAirbnbTools(server, getClient) {
|
|
31
|
-
server.tool("search_airbnb", `Search Airbnb stays
|
|
32
|
-
|
|
33
|
-
This is the ONLY source of Airbnb prices. get_airbnb_listing returns no nightly rate under any parameters, so any pricing question has to be answered from here.
|
|
34
|
-
|
|
35
|
-
Send check_in and check_out TOGETHER. A dateless search silently defaults to a window 30 days out for 5 nights AND Airbnb A/B-tests both the window and the prices — the same URL has returned first-row prices of $680, $802 and $2,238 across runs. The response flags this as dates_are_defaulted. Pass explicit dates for anything you intend to reproduce or compare.
|
|
36
|
-
|
|
37
|
-
min_price and max_price are WHOLE-STAY totals, not per night. currency defaults to USD. room_type and the amenity names are validated before the scrape on purpose: an unrecognised value is not an error upstream, it returns the UNFILTERED set under a 200.
|
|
38
|
-
|
|
39
|
-
Paginate with page (18 listings per page) OR with cursor from a previous response — never both, because cursor wins and the request is rejected rather than silently billed for a discarded page number. Costs 1 credit per page.`, {
|
|
32
|
+
server.tool("search_airbnb", `Search Airbnb stays. ONLY source of prices (get_airbnb_listing has none). Send check_in+check_out together or prices default to random window. Prices are whole-stay totals, not per night. Paginate with page OR cursor, not both. 1 credit/page.`, {
|
|
40
33
|
location: z.string().min(1).max(200)
|
|
41
|
-
.describe("
|
|
34
|
+
.describe("City, region, ZIP, or airbnb.com/s/ URL."),
|
|
42
35
|
...stayFields(),
|
|
43
36
|
min_price: z.number().min(0).optional()
|
|
44
|
-
.describe("
|
|
37
|
+
.describe("Min whole-stay total (not per night)."),
|
|
45
38
|
max_price: z.number().min(0).optional()
|
|
46
|
-
.describe("
|
|
39
|
+
.describe("Max whole-stay total."),
|
|
47
40
|
room_type: z.enum(["entire_home", "private_room", "shared_room", "hotel_room"]).optional()
|
|
48
|
-
.describe("
|
|
41
|
+
.describe("Room type filter."),
|
|
49
42
|
min_bedrooms: z.number().int().min(0).optional()
|
|
50
|
-
.describe("
|
|
43
|
+
.describe("Min bedrooms."),
|
|
51
44
|
min_beds: z.number().int().min(0).optional()
|
|
52
|
-
.describe("
|
|
45
|
+
.describe("Min beds."),
|
|
53
46
|
min_bathrooms: z.number().int().min(0).optional()
|
|
54
|
-
.describe("
|
|
47
|
+
.describe("Min baths."),
|
|
55
48
|
superhost: z.boolean().optional()
|
|
56
|
-
.describe("
|
|
49
|
+
.describe("Superhost only."),
|
|
57
50
|
instant_book: z.boolean().optional()
|
|
58
|
-
.describe("
|
|
51
|
+
.describe("Instant Book only."),
|
|
59
52
|
guest_favorite: z.boolean().optional()
|
|
60
|
-
.describe("
|
|
53
|
+
.describe("Guest Favourite only."),
|
|
61
54
|
free_cancellation: z.boolean().optional()
|
|
62
|
-
.describe("
|
|
55
|
+
.describe("Free cancellation only."),
|
|
63
56
|
amenities: z.string().min(1).max(200).optional()
|
|
64
|
-
.describe("Comma-separated
|
|
57
|
+
.describe("Comma-separated, e.g. 'wifi,pool'. Names: wifi, air_conditioning, pool, kitchen, free_parking, washer, self_check_in, tv."),
|
|
65
58
|
currency: currencyField(),
|
|
66
59
|
page: z.number().int().min(1).optional()
|
|
67
|
-
.describe("
|
|
60
|
+
.describe("Page, 1-based. Cannot combine with cursor."),
|
|
68
61
|
cursor: z.string().min(1).max(500).optional()
|
|
69
|
-
.describe("
|
|
62
|
+
.describe("Cursor from previous response. Cannot combine with page."),
|
|
70
63
|
}, async (params) => {
|
|
71
64
|
try {
|
|
72
65
|
const data = await getClient().post("/api/v1/airbnb/search", params);
|
|
73
|
-
return
|
|
66
|
+
return trimResponse(data);
|
|
74
67
|
}
|
|
75
68
|
catch (err) {
|
|
76
69
|
return handleApiError(err);
|
|
77
70
|
}
|
|
78
71
|
});
|
|
79
|
-
server.tool("get_airbnb_listing", `Get
|
|
80
|
-
|
|
81
|
-
IT RETURNS NO NIGHTLY PRICE. The Airbnb room page carries no rate under ANY parameters, with or without dates. Prices are search-only: use search_airbnb for them.
|
|
82
|
-
|
|
83
|
-
The rating breakdown lives here and NOT on get_airbnb_reviews, so this is the tool for aggregate review sentiment; get_airbnb_reviews is for individual review bodies.
|
|
84
|
-
|
|
85
|
-
Accepts a listing id or a full /rooms/ URL (query parameters are discarded — they carry someone else's dates). The stay parameters are accepted and echoed back, but they do not price anything. No pagination. Costs 1 credit.`, {
|
|
72
|
+
server.tool("get_airbnb_listing", `Get full Airbnb listing details, amenities, host, rules, photos, rating breakdown. NO PRICES here; use search_airbnb for pricing. 1 credit.`, {
|
|
86
73
|
listing_id: listingIdField(),
|
|
87
74
|
...stayFields(),
|
|
88
75
|
currency: currencyField(),
|
|
89
76
|
}, async (params) => {
|
|
90
77
|
try {
|
|
91
78
|
const data = await getClient().post("/api/v1/airbnb/listing", params);
|
|
92
|
-
return
|
|
79
|
+
return trimResponse(data);
|
|
93
80
|
}
|
|
94
81
|
catch (err) {
|
|
95
82
|
return handleApiError(err);
|
|
96
83
|
}
|
|
97
84
|
});
|
|
98
|
-
server.tool("get_airbnb_reviews", `Get Airbnb review
|
|
99
|
-
|
|
100
|
-
It does NOT carry the rating breakdown or the review tags — those are on get_airbnb_listing. Use this tool when you need what guests actually wrote.
|
|
101
|
-
|
|
102
|
-
Paginate with limit and offset: count is the listing's TOTAL review count, returned is how many this response holds, so page while offset + returned is below count. limit accepts 1-50 and defaults to 30 upstream. Send limit explicitly when paging — with no explicit limit the upstream returns a fixed 7 rows.
|
|
103
|
-
|
|
104
|
-
Accepts a listing id or a full /rooms/ URL. Costs 1 credit per page.`, {
|
|
85
|
+
server.tool("get_airbnb_reviews", `Get Airbnb review text for a listing. Rating breakdown is on get_airbnb_listing, not here. Paginate with limit+offset. Send limit explicitly or upstream returns only 7. 1 credit/page.`, {
|
|
105
86
|
listing_id: listingIdField(),
|
|
106
87
|
currency: currencyField(),
|
|
107
88
|
limit: z.number().int().min(1).max(50).optional()
|
|
108
|
-
.describe("
|
|
89
|
+
.describe("Reviews per page, 1-50. Default 30, but returns 7 if omitted."),
|
|
109
90
|
offset: z.number().int().min(0).optional()
|
|
110
|
-
.describe("
|
|
91
|
+
.describe("Skip rows, 0-based."),
|
|
111
92
|
}, async (params) => {
|
|
112
93
|
try {
|
|
113
94
|
const data = await getClient().post("/api/v1/airbnb/reviews", params);
|
|
114
|
-
return
|
|
95
|
+
return trimResponse(data);
|
|
115
96
|
}
|
|
116
97
|
catch (err) {
|
|
117
98
|
return handleApiError(err);
|
package/dist/tools/airbnb.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"airbnb.js","sourceRoot":"","sources":["../../src/tools/airbnb.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"airbnb.js","sourceRoot":"","sources":["../../src/tools/airbnb.ts"],"names":[],"mappings":"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"}
|
package/dist/tools/amazon.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { handleApiError } from "../lib/tool-error.js";
|
|
3
|
+
import { trimResponse } from "../lib/trim-response.js";
|
|
3
4
|
// Amazon moved to a new upstream provider and now returns a normalized shape
|
|
4
5
|
// instead of the old raw passthrough. Two consequences for these tool schemas:
|
|
5
6
|
//
|
|
@@ -23,7 +24,7 @@ import { handleApiError } from "../lib/tool-error.js";
|
|
|
23
24
|
// Kept in one place so search, product and offers cannot drift. `gb` is spelled
|
|
24
25
|
// out because `uk` is the mistake a model makes here, and an unknown code
|
|
25
26
|
// silently falls back to `us` rather than erroring, which would be invisible.
|
|
26
|
-
const COUNTRY_DESCRIPTION = "
|
|
27
|
+
const COUNTRY_DESCRIPTION = "Country, e.g. 'us'. UK is 'gb'. Unknown codes silently fall back to us.";
|
|
27
28
|
const countryField = z
|
|
28
29
|
.string()
|
|
29
30
|
.regex(/^[A-Za-z]{2}$/)
|
|
@@ -32,44 +33,36 @@ const countryField = z
|
|
|
32
33
|
const asinField = z
|
|
33
34
|
.string()
|
|
34
35
|
.length(10)
|
|
35
|
-
.describe("
|
|
36
|
+
.describe("ASIN, e.g. 'B09V3KXJPB'.");
|
|
36
37
|
export function registerAmazonTools(server, getClient) {
|
|
37
38
|
const call = (path) => async (params) => {
|
|
38
39
|
try {
|
|
39
40
|
const data = await getClient().post(path, params);
|
|
40
|
-
return
|
|
41
|
+
return trimResponse(data);
|
|
41
42
|
}
|
|
42
43
|
catch (err) {
|
|
43
44
|
return handleApiError(err);
|
|
44
45
|
}
|
|
45
46
|
};
|
|
46
|
-
server.tool("search_amazon", `Search
|
|
47
|
-
|
|
48
|
-
There is NO sort option. The marketplace ignores every sort value and always returns its default relevance ranking, so results are unordered with respect to price, rating and date. To answer "the cheapest" or "the best rated", fetch the results and sort them yourself; do not expect the API to do it. There is also no category, merchant or price-range filter - filters[] carries the marketplace's own refinement URLs for reference only, and cannot be sent back as a param.
|
|
49
|
-
|
|
50
|
-
Two fields to read carefully: reviews_count is derived from Amazon's rounded display value, so anything above 1000 is approximate (a page showing "1.3K" returns 1300); position is Amazon's grid slot index including ad and carousel slots, so it starts above 1 and has gaps. Costs 1 credit per page.`, {
|
|
47
|
+
server.tool("search_amazon", `Search Amazon products. No sort option (always relevance). No category/price filter. 1 credit/page.`, {
|
|
51
48
|
query: z.string().min(1).max(500)
|
|
52
|
-
.describe("Product
|
|
49
|
+
.describe("Product query."),
|
|
53
50
|
country: countryField,
|
|
54
51
|
page: z.number().int().min(1).optional()
|
|
55
|
-
.describe("
|
|
52
|
+
.describe("Page, 1-based."),
|
|
56
53
|
}, call("/api/v1/amazon/search"));
|
|
57
|
-
server.tool("get_amazon_product", `
|
|
58
|
-
|
|
59
|
-
Caveats worth knowing: price is the current buy-box price and other_sellers_count only counts the rest - call get_amazon_offers for the actual competing sellers and their prices. reviews[] carries review metadata only (id, author, date, verified_purchase); there is no review text and no per-review rating anywhere in the response. availability is marketplace- and language-specific free text, so match on the ASIN's own wording rather than parsing it for stock status. Costs 1 credit.`, {
|
|
54
|
+
server.tool("get_amazon_product", `Full Amazon product page for an ASIN. Price is buy-box price; use get_amazon_offers for competing sellers. 1 credit.`, {
|
|
60
55
|
asin: asinField,
|
|
61
56
|
country: countryField,
|
|
62
57
|
}, call("/api/v1/amazon/product"));
|
|
63
|
-
server.tool("get_amazon_offers", `
|
|
64
|
-
|
|
65
|
-
This returns the first page of the offer list only - has_more_pages may be true, and there is no way to request the next page. An ASIN sold only by Amazon returns an empty offers list plus an explanatory note, which is a normal answer, not an error. Costs 1 credit.`, {
|
|
58
|
+
server.tool("get_amazon_offers", `All sellers for an Amazon ASIN with prices and conditions. First page only, no pagination. 1 credit.`, {
|
|
66
59
|
asin: asinField,
|
|
67
60
|
country: countryField,
|
|
68
61
|
}, call("/api/v1/amazon/offers"));
|
|
69
|
-
server.tool("get_amazon_options", `List
|
|
62
|
+
server.tool("get_amazon_options", `List supported Amazon marketplaces and country codes. Free, no args.`, {}, async () => {
|
|
70
63
|
try {
|
|
71
64
|
const data = await getClient().get("/api/v1/amazon/options");
|
|
72
|
-
return
|
|
65
|
+
return trimResponse(data);
|
|
73
66
|
}
|
|
74
67
|
catch (err) {
|
|
75
68
|
return handleApiError(err);
|
package/dist/tools/amazon.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"amazon.js","sourceRoot":"","sources":["../../src/tools/amazon.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"amazon.js","sourceRoot":"","sources":["../../src/tools/amazon.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAEvD,6EAA6E;AAC7E,+EAA+E;AAC/E,EAAE;AACF,gFAAgF;AAChF,2EAA2E;AAC3E,iFAAiF;AACjF,8EAA8E;AAC9E,6EAA6E;AAC7E,6EAA6E;AAC7E,gFAAgF;AAChF,gFAAgF;AAChF,+EAA+E;AAC/E,iCAAiC;AACjC,EAAE;AACF,+EAA+E;AAC/E,2EAA2E;AAC3E,EAAE;AACF,gFAAgF;AAChF,+EAA+E;AAC/E,iBAAiB;AAEjB,gFAAgF;AAChF,0EAA0E;AAC1E,8EAA8E;AAC9E,MAAM,mBAAmB,GACvB,yEAAyE,CAAC;AAE5E,MAAM,YAAY,GAAG,CAAC;KACnB,MAAM,EAAE;KACR,KAAK,CAAC,eAAe,CAAC;KACtB,QAAQ,EAAE;KACV,QAAQ,CAAC,mBAAmB,CAAC,CAAC;AAEjC,MAAM,SAAS,GAAG,CAAC;KAChB,MAAM,EAAE;KACR,MAAM,CAAC,EAAE,CAAC;KACV,QAAQ,CAAC,0BAA0B,CAAC,CAAC;AAExC,MAAM,UAAU,mBAAmB,CAAC,MAAiB,EAAE,SAA6B;IAClF,MAAM,IAAI,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,KAAK,EAAE,MAA+B,EAAE,EAAE;QACvE,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,SAAS,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;YAClD,OAAO,YAAY,CAAC,IAAI,CAAC,CAAC;QAC5B,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,cAAc,CAAC,GAAG,CAAC,CAAC;QAC7B,CAAC;IACH,CAAC,CAAC;IAEF,MAAM,CAAC,IAAI,CACT,eAAe,EACf,qGAAqG,EACrG;QACE,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC;aAC9B,QAAQ,CAAC,gBAAgB,CAAC;QAC7B,OAAO,EAAE,YAAY;QACrB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE;aACrC,QAAQ,CAAC,gBAAgB,CAAC;KAC9B,EACD,IAAI,CAAC,uBAAuB,CAAC,CAC9B,CAAC;IAEF,MAAM,CAAC,IAAI,CACT,oBAAoB,EACpB,sHAAsH,EACtH;QACE,IAAI,EAAE,SAAS;QACf,OAAO,EAAE,YAAY;KACtB,EACD,IAAI,CAAC,wBAAwB,CAAC,CAC/B,CAAC;IAEF,MAAM,CAAC,IAAI,CACT,mBAAmB,EACnB,sGAAsG,EACtG;QACE,IAAI,EAAE,SAAS;QACf,OAAO,EAAE,YAAY;KACtB,EACD,IAAI,CAAC,uBAAuB,CAAC,CAC9B,CAAC;IAEF,MAAM,CAAC,IAAI,CACT,oBAAoB,EACpB,sEAAsE,EACtE,EAAE,EACF,KAAK,IAAI,EAAE;QACT,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,SAAS,EAAE,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC;YAC7D,OAAO,YAAY,CAAC,IAAI,CAAC,CAAC;QAC5B,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,cAAc,CAAC,GAAG,CAAC,CAAC;QAC7B,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
|
package/dist/tools/appstore.js
CHANGED
|
@@ -1,55 +1,56 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { handleApiError } from "../lib/tool-error.js";
|
|
3
|
+
import { trimResponse } from "../lib/trim-response.js";
|
|
3
4
|
// Two-letter ISO only. The transport falls back to the US storefront for
|
|
4
5
|
// anything that is not two letters, so 'usa' would silently buy a US result set
|
|
5
6
|
// on a billed call — rejecting it here costs nothing.
|
|
6
7
|
const countryField = z.string().regex(/^[A-Za-z]{2}$/, "country must be a two-letter ISO country code, e.g. us").optional()
|
|
7
|
-
.describe("
|
|
8
|
+
.describe("Two-letter country code, e.g. 'us', 'gb'. Decides price/currency/availability.");
|
|
8
9
|
export function registerAppStoreTools(server, getClient) {
|
|
9
|
-
server.tool("search_app_store", `Search Apple
|
|
10
|
+
server.tool("search_app_store", `Search Apple App Store. NO PAGINATION: use limit (1-200) only. Searching a developer name returns their catalogue. 1 credit.`, {
|
|
10
11
|
term: z.string().min(1).max(500)
|
|
11
|
-
.describe("
|
|
12
|
+
.describe("App name, keyword, or publisher name."),
|
|
12
13
|
limit: z.number().int().min(1).max(200).optional()
|
|
13
|
-
.describe("
|
|
14
|
+
.describe("Results 1-200. No pagination, this is the only volume control. Default 25."),
|
|
14
15
|
country: countryField,
|
|
15
16
|
entity: z.enum(["software", "ipad_software", "mac_software"]).optional()
|
|
16
|
-
.describe("
|
|
17
|
+
.describe("Catalogue: 'software' (default), 'ipad_software', 'mac_software'."),
|
|
17
18
|
lang: z.string().regex(/^[A-Za-z]{2}_[A-Za-z]{2}$/, "lang must be a five-letter code, e.g. en_us or ja_jp").optional()
|
|
18
|
-
.describe("
|
|
19
|
+
.describe("Listing text language, e.g. 'en_us', 'ja_jp'."),
|
|
19
20
|
}, async (params) => {
|
|
20
21
|
try {
|
|
21
22
|
const data = await getClient().post("/api/v1/appstore/search", params);
|
|
22
|
-
return
|
|
23
|
+
return trimResponse(data);
|
|
23
24
|
}
|
|
24
25
|
catch (err) {
|
|
25
26
|
return handleApiError(err);
|
|
26
27
|
}
|
|
27
28
|
});
|
|
28
|
-
server.tool("get_app_store_app", `Get
|
|
29
|
+
server.tool("get_app_store_app", `Get full App Store listing. Accepts numeric id or bundle id (e.g. 'notion.id'). Full URL rejected (extract the id). Unknown id = billed 404. 1 credit.`, {
|
|
29
30
|
app_id: z.string().min(1).max(255).regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/, "app_id must be a numeric App Store id (e.g. 1232780281) or a bundle id (e.g. notion.id)")
|
|
30
|
-
.describe("App Store id
|
|
31
|
+
.describe("Numeric App Store id or bundle id, e.g. '1232780281' or 'notion.id'."),
|
|
31
32
|
country: countryField,
|
|
32
33
|
}, async (params) => {
|
|
33
34
|
try {
|
|
34
35
|
const data = await getClient().post("/api/v1/appstore/app", params);
|
|
35
|
-
return
|
|
36
|
+
return trimResponse(data);
|
|
36
37
|
}
|
|
37
38
|
catch (err) {
|
|
38
39
|
return handleApiError(err);
|
|
39
40
|
}
|
|
40
41
|
});
|
|
41
|
-
server.tool("get_app_store_reviews", `Get
|
|
42
|
+
server.tool("get_app_store_reviews", `Get App Store reviews. NUMERIC ID ONLY (no bundle id). Max page 10, 50 reviews/page (500 max per storefront). Empty result doesn't mean app missing. 1 credit/page.`, {
|
|
42
43
|
app_id: z.string().regex(/^\d+$/, "app_id must be a numeric App Store id")
|
|
43
|
-
.describe("App Store id, e.g. '1232780281'.
|
|
44
|
+
.describe("Numeric App Store id only, e.g. '1232780281'."),
|
|
44
45
|
country: countryField,
|
|
45
46
|
page: z.number().int().min(1).max(10).optional()
|
|
46
|
-
.describe("
|
|
47
|
+
.describe("Page 1-10, 50 reviews each."),
|
|
47
48
|
sort: z.enum(["most_recent", "most_helpful"]).optional()
|
|
48
|
-
.describe("
|
|
49
|
+
.describe("Sort order. 'most_recent' vote fields are zeroes; 'most_helpful' has real votes."),
|
|
49
50
|
}, async (params) => {
|
|
50
51
|
try {
|
|
51
52
|
const data = await getClient().post("/api/v1/appstore/reviews", params);
|
|
52
|
-
return
|
|
53
|
+
return trimResponse(data);
|
|
53
54
|
}
|
|
54
55
|
catch (err) {
|
|
55
56
|
return handleApiError(err);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"appstore.js","sourceRoot":"","sources":["../../src/tools/appstore.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"appstore.js","sourceRoot":"","sources":["../../src/tools/appstore.ts"],"names":[],"mappings":"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"}
|
package/dist/tools/booking.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { handleApiError } from "../lib/tool-error.js";
|
|
3
|
+
import { trimResponse } from "../lib/trim-response.js";
|
|
3
4
|
// No zod `.default()` anywhere in this file, on purpose. The MCP SDK applies a
|
|
4
5
|
// zod default BEFORE the handler runs, so a defaulted field is posted on every
|
|
5
6
|
// single call whether the model set it or not — which is exactly how the
|
|
@@ -11,102 +12,84 @@ const ISO_DATE = /^\d{4}-\d{2}-\d{2}$/;
|
|
|
11
12
|
// occupancy block. Built per-tool so no two tool schemas share an instance.
|
|
12
13
|
const stayFields = () => ({
|
|
13
14
|
checkin: z.string().regex(ISO_DATE, "checkin must be YYYY-MM-DD").optional()
|
|
14
|
-
.describe("
|
|
15
|
+
.describe("YYYY-MM-DD. MUST send with checkout."),
|
|
15
16
|
checkout: z.string().regex(ISO_DATE, "checkout must be YYYY-MM-DD").optional()
|
|
16
|
-
.describe("
|
|
17
|
+
.describe("YYYY-MM-DD. MUST send with checkin."),
|
|
17
18
|
adults: z.number().int().min(1).optional()
|
|
18
|
-
.describe("
|
|
19
|
+
.describe("Default 2."),
|
|
19
20
|
children_ages: z.array(z.number().int().min(0).max(17)).max(10).optional()
|
|
20
|
-
.describe("One AGE
|
|
21
|
+
.describe("One AGE per child, not a count."),
|
|
21
22
|
rooms: z.number().int().min(1).optional()
|
|
22
|
-
.describe("
|
|
23
|
+
.describe("Default 1."),
|
|
23
24
|
currency: z.string().regex(/^[A-Za-z]{3}$/, "currency must be a 3-letter ISO 4217 code").optional()
|
|
24
|
-
.describe("
|
|
25
|
+
.describe("e.g. 'USD'. Default USD."),
|
|
25
26
|
});
|
|
26
27
|
const hotelField = () => z.string().min(1).max(500)
|
|
27
|
-
.describe("
|
|
28
|
+
.describe("Booking.com URL or slug. Prefer URL from search_booking; wrong slug+country is a BILLED 404.");
|
|
28
29
|
const countryCodeField = () => z.string().regex(/^[A-Za-z]{2}$/, "country_code must be a two-letter country code").optional()
|
|
29
|
-
.describe("
|
|
30
|
+
.describe("e.g. 'us'. Only for bare slugs. Wrong value is a billed 404.");
|
|
30
31
|
export function registerBookingTools(server, getClient) {
|
|
31
|
-
server.tool("search_booking", `Search Booking.com
|
|
32
|
-
|
|
33
|
-
Either destination or dest_id is required. A search with NEITHER returns Booking's homepage — it still costs a credit and returns nothing. dest_type is silently ignored by Booking without dest_id, so it is rejected here rather than billed.
|
|
34
|
-
|
|
35
|
-
checkin and checkout must be sent TOGETHER. Send only one and Booking prices a default range of its own, returning real prices for dates you never asked for. currency defaults to USD; without it Booking prices off the proxy exit and two identical requests can disagree.
|
|
36
|
-
|
|
37
|
-
Chain the url on a result row into get_booking_hotel or get_booking_reviews — that is always cheaper than guessing a slug plus country_code, which is a real, BILLED 404 when wrong.
|
|
38
|
-
|
|
39
|
-
Paginate with page: 25 properties per page. min_review_score is an ENUM of '6', '7', '8' or '9' — Booking silently drops any other threshold. Costs 1 credit per page.`, {
|
|
32
|
+
server.tool("search_booking", `Search Booking.com stays. Requires destination or dest_id. checkin+checkout MUST be sent together. 25/page. 1 credit/page.`, {
|
|
40
33
|
destination: z.string().min(1).max(200).optional()
|
|
41
|
-
.describe("
|
|
34
|
+
.describe("e.g. 'Austin, Texas'. Required unless dest_id."),
|
|
42
35
|
dest_id: z.string().regex(/^\d+$/, "dest_id must be numeric").optional()
|
|
43
|
-
.describe("
|
|
36
|
+
.describe("Numeric destination id. Required unless destination."),
|
|
44
37
|
dest_type: z.enum(["city", "region", "country", "district", "landmark", "airport", "hotel"]).optional()
|
|
45
|
-
.describe("
|
|
38
|
+
.describe("Only with dest_id."),
|
|
46
39
|
page: z.number().int().min(1).optional()
|
|
47
|
-
.describe("
|
|
40
|
+
.describe("Page, 1-based."),
|
|
48
41
|
sort_by: z.enum(["popularity", "price_low", "price_high", "stars_high", "stars_low", "stars_and_price", "distance", "review_score"]).optional()
|
|
49
|
-
.describe("
|
|
42
|
+
.describe("Sort order."),
|
|
50
43
|
min_price: z.number().min(0).optional()
|
|
51
|
-
.describe("
|
|
44
|
+
.describe("Min price/night."),
|
|
52
45
|
max_price: z.number().min(0).optional()
|
|
53
|
-
.describe("
|
|
46
|
+
.describe("Max price/night."),
|
|
54
47
|
stars: z.array(z.number().int().min(1).max(5)).min(1).max(5).optional()
|
|
55
|
-
.describe("
|
|
48
|
+
.describe("e.g. [4, 5]."),
|
|
56
49
|
min_review_score: z.enum(["6", "7", "8", "9"]).optional()
|
|
57
|
-
.describe("
|
|
50
|
+
.describe("Min review score."),
|
|
58
51
|
property_type: z.union([
|
|
59
52
|
z.enum(["apartments", "hostels", "hotels", "motels", "resorts", "bed_and_breakfasts", "villas", "campgrounds", "vacation_homes", "lodges", "homestays"]),
|
|
60
53
|
z.number().int().min(1),
|
|
61
54
|
]).optional()
|
|
62
|
-
.describe("
|
|
55
|
+
.describe("Type name or numeric id."),
|
|
63
56
|
free_cancellation: z.boolean().optional()
|
|
64
|
-
.describe("
|
|
57
|
+
.describe("Free cancellation only."),
|
|
65
58
|
no_prepayment: z.boolean().optional()
|
|
66
|
-
.describe("
|
|
59
|
+
.describe("No prepayment only."),
|
|
67
60
|
breakfast_included: z.boolean().optional()
|
|
68
|
-
.describe("
|
|
61
|
+
.describe("Breakfast included only."),
|
|
69
62
|
...stayFields(),
|
|
70
63
|
}, async (params) => {
|
|
71
64
|
try {
|
|
72
65
|
const data = await getClient().post("/api/v1/booking/search", params);
|
|
73
|
-
return
|
|
66
|
+
return trimResponse(data);
|
|
74
67
|
}
|
|
75
68
|
catch (err) {
|
|
76
69
|
return handleApiError(err);
|
|
77
70
|
}
|
|
78
71
|
});
|
|
79
|
-
server.tool("get_booking_hotel", `
|
|
80
|
-
|
|
81
|
-
It takes dates because Booking prices a STAY, not a property. Omit them and you do not get a property without prices — you get prices for a two-night range Booking chose. The response echoes whichever dates were used, so a price is never for an unknown stay. checkin and checkout must be sent together.
|
|
82
|
-
|
|
83
|
-
Accepts a booking.com property URL or the bare page slug. country_code is only consulted for a bare slug and defaults to 'us'; a wrong country on a bare slug is a real, BILLED 404, so chaining the url a search_booking row returns is always cheaper.
|
|
84
|
-
|
|
85
|
-
Single-property lookup with NO pagination. Costs 1 credit.`, {
|
|
72
|
+
server.tool("get_booking_hotel", `Full Booking.com property: rooms, rates, facilities, policies. Send checkin+checkout together. 1 credit.`, {
|
|
86
73
|
hotel: hotelField(),
|
|
87
74
|
country_code: countryCodeField(),
|
|
88
75
|
...stayFields(),
|
|
89
76
|
}, async (params) => {
|
|
90
77
|
try {
|
|
91
78
|
const data = await getClient().post("/api/v1/booking/hotel", params);
|
|
92
|
-
return
|
|
79
|
+
return trimResponse(data);
|
|
93
80
|
}
|
|
94
81
|
catch (err) {
|
|
95
82
|
return handleApiError(err);
|
|
96
83
|
}
|
|
97
84
|
});
|
|
98
|
-
server.tool("get_booking_reviews", `
|
|
99
|
-
|
|
100
|
-
There is NO PAGE PARAMETER — do not try to paginate this and do not invent one. One call returns one response: total_count is the property's WHOLE review history, count is how many reviews this response actually holds.
|
|
101
|
-
|
|
102
|
-
Accepts a booking.com property URL or the bare page slug, exactly like get_booking_hotel; country_code is only consulted for a bare slug and a wrong one is a real, BILLED 404. The stay parameters are accepted because Booking serves the review page inside a priced stay context. Costs 1 credit.`, {
|
|
85
|
+
server.tool("get_booking_reviews", `Booking.com guest reviews with score breakdown. No pagination. 1 credit.`, {
|
|
103
86
|
hotel: hotelField(),
|
|
104
87
|
country_code: countryCodeField(),
|
|
105
88
|
...stayFields(),
|
|
106
89
|
}, async (params) => {
|
|
107
90
|
try {
|
|
108
91
|
const data = await getClient().post("/api/v1/booking/reviews", params);
|
|
109
|
-
return
|
|
92
|
+
return trimResponse(data);
|
|
110
93
|
}
|
|
111
94
|
catch (err) {
|
|
112
95
|
return handleApiError(err);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"booking.js","sourceRoot":"","sources":["../../src/tools/booking.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"booking.js","sourceRoot":"","sources":["../../src/tools/booking.ts"],"names":[],"mappings":"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"}
|