@automatelab/ai-seo-mcp 0.1.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 +21 -0
- package/README.md +217 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +145 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/config.d.ts +8 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +11 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/crawlers.json +6 -0
- package/dist/lib/entities.d.ts +17 -0
- package/dist/lib/entities.d.ts.map +1 -0
- package/dist/lib/entities.js +113 -0
- package/dist/lib/entities.js.map +1 -0
- package/dist/lib/fetch.d.ts +33 -0
- package/dist/lib/fetch.d.ts.map +1 -0
- package/dist/lib/fetch.js +190 -0
- package/dist/lib/fetch.js.map +1 -0
- package/dist/lib/html.d.ts +45 -0
- package/dist/lib/html.d.ts.map +1 -0
- package/dist/lib/html.js +166 -0
- package/dist/lib/html.js.map +1 -0
- package/dist/lib/llms-txt.d.ts +25 -0
- package/dist/lib/llms-txt.d.ts.map +1 -0
- package/dist/lib/llms-txt.js +149 -0
- package/dist/lib/llms-txt.js.map +1 -0
- package/dist/lib/robots.d.ts +26 -0
- package/dist/lib/robots.d.ts.map +1 -0
- package/dist/lib/robots.js +83 -0
- package/dist/lib/robots.js.map +1 -0
- package/dist/lib/schema-types.json +56 -0
- package/dist/lib/schema.d.ts +20 -0
- package/dist/lib/schema.d.ts.map +1 -0
- package/dist/lib/schema.js +311 -0
- package/dist/lib/schema.js.map +1 -0
- package/dist/lib/score.d.ts +32 -0
- package/dist/lib/score.d.ts.map +1 -0
- package/dist/lib/score.js +62 -0
- package/dist/lib/score.js.map +1 -0
- package/dist/tools/audit-canonical.d.ts +26 -0
- package/dist/tools/audit-canonical.d.ts.map +1 -0
- package/dist/tools/audit-canonical.js +122 -0
- package/dist/tools/audit-canonical.js.map +1 -0
- package/dist/tools/audit-page.d.ts +31 -0
- package/dist/tools/audit-page.d.ts.map +1 -0
- package/dist/tools/audit-page.js +221 -0
- package/dist/tools/audit-page.js.map +1 -0
- package/dist/tools/audit-schema.d.ts +34 -0
- package/dist/tools/audit-schema.d.ts.map +1 -0
- package/dist/tools/audit-schema.js +65 -0
- package/dist/tools/audit-schema.js.map +1 -0
- package/dist/tools/check-robots.d.ts +22 -0
- package/dist/tools/check-robots.d.ts.map +1 -0
- package/dist/tools/check-robots.js +142 -0
- package/dist/tools/check-robots.js.map +1 -0
- package/dist/tools/check-sitemap.d.ts +27 -0
- package/dist/tools/check-sitemap.d.ts.map +1 -0
- package/dist/tools/check-sitemap.js +214 -0
- package/dist/tools/check-sitemap.js.map +1 -0
- package/dist/tools/check-technical.d.ts +50 -0
- package/dist/tools/check-technical.d.ts.map +1 -0
- package/dist/tools/check-technical.js +209 -0
- package/dist/tools/check-technical.js.map +1 -0
- package/dist/tools/extract-entities.d.ts +35 -0
- package/dist/tools/extract-entities.d.ts.map +1 -0
- package/dist/tools/extract-entities.js +69 -0
- package/dist/tools/extract-entities.js.map +1 -0
- package/dist/tools/generate-llms-txt.d.ts +32 -0
- package/dist/tools/generate-llms-txt.d.ts.map +1 -0
- package/dist/tools/generate-llms-txt.js +157 -0
- package/dist/tools/generate-llms-txt.js.map +1 -0
- package/dist/tools/rewrite-for-aeo.d.ts +50 -0
- package/dist/tools/rewrite-for-aeo.d.ts.map +1 -0
- package/dist/tools/rewrite-for-aeo.js +119 -0
- package/dist/tools/rewrite-for-aeo.js.map +1 -0
- package/dist/tools/rewrite-for-geo.d.ts +51 -0
- package/dist/tools/rewrite-for-geo.d.ts.map +1 -0
- package/dist/tools/rewrite-for-geo.js +115 -0
- package/dist/tools/rewrite-for-geo.js.map +1 -0
- package/dist/tools/score-ai-overview-eligibility.d.ts +34 -0
- package/dist/tools/score-ai-overview-eligibility.d.ts.map +1 -0
- package/dist/tools/score-ai-overview-eligibility.js +186 -0
- package/dist/tools/score-ai-overview-eligibility.js.map +1 -0
- package/dist/tools/score-citation-worthiness.d.ts +51 -0
- package/dist/tools/score-citation-worthiness.d.ts.map +1 -0
- package/dist/tools/score-citation-worthiness.js +145 -0
- package/dist/tools/score-citation-worthiness.js.map +1 -0
- package/dist/tools/validate-llms-txt.d.ts +33 -0
- package/dist/tools/validate-llms-txt.d.ts.map +1 -0
- package/dist/tools/validate-llms-txt.js +62 -0
- package/dist/tools/validate-llms-txt.js.map +1 -0
- package/dist/types.d.ts +48 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +60 -0
- package/src/lib/crawlers.json +6 -0
- package/src/lib/schema-types.json +56 -0
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
// Tool: check_technical
|
|
2
|
+
// Audits a page's HEAD section for technical signals relevant to AI crawlers.
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { politeFetch, ToolFetchError } from "../lib/fetch.js";
|
|
5
|
+
import { parseHead, levenshtein } from "../lib/html.js";
|
|
6
|
+
export const checkTechnicalInputSchema = z.object({
|
|
7
|
+
url: z.string().url(),
|
|
8
|
+
respect_robots: z.boolean().optional().default(true),
|
|
9
|
+
});
|
|
10
|
+
export async function checkTechnical(input, hostDelays, robotsCache) {
|
|
11
|
+
const result = await politeFetch(input.url, {
|
|
12
|
+
respectRobots: input.respect_robots,
|
|
13
|
+
hostDelays,
|
|
14
|
+
robotsCache,
|
|
15
|
+
});
|
|
16
|
+
const ct = result.headers["content-type"];
|
|
17
|
+
const ctStr = Array.isArray(ct) ? ct[0] : (ct ?? "");
|
|
18
|
+
if (ctStr && !ctStr.includes("html")) {
|
|
19
|
+
throw new ToolFetchError({
|
|
20
|
+
type: "non_html_response",
|
|
21
|
+
url: input.url,
|
|
22
|
+
content_type: ctStr,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
const xRobotsTag = result.headers["x-robots-tag"];
|
|
26
|
+
const head = parseHead(result.body, xRobotsTag);
|
|
27
|
+
const findings = [];
|
|
28
|
+
// HTTPS
|
|
29
|
+
const https = input.url.startsWith("https://");
|
|
30
|
+
// Canonical
|
|
31
|
+
let canonicalSelfRef = false;
|
|
32
|
+
let canonicalCrossDomain = false;
|
|
33
|
+
if (head.canonical) {
|
|
34
|
+
try {
|
|
35
|
+
const pageUrl = new URL(result.finalUrl);
|
|
36
|
+
const canonUrl = new URL(head.canonical, input.url);
|
|
37
|
+
canonicalSelfRef =
|
|
38
|
+
canonUrl.hostname === pageUrl.hostname &&
|
|
39
|
+
canonUrl.pathname === pageUrl.pathname;
|
|
40
|
+
canonicalCrossDomain = canonUrl.hostname !== pageUrl.hostname;
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
// ignore URL parse errors
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (!head.canonical) {
|
|
47
|
+
findings.push({
|
|
48
|
+
severity: "warning",
|
|
49
|
+
category: "technical",
|
|
50
|
+
where: "<head>",
|
|
51
|
+
message: "No canonical link element found.",
|
|
52
|
+
fix: 'Add <link rel="canonical" href="https://example.com/page"> to <head>.',
|
|
53
|
+
estimated_impact: "medium",
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
else if (canonicalCrossDomain) {
|
|
57
|
+
findings.push({
|
|
58
|
+
severity: "warning",
|
|
59
|
+
category: "technical",
|
|
60
|
+
where: 'link[rel="canonical"]',
|
|
61
|
+
message: "Canonical points to a different domain.",
|
|
62
|
+
fix: "Verify this is intentional (syndicated content). If not, update to the self-referencing canonical.",
|
|
63
|
+
estimated_impact: "medium",
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
// noindex
|
|
67
|
+
if (head.noindex) {
|
|
68
|
+
findings.push({
|
|
69
|
+
severity: "critical",
|
|
70
|
+
category: "technical",
|
|
71
|
+
where: head.noindexHeader ? "X-Robots-Tag header" : 'meta[name="robots"]',
|
|
72
|
+
message: "Page has noindex directive - no AI search engine can index this page.",
|
|
73
|
+
fix: "Remove the noindex directive if you want this page to appear in AI search results.",
|
|
74
|
+
estimated_impact: "high",
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
// Redirect finding
|
|
78
|
+
if (result.redirected && result.finalUrl !== input.url) {
|
|
79
|
+
findings.push({
|
|
80
|
+
severity: "info",
|
|
81
|
+
category: "technical",
|
|
82
|
+
where: "page-level",
|
|
83
|
+
message: `Page redirects to ${result.finalUrl} - ensure canonical and OG tags reflect the canonical URL.`,
|
|
84
|
+
fix: "Update og:url and canonical href to the final redirect target URL.",
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
// OG tags
|
|
88
|
+
if (!head.ogTitle) {
|
|
89
|
+
findings.push({
|
|
90
|
+
severity: "warning",
|
|
91
|
+
category: "technical",
|
|
92
|
+
where: "og:title",
|
|
93
|
+
message: "og:title is missing.",
|
|
94
|
+
fix: 'Add <meta property="og:title" content="Page Title">.',
|
|
95
|
+
estimated_impact: "medium",
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
if (!head.ogDescription) {
|
|
99
|
+
findings.push({
|
|
100
|
+
severity: "warning",
|
|
101
|
+
category: "technical",
|
|
102
|
+
where: "og:description",
|
|
103
|
+
message: "og:description is missing.",
|
|
104
|
+
fix: 'Add <meta property="og:description" content="120-160 character description.">.',
|
|
105
|
+
estimated_impact: "medium",
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
if (!head.ogImage) {
|
|
109
|
+
findings.push({
|
|
110
|
+
severity: "warning",
|
|
111
|
+
category: "technical",
|
|
112
|
+
where: "og:image",
|
|
113
|
+
message: "og:image is missing.",
|
|
114
|
+
fix: 'Add <meta property="og:image" content="https://example.com/image.jpg">.',
|
|
115
|
+
estimated_impact: "medium",
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
// Twitter card
|
|
119
|
+
if (!head.twitterCard) {
|
|
120
|
+
findings.push({
|
|
121
|
+
severity: "info",
|
|
122
|
+
category: "technical",
|
|
123
|
+
where: "twitter:card",
|
|
124
|
+
message: "Twitter Card tags are absent.",
|
|
125
|
+
fix: 'Add <meta name="twitter:card" content="summary_large_image"> and twitter:title, twitter:description.',
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
// Meta description length
|
|
129
|
+
const metaDescLen = head.metaDescription?.length ?? 0;
|
|
130
|
+
if (!head.metaDescription) {
|
|
131
|
+
findings.push({
|
|
132
|
+
severity: "warning",
|
|
133
|
+
category: "technical",
|
|
134
|
+
where: 'meta[name="description"]',
|
|
135
|
+
message: "Meta description is missing.",
|
|
136
|
+
fix: 'Add <meta name="description" content="120-160 character description.">.',
|
|
137
|
+
estimated_impact: "medium",
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
else if (metaDescLen < 50) {
|
|
141
|
+
findings.push({
|
|
142
|
+
severity: "warning",
|
|
143
|
+
category: "technical",
|
|
144
|
+
where: 'meta[name="description"]',
|
|
145
|
+
message: `Meta description is only ${metaDescLen} chars - too short (ideal: 120-160).`,
|
|
146
|
+
fix: "Expand the meta description to 120-160 characters summarizing the page content.",
|
|
147
|
+
estimated_impact: "low",
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
else if (metaDescLen > 200) {
|
|
151
|
+
findings.push({
|
|
152
|
+
severity: "warning",
|
|
153
|
+
category: "technical",
|
|
154
|
+
where: 'meta[name="description"]',
|
|
155
|
+
message: `Meta description is ${metaDescLen} chars - too long (ideal: 120-160).`,
|
|
156
|
+
fix: "Trim the meta description to under 200 characters.",
|
|
157
|
+
estimated_impact: "low",
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
// Title/OG match
|
|
161
|
+
const titleOgMatch = !head.title || !head.ogTitle
|
|
162
|
+
? true // can't compare if either is missing
|
|
163
|
+
: levenshtein(head.title, head.ogTitle) <= 10;
|
|
164
|
+
if (!titleOgMatch) {
|
|
165
|
+
findings.push({
|
|
166
|
+
severity: "warning",
|
|
167
|
+
category: "technical",
|
|
168
|
+
where: "og:title vs <title>",
|
|
169
|
+
message: "og:title differs significantly from <title> - may signal content inconsistency.",
|
|
170
|
+
fix: "Align og:title with <title> or ensure the difference is intentional.",
|
|
171
|
+
estimated_impact: "low",
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
return {
|
|
175
|
+
url: input.url,
|
|
176
|
+
https,
|
|
177
|
+
canonical: {
|
|
178
|
+
present: !!head.canonical,
|
|
179
|
+
value: head.canonical,
|
|
180
|
+
self_referential: canonicalSelfRef,
|
|
181
|
+
cross_domain: canonicalCrossDomain,
|
|
182
|
+
},
|
|
183
|
+
noindex: head.noindex,
|
|
184
|
+
noindex_header: head.noindexHeader,
|
|
185
|
+
og_tags: {
|
|
186
|
+
title: !!head.ogTitle,
|
|
187
|
+
description: !!head.ogDescription,
|
|
188
|
+
image: !!head.ogImage,
|
|
189
|
+
url: !!head.ogUrl,
|
|
190
|
+
type: !!head.ogType,
|
|
191
|
+
},
|
|
192
|
+
twitter_card: {
|
|
193
|
+
present: !!head.twitterCard,
|
|
194
|
+
card_type: head.twitterCard,
|
|
195
|
+
},
|
|
196
|
+
hreflang: {
|
|
197
|
+
present: head.hreflangTags.length > 0,
|
|
198
|
+
count: head.hreflangTags.length,
|
|
199
|
+
x_default: head.hreflangTags.some((h) => h.lang === "x-default"),
|
|
200
|
+
},
|
|
201
|
+
title_og_match: titleOgMatch,
|
|
202
|
+
meta_description: {
|
|
203
|
+
present: !!head.metaDescription,
|
|
204
|
+
length: metaDescLen,
|
|
205
|
+
},
|
|
206
|
+
findings,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
//# sourceMappingURL=check-technical.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"check-technical.js","sourceRoot":"","sources":["../../src/tools/check-technical.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { type HostDelayMap } from "../lib/fetch.js";
|
|
3
|
+
import { type ExtractedEntity } from "../lib/entities.js";
|
|
4
|
+
import type { Finding } from "../types.js";
|
|
5
|
+
export declare const extractEntitiesInputSchema: z.ZodEffects<z.ZodObject<{
|
|
6
|
+
url: z.ZodOptional<z.ZodString>;
|
|
7
|
+
text: z.ZodOptional<z.ZodString>;
|
|
8
|
+
respect_robots: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
9
|
+
}, "strip", z.ZodTypeAny, {
|
|
10
|
+
respect_robots: boolean;
|
|
11
|
+
url?: string | undefined;
|
|
12
|
+
text?: string | undefined;
|
|
13
|
+
}, {
|
|
14
|
+
url?: string | undefined;
|
|
15
|
+
respect_robots?: boolean | undefined;
|
|
16
|
+
text?: string | undefined;
|
|
17
|
+
}>, {
|
|
18
|
+
respect_robots: boolean;
|
|
19
|
+
url?: string | undefined;
|
|
20
|
+
text?: string | undefined;
|
|
21
|
+
}, {
|
|
22
|
+
url?: string | undefined;
|
|
23
|
+
respect_robots?: boolean | undefined;
|
|
24
|
+
text?: string | undefined;
|
|
25
|
+
}>;
|
|
26
|
+
export type ExtractEntitiesInput = z.infer<typeof extractEntitiesInputSchema>;
|
|
27
|
+
export interface ExtractEntitiesResult {
|
|
28
|
+
entities: ExtractedEntity[];
|
|
29
|
+
entity_count: number;
|
|
30
|
+
connected_entity_count: number;
|
|
31
|
+
citation_density_score: number;
|
|
32
|
+
findings: Finding[];
|
|
33
|
+
}
|
|
34
|
+
export declare function extractEntities(input: ExtractEntitiesInput, hostDelays?: HostDelayMap, robotsCache?: Map<string, string>): Promise<ExtractEntitiesResult>;
|
|
35
|
+
//# sourceMappingURL=extract-entities.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extract-entities.d.ts","sourceRoot":"","sources":["../../src/tools/extract-entities.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAe,KAAK,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAGjE,OAAO,EAIL,KAAK,eAAe,EACrB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAE3C,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;EAQnC,CAAC;AAEL,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAE9E,MAAM,WAAW,qBAAqB;IACpC,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,sBAAsB,EAAE,MAAM,CAAC;IAC/B,sBAAsB,EAAE,MAAM,CAAC;IAC/B,QAAQ,EAAE,OAAO,EAAE,CAAC;CACrB;AAED,wBAAsB,eAAe,CACnC,KAAK,EAAE,oBAAoB,EAC3B,UAAU,CAAC,EAAE,YAAY,EACzB,WAAW,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAChC,OAAO,CAAC,qBAAqB,CAAC,CAsDhC"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// Tool: extract_entities
|
|
2
|
+
// Extracts named entities, linked concepts, and sameAs graph nodes from page content.
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { politeFetch } from "../lib/fetch.js";
|
|
5
|
+
import { parseBody } from "../lib/html.js";
|
|
6
|
+
import { parseJsonLd } from "../lib/schema.js";
|
|
7
|
+
import { extractJsonLdEntities, extractTextEntities, mergeEntities, } from "../lib/entities.js";
|
|
8
|
+
export const extractEntitiesInputSchema = z
|
|
9
|
+
.object({
|
|
10
|
+
url: z.string().url().optional(),
|
|
11
|
+
text: z.string().optional(),
|
|
12
|
+
respect_robots: z.boolean().optional().default(true),
|
|
13
|
+
})
|
|
14
|
+
.refine((d) => d.url !== undefined || d.text !== undefined, {
|
|
15
|
+
message: "One of url or text is required",
|
|
16
|
+
});
|
|
17
|
+
export async function extractEntities(input, hostDelays, robotsCache) {
|
|
18
|
+
const findings = [];
|
|
19
|
+
let bodyText = "";
|
|
20
|
+
let jsonLdBlocks = [];
|
|
21
|
+
if (input.url) {
|
|
22
|
+
const result = await politeFetch(input.url, {
|
|
23
|
+
respectRobots: input.respect_robots,
|
|
24
|
+
hostDelays,
|
|
25
|
+
robotsCache,
|
|
26
|
+
});
|
|
27
|
+
const pageData = parseBody(result.body, input.url);
|
|
28
|
+
bodyText = pageData.bodyText;
|
|
29
|
+
jsonLdBlocks = parseJsonLd(result.body);
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
bodyText = input.text;
|
|
33
|
+
}
|
|
34
|
+
const jsonLdEntities = extractJsonLdEntities(jsonLdBlocks);
|
|
35
|
+
const textEntities = extractTextEntities(bodyText);
|
|
36
|
+
const entities = mergeEntities(jsonLdEntities, textEntities, bodyText);
|
|
37
|
+
const entity_count = entities.length;
|
|
38
|
+
const connected_entity_count = entities.filter((e) => e.same_as.length > 0).length;
|
|
39
|
+
// Threshold: 15 connected entities = score of 100
|
|
40
|
+
const citation_density_score = Math.min(100, Math.round((connected_entity_count / 15) * 100));
|
|
41
|
+
if (entity_count === 0) {
|
|
42
|
+
findings.push({
|
|
43
|
+
severity: "warning",
|
|
44
|
+
category: "authority",
|
|
45
|
+
where: "page-level",
|
|
46
|
+
message: "No named entities detected on this page.",
|
|
47
|
+
fix: "Add JSON-LD schema with named entities (Organization, Person, Product) and sameAs links.",
|
|
48
|
+
estimated_impact: "medium",
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
else if (connected_entity_count < 5) {
|
|
52
|
+
findings.push({
|
|
53
|
+
severity: "warning",
|
|
54
|
+
category: "authority",
|
|
55
|
+
where: "page-level",
|
|
56
|
+
message: `Only ${connected_entity_count} entities have sameAs links. Pages with 15+ connected entities have 4.8x higher AI citation probability.`,
|
|
57
|
+
fix: "Add sameAs links to JSON-LD entity nodes pointing to Wikidata, Wikipedia, LinkedIn, or official brand sites.",
|
|
58
|
+
estimated_impact: "high",
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
entities,
|
|
63
|
+
entity_count,
|
|
64
|
+
connected_entity_count,
|
|
65
|
+
citation_density_score,
|
|
66
|
+
findings,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=extract-entities.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extract-entities.js","sourceRoot":"","sources":["../../src/tools/extract-entities.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { type HostDelayMap } from "../lib/fetch.js";
|
|
3
|
+
import type { Finding } from "../types.js";
|
|
4
|
+
export declare const generateLlmsTxtInputSchema: z.ZodObject<{
|
|
5
|
+
domain: z.ZodString;
|
|
6
|
+
max_pages: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
7
|
+
include_full: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
8
|
+
site_name: z.ZodOptional<z.ZodString>;
|
|
9
|
+
site_description: z.ZodOptional<z.ZodString>;
|
|
10
|
+
}, "strip", z.ZodTypeAny, {
|
|
11
|
+
domain: string;
|
|
12
|
+
max_pages: number;
|
|
13
|
+
include_full: boolean;
|
|
14
|
+
site_name?: string | undefined;
|
|
15
|
+
site_description?: string | undefined;
|
|
16
|
+
}, {
|
|
17
|
+
domain: string;
|
|
18
|
+
max_pages?: number | undefined;
|
|
19
|
+
include_full?: boolean | undefined;
|
|
20
|
+
site_name?: string | undefined;
|
|
21
|
+
site_description?: string | undefined;
|
|
22
|
+
}>;
|
|
23
|
+
export type GenerateLlmsTxtInput = z.infer<typeof generateLlmsTxtInputSchema>;
|
|
24
|
+
export interface LlmsTxtResult {
|
|
25
|
+
llms_txt: string;
|
|
26
|
+
llms_full_txt: string | null;
|
|
27
|
+
pages_indexed: number;
|
|
28
|
+
validation_issues: Finding[];
|
|
29
|
+
suggested_path: "/llms.txt";
|
|
30
|
+
}
|
|
31
|
+
export declare function generateLlmsTxtTool(input: GenerateLlmsTxtInput, hostDelays?: HostDelayMap, robotsCache?: Map<string, string>): Promise<LlmsTxtResult>;
|
|
32
|
+
//# sourceMappingURL=generate-llms-txt.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate-llms-txt.d.ts","sourceRoot":"","sources":["../../src/tools/generate-llms-txt.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAe,KAAK,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAWjE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAE3C,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;EAMrC,CAAC;AAEH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAE9E,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,aAAa,EAAE,MAAM,CAAC;IACtB,iBAAiB,EAAE,OAAO,EAAE,CAAC;IAC7B,cAAc,EAAE,WAAW,CAAC;CAC7B;AAaD,wBAAsB,mBAAmB,CACvC,KAAK,EAAE,oBAAoB,EAC3B,UAAU,CAAC,EAAE,YAAY,EACzB,WAAW,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAChC,OAAO,CAAC,aAAa,CAAC,CA4IxB"}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
// Tool: generate_llms_txt
|
|
2
|
+
// Generates a valid llms.txt (and optionally llms-full.txt) for a domain.
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { politeFetch } from "../lib/fetch.js";
|
|
5
|
+
import { parseHead, parseBody } from "../lib/html.js";
|
|
6
|
+
import { XMLParser } from "fast-xml-parser";
|
|
7
|
+
import { checkSitemap } from "./check-sitemap.js";
|
|
8
|
+
import { groupPagesBySection, generateLlmsTxt as buildLlmsTxt, generateLlmsFullTxt, validateLlmsTxtContent, } from "../lib/llms-txt.js";
|
|
9
|
+
export const generateLlmsTxtInputSchema = z.object({
|
|
10
|
+
domain: z.string().min(3),
|
|
11
|
+
max_pages: z.number().int().min(1).max(100).optional().default(30),
|
|
12
|
+
include_full: z.boolean().optional().default(false),
|
|
13
|
+
site_name: z.string().optional(),
|
|
14
|
+
site_description: z.string().optional(),
|
|
15
|
+
});
|
|
16
|
+
function normalizeDomain(domain) {
|
|
17
|
+
if (domain.startsWith("http://") || domain.startsWith("https://")) {
|
|
18
|
+
try {
|
|
19
|
+
return new URL(domain).hostname;
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return domain;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return domain.replace(/\/$/, "");
|
|
26
|
+
}
|
|
27
|
+
export async function generateLlmsTxtTool(input, hostDelays, robotsCache) {
|
|
28
|
+
const hostname = normalizeDomain(input.domain);
|
|
29
|
+
const baseUrl = `https://${hostname}`;
|
|
30
|
+
const validation_issues = [];
|
|
31
|
+
// Discover pages from sitemap
|
|
32
|
+
let pageUrls = [];
|
|
33
|
+
try {
|
|
34
|
+
const sitemapResult = await checkSitemap({ domain: hostname, max_urls_to_check: input.max_pages }, hostDelays, robotsCache);
|
|
35
|
+
if (sitemapResult.status === "found" && sitemapResult.sitemap_url) {
|
|
36
|
+
// Re-fetch the sitemap to get priority-sorted URLs
|
|
37
|
+
try {
|
|
38
|
+
const sRes = await politeFetch(sitemapResult.sitemap_url, {
|
|
39
|
+
respectRobots: false,
|
|
40
|
+
hostDelays,
|
|
41
|
+
robotsCache,
|
|
42
|
+
});
|
|
43
|
+
const parser = new XMLParser({ ignoreAttributes: false });
|
|
44
|
+
const parsed = parser.parse(sRes.body);
|
|
45
|
+
const urlset = parsed["urlset"];
|
|
46
|
+
if (urlset) {
|
|
47
|
+
const urlEntries = urlset["url"];
|
|
48
|
+
const urlList = Array.isArray(urlEntries)
|
|
49
|
+
? urlEntries
|
|
50
|
+
: urlEntries ? [urlEntries] : [];
|
|
51
|
+
pageUrls = urlList
|
|
52
|
+
.sort((a, b) => {
|
|
53
|
+
const pa = parseFloat(String(a["priority"] ?? "0.5"));
|
|
54
|
+
const pb = parseFloat(String(b["priority"] ?? "0.5"));
|
|
55
|
+
return pb - pa;
|
|
56
|
+
})
|
|
57
|
+
.slice(0, input.max_pages)
|
|
58
|
+
.map((u) => String(u["loc"] ?? ""))
|
|
59
|
+
.filter(Boolean);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
// fall through to root page fallback
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
// sitemap unavailable
|
|
69
|
+
}
|
|
70
|
+
// Fallback to root page if no sitemap
|
|
71
|
+
if (pageUrls.length === 0) {
|
|
72
|
+
pageUrls = [baseUrl];
|
|
73
|
+
validation_issues.push({
|
|
74
|
+
severity: "warning",
|
|
75
|
+
category: "sitemap",
|
|
76
|
+
where: `https://${hostname}/sitemap.xml`,
|
|
77
|
+
message: "No sitemap found - llms.txt generated from root page only.",
|
|
78
|
+
fix: "Create a sitemap.xml to enable comprehensive llms.txt generation.",
|
|
79
|
+
estimated_impact: "medium",
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
// Fetch each page
|
|
83
|
+
const pages = [];
|
|
84
|
+
let siteName = input.site_name ?? hostname;
|
|
85
|
+
let siteDescription = input.site_description ?? `Content from ${hostname}.`;
|
|
86
|
+
for (const url of pageUrls) {
|
|
87
|
+
if (pages.length >= input.max_pages)
|
|
88
|
+
break;
|
|
89
|
+
try {
|
|
90
|
+
const res = await politeFetch(url, {
|
|
91
|
+
respectRobots: true,
|
|
92
|
+
hostDelays: hostDelays ?? new Map(),
|
|
93
|
+
robotsCache,
|
|
94
|
+
});
|
|
95
|
+
const head = parseHead(res.body);
|
|
96
|
+
const body = parseBody(res.body, url);
|
|
97
|
+
let pathFallback = "";
|
|
98
|
+
try {
|
|
99
|
+
pathFallback = new URL(url).pathname.replace(/\//g, " ").trim();
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
pathFallback = url;
|
|
103
|
+
}
|
|
104
|
+
const title = head.ogTitle ?? head.title ?? (pathFallback || url);
|
|
105
|
+
const description = head.metaDescription ??
|
|
106
|
+
head.ogDescription ??
|
|
107
|
+
(body.paragraphs[0] ? body.paragraphs[0].substring(0, 120) : "");
|
|
108
|
+
// Use site root for name/description if this is the home page
|
|
109
|
+
if (url === baseUrl || url === `${baseUrl}/`) {
|
|
110
|
+
if (!input.site_name && head.ogTitle)
|
|
111
|
+
siteName = head.ogTitle;
|
|
112
|
+
if (!input.site_description && head.metaDescription) {
|
|
113
|
+
siteDescription = head.metaDescription;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
pages.push({
|
|
117
|
+
url,
|
|
118
|
+
title,
|
|
119
|
+
description,
|
|
120
|
+
fullText: body.bodyText.substring(0, 5000),
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
// skip pages that fail to fetch
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (pages.length === 0) {
|
|
128
|
+
pages.push({ url: baseUrl, title: siteName, description: siteDescription });
|
|
129
|
+
}
|
|
130
|
+
const groups = groupPagesBySection(pages);
|
|
131
|
+
const llms_txt = buildLlmsTxt(siteName, siteDescription, groups);
|
|
132
|
+
let llms_full_txt = null;
|
|
133
|
+
if (input.include_full) {
|
|
134
|
+
const { content, truncated } = generateLlmsFullTxt(siteName, siteDescription, pages);
|
|
135
|
+
llms_full_txt = content;
|
|
136
|
+
if (truncated) {
|
|
137
|
+
validation_issues.push({
|
|
138
|
+
severity: "info",
|
|
139
|
+
category: "llms_txt",
|
|
140
|
+
where: "llms-full.txt",
|
|
141
|
+
message: "llms-full.txt was truncated at 500KB.",
|
|
142
|
+
fix: "Reduce max_pages or trim per-page body text extraction.",
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
// Validate generated output
|
|
147
|
+
const structuralIssues = validateLlmsTxtContent(llms_txt);
|
|
148
|
+
validation_issues.push(...structuralIssues);
|
|
149
|
+
return {
|
|
150
|
+
llms_txt,
|
|
151
|
+
llms_full_txt,
|
|
152
|
+
pages_indexed: pages.length,
|
|
153
|
+
validation_issues,
|
|
154
|
+
suggested_path: "/llms.txt",
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
//# sourceMappingURL=generate-llms-txt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate-llms-txt.js","sourceRoot":"","sources":["../../src/tools/generate-llms-txt.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { type HostDelayMap } from "../lib/fetch.js";
|
|
3
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4
|
+
export declare const rewriteForAeoInputSchema: z.ZodEffects<z.ZodObject<{
|
|
5
|
+
url: z.ZodOptional<z.ZodString>;
|
|
6
|
+
text: z.ZodOptional<z.ZodString>;
|
|
7
|
+
target_query: z.ZodString;
|
|
8
|
+
format: z.ZodDefault<z.ZodEnum<["article", "faq", "howto", "comparison"]>>;
|
|
9
|
+
max_words: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
10
|
+
respect_robots: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
11
|
+
}, "strip", z.ZodTypeAny, {
|
|
12
|
+
respect_robots: boolean;
|
|
13
|
+
target_query: string;
|
|
14
|
+
format: "article" | "faq" | "howto" | "comparison";
|
|
15
|
+
max_words: number;
|
|
16
|
+
url?: string | undefined;
|
|
17
|
+
text?: string | undefined;
|
|
18
|
+
}, {
|
|
19
|
+
target_query: string;
|
|
20
|
+
url?: string | undefined;
|
|
21
|
+
respect_robots?: boolean | undefined;
|
|
22
|
+
text?: string | undefined;
|
|
23
|
+
format?: "article" | "faq" | "howto" | "comparison" | undefined;
|
|
24
|
+
max_words?: number | undefined;
|
|
25
|
+
}>, {
|
|
26
|
+
respect_robots: boolean;
|
|
27
|
+
target_query: string;
|
|
28
|
+
format: "article" | "faq" | "howto" | "comparison";
|
|
29
|
+
max_words: number;
|
|
30
|
+
url?: string | undefined;
|
|
31
|
+
text?: string | undefined;
|
|
32
|
+
}, {
|
|
33
|
+
target_query: string;
|
|
34
|
+
url?: string | undefined;
|
|
35
|
+
respect_robots?: boolean | undefined;
|
|
36
|
+
text?: string | undefined;
|
|
37
|
+
format?: "article" | "faq" | "howto" | "comparison" | undefined;
|
|
38
|
+
max_words?: number | undefined;
|
|
39
|
+
}>;
|
|
40
|
+
export type RewriteForAeoInput = z.infer<typeof rewriteForAeoInputSchema>;
|
|
41
|
+
export interface RewriteAeoResult {
|
|
42
|
+
rewritten_text: string;
|
|
43
|
+
schema_additions: string;
|
|
44
|
+
changes_made: string[];
|
|
45
|
+
before_score: number;
|
|
46
|
+
after_score: number;
|
|
47
|
+
mode: "sampling" | "prompt_template";
|
|
48
|
+
}
|
|
49
|
+
export declare function rewriteForAeo(input: RewriteForAeoInput, hostDelays?: HostDelayMap, robotsCache?: Map<string, string>, server?: McpServer): Promise<RewriteAeoResult>;
|
|
50
|
+
//# sourceMappingURL=rewrite-for-aeo.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rewrite-for-aeo.d.ts","sourceRoot":"","sources":["../../src/tools/rewrite-for-aeo.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAe,KAAK,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAGjE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEzE,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAWjC,CAAC;AAEL,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAE1E,MAAM,WAAW,gBAAgB;IAC/B,cAAc,EAAE,MAAM,CAAC;IACvB,gBAAgB,EAAE,MAAM,CAAC;IACzB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,UAAU,GAAG,iBAAiB,CAAC;CACtC;AAkBD,wBAAsB,aAAa,CACjC,KAAK,EAAE,kBAAkB,EACzB,UAAU,CAAC,EAAE,YAAY,EACzB,WAAW,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EACjC,MAAM,CAAC,EAAE,SAAS,GACjB,OAAO,CAAC,gBAAgB,CAAC,CAuG3B"}
|