@chrischall/tripadvisor-mcp 0.4.0 → 0.5.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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/dist/bundle.js +623 -181
- package/dist/tools/location.js +27 -14
- package/dist/tools/search.js +12 -17
- package/dist/tools/web.js +10 -2
- package/dist/version.js +1 -1
- package/dist/view.js +45 -0
- package/package.json +2 -2
- package/server.json +2 -2
package/dist/tools/location.js
CHANGED
|
@@ -1,23 +1,20 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import {
|
|
2
|
+
import { minifiedResult } from '@chrischall/mcp-utils';
|
|
3
3
|
import { client } from '../client.js';
|
|
4
4
|
import { LocationId, LocaleList, pageParams, qs } from './shared.js';
|
|
5
|
-
import {
|
|
5
|
+
import { viewArg, viewResponse } from '../view.js';
|
|
6
6
|
export function registerLocationTools(server) {
|
|
7
7
|
server.registerTool('ta_get_locations', {
|
|
8
|
-
description: 'Get details for MULTIPLE locations in one call (batch). Pass an array of location ids — cheaper than repeated ta_get_location_details. Unknown or unlicensed ids are silently omitted.
|
|
8
|
+
description: 'Get details for MULTIPLE locations in one call (batch). Pass an array of location ids — cheaper than repeated ta_get_location_details. Unknown or unlicensed ids are silently omitted. Returns slim summaries by default; pass view:"full" for the whole records.',
|
|
9
9
|
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
10
10
|
inputSchema: {
|
|
11
11
|
ids: z.array(LocationId).min(1).max(50).describe('Location IDs to fetch (1–50)'),
|
|
12
12
|
locale: LocaleList,
|
|
13
|
-
|
|
14
|
-
.boolean()
|
|
15
|
-
.optional()
|
|
16
|
-
.describe('Return a slim summary per location instead of full records'),
|
|
13
|
+
view: viewArg(),
|
|
17
14
|
},
|
|
18
|
-
}, async ({ ids, locale,
|
|
15
|
+
}, async ({ ids, locale, view }) => {
|
|
19
16
|
const data = await client.get(`/locations${qs({ id: ids, locale })}`, { cache: 'static' });
|
|
20
|
-
return
|
|
17
|
+
return viewResponse(view, data, 'locationList');
|
|
21
18
|
});
|
|
22
19
|
server.registerTool('ta_get_location_details', {
|
|
23
20
|
description: 'Get full details for a TripAdvisor location: names, descriptions, address, coordinates, traveler ratings, phone, category, and listing URLs.',
|
|
@@ -28,8 +25,16 @@ export function registerLocationTools(server) {
|
|
|
28
25
|
},
|
|
29
26
|
}, async ({ locationId, locale }) => {
|
|
30
27
|
const data = await client.get(`/locations/${locationId}${qs({ locale })}`, { cache: 'static' });
|
|
31
|
-
return
|
|
28
|
+
return minifiedResult(data);
|
|
32
29
|
});
|
|
30
|
+
// NO `view` on this tool, deliberately. Its product IS the image URLs: a
|
|
31
|
+
// photos item is `{id, location_id, photo: {key, original_size_url, …}, …}`
|
|
32
|
+
// (docs/TRIPADVISOR-API.md §5), and `photo` is a media KEY — stripping it
|
|
33
|
+
// does not shrink the response, it EMPTIES it, leaving ids and a publish
|
|
34
|
+
// timestamp pointing at nothing. Same rule as `musicbrainz_cover_art`,
|
|
35
|
+
// `alltrails_get_trail_photos`, `sw_get_receipt` and redfin's photo bundles
|
|
36
|
+
// — see @chrischall/mcp-utils' `stripMediaUrls` docs ("Never apply this to a
|
|
37
|
+
// tool whose PRODUCT is the image. The tool's own name is the test.").
|
|
33
38
|
server.registerTool('ta_get_location_photos', {
|
|
34
39
|
description: 'Get photos for a TripAdvisor location (multi-size image URLs, source, dimensions), with pagination.',
|
|
35
40
|
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
@@ -42,20 +47,28 @@ export function registerLocationTools(server) {
|
|
|
42
47
|
const data = await client.get(`/locations/${locationId}/photos${qs({ locale, page, size })}`, {
|
|
43
48
|
cache: 'static',
|
|
44
49
|
});
|
|
45
|
-
return
|
|
50
|
+
return minifiedResult(data);
|
|
46
51
|
});
|
|
52
|
+
// `view` DOES belong here, and it is the opposite case to photos above. A
|
|
53
|
+
// review's product is its TEXT; the image URLs it carries — reviewer avatars,
|
|
54
|
+
// per-review snapshots — are incidental to the thing the caller asked for, so
|
|
55
|
+
// dropping them shrinks the payload instead of emptying it. There is no
|
|
56
|
+
// hand-written projection for this shape, so `viewResponse` falls through to
|
|
57
|
+
// `stripMediaUrls`, which needs no knowledge of the fields.
|
|
47
58
|
server.registerTool('ta_get_location_reviews', {
|
|
48
|
-
description: 'Get traveler reviews for a TripAdvisor location, with pagination.'
|
|
59
|
+
description: 'Get traveler reviews for a TripAdvisor location, with pagination. Reviewer avatars and other image URLs ' +
|
|
60
|
+
'are dropped by default; pass view:"full" for TripAdvisor\'s whole records.',
|
|
49
61
|
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
50
62
|
inputSchema: {
|
|
51
63
|
locationId: LocationId,
|
|
52
64
|
locale: LocaleList,
|
|
53
65
|
...pageParams,
|
|
66
|
+
view: viewArg(),
|
|
54
67
|
},
|
|
55
|
-
}, async ({ locationId, locale, page, size }) => {
|
|
68
|
+
}, async ({ locationId, locale, page, size, view }) => {
|
|
56
69
|
const data = await client.get(`/locations/${locationId}/reviews${qs({ locale, page, size })}`, {
|
|
57
70
|
cache: 'static',
|
|
58
71
|
});
|
|
59
|
-
return
|
|
72
|
+
return viewResponse(view, data);
|
|
60
73
|
});
|
|
61
74
|
}
|
package/dist/tools/search.js
CHANGED
|
@@ -1,18 +1,13 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import {
|
|
2
|
+
import { McpToolError } from '@chrischall/mcp-utils';
|
|
3
3
|
import { client } from '../client.js';
|
|
4
4
|
import { Category, LocaleList, pageParams, qs } from './shared.js';
|
|
5
|
-
import {
|
|
6
|
-
/** `
|
|
7
|
-
const
|
|
8
|
-
compact: z
|
|
9
|
-
.boolean()
|
|
10
|
-
.optional()
|
|
11
|
-
.describe('Return a slim summary per result (id, name, category, city, rating, review_count, url) instead of full records'),
|
|
12
|
-
};
|
|
5
|
+
import { viewArg, viewResponse } from '../view.js';
|
|
6
|
+
/** The `view` arg shared by the list-returning search tools. */
|
|
7
|
+
const viewParamShared = { view: viewArg() };
|
|
13
8
|
export function registerSearchTools(server) {
|
|
14
9
|
server.registerTool('ta_search_locations', {
|
|
15
|
-
description: 'Search TripAdvisor locations (restaurants, attractions, hotels) by name. Returns matches with a location id for the detail tools, plus pagination.
|
|
10
|
+
description: 'Search TripAdvisor locations (restaurants, attractions, hotels) by name. Returns matches with a location id for the detail tools, plus pagination. Returns slim summaries by default; pass view:"full" for the whole records.',
|
|
16
11
|
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
17
12
|
inputSchema: {
|
|
18
13
|
query: z.string().min(1).max(500).describe('Text to search location names for'),
|
|
@@ -22,14 +17,14 @@ export function registerSearchTools(server) {
|
|
|
22
17
|
postal_code: z.string().optional().describe('Postal/ZIP code (takes precedence over geo_name)'),
|
|
23
18
|
locale: LocaleList,
|
|
24
19
|
...pageParams,
|
|
25
|
-
...
|
|
20
|
+
...viewParamShared,
|
|
26
21
|
},
|
|
27
|
-
}, async ({ query, category, country_code, geo_name, postal_code, locale, page, size,
|
|
22
|
+
}, async ({ query, category, country_code, geo_name, postal_code, locale, page, size, view }) => {
|
|
28
23
|
const data = await client.get(`/locations/search${qs({ query, category, country_code, geo_name, postal_code, locale, page, size })}`, { cache: 'dynamic' });
|
|
29
|
-
return
|
|
24
|
+
return viewResponse(view, data, 'list');
|
|
30
25
|
});
|
|
31
26
|
server.registerTool('ta_search_nearby', {
|
|
32
|
-
description: 'Find TripAdvisor locations near a point within a radius, or inside a bounding box. Center by lat+lon+radius, by a reference location_id+radius, or by a sw/ne bounding box. Returns matches with distance and a location id.
|
|
27
|
+
description: 'Find TripAdvisor locations near a point within a radius, or inside a bounding box. Center by lat+lon+radius, by a reference location_id+radius, or by a sw/ne bounding box. Returns matches with distance and a location id. Returns slim summaries by default; pass view:"full" for the whole records.',
|
|
33
28
|
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
34
29
|
inputSchema: {
|
|
35
30
|
// Center — supply exactly one of: lat+lon, location_id, or the sw/ne box.
|
|
@@ -48,10 +43,10 @@ export function registerSearchTools(server) {
|
|
|
48
43
|
sort: z.enum(['distance', 'rating']).optional().describe('Sort order (default distance)'),
|
|
49
44
|
locale: LocaleList,
|
|
50
45
|
...pageParams,
|
|
51
|
-
...
|
|
46
|
+
...viewParamShared,
|
|
52
47
|
},
|
|
53
48
|
}, async (args) => {
|
|
54
|
-
const { lat, lon, location_id, radius, sw_lat, sw_lon, ne_lat, ne_lon,
|
|
49
|
+
const { lat, lon, location_id, radius, sw_lat, sw_lon, ne_lat, ne_lon, view, ...rest } = args;
|
|
55
50
|
const boxParts = [sw_lat, sw_lon, ne_lat, ne_lon];
|
|
56
51
|
const boxGiven = boxParts.filter((v) => v !== undefined).length;
|
|
57
52
|
// A partial box (1–3 of 4) is never valid: it can't form a center on its
|
|
@@ -81,6 +76,6 @@ export function registerSearchTools(server) {
|
|
|
81
76
|
});
|
|
82
77
|
}
|
|
83
78
|
const data = await client.get(`/locations/nearby${qs({ lat, lon, location_id, radius, sw_lat, sw_lon, ne_lat, ne_lon, ...rest })}`, { cache: 'dynamic' });
|
|
84
|
-
return
|
|
79
|
+
return viewResponse(view, data, 'list');
|
|
85
80
|
});
|
|
86
81
|
}
|
package/dist/tools/web.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { registerBridgeHealthcheckTool } from '@chrischall/mcp-utils/fetchproxy';
|
|
2
|
-
import { McpToolError,
|
|
2
|
+
import { McpToolError, minifiedResult } from '@chrischall/mcp-utils';
|
|
3
3
|
import { webClient } from '../web/client.js';
|
|
4
4
|
import { parseLocationDetail } from '../web/parse.js';
|
|
5
5
|
import { LocationId } from './shared.js';
|
|
@@ -28,6 +28,14 @@ export function registerWebTools(server) {
|
|
|
28
28
|
},
|
|
29
29
|
probeFn: (path) => webClient.getHtml(path),
|
|
30
30
|
});
|
|
31
|
+
// NO `view` on this tool, deliberately, and for the OPPOSITE reason to
|
|
32
|
+
// `ta_get_location_photos`. It does not return an upstream payload at all:
|
|
33
|
+
// `parseLocationDetail` is already a hand-written projection down to a dozen
|
|
34
|
+
// named fields, and one of them is `image` — chosen on purpose, from a page
|
|
35
|
+
// that offers hundreds. Media-stripping a grounded projection lets a blind
|
|
36
|
+
// subtractive rule overrule a rule written with knowledge of the source,
|
|
37
|
+
// which is exactly what deleted viator-mcp's `coverImageUrl`
|
|
38
|
+
// (chrischall/viator-mcp#72). There is no cheaper rung to offer here.
|
|
31
39
|
server.registerTool('ta_web_get_location', {
|
|
32
40
|
description: "Get a TripAdvisor location's core details (name, rating, review count, address, coordinates, phone, photo, listing URL) by location ID, read from the public page via the browser bridge. Works without an API key — use this when ta_get_location_details is unavailable or its key is blocked. Covers attractions, hotels, and restaurants. Does not return individual review text.",
|
|
33
41
|
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
@@ -42,6 +50,6 @@ export function registerWebTools(server) {
|
|
|
42
50
|
hint: 'The page may be a bot-challenge shell or the id may be wrong — run ta_web_healthcheck and confirm a signed-in www.tripadvisor.com tab is open, then retry.',
|
|
43
51
|
});
|
|
44
52
|
}
|
|
45
|
-
return
|
|
53
|
+
return minifiedResult({ location_id: locationId, ...detail });
|
|
46
54
|
});
|
|
47
55
|
}
|
package/dist/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** Single source of the server version. release-please bumps the literal below. */
|
|
2
|
-
export const VERSION = '0.
|
|
2
|
+
export const VERSION = '0.5.0'; // x-release-please-version
|
package/dist/view.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { minifiedResult, resolveView, stripMediaUrls, viewParam } from '@chrischall/mcp-utils';
|
|
2
|
+
import { compactList, compactLocationList } from './projection.js';
|
|
3
|
+
/**
|
|
4
|
+
* The rungs this server honours (`@chrischall/mcp-utils`' `view` vocabulary;
|
|
5
|
+
* `chrischall/workflows` `docs/fleet-conventions.md`, "Response shape").
|
|
6
|
+
*
|
|
7
|
+
* This is a GROUNDED repo: `projection.ts` has carried `compactLocation` /
|
|
8
|
+
* `compactList` / `compactLocationList` all along, and they were opt-in —
|
|
9
|
+
* `compact: false`, with the tool descriptions saying "Pass compact:true for
|
|
10
|
+
* slim summaries". An efficiency that has to be requested is one that usually
|
|
11
|
+
* is not, and the caller paying for it is the one least able to know a slim
|
|
12
|
+
* rung existed.
|
|
13
|
+
*
|
|
14
|
+
* A hand-written projection is NOT then media-stripped. It was written with
|
|
15
|
+
* knowledge of the API and its field choices are deliberate; running a blind
|
|
16
|
+
* subtractive rule over its output would let an un-grounded rule overrule a
|
|
17
|
+
* grounded one, which bit viator-mcp where the projection intentionally keeps
|
|
18
|
+
* a cover image. Media stripping is for the payloads that have no projection
|
|
19
|
+
* to speak for them — here, `ta_get_location_reviews` and nothing else.
|
|
20
|
+
*
|
|
21
|
+
* NOT `ta_get_location_photos`, which is the other half of the same rule and
|
|
22
|
+
* the one that is easy to get wrong: a tool whose PRODUCT is the image URLs is
|
|
23
|
+
* not media-stripped either, because there the rule does not shrink the
|
|
24
|
+
* response, it empties it. So that tool registers no `view` at all — see the
|
|
25
|
+
* comment above its registrar. `viewResponse`'s no-projector branch is
|
|
26
|
+
* therefore reached by exactly one tool; it is a fallback, not dead code.
|
|
27
|
+
*
|
|
28
|
+
* No `raw` rung: `full` already returns the untouched upstream payload.
|
|
29
|
+
*/
|
|
30
|
+
export const TA_VIEWS = ['compact', 'full'];
|
|
31
|
+
const NOTE = 'compact returns the slim projection where one exists and strips image URLs elsewhere; ' +
|
|
32
|
+
'"full" returns TripAdvisor\'s whole records.';
|
|
33
|
+
/** The `view` parameter every read tool in this server takes. */
|
|
34
|
+
export const viewArg = () => viewParam(TA_VIEWS, { note: NOTE });
|
|
35
|
+
/** Answer in the requested rung, running the named projection when there is one. */
|
|
36
|
+
export function viewResponse(view, data, projector) {
|
|
37
|
+
const rung = resolveView(view, TA_VIEWS);
|
|
38
|
+
if (rung !== 'compact')
|
|
39
|
+
return minifiedResult(data);
|
|
40
|
+
if (projector === 'list')
|
|
41
|
+
return minifiedResult(compactList(data));
|
|
42
|
+
if (projector === 'locationList')
|
|
43
|
+
return minifiedResult(compactLocationList(data));
|
|
44
|
+
return minifiedResult(stripMediaUrls(data));
|
|
45
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chrischall/tripadvisor-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"mcpName": "io.github.chrischall/tripadvisor-mcp",
|
|
5
5
|
"description": "TripAdvisor Terra API MCP server for Claude — search locations, details, photos, and reviews. Developed and maintained by AI (Claude Code).",
|
|
6
6
|
"author": "Claude Code (AI) <https://www.anthropic.com/claude>",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@chrischall/mcp-utils": "^0.
|
|
49
|
+
"@chrischall/mcp-utils": "^0.23.0",
|
|
50
50
|
"@fetchproxy/server": "^2.2.0",
|
|
51
51
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
52
52
|
"dotenv": "^17.4.0",
|
package/server.json
CHANGED
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
"url": "https://github.com/chrischall/tripadvisor-mcp",
|
|
7
7
|
"source": "github"
|
|
8
8
|
},
|
|
9
|
-
"version": "0.
|
|
9
|
+
"version": "0.5.0",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "@chrischall/tripadvisor-mcp",
|
|
14
|
-
"version": "0.
|
|
14
|
+
"version": "0.5.0",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
},
|