@rankcli/mcp-server 0.0.1 → 0.0.3
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 +41 -90
- package/dist/index.js +160 -0
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -1,24 +1,12 @@
|
|
|
1
1
|
# @rankcli/mcp-server
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
The industry's first SEO tool accessible via Model Context Protocol. Let Claude, GPT, and other AI assistants run comprehensive SEO audits directly.
|
|
6
|
-
|
|
7
|
-
## What is MCP?
|
|
8
|
-
|
|
9
|
-
[Model Context Protocol](https://modelcontextprotocol.io) (MCP) is Anthropic's standard for connecting AI assistants to external tools. This server exposes RankCLI's full SEO analysis capabilities to AI.
|
|
10
|
-
|
|
11
|
-
## Installation
|
|
3
|
+
Free, local SEO + GEO (AI-search-citation) analysis as an MCP tool. No signup, no API key, nothing sent to RankCLI's servers — it runs entirely inside your MCP host (Claude Code, Claude Desktop, Cursor, etc.) against HTML you already have or that your host fetches for you.
|
|
12
4
|
|
|
13
5
|
```bash
|
|
14
|
-
|
|
6
|
+
npx @rankcli/mcp-server
|
|
15
7
|
```
|
|
16
8
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
### Claude Desktop
|
|
20
|
-
|
|
21
|
-
Add to your Claude Desktop config (`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS):
|
|
9
|
+
**Claude Desktop config** (`~/Library/Application Support/Claude/claude_desktop_config.json`):
|
|
22
10
|
|
|
23
11
|
```json
|
|
24
12
|
{
|
|
@@ -31,90 +19,53 @@ Add to your Claude Desktop config (`~/Library/Application Support/Claude/claude_
|
|
|
31
19
|
}
|
|
32
20
|
```
|
|
33
21
|
|
|
34
|
-
Restart Claude Desktop. You'll now have SEO tools available.
|
|
35
|
-
|
|
36
|
-
### Other MCP Clients
|
|
37
|
-
|
|
38
|
-
Run the server directly:
|
|
39
|
-
|
|
40
|
-
```bash
|
|
41
|
-
npx @rankcli/mcp-server
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
The server communicates via stdio using JSON-RPC.
|
|
45
|
-
|
|
46
22
|
## Available Tools
|
|
47
23
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
### `seo_structured_data`
|
|
64
|
-
**Validate JSON-LD schemas.** Check Article, Product, FAQ, HowTo, LocalBusiness, BreadcrumbList.
|
|
24
|
+
| Tool | Description |
|
|
25
|
+
|------|-------------|
|
|
26
|
+
| `seo_analyze` | Full SEO analysis: GEO, Core Web Vitals, structured data, security headers, mobile, images, internal linking |
|
|
27
|
+
| `seo_geo_check` | AI search visibility (GEO) — crawler access, LLM content signals, citation readiness |
|
|
28
|
+
| `seo_robots_ai` | Which AI crawlers (GPTBot, ClaudeBot, PerplexityBot, etc.) your robots.txt allows or blocks |
|
|
29
|
+
| `seo_generate_robots` | Generate an AI-crawler-friendly robots.txt |
|
|
30
|
+
| `seo_core_web_vitals` | Estimate LCP, CLS, INP, TTFB from HTML |
|
|
31
|
+
| `seo_structured_data` | Validate JSON-LD / Schema.org markup |
|
|
32
|
+
| `seo_generate_schema` | Generate a JSON-LD template for a page type |
|
|
33
|
+
| `seo_security_headers` | Grade HTTPS, HSTS, CSP, and related headers |
|
|
34
|
+
| `seo_generate_security_headers` | Generate recommended security header config |
|
|
35
|
+
| `seo_images` | Alt text, dimensions, formats, lazy loading |
|
|
36
|
+
| `seo_internal_links` | Anchor text quality, orphan-page risk |
|
|
37
|
+
| `seo_mobile` | Viewport, touch targets, PWA readiness |
|
|
38
|
+
| `seo_ai_crawlers` | Reference list of known AI crawler user agents |
|
|
65
39
|
|
|
66
|
-
|
|
67
|
-
**Generate schema templates.** For article, product, faq, local-business, website.
|
|
40
|
+
## Example
|
|
68
41
|
|
|
69
|
-
|
|
70
|
-
**Analyze security headers.** HTTPS, HSTS, CSP with A+ to F grading.
|
|
42
|
+
Just ask Claude:
|
|
71
43
|
|
|
72
|
-
|
|
73
|
-
**Generate recommended security headers.**
|
|
44
|
+
> "Run an SEO audit on https://example.com"
|
|
74
45
|
|
|
75
|
-
|
|
76
|
-
**Image optimization analysis.** Alt text, dimensions, formats, lazy loading.
|
|
46
|
+
Your MCP host fetches the page and Claude calls these tools directly — nothing round-trips through RankCLI.
|
|
77
47
|
|
|
78
|
-
|
|
79
|
-
**Internal linking analysis.** Anchor text quality, orphan detection.
|
|
48
|
+
## Want more?
|
|
80
49
|
|
|
81
|
-
|
|
82
|
-
**Mobile SEO analysis.** Viewport, touch targets, PWA readiness.
|
|
50
|
+
This local server works from HTML/robots.txt you or your MCP host supply. For **URL-only tools that fetch and crawl for you** (`seo_audit`, `seo_geo_check`, `seo_robots_check`, `seo_compare`), plus the newer Cloudflare AI-crawler-gating compliance check, CI/CD scheduling, and auto-fix PRs, see the hosted endpoint and paid tiers:
|
|
83
51
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
> "What structured data should I add to my blog posts?"
|
|
98
|
-
|
|
99
|
-
Claude will use the appropriate tools to analyze and respond.
|
|
100
|
-
|
|
101
|
-
## Why This Matters
|
|
102
|
-
|
|
103
|
-
1. **AI doing SEO**: Your AI assistant can now audit sites, generate fixes, and monitor SEO.
|
|
104
|
-
2. **Meta-optimization**: Make sure YOUR site is visible to AI crawlers—checked by AI.
|
|
105
|
-
3. **Developer workflow**: Ask Claude to audit during code review or deployment.
|
|
106
|
-
|
|
107
|
-
## Requirements
|
|
108
|
-
|
|
109
|
-
- Node.js 18+
|
|
110
|
-
- An MCP-compatible AI assistant (Claude Desktop, etc.)
|
|
111
|
-
|
|
112
|
-
## Related
|
|
52
|
+
```json
|
|
53
|
+
{
|
|
54
|
+
"mcpServers": {
|
|
55
|
+
"rankcli-hosted": {
|
|
56
|
+
"url": "https://rankcli-audit-worker.fly.dev/mcp",
|
|
57
|
+
"headers": {
|
|
58
|
+
"Authorization": "Bearer YOUR_API_KEY"
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
```
|
|
113
64
|
|
|
114
|
-
|
|
115
|
-
- [RankCLI Website](https://rankcli.dev) - Full documentation
|
|
116
|
-
- [MCP Protocol](https://modelcontextprotocol.io) - Model Context Protocol
|
|
65
|
+
Get a free API key at [rankcli.dev/dashboard](https://rankcli.dev/dashboard).
|
|
117
66
|
|
|
118
|
-
##
|
|
67
|
+
## Links
|
|
119
68
|
|
|
120
|
-
|
|
69
|
+
- **Main package**: [@rankcli/cli](https://www.npmjs.com/package/@rankcli/cli)
|
|
70
|
+
- **Docs**: [rankcli.dev/docs](https://rankcli.dev/docs)
|
|
71
|
+
- **Dashboard**: [rankcli.dev/dashboard](https://rankcli.dev/dashboard)
|
package/dist/index.js
CHANGED
|
@@ -7,7 +7,10 @@ import {
|
|
|
7
7
|
CallToolRequestSchema,
|
|
8
8
|
ListToolsRequestSchema
|
|
9
9
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
10
|
+
import Conf from "conf";
|
|
10
11
|
import { analyzers } from "@rankcli/agent-runtime";
|
|
12
|
+
var SUPABASE_URL = process.env.RANKCLI_SUPABASE_URL || "https://bspljbxwbjiqueeyzyat.supabase.co";
|
|
13
|
+
var localConfig = new Conf({ projectName: "rankcli" });
|
|
11
14
|
var TOOLS = [
|
|
12
15
|
{
|
|
13
16
|
name: "seo_analyze",
|
|
@@ -269,6 +272,22 @@ Critical for visibility in ChatGPT, Perplexity, Claude, and Gemini responses.`,
|
|
|
269
272
|
type: "object",
|
|
270
273
|
properties: {}
|
|
271
274
|
}
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
name: "rankcli_connect",
|
|
278
|
+
description: `Link this AI assistant to a RankCLI account (rankcli.dev). This is the only tool in this server that contacts rankcli.dev, and only when you call it - every other tool runs fully locally. Connecting unlocks GitHub auto-fix PRs, scheduled monitoring, and a dashboard with history across audits; it does not change how the other tools work. Opens a browser tab for the user to approve (or reuses an existing session if already logged in). Safe to call anytime - if already connected, it just reports who's connected.`,
|
|
279
|
+
inputSchema: {
|
|
280
|
+
type: "object",
|
|
281
|
+
properties: {}
|
|
282
|
+
}
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
name: "rankcli_disconnect",
|
|
286
|
+
description: `Unlink this AI assistant from its RankCLI account. Only affects this local connection - does not delete the RankCLI account or revoke other API keys.`,
|
|
287
|
+
inputSchema: {
|
|
288
|
+
type: "object",
|
|
289
|
+
properties: {}
|
|
290
|
+
}
|
|
272
291
|
}
|
|
273
292
|
];
|
|
274
293
|
var server = new Server(
|
|
@@ -442,6 +461,27 @@ ${Object.entries(headers).map(([k, v]) => `**${k}:**
|
|
|
442
461
|
]
|
|
443
462
|
};
|
|
444
463
|
}
|
|
464
|
+
case "rankcli_connect": {
|
|
465
|
+
const result = await connectToRankCLI();
|
|
466
|
+
return {
|
|
467
|
+
content: [{ type: "text", text: result.text }],
|
|
468
|
+
...result.isError ? { isError: true } : {}
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
case "rankcli_disconnect": {
|
|
472
|
+
const wasConnected = !!localConfig.get("apiKey");
|
|
473
|
+
localConfig.delete("apiKey");
|
|
474
|
+
localConfig.delete("apiKeyName");
|
|
475
|
+
localConfig.delete("email");
|
|
476
|
+
return {
|
|
477
|
+
content: [
|
|
478
|
+
{
|
|
479
|
+
type: "text",
|
|
480
|
+
text: wasConnected ? "Disconnected. This assistant no longer has access to your RankCLI dashboard." : "Not connected - nothing to do."
|
|
481
|
+
}
|
|
482
|
+
]
|
|
483
|
+
};
|
|
484
|
+
}
|
|
445
485
|
default:
|
|
446
486
|
return {
|
|
447
487
|
content: [{ type: "text", text: `Unknown tool: ${name}` }],
|
|
@@ -460,6 +500,124 @@ ${Object.entries(headers).map(([k, v]) => `**${k}:**
|
|
|
460
500
|
};
|
|
461
501
|
}
|
|
462
502
|
});
|
|
503
|
+
var BRIDGE_FOOTER = `
|
|
504
|
+
---
|
|
505
|
+
*This ran locally with no signup. For GitHub auto-fix PRs, scheduled monitoring, and a dashboard with history across audits, call the rankcli_connect tool or create a free account at [rankcli.dev](https://rankcli.dev).*`;
|
|
506
|
+
async function pollConnectStatus(connectId, timeoutMs) {
|
|
507
|
+
const deadline = Date.now() + timeoutMs;
|
|
508
|
+
while (Date.now() < deadline) {
|
|
509
|
+
await new Promise((resolve) => setTimeout(resolve, 2e3));
|
|
510
|
+
try {
|
|
511
|
+
const res = await fetch(`${SUPABASE_URL}/functions/v1/mcp-connect-status`, {
|
|
512
|
+
method: "POST",
|
|
513
|
+
headers: { "Content-Type": "application/json" },
|
|
514
|
+
body: JSON.stringify({ connectId })
|
|
515
|
+
});
|
|
516
|
+
const data = await res.json();
|
|
517
|
+
if (data.status === "claimed") {
|
|
518
|
+
return { email: data.email, apiKey: data.apiKey, apiKeyName: data.apiKeyName };
|
|
519
|
+
}
|
|
520
|
+
if (data.status === "expired" || data.status === "not_found") {
|
|
521
|
+
return null;
|
|
522
|
+
}
|
|
523
|
+
} catch {
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
return null;
|
|
527
|
+
}
|
|
528
|
+
function backgroundAwaitConnect(connectId) {
|
|
529
|
+
pollConnectStatus(connectId, 5 * 6e4).then(async (claimed) => {
|
|
530
|
+
if (claimed) {
|
|
531
|
+
localConfig.set("apiKey", claimed.apiKey);
|
|
532
|
+
localConfig.set("apiKeyName", claimed.apiKeyName);
|
|
533
|
+
localConfig.set("email", claimed.email);
|
|
534
|
+
if (localConfig.get("pendingConnectId") === connectId) clearPendingConnect();
|
|
535
|
+
}
|
|
536
|
+
await server.createElicitationCompletionNotifier(connectId)().catch(() => {
|
|
537
|
+
});
|
|
538
|
+
}).catch((err) => {
|
|
539
|
+
console.error("rankcli_connect: background poll failed:", err);
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
function clearPendingConnect() {
|
|
543
|
+
localConfig.delete("pendingConnectId");
|
|
544
|
+
localConfig.delete("pendingConnectUrl");
|
|
545
|
+
localConfig.delete("pendingConnectExpiresAt");
|
|
546
|
+
}
|
|
547
|
+
async function connectToRankCLI() {
|
|
548
|
+
const existingKey = localConfig.get("apiKey");
|
|
549
|
+
if (existingKey) {
|
|
550
|
+
const email = localConfig.get("email") || "your account";
|
|
551
|
+
return { text: `Already connected as ${email}. Run rankcli_disconnect first to switch accounts.` };
|
|
552
|
+
}
|
|
553
|
+
const pendingId = localConfig.get("pendingConnectId");
|
|
554
|
+
const pendingUrl = localConfig.get("pendingConnectUrl");
|
|
555
|
+
const pendingExpiresAt = localConfig.get("pendingConnectExpiresAt");
|
|
556
|
+
if (pendingId && pendingUrl && pendingExpiresAt) {
|
|
557
|
+
if (Date.now() >= pendingExpiresAt) {
|
|
558
|
+
clearPendingConnect();
|
|
559
|
+
} else {
|
|
560
|
+
const claimed = await pollConnectStatus(pendingId, 3e3);
|
|
561
|
+
if (claimed) {
|
|
562
|
+
localConfig.set("apiKey", claimed.apiKey);
|
|
563
|
+
localConfig.set("apiKeyName", claimed.apiKeyName);
|
|
564
|
+
localConfig.set("email", claimed.email);
|
|
565
|
+
clearPendingConnect();
|
|
566
|
+
return { text: `Connected as ${claimed.email}! Your dashboard, GitHub auto-fix PRs, and audit history are now linked to this AI assistant.` };
|
|
567
|
+
}
|
|
568
|
+
const minutesLeft = Math.max(1, Math.round((pendingExpiresAt - Date.now()) / 6e4));
|
|
569
|
+
return {
|
|
570
|
+
text: `Still waiting for approval. Open this link if you haven't yet, then run rankcli_connect again:
|
|
571
|
+
|
|
572
|
+
${pendingUrl}
|
|
573
|
+
|
|
574
|
+
Expires in ${minutesLeft} minute${minutesLeft === 1 ? "" : "s"}.`
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
let startData;
|
|
579
|
+
try {
|
|
580
|
+
const startRes = await fetch(`${SUPABASE_URL}/functions/v1/mcp-connect-start`, { method: "POST" });
|
|
581
|
+
if (!startRes.ok) throw new Error(`status ${startRes.status}`);
|
|
582
|
+
startData = await startRes.json();
|
|
583
|
+
} catch (err) {
|
|
584
|
+
return {
|
|
585
|
+
text: `Could not reach rankcli.dev to start the connection (${err instanceof Error ? err.message : "network error"}). Try again in a moment.`,
|
|
586
|
+
isError: true
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
const { connectId, url, expiresInSeconds } = startData;
|
|
590
|
+
const expiresMinutes = Math.round(expiresInSeconds / 60);
|
|
591
|
+
localConfig.set("pendingConnectId", connectId);
|
|
592
|
+
localConfig.set("pendingConnectUrl", url);
|
|
593
|
+
localConfig.set("pendingConnectExpiresAt", Date.now() + expiresInSeconds * 1e3);
|
|
594
|
+
const supportsUrlElicitation = !!server.getClientCapabilities()?.elicitation?.url;
|
|
595
|
+
if (supportsUrlElicitation) {
|
|
596
|
+
try {
|
|
597
|
+
const result = await server.elicitInput({
|
|
598
|
+
mode: "url",
|
|
599
|
+
message: "Connect your RankCLI account to enable GitHub auto-fix PRs, scheduled monitoring, and a dashboard with audit history.",
|
|
600
|
+
url,
|
|
601
|
+
elicitationId: connectId
|
|
602
|
+
});
|
|
603
|
+
if (result.action === "accept") {
|
|
604
|
+
backgroundAwaitConnect(connectId);
|
|
605
|
+
return { text: "Opening rankcli.dev to connect your account - I'll let you know as soon as it's done." };
|
|
606
|
+
}
|
|
607
|
+
clearPendingConnect();
|
|
608
|
+
return { text: "Connection cancelled." };
|
|
609
|
+
} catch (err) {
|
|
610
|
+
console.error("rankcli_connect: elicitation failed, falling back to link:", err);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
return {
|
|
614
|
+
text: `Open this link to connect your RankCLI account:
|
|
615
|
+
|
|
616
|
+
${url}
|
|
617
|
+
|
|
618
|
+
Then run rankcli_connect again to finish (link expires in ${expiresMinutes} minutes).`
|
|
619
|
+
};
|
|
620
|
+
}
|
|
463
621
|
function formatComprehensiveResult(result) {
|
|
464
622
|
const criticalIssues = result.allIssues.filter((i) => i.severity === "critical");
|
|
465
623
|
const warnings = result.allIssues.filter((i) => i.severity === "warning");
|
|
@@ -495,6 +653,7 @@ ${warnings.slice(0, 5).map((i) => `- **${i.title}:** ${i.description}`).join("\n
|
|
|
495
653
|
## Priority Recommendations
|
|
496
654
|
|
|
497
655
|
${result.prioritizedRecommendations.map((r, i) => `${i + 1}. ${r}`).join("\n")}
|
|
656
|
+
${BRIDGE_FOOTER}
|
|
498
657
|
`;
|
|
499
658
|
}
|
|
500
659
|
function formatGEOResult(result) {
|
|
@@ -535,6 +694,7 @@ Trust Signals: ${result.citationReadiness.trustSignals.join(", ") || "None detec
|
|
|
535
694
|
## Recommendations
|
|
536
695
|
|
|
537
696
|
${result.recommendations.map((r) => `- ${r}`).join("\n")}
|
|
697
|
+
${BRIDGE_FOOTER}
|
|
538
698
|
`;
|
|
539
699
|
}
|
|
540
700
|
function formatCWVResult(result) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rankcli/mcp-server",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"description": "MCP (Model Context Protocol) server for RankCLI SEO analysis",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -26,8 +26,9 @@
|
|
|
26
26
|
"author": "Integrallis",
|
|
27
27
|
"license": "MIT",
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
30
|
-
"@rankcli/agent-runtime": "^0.0.
|
|
29
|
+
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
30
|
+
"@rankcli/agent-runtime": "^0.0.17",
|
|
31
|
+
"conf": "^12.0.0"
|
|
31
32
|
},
|
|
32
33
|
"devDependencies": {
|
|
33
34
|
"tsup": "^8.0.0",
|