claude-code-ultimate-guide-mcp 1.0.4 → 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.
Files changed (2) hide show
  1. package/dist/index.js +550 -0
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -15125,6 +15125,555 @@ function registerSearchExamples(server) {
15125
15125
  );
15126
15126
  }
15127
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
+
15128
15677
  // src/resources/index.ts
15129
15678
  function registerResources(server) {
15130
15679
  server.resource(
@@ -15288,6 +15837,7 @@ function createServer() {
15288
15837
  registerGetThreat(server);
15289
15838
  registerListThreats(server);
15290
15839
  registerSearchExamples(server);
15840
+ registerOfficialDocs(server);
15291
15841
  registerResources(server);
15292
15842
  registerPrompts(server);
15293
15843
  return server;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-code-ultimate-guide-mcp",
3
- "version": "1.0.4",
3
+ "version": "1.1.0",
4
4
  "description": "MCP server for the Claude Code Ultimate Guide — search, read, and explore 20K+ lines of documentation directly from Claude Code",
5
5
  "keywords": [
6
6
  "mcp",