@rankcli/mcp-server 0.0.6 → 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/dist/index.js CHANGED
@@ -1,4 +1,8 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ ANALYSIS_TOOLS,
4
+ handleAnalysisTool
5
+ } from "./chunk-JSSECRCS.js";
2
6
 
3
7
  // src/index.ts
4
8
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
@@ -8,271 +12,9 @@ import {
8
12
  ListToolsRequestSchema
9
13
  } from "@modelcontextprotocol/sdk/types.js";
10
14
  import Conf from "conf";
11
- import { analyzers } from "@rankcli/agent-runtime";
12
15
  var SUPABASE_URL = process.env.RANKCLI_SUPABASE_URL || "https://bspljbxwbjiqueeyzyat.supabase.co";
13
16
  var localConfig = new Conf({ projectName: "rankcli" });
14
- var TOOLS = [
15
- {
16
- name: "seo_analyze",
17
- description: `Run comprehensive SEO analysis on a webpage. Returns scores and issues for:
18
- - GEO (AI Search Optimization) - Is the site visible to ChatGPT, Perplexity, Claude?
19
- - Core Web Vitals (LCP, CLS, INP) - Performance estimates
20
- - Structured Data - Schema.org validation
21
- - Security Headers - HTTPS, HSTS, CSP
22
- - Mobile SEO - Responsive design, touch targets
23
- - Images - Alt text, formats, dimensions
24
- - Internal Linking - Anchor text, orphan detection
25
-
26
- Use this for a complete SEO audit.`,
27
- inputSchema: {
28
- type: "object",
29
- properties: {
30
- url: {
31
- type: "string",
32
- description: "URL of the page to analyze"
33
- },
34
- html: {
35
- type: "string",
36
- description: "HTML content of the page (optional if URL is provided)"
37
- },
38
- robotsTxt: {
39
- type: "string",
40
- description: "robots.txt content for AI crawler analysis (optional)"
41
- }
42
- },
43
- required: ["url"]
44
- }
45
- },
46
- {
47
- name: "seo_geo_check",
48
- description: `Check if a website is optimized for AI search engines (GEO - Generative Engine Optimization).
49
-
50
- Analyzes:
51
- - AI crawler access (GPTBot, ClaudeBot, PerplexityBot, etc.)
52
- - robots.txt rules for AI crawlers
53
- - JS rendering requirements (can AI crawlers see content?)
54
- - Content structure for LLM consumption
55
- - Citation readiness (trust signals)
56
- - FAQ/entity extraction capability
57
-
58
- Critical for visibility in ChatGPT, Perplexity, Claude, and Gemini responses.`,
59
- inputSchema: {
60
- type: "object",
61
- properties: {
62
- url: {
63
- type: "string",
64
- description: "URL of the page to analyze"
65
- },
66
- html: {
67
- type: "string",
68
- description: "HTML content of the page"
69
- },
70
- robotsTxt: {
71
- type: "string",
72
- description: "robots.txt content"
73
- }
74
- },
75
- required: ["url"]
76
- }
77
- },
78
- {
79
- name: "seo_robots_ai",
80
- description: `Analyze robots.txt for AI crawler permissions. Shows which AI crawlers (GPTBot, ClaudeBot, PerplexityBot, etc.) are allowed or blocked.`,
81
- inputSchema: {
82
- type: "object",
83
- properties: {
84
- robotsTxt: {
85
- type: "string",
86
- description: "Content of robots.txt file"
87
- }
88
- },
89
- required: ["robotsTxt"]
90
- }
91
- },
92
- {
93
- name: "seo_generate_robots",
94
- description: `Generate an AI-friendly robots.txt that allows all major AI crawlers (GPTBot, ClaudeBot, PerplexityBot, Google-Extended, etc.)`,
95
- inputSchema: {
96
- type: "object",
97
- properties: {
98
- siteUrl: {
99
- type: "string",
100
- description: "Base URL of the site (e.g., https://example.com)"
101
- }
102
- },
103
- required: ["siteUrl"]
104
- }
105
- },
106
- {
107
- name: "seo_core_web_vitals",
108
- description: `Estimate Core Web Vitals (LCP, CLS, INP, TTFB) from HTML analysis. Identifies issues like:
109
- - Render-blocking resources
110
- - Images without dimensions
111
- - Large JavaScript bundles
112
- - Missing preload hints`,
113
- inputSchema: {
114
- type: "object",
115
- properties: {
116
- url: {
117
- type: "string",
118
- description: "URL of the page"
119
- },
120
- html: {
121
- type: "string",
122
- description: "HTML content of the page"
123
- }
124
- },
125
- required: ["url", "html"]
126
- }
127
- },
128
- {
129
- name: "seo_structured_data",
130
- description: `Validate JSON-LD structured data (Schema.org). Checks for:
131
- - Required properties per schema type
132
- - Article, Product, FAQ, HowTo, LocalBusiness schemas
133
- - Rich result eligibility
134
- - Common mistakes`,
135
- inputSchema: {
136
- type: "object",
137
- properties: {
138
- url: {
139
- type: "string",
140
- description: "URL of the page"
141
- },
142
- html: {
143
- type: "string",
144
- description: "HTML content with JSON-LD"
145
- }
146
- },
147
- required: ["url", "html"]
148
- }
149
- },
150
- {
151
- name: "seo_generate_schema",
152
- description: `Generate JSON-LD structured data template for a page type (article, product, faq, local-business, website).`,
153
- inputSchema: {
154
- type: "object",
155
- properties: {
156
- pageType: {
157
- type: "string",
158
- enum: ["article", "product", "faq", "local-business", "website"],
159
- description: "Type of page"
160
- },
161
- siteName: {
162
- type: "string",
163
- description: "Name of the website"
164
- },
165
- siteUrl: {
166
- type: "string",
167
- description: "Base URL of the website"
168
- },
169
- authorName: {
170
- type: "string",
171
- description: "Default author name (for articles)"
172
- },
173
- organizationName: {
174
- type: "string",
175
- description: "Organization name"
176
- }
177
- },
178
- required: ["pageType", "siteName", "siteUrl"]
179
- }
180
- },
181
- {
182
- name: "seo_security_headers",
183
- description: `Analyze security headers (HTTPS, HSTS, CSP, X-Frame-Options, etc.). Returns a security grade A+ through F.`,
184
- inputSchema: {
185
- type: "object",
186
- properties: {
187
- url: {
188
- type: "string",
189
- description: "URL of the page"
190
- },
191
- headers: {
192
- type: "object",
193
- description: "HTTP response headers",
194
- additionalProperties: { type: "string" }
195
- }
196
- },
197
- required: ["url", "headers"]
198
- }
199
- },
200
- {
201
- name: "seo_generate_security_headers",
202
- description: `Generate recommended security headers configuration for a site.`,
203
- inputSchema: {
204
- type: "object",
205
- properties: {
206
- siteUrl: {
207
- type: "string",
208
- description: "Base URL of the site"
209
- }
210
- },
211
- required: ["siteUrl"]
212
- }
213
- },
214
- {
215
- name: "seo_images",
216
- description: `Analyze images for SEO and performance. Checks alt text, dimensions, formats (WebP/AVIF), lazy loading, and responsive images.`,
217
- inputSchema: {
218
- type: "object",
219
- properties: {
220
- url: {
221
- type: "string",
222
- description: "URL of the page"
223
- },
224
- html: {
225
- type: "string",
226
- description: "HTML content of the page"
227
- }
228
- },
229
- required: ["url", "html"]
230
- }
231
- },
232
- {
233
- name: "seo_internal_links",
234
- description: `Analyze internal linking structure. Checks anchor text quality, orphan page risk, link distribution, and suggests linking opportunities.`,
235
- inputSchema: {
236
- type: "object",
237
- properties: {
238
- url: {
239
- type: "string",
240
- description: "URL of the page"
241
- },
242
- html: {
243
- type: "string",
244
- description: "HTML content of the page"
245
- }
246
- },
247
- required: ["url", "html"]
248
- }
249
- },
250
- {
251
- name: "seo_mobile",
252
- description: `Analyze mobile SEO. Checks viewport, touch targets, font sizes, content width, PWA readiness.`,
253
- inputSchema: {
254
- type: "object",
255
- properties: {
256
- url: {
257
- type: "string",
258
- description: "URL of the page"
259
- },
260
- html: {
261
- type: "string",
262
- description: "HTML content of the page"
263
- }
264
- },
265
- required: ["url", "html"]
266
- }
267
- },
268
- {
269
- name: "seo_ai_crawlers",
270
- description: `Get information about all known AI crawlers (GPTBot, ClaudeBot, PerplexityBot, Google-Extended, etc.) including their user agents and purposes.`,
271
- inputSchema: {
272
- type: "object",
273
- properties: {}
274
- }
275
- },
17
+ var ACCOUNT_TOOLS = [
276
18
  {
277
19
  name: "rankcli_connect",
278
20
  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.`,
@@ -290,6 +32,7 @@ Critical for visibility in ChatGPT, Perplexity, Claude, and Gemini responses.`,
290
32
  }
291
33
  }
292
34
  ];
35
+ var TOOLS = [...ANALYSIS_TOOLS, ...ACCOUNT_TOOLS];
293
36
  var server = new Server(
294
37
  {
295
38
  name: "rankcli",
@@ -307,160 +50,9 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
307
50
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
308
51
  const { name, arguments: args } = request.params;
309
52
  try {
53
+ const analysisResult = await handleAnalysisTool(name, args);
54
+ if (analysisResult) return analysisResult;
310
55
  switch (name) {
311
- case "seo_analyze": {
312
- const { url, html, robotsTxt } = args;
313
- if (!html) {
314
- return {
315
- content: [
316
- {
317
- type: "text",
318
- text: "HTML content is required for analysis. Please provide the HTML of the page."
319
- }
320
- ]
321
- };
322
- }
323
- const result = await analyzers.analyzeComprehensive(html, url, { robotsTxt });
324
- return {
325
- content: [
326
- {
327
- type: "text",
328
- text: formatComprehensiveResult(result)
329
- }
330
- ]
331
- };
332
- }
333
- case "seo_geo_check": {
334
- const { url, html, robotsTxt } = args;
335
- if (!html) {
336
- return {
337
- content: [{ type: "text", text: "HTML content required for GEO analysis." }]
338
- };
339
- }
340
- const result = await analyzers.analyzeGEO(html, url, robotsTxt);
341
- return {
342
- content: [{ type: "text", text: formatGEOResult(result) }]
343
- };
344
- }
345
- case "seo_robots_ai": {
346
- const { robotsTxt } = args;
347
- const result = analyzers.analyzeRobotsTxtForAI(robotsTxt);
348
- return {
349
- content: [
350
- {
351
- type: "text",
352
- text: `## AI Crawler Analysis
353
-
354
- **Allowed:** ${result.allowed.join(", ") || "None"}
355
-
356
- **Blocked:** ${result.blocked.join(", ") || "None"}
357
-
358
- **Recommendations:**
359
- ${result.recommendations.map((r) => `- ${r}`).join("\n") || "- All good!"}`
360
- }
361
- ]
362
- };
363
- }
364
- case "seo_generate_robots": {
365
- const { siteUrl } = args;
366
- const robotsTxt = analyzers.generateAIFriendlyRobotsTxt(siteUrl);
367
- return {
368
- content: [
369
- {
370
- type: "text",
371
- text: `## AI-Friendly robots.txt
372
-
373
- \`\`\`
374
- ${robotsTxt}\`\`\``
375
- }
376
- ]
377
- };
378
- }
379
- case "seo_core_web_vitals": {
380
- const { url, html } = args;
381
- const result = analyzers.analyzeCoreWebVitals(html, url);
382
- return {
383
- content: [{ type: "text", text: formatCWVResult(result) }]
384
- };
385
- }
386
- case "seo_structured_data": {
387
- const { url, html } = args;
388
- const result = analyzers.analyzeStructuredData(html, url);
389
- return {
390
- content: [{ type: "text", text: formatStructuredDataResult(result) }]
391
- };
392
- }
393
- case "seo_generate_schema": {
394
- const { pageType, siteName, siteUrl, authorName, organizationName } = args;
395
- const schema = analyzers.generateSchemaTemplate(pageType, { siteName, siteUrl, authorName, organizationName });
396
- return {
397
- content: [
398
- {
399
- type: "text",
400
- text: `## ${pageType} Schema Template
401
-
402
- \`\`\`json
403
- ${schema}
404
- \`\`\`
405
-
406
- Replace {{placeholders}} with actual values.`
407
- }
408
- ]
409
- };
410
- }
411
- case "seo_security_headers": {
412
- const { url, headers } = args;
413
- const result = analyzers.analyzeSecurityHeaders(headers, url);
414
- return {
415
- content: [{ type: "text", text: formatSecurityResult(result) }]
416
- };
417
- }
418
- case "seo_generate_security_headers": {
419
- const { siteUrl } = args;
420
- const headers = analyzers.generateSecurityHeaders(siteUrl);
421
- return {
422
- content: [
423
- {
424
- type: "text",
425
- text: `## Recommended Security Headers
426
-
427
- ${Object.entries(headers).map(([k, v]) => `**${k}:**
428
- \`${v}\``).join("\n\n")}`
429
- }
430
- ]
431
- };
432
- }
433
- case "seo_images": {
434
- const { url, html } = args;
435
- const result = analyzers.analyzeImages(html, url);
436
- return {
437
- content: [{ type: "text", text: formatImagesResult(result) }]
438
- };
439
- }
440
- case "seo_internal_links": {
441
- const { url, html } = args;
442
- const result = analyzers.analyzeInternalLinking(html, url);
443
- return {
444
- content: [{ type: "text", text: formatInternalLinksResult(result) }]
445
- };
446
- }
447
- case "seo_mobile": {
448
- const { url, html } = args;
449
- const result = analyzers.analyzeMobileSEO(html, url);
450
- return {
451
- content: [{ type: "text", text: formatMobileResult(result) }]
452
- };
453
- }
454
- case "seo_ai_crawlers": {
455
- return {
456
- content: [
457
- {
458
- type: "text",
459
- text: formatAICrawlersInfo()
460
- }
461
- ]
462
- };
463
- }
464
56
  case "rankcli_connect": {
465
57
  const result = await connectToRankCLI();
466
58
  return {
@@ -500,28 +92,25 @@ ${Object.entries(headers).map(([k, v]) => `**${k}:**
500
92
  };
501
93
  }
502
94
  });
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
95
  async function pollConnectStatus(connectId, timeoutMs) {
507
96
  const deadline = Date.now() + timeoutMs;
508
97
  while (Date.now() < deadline) {
509
- await new Promise((resolve) => setTimeout(resolve, 2e3));
510
98
  try {
511
99
  const res = await fetch(`${SUPABASE_URL}/functions/v1/mcp-connect-status`, {
512
100
  method: "POST",
513
101
  headers: { "Content-Type": "application/json" },
514
102
  body: JSON.stringify({ connectId })
515
103
  });
516
- const data = await res.json();
517
- if (data.status === "claimed") {
518
- return { email: data.email, apiKey: data.apiKey, apiKeyName: data.apiKeyName };
104
+ const status = await res.json();
105
+ if (status.status === "claimed" && status.apiKey) {
106
+ return { apiKey: status.apiKey, apiKeyName: status.apiKeyName, email: status.email };
519
107
  }
520
- if (data.status === "expired" || data.status === "not_found") {
108
+ if (status.status === "expired" || status.status === "not_found") {
521
109
  return null;
522
110
  }
523
111
  } catch {
524
112
  }
113
+ await new Promise((resolve) => setTimeout(resolve, 2e3));
525
114
  }
526
115
  return null;
527
116
  }
@@ -611,265 +200,6 @@ ${url}
611
200
  Then run rankcli_connect again to finish (link expires in ${expiresMinutes} minutes).`
612
201
  };
613
202
  }
614
- function formatComprehensiveResult(result) {
615
- const criticalIssues = result.allIssues.filter((i) => i.severity === "critical");
616
- const warnings = result.allIssues.filter((i) => i.severity === "warning");
617
- return `# SEO Analysis Report
618
-
619
- **URL:** ${result.url}
620
- **Overall Score:** ${result.overallScore}/100
621
- **Analyzed:** ${result.timestamp}
622
-
623
- ## Scores
624
-
625
- | Category | Score |
626
- |----------|-------|
627
- | GEO (AI Search) | ${result.grades.geo}/100 |
628
- | Core Web Vitals | ${result.grades.coreWebVitals}/100 |
629
- | Security | ${result.grades.security} |
630
- | Structured Data | ${result.grades.structuredData}/100 |
631
- | Images | ${result.grades.images}/100 |
632
- | Internal Links | ${result.grades.internalLinking}/100 |
633
- | Mobile SEO | ${result.grades.mobile}/100 |
634
-
635
- ## Critical Issues (${criticalIssues.length})
636
-
637
- ${criticalIssues.map((i) => `### \u274C ${i.title}
638
- ${i.description}
639
-
640
- **Fix:** ${i.howToFix}`).join("\n\n") || "None!"}
641
-
642
- ## Warnings (${warnings.length})
643
-
644
- ${warnings.slice(0, 5).map((i) => `- **${i.title}:** ${i.description}`).join("\n") || "None!"}
645
-
646
- ## Priority Recommendations
647
-
648
- ${result.prioritizedRecommendations.map((r, i) => `${i + 1}. ${r}`).join("\n")}
649
- ${BRIDGE_FOOTER}
650
- `;
651
- }
652
- function formatGEOResult(result) {
653
- return `# GEO Analysis (AI Search Optimization)
654
-
655
- **Score:** ${result.score}/100
656
-
657
- ## AI Crawler Access
658
-
659
- | Status | Crawlers |
660
- |--------|----------|
661
- | \u2705 Allowed | ${result.aiCrawlerAccess.allowedCrawlers.join(", ") || "None"} |
662
- | \u274C Blocked | ${result.aiCrawlerAccess.blockedCrawlers.join(", ") || "None"} |
663
-
664
- **Server-Side Rendered:** ${result.aiCrawlerAccess.serverSideRendered ? "\u2705 Yes" : "\u274C No"}
665
- **JS Rendering Required:** ${result.aiCrawlerAccess.jsRenderingRequired ? "\u26A0\uFE0F Yes (AI crawlers may not see content)" : "\u2705 No"}
666
-
667
- ## LLM Friendliness Scores
668
-
669
- | Signal | Score |
670
- |--------|-------|
671
- | Content Clarity | ${result.llmSignals.contentClarity}/100 |
672
- | Fact Density | ${result.llmSignals.factDensity}/100 |
673
- | Structure Quality | ${result.llmSignals.structureQuality}/100 |
674
- | Citation Quality | ${result.llmSignals.citationQuality}/100 |
675
-
676
- ## Content Structure
677
-
678
- - Structured Data: ${result.contentStructure.hasStructuredData ? "\u2705" : "\u274C"}
679
- - FAQ Schema: ${result.contentStructure.hasFAQSchema ? "\u2705" : "\u274C"}
680
- - Article Schema: ${result.contentStructure.hasArticleSchema ? "\u2705" : "\u274C"}
681
- - Heading Hierarchy: ${result.contentStructure.headingHierarchy}
682
-
683
- ## Citation Readiness
684
-
685
- Trust Signals: ${result.citationReadiness.trustSignals.join(", ") || "None detected"}
686
-
687
- ## Recommendations
688
-
689
- ${result.recommendations.map((r) => `- ${r}`).join("\n")}
690
- ${BRIDGE_FOOTER}
691
- `;
692
- }
693
- function formatCWVResult(result) {
694
- const emoji = (est) => est === "good" ? "\u{1F7E2}" : est === "needs-improvement" ? "\u{1F7E1}" : "\u{1F534}";
695
- return `# Core Web Vitals Estimate
696
-
697
- **Overall Score:** ${result.overallScore}/100
698
-
699
- | Metric | Estimate | Issues |
700
- |--------|----------|--------|
701
- | LCP (Largest Contentful Paint) | ${emoji(result.lcp.estimate)} ${result.lcp.estimate} | ${result.lcp.issues.length} |
702
- | CLS (Cumulative Layout Shift) | ${emoji(result.cls.estimate)} ${result.cls.estimate} | ${result.cls.issues.length} |
703
- | INP (Interaction to Next Paint) | ${emoji(result.inp.estimate)} ${result.inp.estimate} | ${result.inp.issues.length} |
704
- | TTFB (Time to First Byte) | ${emoji(result.ttfb.estimate)} ${result.ttfb.estimate} | ${result.ttfb.issues.length} |
705
-
706
- ## Issues
707
-
708
- ${result.issues.map((i) => `- **${i.title}:** ${i.howToFix}`).join("\n") || "No critical issues!"}
709
- `;
710
- }
711
- function formatStructuredDataResult(result) {
712
- return `# Structured Data Analysis
713
-
714
- **Score:** ${result.score}/100
715
- **Schemas Found:** ${result.schemas.length}
716
-
717
- ## Schema Types
718
-
719
- | Type | Valid | Errors |
720
- |------|-------|--------|
721
- ${result.schemas.map((s) => `| ${s.type} | ${s.isValid ? "\u2705" : "\u274C"} | ${s.errors.join("; ") || "-"} |`).join("\n") || "| None found | - | - |"}
722
-
723
- ## Rich Result Eligibility
724
-
725
- - Organization: ${result.hasOrganization ? "\u2705" : "\u274C"}
726
- - WebSite: ${result.hasWebSite ? "\u2705" : "\u274C"}
727
- - Breadcrumb: ${result.hasBreadcrumb ? "\u2705" : "\u274C"}
728
- - Article: ${result.hasArticle ? "\u2705" : "\u274C"}
729
- - Product: ${result.hasProduct ? "\u2705" : "\u274C"}
730
- - FAQ: ${result.hasFAQ ? "\u2705" : "\u274C"}
731
- - HowTo: ${result.hasHowTo ? "\u2705" : "\u274C"}
732
-
733
- ## Recommendations
734
-
735
- ${result.recommendations.map((r) => `- ${r}`).join("\n")}
736
- `;
737
- }
738
- function formatSecurityResult(result) {
739
- return `# Security Headers Analysis
740
-
741
- **Grade:** ${result.grade}
742
- **Score:** ${result.score}/100
743
-
744
- ## Headers
745
-
746
- | Header | Status |
747
- |--------|--------|
748
- | HTTPS | ${result.https.enabled ? "\u2705" : "\u274C"} |
749
- | HSTS | ${result.https.hasHSTS ? "\u2705" : "\u274C"} |
750
- | CSP | ${result.contentSecurity.hasCSP ? "\u2705" : "\u274C"} |
751
- | X-Frame-Options | ${result.frameOptions.hasXFrameOptions ? "\u2705" : "\u274C"} |
752
- | X-Content-Type-Options | ${result.contentTypeOptions.hasXContentTypeOptions ? "\u2705" : "\u274C"} |
753
- | Referrer-Policy | ${result.referrerPolicy.hasReferrerPolicy ? "\u2705" : "\u274C"} |
754
- | Permissions-Policy | ${result.permissionsPolicy.hasPermissionsPolicy ? "\u2705" : "\u274C"} |
755
-
756
- ## Issues
757
-
758
- ${result.issues.map((i) => `- **${i.title}:** ${i.howToFix}`).join("\n") || "No issues!"}
759
- `;
760
- }
761
- function formatImagesResult(result) {
762
- return `# Image Analysis
763
-
764
- **Score:** ${result.score}/100
765
- **Total Images:** ${result.totalImages}
766
-
767
- ## Stats
768
-
769
- | Metric | Count |
770
- |--------|-------|
771
- | With Alt Text | ${result.imagesWithAlt} |
772
- | With Dimensions | ${result.imagesWithDimensions} |
773
- | Lazy Loading | ${result.imagesWithLazyLoading} |
774
- | Modern Formats | ${result.modernFormats} |
775
- | Legacy Formats | ${result.legacyFormats} |
776
-
777
- ## Issues
778
-
779
- ${result.issues.map((i) => `- **${i.title}:** ${i.howToFix}`).join("\n") || "No issues!"}
780
-
781
- ## Recommendations
782
-
783
- ${result.recommendations.map((r) => `- ${r}`).join("\n")}
784
- `;
785
- }
786
- function formatInternalLinksResult(result) {
787
- return `# Internal Linking Analysis
788
-
789
- **Score:** ${result.score}/100
790
-
791
- ## Stats
792
-
793
- | Metric | Count |
794
- |--------|-------|
795
- | Total Links | ${result.totalLinks} |
796
- | Internal Links | ${result.internalLinks} |
797
- | External Links | ${result.externalLinks} |
798
- | Navigation Links | ${result.navigationLinks} |
799
- | Content Links | ${result.contentLinks} |
800
- | Unique Internal Targets | ${result.uniqueInternalTargets} |
801
-
802
- ## Anchor Text Quality
803
-
804
- - Descriptive: ${result.anchorTextAnalysis.descriptive}
805
- - Generic: ${result.anchorTextAnalysis.generic}
806
- - Empty: ${result.anchorTextAnalysis.empty}
807
-
808
- ## Issues
809
-
810
- ${result.issues.map((i) => `- **${i.title}:** ${i.howToFix}`).join("\n") || "No issues!"}
811
-
812
- ## Recommendations
813
-
814
- ${result.recommendations.map((r) => `- ${r}`).join("\n")}
815
- `;
816
- }
817
- function formatMobileResult(result) {
818
- return `# Mobile SEO Analysis
819
-
820
- **Score:** ${result.score}/100
821
-
822
- ## Viewport
823
-
824
- - Has Viewport: ${result.viewport.hasViewport ? "\u2705" : "\u274C"}
825
- - Responsive: ${result.viewport.isResponsive ? "\u2705" : "\u274C"}
826
- ${result.viewport.viewportContent ? `- Content: \`${result.viewport.viewportContent}\`` : ""}
827
-
828
- ## Touch Targets
829
-
830
- - Small Targets: ${result.touchTargets.smallTargets}
831
- - Proper Targets: ${result.touchTargets.properTargets}
832
-
833
- ## Mobile Features
834
-
835
- - Apple Touch Icon: ${result.mobileSpecific.hasAppleTouchIcon ? "\u2705" : "\u274C"}
836
- - Theme Color: ${result.mobileSpecific.hasThemeColor ? "\u2705" : "\u274C"}
837
- - Web App Manifest: ${result.mobileSpecific.hasManifest ? "\u2705" : "\u274C"}
838
- - Responsive Images: ${result.mobileSpecific.hasMobileOptimizedImages ? "\u2705" : "\u274C"}
839
-
840
- ## Issues
841
-
842
- ${result.issues.map((i) => `- **${i.title}:** ${i.howToFix}`).join("\n") || "No issues!"}
843
-
844
- ## Recommendations
845
-
846
- ${result.recommendations.map((r) => `- ${r}`).join("\n")}
847
- `;
848
- }
849
- function formatAICrawlersInfo() {
850
- const crawlers = Object.entries(analyzers.AI_CRAWLERS_INFO);
851
- return `# Known AI Crawlers
852
-
853
- ${crawlers.map(([name, info]) => `## ${name}
854
- - **User-Agent:** ${info.userAgent}
855
- - **Company:** ${info.company}
856
- - **Purpose:** ${info.purpose}
857
- `).join("\n")}
858
-
859
- ## robots.txt Example
860
-
861
- \`\`\`
862
- User-agent: GPTBot
863
- Allow: /
864
-
865
- User-agent: Claude-Web
866
- Allow: /
867
-
868
- User-agent: PerplexityBot
869
- Allow: /
870
- \`\`\`
871
- `;
872
- }
873
203
  async function main() {
874
204
  const transport = new StdioServerTransport();
875
205
  await server.connect(transport);