litescrape-mcp-server 0.1.1 → 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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Litescrape MCP Server
2
2
 
3
- Google Search, Bing, DuckDuckGo and Google Maps results for any MCP client, free and without an API key. Add a key for Google AI Mode, Google AI Overview and Google Shopping, and to lift every limit.
3
+ Google Search, Bing, DuckDuckGo and Google Maps results for any MCP client, free and without an API key. Add a key for Google AI Mode, Google AI Overview, Google Shopping and Google Reviews, and to lift every limit.
4
4
 
5
5
  Results come straight from the search engines' own pages as structured JSON, through the [Litescrape API](https://litescrape.com). Links are real destinations, never redirects, and nothing is cached: every call runs fresh.
6
6
 
@@ -47,6 +47,12 @@ gemini mcp add litescrape npx -y litescrape-mcp-server
47
47
 
48
48
  Requires Node.js 20 or newer.
49
49
 
50
+ **Hosted, nothing to install**: clients that connect to remote servers can use `https://mcp.litescrape.com/mcp` instead. Same tools, same free allowance; send an API key as `Authorization: Bearer ls_live_...` to unlock the rest.
51
+
52
+ ```
53
+ claude mcp add --transport http litescrape https://mcp.litescrape.com/mcp
54
+ ```
55
+
50
56
  ## What you get without a key
51
57
 
52
58
  | Tool | Returns | Free calls per network per UTC day |
@@ -61,7 +67,7 @@ One call runs at a time per network, and failed calls never count. When a limit
61
67
 
62
68
  ## Add an API key
63
69
 
64
- A key unlocks `google_ai_mode`, `google_ai_overview` and `google_shopping`, removes the daily limits and allows concurrent calls. Get one at [litescrape.com](https://litescrape.com), then set `LITESCRAPE_API_KEY` in the server's environment:
70
+ A key unlocks `google_ai_mode`, `google_ai_overview`, `google_shopping` and `google_reviews`, removes the daily limits and allows concurrent calls. Get one at [litescrape.com](https://litescrape.com), then set `LITESCRAPE_API_KEY` in the server's environment:
65
71
 
66
72
  ```json
67
73
  {
@@ -79,6 +85,8 @@ A key unlocks `google_ai_mode`, `google_ai_overview` and `google_shopping`, remo
79
85
 
80
86
  With Claude Code: `claude mcp add litescrape -e LITESCRAPE_API_KEY=ls_live_... -- npx -y litescrape-mcp-server`
81
87
 
88
+ With the hosted endpoint: `claude mcp add --transport http litescrape https://mcp.litescrape.com/mcp --header "Authorization: Bearer ls_live_..."`
89
+
82
90
  ## Tools
83
91
 
84
92
  | Tool | Key needed | What it does |
@@ -91,6 +99,7 @@ With Claude Code: `claude mcp add litescrape -e LITESCRAPE_API_KEY=ls_live_... -
91
99
  | `google_ai_overview` | Yes | Only the AI Overview for a Google search, or `null` when Google shows none |
92
100
  | `google_ai_mode` | Yes | Google AI Mode's generated answer with its cited sources; `continuable` returns a follow-up token, `image_url` adds a picture to the question |
93
101
  | `google_shopping` | Yes | The Google Shopping product grid, category blocks, sponsored listings and refinement chips; price, sale, shipping and small-business refinements, `sort_by`, pagination |
102
+ | `google_reviews` | Yes | The Google Maps reviews of one place by `place_id` or `data_id` (from a `google_maps` result) with `place_info`; `sort_by`, `topic_id` or `query` filters, `num`, and `next_page_token` for the next page |
94
103
 
95
104
  Parameter names and accepted values follow the [Litescrape API reference](https://litescrape.com/docs).
96
105
 
@@ -128,6 +137,16 @@ Requests time out after 120 seconds by default (the API's own deadline is 90 sec
128
137
  | `LITESCRAPE_API_URL` | `https://api.litescrape.com` | API origin, for self-hosted or staging deployments |
129
138
  | `LITESCRAPE_TIMEOUT_MS` | `120000` | Per-request timeout in milliseconds |
130
139
 
140
+ ### Streamable HTTP mode
141
+
142
+ `litescrape-mcp-server --http` (or `LITESCRAPE_MCP_TRANSPORT=http`) serves MCP over HTTP instead of stdio: stateless `POST /mcp`, `GET /healthz`, listening on `PORT` (default 8080) and `HOST` (default `0.0.0.0`), or `--port` and `--host`. Each request's API key comes from its `Authorization: Bearer` header or `?api_key=` query parameter, so one process serves keyed and keyless callers; `LITESCRAPE_API_KEY` is ignored in this mode. This is what runs at `https://mcp.litescrape.com/mcp`.
143
+
144
+ Behind a shared address, keyless callers would all count against one allowance. `LITESCRAPE_KEYLESS_PROXY_SECRET` is the secret the Litescrape API shares with its own hosted endpoint for forwarding each caller's address, and `LITESCRAPE_EDGE_SECRET` is the value Cloudflare adds in front of that endpoint so the caller can be read from `CF-Connecting-IP`. Neither is needed for a private deployment, where the server's own address is metered.
145
+
146
+ ```
147
+ docker build -t litescrape-mcp-server . && docker run -p 8080:8080 litescrape-mcp-server --http
148
+ ```
149
+
131
150
  Every request identifies itself with `X-Litescrape-Client: mcp/<version>` and a `litescrape-mcp-server/<version>` user agent. The server writes protocol messages to stdout and its logs to stderr.
132
151
 
133
152
  ## Development
@@ -135,7 +154,7 @@ Every request identifies itself with `X-Litescrape-Client: mcp/<version>` and a
135
154
  ```
136
155
  npm install
137
156
  npm run lint # prettier + tsc
138
- npm test # vitest, including a real stdio round trip
157
+ npm test # vitest, including real stdio and HTTP round trips
139
158
  npm run build # tsc -> dist/
140
159
  npm run inspector # MCP Inspector against dist/index.js
141
160
  ```
package/dist/client.d.ts CHANGED
@@ -25,6 +25,8 @@ export interface ClientOptions {
25
25
  apiKey?: string;
26
26
  apiUrl?: string;
27
27
  timeoutMs?: number;
28
+ /** Extra request headers, e.g. the caller's address forwarded by the hosted endpoint. */
29
+ headers?: Record<string, string>;
28
30
  fetch?: typeof globalThis.fetch;
29
31
  sleep?: (ms: number) => Promise<void>;
30
32
  }
@@ -32,6 +34,7 @@ export declare class LitescrapeClient {
32
34
  readonly apiUrl: string;
33
35
  readonly timeoutMs: number;
34
36
  private readonly apiKey;
37
+ private readonly extraHeaders;
35
38
  private readonly fetchImpl;
36
39
  private readonly sleep;
37
40
  constructor(options?: ClientOptions);
package/dist/client.js CHANGED
@@ -28,12 +28,14 @@ export class LitescrapeClient {
28
28
  apiUrl;
29
29
  timeoutMs;
30
30
  apiKey;
31
+ extraHeaders;
31
32
  fetchImpl;
32
33
  sleep;
33
34
  constructor(options = {}) {
34
35
  this.apiKey = (options.apiKey ?? '').trim();
35
36
  this.apiUrl = (options.apiUrl ?? DEFAULT_API_URL).replace(/\/+$/, '');
36
37
  this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
38
+ this.extraHeaders = { ...(options.headers ?? {}) };
37
39
  this.fetchImpl = options.fetch ?? globalThis.fetch;
38
40
  this.sleep = options.sleep ?? defaultSleep;
39
41
  }
@@ -62,6 +64,7 @@ export class LitescrapeClient {
62
64
  }
63
65
  headers() {
64
66
  const headers = {
67
+ ...this.extraHeaders,
65
68
  Accept: 'application/json',
66
69
  'User-Agent': this.userAgent,
67
70
  'X-Litescrape-Client': `mcp/${VERSION}`,
package/dist/http.d.ts ADDED
@@ -0,0 +1,40 @@
1
+ import { type IncomingMessage, type Server, type ServerResponse } from 'node:http';
2
+ import { LitescrapeClient, type ClientOptions } from './client.js';
3
+ export declare const MCP_PATH = "/mcp";
4
+ export declare const HEALTH_PATH = "/healthz";
5
+ export declare const FORWARDED_IP_HEADER = "X-Litescrape-Keyless-Ip";
6
+ export declare const PROXY_SECRET_HEADER = "X-Litescrape-Keyless-Secret";
7
+ export declare const EDGE_SECRET_HEADER = "x-litescrape-edge-secret";
8
+ export declare const EDGE_CLIENT_HEADER = "cf-connecting-ip";
9
+ export interface HttpOptions {
10
+ /**
11
+ * Secret shared with the Litescrape API. When set, every upstream request
12
+ * carries the caller's address under it, so keyless metering applies to the
13
+ * caller rather than to this server's own address.
14
+ */
15
+ proxySecret?: string;
16
+ /**
17
+ * Secret Cloudflare adds to every request it relays. When set, the caller is
18
+ * read from CF-Connecting-IP on requests that carry it, and nothing is
19
+ * forwarded for requests that bypassed Cloudflare.
20
+ */
21
+ edgeSecret?: string;
22
+ apiUrl?: string;
23
+ timeoutMs?: number;
24
+ fetch?: ClientOptions['fetch'];
25
+ sleep?: ClientOptions['sleep'];
26
+ log?: (line: string) => void;
27
+ }
28
+ export declare function bearerToken(req: IncomingMessage): string | undefined;
29
+ /**
30
+ * The caller as the ingress reports it. With an edge secret configured, only a
31
+ * request carrying it is known to have come through Cloudflare, and its caller
32
+ * is CF-Connecting-IP; a request that bypassed Cloudflare gets no caller at all.
33
+ * Without one, X-Forwarded-For is passed through unchanged: the API meters it
34
+ * only when it holds exactly one address, so a crafted multi-hop value is
35
+ * refused there instead of being trusted here.
36
+ */
37
+ export declare function callerAddress(req: IncomingMessage, options?: Pick<HttpOptions, 'edgeSecret'>): string | undefined;
38
+ export declare function clientForRequest(req: IncomingMessage, options: HttpOptions): LitescrapeClient;
39
+ export declare function handleRequest(req: IncomingMessage, res: ServerResponse, options: HttpOptions): Promise<void>;
40
+ export declare function createHttpServer(options?: HttpOptions): Server;
package/dist/http.js ADDED
@@ -0,0 +1,132 @@
1
+ import { createHash, timingSafeEqual } from 'node:crypto';
2
+ import { createServer as createNodeServer, } from 'node:http';
3
+ import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
4
+ import { LitescrapeClient } from './client.js';
5
+ import { createServer } from './server.js';
6
+ import { PACKAGE_NAME, VERSION } from './version.js';
7
+ export const MCP_PATH = '/mcp';
8
+ export const HEALTH_PATH = '/healthz';
9
+ // The Litescrape API reads the forwarded address only together with the
10
+ // shared secret, and only when it holds exactly one address.
11
+ export const FORWARDED_IP_HEADER = 'X-Litescrape-Keyless-Ip';
12
+ export const PROXY_SECRET_HEADER = 'X-Litescrape-Keyless-Secret';
13
+ // Behind Cloudflare the socket peer is one of its egress addresses and
14
+ // X-Forwarded-For is a chain, so the caller is CF-Connecting-IP; a transform
15
+ // rule on the zone adds the edge secret, which is how a request proves it came
16
+ // through Cloudflare rather than straight to the origin with forged headers.
17
+ export const EDGE_SECRET_HEADER = 'x-litescrape-edge-secret';
18
+ export const EDGE_CLIENT_HEADER = 'cf-connecting-ip';
19
+ function requestUrl(req) {
20
+ return new URL(req.url ?? '/', 'http://localhost');
21
+ }
22
+ export function bearerToken(req) {
23
+ const header = req.headers.authorization;
24
+ const match = typeof header === 'string' ? /^Bearer\s+(.+)$/i.exec(header.trim()) : null;
25
+ if (match?.[1])
26
+ return match[1].trim();
27
+ const fromQuery = requestUrl(req).searchParams.get('api_key')?.trim();
28
+ return fromQuery || undefined;
29
+ }
30
+ function headerValue(req, name) {
31
+ const raw = req.headers[name];
32
+ return (Array.isArray(raw) ? raw.join(', ') : raw)?.trim() ?? '';
33
+ }
34
+ function sameSecret(presented, expected) {
35
+ const digest = (value) => createHash('sha256').update(value).digest();
36
+ return timingSafeEqual(digest(presented), digest(expected));
37
+ }
38
+ /**
39
+ * The caller as the ingress reports it. With an edge secret configured, only a
40
+ * request carrying it is known to have come through Cloudflare, and its caller
41
+ * is CF-Connecting-IP; a request that bypassed Cloudflare gets no caller at all.
42
+ * Without one, X-Forwarded-For is passed through unchanged: the API meters it
43
+ * only when it holds exactly one address, so a crafted multi-hop value is
44
+ * refused there instead of being trusted here.
45
+ */
46
+ export function callerAddress(req, options = {}) {
47
+ if (options.edgeSecret) {
48
+ const presented = headerValue(req, EDGE_SECRET_HEADER);
49
+ if (!presented || !sameSecret(presented, options.edgeSecret))
50
+ return undefined;
51
+ return headerValue(req, EDGE_CLIENT_HEADER) || undefined;
52
+ }
53
+ const forwarded = headerValue(req, 'x-forwarded-for');
54
+ if (forwarded)
55
+ return forwarded;
56
+ return req.socket?.remoteAddress || undefined;
57
+ }
58
+ export function clientForRequest(req, options) {
59
+ const headers = {};
60
+ const caller = options.proxySecret ? callerAddress(req, options) : undefined;
61
+ if (options.proxySecret && caller) {
62
+ headers[FORWARDED_IP_HEADER] = caller;
63
+ headers[PROXY_SECRET_HEADER] = options.proxySecret;
64
+ }
65
+ return new LitescrapeClient({
66
+ apiKey: bearerToken(req),
67
+ apiUrl: options.apiUrl,
68
+ timeoutMs: options.timeoutMs,
69
+ headers,
70
+ fetch: options.fetch,
71
+ sleep: options.sleep,
72
+ });
73
+ }
74
+ function json(res, status, body) {
75
+ res.writeHead(status, { 'Content-Type': 'application/json' });
76
+ res.end(JSON.stringify(body));
77
+ }
78
+ const CORS_HEADERS = {
79
+ 'Access-Control-Allow-Origin': '*',
80
+ 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
81
+ 'Access-Control-Allow-Headers': 'Content-Type, Authorization, Accept, Mcp-Session-Id, Mcp-Protocol-Version',
82
+ 'Access-Control-Expose-Headers': 'Mcp-Session-Id, Mcp-Protocol-Version',
83
+ 'Access-Control-Max-Age': '86400',
84
+ };
85
+ export async function handleRequest(req, res, options) {
86
+ for (const [name, value] of Object.entries(CORS_HEADERS))
87
+ res.setHeader(name, value);
88
+ const { pathname } = requestUrl(req);
89
+ if (req.method === 'OPTIONS') {
90
+ res.writeHead(204);
91
+ res.end();
92
+ return;
93
+ }
94
+ if (pathname === HEALTH_PATH || (pathname === '/' && req.method === 'GET')) {
95
+ json(res, 200, { status: 'ok', name: PACKAGE_NAME, version: VERSION, mcp: MCP_PATH });
96
+ return;
97
+ }
98
+ if (pathname !== MCP_PATH) {
99
+ json(res, 404, { error: `Not found. The MCP endpoint is POST ${MCP_PATH}.` });
100
+ return;
101
+ }
102
+ if (req.method !== 'POST') {
103
+ res.setHeader('Allow', 'POST, OPTIONS');
104
+ json(res, 405, {
105
+ error: `This server is stateless: POST each JSON-RPC message to ${MCP_PATH}.`,
106
+ });
107
+ return;
108
+ }
109
+ // One server and transport per request: nothing is shared between callers,
110
+ // and the client built from this request's own credentials goes with it.
111
+ const server = createServer({ client: clientForRequest(req, options) });
112
+ const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
113
+ res.on('close', () => {
114
+ void transport.close();
115
+ void server.close();
116
+ });
117
+ await server.connect(transport);
118
+ await transport.handleRequest(req, res);
119
+ }
120
+ export function createHttpServer(options = {}) {
121
+ return createNodeServer((req, res) => {
122
+ handleRequest(req, res, options).catch((error) => {
123
+ options.log?.(`${req.method ?? ''} ${req.url ?? ''} failed: ${String(error)}`);
124
+ if (res.headersSent) {
125
+ res.end();
126
+ }
127
+ else {
128
+ json(res, 500, { error: 'Internal server error.' });
129
+ }
130
+ });
131
+ });
132
+ }
package/dist/index.js CHANGED
@@ -1,15 +1,51 @@
1
1
  #!/usr/bin/env node
2
2
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
3
  import { LitescrapeClient } from './client.js';
4
+ import { createHttpServer, MCP_PATH } from './http.js';
4
5
  import { createServer } from './server.js';
5
6
  import { PACKAGE_NAME, VERSION } from './version.js';
6
- async function main() {
7
+ function flag(args, name) {
8
+ const index = args.indexOf(name);
9
+ return index === -1 ? undefined : (args[index + 1] ?? '');
10
+ }
11
+ async function stdio() {
7
12
  const client = LitescrapeClient.fromEnv(process.env);
8
13
  const server = createServer({ client });
9
14
  await server.connect(new StdioServerTransport());
10
15
  // stdout carries the protocol; every log line goes to stderr.
11
16
  console.error(`${PACKAGE_NAME} ${VERSION} ready (${client.keyed ? 'API key' : 'keyless'} mode, ${client.apiUrl})`);
12
17
  }
18
+ function http(args) {
19
+ const port = Number(flag(args, '--port') ?? process.env.PORT ?? 8080);
20
+ const host = flag(args, '--host') ?? process.env.HOST ?? '0.0.0.0';
21
+ const proxySecret = process.env.LITESCRAPE_KEYLESS_PROXY_SECRET?.trim() || undefined;
22
+ const edgeSecret = process.env.LITESCRAPE_EDGE_SECRET?.trim() || undefined;
23
+ const apiUrl = process.env.LITESCRAPE_API_URL?.trim() || undefined;
24
+ const timeoutMs = Number(process.env.LITESCRAPE_TIMEOUT_MS) || undefined;
25
+ const server = createHttpServer({
26
+ proxySecret,
27
+ edgeSecret,
28
+ apiUrl,
29
+ timeoutMs,
30
+ log: console.error,
31
+ });
32
+ server.listen(port, host, () => {
33
+ const forwarding = proxySecret ? (edgeSecret ? 'on, Cloudflare edge' : 'on') : 'off';
34
+ console.error(`${PACKAGE_NAME} ${VERSION} listening on http://${host}:${port}${MCP_PATH} ` +
35
+ `(API key per request, caller forwarding ${forwarding})`);
36
+ });
37
+ const stop = () => server.close(() => process.exit(0));
38
+ process.on('SIGTERM', stop);
39
+ process.on('SIGINT', stop);
40
+ }
41
+ async function main() {
42
+ const args = process.argv.slice(2);
43
+ if (args.includes('--http') || process.env.LITESCRAPE_MCP_TRANSPORT === 'http') {
44
+ http(args);
45
+ return;
46
+ }
47
+ await stdio();
48
+ }
13
49
  main().catch((error) => {
14
50
  console.error(`${PACKAGE_NAME} failed to start:`, error);
15
51
  process.exit(1);
package/dist/messages.js CHANGED
@@ -18,14 +18,15 @@ export function instructions(keyed) {
18
18
  return ('This session is authenticated with a Litescrape API key: every tool is available and ' +
19
19
  'each successful call is billed to that key. search is google_search in fast mode ' +
20
20
  '(organic results only). google_ai_mode generates an answer per request and is the ' +
21
- 'slowest tool. ' +
21
+ 'slowest tool. google_reviews takes the place_id or data_id of a google_maps result ' +
22
+ 'and pages with next_page_token. ' +
22
23
  shared);
23
24
  }
24
25
  return ('This session has no Litescrape API key. google_search (25 calls), bing_search (50), ' +
25
26
  'google_maps (50) and duckduckgo_search (50) are free per network per UTC day, one call ' +
26
27
  'at a time; failed calls do not count. search is google_search in fast mode (organic ' +
27
- 'results only) and shares its allowance. google_ai_mode, google_ai_overview and ' +
28
- 'google_shopping need an API key, which also lifts every limit: get one at ' +
28
+ 'results only) and shares its allowance. google_ai_mode, google_ai_overview, ' +
29
+ 'google_shopping and google_reviews need an API key, which also lifts every limit: get one at ' +
29
30
  `${KEY_URL} and set LITESCRAPE_API_KEY in this server's environment. ` +
30
31
  shared);
31
32
  }
package/dist/surfaces.js CHANGED
@@ -383,6 +383,61 @@ export const SURFACES = [
383
383
  `${sponsored} sponsored listings.`);
384
384
  },
385
385
  },
386
+ {
387
+ name: 'google_reviews',
388
+ title: 'Google Reviews',
389
+ description: 'Return the Google Maps reviews of one place (reviews with rating, snippet, author, ' +
390
+ 'date, likes, owner response, guided details and dining subratings) plus place_info. ' +
391
+ 'Identify the place by place_id or data_id from a google_maps result. Sort, filter by ' +
392
+ 'topic or text, and follow pagination.next_page_token for more. Requires an API key.',
393
+ path: '/api/google/reviews',
394
+ keyless: false,
395
+ inputSchema: {
396
+ place_id: z
397
+ .string()
398
+ .optional()
399
+ .describe('Google place ID such as ChIJ...; exactly one of place_id and data_id'),
400
+ data_id: z
401
+ .string()
402
+ .optional()
403
+ .describe('Google data ID such as 0x89c2...:0x...; exactly one of place_id and data_id'),
404
+ hl,
405
+ gl,
406
+ sort_by: z
407
+ .enum(['qualityScore', 'newestFirst', 'ratingHigh', 'ratingLow'])
408
+ .optional()
409
+ .describe('Order: qualityScore (most relevant, default), newestFirst, ratingHigh, ratingLow'),
410
+ topic_id: z
411
+ .string()
412
+ .optional()
413
+ .describe('Keep reviews on one Google review topic; conflicts with query'),
414
+ query: z
415
+ .string()
416
+ .optional()
417
+ .describe('Keep reviews mentioning this text; conflicts with topic_id'),
418
+ num: z
419
+ .number()
420
+ .int()
421
+ .min(1)
422
+ .max(100)
423
+ .optional()
424
+ .describe('Reviews to return: 1-100 on a first unfiltered request (default 8), 1-20 with ' +
425
+ 'topic_id, query or next_page_token (default 10)'),
426
+ next_page_token: z
427
+ .string()
428
+ .optional()
429
+ .describe('pagination.next_page_token from the previous response; keep the same place, sort and filters'),
430
+ result_groups: resultGroups,
431
+ },
432
+ summarize: (payload) => {
433
+ const reviews = count(payload, 'reviews') ?? 0;
434
+ const place = isRecord(payload.place_info) ? payload.place_info : {};
435
+ const name = typeof place.title === 'string' ? ` for ${place.title}` : '';
436
+ const pagination = isRecord(payload.pagination) ? payload.pagination : {};
437
+ const more = typeof pagination.next_page_token === 'string' ? '; more pages available' : '';
438
+ return `Google Reviews${name}: ${reviews} reviews${more}.`;
439
+ },
440
+ },
386
441
  ];
387
442
  export function findSurface(name) {
388
443
  return SURFACES.find((surface) => surface.name === name);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "litescrape-mcp-server",
3
- "version": "0.1.1",
4
- "description": "MCP server for the Litescrape API: Google Search, Bing, DuckDuckGo and Google Maps free without an API key, plus Google AI Mode, AI Overview and Shopping with one",
3
+ "version": "0.2.1",
4
+ "description": "MCP server for the Litescrape API: Google Search, Bing, DuckDuckGo and Google Maps free without an API key, plus Google AI Mode, AI Overview, Shopping and Reviews with one. Stdio or Streamable HTTP.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://litescrape.com",
7
7
  "mcpName": "com.litescrape/litescrape-mcp-server",
@@ -31,6 +31,7 @@
31
31
  "google ai mode",
32
32
  "google ai overview",
33
33
  "google shopping",
34
+ "google reviews",
34
35
  "web search",
35
36
  "claude",
36
37
  "cursor"
package/server.json CHANGED
@@ -1,17 +1,41 @@
1
1
  {
2
- "$schema": "https://static.modelcontextprotocol.io/schemas/2025-07-09/server.schema.json",
2
+ "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
3
3
  "name": "com.litescrape/litescrape-mcp-server",
4
- "description": "Google Search, Bing, DuckDuckGo and Google Maps results free without an API key; Google AI Mode, AI Overview and Shopping with one.",
5
- "version": "0.1.1",
4
+ "title": "Litescrape",
5
+ "description": "Google, Bing, DuckDuckGo and Google Maps search results, free without an API key.",
6
+ "repository": {
7
+ "url": "https://github.com/litescrape/litescrape-mcp-server",
8
+ "source": "github"
9
+ },
10
+ "websiteUrl": "https://litescrape.com",
11
+ "version": "0.2.1",
6
12
  "packages": [
7
13
  {
8
14
  "registryType": "npm",
9
15
  "identifier": "litescrape-mcp-server",
10
- "version": "0.1.1",
16
+ "version": "0.2.1",
17
+ "transport": {
18
+ "type": "stdio"
19
+ },
11
20
  "environmentVariables": [
12
21
  {
13
22
  "name": "LITESCRAPE_API_KEY",
14
- "description": "Optional Litescrape API key. Without it, google_search, bing_search, google_maps and duckduckgo_search work from a free daily allowance; with it every tool is available.",
23
+ "description": "Optional Litescrape API key. Without it, search, google_search, bing_search, google_maps and duckduckgo_search work from a free daily allowance; with it google_ai_mode, google_ai_overview, google_shopping and google_reviews are available too and the limits are lifted.",
24
+ "isRequired": false,
25
+ "isSecret": true,
26
+ "format": "string"
27
+ }
28
+ ]
29
+ }
30
+ ],
31
+ "remotes": [
32
+ {
33
+ "type": "streamable-http",
34
+ "url": "https://mcp.litescrape.com/mcp",
35
+ "headers": [
36
+ {
37
+ "name": "Authorization",
38
+ "description": "Optional: Bearer <Litescrape API key>. Without it the same free daily allowance applies.",
15
39
  "isRequired": false,
16
40
  "isSecret": true
17
41
  }