@rankcli/mcp-server 0.0.7 → 0.0.8

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
@@ -19,6 +19,18 @@ npx @rankcli/mcp-server
19
19
  }
20
20
  ```
21
21
 
22
+ **Prefer not to run anything locally?** The same tools are available at a free hosted HTTP endpoint, no npx and no signup:
23
+
24
+ ```json
25
+ {
26
+ "mcpServers": {
27
+ "rankcli": {
28
+ "url": "https://mcp.rankcli.dev/mcp/free"
29
+ }
30
+ }
31
+ }
32
+ ```
33
+
22
34
  ## Available Tools
23
35
 
24
36
  | Tool | Description |
@@ -59,7 +71,7 @@ This local server works from HTML/robots.txt you or your MCP host supply. For **
59
71
  {
60
72
  "mcpServers": {
61
73
  "rankcli-hosted": {
62
- "url": "https://rankcli-audit-worker.fly.dev/mcp",
74
+ "url": "https://mcp.rankcli.dev/mcp",
63
75
  "headers": {
64
76
  "Authorization": "Bearer YOUR_API_KEY"
65
77
  }
@@ -0,0 +1,684 @@
1
+ // src/server.ts
2
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
+ import {
4
+ CallToolRequestSchema,
5
+ ListToolsRequestSchema
6
+ } from "@modelcontextprotocol/sdk/types.js";
7
+ import { analyzers } from "@rankcli/agent-runtime";
8
+ async function fetchHtml(url) {
9
+ const res = await fetch(url, {
10
+ headers: { "User-Agent": "RankCLI/1.0 (+https://rankcli.dev)" }
11
+ });
12
+ if (!res.ok) {
13
+ throw new Error(`Fetching ${url} returned ${res.status} ${res.statusText}`);
14
+ }
15
+ return res.text();
16
+ }
17
+ var ANALYSIS_TOOLS = [
18
+ {
19
+ name: "seo_analyze",
20
+ description: `Run comprehensive SEO analysis on a webpage. Returns scores and issues for:
21
+ - GEO (AI Search Optimization) - Is the site visible to ChatGPT, Perplexity, Claude?
22
+ - Core Web Vitals (LCP, CLS, INP) - Performance estimates
23
+ - Structured Data - Schema.org validation
24
+ - Security Headers - HTTPS, HSTS, CSP
25
+ - Mobile SEO - Responsive design, touch targets
26
+ - Images - Alt text, formats, dimensions
27
+ - Internal Linking - Anchor text, orphan detection
28
+
29
+ Use this for a complete SEO audit.`,
30
+ inputSchema: {
31
+ type: "object",
32
+ properties: {
33
+ url: {
34
+ type: "string",
35
+ description: "URL of the page to analyze"
36
+ },
37
+ html: {
38
+ type: "string",
39
+ description: "HTML content of the page (optional if URL is provided)"
40
+ },
41
+ robotsTxt: {
42
+ type: "string",
43
+ description: "robots.txt content for AI crawler analysis (optional)"
44
+ }
45
+ },
46
+ required: ["url"]
47
+ }
48
+ },
49
+ {
50
+ name: "seo_geo_check",
51
+ description: `Check if a website is optimized for AI search engines (GEO - Generative Engine Optimization).
52
+
53
+ Analyzes:
54
+ - AI crawler access (GPTBot, ClaudeBot, PerplexityBot, etc.)
55
+ - robots.txt rules for AI crawlers
56
+ - JS rendering requirements (can AI crawlers see content?)
57
+ - Content structure for LLM consumption
58
+ - Citation readiness (trust signals)
59
+ - FAQ/entity extraction capability
60
+
61
+ Critical for visibility in ChatGPT, Perplexity, Claude, and Gemini responses.`,
62
+ inputSchema: {
63
+ type: "object",
64
+ properties: {
65
+ url: {
66
+ type: "string",
67
+ description: "URL of the page to analyze"
68
+ },
69
+ html: {
70
+ type: "string",
71
+ description: "HTML content of the page"
72
+ },
73
+ robotsTxt: {
74
+ type: "string",
75
+ description: "robots.txt content"
76
+ }
77
+ },
78
+ required: ["url"]
79
+ }
80
+ },
81
+ {
82
+ name: "seo_robots_ai",
83
+ description: `Analyze robots.txt for AI crawler permissions. Shows which AI crawlers (GPTBot, ClaudeBot, PerplexityBot, etc.) are allowed or blocked.`,
84
+ inputSchema: {
85
+ type: "object",
86
+ properties: {
87
+ robotsTxt: {
88
+ type: "string",
89
+ description: "Content of robots.txt file"
90
+ }
91
+ },
92
+ required: ["robotsTxt"]
93
+ }
94
+ },
95
+ {
96
+ name: "seo_generate_robots",
97
+ description: `Generate an AI-friendly robots.txt that allows all major AI crawlers (GPTBot, ClaudeBot, PerplexityBot, Google-Extended, etc.)`,
98
+ inputSchema: {
99
+ type: "object",
100
+ properties: {
101
+ siteUrl: {
102
+ type: "string",
103
+ description: "Base URL of the site (e.g., https://example.com)"
104
+ }
105
+ },
106
+ required: ["siteUrl"]
107
+ }
108
+ },
109
+ {
110
+ name: "seo_core_web_vitals",
111
+ description: `Estimate Core Web Vitals (LCP, CLS, INP, TTFB) from HTML analysis. Identifies issues like:
112
+ - Render-blocking resources
113
+ - Images without dimensions
114
+ - Large JavaScript bundles
115
+ - Missing preload hints`,
116
+ inputSchema: {
117
+ type: "object",
118
+ properties: {
119
+ url: {
120
+ type: "string",
121
+ description: "URL of the page"
122
+ },
123
+ html: {
124
+ type: "string",
125
+ description: "HTML content of the page"
126
+ }
127
+ },
128
+ required: ["url", "html"]
129
+ }
130
+ },
131
+ {
132
+ name: "seo_structured_data",
133
+ description: `Validate JSON-LD structured data (Schema.org). Checks for:
134
+ - Required properties per schema type
135
+ - Article, Product, FAQ, HowTo, LocalBusiness schemas
136
+ - Rich result eligibility
137
+ - Common mistakes`,
138
+ inputSchema: {
139
+ type: "object",
140
+ properties: {
141
+ url: {
142
+ type: "string",
143
+ description: "URL of the page"
144
+ },
145
+ html: {
146
+ type: "string",
147
+ description: "HTML content with JSON-LD"
148
+ }
149
+ },
150
+ required: ["url", "html"]
151
+ }
152
+ },
153
+ {
154
+ name: "seo_generate_schema",
155
+ description: `Generate JSON-LD structured data template for a page type (article, product, faq, local-business, website).`,
156
+ inputSchema: {
157
+ type: "object",
158
+ properties: {
159
+ pageType: {
160
+ type: "string",
161
+ enum: ["article", "product", "faq", "local-business", "website"],
162
+ description: "Type of page"
163
+ },
164
+ siteName: {
165
+ type: "string",
166
+ description: "Name of the website"
167
+ },
168
+ siteUrl: {
169
+ type: "string",
170
+ description: "Base URL of the website"
171
+ },
172
+ authorName: {
173
+ type: "string",
174
+ description: "Default author name (for articles)"
175
+ },
176
+ organizationName: {
177
+ type: "string",
178
+ description: "Organization name"
179
+ }
180
+ },
181
+ required: ["pageType", "siteName", "siteUrl"]
182
+ }
183
+ },
184
+ {
185
+ name: "seo_security_headers",
186
+ description: `Analyze security headers (HTTPS, HSTS, CSP, X-Frame-Options, etc.). Returns a security grade A+ through F.`,
187
+ inputSchema: {
188
+ type: "object",
189
+ properties: {
190
+ url: {
191
+ type: "string",
192
+ description: "URL of the page"
193
+ },
194
+ headers: {
195
+ type: "object",
196
+ description: "HTTP response headers",
197
+ additionalProperties: { type: "string" }
198
+ }
199
+ },
200
+ required: ["url", "headers"]
201
+ }
202
+ },
203
+ {
204
+ name: "seo_generate_security_headers",
205
+ description: `Generate recommended security headers configuration for a site.`,
206
+ inputSchema: {
207
+ type: "object",
208
+ properties: {
209
+ siteUrl: {
210
+ type: "string",
211
+ description: "Base URL of the site"
212
+ }
213
+ },
214
+ required: ["siteUrl"]
215
+ }
216
+ },
217
+ {
218
+ name: "seo_images",
219
+ description: `Analyze images for SEO and performance. Checks alt text, dimensions, formats (WebP/AVIF), lazy loading, and responsive images.`,
220
+ inputSchema: {
221
+ type: "object",
222
+ properties: {
223
+ url: {
224
+ type: "string",
225
+ description: "URL of the page"
226
+ },
227
+ html: {
228
+ type: "string",
229
+ description: "HTML content of the page"
230
+ }
231
+ },
232
+ required: ["url", "html"]
233
+ }
234
+ },
235
+ {
236
+ name: "seo_internal_links",
237
+ description: `Analyze internal linking structure. Checks anchor text quality, orphan page risk, link distribution, and suggests linking opportunities.`,
238
+ inputSchema: {
239
+ type: "object",
240
+ properties: {
241
+ url: {
242
+ type: "string",
243
+ description: "URL of the page"
244
+ },
245
+ html: {
246
+ type: "string",
247
+ description: "HTML content of the page"
248
+ }
249
+ },
250
+ required: ["url", "html"]
251
+ }
252
+ },
253
+ {
254
+ name: "seo_mobile",
255
+ description: `Analyze mobile SEO. Checks viewport, touch targets, font sizes, content width, PWA readiness.`,
256
+ inputSchema: {
257
+ type: "object",
258
+ properties: {
259
+ url: {
260
+ type: "string",
261
+ description: "URL of the page"
262
+ },
263
+ html: {
264
+ type: "string",
265
+ description: "HTML content of the page"
266
+ }
267
+ },
268
+ required: ["url", "html"]
269
+ }
270
+ },
271
+ {
272
+ name: "seo_ai_crawlers",
273
+ description: `Get information about all known AI crawlers (GPTBot, ClaudeBot, PerplexityBot, Google-Extended, etc.) including their user agents and purposes.`,
274
+ inputSchema: {
275
+ type: "object",
276
+ properties: {}
277
+ }
278
+ }
279
+ ];
280
+ var BRIDGE_FOOTER = `
281
+ ---
282
+ *This ran with no signup. For GitHub auto-fix PRs, scheduled monitoring, and a dashboard with history across audits, connect via the RankCLI CLI (\`rankcli login\`) or create a free account at [rankcli.dev](https://rankcli.dev).*`;
283
+ function formatComprehensiveResult(result) {
284
+ const criticalIssues = result.allIssues.filter((i) => i.severity === "critical");
285
+ const warnings = result.allIssues.filter((i) => i.severity === "warning");
286
+ return `# SEO Analysis Report
287
+
288
+ **URL:** ${result.url}
289
+ **Overall Score:** ${result.overallScore}/100
290
+ **Analyzed:** ${result.timestamp}
291
+
292
+ ## Scores
293
+
294
+ | Category | Score |
295
+ |----------|-------|
296
+ | GEO (AI Search) | ${result.grades.geo}/100 |
297
+ | Core Web Vitals | ${result.grades.coreWebVitals}/100 |
298
+ | Security | ${result.grades.security} |
299
+ | Structured Data | ${result.grades.structuredData}/100 |
300
+ | Images | ${result.grades.images}/100 |
301
+ | Internal Links | ${result.grades.internalLinking}/100 |
302
+ | Mobile SEO | ${result.grades.mobile}/100 |
303
+
304
+ ## Critical Issues (${criticalIssues.length})
305
+
306
+ ${criticalIssues.map((i) => `### \u274C ${i.title}
307
+ ${i.description}
308
+
309
+ **Fix:** ${i.howToFix}`).join("\n\n") || "None!"}
310
+
311
+ ## Warnings (${warnings.length})
312
+
313
+ ${warnings.slice(0, 5).map((i) => `- **${i.title}:** ${i.description}`).join("\n") || "None!"}
314
+
315
+ ## Priority Recommendations
316
+
317
+ ${result.prioritizedRecommendations.map((r, i) => `${i + 1}. ${r}`).join("\n")}
318
+ ${BRIDGE_FOOTER}
319
+ `;
320
+ }
321
+ function formatGEOResult(result) {
322
+ return `# GEO Analysis (AI Search Optimization)
323
+
324
+ **Score:** ${result.score}/100
325
+
326
+ ## AI Crawler Access
327
+
328
+ | Status | Crawlers |
329
+ |--------|----------|
330
+ | \u2705 Allowed | ${result.aiCrawlerAccess.allowedCrawlers.join(", ") || "None"} |
331
+ | \u274C Blocked | ${result.aiCrawlerAccess.blockedCrawlers.join(", ") || "None"} |
332
+
333
+ **Server-Side Rendered:** ${result.aiCrawlerAccess.serverSideRendered ? "\u2705 Yes" : "\u274C No"}
334
+ **JS Rendering Required:** ${result.aiCrawlerAccess.jsRenderingRequired ? "\u26A0\uFE0F Yes (AI crawlers may not see content)" : "\u2705 No"}
335
+
336
+ ## LLM Friendliness Scores
337
+
338
+ | Signal | Score |
339
+ |--------|-------|
340
+ | Content Clarity | ${result.llmSignals.contentClarity}/100 |
341
+ | Fact Density | ${result.llmSignals.factDensity}/100 |
342
+ | Structure Quality | ${result.llmSignals.structureQuality}/100 |
343
+ | Citation Quality | ${result.llmSignals.citationQuality}/100 |
344
+
345
+ ## Content Structure
346
+
347
+ - Structured Data: ${result.contentStructure.hasStructuredData ? "\u2705" : "\u274C"}
348
+ - FAQ Schema: ${result.contentStructure.hasFAQSchema ? "\u2705" : "\u274C"}
349
+ - Article Schema: ${result.contentStructure.hasArticleSchema ? "\u2705" : "\u274C"}
350
+ - Heading Hierarchy: ${result.contentStructure.headingHierarchy}
351
+
352
+ ## Citation Readiness
353
+
354
+ Trust Signals: ${result.citationReadiness.trustSignals.join(", ") || "None detected"}
355
+
356
+ ## Recommendations
357
+
358
+ ${result.recommendations.map((r) => `- ${r}`).join("\n")}
359
+ ${BRIDGE_FOOTER}
360
+ `;
361
+ }
362
+ function formatCWVResult(result) {
363
+ const emoji = (est) => est === "good" ? "\u{1F7E2}" : est === "needs-improvement" ? "\u{1F7E1}" : "\u{1F534}";
364
+ return `# Core Web Vitals Estimate
365
+
366
+ **Overall Score:** ${result.overallScore}/100
367
+
368
+ | Metric | Estimate | Issues |
369
+ |--------|----------|--------|
370
+ | LCP (Largest Contentful Paint) | ${emoji(result.lcp.estimate)} ${result.lcp.estimate} | ${result.lcp.issues.length} |
371
+ | CLS (Cumulative Layout Shift) | ${emoji(result.cls.estimate)} ${result.cls.estimate} | ${result.cls.issues.length} |
372
+ | INP (Interaction to Next Paint) | ${emoji(result.inp.estimate)} ${result.inp.estimate} | ${result.inp.issues.length} |
373
+ | TTFB (Time to First Byte) | ${emoji(result.ttfb.estimate)} ${result.ttfb.estimate} | ${result.ttfb.issues.length} |
374
+
375
+ ## Issues
376
+
377
+ ${result.issues.map((i) => `- **${i.title}:** ${i.howToFix}`).join("\n") || "No critical issues!"}
378
+ `;
379
+ }
380
+ function formatStructuredDataResult(result) {
381
+ return `# Structured Data Analysis
382
+
383
+ **Score:** ${result.score}/100
384
+ **Schemas Found:** ${result.schemas.length}
385
+
386
+ ## Schema Types
387
+
388
+ | Type | Valid | Errors |
389
+ |------|-------|--------|
390
+ ${result.schemas.map((s) => `| ${s.type} | ${s.isValid ? "\u2705" : "\u274C"} | ${s.errors.join("; ") || "-"} |`).join("\n") || "| None found | - | - |"}
391
+
392
+ ## Rich Result Eligibility
393
+
394
+ - Organization: ${result.hasOrganization ? "\u2705" : "\u274C"}
395
+ - WebSite: ${result.hasWebSite ? "\u2705" : "\u274C"}
396
+ - Breadcrumb: ${result.hasBreadcrumb ? "\u2705" : "\u274C"}
397
+ - Article: ${result.hasArticle ? "\u2705" : "\u274C"}
398
+ - Product: ${result.hasProduct ? "\u2705" : "\u274C"}
399
+ - FAQ: ${result.hasFAQ ? "\u2705" : "\u274C"}
400
+ - HowTo: ${result.hasHowTo ? "\u2705" : "\u274C"}
401
+
402
+ ## Recommendations
403
+
404
+ ${result.recommendations.map((r) => `- ${r}`).join("\n")}
405
+ `;
406
+ }
407
+ function formatSecurityResult(result) {
408
+ return `# Security Headers Analysis
409
+
410
+ **Grade:** ${result.grade}
411
+ **Score:** ${result.score}/100
412
+
413
+ ## Headers
414
+
415
+ | Header | Status |
416
+ |--------|--------|
417
+ | HTTPS | ${result.https.enabled ? "\u2705" : "\u274C"} |
418
+ | HSTS | ${result.https.hasHSTS ? "\u2705" : "\u274C"} |
419
+ | CSP | ${result.contentSecurity.hasCSP ? "\u2705" : "\u274C"} |
420
+ | X-Frame-Options | ${result.frameOptions.hasXFrameOptions ? "\u2705" : "\u274C"} |
421
+ | X-Content-Type-Options | ${result.contentTypeOptions.hasXContentTypeOptions ? "\u2705" : "\u274C"} |
422
+ | Referrer-Policy | ${result.referrerPolicy.hasReferrerPolicy ? "\u2705" : "\u274C"} |
423
+ | Permissions-Policy | ${result.permissionsPolicy.hasPermissionsPolicy ? "\u2705" : "\u274C"} |
424
+
425
+ ## Issues
426
+
427
+ ${result.issues.map((i) => `- **${i.title}:** ${i.howToFix}`).join("\n") || "No issues!"}
428
+ `;
429
+ }
430
+ function formatImagesResult(result) {
431
+ return `# Image Analysis
432
+
433
+ **Score:** ${result.score}/100
434
+ **Total Images:** ${result.totalImages}
435
+
436
+ ## Stats
437
+
438
+ | Metric | Count |
439
+ |--------|-------|
440
+ | With Alt Text | ${result.imagesWithAlt} |
441
+ | With Dimensions | ${result.imagesWithDimensions} |
442
+ | Lazy Loading | ${result.imagesWithLazyLoading} |
443
+ | Modern Formats | ${result.modernFormats} |
444
+ | Legacy Formats | ${result.legacyFormats} |
445
+
446
+ ## Issues
447
+
448
+ ${result.issues.map((i) => `- **${i.title}:** ${i.howToFix}`).join("\n") || "No issues!"}
449
+
450
+ ## Recommendations
451
+
452
+ ${result.recommendations.map((r) => `- ${r}`).join("\n")}
453
+ `;
454
+ }
455
+ function formatInternalLinksResult(result) {
456
+ return `# Internal Linking Analysis
457
+
458
+ **Score:** ${result.score}/100
459
+
460
+ ## Stats
461
+
462
+ | Metric | Count |
463
+ |--------|-------|
464
+ | Total Links | ${result.totalLinks} |
465
+ | Internal Links | ${result.internalLinks} |
466
+ | External Links | ${result.externalLinks} |
467
+ | Navigation Links | ${result.navigationLinks} |
468
+ | Content Links | ${result.contentLinks} |
469
+ | Unique Internal Targets | ${result.uniqueInternalTargets} |
470
+
471
+ ## Anchor Text Quality
472
+
473
+ - Descriptive: ${result.anchorTextAnalysis.descriptive}
474
+ - Generic: ${result.anchorTextAnalysis.generic}
475
+ - Empty: ${result.anchorTextAnalysis.empty}
476
+
477
+ ## Issues
478
+
479
+ ${result.issues.map((i) => `- **${i.title}:** ${i.howToFix}`).join("\n") || "No issues!"}
480
+
481
+ ## Recommendations
482
+
483
+ ${result.recommendations.map((r) => `- ${r}`).join("\n")}
484
+ `;
485
+ }
486
+ function formatMobileResult(result) {
487
+ return `# Mobile SEO Analysis
488
+
489
+ **Score:** ${result.score}/100
490
+
491
+ ## Viewport
492
+
493
+ - Has Viewport: ${result.viewport.hasViewport ? "\u2705" : "\u274C"}
494
+ - Responsive: ${result.viewport.isResponsive ? "\u2705" : "\u274C"}
495
+ ${result.viewport.viewportContent ? `- Content: \`${result.viewport.viewportContent}\`` : ""}
496
+
497
+ ## Touch Targets
498
+
499
+ - Small Targets: ${result.touchTargets.smallTargets}
500
+ - Proper Targets: ${result.touchTargets.properTargets}
501
+
502
+ ## Mobile Features
503
+
504
+ - Apple Touch Icon: ${result.mobileSpecific.hasAppleTouchIcon ? "\u2705" : "\u274C"}
505
+ - Theme Color: ${result.mobileSpecific.hasThemeColor ? "\u2705" : "\u274C"}
506
+ - Web App Manifest: ${result.mobileSpecific.hasManifest ? "\u2705" : "\u274C"}
507
+ - Responsive Images: ${result.mobileSpecific.hasMobileOptimizedImages ? "\u2705" : "\u274C"}
508
+
509
+ ## Issues
510
+
511
+ ${result.issues.map((i) => `- **${i.title}:** ${i.howToFix}`).join("\n") || "No issues!"}
512
+
513
+ ## Recommendations
514
+
515
+ ${result.recommendations.map((r) => `- ${r}`).join("\n")}
516
+ `;
517
+ }
518
+ function formatAICrawlersInfo() {
519
+ const crawlers = Object.entries(analyzers.AI_CRAWLERS_INFO);
520
+ return `# Known AI Crawlers
521
+
522
+ ${crawlers.map(([name, info]) => `## ${name}
523
+ - **User-Agent:** ${info.userAgent}
524
+ - **Company:** ${info.company}
525
+ - **Purpose:** ${info.purpose}
526
+ `).join("\n")}
527
+
528
+ ## robots.txt Example
529
+
530
+ \`\`\`
531
+ User-agent: GPTBot
532
+ Allow: /
533
+
534
+ User-agent: Claude-Web
535
+ Allow: /
536
+
537
+ User-agent: PerplexityBot
538
+ Allow: /
539
+ \`\`\`
540
+ `;
541
+ }
542
+ async function handleAnalysisTool(name, args) {
543
+ switch (name) {
544
+ case "seo_analyze": {
545
+ const { url, html: providedHtml, robotsTxt } = args;
546
+ const html = providedHtml ?? await fetchHtml(url);
547
+ const result = await analyzers.analyzeComprehensive(html, url, { robotsTxt });
548
+ return { content: [{ type: "text", text: formatComprehensiveResult(result) }] };
549
+ }
550
+ case "seo_geo_check": {
551
+ const { url, html: providedHtml, robotsTxt } = args;
552
+ const html = providedHtml ?? await fetchHtml(url);
553
+ const result = await analyzers.analyzeGEO(html, url, robotsTxt);
554
+ return { content: [{ type: "text", text: formatGEOResult(result) }] };
555
+ }
556
+ case "seo_robots_ai": {
557
+ const { robotsTxt } = args;
558
+ const result = analyzers.analyzeRobotsTxtForAI(robotsTxt);
559
+ return {
560
+ content: [
561
+ {
562
+ type: "text",
563
+ text: `## AI Crawler Analysis
564
+
565
+ **Allowed:** ${result.allowed.join(", ") || "None"}
566
+
567
+ **Blocked:** ${result.blocked.join(", ") || "None"}
568
+
569
+ **Recommendations:**
570
+ ${result.recommendations.map((r) => `- ${r}`).join("\n") || "- All good!"}`
571
+ }
572
+ ]
573
+ };
574
+ }
575
+ case "seo_generate_robots": {
576
+ const { siteUrl } = args;
577
+ const robotsTxt = analyzers.generateAIFriendlyRobotsTxt(siteUrl);
578
+ return {
579
+ content: [{ type: "text", text: `## AI-Friendly robots.txt
580
+
581
+ \`\`\`
582
+ ${robotsTxt}\`\`\`` }]
583
+ };
584
+ }
585
+ case "seo_core_web_vitals": {
586
+ const { url, html } = args;
587
+ const result = analyzers.analyzeCoreWebVitals(html, url);
588
+ return { content: [{ type: "text", text: formatCWVResult(result) }] };
589
+ }
590
+ case "seo_structured_data": {
591
+ const { url, html } = args;
592
+ const result = analyzers.analyzeStructuredData(html, url);
593
+ return { content: [{ type: "text", text: formatStructuredDataResult(result) }] };
594
+ }
595
+ case "seo_generate_schema": {
596
+ const { pageType, siteName, siteUrl, authorName, organizationName } = args;
597
+ const schema = analyzers.generateSchemaTemplate(pageType, { siteName, siteUrl, authorName, organizationName });
598
+ return {
599
+ content: [
600
+ {
601
+ type: "text",
602
+ text: `## ${pageType} Schema Template
603
+
604
+ \`\`\`json
605
+ ${schema}
606
+ \`\`\`
607
+
608
+ Replace {{placeholders}} with actual values.`
609
+ }
610
+ ]
611
+ };
612
+ }
613
+ case "seo_security_headers": {
614
+ const { url, headers } = args;
615
+ const result = analyzers.analyzeSecurityHeaders(headers, url);
616
+ return { content: [{ type: "text", text: formatSecurityResult(result) }] };
617
+ }
618
+ case "seo_generate_security_headers": {
619
+ const { siteUrl } = args;
620
+ const headers = analyzers.generateSecurityHeaders(siteUrl);
621
+ return {
622
+ content: [
623
+ {
624
+ type: "text",
625
+ text: `## Recommended Security Headers
626
+
627
+ ${Object.entries(headers).map(([k, v]) => `**${k}:**
628
+ \`${v}\``).join("\n\n")}`
629
+ }
630
+ ]
631
+ };
632
+ }
633
+ case "seo_images": {
634
+ const { url, html } = args;
635
+ const result = analyzers.analyzeImages(html, url);
636
+ return { content: [{ type: "text", text: formatImagesResult(result) }] };
637
+ }
638
+ case "seo_internal_links": {
639
+ const { url, html } = args;
640
+ const result = analyzers.analyzeInternalLinking(html, url);
641
+ return { content: [{ type: "text", text: formatInternalLinksResult(result) }] };
642
+ }
643
+ case "seo_mobile": {
644
+ const { url, html } = args;
645
+ const result = analyzers.analyzeMobileSEO(html, url);
646
+ return { content: [{ type: "text", text: formatMobileResult(result) }] };
647
+ }
648
+ case "seo_ai_crawlers": {
649
+ return { content: [{ type: "text", text: formatAICrawlersInfo() }] };
650
+ }
651
+ default:
652
+ return void 0;
653
+ }
654
+ }
655
+ function createAnalysisServer() {
656
+ const server = new Server(
657
+ { name: "rankcli", version: "0.0.1" },
658
+ { capabilities: { tools: {} } }
659
+ );
660
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
661
+ return { tools: ANALYSIS_TOOLS };
662
+ });
663
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
664
+ const { name, arguments: args } = request.params;
665
+ try {
666
+ const result = await handleAnalysisTool(name, args);
667
+ if (result) return result;
668
+ return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true };
669
+ } catch (error) {
670
+ return {
671
+ content: [{ type: "text", text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
672
+ isError: true
673
+ };
674
+ }
675
+ });
676
+ return server;
677
+ }
678
+
679
+ export {
680
+ fetchHtml,
681
+ ANALYSIS_TOOLS,
682
+ handleAnalysisTool,
683
+ createAnalysisServer
684
+ };