@loguro/mcp 1.1.0 → 1.2.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 +293 -5
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -19560,6 +19560,263 @@ class StdioServerTransport {
19560
19560
  import { existsSync, readFileSync } from "node:fs";
19561
19561
  import { homedir } from "node:os";
19562
19562
  import { join } from "node:path";
19563
+
19564
+ // src/docs-catalog.ts
19565
+ var DOCS_CATALOG = [
19566
+ { path: "/docs/getting-started", title: "Getting Started", section: "General" },
19567
+ { path: "/docs/query-syntax", title: "Query Syntax", section: "General" },
19568
+ { path: "/docs/alerting", title: "Alerting", section: "General" },
19569
+ { path: "/docs/integrations", title: "Integrations", section: "General" },
19570
+ { path: "/docs/cli/install", title: "Install", section: "CLI" },
19571
+ { path: "/docs/cli/getting-started", title: "Getting Started", section: "CLI" },
19572
+ { path: "/docs/cli/auth", title: "Auth", section: "CLI" },
19573
+ { path: "/docs/cli/project-link", title: "Project Link", section: "CLI" },
19574
+ { path: "/docs/cli/commands", title: "Commands", section: "CLI" },
19575
+ { path: "/docs/cli/querying", title: "Querying", section: "CLI" },
19576
+ { path: "/docs/cli/query-syntax", title: "Query Syntax", section: "CLI" },
19577
+ { path: "/docs/cli/investigate", title: "Investigate", section: "CLI" },
19578
+ { path: "/docs/cli/visualizations", title: "Visualizations", section: "CLI" },
19579
+ { path: "/docs/cli/replay", title: "Replay", section: "CLI" },
19580
+ { path: "/docs/cli/alerts", title: "Alerts", section: "CLI" },
19581
+ { path: "/docs/cli/integrations", title: "Integrations", section: "CLI" },
19582
+ { path: "/docs/cli/share", title: "Share", section: "CLI" },
19583
+ { path: "/docs/cli/account", title: "Account", section: "CLI" },
19584
+ { path: "/docs/commands/count", title: "--count", section: "Commands" },
19585
+ { path: "/docs/commands/top", title: "--top", section: "Commands" },
19586
+ { path: "/docs/commands/unique", title: "--unique", section: "Commands" },
19587
+ { path: "/docs/commands/rate", title: "--rate", section: "Commands" },
19588
+ { path: "/docs/commands/diff", title: "--diff", section: "Commands" },
19589
+ { path: "/docs/commands/downloads", title: "--downloads", section: "Commands" },
19590
+ { path: "/docs/commands/replay", title: "--replay", section: "Commands" },
19591
+ { path: "/docs/commands/live", title: "--live", section: "Commands" },
19592
+ { path: "/docs/commands/group", title: "--group", section: "Commands" },
19593
+ { path: "/docs/commands/alerts", title: "--alerts", section: "Commands" },
19594
+ { path: "/docs/commands/views", title: "--views", section: "Commands" },
19595
+ { path: "/docs/commands/keys", title: "--keys", section: "Commands" },
19596
+ { path: "/docs/commands/channels", title: "--channels", section: "Commands" },
19597
+ { path: "/docs/commands/embed-status", title: "--embed::status", section: "Commands" },
19598
+ { path: "/docs/commands/billing", title: "--billing", section: "Commands" },
19599
+ { path: "/docs/commands/usage", title: "--usage", section: "Commands" },
19600
+ { path: "/docs/commands/integrations-config", title: "--integrations::config", section: "Commands" },
19601
+ { path: "/docs/commands/send", title: "--send", section: "Commands" },
19602
+ { path: "/docs/commands/tasks", title: "--task", section: "Commands" },
19603
+ { path: "/docs/commands/slow", title: "--slow", section: "Commands" },
19604
+ { path: "/docs/commands/sample", title: "--sample", section: "Commands" },
19605
+ { path: "/docs/commands/memory", title: "--memory", section: "Commands" },
19606
+ { path: "/docs/commands/chart", title: "--chart", section: "Commands" },
19607
+ { path: "/docs/commands/timeline", title: "--timeline", section: "Commands" },
19608
+ { path: "/docs/commands/notifications", title: "--notifications", section: "Commands" },
19609
+ { path: "/docs/commands/severity", title: "--severity", section: "Commands" },
19610
+ { path: "/docs/commands/separate-message", title: "--separate::message", section: "Commands" },
19611
+ { path: "/docs/commands/no-group", title: "--no-group", section: "Commands" },
19612
+ { path: "/docs/commands/hashtag", title: "#view", section: "Commands" },
19613
+ { path: "/docs/commands/investigate", title: "--investigate", section: "Commands" },
19614
+ { path: "/docs/commands/explain", title: "--explain", section: "Commands" },
19615
+ { path: "/docs/commands/notes", title: "--notes", section: "Commands" },
19616
+ { path: "/docs/commands/share", title: "--share:md", section: "Commands" },
19617
+ { path: "/docs/commands/slack", title: "--slack", section: "Commands" },
19618
+ { path: "/docs/commands/trace", title: "--trace:full", section: "Commands" },
19619
+ { path: "/docs/features/saved-views", title: "Saved Views", section: "Features" },
19620
+ { path: "/docs/features/log-export", title: "Log Export", section: "Features" },
19621
+ { path: "/docs/features/embed-widgets", title: "Embed Widgets", section: "Features" },
19622
+ { path: "/docs/features/replay", title: "Replay", section: "Features" },
19623
+ { path: "/docs/features/issue-tracker", title: "Issue Tracker", section: "Features" },
19624
+ { path: "/docs/features/messaging", title: "Messaging", section: "Features" },
19625
+ { path: "/docs/features/heartbeat", title: "Heartbeat", section: "Features" },
19626
+ { path: "/docs/features/memory", title: "Memory", section: "Features" },
19627
+ { path: "/docs/mcp/setup", title: "Setup", section: "MCP" },
19628
+ { path: "/docs/mcp/auth", title: "Auth", section: "MCP" },
19629
+ { path: "/docs/mcp/config", title: "Config", section: "MCP" },
19630
+ { path: "/docs/mcp/tools", title: "Tools", section: "MCP" },
19631
+ { path: "/docs/mcp/clients", title: "Clients", section: "MCP" },
19632
+ { path: "/docs/mcp/vs-cli", title: "MCP vs CLI", section: "MCP" },
19633
+ { path: "/docs/tui/getting-started", title: "Getting Started", section: "TUI" },
19634
+ { path: "/docs/tui/views", title: "Views", section: "TUI" },
19635
+ { path: "/docs/tui/keymap", title: "Keymap", section: "TUI" },
19636
+ { path: "/docs/tui/search", title: "Search", section: "TUI" },
19637
+ { path: "/docs/tui/alerts", title: "Alerts", section: "TUI" },
19638
+ { path: "/docs/tui/saved-views", title: "Saved Views", section: "TUI" },
19639
+ { path: "/docs/tui/docs-browser", title: "Docs Browser", section: "TUI" },
19640
+ { path: "/docs/tui/health-badge", title: "Health Badge", section: "TUI" },
19641
+ { path: "/docs/tui/troubleshooting", title: "Troubleshooting", section: "TUI" },
19642
+ { path: "/docs/web-analytics/getting-started", title: "Getting Started", section: "Web Analytics" },
19643
+ { path: "/docs/web-analytics/install", title: "Install", section: "Web Analytics" },
19644
+ { path: "/docs/web-analytics/events", title: "Events", section: "Web Analytics" },
19645
+ { path: "/docs/web-analytics/pageviews", title: "Pageviews", section: "Web Analytics" },
19646
+ { path: "/docs/web-analytics/identity", title: "Identity", section: "Web Analytics" },
19647
+ { path: "/docs/web-analytics/privacy", title: "Privacy", section: "Web Analytics" },
19648
+ { path: "/docs/web-analytics/api-reference", title: "API Reference", section: "Web Analytics" },
19649
+ { path: "/docs/web-analytics/troubleshooting", title: "Troubleshooting", section: "Web Analytics" }
19650
+ ];
19651
+
19652
+ // src/docs.ts
19653
+ var DOCS_BASE_URL = (process.env.LOGURO_DOCS_BASE_URL ?? "https://logu.ro").replace(/\/$/, "");
19654
+ var CACHE_TTL_MS = 15 * 60 * 1000;
19655
+ var FETCH_TIMEOUT_MS = 1e4;
19656
+ var USER_AGENT = `loguro-mcp-docs/1.0.0 (node/${process.version})`;
19657
+ var cache = new Map;
19658
+ var allFetchedAt = 0;
19659
+ function extractHeadings(md) {
19660
+ const out = [];
19661
+ for (const line of md.split(`
19662
+ `)) {
19663
+ const m = line.match(/^(#{1,6})\s+(.+?)\s*$/);
19664
+ if (m)
19665
+ out.push(m[2].trim());
19666
+ }
19667
+ return out;
19668
+ }
19669
+ function isFresh(entry) {
19670
+ return Date.now() - entry.fetchedAt < CACHE_TTL_MS;
19671
+ }
19672
+ async function fetchDoc(path, force = false) {
19673
+ const cached2 = cache.get(path);
19674
+ if (!force && cached2 && isFresh(cached2))
19675
+ return cached2;
19676
+ const url = `${DOCS_BASE_URL}${path}.md`;
19677
+ const res = await fetch(url, {
19678
+ headers: { "User-Agent": USER_AGENT, Accept: "text/markdown,text/plain,*/*" },
19679
+ signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
19680
+ });
19681
+ if (!res.ok) {
19682
+ throw new Error(`Failed to fetch ${url}: HTTP ${res.status}`);
19683
+ }
19684
+ const body = await res.text();
19685
+ const entry = {
19686
+ path,
19687
+ body,
19688
+ headings: extractHeadings(body),
19689
+ fetchedAt: Date.now()
19690
+ };
19691
+ cache.set(path, entry);
19692
+ return entry;
19693
+ }
19694
+ async function fetchAllDocs() {
19695
+ if (Date.now() - allFetchedAt < CACHE_TTL_MS)
19696
+ return;
19697
+ await Promise.all(DOCS_CATALOG.map((entry) => fetchDoc(entry.path).catch((err) => {
19698
+ process.stderr.write(`docs: failed to fetch ${entry.path}: ${err.message}
19699
+ `);
19700
+ })));
19701
+ allFetchedAt = Date.now();
19702
+ }
19703
+ function listDocs(section) {
19704
+ const filtered = section ? DOCS_CATALOG.filter((e) => e.section.toLowerCase() === section.toLowerCase()) : DOCS_CATALOG;
19705
+ return filtered.map((e) => {
19706
+ const cached2 = cache.get(e.path);
19707
+ return cached2 ? { ...e, headings: cached2.headings } : { ...e };
19708
+ });
19709
+ }
19710
+ function listSections() {
19711
+ const counts = new Map;
19712
+ for (const e of DOCS_CATALOG)
19713
+ counts.set(e.section, (counts.get(e.section) ?? 0) + 1);
19714
+ return [...counts.entries()].map(([section, count]) => ({ section, count }));
19715
+ }
19716
+ function tokens(s) {
19717
+ return s.toLowerCase().split(/\s+/).filter(Boolean);
19718
+ }
19719
+ function countOccurrences(haystack, needle) {
19720
+ if (!needle)
19721
+ return 0;
19722
+ let count = 0;
19723
+ let i = 0;
19724
+ const lc = haystack.toLowerCase();
19725
+ while ((i = lc.indexOf(needle, i)) !== -1) {
19726
+ count++;
19727
+ i += needle.length;
19728
+ }
19729
+ return count;
19730
+ }
19731
+ function buildSnippet(body, query, max = 280) {
19732
+ const lc = body.toLowerCase();
19733
+ const idx = lc.indexOf(query.toLowerCase());
19734
+ if (idx === -1) {
19735
+ return body.slice(0, max).trim() + (body.length > max ? "…" : "");
19736
+ }
19737
+ const start = Math.max(0, idx - 80);
19738
+ const end = Math.min(body.length, idx + query.length + 200);
19739
+ const prefix = start > 0 ? "…" : "";
19740
+ const suffix = end < body.length ? "…" : "";
19741
+ return prefix + body.slice(start, end).replace(/\s+/g, " ").trim() + suffix;
19742
+ }
19743
+ async function searchDocs(query, limit = 5) {
19744
+ await fetchAllDocs();
19745
+ const q = query.trim().toLowerCase();
19746
+ const qTokens = tokens(q);
19747
+ if (!q)
19748
+ return [];
19749
+ const hits = [];
19750
+ for (const entry of DOCS_CATALOG) {
19751
+ const cached2 = cache.get(entry.path);
19752
+ const titleLc = entry.title.toLowerCase();
19753
+ const sectionLc = entry.section.toLowerCase();
19754
+ const matchedIn = new Set;
19755
+ let score = 0;
19756
+ const matchedHeadings = [];
19757
+ if (titleLc.includes(q)) {
19758
+ score += 50;
19759
+ matchedIn.add("title");
19760
+ } else {
19761
+ for (const t of qTokens)
19762
+ if (titleLc.includes(t)) {
19763
+ score += 15;
19764
+ matchedIn.add("title");
19765
+ }
19766
+ }
19767
+ if (sectionLc.includes(q)) {
19768
+ score += 10;
19769
+ matchedIn.add("section");
19770
+ }
19771
+ if (cached2) {
19772
+ for (const h of cached2.headings) {
19773
+ const hLc = h.toLowerCase();
19774
+ if (hLc.includes(q)) {
19775
+ score += 20;
19776
+ matchedIn.add("heading");
19777
+ matchedHeadings.push(h);
19778
+ } else {
19779
+ for (const t of qTokens) {
19780
+ if (hLc.includes(t)) {
19781
+ score += 5;
19782
+ matchedIn.add("heading");
19783
+ if (!matchedHeadings.includes(h))
19784
+ matchedHeadings.push(h);
19785
+ }
19786
+ }
19787
+ }
19788
+ }
19789
+ const bodyOccurrences = countOccurrences(cached2.body, q);
19790
+ if (bodyOccurrences > 0) {
19791
+ score += Math.min(bodyOccurrences, 10);
19792
+ matchedIn.add("body");
19793
+ } else {
19794
+ for (const t of qTokens) {
19795
+ const occ = countOccurrences(cached2.body, t);
19796
+ if (occ > 0) {
19797
+ score += Math.min(occ, 5) * 0.5;
19798
+ matchedIn.add("body");
19799
+ }
19800
+ }
19801
+ }
19802
+ }
19803
+ if (score > 0) {
19804
+ hits.push({
19805
+ path: entry.path,
19806
+ title: entry.title,
19807
+ section: entry.section,
19808
+ score,
19809
+ matchedIn: [...matchedIn],
19810
+ snippet: cached2 ? buildSnippet(cached2.body, q) : "",
19811
+ matchedHeadings
19812
+ });
19813
+ }
19814
+ }
19815
+ hits.sort((a, b) => b.score - a.score);
19816
+ return hits.slice(0, limit);
19817
+ }
19818
+
19819
+ // src/index.ts
19563
19820
  function loadCliAuth() {
19564
19821
  const xdg = process.env.XDG_CONFIG_HOME;
19565
19822
  const base = xdg && xdg.length > 0 ? xdg : join(homedir(), ".config");
@@ -19589,7 +19846,7 @@ if (!TOKEN) {
19589
19846
  process.stderr.write("Error: no PAT found. Run `loguro login` first, or set LOGURO_TOKEN env var.\n");
19590
19847
  process.exit(1);
19591
19848
  }
19592
- var USER_AGENT = `loguro-mcp/1.0.0 (node/${process.version})`;
19849
+ var USER_AGENT2 = `loguro-mcp/1.0.0 (node/${process.version})`;
19593
19850
  var REQUEST_TIMEOUT_MS = 15000;
19594
19851
  function buildQS(params) {
19595
19852
  const qs = new URLSearchParams;
@@ -19610,7 +19867,7 @@ async function logsRequest(project, params) {
19610
19867
  const res = await fetch(url, {
19611
19868
  headers: {
19612
19869
  Authorization: `Bearer ${TOKEN}`,
19613
- "User-Agent": USER_AGENT,
19870
+ "User-Agent": USER_AGENT2,
19614
19871
  Accept: "application/json"
19615
19872
  },
19616
19873
  signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
@@ -19630,7 +19887,7 @@ async function logsSubRequest(project, subPath, params, method = "GET") {
19630
19887
  method,
19631
19888
  headers: {
19632
19889
  Authorization: `Bearer ${TOKEN}`,
19633
- "User-Agent": USER_AGENT,
19890
+ "User-Agent": USER_AGENT2,
19634
19891
  Accept: "application/json"
19635
19892
  },
19636
19893
  signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
@@ -19647,7 +19904,7 @@ async function apiRequest(method, path, body) {
19647
19904
  method,
19648
19905
  headers: {
19649
19906
  Authorization: `Bearer ${TOKEN}`,
19650
- "User-Agent": USER_AGENT,
19907
+ "User-Agent": USER_AGENT2,
19651
19908
  Accept: "application/json",
19652
19909
  ...body !== undefined ? { "Content-Type": "application/json" } : {}
19653
19910
  },
@@ -20314,7 +20571,7 @@ server.tool("check_investigated", "Batch check which fingerprints have cached AI
20314
20571
  method: "POST",
20315
20572
  headers: {
20316
20573
  Authorization: `Bearer ${TOKEN}`,
20317
- "User-Agent": USER_AGENT,
20574
+ "User-Agent": USER_AGENT2,
20318
20575
  Accept: "application/json",
20319
20576
  "Content-Type": "application/json"
20320
20577
  },
@@ -20327,5 +20584,36 @@ server.tool("check_investigated", "Batch check which fingerprints have cached AI
20327
20584
  const data = await res.json();
20328
20585
  return jsonResult(data, `Cached investigations: ${data.investigated?.length ?? 0}/${fingerprints.length}`);
20329
20586
  });
20587
+ server.tool("list_docs", "Browse the Loguro documentation catalog. Returns titles + paths grouped by section. Call without args to see everything; pass `section` to filter. Use `read_doc` to fetch full content of a page.", {
20588
+ section: exports_external.string().optional().describe("Filter by section: General, CLI, Commands, Features, MCP, TUI, Web Analytics")
20589
+ }, async ({ section }) => {
20590
+ const docs = listDocs(section);
20591
+ const sections = listSections();
20592
+ const grouped = {};
20593
+ for (const d of docs) {
20594
+ if (!grouped[d.section])
20595
+ grouped[d.section] = [];
20596
+ grouped[d.section].push({ path: d.path, title: d.title, headings: d.headings });
20597
+ }
20598
+ const summary = section ? `${docs.length} page(s) in section "${section}"` : `${docs.length} pages across ${sections.length} sections: ${sections.map((s) => `${s.section} (${s.count})`).join(", ")}`;
20599
+ return jsonResult({ sections, docs: grouped }, summary);
20600
+ });
20601
+ server.tool("search_docs", "Search Loguro documentation by keyword. Fetches all docs on first call (cached 15min), then scores matches across title, section, headings, and body. Returns top hits with snippets. Use this when you don't know which page covers a topic.", {
20602
+ query: exports_external.string().min(1).describe("Search terms — natural words, not regex. E.g. 'how do alerts work', 'PAT token', 'context filter'."),
20603
+ limit: exports_external.number().min(1).max(20).default(5).describe("Max results (default 5)")
20604
+ }, async ({ query, limit }) => {
20605
+ const hits = await searchDocs(query, limit);
20606
+ const summary = hits.length === 0 ? `No matches for "${query}"` : `Top ${hits.length} hit(s) for "${query}" — best: ${hits[0].title} (${hits[0].section}) score=${hits[0].score}`;
20607
+ return jsonResult({ query, hits }, summary);
20608
+ });
20609
+ server.tool("read_doc", "Fetch the full markdown body of a documentation page by its path (e.g. '/docs/cli/alerts'). Use after `search_docs` or `list_docs` to read a specific page end-to-end.", {
20610
+ path: exports_external.string().describe("Doc path starting with /docs/ — e.g. /docs/mcp/setup"),
20611
+ refresh: exports_external.boolean().default(false).describe("Bypass the 15min cache and refetch from logu.ro")
20612
+ }, async ({ path, refresh }) => {
20613
+ const normalized = path.startsWith("/") ? path : "/" + path;
20614
+ const doc2 = await fetchDoc(normalized, refresh);
20615
+ const summary = `${normalized} (${doc2.body.length} chars, ${doc2.headings.length} heading(s))`;
20616
+ return jsonResult({ path: normalized, headings: doc2.headings, body: doc2.body, fetchedAt: new Date(doc2.fetchedAt).toISOString() }, summary);
20617
+ });
20330
20618
  var transport = new StdioServerTransport;
20331
20619
  await server.connect(transport);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loguro/mcp",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "MCP server for Loguro — query logs from Claude Code, Cursor, and other MCP-compatible AI tools. Reuses the CLI's saved auth (zero config).",
5
5
  "keywords": [
6
6
  "loguro",