@chrischall/tripadvisor-mcp 0.2.0 → 0.2.1
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 +136 -51
- package/dist/client.js +9 -48
- package/dist/version.js +1 -1
- package/package.json +2 -2
- package/server.json +2 -2
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
},
|
|
8
8
|
"metadata": {
|
|
9
9
|
"description": "MCP server for the TripAdvisor Terra API — location search, details, photos, and reviews",
|
|
10
|
-
"version": "0.2.
|
|
10
|
+
"version": "0.2.1"
|
|
11
11
|
},
|
|
12
12
|
"plugins": [
|
|
13
13
|
{
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"displayName": "TripAdvisor",
|
|
16
16
|
"source": "./",
|
|
17
17
|
"description": "TripAdvisor travel data via the Terra API — search hotels, restaurants, and attractions, with details, photos, and reviews",
|
|
18
|
-
"version": "0.2.
|
|
18
|
+
"version": "0.2.1",
|
|
19
19
|
"author": {
|
|
20
20
|
"name": "Chris Hall"
|
|
21
21
|
},
|
package/dist/bundle.js
CHANGED
|
@@ -34718,8 +34718,12 @@ var API_KEY_RE = new RegExp([
|
|
|
34718
34718
|
// webhook signing secret (Stripe-style)
|
|
34719
34719
|
].map((p) => `\\b${p}`).join("|"), "g");
|
|
34720
34720
|
var QUERY_SECRET_RE = /([?&](?:access_token|refresh_token|client_secret|api_?key|signature|token|key|sig)=)[^&#\s"'<>`]+/gi;
|
|
34721
|
+
var AWS_SIGV4_RE = /([?&]X-Amz-(?:Signature|Security-Token|Credential)=)[^&#\s"'<>`]+/gi;
|
|
34722
|
+
var JSON_SECRET_KEYS = "access_token|refresh_token|client_secret|api_?key|password|passwd|secret|token";
|
|
34723
|
+
var JSON_SECRET_DQ_RE = new RegExp(`("(?:${JSON_SECRET_KEYS})"\\s*:\\s*")[^"]*(")`, "gi");
|
|
34724
|
+
var JSON_SECRET_SQ_RE = new RegExp(`('(?:${JSON_SECRET_KEYS})'\\s*:\\s*')[^']*(')`, "gi");
|
|
34721
34725
|
function redactSecrets(text) {
|
|
34722
|
-
return text.replace(BEARER_RE, "$1[REDACTED]").replace(BASIC_AUTH_RE, "$1[REDACTED]").replace(SET_COOKIE_RE, "$1$2=[REDACTED]").replace(COOKIE_HEADER_RE, (_m, prefix, pairs) => `${prefix}${pairs.replace(/=[^;,\s]*/g, "=[REDACTED]")}`).replace(API_KEY_RE, "[REDACTED]").replace(QUERY_SECRET_RE, "$1[REDACTED]").replace(JWT_RE, "[REDACTED]");
|
|
34726
|
+
return text.replace(BEARER_RE, "$1[REDACTED]").replace(BASIC_AUTH_RE, "$1[REDACTED]").replace(SET_COOKIE_RE, "$1$2=[REDACTED]").replace(COOKIE_HEADER_RE, (_m, prefix, pairs) => `${prefix}${pairs.replace(/=[^;,\s]*/g, "=[REDACTED]")}`).replace(API_KEY_RE, "[REDACTED]").replace(QUERY_SECRET_RE, "$1[REDACTED]").replace(AWS_SIGV4_RE, "$1[REDACTED]").replace(JSON_SECRET_DQ_RE, "$1[REDACTED]$2").replace(JSON_SECRET_SQ_RE, "$1[REDACTED]$2").replace(JWT_RE, "[REDACTED]");
|
|
34723
34727
|
}
|
|
34724
34728
|
function truncateErrorMessage(text, max = DEFAULT_ERROR_MESSAGE_MAX) {
|
|
34725
34729
|
const str2 = text === null || text === void 0 ? "" : String(text);
|
|
@@ -34795,6 +34799,100 @@ async function loadDotenvSafely(opts = {}) {
|
|
|
34795
34799
|
return false;
|
|
34796
34800
|
}
|
|
34797
34801
|
}
|
|
34802
|
+
function readIntEnv(key, opts = {}) {
|
|
34803
|
+
const raw = readEnvVar(key, opts.env ? { env: opts.env } : {});
|
|
34804
|
+
if (raw === void 0)
|
|
34805
|
+
return opts.default;
|
|
34806
|
+
if (!/^-?\d+$/.test(raw))
|
|
34807
|
+
return opts.default;
|
|
34808
|
+
const n = Number(raw);
|
|
34809
|
+
if (!Number.isSafeInteger(n))
|
|
34810
|
+
return opts.default;
|
|
34811
|
+
const min = opts.min ?? 0;
|
|
34812
|
+
if (n < min)
|
|
34813
|
+
return opts.default;
|
|
34814
|
+
if (opts.max !== void 0 && n > opts.max)
|
|
34815
|
+
return opts.default;
|
|
34816
|
+
return n;
|
|
34817
|
+
}
|
|
34818
|
+
function readTtlMsEnv(key, defaultMs, opts = {}) {
|
|
34819
|
+
const seconds = readIntEnv(key, { ...opts.env ? { env: opts.env } : {}, min: 0 });
|
|
34820
|
+
return seconds === void 0 ? defaultMs : seconds * 1e3;
|
|
34821
|
+
}
|
|
34822
|
+
|
|
34823
|
+
// node_modules/@chrischall/mcp-utils/dist/http/response-cache.js
|
|
34824
|
+
var RESPONSE_CACHE_MAX_ENTRIES = 256;
|
|
34825
|
+
function createResponseCache(opts) {
|
|
34826
|
+
const now = opts.now ?? Date.now;
|
|
34827
|
+
const maxEntries = opts.maxEntries ?? RESPONSE_CACHE_MAX_ENTRIES;
|
|
34828
|
+
const store = /* @__PURE__ */ new Map();
|
|
34829
|
+
const ttlFor = (tier) => opts.ttlMs[tier] ?? 0;
|
|
34830
|
+
function evictForInsert() {
|
|
34831
|
+
if (store.size < maxEntries)
|
|
34832
|
+
return;
|
|
34833
|
+
const t = now();
|
|
34834
|
+
for (const [key, entry] of store) {
|
|
34835
|
+
if (entry.expiresAt <= t)
|
|
34836
|
+
store.delete(key);
|
|
34837
|
+
}
|
|
34838
|
+
while (store.size >= maxEntries) {
|
|
34839
|
+
const oldest = store.keys().next();
|
|
34840
|
+
if (oldest.done)
|
|
34841
|
+
break;
|
|
34842
|
+
store.delete(oldest.value);
|
|
34843
|
+
}
|
|
34844
|
+
}
|
|
34845
|
+
function get(key) {
|
|
34846
|
+
const entry = store.get(key);
|
|
34847
|
+
if (!entry)
|
|
34848
|
+
return void 0;
|
|
34849
|
+
if (entry.expiresAt <= now()) {
|
|
34850
|
+
store.delete(key);
|
|
34851
|
+
return void 0;
|
|
34852
|
+
}
|
|
34853
|
+
return entry.value;
|
|
34854
|
+
}
|
|
34855
|
+
function set2(key, value, tier = "dynamic") {
|
|
34856
|
+
const ttl = ttlFor(tier);
|
|
34857
|
+
if (ttl <= 0)
|
|
34858
|
+
return;
|
|
34859
|
+
if (!store.has(key))
|
|
34860
|
+
evictForInsert();
|
|
34861
|
+
else
|
|
34862
|
+
store.delete(key);
|
|
34863
|
+
store.set(key, { expiresAt: now() + ttl, value });
|
|
34864
|
+
}
|
|
34865
|
+
return {
|
|
34866
|
+
get,
|
|
34867
|
+
set: set2,
|
|
34868
|
+
async fetchThrough(key, load, tier = "dynamic") {
|
|
34869
|
+
const hit = get(key);
|
|
34870
|
+
if (hit !== void 0)
|
|
34871
|
+
return hit;
|
|
34872
|
+
const value = await load();
|
|
34873
|
+
set2(key, value, tier);
|
|
34874
|
+
return value;
|
|
34875
|
+
},
|
|
34876
|
+
clear() {
|
|
34877
|
+
store.clear();
|
|
34878
|
+
},
|
|
34879
|
+
get size() {
|
|
34880
|
+
return store.size;
|
|
34881
|
+
}
|
|
34882
|
+
};
|
|
34883
|
+
}
|
|
34884
|
+
|
|
34885
|
+
// node_modules/@chrischall/mcp-utils/dist/http/net-atoms.js
|
|
34886
|
+
function parseRetryAfterMs(header, opts = {}) {
|
|
34887
|
+
const defaultMs = opts.defaultMs ?? 2e3;
|
|
34888
|
+
const capMs = opts.capMs ?? 3e4;
|
|
34889
|
+
if (header == null)
|
|
34890
|
+
return defaultMs;
|
|
34891
|
+
const trimmed = header.trim();
|
|
34892
|
+
if (!/^\d+$/.test(trimmed))
|
|
34893
|
+
return defaultMs;
|
|
34894
|
+
return Math.min(Number(trimmed) * 1e3, capMs);
|
|
34895
|
+
}
|
|
34798
34896
|
|
|
34799
34897
|
// node_modules/@chrischall/mcp-utils/dist/http/index.js
|
|
34800
34898
|
function buildQueryString(params) {
|
|
@@ -34841,7 +34939,7 @@ var pageSchema = {
|
|
|
34841
34939
|
};
|
|
34842
34940
|
|
|
34843
34941
|
// src/version.ts
|
|
34844
|
-
var VERSION = "0.2.
|
|
34942
|
+
var VERSION = "0.2.1";
|
|
34845
34943
|
|
|
34846
34944
|
// src/client.ts
|
|
34847
34945
|
import { dirname, join } from "node:path";
|
|
@@ -34853,33 +34951,24 @@ var SERVICE = "TripAdvisor Terra API";
|
|
|
34853
34951
|
var REQUEST_TIMEOUT_MS = 3e4;
|
|
34854
34952
|
var DEFAULT_CACHE_TTL_MS = 3e5;
|
|
34855
34953
|
var DEFAULT_STATIC_CACHE_TTL_MS = 36e5;
|
|
34856
|
-
var CACHE_MAX_ENTRIES = 256;
|
|
34857
34954
|
var MAX_RETRY_AFTER_MS = 1e4;
|
|
34858
|
-
function readCacheTtlMs(envVar, defaultMs) {
|
|
34859
|
-
const raw = readEnvVar(envVar);
|
|
34860
|
-
if (raw === void 0) return defaultMs;
|
|
34861
|
-
const secs = Number(raw);
|
|
34862
|
-
return Number.isFinite(secs) && secs >= 0 ? secs * 1e3 : defaultMs;
|
|
34863
|
-
}
|
|
34864
34955
|
var TripAdvisorClient = class {
|
|
34865
34956
|
apiKey;
|
|
34866
34957
|
configError;
|
|
34867
34958
|
fetchImpl;
|
|
34868
34959
|
sleep;
|
|
34869
|
-
|
|
34870
|
-
staticCacheTtlMs;
|
|
34871
|
-
now;
|
|
34872
|
-
cache = /* @__PURE__ */ new Map();
|
|
34960
|
+
cache;
|
|
34873
34961
|
/**
|
|
34874
34962
|
* Defer the config error so the server still boots (and answers the host's
|
|
34875
34963
|
* install-time tools/list probe) when TRIPADVISOR_API_KEY isn't set yet. The
|
|
34876
34964
|
* error is re-raised at request time via requireKey().
|
|
34877
34965
|
*/
|
|
34878
34966
|
constructor(opts = {}) {
|
|
34879
|
-
|
|
34967
|
+
const now = opts.now ?? Date.now;
|
|
34880
34968
|
this.sleep = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
34881
|
-
|
|
34882
|
-
|
|
34969
|
+
const cacheTtlMs = opts.cacheTtlMs ?? readTtlMsEnv("TRIPADVISOR_CACHE_TTL", DEFAULT_CACHE_TTL_MS);
|
|
34970
|
+
const staticCacheTtlMs = opts.staticCacheTtlMs ?? readTtlMsEnv("TRIPADVISOR_STATIC_CACHE_TTL", DEFAULT_STATIC_CACHE_TTL_MS);
|
|
34971
|
+
this.cache = createResponseCache({ ttlMs: { dynamic: cacheTtlMs, static: staticCacheTtlMs }, now });
|
|
34883
34972
|
this.fetchImpl = opts.fetchImpl ?? fetch;
|
|
34884
34973
|
const key = readEnvVar("TRIPADVISOR_API_KEY");
|
|
34885
34974
|
if (!key) {
|
|
@@ -34905,25 +34994,8 @@ var TripAdvisorClient = class {
|
|
|
34905
34994
|
* (TRIPADVISOR_CACHE_TTL) is for searches.
|
|
34906
34995
|
*/
|
|
34907
34996
|
async get(path, opts = {}) {
|
|
34908
|
-
const
|
|
34909
|
-
|
|
34910
|
-
const hit = this.cache.get(path);
|
|
34911
|
-
if (hit && hit.expiresAt > this.now()) return hit.value;
|
|
34912
|
-
}
|
|
34913
|
-
const value = await this.request(path);
|
|
34914
|
-
if (ttl > 0) {
|
|
34915
|
-
if (this.cache.size >= CACHE_MAX_ENTRIES) {
|
|
34916
|
-
const t = this.now();
|
|
34917
|
-
for (const [k, v] of this.cache) if (v.expiresAt <= t) this.cache.delete(k);
|
|
34918
|
-
while (this.cache.size >= CACHE_MAX_ENTRIES) {
|
|
34919
|
-
const oldest = this.cache.keys().next().value;
|
|
34920
|
-
if (oldest === void 0) break;
|
|
34921
|
-
this.cache.delete(oldest);
|
|
34922
|
-
}
|
|
34923
|
-
}
|
|
34924
|
-
this.cache.set(path, { expiresAt: this.now() + ttl, value });
|
|
34925
|
-
}
|
|
34926
|
-
return value;
|
|
34997
|
+
const tier = opts.cache === "static" ? "static" : "dynamic";
|
|
34998
|
+
return this.cache.fetchThrough(path, () => this.request(path), tier);
|
|
34927
34999
|
}
|
|
34928
35000
|
async request(path, isRetry = false) {
|
|
34929
35001
|
const key = this.requireKey();
|
|
@@ -34932,8 +35004,7 @@ var TripAdvisorClient = class {
|
|
|
34932
35004
|
if (res.ok) return await res.json();
|
|
34933
35005
|
const text = await res.text();
|
|
34934
35006
|
if (res.status === 429 && !isRetry) {
|
|
34935
|
-
const
|
|
34936
|
-
const delayMs = Number.isFinite(retryAfter) && retryAfter > 0 ? Math.min(retryAfter * 1e3, MAX_RETRY_AFTER_MS) : 1e3;
|
|
35007
|
+
const delayMs = parseRetryAfterMs(res.headers.get("retry-after"), { defaultMs: 1e3, capMs: MAX_RETRY_AFTER_MS });
|
|
34937
35008
|
await this.sleep(delayMs);
|
|
34938
35009
|
return this.request(path, true);
|
|
34939
35010
|
}
|
|
@@ -38511,7 +38582,7 @@ function healthcheckHint(args) {
|
|
|
38511
38582
|
return `Unexpected error \u2014 see the error.message field for details.`;
|
|
38512
38583
|
}
|
|
38513
38584
|
function registerBridgeHealthcheckTool(args) {
|
|
38514
|
-
const { server, prefix, probePath, hostLabel, transport, probeFn } = args;
|
|
38585
|
+
const { server, prefix, probePath, hostLabel, transport, probeFn, classifyThrown, hints } = args;
|
|
38515
38586
|
const probeUrl = `https://${hostLabel}${probePath}`;
|
|
38516
38587
|
server.registerTool(`${prefix}_healthcheck`, {
|
|
38517
38588
|
title: "Verify the fetchproxy bridge end-to-end",
|
|
@@ -38544,16 +38615,39 @@ function registerBridgeHealthcheckTool(args) {
|
|
|
38544
38615
|
} : { url: probeUrl, elapsed_ms: probeResult.elapsed_ms };
|
|
38545
38616
|
let error51;
|
|
38546
38617
|
let bridgeHint;
|
|
38618
|
+
let customHint;
|
|
38619
|
+
let customDetail;
|
|
38547
38620
|
if (probeResult.error) {
|
|
38548
|
-
|
|
38621
|
+
let kind = probeResult.error.kind === "other" ? "unknown" : probeResult.error.kind;
|
|
38549
38622
|
bridgeHint = thrown instanceof FetchproxyBridgeDownError ? thrown.hint : void 0;
|
|
38623
|
+
if (thrown !== void 0 && classifyThrown) {
|
|
38624
|
+
const custom2 = classifyThrown(thrown);
|
|
38625
|
+
if (custom2) {
|
|
38626
|
+
kind = custom2.kind;
|
|
38627
|
+
customHint = custom2.hint;
|
|
38628
|
+
customDetail = custom2.detail;
|
|
38629
|
+
}
|
|
38630
|
+
}
|
|
38550
38631
|
error51 = {
|
|
38551
38632
|
kind,
|
|
38552
38633
|
message: probeResult.error.message,
|
|
38553
|
-
...bridgeHint !== void 0 ? { bridge_hint: bridgeHint } : {}
|
|
38634
|
+
...bridgeHint !== void 0 ? { bridge_hint: bridgeHint } : {},
|
|
38635
|
+
...customDetail !== void 0 ? { detail: customDetail } : {}
|
|
38554
38636
|
};
|
|
38555
38637
|
}
|
|
38556
38638
|
const lastExtensionMessageAt = transport.status().lastExtensionMessageAt;
|
|
38639
|
+
const arm = ok ? "ok" : error51?.kind === "bridge_down" ? "bridge_down" : probeResult.bridge.role === null ? "no_role" : error51?.kind === "timeout" ? "timeout" : error51?.kind === "protocol" || error51?.kind === "http" ? "protocol" : "unknown";
|
|
38640
|
+
const defaultHint = healthcheckHint({
|
|
38641
|
+
ok,
|
|
38642
|
+
role: probeResult.bridge.role,
|
|
38643
|
+
// FIX: the real configured port from bridgeHealth(), not a literal 37149.
|
|
38644
|
+
port: probeResult.bridge.port,
|
|
38645
|
+
hostLabel,
|
|
38646
|
+
prefix,
|
|
38647
|
+
probePath,
|
|
38648
|
+
errorKind: error51?.kind,
|
|
38649
|
+
bridgeHint: error51?.kind === "bridge_down" ? bridgeHint : void 0
|
|
38650
|
+
});
|
|
38557
38651
|
const result = {
|
|
38558
38652
|
ok,
|
|
38559
38653
|
bridge: {
|
|
@@ -38562,17 +38656,8 @@ function registerBridgeHealthcheckTool(args) {
|
|
|
38562
38656
|
},
|
|
38563
38657
|
probe,
|
|
38564
38658
|
...error51 ? { error: error51 } : {},
|
|
38565
|
-
|
|
38566
|
-
|
|
38567
|
-
role: probeResult.bridge.role,
|
|
38568
|
-
// FIX: the real configured port from bridgeHealth(), not a literal 37149.
|
|
38569
|
-
port: probeResult.bridge.port,
|
|
38570
|
-
hostLabel,
|
|
38571
|
-
prefix,
|
|
38572
|
-
probePath,
|
|
38573
|
-
errorKind: error51?.kind,
|
|
38574
|
-
bridgeHint: error51?.kind === "bridge_down" ? bridgeHint : void 0
|
|
38575
|
-
})
|
|
38659
|
+
// Precedence: classifyThrown's hint > per-arm override > default ladder.
|
|
38660
|
+
hint: customHint ?? hints?.[arm] ?? defaultHint
|
|
38576
38661
|
};
|
|
38577
38662
|
return {
|
|
38578
38663
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
package/dist/client.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { dirname, join } from 'node:path';
|
|
2
2
|
import { fileURLToPath } from 'node:url';
|
|
3
|
-
import { loadDotenvSafely, readEnvVar, formatApiError, truncateErrorMessage, McpToolError } from '@chrischall/mcp-utils';
|
|
3
|
+
import { loadDotenvSafely, readEnvVar, readTtlMsEnv, createResponseCache, parseRetryAfterMs, formatApiError, truncateErrorMessage, McpToolError, } from '@chrischall/mcp-utils';
|
|
4
4
|
// Load .env for local dev; silently skip if dotenv is unavailable (e.g. the
|
|
5
5
|
// .mcpb bundle). loadDotenvSafely never lets .env override a host-provided value.
|
|
6
6
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
@@ -15,40 +15,25 @@ const DEFAULT_CACHE_TTL_MS = 300_000;
|
|
|
15
15
|
// Location details/photos/reviews change slowly — 1 hour by default.
|
|
16
16
|
// Override with TRIPADVISOR_STATIC_CACHE_TTL (seconds; 0 = off).
|
|
17
17
|
const DEFAULT_STATIC_CACHE_TTL_MS = 3_600_000;
|
|
18
|
-
// Bound the cache so a long-lived server doesn't grow unbounded across many
|
|
19
|
-
// distinct paths; oldest entries are evicted first.
|
|
20
|
-
const CACHE_MAX_ENTRIES = 256;
|
|
21
18
|
// Cap a server-supplied Retry-After so one bad header can't stall a tool call.
|
|
22
19
|
const MAX_RETRY_AFTER_MS = 10_000;
|
|
23
|
-
/** Resolve a cache TTL (ms) from an env var holding seconds. A blank or
|
|
24
|
-
* non-numeric value falls back to `defaultMs`; a valid `0` disables caching. */
|
|
25
|
-
function readCacheTtlMs(envVar, defaultMs) {
|
|
26
|
-
const raw = readEnvVar(envVar);
|
|
27
|
-
if (raw === undefined)
|
|
28
|
-
return defaultMs;
|
|
29
|
-
const secs = Number(raw);
|
|
30
|
-
return Number.isFinite(secs) && secs >= 0 ? secs * 1000 : defaultMs;
|
|
31
|
-
}
|
|
32
20
|
export class TripAdvisorClient {
|
|
33
21
|
apiKey;
|
|
34
22
|
configError;
|
|
35
23
|
fetchImpl;
|
|
36
24
|
sleep;
|
|
37
|
-
|
|
38
|
-
staticCacheTtlMs;
|
|
39
|
-
now;
|
|
40
|
-
cache = new Map();
|
|
25
|
+
cache;
|
|
41
26
|
/**
|
|
42
27
|
* Defer the config error so the server still boots (and answers the host's
|
|
43
28
|
* install-time tools/list probe) when TRIPADVISOR_API_KEY isn't set yet. The
|
|
44
29
|
* error is re-raised at request time via requireKey().
|
|
45
30
|
*/
|
|
46
31
|
constructor(opts = {}) {
|
|
47
|
-
|
|
32
|
+
const now = opts.now ?? Date.now;
|
|
48
33
|
this.sleep = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
34
|
+
const cacheTtlMs = opts.cacheTtlMs ?? readTtlMsEnv('TRIPADVISOR_CACHE_TTL', DEFAULT_CACHE_TTL_MS);
|
|
35
|
+
const staticCacheTtlMs = opts.staticCacheTtlMs ?? readTtlMsEnv('TRIPADVISOR_STATIC_CACHE_TTL', DEFAULT_STATIC_CACHE_TTL_MS);
|
|
36
|
+
this.cache = createResponseCache({ ttlMs: { dynamic: cacheTtlMs, static: staticCacheTtlMs }, now });
|
|
52
37
|
this.fetchImpl = opts.fetchImpl ?? fetch;
|
|
53
38
|
const key = readEnvVar('TRIPADVISOR_API_KEY');
|
|
54
39
|
if (!key) {
|
|
@@ -76,31 +61,8 @@ export class TripAdvisorClient {
|
|
|
76
61
|
* (TRIPADVISOR_CACHE_TTL) is for searches.
|
|
77
62
|
*/
|
|
78
63
|
async get(path, opts = {}) {
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
const hit = this.cache.get(path);
|
|
82
|
-
if (hit && hit.expiresAt > this.now())
|
|
83
|
-
return hit.value;
|
|
84
|
-
}
|
|
85
|
-
const value = await this.request(path);
|
|
86
|
-
if (ttl > 0) {
|
|
87
|
-
if (this.cache.size >= CACHE_MAX_ENTRIES) {
|
|
88
|
-
// Evict expired entries first; if still full, drop the oldest (Map
|
|
89
|
-
// preserves insertion order, so the first key is the oldest).
|
|
90
|
-
const t = this.now();
|
|
91
|
-
for (const [k, v] of this.cache)
|
|
92
|
-
if (v.expiresAt <= t)
|
|
93
|
-
this.cache.delete(k);
|
|
94
|
-
while (this.cache.size >= CACHE_MAX_ENTRIES) {
|
|
95
|
-
const oldest = this.cache.keys().next().value;
|
|
96
|
-
if (oldest === undefined)
|
|
97
|
-
break;
|
|
98
|
-
this.cache.delete(oldest);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
this.cache.set(path, { expiresAt: this.now() + ttl, value });
|
|
102
|
-
}
|
|
103
|
-
return value;
|
|
64
|
+
const tier = opts.cache === 'static' ? 'static' : 'dynamic';
|
|
65
|
+
return this.cache.fetchThrough(path, () => this.request(path), tier);
|
|
104
66
|
}
|
|
105
67
|
async request(path, isRetry = false) {
|
|
106
68
|
const key = this.requireKey();
|
|
@@ -112,8 +74,7 @@ export class TripAdvisorClient {
|
|
|
112
74
|
return (await res.json());
|
|
113
75
|
const text = await res.text();
|
|
114
76
|
if (res.status === 429 && !isRetry) {
|
|
115
|
-
const
|
|
116
|
-
const delayMs = Number.isFinite(retryAfter) && retryAfter > 0 ? Math.min(retryAfter * 1000, MAX_RETRY_AFTER_MS) : 1_000;
|
|
77
|
+
const delayMs = parseRetryAfterMs(res.headers.get('retry-after'), { defaultMs: 1_000, capMs: MAX_RETRY_AFTER_MS });
|
|
117
78
|
await this.sleep(delayMs);
|
|
118
79
|
return this.request(path, true);
|
|
119
80
|
}
|
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.
|
|
2
|
+
export const VERSION = '0.2.1'; // x-release-please-version
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chrischall/tripadvisor-mcp",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
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>",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"test:coverage": "vitest run --coverage"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@chrischall/mcp-utils": "^0.
|
|
47
|
+
"@chrischall/mcp-utils": "^0.12.0",
|
|
48
48
|
"@fetchproxy/server": "^1.3.4",
|
|
49
49
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
50
50
|
"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.2.
|
|
9
|
+
"version": "0.2.1",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "@chrischall/tripadvisor-mcp",
|
|
14
|
-
"version": "0.2.
|
|
14
|
+
"version": "0.2.1",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
},
|