@georanker/seo-mcp 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 GeoRanker
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,47 @@
1
+ **SEO & SERP MCP by GeoRanker**
2
+
3
+ SERP data, multi-location rank tracking, Lighthouse SEO audits, broken internal links, backlinks and keyword volumes for AI agents, powered by GeoRanker.
4
+
5
+ Client version 0.14.0. The client is MIT-licensed. npm is the primary installation route for released versions; GitHub source installation is the fallback. If the requested npm version is not yet available, use the source instructions below. The hosted service is managed separately by GeoRanker.
6
+
7
+ **Install once, receive updates automatically**
8
+
9
+ Use a supported Node.js version (see package.json engines) and npm. Run the no-query setup check, then add the npm launcher to your AI host. Git and a TypeScript build are needed only for the source-install fallback. Follow the [installation guide](docs/install.md) for configuration and migration from older clients.
10
+
11
+ Each push to the public main branch runs the release workflow. After tests and clean-install checks pass, it publishes a client package with signed GitHub provenance. The launcher checks for this release at startup and every five minutes while running, verifies its repository, workflow and commit identity and artifact checksum, then installs locked production dependencies without lifecycle scripts. Automatic updates need Node.js and npm; Git and TypeScript builds are not required after initial installation. From 0.13.1, a version or schema compatibility error triggers an immediate signed release check without waiting for the five-minute interval. Runtime recovery checks are limited to once per worker release per session; ordinary errors do not trigger them. Active calls remain protected and failed requests are never replayed. From 0.13.0, a stable supervisor keeps the host MCP connection open and runs tools through an internal worker. It applies a verified worker update when no tool calls are active and 60 seconds have passed without tool activity. Idle refers to this MCP connection; existing provider jobs can remain pending. Calls are not replayed. A failed candidate leaves the current worker in place. Automatic checks stay quiet when nothing changes or an update is unavailable; a message is written only when an update is applied. After a cancelled or timed-out worker request, worker swaps wait for a normal host reconnect because completion is uncertain. Changes to the supervisor itself take effect on a normal host restart. Existing 0.12.0 clients acquire the supervisor on their next reconnect once 0.13.0 is prepared; subsequent compatible worker updates apply within the session without reinstalling or reconfiguring the host. If the release workflow or update verification fails, the installed version continues working. Use --update to prepare the latest commit immediately, or set GEORANKER_MCP_AUTO_UPDATE=0 to opt out. Updates do not create provider data jobs.
12
+
13
+ > Show up to 20 Google organic results for car insurance in Bucharest, Romania, with positions, source URLs and freshness.
14
+
15
+ **Tools**
16
+
17
+ - search_serps
18
+ - get_serp_result
19
+ - create_rank_tracking_report
20
+ - get_rank_tracking_report
21
+ - update_rank_tracking_schedule
22
+ - create_onpage_report
23
+ - get_onpage_report
24
+ - create_broken_links_report
25
+ - get_broken_links_report
26
+ - create_backlinks_report
27
+ - get_backlinks_report
28
+ - create_keyword_volume_report
29
+ - get_keyword_volume_report
30
+
31
+ Completed MCP results are eligible for reuse for seven days by default. Pass forceLive: true to bypass completed cache and request fresh upstream work. Pending work can be reused safely; retrieve the returned job ID instead of creating another request. Results disclose cache metadata and provider generation time when supplied. Report tools create persistent reports and use opaque reportId values, distinct from SERP jobId. Use each report’s matching get tool to retrieve it. Unknown or pending report status never triggers automatic recreation. For recurring rank reports, forceLive retrieves existing provider data and returns refreshNotice without a manual capture or duplicate schedule. The client negotiates the expanded SEO catalog while older clients retain their original tools. See [examples and limits](docs/examples.md). The [SEO report guide](docs/seo-reports.md) includes input examples, retrieval steps, scheduling and report-specific limits for all five families.
32
+
33
+ The client enrolls automatically and stores its own installation credentials. Both GeoRanker products share their installation/account relationship for the same service origin. No manual provider API key is required. Setup performs no data query; data calls use the configured allowance and provider credits. SEO reports use server-managed provider credentials. Unregistered installations use the platform key; linked accounts use their own SEO key with free, verified or provider-credit allowances. Account linking and rebilling require service support. Rank checks count keywords × locations × engines, Lighthouse counts devices, link crawls count blocks of up to 100 requested pages (rounded up), keyword reports count keywords, and backlink reports count one. These are admission units, not provider credit prices. Only platform-key work also consumes the shared server allowance. Use optional campaignName to select an owned MCP campaign, or omit it for the installation default.
34
+
35
+ Request independent tasks in parallel. The hosted service applies shared and per-installation limits and may briefly queue a call. Clients sharing an installation share its limits; the operator controls slots centrally. Keep pending job or report IDs and retrieve existing results. Cancelling a call does not guarantee that submitted work stopped. Completed-result reuse remains seven days by default.
36
+
37
+ See [client privacy and data flow](docs/privacy.md). Package source is restricted to client transport, identity, updates and tool contracts. The hosted service, provider adapter and administration are not included.
38
+
39
+ **Development**
40
+
41
+ ```sh
42
+ npm ci
43
+ npm test
44
+ npm run pack:check
45
+ ```
46
+
47
+ This client and its documentation are licensed under MIT; see LICENSE. Dependencies retain their own licenses. The hosted GeoRanker service, its private source and data access are governed separately by GeoRanker's service terms. Public exports allow npm publication; internal exports remain private. metadata.json describes the product; it is not an official MCP Registry submission.
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env node
2
+ interface StartupRecovery {
3
+ checksRemaining: number;
4
+ }
5
+ export declare function launch(bundledOnly?: boolean, recovery?: StartupRecovery): Promise<void>;
6
+ export {};
@@ -0,0 +1,144 @@
1
+ #!/usr/bin/env node
2
+ // Keep the configured entry point stable; verified releases also update this launcher.
3
+ import { realpathSync } from 'node:fs';
4
+ import { pathToFileURL, fileURLToPath } from 'node:url';
5
+ import { dirname, resolve } from 'node:path';
6
+ import { CLIENT_PROFILE, CLIENT_REPOSITORY, CLIENT_VERSION } from './product.js';
7
+ import { checkForUpdate, selectRelease, rollbackRelease } from './updater.js';
8
+ const args = process.argv.slice(2);
9
+ const supported = ['--setup', '--help', '-h', '--version', '-v', '--update'];
10
+ const options = {
11
+ repository: CLIENT_REPOSITORY, version: CLIENT_VERSION,
12
+ bundledRoot: resolve(dirname(fileURLToPath(import.meta.url)), '../..'),
13
+ env: process.env,
14
+ log: message => process.stderr.write('GeoRanker ' + CLIENT_PROFILE + ' MCP: ' + message + '\n'),
15
+ };
16
+ async function runBundled() {
17
+ if (args[0] === '--update') {
18
+ const result = await checkForUpdate({ ...options, force: true });
19
+ process.stdout.write(JSON.stringify(result) + '\n');
20
+ if (result.status === 'failed')
21
+ process.exitCode = 1;
22
+ return;
23
+ }
24
+ if (args.length === 0) {
25
+ const { createSupervisor } = await import('./supervisor.js');
26
+ const { StdioServerTransport } = await import('@modelcontextprotocol/sdk/server/stdio.js');
27
+ const supervisor = await createSupervisor(options);
28
+ try {
29
+ await supervisor.connect(new StdioServerTransport());
30
+ }
31
+ catch (error) {
32
+ await supervisor.close();
33
+ throw error;
34
+ }
35
+ return;
36
+ }
37
+ const runtime = await import(pathToFileURL(resolve(options.bundledRoot, 'dist/src/runtime.js')).href);
38
+ await runtime.run();
39
+ }
40
+ async function runRelease(root, recovery) {
41
+ if (root === options.bundledRoot)
42
+ return runBundled();
43
+ const client = await import(pathToFileURL(resolve(root, 'dist/src/cli.js')).href);
44
+ if (typeof client.launch !== 'function')
45
+ throw new Error('Prepared client does not export its launcher.');
46
+ // The selected release owns its runtime AND future update checks. Avoid recursion.
47
+ await client.launch(true, recovery);
48
+ }
49
+ // This export is the stable delegation contract for already-installed launchers.
50
+ // bundledOnly is used only by a parent launcher after it selects a verified release.
51
+ export async function launch(bundledOnly = false, recovery = { checksRemaining: 1 }) {
52
+ if (args.length > 1 || (args.length === 1 && !supported.includes(args[0]))) {
53
+ throw Object.assign(new Error('Unsupported argument. Use --help. This package only connects to the hosted GeoRanker service.'), { code: 'CLI_ARGUMENTS' });
54
+ }
55
+ const attempted = new Set();
56
+ let failure;
57
+ // A cached launcher must own recovery too, even when an older bootstrap delegates.
58
+ if (bundledOnly) {
59
+ attempted.add(options.bundledRoot);
60
+ try {
61
+ await runBundled();
62
+ return;
63
+ }
64
+ catch (error) {
65
+ failure = error;
66
+ }
67
+ }
68
+ // Initialization may fall back through current, previous and bundled releases.
69
+ // runtime.run rejects only before a usable stdio connection is established.
70
+ for (let attempt = 0; !bundledOnly && attempt < 3; attempt++) {
71
+ const release = await selectRelease(options);
72
+ if (attempted.has(release.root))
73
+ break;
74
+ attempted.add(release.root);
75
+ try {
76
+ await runRelease(release.root, recovery);
77
+ return;
78
+ }
79
+ catch (error) {
80
+ failure = error;
81
+ if (!release.commit)
82
+ break;
83
+ options.log?.('The prepared client could not start. Trying the previous working client.');
84
+ await rollbackRelease(options, release.commit);
85
+ }
86
+ }
87
+ // A read-only or damaged pointer must not prevent use of the bundled client.
88
+ if (!attempted.has(options.bundledRoot)) {
89
+ attempted.add(options.bundledRoot);
90
+ try {
91
+ await runBundled();
92
+ return;
93
+ }
94
+ catch (error) {
95
+ failure = error;
96
+ }
97
+ }
98
+ // Older tool schemas can fail initialization before periodic updates start.
99
+ // Check once here, before accepting requests, so a verified compatible update can heal it.
100
+ if (recovery.checksRemaining > 0 && options.env.GEORANKER_MCP_AUTO_UPDATE !== '0' && (args.length === 0 || args[0] === '--setup')) {
101
+ // Share one recovery check across delegated launchers to bound startup retries.
102
+ recovery.checksRemaining--;
103
+ // Initialization cannot wait for the normal polling cooldown to heal stale schemas.
104
+ const prepared = await checkForUpdate({ ...options, force: true, log: undefined });
105
+ const updated = await selectRelease(options);
106
+ if (updated.commit && !attempted.has(updated.root)) {
107
+ try {
108
+ await runRelease(updated.root, recovery);
109
+ try {
110
+ options.log?.('Applied verified client update ' + (prepared.version || updated.commit) + ' during startup recovery.');
111
+ }
112
+ catch { /* Logging cannot interrupt a recovered connection. */ }
113
+ return;
114
+ }
115
+ catch (error) {
116
+ failure = error;
117
+ await rollbackRelease(options, updated.commit);
118
+ }
119
+ }
120
+ }
121
+ throw failure;
122
+ }
123
+ function isEntryPoint() {
124
+ if (!process.argv[1])
125
+ return false;
126
+ const entry = resolve(process.argv[1]);
127
+ if (pathToFileURL(entry).href === import.meta.url)
128
+ return true;
129
+ try {
130
+ return pathToFileURL(realpathSync(entry)).href === import.meta.url;
131
+ }
132
+ catch {
133
+ return false;
134
+ }
135
+ }
136
+ if (isEntryPoint()) {
137
+ launch().catch((error) => {
138
+ const diagnostic = error;
139
+ const message = typeof diagnostic?.code === 'string' && typeof diagnostic?.message === 'string'
140
+ ? diagnostic.code + ': ' + diagnostic.message : 'The connection could not be started.';
141
+ process.stderr.write('GeoRanker ' + CLIENT_PROFILE + ' MCP: ' + message + '\n');
142
+ process.exitCode = 1;
143
+ });
144
+ }
@@ -0,0 +1 @@
1
+ export declare function secureUrl(value: string, name: string): URL;
@@ -0,0 +1,16 @@
1
+ import { AppError } from './errors.js';
2
+ // Client URL validation only. Upstream API and operator configuration stay private.
3
+ export function secureUrl(value, name) {
4
+ let url;
5
+ try {
6
+ url = new URL(value);
7
+ }
8
+ catch {
9
+ throw new AppError('CONFIG_ERROR', `${name} must be a valid URL.`);
10
+ }
11
+ const local = ['localhost', '127.0.0.1', '[::1]'].includes(url.hostname);
12
+ if ((url.protocol !== 'https:' && !(local && url.protocol === 'http:')) || url.username || url.password || url.search || url.hash) {
13
+ throw new AppError('CONFIG_ERROR', `${name} requires HTTPS without credentials, query parameters, or fragments. HTTP is allowed only on loopback.`);
14
+ }
15
+ return url;
16
+ }
@@ -0,0 +1 @@
1
+ export declare function installationHeaders(url: URL, env: NodeJS.ProcessEnv): Promise<Record<string, string>>;
@@ -0,0 +1,68 @@
1
+ import { createHash, randomUUID } from 'node:crypto';
2
+ import { link, mkdir, readFile, rename, unlink, writeFile } from 'node:fs/promises';
3
+ import { homedir } from 'node:os';
4
+ import { join, resolve } from 'node:path';
5
+ import { AppError } from './errors.js';
6
+ import { newIdentity, registration } from './identity.js';
7
+ function valid(value) {
8
+ return !!value && /^[a-f0-9]{8}-(?:[a-f0-9]{4}-){3}[a-f0-9]{12}$/.test(value.installationId) && /^[A-Za-z0-9_-]{43}$/.test(value.accessToken);
9
+ }
10
+ export async function installationHeaders(url, env) {
11
+ const directory = join(resolve(env.GEORANKER_STATE_DIR || join(homedir(), '.config', 'georanker-search-mcp')), 'client', createHash('sha256').update(url.origin).digest('hex'));
12
+ await mkdir(directory, { recursive: true, mode: 0o700 });
13
+ const identityPath = join(directory, 'identity.json');
14
+ let identity;
15
+ try {
16
+ identity = JSON.parse(await readFile(identityPath, 'utf8'));
17
+ }
18
+ catch (error) {
19
+ if (error.code !== 'ENOENT')
20
+ throw new AppError('STATE_ERROR', 'Cannot read the saved installation identity. Restore its backup; it was not replaced.');
21
+ // Link a completely written file without overwriting another launch's identity.
22
+ const temporary = `${identityPath}.${randomUUID()}.tmp`;
23
+ await writeFile(temporary, JSON.stringify(newIdentity(env.GEORANKER_DEVICE_FINGERPRINT)), { mode: 0o600, flag: 'wx' });
24
+ try {
25
+ await link(temporary, identityPath);
26
+ }
27
+ catch (linkError) {
28
+ if (linkError.code !== 'EEXIST')
29
+ throw linkError;
30
+ }
31
+ finally {
32
+ await unlink(temporary);
33
+ }
34
+ identity = JSON.parse(await readFile(identityPath, 'utf8'));
35
+ }
36
+ const path = join(directory, 'credentials.json');
37
+ let credentials;
38
+ try {
39
+ credentials = JSON.parse(await readFile(path, 'utf8'));
40
+ if (!valid(credentials))
41
+ throw new Error();
42
+ }
43
+ catch (error) {
44
+ if (error.code !== 'ENOENT')
45
+ throw new AppError('STATE_ERROR', 'Cannot read saved installation credentials. They were not replaced.');
46
+ let response;
47
+ try {
48
+ response = await fetch(new URL('/v1/installations', url), { method: 'POST', redirect: 'error', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(registration(identity, url.origin)), signal: AbortSignal.timeout(15_000) });
49
+ }
50
+ catch {
51
+ throw new AppError('ENROLLMENT_FAILED', 'Cannot register this installation. Check internet access and the system clock, then launch again. No paid request was made.');
52
+ }
53
+ if (!response.ok)
54
+ throw new AppError('ENROLLMENT_FAILED', `Registration returned HTTP ${response.status}. ${response.status === 429 ? 'Pilot signup limits were reached. Try again later.' : 'Contact the pilot operator if this persists.'} No paid request was made.`);
55
+ const text = await response.text();
56
+ if (text.length > 4096)
57
+ throw new AppError('ENROLLMENT_FAILED', 'Unexpected registration response.');
58
+ credentials = JSON.parse(text);
59
+ if (!valid(credentials))
60
+ throw new AppError('ENROLLMENT_FAILED', 'Invalid registration response.');
61
+ const temporary = `${path}.${randomUUID()}.tmp`;
62
+ await writeFile(temporary, JSON.stringify(credentials), { mode: 0o600, flag: 'wx' });
63
+ await rename(temporary, path);
64
+ }
65
+ if (!/^[a-f0-9]{64}$/.test(identity.deviceFingerprint))
66
+ throw new AppError('STATE_ERROR', 'Invalid saved device identity.');
67
+ return { Authorization: `Bearer ${credentials.accessToken}`, 'X-GeoRanker-Installation-Id': credentials.installationId, 'X-GeoRanker-Device-Fingerprint': identity.deviceFingerprint };
68
+ }
@@ -0,0 +1,6 @@
1
+ export declare class AppError extends Error {
2
+ readonly code: string;
3
+ readonly retryAfterSeconds?: number | undefined;
4
+ readonly details?: Record<string, unknown> | undefined;
5
+ constructor(code: string, message: string, retryAfterSeconds?: number | undefined, details?: Record<string, unknown> | undefined);
6
+ }
@@ -0,0 +1,12 @@
1
+ export class AppError extends Error {
2
+ code;
3
+ retryAfterSeconds;
4
+ details;
5
+ constructor(code, message, retryAfterSeconds, details) {
6
+ super(message);
7
+ this.code = code;
8
+ this.retryAfterSeconds = retryAfterSeconds;
9
+ this.details = details;
10
+ this.name = 'AppError';
11
+ }
12
+ }
@@ -0,0 +1,15 @@
1
+ export declare function deviceFingerprint(): string;
2
+ export interface InstallationIdentity {
3
+ privateKey: string;
4
+ publicKey: string;
5
+ deviceFingerprint: string;
6
+ }
7
+ export interface Registration {
8
+ publicKey: string;
9
+ deviceFingerprint: string;
10
+ timestamp: number;
11
+ nonce: string;
12
+ signature: string;
13
+ }
14
+ export declare function newIdentity(fingerprint?: string): InstallationIdentity;
15
+ export declare function registration(identity: InstallationIdentity, origin: string): Registration;
@@ -0,0 +1,21 @@
1
+ import { createHash, createPrivateKey, generateKeyPairSync, randomBytes, sign } from 'node:crypto';
2
+ import { arch, cpus, hostname, platform } from 'node:os';
3
+ import { AppError } from './errors.js';
4
+ export function deviceFingerprint() {
5
+ // Supporting, self-reported signal only. This is not hardware attestation.
6
+ return createHash('sha256').update(JSON.stringify({ hostname: hostname(), platform: platform(), arch: arch(), cpu: cpus()[0]?.model || '' })).digest('hex');
7
+ }
8
+ export function newIdentity(fingerprint = deviceFingerprint()) {
9
+ if (!/^[a-f0-9]{64}$/.test(fingerprint))
10
+ throw new AppError('CONFIG_ERROR', 'Device fingerprint must be a lowercase SHA-256 hex string.');
11
+ const pair = generateKeyPairSync('ed25519');
12
+ return { privateKey: pair.privateKey.export({ format: 'der', type: 'pkcs8' }).toString('base64url'), publicKey: pair.publicKey.export({ format: 'der', type: 'spki' }).toString('base64url'), deviceFingerprint: fingerprint };
13
+ }
14
+ function message(input, origin) {
15
+ return Buffer.from(JSON.stringify(['georanker-installation-v1', origin, input.publicKey, input.deviceFingerprint, input.timestamp, input.nonce]));
16
+ }
17
+ export function registration(identity, origin) {
18
+ const input = { publicKey: identity.publicKey, deviceFingerprint: identity.deviceFingerprint, timestamp: Date.now(), nonce: randomBytes(24).toString('base64url') };
19
+ const key = createPrivateKey({ key: Buffer.from(identity.privateKey, 'base64url'), format: 'der', type: 'pkcs8' });
20
+ return { ...input, signature: sign(null, message(input, origin), key).toString('base64url') };
21
+ }
@@ -0,0 +1,60 @@
1
+ /** Public MCP product identities and input contracts. No provider configuration belongs here. */
2
+ import type { SeoToolName } from './seo-contract.js';
3
+ export declare const SERVER_VERSION = "0.14.0";
4
+ export type ProductProfile = 'combined' | 'seo' | 'scraping';
5
+ export type JobKind = 'search' | 'page';
6
+ export declare const PRODUCT_PROFILES: {
7
+ readonly combined: {
8
+ readonly name: "georanker-search-mcp";
9
+ readonly title: "GeoRanker Search MCP";
10
+ readonly endpoint: "/mcp";
11
+ readonly searchTool: "search";
12
+ readonly fetchTool: "fetch_page";
13
+ readonly resultTool: "get_search_result";
14
+ };
15
+ readonly seo: {
16
+ readonly name: "georanker-seo-mcp";
17
+ readonly title: "SEO & SERP MCP by GeoRanker";
18
+ readonly endpoint: "/seo/mcp";
19
+ readonly searchTool: "search_serps";
20
+ readonly resultTool: "get_serp_result";
21
+ };
22
+ readonly scraping: {
23
+ readonly name: "georanker-web-scraping-mcp";
24
+ readonly title: "Web Scraping MCP by GeoRanker";
25
+ readonly endpoint: "/scraping/mcp";
26
+ readonly fetchTool: "fetch_page";
27
+ readonly resultTool: "get_fetch_result";
28
+ };
29
+ };
30
+ export declare function profileForEndpoint(path: string): ProductProfile | undefined;
31
+ export interface SearchInput {
32
+ query: string;
33
+ limit?: number;
34
+ pages?: number;
35
+ region?: string;
36
+ searchEngine?: 'google' | 'bing' | 'yahoo';
37
+ language?: string;
38
+ waitMs?: number;
39
+ forceLive?: boolean;
40
+ }
41
+ export interface SearchResultInput {
42
+ jobId: string;
43
+ waitMs?: number;
44
+ limit?: number;
45
+ format?: 'text' | 'html';
46
+ /** Internal constraint set by the server profile, never a caller-controlled tool argument. */
47
+ expectedKind?: JobKind;
48
+ }
49
+ export interface FetchPageInput {
50
+ url: string;
51
+ format?: 'text' | 'html';
52
+ waitMs?: number;
53
+ forceLive?: boolean;
54
+ }
55
+ export interface SearchServiceLike {
56
+ seoReport?(name: SeoToolName, input: Record<string, unknown>, signal?: AbortSignal): Promise<object>;
57
+ search(input: SearchInput, signal?: AbortSignal): Promise<object>;
58
+ fetchPage(input: FetchPageInput, signal?: AbortSignal): Promise<object>;
59
+ getSearchResult(input: SearchResultInput, signal?: AbortSignal): Promise<object>;
60
+ }
@@ -0,0 +1,29 @@
1
+ export const SERVER_VERSION = '0.14.0';
2
+ export const PRODUCT_PROFILES = {
3
+ combined: {
4
+ name: 'georanker-search-mcp',
5
+ title: 'GeoRanker Search MCP',
6
+ endpoint: '/mcp',
7
+ searchTool: 'search',
8
+ fetchTool: 'fetch_page',
9
+ resultTool: 'get_search_result',
10
+ },
11
+ seo: {
12
+ name: 'georanker-seo-mcp',
13
+ title: 'SEO & SERP MCP by GeoRanker',
14
+ endpoint: '/seo/mcp',
15
+ searchTool: 'search_serps',
16
+ resultTool: 'get_serp_result',
17
+ },
18
+ scraping: {
19
+ name: 'georanker-web-scraping-mcp',
20
+ title: 'Web Scraping MCP by GeoRanker',
21
+ endpoint: '/scraping/mcp',
22
+ fetchTool: 'fetch_page',
23
+ resultTool: 'get_fetch_result',
24
+ },
25
+ };
26
+ export function profileForEndpoint(path) {
27
+ return Object.keys(PRODUCT_PROFILES)
28
+ .find(profile => PRODUCT_PROFILES[profile].endpoint === path);
29
+ }
@@ -0,0 +1,3 @@
1
+ export declare const CLIENT_PROFILE: "seo";
2
+ export declare const CLIENT_REPOSITORY: "georanker/georanker-seo-mcp";
3
+ export declare const CLIENT_VERSION: "0.14.0";
@@ -0,0 +1,4 @@
1
+ // Fixed product identity; users install the other package for the other audience.
2
+ export const CLIENT_PROFILE = 'seo';
3
+ export const CLIENT_REPOSITORY = 'georanker/georanker-seo-mcp';
4
+ export const CLIENT_VERSION = '0.14.0';
@@ -0,0 +1,20 @@
1
+ export { deviceFingerprint } from './identity.js';
2
+ import { type ProductProfile, type SearchServiceLike, type SearchInput, type SearchResultInput, type FetchPageInput } from './product-contract.js';
3
+ import { type SeoToolName } from './seo-contract.js';
4
+ export declare const PILOT_MCP_URL = "https://grmcp.ibl.ro/mcp";
5
+ export declare class RemoteService implements SearchServiceLike {
6
+ private readonly env;
7
+ private readonly profile;
8
+ private connection?;
9
+ private readonly url;
10
+ private headers?;
11
+ constructor(env: NodeJS.ProcessEnv, profile?: ProductProfile);
12
+ private connect;
13
+ initialize(): Promise<void>;
14
+ private call;
15
+ search(input: SearchInput, signal?: AbortSignal): Promise<object>;
16
+ seoReport(name: SeoToolName, input: Record<string, unknown>, signal?: AbortSignal): Promise<object>;
17
+ fetchPage(input: FetchPageInput, signal?: AbortSignal): Promise<object>;
18
+ getSearchResult(input: SearchResultInput, signal?: AbortSignal): Promise<object>;
19
+ close(): Promise<void>;
20
+ }