claude-code-ultimate-guide-mcp 1.0.3 → 1.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/content/llms.txt +96 -150
- package/content/reference.yaml +161 -161
- package/dist/index.js +578 -5
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -13986,14 +13986,37 @@ function searchGuide(query, limit = 10) {
|
|
|
13986
13986
|
|
|
13987
13987
|
// src/lib/urls.ts
|
|
13988
13988
|
var GITHUB_BASE = "https://github.com/FlorianBruniaux/claude-code-ultimate-guide/blob/main";
|
|
13989
|
-
var GUIDE_SITE_BASE = "https://cc.bruniaux.com/guide";
|
|
13989
|
+
var GUIDE_SITE_BASE = "https://cc.bruniaux.com/guide/ultimate-guide";
|
|
13990
|
+
var CHAPTER_RANGES = [
|
|
13991
|
+
{ from: 1, to: 220, slug: "00-introduction" },
|
|
13992
|
+
{ from: 221, to: 1378, slug: "01-quick-start" },
|
|
13993
|
+
{ from: 1379, to: 4231, slug: "02-core-workflow" },
|
|
13994
|
+
{ from: 4232, to: 5648, slug: "03-memory-files" },
|
|
13995
|
+
{ from: 5649, to: 6361, slug: "04-agents" },
|
|
13996
|
+
{ from: 6362, to: 7470, slug: "05-skills" },
|
|
13997
|
+
{ from: 7471, to: 8106, slug: "06-commands" },
|
|
13998
|
+
{ from: 8107, to: 9482, slug: "07-hooks" },
|
|
13999
|
+
{ from: 9483, to: 12118, slug: "08-mcp" },
|
|
14000
|
+
{ from: 12119, to: 19831, slug: "09-advanced-patterns" },
|
|
14001
|
+
{ from: 19832, to: 20760, slug: "10-reference" },
|
|
14002
|
+
{ from: 20761, to: 21384, slug: "11-ai-ecosystem" },
|
|
14003
|
+
{ from: 21385, to: Infinity, slug: "12-appendices" }
|
|
14004
|
+
];
|
|
14005
|
+
function lineToChapterSlug(line) {
|
|
14006
|
+
for (const range of CHAPTER_RANGES) {
|
|
14007
|
+
if (line >= range.from && line <= range.to) return range.slug;
|
|
14008
|
+
}
|
|
14009
|
+
return "12-appendices";
|
|
14010
|
+
}
|
|
13990
14011
|
function githubUrl(filePath, line) {
|
|
13991
14012
|
const base = `${GITHUB_BASE}/${filePath}`;
|
|
13992
14013
|
return line ? `${base}#L${line}` : base;
|
|
13993
14014
|
}
|
|
13994
14015
|
function guideSiteUrl(filePath, line) {
|
|
13995
14016
|
if (filePath !== "guide/ultimate-guide.md") return null;
|
|
13996
|
-
|
|
14017
|
+
if (!line) return `${GUIDE_SITE_BASE}/`;
|
|
14018
|
+
const chapterSlug = lineToChapterSlug(line);
|
|
14019
|
+
return `${GUIDE_SITE_BASE}/${chapterSlug}/`;
|
|
13997
14020
|
}
|
|
13998
14021
|
function formatLinks(filePath, line) {
|
|
13999
14022
|
const gh = githubUrl(filePath, line);
|
|
@@ -14512,7 +14535,7 @@ function registerChangelog(server) {
|
|
|
14512
14535
|
for (const r of ccReleases) {
|
|
14513
14536
|
lines.push(`### v${r.version} (${r.date})`);
|
|
14514
14537
|
for (const h of r.highlights ?? []) lines.push(`- ${h}`);
|
|
14515
|
-
lines.push(`GitHub: ${githubUrl("guide/claude-code-releases.md")}`);
|
|
14538
|
+
lines.push(`GitHub: ${githubUrl("guide/core/claude-code-releases.md")}`);
|
|
14516
14539
|
lines.push("");
|
|
14517
14540
|
}
|
|
14518
14541
|
}
|
|
@@ -14630,7 +14653,7 @@ function registerListExamples(server) {
|
|
|
14630
14653
|
}
|
|
14631
14654
|
|
|
14632
14655
|
// src/tools/releases.ts
|
|
14633
|
-
var RELEASES_GITHUB = githubUrl("guide/claude-code-releases.md");
|
|
14656
|
+
var RELEASES_GITHUB = githubUrl("guide/core/claude-code-releases.md");
|
|
14634
14657
|
function registerReleases(server) {
|
|
14635
14658
|
server.tool(
|
|
14636
14659
|
"get_release",
|
|
@@ -14696,7 +14719,7 @@ Full history: ${RELEASES_GITHUB}`
|
|
|
14696
14719
|
}
|
|
14697
14720
|
|
|
14698
14721
|
// src/tools/compare-versions.ts
|
|
14699
|
-
var RELEASES_GITHUB2 = githubUrl("guide/claude-code-releases.md");
|
|
14722
|
+
var RELEASES_GITHUB2 = githubUrl("guide/core/claude-code-releases.md");
|
|
14700
14723
|
function parseSemver(v) {
|
|
14701
14724
|
const parts = v.replace(/^v/, "").split(".").map(Number);
|
|
14702
14725
|
return [parts[0] ?? 0, parts[1] ?? 0, parts[2] ?? 0];
|
|
@@ -15102,6 +15125,555 @@ function registerSearchExamples(server) {
|
|
|
15102
15125
|
);
|
|
15103
15126
|
}
|
|
15104
15127
|
|
|
15128
|
+
// src/lib/docs-cache.ts
|
|
15129
|
+
import { createHash } from "crypto";
|
|
15130
|
+
import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, renameSync, writeFileSync } from "fs";
|
|
15131
|
+
import { homedir } from "os";
|
|
15132
|
+
import { resolve } from "path";
|
|
15133
|
+
var ANTHROPIC_DOCS_URL = "https://code.claude.com/docs/llms-full.txt";
|
|
15134
|
+
var SNAPSHOT_DIR = resolve(homedir(), ".cache", "claude-code-guide");
|
|
15135
|
+
function getSnapshotDir() {
|
|
15136
|
+
return SNAPSHOT_DIR;
|
|
15137
|
+
}
|
|
15138
|
+
function indexPath(type) {
|
|
15139
|
+
return resolve(SNAPSHOT_DIR, `anthropic-docs-${type}-index.json`);
|
|
15140
|
+
}
|
|
15141
|
+
function contentPath(type) {
|
|
15142
|
+
return resolve(SNAPSHOT_DIR, `anthropic-docs-${type}-content.json`);
|
|
15143
|
+
}
|
|
15144
|
+
async function fetchOfficialDocs() {
|
|
15145
|
+
const response = await fetch(ANTHROPIC_DOCS_URL, {
|
|
15146
|
+
headers: { "User-Agent": "claude-code-ultimate-guide-mcp/1.1.0" },
|
|
15147
|
+
signal: AbortSignal.timeout(15e3)
|
|
15148
|
+
});
|
|
15149
|
+
if (!response.ok) {
|
|
15150
|
+
throw new Error(`HTTP ${response.status} fetching ${ANTHROPIC_DOCS_URL}`);
|
|
15151
|
+
}
|
|
15152
|
+
const text = await response.text();
|
|
15153
|
+
if (text.length < 5e5) {
|
|
15154
|
+
throw new Error(
|
|
15155
|
+
`Response looks malformed (got ${(text.length / 1024).toFixed(0)}KB, expected >500KB). The URL format may have changed.`
|
|
15156
|
+
);
|
|
15157
|
+
}
|
|
15158
|
+
return text;
|
|
15159
|
+
}
|
|
15160
|
+
function parseIntoSections(raw) {
|
|
15161
|
+
const lines = raw.split("\n");
|
|
15162
|
+
const sections = {};
|
|
15163
|
+
const contents = {};
|
|
15164
|
+
let currentSlug = null;
|
|
15165
|
+
let currentTitle = null;
|
|
15166
|
+
let currentSourceUrl = null;
|
|
15167
|
+
let currentStartLine = 0;
|
|
15168
|
+
let titleCandidate = null;
|
|
15169
|
+
let titleCandidateLine = -1;
|
|
15170
|
+
const flushSection = (endLine) => {
|
|
15171
|
+
if (!currentSlug || !currentTitle || !currentSourceUrl) return;
|
|
15172
|
+
const sectionLines = lines.slice(currentStartLine, endLine);
|
|
15173
|
+
const content2 = sectionLines.join("\n").trimEnd();
|
|
15174
|
+
if (content2.length === 0) return;
|
|
15175
|
+
const hash2 = createHash("sha256").update(content2).digest("hex").slice(0, 16);
|
|
15176
|
+
sections[currentSlug] = {
|
|
15177
|
+
slug: currentSlug,
|
|
15178
|
+
title: currentTitle,
|
|
15179
|
+
sourceUrl: currentSourceUrl,
|
|
15180
|
+
hash: hash2,
|
|
15181
|
+
lineCount: sectionLines.length,
|
|
15182
|
+
charCount: content2.length
|
|
15183
|
+
};
|
|
15184
|
+
contents[currentSlug] = { content: content2 };
|
|
15185
|
+
};
|
|
15186
|
+
for (let i = 0; i < lines.length; i++) {
|
|
15187
|
+
const line = lines[i];
|
|
15188
|
+
if (line.startsWith("# ") && !line.startsWith("## ")) {
|
|
15189
|
+
titleCandidate = line;
|
|
15190
|
+
titleCandidateLine = i;
|
|
15191
|
+
continue;
|
|
15192
|
+
}
|
|
15193
|
+
const sourceMatch = line.match(
|
|
15194
|
+
/^Source:\s+(https:\/\/code\.claude\.com\/docs\/(?:en\/)?([a-zA-Z0-9-]+))\s*$/
|
|
15195
|
+
);
|
|
15196
|
+
if (sourceMatch && titleCandidate !== null && i === titleCandidateLine + 1) {
|
|
15197
|
+
const sourceUrl = sourceMatch[1];
|
|
15198
|
+
const slug = sourceMatch[2];
|
|
15199
|
+
flushSection(titleCandidateLine);
|
|
15200
|
+
currentSlug = slug;
|
|
15201
|
+
currentTitle = titleCandidate;
|
|
15202
|
+
currentSourceUrl = sourceUrl;
|
|
15203
|
+
currentStartLine = titleCandidateLine;
|
|
15204
|
+
titleCandidate = null;
|
|
15205
|
+
titleCandidateLine = -1;
|
|
15206
|
+
continue;
|
|
15207
|
+
}
|
|
15208
|
+
if (titleCandidate !== null && i > titleCandidateLine + 1) {
|
|
15209
|
+
titleCandidate = null;
|
|
15210
|
+
titleCandidateLine = -1;
|
|
15211
|
+
}
|
|
15212
|
+
}
|
|
15213
|
+
flushSection(lines.length);
|
|
15214
|
+
if (Object.keys(sections).length === 0) {
|
|
15215
|
+
throw new Error(
|
|
15216
|
+
"Section parser found 0 sections \u2014 llms-full.txt format may have changed. Please open an issue at https://github.com/FlorianBruniaux/claude-code-ultimate-guide"
|
|
15217
|
+
);
|
|
15218
|
+
}
|
|
15219
|
+
const contentHash = createHash("sha256").update(raw).digest("hex").slice(0, 16);
|
|
15220
|
+
const index = {
|
|
15221
|
+
schemaVersion: 1,
|
|
15222
|
+
fetchedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
15223
|
+
contentHash,
|
|
15224
|
+
sectionCount: Object.keys(sections).length,
|
|
15225
|
+
sections
|
|
15226
|
+
};
|
|
15227
|
+
const content = {
|
|
15228
|
+
schemaVersion: 1,
|
|
15229
|
+
sections: contents
|
|
15230
|
+
};
|
|
15231
|
+
return { index, content };
|
|
15232
|
+
}
|
|
15233
|
+
function saveSnapshot(type, index, content) {
|
|
15234
|
+
mkdirSync(SNAPSHOT_DIR, { recursive: true });
|
|
15235
|
+
const iPath = indexPath(type);
|
|
15236
|
+
writeFileSync(`${iPath}.tmp`, JSON.stringify(index, null, 2), "utf8");
|
|
15237
|
+
renameSync(`${iPath}.tmp`, iPath);
|
|
15238
|
+
const cPath = contentPath(type);
|
|
15239
|
+
writeFileSync(`${cPath}.tmp`, JSON.stringify(content, null, 2), "utf8");
|
|
15240
|
+
renameSync(`${cPath}.tmp`, cPath);
|
|
15241
|
+
}
|
|
15242
|
+
function loadIndex(type) {
|
|
15243
|
+
const path = indexPath(type);
|
|
15244
|
+
if (!existsSync2(path)) return null;
|
|
15245
|
+
try {
|
|
15246
|
+
const data = JSON.parse(readFileSync2(path, "utf8"));
|
|
15247
|
+
if (data.schemaVersion !== 1) return null;
|
|
15248
|
+
return data;
|
|
15249
|
+
} catch {
|
|
15250
|
+
return null;
|
|
15251
|
+
}
|
|
15252
|
+
}
|
|
15253
|
+
function loadSections(type, slugs) {
|
|
15254
|
+
const path = contentPath(type);
|
|
15255
|
+
if (!existsSync2(path)) return {};
|
|
15256
|
+
try {
|
|
15257
|
+
const data = JSON.parse(readFileSync2(path, "utf8"));
|
|
15258
|
+
const result = {};
|
|
15259
|
+
for (const slug of slugs) {
|
|
15260
|
+
const section = data.sections[slug];
|
|
15261
|
+
if (section) result[slug] = section.content;
|
|
15262
|
+
}
|
|
15263
|
+
return result;
|
|
15264
|
+
} catch {
|
|
15265
|
+
return {};
|
|
15266
|
+
}
|
|
15267
|
+
}
|
|
15268
|
+
function snapshotAgeDays(index) {
|
|
15269
|
+
return Math.floor((Date.now() - new Date(index.fetchedAt).getTime()) / (1e3 * 60 * 60 * 24));
|
|
15270
|
+
}
|
|
15271
|
+
|
|
15272
|
+
// src/lib/docs-diff.ts
|
|
15273
|
+
function diffSnapshots(baseline, current) {
|
|
15274
|
+
const baselineSlugs = new Set(Object.keys(baseline.sections));
|
|
15275
|
+
const currentSlugs = new Set(Object.keys(current.sections));
|
|
15276
|
+
const added = [];
|
|
15277
|
+
const removed = [];
|
|
15278
|
+
const modified = [];
|
|
15279
|
+
let unchanged = 0;
|
|
15280
|
+
for (const slug of currentSlugs) {
|
|
15281
|
+
if (!baselineSlugs.has(slug)) {
|
|
15282
|
+
const s = current.sections[slug];
|
|
15283
|
+
added.push({ slug, title: s.title, sourceUrl: s.sourceUrl, kind: "added" });
|
|
15284
|
+
}
|
|
15285
|
+
}
|
|
15286
|
+
for (const slug of baselineSlugs) {
|
|
15287
|
+
if (!currentSlugs.has(slug)) {
|
|
15288
|
+
const s = baseline.sections[slug];
|
|
15289
|
+
removed.push({ slug, title: s.title, sourceUrl: s.sourceUrl, kind: "removed" });
|
|
15290
|
+
}
|
|
15291
|
+
}
|
|
15292
|
+
for (const slug of baselineSlugs) {
|
|
15293
|
+
if (!currentSlugs.has(slug)) continue;
|
|
15294
|
+
const base = baseline.sections[slug];
|
|
15295
|
+
const curr = current.sections[slug];
|
|
15296
|
+
if (base.hash === curr.hash) {
|
|
15297
|
+
unchanged++;
|
|
15298
|
+
continue;
|
|
15299
|
+
}
|
|
15300
|
+
modified.push({
|
|
15301
|
+
slug,
|
|
15302
|
+
title: curr.title,
|
|
15303
|
+
sourceUrl: curr.sourceUrl,
|
|
15304
|
+
kind: "modified",
|
|
15305
|
+
linesBefore: base.lineCount,
|
|
15306
|
+
linesAfter: curr.lineCount,
|
|
15307
|
+
charsBefore: base.charCount,
|
|
15308
|
+
charsAfter: curr.charCount,
|
|
15309
|
+
lineDelta: curr.lineCount - base.lineCount
|
|
15310
|
+
// firstChangedLine is enriched by the tool layer after loading content
|
|
15311
|
+
});
|
|
15312
|
+
}
|
|
15313
|
+
return {
|
|
15314
|
+
unchanged,
|
|
15315
|
+
added,
|
|
15316
|
+
removed,
|
|
15317
|
+
modified,
|
|
15318
|
+
baselineDate: baseline.fetchedAt,
|
|
15319
|
+
currentDate: current.fetchedAt
|
|
15320
|
+
};
|
|
15321
|
+
}
|
|
15322
|
+
function findFirstChangedLine(baseContent, currentContent) {
|
|
15323
|
+
const baseLines = baseContent.split("\n");
|
|
15324
|
+
const currLines = currentContent.split("\n");
|
|
15325
|
+
const len = Math.min(baseLines.length, currLines.length);
|
|
15326
|
+
for (let i = 0; i < len; i++) {
|
|
15327
|
+
if (baseLines[i] !== currLines[i]) {
|
|
15328
|
+
return currLines[i].slice(0, 120);
|
|
15329
|
+
}
|
|
15330
|
+
}
|
|
15331
|
+
if (currLines.length > baseLines.length) {
|
|
15332
|
+
return (currLines[baseLines.length] ?? "").slice(0, 120);
|
|
15333
|
+
}
|
|
15334
|
+
return "";
|
|
15335
|
+
}
|
|
15336
|
+
|
|
15337
|
+
// src/tools/official-docs.ts
|
|
15338
|
+
var STALENESS_WARN_DAYS = 30;
|
|
15339
|
+
function formatDate(iso) {
|
|
15340
|
+
return iso.slice(0, 10);
|
|
15341
|
+
}
|
|
15342
|
+
function formatSize(bytes) {
|
|
15343
|
+
if (bytes >= 1e6) return `${(bytes / 1e6).toFixed(2)} MB`;
|
|
15344
|
+
return `${(bytes / 1024).toFixed(0)} KB`;
|
|
15345
|
+
}
|
|
15346
|
+
function extractExcerpt(content, query, maxLen = 300) {
|
|
15347
|
+
const lowerContent = content.toLowerCase();
|
|
15348
|
+
const lowerQuery = query.toLowerCase();
|
|
15349
|
+
const idx = lowerContent.indexOf(lowerQuery);
|
|
15350
|
+
if (idx === -1) {
|
|
15351
|
+
const excerpt = content.slice(0, maxLen);
|
|
15352
|
+
return excerpt + (content.length > maxLen ? "\u2026" : "");
|
|
15353
|
+
}
|
|
15354
|
+
const start = Math.max(0, idx - 100);
|
|
15355
|
+
const end = Math.min(content.length, idx + query.length + 200);
|
|
15356
|
+
const prefix = start > 0 ? "\u2026" : "";
|
|
15357
|
+
const suffix = end < content.length ? "\u2026" : "";
|
|
15358
|
+
return prefix + content.slice(start, end) + suffix;
|
|
15359
|
+
}
|
|
15360
|
+
function scoreSection(title, content, query) {
|
|
15361
|
+
const q = query.toLowerCase();
|
|
15362
|
+
const titleScore = title.toLowerCase().includes(q) ? 3 : 0;
|
|
15363
|
+
const bodyScore = content.toLowerCase().includes(q) ? 1 : 0;
|
|
15364
|
+
return titleScore + bodyScore;
|
|
15365
|
+
}
|
|
15366
|
+
function registerOfficialDocs(server) {
|
|
15367
|
+
server.tool(
|
|
15368
|
+
"init_official_docs",
|
|
15369
|
+
"Fetch the official Anthropic Claude Code docs (llms-full.txt) and store a local snapshot as the diff baseline. Run this first. Safe to re-run \u2014 overwrites previous baseline AND current. Takes ~5s (fetches ~1.2MB from Anthropic).",
|
|
15370
|
+
{},
|
|
15371
|
+
{ readOnlyHint: false, destructiveHint: false, openWorldHint: true },
|
|
15372
|
+
async () => {
|
|
15373
|
+
let raw;
|
|
15374
|
+
try {
|
|
15375
|
+
raw = await fetchOfficialDocs();
|
|
15376
|
+
} catch (err) {
|
|
15377
|
+
return {
|
|
15378
|
+
content: [{
|
|
15379
|
+
type: "text",
|
|
15380
|
+
text: `Failed to fetch official docs: ${err instanceof Error ? err.message : String(err)}
|
|
15381
|
+
|
|
15382
|
+
Check network connectivity and retry.`
|
|
15383
|
+
}],
|
|
15384
|
+
isError: true
|
|
15385
|
+
};
|
|
15386
|
+
}
|
|
15387
|
+
let parsed;
|
|
15388
|
+
try {
|
|
15389
|
+
parsed = parseIntoSections(raw);
|
|
15390
|
+
} catch (err) {
|
|
15391
|
+
return {
|
|
15392
|
+
content: [{
|
|
15393
|
+
type: "text",
|
|
15394
|
+
text: `Parsing failed: ${err instanceof Error ? err.message : String(err)}`
|
|
15395
|
+
}],
|
|
15396
|
+
isError: true
|
|
15397
|
+
};
|
|
15398
|
+
}
|
|
15399
|
+
try {
|
|
15400
|
+
saveSnapshot("baseline", parsed.index, parsed.content);
|
|
15401
|
+
saveSnapshot("current", parsed.index, parsed.content);
|
|
15402
|
+
} catch (err) {
|
|
15403
|
+
return {
|
|
15404
|
+
content: [{
|
|
15405
|
+
type: "text",
|
|
15406
|
+
text: `Failed to save snapshot to disk: ${err instanceof Error ? err.message : String(err)}
|
|
15407
|
+
|
|
15408
|
+
Check that ${getSnapshotDir()} is writable.`
|
|
15409
|
+
}],
|
|
15410
|
+
isError: true
|
|
15411
|
+
};
|
|
15412
|
+
}
|
|
15413
|
+
const { index } = parsed;
|
|
15414
|
+
const totalChars = Object.values(index.sections).reduce((s, sec) => s + sec.charCount, 0);
|
|
15415
|
+
const lines = [
|
|
15416
|
+
"# Official Docs Snapshot Created",
|
|
15417
|
+
"",
|
|
15418
|
+
`Fetched: ${index.fetchedAt}`,
|
|
15419
|
+
`Sections: ${index.sectionCount}`,
|
|
15420
|
+
`Total size: ${formatSize(totalChars)}`,
|
|
15421
|
+
`Snapshot dir: ${getSnapshotDir()}`,
|
|
15422
|
+
"",
|
|
15423
|
+
"Both baseline and current snapshots have been set.",
|
|
15424
|
+
"",
|
|
15425
|
+
"Next steps:",
|
|
15426
|
+
" - Run refresh_official_docs() to update current without touching the baseline",
|
|
15427
|
+
" - Run diff_official_docs() to see changes between baseline and current",
|
|
15428
|
+
" - Run search_official_docs(query) to search the official docs"
|
|
15429
|
+
];
|
|
15430
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
15431
|
+
}
|
|
15432
|
+
);
|
|
15433
|
+
server.tool(
|
|
15434
|
+
"refresh_official_docs",
|
|
15435
|
+
'Re-fetch the official Anthropic Claude Code docs and update the "current" snapshot without touching the baseline. Run this to update the comparison target before diffing. Takes ~5s (fetches ~1.2MB from Anthropic).',
|
|
15436
|
+
{},
|
|
15437
|
+
{ readOnlyHint: false, destructiveHint: false, openWorldHint: true },
|
|
15438
|
+
async () => {
|
|
15439
|
+
const baseline = loadIndex("baseline");
|
|
15440
|
+
if (!baseline) {
|
|
15441
|
+
return {
|
|
15442
|
+
content: [{
|
|
15443
|
+
type: "text",
|
|
15444
|
+
text: "No baseline snapshot found. Run init_official_docs() first to create both the baseline and the initial current snapshot."
|
|
15445
|
+
}],
|
|
15446
|
+
isError: true
|
|
15447
|
+
};
|
|
15448
|
+
}
|
|
15449
|
+
let raw;
|
|
15450
|
+
try {
|
|
15451
|
+
raw = await fetchOfficialDocs();
|
|
15452
|
+
} catch (err) {
|
|
15453
|
+
return {
|
|
15454
|
+
content: [{
|
|
15455
|
+
type: "text",
|
|
15456
|
+
text: `Failed to fetch official docs: ${err instanceof Error ? err.message : String(err)}
|
|
15457
|
+
|
|
15458
|
+
Your existing snapshots are intact.`
|
|
15459
|
+
}],
|
|
15460
|
+
isError: true
|
|
15461
|
+
};
|
|
15462
|
+
}
|
|
15463
|
+
let parsed;
|
|
15464
|
+
try {
|
|
15465
|
+
parsed = parseIntoSections(raw);
|
|
15466
|
+
} catch (err) {
|
|
15467
|
+
return {
|
|
15468
|
+
content: [{
|
|
15469
|
+
type: "text",
|
|
15470
|
+
text: `Parsing failed: ${err instanceof Error ? err.message : String(err)}
|
|
15471
|
+
|
|
15472
|
+
Your existing snapshots are intact.`
|
|
15473
|
+
}],
|
|
15474
|
+
isError: true
|
|
15475
|
+
};
|
|
15476
|
+
}
|
|
15477
|
+
try {
|
|
15478
|
+
saveSnapshot("current", parsed.index, parsed.content);
|
|
15479
|
+
} catch (err) {
|
|
15480
|
+
return {
|
|
15481
|
+
content: [{
|
|
15482
|
+
type: "text",
|
|
15483
|
+
text: `Failed to save current snapshot: ${err instanceof Error ? err.message : String(err)}`
|
|
15484
|
+
}],
|
|
15485
|
+
isError: true
|
|
15486
|
+
};
|
|
15487
|
+
}
|
|
15488
|
+
const diff = diffSnapshots(baseline, parsed.index);
|
|
15489
|
+
const total = diff.added.length + diff.removed.length + diff.modified.length;
|
|
15490
|
+
const lines = [
|
|
15491
|
+
"# Official Docs \u2014 Current Snapshot Updated",
|
|
15492
|
+
"",
|
|
15493
|
+
`Fetched: ${parsed.index.fetchedAt}`,
|
|
15494
|
+
`Sections: ${parsed.index.sectionCount}`,
|
|
15495
|
+
""
|
|
15496
|
+
];
|
|
15497
|
+
if (total === 0) {
|
|
15498
|
+
lines.push(`No changes vs baseline (${formatDate(baseline.fetchedAt)}). All ${diff.unchanged} sections unchanged.`);
|
|
15499
|
+
} else {
|
|
15500
|
+
lines.push(`vs baseline (${formatDate(baseline.fetchedAt)}): ${diff.added.length} added, ${diff.removed.length} removed, ${diff.modified.length} modified, ${diff.unchanged} unchanged`);
|
|
15501
|
+
lines.push("");
|
|
15502
|
+
lines.push("Run diff_official_docs() for the full diff.");
|
|
15503
|
+
}
|
|
15504
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
15505
|
+
}
|
|
15506
|
+
);
|
|
15507
|
+
server.tool(
|
|
15508
|
+
"diff_official_docs",
|
|
15509
|
+
"Compare the baseline and current official Anthropic Claude Code docs snapshots. Shows added, removed, and modified pages. No network call \u2014 reads local files only. Run init_official_docs() first, then refresh_official_docs() when you want to update the current snapshot.",
|
|
15510
|
+
{},
|
|
15511
|
+
{ readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
15512
|
+
async () => {
|
|
15513
|
+
const baseline = loadIndex("baseline");
|
|
15514
|
+
if (!baseline) {
|
|
15515
|
+
return {
|
|
15516
|
+
content: [{
|
|
15517
|
+
type: "text",
|
|
15518
|
+
text: "No baseline found. Run init_official_docs() first to create a baseline snapshot."
|
|
15519
|
+
}],
|
|
15520
|
+
isError: true
|
|
15521
|
+
};
|
|
15522
|
+
}
|
|
15523
|
+
const current = loadIndex("current");
|
|
15524
|
+
if (!current) {
|
|
15525
|
+
return {
|
|
15526
|
+
content: [{
|
|
15527
|
+
type: "text",
|
|
15528
|
+
text: "No current snapshot found. Run refresh_official_docs() to fetch the latest docs."
|
|
15529
|
+
}],
|
|
15530
|
+
isError: true
|
|
15531
|
+
};
|
|
15532
|
+
}
|
|
15533
|
+
if (baseline.contentHash === current.contentHash) {
|
|
15534
|
+
const ageDays2 = snapshotAgeDays(current);
|
|
15535
|
+
const staleWarn = ageDays2 > STALENESS_WARN_DAYS ? `
|
|
15536
|
+
\u26A0\uFE0F Current snapshot is ${ageDays2} days old \u2014 consider running refresh_official_docs()` : "";
|
|
15537
|
+
return {
|
|
15538
|
+
content: [{
|
|
15539
|
+
type: "text",
|
|
15540
|
+
text: [
|
|
15541
|
+
"# Official Docs Diff",
|
|
15542
|
+
"",
|
|
15543
|
+
`Baseline: ${formatDate(baseline.fetchedAt)} | Current: ${formatDate(current.fetchedAt)}`,
|
|
15544
|
+
"",
|
|
15545
|
+
`No changes detected. All ${baseline.sectionCount} sections unchanged.${staleWarn}`,
|
|
15546
|
+
"",
|
|
15547
|
+
"Run refresh_official_docs() to fetch latest docs and update current."
|
|
15548
|
+
].join("\n")
|
|
15549
|
+
}]
|
|
15550
|
+
};
|
|
15551
|
+
}
|
|
15552
|
+
const diff = diffSnapshots(baseline, current);
|
|
15553
|
+
if (diff.modified.length > 0) {
|
|
15554
|
+
const modifiedSlugs = diff.modified.map((m) => m.slug);
|
|
15555
|
+
const baselineContents = loadSections("baseline", modifiedSlugs);
|
|
15556
|
+
const currentContents = loadSections("current", modifiedSlugs);
|
|
15557
|
+
for (const entry of diff.modified) {
|
|
15558
|
+
const baseContent = baselineContents[entry.slug];
|
|
15559
|
+
const currContent = currentContents[entry.slug];
|
|
15560
|
+
if (baseContent && currContent) {
|
|
15561
|
+
const line = findFirstChangedLine(baseContent, currContent);
|
|
15562
|
+
if (line) entry.firstChangedLine = line;
|
|
15563
|
+
}
|
|
15564
|
+
}
|
|
15565
|
+
}
|
|
15566
|
+
const lines = [
|
|
15567
|
+
"# Official Docs Diff",
|
|
15568
|
+
"",
|
|
15569
|
+
`Baseline: ${formatDate(baseline.fetchedAt)} | Current: ${formatDate(current.fetchedAt)}`,
|
|
15570
|
+
""
|
|
15571
|
+
];
|
|
15572
|
+
const ageDays = snapshotAgeDays(current);
|
|
15573
|
+
if (ageDays > STALENESS_WARN_DAYS) {
|
|
15574
|
+
lines.push(`\u26A0\uFE0F Current snapshot is ${ageDays} days old \u2014 consider running refresh_official_docs()`);
|
|
15575
|
+
lines.push("");
|
|
15576
|
+
}
|
|
15577
|
+
if (diff.added.length > 0) {
|
|
15578
|
+
lines.push(`## Added (${diff.added.length})`);
|
|
15579
|
+
for (const s of diff.added) {
|
|
15580
|
+
lines.push(`+ ${s.slug} \u2014 "${s.title}"`);
|
|
15581
|
+
lines.push(` ${s.sourceUrl}`);
|
|
15582
|
+
}
|
|
15583
|
+
lines.push("");
|
|
15584
|
+
}
|
|
15585
|
+
if (diff.removed.length > 0) {
|
|
15586
|
+
lines.push(`## Removed (${diff.removed.length})`);
|
|
15587
|
+
for (const s of diff.removed) {
|
|
15588
|
+
lines.push(`- ${s.slug} \u2014 "${s.title}"`);
|
|
15589
|
+
lines.push(` ${s.sourceUrl}`);
|
|
15590
|
+
}
|
|
15591
|
+
lines.push("");
|
|
15592
|
+
}
|
|
15593
|
+
if (diff.modified.length > 0) {
|
|
15594
|
+
lines.push(`## Modified (${diff.modified.length})`);
|
|
15595
|
+
for (const s of diff.modified) {
|
|
15596
|
+
const delta = s.lineDelta !== void 0 ? s.lineDelta >= 0 ? `+${s.lineDelta}` : `${s.lineDelta}` : "";
|
|
15597
|
+
const charDelta = s.charsAfter !== void 0 && s.charsBefore !== void 0 ? s.charsAfter - s.charsBefore >= 0 ? `+${s.charsAfter - s.charsBefore}` : `${s.charsAfter - s.charsBefore}` : "";
|
|
15598
|
+
lines.push(`~ ${s.slug} (L${s.linesBefore} \u2192 L${s.linesAfter}, ${delta} lines, ${charDelta} chars)`);
|
|
15599
|
+
if (s.firstChangedLine) {
|
|
15600
|
+
lines.push(` First change: "${s.firstChangedLine}"`);
|
|
15601
|
+
}
|
|
15602
|
+
lines.push(` ${s.sourceUrl}`);
|
|
15603
|
+
}
|
|
15604
|
+
lines.push("");
|
|
15605
|
+
}
|
|
15606
|
+
const total = diff.added.length + diff.removed.length + diff.modified.length;
|
|
15607
|
+
lines.push("---");
|
|
15608
|
+
lines.push(`${diff.added.length} added, ${diff.removed.length} removed, ${diff.modified.length} modified, ${diff.unchanged} unchanged (${total} total changes)`);
|
|
15609
|
+
lines.push("Run refresh_official_docs() to update current to latest.");
|
|
15610
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
15611
|
+
}
|
|
15612
|
+
);
|
|
15613
|
+
server.tool(
|
|
15614
|
+
"search_official_docs",
|
|
15615
|
+
"Search the official Anthropic Claude Code documentation by keyword or topic. Uses the local current snapshot \u2014 no network call. Run init_official_docs() first.",
|
|
15616
|
+
{
|
|
15617
|
+
query: external_exports.string().describe('Search term or topic (e.g. "hooks", "MCP authentication", "cost limits")'),
|
|
15618
|
+
limit: external_exports.number().min(1).max(10).optional().default(5).describe("Max sections to return (default 5)")
|
|
15619
|
+
},
|
|
15620
|
+
{ readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
15621
|
+
async ({ query, limit }) => {
|
|
15622
|
+
const current = loadIndex("current");
|
|
15623
|
+
if (!current) {
|
|
15624
|
+
return {
|
|
15625
|
+
content: [{
|
|
15626
|
+
type: "text",
|
|
15627
|
+
text: "No snapshot found. Run init_official_docs() first to create a local cache of the official docs."
|
|
15628
|
+
}],
|
|
15629
|
+
isError: true
|
|
15630
|
+
};
|
|
15631
|
+
}
|
|
15632
|
+
const scored = Object.values(current.sections).map((sec) => ({ meta: sec, titleScore: sec.title.toLowerCase().includes(query.toLowerCase()) ? 3 : 0 })).filter((item) => item.titleScore > 0);
|
|
15633
|
+
let finalSlugs;
|
|
15634
|
+
if (scored.length >= (limit ?? 5)) {
|
|
15635
|
+
finalSlugs = scored.sort((a, b) => b.titleScore - a.titleScore).slice(0, limit ?? 5).map((item) => item.meta.slug);
|
|
15636
|
+
} else {
|
|
15637
|
+
const allSlugs = Object.keys(current.sections);
|
|
15638
|
+
const allContents = loadSections("current", allSlugs);
|
|
15639
|
+
const fullScored = Object.values(current.sections).map((sec) => ({
|
|
15640
|
+
meta: sec,
|
|
15641
|
+
score: scoreSection(sec.title, allContents[sec.slug] ?? "", query)
|
|
15642
|
+
})).filter((item) => item.score > 0).sort((a, b) => b.score - a.score).slice(0, limit ?? 5);
|
|
15643
|
+
finalSlugs = fullScored.map((item) => item.meta.slug);
|
|
15644
|
+
}
|
|
15645
|
+
if (finalSlugs.length === 0) {
|
|
15646
|
+
return {
|
|
15647
|
+
content: [{
|
|
15648
|
+
type: "text",
|
|
15649
|
+
text: `No results for "${query}" in ${current.sectionCount} sections (snapshot: ${formatDate(current.fetchedAt)}).
|
|
15650
|
+
|
|
15651
|
+
Try broader terms or run refresh_official_docs() to update the cache.`
|
|
15652
|
+
}]
|
|
15653
|
+
};
|
|
15654
|
+
}
|
|
15655
|
+
const matchingContents = loadSections("current", finalSlugs);
|
|
15656
|
+
const lines = [
|
|
15657
|
+
`# Search: "${query}" \u2014 ${finalSlugs.length} result${finalSlugs.length !== 1 ? "s" : ""} (snapshot: ${formatDate(current.fetchedAt)})`,
|
|
15658
|
+
""
|
|
15659
|
+
];
|
|
15660
|
+
for (const slug of finalSlugs) {
|
|
15661
|
+
const meta3 = current.sections[slug];
|
|
15662
|
+
const content = matchingContents[slug] ?? "";
|
|
15663
|
+
const excerpt = extractExcerpt(content, query);
|
|
15664
|
+
lines.push(`## ${meta3.title}`);
|
|
15665
|
+
lines.push(meta3.sourceUrl);
|
|
15666
|
+
lines.push("");
|
|
15667
|
+
lines.push(excerpt);
|
|
15668
|
+
lines.push("");
|
|
15669
|
+
}
|
|
15670
|
+
lines.push("---");
|
|
15671
|
+
lines.push(`Showing ${finalSlugs.length} of ${current.sectionCount} sections. Run refresh_official_docs() to update the cache.`);
|
|
15672
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
15673
|
+
}
|
|
15674
|
+
);
|
|
15675
|
+
}
|
|
15676
|
+
|
|
15105
15677
|
// src/resources/index.ts
|
|
15106
15678
|
function registerResources(server) {
|
|
15107
15679
|
server.resource(
|
|
@@ -15265,6 +15837,7 @@ function createServer() {
|
|
|
15265
15837
|
registerGetThreat(server);
|
|
15266
15838
|
registerListThreats(server);
|
|
15267
15839
|
registerSearchExamples(server);
|
|
15840
|
+
registerOfficialDocs(server);
|
|
15268
15841
|
registerResources(server);
|
|
15269
15842
|
registerPrompts(server);
|
|
15270
15843
|
return server;
|
package/package.json
CHANGED