@forkpoint/agent-lighthouse-mcp 1.0.0 → 2.0.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/README.md CHANGED
@@ -19,7 +19,7 @@ Use this package to let Claude Desktop, Cursor, and other MCP-compatible agentic
19
19
 
20
20
  ## What It Exposes
21
21
 
22
- The MCP server wraps Agent Lighthouse scans so an assistant can check `llms.txt`, robots.txt AI crawler access, Schema.org, WebMCP, OpenAPI discovery, accessibility, semantic HTML, AEO/GEO signals, and technical readiness from inside the IDE.
22
+ The MCP server wraps Agent Lighthouse scans so an assistant can check a site across the v2 taxonomy's eight categories — access & crawl control, machine discovery, agent interfaces, agentic commerce, content extraction, structured data, answer readiness, and agent operability & safety — from inside the IDE.
23
23
 
24
24
  ## Links
25
25
 
package/dist/server.js CHANGED
@@ -35,8 +35,57 @@ function createProgressNotifier(progressToken, notify, options = {}) {
35
35
  };
36
36
  }
37
37
 
38
+ // src/tool.ts
39
+ var AUDIT_TOOL = {
40
+ name: "audit_website",
41
+ description: "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.",
42
+ inputSchema: {
43
+ type: "object",
44
+ properties: {
45
+ url: {
46
+ type: "string",
47
+ description: "The target website or storefront URL to audit (e.g. https://example.com)"
48
+ }
49
+ },
50
+ required: ["url"]
51
+ }
52
+ };
53
+ var MAX_OPPORTUNITIES = 10;
54
+ function buildAuditSummary(report, view) {
55
+ return {
56
+ url: report.url,
57
+ overallScore: view.overallScore,
58
+ scoreTier: view.scoreTier,
59
+ durationSeconds: (view.durationMs / 1e3).toFixed(1),
60
+ vitals: view.vitals,
61
+ categories: view.groups.flatMap(
62
+ (g) => g.categories.map((c) => ({
63
+ name: c.name,
64
+ score: c.score,
65
+ passCount: c.counts.pass,
66
+ warnCount: c.counts.warn,
67
+ failCount: c.counts.fail
68
+ }))
69
+ ),
70
+ topOpportunities: report.topFails.slice(0, MAX_OPPORTUNITIES).map((f) => ({
71
+ id: f.id,
72
+ title: f.title,
73
+ priority: f.priority,
74
+ impact: f.impact,
75
+ fix: f.fix
76
+ }))
77
+ };
78
+ }
79
+ function targetUrl(args) {
80
+ const raw = args?.["url"];
81
+ if (typeof raw !== "string" || raw.trim() === "") {
82
+ throw new Error("Missing target URL");
83
+ }
84
+ return raw.trim();
85
+ }
86
+
38
87
  // src/server.ts
39
- var MCP_SERVER_VERSION = true ? "1.0.0" : "unknown";
88
+ var MCP_SERVER_VERSION = true ? "2.0.0" : "unknown";
40
89
  var server = new import_server.Server(
41
90
  {
42
91
  name: "agent-lighthouse-mcp",
@@ -49,31 +98,11 @@ var server = new import_server.Server(
49
98
  }
50
99
  );
51
100
  server.setRequestHandler(import_types.ListToolsRequestSchema, async () => {
52
- return {
53
- tools: [
54
- {
55
- name: "audit_website",
56
- description: "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.",
57
- inputSchema: {
58
- type: "object",
59
- properties: {
60
- url: {
61
- type: "string",
62
- description: "The target website or storefront URL to audit (e.g. https://example.com)"
63
- }
64
- },
65
- required: ["url"]
66
- }
67
- }
68
- ]
69
- };
101
+ return { tools: [AUDIT_TOOL] };
70
102
  });
71
103
  server.setRequestHandler(import_types.CallToolRequestSchema, async (request) => {
72
- if (request.params.name === "audit_website") {
73
- const url = String(request.params.arguments?.url);
74
- if (!url) {
75
- throw new Error("Missing target URL");
76
- }
104
+ if (request.params.name === AUDIT_TOOL.name) {
105
+ const url = targetUrl(request.params.arguments);
77
106
  const onEvent = createProgressNotifier(
78
107
  request.params._meta?.progressToken,
79
108
  (params) => {
@@ -82,30 +111,7 @@ server.setRequestHandler(import_types.CallToolRequestSchema, async (request) =>
82
111
  }
83
112
  );
84
113
  const report = await (0, import_agent_lighthouse_core.runScan)(url, { onEvent });
85
- const view = (0, import_agent_lighthouse_report.buildReportView)(report);
86
- const summary = {
87
- url: report.url,
88
- overallScore: view.overallScore,
89
- scoreTier: view.scoreTier,
90
- durationSeconds: (view.durationMs / 1e3).toFixed(1),
91
- vitals: view.vitals,
92
- categories: view.groups.flatMap(
93
- (g) => g.categories.map((c) => ({
94
- name: c.name,
95
- score: c.score,
96
- passCount: c.counts.pass,
97
- warnCount: c.counts.warn,
98
- failCount: c.counts.fail
99
- }))
100
- ),
101
- topOpportunities: report.topFails.slice(0, 10).map((f) => ({
102
- id: f.id,
103
- title: f.title,
104
- priority: f.priority,
105
- impact: f.impact,
106
- fix: f.fix
107
- }))
108
- };
114
+ const summary = buildAuditSummary(report, (0, import_agent_lighthouse_report.buildReportView)(report));
109
115
  return {
110
116
  content: [
111
117
  {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/server.ts","../src/progress.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';\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 {\n tools: [\n {\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 },\n ],\n };\n});\n\n// Handle Tool Execution\nserver.setRequestHandler(CallToolRequestSchema, async (request) => {\n if (request.params.name === 'audit_website') {\n const url = String(request.params.arguments?.url);\n if (!url) {\n throw new Error('Missing target URL');\n }\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 view = buildReportView(report);\n\n const summary = {\n url: report.url,\n overallScore: view.overallScore,\n scoreTier: view.scoreTier,\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, 10).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 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"],"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 = 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 overallScore: number;\n scoreTier: 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 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
@@ -36,8 +36,57 @@ function createProgressNotifier(progressToken, notify, options = {}) {
36
36
  };
37
37
  }
38
38
 
39
+ // src/tool.ts
40
+ var AUDIT_TOOL = {
41
+ name: "audit_website",
42
+ description: "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.",
43
+ inputSchema: {
44
+ type: "object",
45
+ properties: {
46
+ url: {
47
+ type: "string",
48
+ description: "The target website or storefront URL to audit (e.g. https://example.com)"
49
+ }
50
+ },
51
+ required: ["url"]
52
+ }
53
+ };
54
+ var MAX_OPPORTUNITIES = 10;
55
+ function buildAuditSummary(report, view) {
56
+ return {
57
+ url: report.url,
58
+ overallScore: view.overallScore,
59
+ scoreTier: view.scoreTier,
60
+ durationSeconds: (view.durationMs / 1e3).toFixed(1),
61
+ vitals: view.vitals,
62
+ categories: view.groups.flatMap(
63
+ (g) => g.categories.map((c) => ({
64
+ name: c.name,
65
+ score: c.score,
66
+ passCount: c.counts.pass,
67
+ warnCount: c.counts.warn,
68
+ failCount: c.counts.fail
69
+ }))
70
+ ),
71
+ topOpportunities: report.topFails.slice(0, MAX_OPPORTUNITIES).map((f) => ({
72
+ id: f.id,
73
+ title: f.title,
74
+ priority: f.priority,
75
+ impact: f.impact,
76
+ fix: f.fix
77
+ }))
78
+ };
79
+ }
80
+ function targetUrl(args) {
81
+ const raw = args?.["url"];
82
+ if (typeof raw !== "string" || raw.trim() === "") {
83
+ throw new Error("Missing target URL");
84
+ }
85
+ return raw.trim();
86
+ }
87
+
39
88
  // src/server.ts
40
- var MCP_SERVER_VERSION = true ? "1.0.0" : "unknown";
89
+ var MCP_SERVER_VERSION = true ? "2.0.0" : "unknown";
41
90
  var server = new Server(
42
91
  {
43
92
  name: "agent-lighthouse-mcp",
@@ -50,31 +99,11 @@ var server = new Server(
50
99
  }
51
100
  );
52
101
  server.setRequestHandler(ListToolsRequestSchema, async () => {
53
- return {
54
- tools: [
55
- {
56
- name: "audit_website",
57
- description: "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.",
58
- inputSchema: {
59
- type: "object",
60
- properties: {
61
- url: {
62
- type: "string",
63
- description: "The target website or storefront URL to audit (e.g. https://example.com)"
64
- }
65
- },
66
- required: ["url"]
67
- }
68
- }
69
- ]
70
- };
102
+ return { tools: [AUDIT_TOOL] };
71
103
  });
72
104
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
73
- if (request.params.name === "audit_website") {
74
- const url = String(request.params.arguments?.url);
75
- if (!url) {
76
- throw new Error("Missing target URL");
77
- }
105
+ if (request.params.name === AUDIT_TOOL.name) {
106
+ const url = targetUrl(request.params.arguments);
78
107
  const onEvent = createProgressNotifier(
79
108
  request.params._meta?.progressToken,
80
109
  (params) => {
@@ -83,30 +112,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
83
112
  }
84
113
  );
85
114
  const report = await runScan(url, { onEvent });
86
- const view = buildReportView(report);
87
- const summary = {
88
- url: report.url,
89
- overallScore: view.overallScore,
90
- scoreTier: view.scoreTier,
91
- durationSeconds: (view.durationMs / 1e3).toFixed(1),
92
- vitals: view.vitals,
93
- categories: view.groups.flatMap(
94
- (g) => g.categories.map((c) => ({
95
- name: c.name,
96
- score: c.score,
97
- passCount: c.counts.pass,
98
- warnCount: c.counts.warn,
99
- failCount: c.counts.fail
100
- }))
101
- ),
102
- topOpportunities: report.topFails.slice(0, 10).map((f) => ({
103
- id: f.id,
104
- title: f.title,
105
- priority: f.priority,
106
- impact: f.impact,
107
- fix: f.fix
108
- }))
109
- };
115
+ const summary = buildAuditSummary(report, buildReportView(report));
110
116
  return {
111
117
  content: [
112
118
  {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/server.ts","../src/progress.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';\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 {\n tools: [\n {\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 },\n ],\n };\n});\n\n// Handle Tool Execution\nserver.setRequestHandler(CallToolRequestSchema, async (request) => {\n if (request.params.name === 'audit_website') {\n const url = String(request.params.arguments?.url);\n if (!url) {\n throw new Error('Missing target URL');\n }\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 view = buildReportView(report);\n\n const summary = {\n url: report.url,\n overallScore: view.overallScore,\n scoreTier: view.scoreTier,\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, 10).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 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"],"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 = 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 overallScore: number;\n scoreTier: 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 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": "1.0.0",
3
+ "version": "2.0.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": "1.0.0",
51
- "@forkpoint/agent-lighthouse-report": "1.0.0"
50
+ "@forkpoint/agent-lighthouse-report": "2.0.0",
51
+ "@forkpoint/agent-lighthouse-core": "2.0.0"
52
52
  },
53
53
  "devDependencies": {
54
54
  "@types/node": "^22.10.5",