@forkpoint/agent-lighthouse-mcp 3.1.0 → 4.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/dist/index.js +9 -7
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +9 -7
- package/dist/index.mjs.map +1 -1
- package/dist/server.js +1 -1
- package/dist/server.js.map +1 -1
- package/dist/server.mjs +1 -1
- package/dist/server.mjs.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -34,13 +34,15 @@ async function auditWebsite(url) {
|
|
|
34
34
|
scoreTier: view.scoreTier,
|
|
35
35
|
...view.unscoredReason ? { unscoredReason: view.unscoredReason } : {},
|
|
36
36
|
durationMs: view.durationMs,
|
|
37
|
-
categories: view.groups.flatMap(
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
37
|
+
categories: view.groups.flatMap(
|
|
38
|
+
(g) => g.categories.map((c) => ({
|
|
39
|
+
name: c.name,
|
|
40
|
+
score: c.score,
|
|
41
|
+
pass: c.counts.pass,
|
|
42
|
+
warn: c.counts.warn,
|
|
43
|
+
fail: c.counts.fail
|
|
44
|
+
}))
|
|
45
|
+
),
|
|
44
46
|
topFails: report.topFails.map((f) => ({
|
|
45
47
|
id: f.id,
|
|
46
48
|
title: f.title,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { runScan } from
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { runScan } from \"@forkpoint/agent-lighthouse-core\";\nimport { buildReportView } from \"@forkpoint/agent-lighthouse-report\";\n\n/**\n * Programmatic helper functions powering the Agent Lighthouse MCP server and custom agent tools.\n */\nexport async function auditWebsite(url: string) {\n const report = await runScan(url);\n const view = buildReportView(report);\n\n return {\n url: report.url,\n overallScore: view.overallScore,\n scoreTier: view.scoreTier,\n ...(view.unscoredReason ? { unscoredReason: view.unscoredReason } : {}),\n durationMs: view.durationMs,\n categories: view.groups.flatMap((g) =>\n g.categories.map((c) => ({\n name: c.name,\n score: c.score,\n pass: c.counts.pass,\n warn: c.counts.warn,\n fail: c.counts.fail,\n })),\n ),\n topFails: report.topFails.map((f) => ({\n id: f.id,\n title: f.title,\n priority: f.priority,\n impact: f.impact,\n fix: f.fix,\n })),\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mCAAwB;AACxB,qCAAgC;AAKhC,eAAsB,aAAa,KAAa;AAC9C,QAAM,SAAS,UAAM,sCAAQ,GAAG;AAChC,QAAM,WAAO,gDAAgB,MAAM;AAEnC,SAAO;AAAA,IACL,KAAK,OAAO;AAAA,IACZ,cAAc,KAAK;AAAA,IACnB,WAAW,KAAK;AAAA,IAChB,GAAI,KAAK,iBAAiB,EAAE,gBAAgB,KAAK,eAAe,IAAI,CAAC;AAAA,IACrE,YAAY,KAAK;AAAA,IACjB,YAAY,KAAK,OAAO;AAAA,MAAQ,CAAC,MAC/B,EAAE,WAAW,IAAI,CAAC,OAAO;AAAA,QACvB,MAAM,EAAE;AAAA,QACR,OAAO,EAAE;AAAA,QACT,MAAM,EAAE,OAAO;AAAA,QACf,MAAM,EAAE,OAAO;AAAA,QACf,MAAM,EAAE,OAAO;AAAA,MACjB,EAAE;AAAA,IACJ;AAAA,IACA,UAAU,OAAO,SAAS,IAAI,CAAC,OAAO;AAAA,MACpC,IAAI,EAAE;AAAA,MACN,OAAO,EAAE;AAAA,MACT,UAAU,EAAE;AAAA,MACZ,QAAQ,EAAE;AAAA,MACV,KAAK,EAAE;AAAA,IACT,EAAE;AAAA,EACJ;AACF;","names":[]}
|
package/dist/index.mjs
CHANGED
|
@@ -10,13 +10,15 @@ async function auditWebsite(url) {
|
|
|
10
10
|
scoreTier: view.scoreTier,
|
|
11
11
|
...view.unscoredReason ? { unscoredReason: view.unscoredReason } : {},
|
|
12
12
|
durationMs: view.durationMs,
|
|
13
|
-
categories: view.groups.flatMap(
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
13
|
+
categories: view.groups.flatMap(
|
|
14
|
+
(g) => g.categories.map((c) => ({
|
|
15
|
+
name: c.name,
|
|
16
|
+
score: c.score,
|
|
17
|
+
pass: c.counts.pass,
|
|
18
|
+
warn: c.counts.warn,
|
|
19
|
+
fail: c.counts.fail
|
|
20
|
+
}))
|
|
21
|
+
),
|
|
20
22
|
topFails: report.topFails.map((f) => ({
|
|
21
23
|
id: f.id,
|
|
22
24
|
title: f.title,
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { runScan } from
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { runScan } from \"@forkpoint/agent-lighthouse-core\";\nimport { buildReportView } from \"@forkpoint/agent-lighthouse-report\";\n\n/**\n * Programmatic helper functions powering the Agent Lighthouse MCP server and custom agent tools.\n */\nexport async function auditWebsite(url: string) {\n const report = await runScan(url);\n const view = buildReportView(report);\n\n return {\n url: report.url,\n overallScore: view.overallScore,\n scoreTier: view.scoreTier,\n ...(view.unscoredReason ? { unscoredReason: view.unscoredReason } : {}),\n durationMs: view.durationMs,\n categories: view.groups.flatMap((g) =>\n g.categories.map((c) => ({\n name: c.name,\n score: c.score,\n pass: c.counts.pass,\n warn: c.counts.warn,\n fail: c.counts.fail,\n })),\n ),\n topFails: report.topFails.map((f) => ({\n id: f.id,\n title: f.title,\n priority: f.priority,\n impact: f.impact,\n fix: f.fix,\n })),\n };\n}\n"],"mappings":";AAAA,SAAS,eAAe;AACxB,SAAS,uBAAuB;AAKhC,eAAsB,aAAa,KAAa;AAC9C,QAAM,SAAS,MAAM,QAAQ,GAAG;AAChC,QAAM,OAAO,gBAAgB,MAAM;AAEnC,SAAO;AAAA,IACL,KAAK,OAAO;AAAA,IACZ,cAAc,KAAK;AAAA,IACnB,WAAW,KAAK;AAAA,IAChB,GAAI,KAAK,iBAAiB,EAAE,gBAAgB,KAAK,eAAe,IAAI,CAAC;AAAA,IACrE,YAAY,KAAK;AAAA,IACjB,YAAY,KAAK,OAAO;AAAA,MAAQ,CAAC,MAC/B,EAAE,WAAW,IAAI,CAAC,OAAO;AAAA,QACvB,MAAM,EAAE;AAAA,QACR,OAAO,EAAE;AAAA,QACT,MAAM,EAAE,OAAO;AAAA,QACf,MAAM,EAAE,OAAO;AAAA,QACf,MAAM,EAAE,OAAO;AAAA,MACjB,EAAE;AAAA,IACJ;AAAA,IACA,UAAU,OAAO,SAAS,IAAI,CAAC,OAAO;AAAA,MACpC,IAAI,EAAE;AAAA,MACN,OAAO,EAAE;AAAA,MACT,UAAU,EAAE;AAAA,MACZ,QAAQ,EAAE;AAAA,MACV,KAAK,EAAE;AAAA,IACT,EAAE;AAAA,EACJ;AACF;","names":[]}
|
package/dist/server.js
CHANGED
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/server.ts","../src/progress.ts","../src/tool.ts"],"sourcesContent":["import { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport {\n CallToolRequestSchema,\n ListToolsRequestSchema,\n} from '@modelcontextprotocol/sdk/types.js';\nimport { runScan } from '@forkpoint/agent-lighthouse-core';\nimport { buildReportView } from '@forkpoint/agent-lighthouse-report';\nimport { createProgressNotifier } from './progress';\nimport { AUDIT_TOOL, buildAuditSummary, targetUrl } from './tool';\n\ndeclare const __PACKAGE_VERSION__: string;\n\nconst MCP_SERVER_VERSION = typeof __PACKAGE_VERSION__ === 'string' ? __PACKAGE_VERSION__ : 'unknown';\n\nconst server = new Server(\n {\n name: 'agent-lighthouse-mcp',\n version: MCP_SERVER_VERSION,\n },\n {\n capabilities: {\n tools: {},\n },\n }\n);\n\n// Register Available Tools\nserver.setRequestHandler(ListToolsRequestSchema, async () => {\n return { tools: [AUDIT_TOOL] };\n});\n\n// Handle Tool Execution\nserver.setRequestHandler(CallToolRequestSchema, async (request) => {\n if (request.params.name === AUDIT_TOOL.name) {\n const url = targetUrl(request.params.arguments);\n\n // Forward scan progress as notifications/progress when the client\n // supplied a progressToken; otherwise scan silently as before.\n const onEvent = createProgressNotifier(\n request.params._meta?.progressToken,\n (params) => {\n // Fire-and-forget, but swallow rejections (e.g. client disconnected\n // mid-scan) so they never surface as unhandled.\n void server.notification({ method: 'notifications/progress', params }).catch(() => {});\n },\n );\n\n const report = await runScan(url, { onEvent });\n const summary = buildAuditSummary(report, buildReportView(report));\n\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(summary, null, 2),\n },\n ],\n };\n }\n\n throw new Error(`Unknown tool: ${request.params.name}`);\n});\n\nasync function main() {\n const transport = new StdioServerTransport();\n await server.connect(transport);\n}\n\nmain().catch((err) => {\n console.error('Fatal MCP server error:', err);\n process.exit(1);\n});\n","import type { ScanEvent } from '@forkpoint/agent-lighthouse-core';\n\nexport interface ProgressNotification {\n progressToken: string | number;\n progress: number;\n total: number;\n}\n\nexport interface ProgressNotifierOptions {\n /** Minimum milliseconds between notifications. Default 1000 (~1/sec). */\n minIntervalMs?: number;\n now?: () => number;\n}\n\n/**\n * Builds a runScan `onEvent` handler that forwards scan progress as MCP\n * `notifications/progress` params, throttled to ~1/sec. The final 1.0 is\n * always sent. Returns undefined when the request carried no progressToken,\n * so callers can pass the result straight into `runScan(url, { onEvent })`.\n */\nexport function createProgressNotifier(\n progressToken: string | number | undefined,\n notify: (notification: ProgressNotification) => void,\n options: ProgressNotifierOptions = {},\n): ((event: ScanEvent) => void) | undefined {\n if (progressToken === undefined) return undefined;\n const minInterval = options.minIntervalMs ?? 1000;\n const now = options.now ?? (() => Date.now());\n let lastSent = Number.NEGATIVE_INFINITY;\n\n const send = (fraction: number, force: boolean) => {\n const t = now();\n if (!force && t - lastSent < minInterval) return;\n lastSent = t;\n notify({ progressToken, progress: fraction, total: 1 });\n };\n\n return (event) => {\n switch (event.type) {\n case 'unit:done':\n case 'phase:start':\n case 'phase:done':\n send(event.fraction, false);\n break;\n case 'scan:done':\n send(event.fraction, true);\n break;\n default:\n break;\n }\n };\n}\n","import type { ScanReport } from '@forkpoint/agent-lighthouse-core';\nimport type { buildReportView } from '@forkpoint/agent-lighthouse-report';\n\n/**\n * The tool contract, lifted out of `server.ts`.\n *\n * `server.ts` constructs a `Server`, registers handlers and connects a stdio\n * transport at import time, so none of it could be reached from a test. What a\n * client actually depends on is the tool's declared schema and the shape of the\n * JSON it gets back — both pure, and both pinned here.\n */\n\nexport const AUDIT_TOOL = {\n name: 'audit_website',\n description:\n 'Audit a website or storefront for Agentic Readiness (WebMCP, OpenAPI, JSON-LD, robots.txt, and answer engines). Returns overall score, category breakdown, and actionable fix recommendations.',\n inputSchema: {\n type: 'object',\n properties: {\n url: {\n type: 'string',\n description: 'The target website or storefront URL to audit (e.g. https://example.com)',\n },\n },\n required: ['url'],\n },\n} as const;\n\ntype ReportView = ReturnType<typeof buildReportView>;\n\nexport interface AuditSummary {\n url: string;\n /** Null when the scan obtained too little evidence to judge the site. */\n overallScore: number | null;\n /** Null exactly when `overallScore` is. */\n scoreTier: string | null;\n /** Present when the score was suppressed: what the scan is missing. */\n unscoredReason?: string;\n durationSeconds: string;\n vitals: ReportView['vitals'];\n categories: Array<{\n name: string;\n score: number;\n passCount: number;\n warnCount: number;\n failCount: number;\n }>;\n topOpportunities: Array<{\n id: string;\n title: string;\n priority: string | undefined;\n impact: string | undefined;\n fix: string | undefined;\n }>;\n}\n\n/** How many fixes the tool hands back. A model does not need all 215. */\nexport const MAX_OPPORTUNITIES = 10;\n\n/**\n * Flatten a scan into the JSON the tool returns.\n *\n * The category list is flattened out of the view's groups because a model has\n * no use for the report's visual grouping, and the duration is pre-formatted so\n * the caller never has to divide by 1000 itself.\n */\nexport function buildAuditSummary(report: ScanReport, view: ReportView): AuditSummary {\n return {\n url: report.url,\n overallScore: view.overallScore,\n scoreTier: view.scoreTier,\n ...(view.unscoredReason ? { unscoredReason: view.unscoredReason } : {}),\n durationSeconds: (view.durationMs / 1000).toFixed(1),\n vitals: view.vitals,\n categories: view.groups.flatMap((g) =>\n g.categories.map((c) => ({\n name: c.name,\n score: c.score,\n passCount: c.counts.pass,\n warnCount: c.counts.warn,\n failCount: c.counts.fail,\n })),\n ),\n topOpportunities: report.topFails.slice(0, MAX_OPPORTUNITIES).map((f) => ({\n id: f.id,\n title: f.title,\n priority: f.priority,\n impact: f.impact,\n fix: f.fix,\n })),\n };\n}\n\n/**\n * The URL a `tools/call` request is asking about.\n *\n * `String(undefined)` is the string \"undefined\", which is truthy — the original\n * emptiness guard could never fire, so a call with no arguments scanned a URL\n * literally named \"undefined\" and failed deep inside the fetcher instead of at\n * the boundary.\n */\nexport function targetUrl(args: Record<string, unknown> | undefined): string {\n const raw = args?.['url'];\n if (typeof raw !== 'string' || raw.trim() === '') {\n throw new Error('Missing target URL');\n }\n return raw.trim();\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/server.ts","../src/progress.ts","../src/tool.ts"],"sourcesContent":["import { Server } from \"@modelcontextprotocol/sdk/server/index.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport {\n CallToolRequestSchema,\n ListToolsRequestSchema,\n} from \"@modelcontextprotocol/sdk/types.js\";\nimport { runScan } from \"@forkpoint/agent-lighthouse-core\";\nimport { buildReportView } from \"@forkpoint/agent-lighthouse-report\";\nimport { createProgressNotifier } from \"./progress\";\nimport { AUDIT_TOOL, buildAuditSummary, targetUrl } from \"./tool\";\n\ndeclare const __PACKAGE_VERSION__: string;\n\nconst MCP_SERVER_VERSION =\n typeof __PACKAGE_VERSION__ === \"string\" ? __PACKAGE_VERSION__ : \"unknown\";\n\nconst server = new Server(\n {\n name: \"agent-lighthouse-mcp\",\n version: MCP_SERVER_VERSION,\n },\n {\n capabilities: {\n tools: {},\n },\n },\n);\n\n// Register Available Tools\nserver.setRequestHandler(ListToolsRequestSchema, async () => {\n return { tools: [AUDIT_TOOL] };\n});\n\n// Handle Tool Execution\nserver.setRequestHandler(CallToolRequestSchema, async (request) => {\n if (request.params.name === AUDIT_TOOL.name) {\n const url = targetUrl(request.params.arguments);\n\n // Forward scan progress as notifications/progress when the client\n // supplied a progressToken; otherwise scan silently as before.\n const onEvent = createProgressNotifier(\n request.params._meta?.progressToken,\n (params) => {\n // Fire-and-forget, but swallow rejections (e.g. client disconnected\n // mid-scan) so they never surface as unhandled.\n void server\n .notification({ method: \"notifications/progress\", params })\n .catch(() => {});\n },\n );\n\n const report = await runScan(url, { onEvent });\n const summary = buildAuditSummary(report, buildReportView(report));\n\n return {\n content: [\n {\n type: \"text\",\n text: JSON.stringify(summary, null, 2),\n },\n ],\n };\n }\n\n throw new Error(`Unknown tool: ${request.params.name}`);\n});\n\nasync function main() {\n const transport = new StdioServerTransport();\n await server.connect(transport);\n}\n\nmain().catch((err) => {\n console.error(\"Fatal MCP server error:\", err);\n process.exit(1);\n});\n","import type { ScanEvent } from \"@forkpoint/agent-lighthouse-core\";\n\nexport interface ProgressNotification {\n progressToken: string | number;\n progress: number;\n total: number;\n}\n\nexport interface ProgressNotifierOptions {\n /** Minimum milliseconds between notifications. Default 1000 (~1/sec). */\n minIntervalMs?: number;\n now?: () => number;\n}\n\n/**\n * Builds a runScan `onEvent` handler that forwards scan progress as MCP\n * `notifications/progress` params, throttled to ~1/sec. The final 1.0 is\n * always sent. Returns undefined when the request carried no progressToken,\n * so callers can pass the result straight into `runScan(url, { onEvent })`.\n */\nexport function createProgressNotifier(\n progressToken: string | number | undefined,\n notify: (notification: ProgressNotification) => void,\n options: ProgressNotifierOptions = {},\n): ((event: ScanEvent) => void) | undefined {\n if (progressToken === undefined) return undefined;\n const minInterval = options.minIntervalMs ?? 1000;\n const now = options.now ?? (() => Date.now());\n let lastSent = Number.NEGATIVE_INFINITY;\n\n const send = (fraction: number, force: boolean) => {\n const t = now();\n if (!force && t - lastSent < minInterval) return;\n lastSent = t;\n notify({ progressToken, progress: fraction, total: 1 });\n };\n\n return (event) => {\n switch (event.type) {\n case \"unit:done\":\n case \"phase:start\":\n case \"phase:done\":\n send(event.fraction, false);\n break;\n case \"scan:done\":\n send(event.fraction, true);\n break;\n default:\n break;\n }\n };\n}\n","import type { ScanReport } from \"@forkpoint/agent-lighthouse-core\";\nimport type { buildReportView } from \"@forkpoint/agent-lighthouse-report\";\n\n/**\n * The tool contract, lifted out of `server.ts`.\n *\n * `server.ts` constructs a `Server`, registers handlers and connects a stdio\n * transport at import time, so none of it could be reached from a test. What a\n * client actually depends on is the tool's declared schema and the shape of the\n * JSON it gets back — both pure, and both pinned here.\n */\n\nexport const AUDIT_TOOL = {\n name: \"audit_website\",\n description:\n \"Audit a website or storefront for Agentic Readiness (WebMCP, OpenAPI, JSON-LD, robots.txt, and answer engines). Returns overall score, category breakdown, and actionable fix recommendations.\",\n inputSchema: {\n type: \"object\",\n properties: {\n url: {\n type: \"string\",\n description:\n \"The target website or storefront URL to audit (e.g. https://example.com)\",\n },\n },\n required: [\"url\"],\n },\n} as const;\n\ntype ReportView = ReturnType<typeof buildReportView>;\n\nexport interface AuditSummary {\n url: string;\n /** Null when the scan obtained too little evidence to judge the site. */\n overallScore: number | null;\n /** Null exactly when `overallScore` is. */\n scoreTier: string | null;\n /** Present when the score was suppressed: what the scan is missing. */\n unscoredReason?: string;\n durationSeconds: string;\n vitals: ReportView[\"vitals\"];\n categories: Array<{\n name: string;\n score: number;\n passCount: number;\n warnCount: number;\n failCount: number;\n }>;\n topOpportunities: Array<{\n id: string;\n title: string;\n priority: string | undefined;\n impact: string | undefined;\n fix: string | undefined;\n }>;\n}\n\n/** How many fixes the tool hands back. A model does not need all 215. */\nexport const MAX_OPPORTUNITIES = 10;\n\n/**\n * Flatten a scan into the JSON the tool returns.\n *\n * The category list is flattened out of the view's groups because a model has\n * no use for the report's visual grouping, and the duration is pre-formatted so\n * the caller never has to divide by 1000 itself.\n */\nexport function buildAuditSummary(\n report: ScanReport,\n view: ReportView,\n): AuditSummary {\n return {\n url: report.url,\n overallScore: view.overallScore,\n scoreTier: view.scoreTier,\n ...(view.unscoredReason ? { unscoredReason: view.unscoredReason } : {}),\n durationSeconds: (view.durationMs / 1000).toFixed(1),\n vitals: view.vitals,\n categories: view.groups.flatMap((g) =>\n g.categories.map((c) => ({\n name: c.name,\n score: c.score,\n passCount: c.counts.pass,\n warnCount: c.counts.warn,\n failCount: c.counts.fail,\n })),\n ),\n topOpportunities: report.topFails.slice(0, MAX_OPPORTUNITIES).map((f) => ({\n id: f.id,\n title: f.title,\n priority: f.priority,\n impact: f.impact,\n fix: f.fix,\n })),\n };\n}\n\n/**\n * The URL a `tools/call` request is asking about.\n *\n * `String(undefined)` is the string \"undefined\", which is truthy — the original\n * emptiness guard could never fire, so a call with no arguments scanned a URL\n * literally named \"undefined\" and failed deep inside the fetcher instead of at\n * the boundary.\n */\nexport function targetUrl(args: Record<string, unknown> | undefined): string {\n const raw = args?.[\"url\"];\n if (typeof raw !== \"string\" || raw.trim() === \"\") {\n throw new Error(\"Missing target URL\");\n }\n return raw.trim();\n}\n"],"mappings":"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","names":[]}
|
package/dist/server.mjs
CHANGED
package/dist/server.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/server.ts","../src/progress.ts","../src/tool.ts"],"sourcesContent":["import { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport {\n CallToolRequestSchema,\n ListToolsRequestSchema,\n} from '@modelcontextprotocol/sdk/types.js';\nimport { runScan } from '@forkpoint/agent-lighthouse-core';\nimport { buildReportView } from '@forkpoint/agent-lighthouse-report';\nimport { createProgressNotifier } from './progress';\nimport { AUDIT_TOOL, buildAuditSummary, targetUrl } from './tool';\n\ndeclare const __PACKAGE_VERSION__: string;\n\nconst MCP_SERVER_VERSION = typeof __PACKAGE_VERSION__ === 'string' ? __PACKAGE_VERSION__ : 'unknown';\n\nconst server = new Server(\n {\n name: 'agent-lighthouse-mcp',\n version: MCP_SERVER_VERSION,\n },\n {\n capabilities: {\n tools: {},\n },\n }\n);\n\n// Register Available Tools\nserver.setRequestHandler(ListToolsRequestSchema, async () => {\n return { tools: [AUDIT_TOOL] };\n});\n\n// Handle Tool Execution\nserver.setRequestHandler(CallToolRequestSchema, async (request) => {\n if (request.params.name === AUDIT_TOOL.name) {\n const url = targetUrl(request.params.arguments);\n\n // Forward scan progress as notifications/progress when the client\n // supplied a progressToken; otherwise scan silently as before.\n const onEvent = createProgressNotifier(\n request.params._meta?.progressToken,\n (params) => {\n // Fire-and-forget, but swallow rejections (e.g. client disconnected\n // mid-scan) so they never surface as unhandled.\n void server.notification({ method: 'notifications/progress', params }).catch(() => {});\n },\n );\n\n const report = await runScan(url, { onEvent });\n const summary = buildAuditSummary(report, buildReportView(report));\n\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(summary, null, 2),\n },\n ],\n };\n }\n\n throw new Error(`Unknown tool: ${request.params.name}`);\n});\n\nasync function main() {\n const transport = new StdioServerTransport();\n await server.connect(transport);\n}\n\nmain().catch((err) => {\n console.error('Fatal MCP server error:', err);\n process.exit(1);\n});\n","import type { ScanEvent } from '@forkpoint/agent-lighthouse-core';\n\nexport interface ProgressNotification {\n progressToken: string | number;\n progress: number;\n total: number;\n}\n\nexport interface ProgressNotifierOptions {\n /** Minimum milliseconds between notifications. Default 1000 (~1/sec). */\n minIntervalMs?: number;\n now?: () => number;\n}\n\n/**\n * Builds a runScan `onEvent` handler that forwards scan progress as MCP\n * `notifications/progress` params, throttled to ~1/sec. The final 1.0 is\n * always sent. Returns undefined when the request carried no progressToken,\n * so callers can pass the result straight into `runScan(url, { onEvent })`.\n */\nexport function createProgressNotifier(\n progressToken: string | number | undefined,\n notify: (notification: ProgressNotification) => void,\n options: ProgressNotifierOptions = {},\n): ((event: ScanEvent) => void) | undefined {\n if (progressToken === undefined) return undefined;\n const minInterval = options.minIntervalMs ?? 1000;\n const now = options.now ?? (() => Date.now());\n let lastSent = Number.NEGATIVE_INFINITY;\n\n const send = (fraction: number, force: boolean) => {\n const t = now();\n if (!force && t - lastSent < minInterval) return;\n lastSent = t;\n notify({ progressToken, progress: fraction, total: 1 });\n };\n\n return (event) => {\n switch (event.type) {\n case 'unit:done':\n case 'phase:start':\n case 'phase:done':\n send(event.fraction, false);\n break;\n case 'scan:done':\n send(event.fraction, true);\n break;\n default:\n break;\n }\n };\n}\n","import type { ScanReport } from '@forkpoint/agent-lighthouse-core';\nimport type { buildReportView } from '@forkpoint/agent-lighthouse-report';\n\n/**\n * The tool contract, lifted out of `server.ts`.\n *\n * `server.ts` constructs a `Server`, registers handlers and connects a stdio\n * transport at import time, so none of it could be reached from a test. What a\n * client actually depends on is the tool's declared schema and the shape of the\n * JSON it gets back — both pure, and both pinned here.\n */\n\nexport const AUDIT_TOOL = {\n name: 'audit_website',\n description:\n 'Audit a website or storefront for Agentic Readiness (WebMCP, OpenAPI, JSON-LD, robots.txt, and answer engines). Returns overall score, category breakdown, and actionable fix recommendations.',\n inputSchema: {\n type: 'object',\n properties: {\n url: {\n type: 'string',\n description: 'The target website or storefront URL to audit (e.g. https://example.com)',\n },\n },\n required: ['url'],\n },\n} as const;\n\ntype ReportView = ReturnType<typeof buildReportView>;\n\nexport interface AuditSummary {\n url: string;\n /** Null when the scan obtained too little evidence to judge the site. */\n overallScore: number | null;\n /** Null exactly when `overallScore` is. */\n scoreTier: string | null;\n /** Present when the score was suppressed: what the scan is missing. */\n unscoredReason?: string;\n durationSeconds: string;\n vitals: ReportView['vitals'];\n categories: Array<{\n name: string;\n score: number;\n passCount: number;\n warnCount: number;\n failCount: number;\n }>;\n topOpportunities: Array<{\n id: string;\n title: string;\n priority: string | undefined;\n impact: string | undefined;\n fix: string | undefined;\n }>;\n}\n\n/** How many fixes the tool hands back. A model does not need all 215. */\nexport const MAX_OPPORTUNITIES = 10;\n\n/**\n * Flatten a scan into the JSON the tool returns.\n *\n * The category list is flattened out of the view's groups because a model has\n * no use for the report's visual grouping, and the duration is pre-formatted so\n * the caller never has to divide by 1000 itself.\n */\nexport function buildAuditSummary(report: ScanReport, view: ReportView): AuditSummary {\n return {\n url: report.url,\n overallScore: view.overallScore,\n scoreTier: view.scoreTier,\n ...(view.unscoredReason ? { unscoredReason: view.unscoredReason } : {}),\n durationSeconds: (view.durationMs / 1000).toFixed(1),\n vitals: view.vitals,\n categories: view.groups.flatMap((g) =>\n g.categories.map((c) => ({\n name: c.name,\n score: c.score,\n passCount: c.counts.pass,\n warnCount: c.counts.warn,\n failCount: c.counts.fail,\n })),\n ),\n topOpportunities: report.topFails.slice(0, MAX_OPPORTUNITIES).map((f) => ({\n id: f.id,\n title: f.title,\n priority: f.priority,\n impact: f.impact,\n fix: f.fix,\n })),\n };\n}\n\n/**\n * The URL a `tools/call` request is asking about.\n *\n * `String(undefined)` is the string \"undefined\", which is truthy — the original\n * emptiness guard could never fire, so a call with no arguments scanned a URL\n * literally named \"undefined\" and failed deep inside the fetcher instead of at\n * the boundary.\n */\nexport function targetUrl(args: Record<string, unknown> | undefined): string {\n const raw = args?.['url'];\n if (typeof raw !== 'string' || raw.trim() === '') {\n throw new Error('Missing target URL');\n }\n return raw.trim();\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/server.ts","../src/progress.ts","../src/tool.ts"],"sourcesContent":["import { Server } from \"@modelcontextprotocol/sdk/server/index.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport {\n CallToolRequestSchema,\n ListToolsRequestSchema,\n} from \"@modelcontextprotocol/sdk/types.js\";\nimport { runScan } from \"@forkpoint/agent-lighthouse-core\";\nimport { buildReportView } from \"@forkpoint/agent-lighthouse-report\";\nimport { createProgressNotifier } from \"./progress\";\nimport { AUDIT_TOOL, buildAuditSummary, targetUrl } from \"./tool\";\n\ndeclare const __PACKAGE_VERSION__: string;\n\nconst MCP_SERVER_VERSION =\n typeof __PACKAGE_VERSION__ === \"string\" ? __PACKAGE_VERSION__ : \"unknown\";\n\nconst server = new Server(\n {\n name: \"agent-lighthouse-mcp\",\n version: MCP_SERVER_VERSION,\n },\n {\n capabilities: {\n tools: {},\n },\n },\n);\n\n// Register Available Tools\nserver.setRequestHandler(ListToolsRequestSchema, async () => {\n return { tools: [AUDIT_TOOL] };\n});\n\n// Handle Tool Execution\nserver.setRequestHandler(CallToolRequestSchema, async (request) => {\n if (request.params.name === AUDIT_TOOL.name) {\n const url = targetUrl(request.params.arguments);\n\n // Forward scan progress as notifications/progress when the client\n // supplied a progressToken; otherwise scan silently as before.\n const onEvent = createProgressNotifier(\n request.params._meta?.progressToken,\n (params) => {\n // Fire-and-forget, but swallow rejections (e.g. client disconnected\n // mid-scan) so they never surface as unhandled.\n void server\n .notification({ method: \"notifications/progress\", params })\n .catch(() => {});\n },\n );\n\n const report = await runScan(url, { onEvent });\n const summary = buildAuditSummary(report, buildReportView(report));\n\n return {\n content: [\n {\n type: \"text\",\n text: JSON.stringify(summary, null, 2),\n },\n ],\n };\n }\n\n throw new Error(`Unknown tool: ${request.params.name}`);\n});\n\nasync function main() {\n const transport = new StdioServerTransport();\n await server.connect(transport);\n}\n\nmain().catch((err) => {\n console.error(\"Fatal MCP server error:\", err);\n process.exit(1);\n});\n","import type { ScanEvent } from \"@forkpoint/agent-lighthouse-core\";\n\nexport interface ProgressNotification {\n progressToken: string | number;\n progress: number;\n total: number;\n}\n\nexport interface ProgressNotifierOptions {\n /** Minimum milliseconds between notifications. Default 1000 (~1/sec). */\n minIntervalMs?: number;\n now?: () => number;\n}\n\n/**\n * Builds a runScan `onEvent` handler that forwards scan progress as MCP\n * `notifications/progress` params, throttled to ~1/sec. The final 1.0 is\n * always sent. Returns undefined when the request carried no progressToken,\n * so callers can pass the result straight into `runScan(url, { onEvent })`.\n */\nexport function createProgressNotifier(\n progressToken: string | number | undefined,\n notify: (notification: ProgressNotification) => void,\n options: ProgressNotifierOptions = {},\n): ((event: ScanEvent) => void) | undefined {\n if (progressToken === undefined) return undefined;\n const minInterval = options.minIntervalMs ?? 1000;\n const now = options.now ?? (() => Date.now());\n let lastSent = Number.NEGATIVE_INFINITY;\n\n const send = (fraction: number, force: boolean) => {\n const t = now();\n if (!force && t - lastSent < minInterval) return;\n lastSent = t;\n notify({ progressToken, progress: fraction, total: 1 });\n };\n\n return (event) => {\n switch (event.type) {\n case \"unit:done\":\n case \"phase:start\":\n case \"phase:done\":\n send(event.fraction, false);\n break;\n case \"scan:done\":\n send(event.fraction, true);\n break;\n default:\n break;\n }\n };\n}\n","import type { ScanReport } from \"@forkpoint/agent-lighthouse-core\";\nimport type { buildReportView } from \"@forkpoint/agent-lighthouse-report\";\n\n/**\n * The tool contract, lifted out of `server.ts`.\n *\n * `server.ts` constructs a `Server`, registers handlers and connects a stdio\n * transport at import time, so none of it could be reached from a test. What a\n * client actually depends on is the tool's declared schema and the shape of the\n * JSON it gets back — both pure, and both pinned here.\n */\n\nexport const AUDIT_TOOL = {\n name: \"audit_website\",\n description:\n \"Audit a website or storefront for Agentic Readiness (WebMCP, OpenAPI, JSON-LD, robots.txt, and answer engines). Returns overall score, category breakdown, and actionable fix recommendations.\",\n inputSchema: {\n type: \"object\",\n properties: {\n url: {\n type: \"string\",\n description:\n \"The target website or storefront URL to audit (e.g. https://example.com)\",\n },\n },\n required: [\"url\"],\n },\n} as const;\n\ntype ReportView = ReturnType<typeof buildReportView>;\n\nexport interface AuditSummary {\n url: string;\n /** Null when the scan obtained too little evidence to judge the site. */\n overallScore: number | null;\n /** Null exactly when `overallScore` is. */\n scoreTier: string | null;\n /** Present when the score was suppressed: what the scan is missing. */\n unscoredReason?: string;\n durationSeconds: string;\n vitals: ReportView[\"vitals\"];\n categories: Array<{\n name: string;\n score: number;\n passCount: number;\n warnCount: number;\n failCount: number;\n }>;\n topOpportunities: Array<{\n id: string;\n title: string;\n priority: string | undefined;\n impact: string | undefined;\n fix: string | undefined;\n }>;\n}\n\n/** How many fixes the tool hands back. A model does not need all 215. */\nexport const MAX_OPPORTUNITIES = 10;\n\n/**\n * Flatten a scan into the JSON the tool returns.\n *\n * The category list is flattened out of the view's groups because a model has\n * no use for the report's visual grouping, and the duration is pre-formatted so\n * the caller never has to divide by 1000 itself.\n */\nexport function buildAuditSummary(\n report: ScanReport,\n view: ReportView,\n): AuditSummary {\n return {\n url: report.url,\n overallScore: view.overallScore,\n scoreTier: view.scoreTier,\n ...(view.unscoredReason ? { unscoredReason: view.unscoredReason } : {}),\n durationSeconds: (view.durationMs / 1000).toFixed(1),\n vitals: view.vitals,\n categories: view.groups.flatMap((g) =>\n g.categories.map((c) => ({\n name: c.name,\n score: c.score,\n passCount: c.counts.pass,\n warnCount: c.counts.warn,\n failCount: c.counts.fail,\n })),\n ),\n topOpportunities: report.topFails.slice(0, MAX_OPPORTUNITIES).map((f) => ({\n id: f.id,\n title: f.title,\n priority: f.priority,\n impact: f.impact,\n fix: f.fix,\n })),\n };\n}\n\n/**\n * The URL a `tools/call` request is asking about.\n *\n * `String(undefined)` is the string \"undefined\", which is truthy — the original\n * emptiness guard could never fire, so a call with no arguments scanned a URL\n * literally named \"undefined\" and failed deep inside the fetcher instead of at\n * the boundary.\n */\nexport function targetUrl(args: Record<string, unknown> | undefined): string {\n const raw = args?.[\"url\"];\n if (typeof raw !== \"string\" || raw.trim() === \"\") {\n throw new Error(\"Missing target URL\");\n }\n return raw.trim();\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forkpoint/agent-lighthouse-mcp",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.1.0",
|
|
4
4
|
"description": "Model Context Protocol (MCP) server that lets Claude, Cursor, and agentic IDEs audit website AI-agent readiness with Agent Lighthouse",
|
|
5
5
|
"author": "ForkPoint",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -47,8 +47,8 @@
|
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
49
|
"@modelcontextprotocol/sdk": "^1.5.0",
|
|
50
|
-
"@forkpoint/agent-lighthouse-core": "
|
|
51
|
-
"@forkpoint/agent-lighthouse-report": "
|
|
50
|
+
"@forkpoint/agent-lighthouse-core": "4.1.0",
|
|
51
|
+
"@forkpoint/agent-lighthouse-report": "4.1.0"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
54
|
"@types/node": "^22.10.5",
|