@jrpool/kilotest 63.1.8 → 63.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.
package/content.ts ADDED
@@ -0,0 +1,12 @@
1
+ /*
2
+ content.ts
3
+ Canonical prose describing what Kilotest is and does, reused verbatim by mcp.ts, scripts/generate-openapi.ts, and scripts/generate-lhm.ts, so the sentences are written once instead of being hand-copied into mcp.ts, lhm.plugin.json, llms.txt, and openapi.yaml.
4
+ */
5
+
6
+ // CONSTANTS
7
+
8
+ // Brief description of the Kilotest tool collection. Used as the MCP server description and as lhm.plugin.json’s top-level description. No length limit.
9
+ export const collectionDescription = 'Tools that test web pages with an ensemble of 12 rule engines for front-end quality (accessibility, usability, and W3C web standards, including HTML, CSS, ARIA, and WCAG)';
10
+
11
+ // Description of the Kilotest domain and data hierarchy. Used as (1) the server instructions, (2) the opening paragraph of llms.txt, (3) the start of the openapi.yaml info.description, and (4) the getKilotestOverview tool description (but not part of the other tool descriptions, to avoid diluting how models read each tool), because some MCP clients do not propagate server-level instructions or the server description to their models.
12
+ export const sharedContext = 'Kilotest integrates an ensemble of 12 rule engines, with about 1,300 rules, to test web pages for front-end quality (accessibility, usability, and W3C web standards, including HTML, CSS, ARIA, and WCAG). Each test job generates a standardized report describing which elements violated which rules and grouping the violations into about 380 issues. Kilotest can list the tested pages, the reports about each page, the issues in each report, the violators of each issue, and the diagnoses of each violation. Kilotest also accepts requests to test pages not yet tested and to retest already tested pages.';
package/docs/AI-TOOLS.md CHANGED
@@ -40,6 +40,8 @@ The internal features that make Kilotest a collection of tools for language mode
40
40
 
41
41
  The external features that support the use of Kilotest as a collection of AI tools are:
42
42
 
43
+ ### Registrations
44
+
43
45
  - A [pull request](https://github.com/public-apis/public-apis/pull/6346/changes) to add Kilotest to the list of public APIs in the `public-apis` repository.
44
46
  - An [issue](https://github.com/APIs-guru/openapi-directory/issues/2677) to add Kilotest to `openapi-directory`.
45
47
  - A [pull request](https://github.com/w3c/wai-evaluation-tools-list/pull/1153) to add Kilotest to the WAI evaluation tools list.
@@ -51,17 +53,27 @@ The external features that support the use of Kilotest as a collection of AI too
51
53
  - Registration of Kilotest with the [RapidAPI](https://rapidapi.com/jrpool/api/kilotest/playground/apiendpoint_0f03577a-ff9a-472a-a0ed-533bd198981a) Hub.
52
54
  - Registration of Kilotest with the [LobeHub MCP Market](https://lobehub.com/mcp) (identifier: `jrpool-kilotest`), enabled with an `lhm.plugin.json` file.
53
55
  - Registration of Kilotest with [MCPTry](https://mcptry.com/servers/io.github.jrpool/kilotest).
56
+ - Registration of the MCP server as a community connector in the [Claude directory of plugins and connectors](https://claude.ai/directory/kilotest).
57
+ - Registration of Kilotest and its sitemap at [Google Search Console](https://search.google.com/search-console?resource_id=sc-domain:kilotest.com) and [Bing Webmaster Tools](https://www.bing.com/webmasters/home?siteUrl=https://kilotest.com/).
58
+ - Submission of the MCP server to `mcp.directory`.
59
+ - Submission of the MCP server to [MCP.so](https://mcp.so/). The submission is in review.
60
+
61
+ ### Deployment
62
+
54
63
  - Deployment of an MCP server in HTTP mode on the Kilotest service host.
55
- - Submission of the MCP server to the [Claude directory of plugins and connectors](https://claude.ai/directory/manage).
64
+
65
+ ### Local configuration
66
+
56
67
  - Configuration of Claude Desktop on the local development host and the `claude.ai` web application to connect Claude Desktop models to the Kilotest MCP server. The configuration was performed in the UI of each platform with the addition of Kilotest as a _connector_. The user used the `Customize/Connectors/Add connector/Add custom connector` interface, providing these data before activating the `Add` button:
57
68
 
58
69
  - Name: Kilotest
59
70
  - Remote MCP server URL: `https://kilotest.com/mcp`
60
71
 
72
+ ### Documentation
73
+
61
74
  - MCP-related topics added to the [repository details](https://github.com/jrpool/kilotest).
62
- - An [MCP Registry badge](https://registry.modelcontextprotocol.io/v0/servers?search=kilotest) added to the `README.md` file.
75
+ - Registry badges added to the `README.md` file.
63
76
  - A link to the MCP server on the `README.md` page of the [`testaro-issues`](https://github.com/jrpool/testaro-issues) repository.
64
- - Registration of Kilotest and its sitemap at [Google Search Console](https://search.google.com/search-console?resource_id=sc-domain:kilotest.com) and [Bing Webmaster Tools](https://www.bing.com/webmasters/home?siteUrl=https://kilotest.com/).
65
77
 
66
78
  ## Required maintenance
67
79
 
@@ -69,7 +81,7 @@ The maintenance routine is prescribed in the [README.md](../README.md) file.
69
81
 
70
82
  Releasing a new version is a two-script procedure:
71
83
 
72
- - On a feature branch, commit all substantive changes, then execute `npm run prePR -- <level>` (`major`, `minor`, or `patch`). The script bumps `package.json` and `package-lock.json`, regenerates `openapi.yaml`, `openapi-3.0.yaml`, and the versions of `server.json` and `lhm.plugin.json` by explicitly running `npm run generate`, commits and pushes the result, and records the branch name for the next step.
84
+ - On a feature branch, commit all substantive changes, then execute `npm run prePR -- <level>` (`major`, `minor`, or `patch`). The script bumps `package.json` and `package-lock.json`, regenerates `openapi.yaml`, `openapi-3.0.yaml`, and the descriptions, schemas, and versions of `server.json` and `lhm.plugin.json` by explicitly running `npm run generate`, commits and pushes the result, and records the branch name for the next step.
73
85
  - Submit a pull request and merge it to `main` (triggers `publish-mcp.yml`, which publishes `server.json` to the MCP Registry).
74
86
  - Execute `npm run postPR`. The script checks out and pulls `main`, verifies that the merge landed, publishes to npm, updates the LobeHub listing via `npm run publish:lhm`, deletes the feature branch, and prunes remote-tracking branches. If the LobeHub update fails, retry it alone with `npm run publish:lhm`.
75
87
 
@@ -84,8 +96,9 @@ A change to the tool or API contract (tool names, parameter names, schemas, resp
84
96
  - `openapi.yaml`: regenerated automatically by the `version` hook during `npm run prePR`; never edit it by hand.
85
97
  - `openapi-3.0.yaml`: an OpenAPI 3.0.3 conversion of `openapi.yaml`, regenerated by the same hook, for registries whose importers reject 3.1 documents; never edit it by hand.
86
98
  - `server.json`: contains only the server descriptor (name, description, repository, remotes, version), no tool schemas. Its version is synced by `update-versions.ts`; edit it manually only if the server description or remote URL changes.
87
- - `lhm.plugin.json`: embeds every tool’s input and output schema and its descriptions, in the LobeHub manifest format. It is hand-maintained, not generated, so transform it to match `api/schemas.ts` and `mcp.ts` whenever the contract changes.
88
- - `llms.txt`, `llms-full.txt`, `capability.md`, `README.md`: hand-maintained; search them for stale parameter and path names after a contract change.
99
+ - `lhm.plugin.json`: embeds every tool’s input and output schema and its descriptions, in the LobeHub manifest format. Its `description` and every tool’s `description`, `inputSchema`, `outputSchema`, and `annotations` are regenerated from `mcp.ts`/`api/schemas.ts` by `scripts/generate-lhm.ts` (run by `npm run generate`, part of `npm run prePR`); never edit those fields by hand. The remaining fields (`author`, `cloudEndpoint`, `homepage`, `identifier`, `name`, `prompts`, `resources`, `tags`, `version`, and each tool’s `execution` block) have no canonical source elsewhere and are still edited manually. See `docs/decisions/010-content-generation.md`.
100
+ - `content.ts`: canonical prose shared verbatim by `mcp.ts`, `scripts/generate-openapi.ts`, and `scripts/generate-lhm.ts`. Also hand-check `llms.txt`’s opening paragraph, which is supposed to match `content.ts`’s `sharedContext` verbatim but is not generated.
101
+ - `llms-full.txt`, `capability.md`, `README.md`, `docs/openai-plugin/skills/kilotest-overview/SKILL.md`: hand-maintained, intentionally paraphrased rather than copied verbatim; search them for stale parameter and path names after a contract change.
89
102
 
90
103
  #### External registries that update themselves, needing no maintainer action
91
104
 
@@ -123,7 +136,7 @@ To-dos recommended by Claude Sonnet 5 Medium on Devin:
123
136
 
124
137
  - **No content/backlink outreach.** Registrations are passive listings; there's no blog post, Show HN, or subreddit post (r/modelcontextprotocol, r/accessibility, r/webdev) building external backlinks and social-discovery signal, which the doc's own "difficult problem…lack of standardization" framing suggests is exactly the gap left for the **human**-driven parts of discoverability.
125
138
 
126
- - **Other directories not yet targeted.** The [mcp.so](https://mcp.so/submit) directory offers a free tier but steers submitters toward a $39 paid listing; deferred as a commercial decision. The Docker MCP Catalog is not a fit because it requires stdio transport and a Dockerfile, and Kilotest is streamable-http.
139
+ - **Other directories not yet targeted.** The Docker MCP Catalog is not a fit because it requires stdio transport and a Dockerfile, and Kilotest is streamable-http.
127
140
 
128
141
  - **Client-onboarding friction isn't documented.** Some MCP clients need the `mcp-remote` npx bridge to use a remote streamable-HTTP server like Kilotest's. Ready-made Claude Desktop/Cursor/Windsurf config instructions added to the AI tutorial would convert "discovered" into "used" faster, since case 3 of your use cases assumes a technically savvy discoverer but not all users are.
129
142
 
@@ -0,0 +1,32 @@
1
+ ---
2
+ date: 2026-11-04
3
+ status: accepted
4
+ ---
5
+
6
+ # Generating duplicated prose and schemas instead of hand-copying them
7
+
8
+ ## Context and problem
9
+
10
+ `mcp.ts`, `lhm.plugin.json`, `llms.txt`, and `scripts/generate-openapi.ts` each carried their own hand-typed copy of the same sentence describing what Kilotest is (“Kilotest integrates an ensemble of 12 rule engines…”). The four copies had already drifted: `llms.txt` had an accidentally duplicated clause and said “test job” where `lhm.plugin.json` said “test session”, and `generate-openapi.ts`’s `info.description` was a fourth, independently worded variant. Separately, `lhm.plugin.json` hand-transcribes every MCP tool’s `inputSchema`, `outputSchema`, `description`, and `annotations` from `mcp.ts`/`api/schemas.ts`, a step `docs/AI-TOOLS.md` already flagged as “hand-maintained, not generated…transform it…whenever the contract changes.” Regenerating `lhm.plugin.json` from the live registrations in `mcp.ts` found that this hand-transcription had already gone stale: several tool descriptions no longer matched `mcp.ts`, and `listDiagnoses`’s output schema was missing two fields (`identifier of the rule engine` and `how to report that you disagree with this diagnosis`) that the real tool returns.
11
+
12
+ ## Considered options
13
+
14
+ - Status quo: keep hand-copying prose and schemas into each surface, relying on the `docs/AI-TOOLS.md` “Contract changes” checklist to remind a maintainer to update every copy.
15
+ - Add a lint/test check that only flags byte-for-byte differences between the copies, without removing the duplication itself.
16
+ - Generate the duplicated surfaces from canonical sources, extending the pattern the project already uses for `package.json`’s `version` (copied into `server.json`/`lhm.plugin.json` by `scripts/update-versions.ts`) and for `api/schemas.ts`/`api/routes.ts` (rendered into `openapi.yaml`/`openapi-3.0.yaml` by `scripts/generate-openapi.ts`).
17
+
18
+ ## Decision
19
+
20
+ Generate, rather than hand-copy, wherever the same sentence or schema is supposed to appear verbatim in more than one file, extending the project's existing generate-from-canonical-source pattern:
21
+
22
+ - `content.ts` is the new single source of truth for prose that must be identical across surfaces (`collectionDescription`, the one-line tool-collection description; `sharedContext`, the domain/data-hierarchy paragraph). `mcp.ts` and `scripts/generate-openapi.ts` import it instead of defining their own copies.
23
+ - `scripts/generate-lhm.ts` regenerates `lhm.plugin.json`’s top-level `description` and every tool’s `description`, `inputSchema`, `outputSchema`, and `annotations` from `content.ts` and the same tool registrations `mcp.ts` creates at runtime (via `createMCPServer()`) and the same Zod schemas `api/schemas.ts` exports, using `z.toJSONSchema`. Fields with no canonical source elsewhere in the repository (`author`, `cloudEndpoint`, `homepage`, `identifier`, `name`, `prompts`, `resources`, `tags`, `version`, and each tool’s `execution` block) are left untouched by the generator.
24
+ - `scripts/generate-lhm.ts` is wired into `npm run generate`, alongside `generate-openapi.ts` and `update-versions.ts`, so `prePR.ts` regenerates it before every release the same way it already regenerates `openapi.yaml`.
25
+ - `llms.txt`’s opening paragraph is hand-corrected to match `content.ts`’s `sharedContext` verbatim; it is not generated, because the rest of the file (and `llms-full.txt`, `capability.md`, `README.md`, and the OpenAI-plugin `SKILL.md`) intentionally paraphrase for their audience rather than repeating the same sentence, so forcing byte-identical text there would reduce documentation quality for no benefit. These remain on the existing hand-maintained checklist in `docs/AI-TOOLS.md`.
26
+ - Publishing remains separate from generation, as it already was for `openapi.yaml`/`server.json`: `npm run generate` produces correct file content locally, and `npm run publish:lhm` still performs the manual, OAuth-gated push to LobeHub.
27
+
28
+ The byte-for-byte lint-check option was rejected as a primary strategy because it would still require a maintainer to hand-edit three or four files in sync on every contract change; it only catches drift after the fact rather than removing the duplication that causes it.
29
+
30
+ ### Confirmation
31
+
32
+ To confirm the correctness of the implementation, run `npm run generate`, verify `lhm.plugin.json`, `openapi.yaml`, and `openapi-3.0.yaml` are unchanged (a clean `git diff`), and run `npm test` and `npm run lint` to confirm nothing else regressed.
package/lhm.plugin.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "author": "Jonathan Robert Pool",
3
3
  "cloudEndpoint": "https://kilotest.com/mcp",
4
- "description": "Tools that test web pages for front-end quality (accessibility, usability, and web standards, including HTML, CSS, ARIA, and WCAG) and make test results available",
4
+ "description": "Tools that test web pages with an ensemble of 12 rule engines for front-end quality (accessibility, usability, and W3C web standards, including HTML, CSS, ARIA, and WCAG)",
5
5
  "homepage": "https://github.com/jrpool/kilotest",
6
6
  "identifier": "jrpool-kilotest",
7
7
  "name": "Kilotest",
@@ -21,7 +21,7 @@
21
21
  "tools": [
22
22
  {
23
23
  "name": "getKilotestOverview",
24
- "description": "Explains what Kilotest is, what its tools do, and how its results are structured. Use this before any other tool when the available tools or workflow are unclear. Returns a prose overview of the Kilotest domain, the tool workflow, and links to further documentation. Kilotest integrates an ensemble of 12 rule engines, with about 1,300 rules, to test web pages for front-end quality (accessibility, usability, and web standards, including HTML, CSS, ARIA, and WCAG). Each test session generates a standardized report describing which elements violated which rules and grouping the violations into about 380 issues. Kilotest can list the tested pages, the reports about each page, the issues in each report, the violators of each issue, and the diagnoses of each violation. Kilotest also accepts requests to test pages not yet tested and to retest already tested pages.",
24
+ "description": "Explains what Kilotest is, what its tools do, and how its results are structured. Use this before any other tool when the available tools or workflow are unclear. Returns a prose overview of the Kilotest domain, the tool workflow, and links to further documentation. Kilotest integrates an ensemble of 12 rule engines, with about 1,300 rules, to test web pages for front-end quality (accessibility, usability, and W3C web standards, including HTML, CSS, ARIA, and WCAG). Each test job generates a standardized report describing which elements violated which rules and grouping the violations into about 380 issues. Kilotest can list the tested pages, the reports about each page, the issues in each report, the violators of each issue, and the diagnoses of each violation. Kilotest also accepts requests to test pages not yet tested and to retest already tested pages.",
25
25
  "inputSchema": {
26
26
  "type": "object",
27
27
  "properties": {},
@@ -30,8 +30,8 @@
30
30
  "annotations": {
31
31
  "title": "Explain Kilotest and its tools",
32
32
  "readOnlyHint": true,
33
- "destructiveHint": false,
34
33
  "idempotentHint": true,
34
+ "destructiveHint": false,
35
35
  "openWorldHint": false
36
36
  },
37
37
  "execution": {
@@ -40,7 +40,7 @@
40
40
  },
41
41
  {
42
42
  "name": "listPages",
43
- "description": "Lists all pages Kilotest has tested. Use this first to discover whether a page already has a report before requesting a test. Returns, for each tested page, its description, URL, and the timestamp and job identifier of its latest report.",
43
+ "description": "Lists all pages Kilotest has tested. Use this first to discover whether a page already has any reports before requesting a test. Returns, for each tested page, its description, URL, and the identifier of its latest available report.",
44
44
  "inputSchema": {
45
45
  "type": "object",
46
46
  "properties": {},
@@ -382,8 +382,8 @@
382
382
  "annotations": {
383
383
  "title": "List tested pages",
384
384
  "readOnlyHint": true,
385
- "destructiveHint": false,
386
385
  "idempotentHint": true,
386
+ "destructiveHint": false,
387
387
  "openWorldHint": false
388
388
  },
389
389
  "execution": {
@@ -392,7 +392,7 @@
392
392
  },
393
393
  {
394
394
  "name": "listIssues",
395
- "description": "Lists the issues reported in one report, along with the history of all reports about the same page. Use this after listPages to drill into a specific report, identified by its timestamp and job identifier. Returns basics about the report’s issues and a list of every report about the same page, from oldest to latest.",
395
+ "description": "Lists the issues reported in one report, along with the history of all reports about the same page. Use this after listPages to switch to an earlier or later report or to begin examining this report. Returns the history and the basics about this report’s issues.",
396
396
  "inputSchema": {
397
397
  "type": "object",
398
398
  "properties": {
@@ -1124,8 +1124,8 @@
1124
1124
  "annotations": {
1125
1125
  "title": "List issues in a report",
1126
1126
  "readOnlyHint": true,
1127
- "destructiveHint": false,
1128
1127
  "idempotentHint": true,
1128
+ "destructiveHint": false,
1129
1129
  "openWorldHint": false
1130
1130
  },
1131
1131
  "execution": {
@@ -1134,7 +1134,7 @@
1134
1134
  },
1135
1135
  {
1136
1136
  "name": "listViolators",
1137
- "description": "Lists the elements of a tested page that were reported as exhibiting one issue in one report. Use this after listIssues to drill from an issue down to the elements it affects. Returns basics about each violating element, including its identifier in the page’s element catalog.",
1137
+ "description": "Lists the elements of a tested page that were reported as exhibiting one issue in one report. Use this after listIssues to drill down from an issue to the elements violating rules belonging to that issue. Returns basics about each violating element.",
1138
1138
  "inputSchema": {
1139
1139
  "type": "object",
1140
1140
  "properties": {
@@ -1633,8 +1633,8 @@
1633
1633
  "annotations": {
1634
1634
  "title": "List elements exhibiting an issue",
1635
1635
  "readOnlyHint": true,
1636
- "destructiveHint": false,
1637
1636
  "idempotentHint": true,
1637
+ "destructiveHint": false,
1638
1638
  "openWorldHint": false
1639
1639
  },
1640
1640
  "execution": {
@@ -1643,7 +1643,7 @@
1643
1643
  },
1644
1644
  {
1645
1645
  "name": "listDiagnoses",
1646
- "description": "Lists the diagnoses that rule engines gave for how one element exhibited one issue in one report. Use this after listViolators to drill from an element down to the rule-engine explanations of why it was flagged. Returns, for each rule engine that reported the issue on that element, its diagnosis.",
1646
+ "description": "Lists the diagnoses that rule engines gave for how one element exhibited one issue in one report. Use this after listViolators to get details about the element as a violator of the issue. Returns, for each rule engine that reported a violation by the element of a rule belonging to the issue, the diagnosis given by the rule engine.",
1647
1647
  "inputSchema": {
1648
1648
  "type": "object",
1649
1649
  "properties": {
@@ -1802,6 +1802,9 @@
1802
1802
  "items": {
1803
1803
  "type": "object",
1804
1804
  "properties": {
1805
+ "identifier of the rule engine": {
1806
+ "description": "Copied from the report act (which); type not guaranteed."
1807
+ },
1805
1808
  "identifier of the violated rule": {
1806
1809
  "description": "Copied from the report instance (ruleID); type not guaranteed."
1807
1810
  },
@@ -1813,13 +1816,53 @@
1813
1816
  },
1814
1817
  "count of violations of the rule by the element": {
1815
1818
  "type": "number"
1819
+ },
1820
+ "how to report that you disagree with this diagnosis": {
1821
+ "type": "object",
1822
+ "properties": {
1823
+ "method": {
1824
+ "type": "string",
1825
+ "const": "POST"
1826
+ },
1827
+ "URL": {
1828
+ "type": "string"
1829
+ },
1830
+ "request body": {
1831
+ "type": "object",
1832
+ "properties": {
1833
+ "engineID": {
1834
+ "type": "string"
1835
+ },
1836
+ "ruleID": {
1837
+ "type": "string"
1838
+ },
1839
+ "reason": {
1840
+ "type": "string"
1841
+ }
1842
+ },
1843
+ "required": [
1844
+ "engineID",
1845
+ "ruleID",
1846
+ "reason"
1847
+ ],
1848
+ "additionalProperties": false
1849
+ }
1850
+ },
1851
+ "required": [
1852
+ "method",
1853
+ "URL",
1854
+ "request body"
1855
+ ],
1856
+ "additionalProperties": false
1816
1857
  }
1817
1858
  },
1818
1859
  "required": [
1860
+ "identifier of the rule engine",
1819
1861
  "identifier of the violated rule",
1820
1862
  "description of the violation",
1821
1863
  "severity of the violation on a 0-to-3 scale",
1822
- "count of violations of the rule by the element"
1864
+ "count of violations of the rule by the element",
1865
+ "how to report that you disagree with this diagnosis"
1823
1866
  ],
1824
1867
  "additionalProperties": false
1825
1868
  }
@@ -2160,8 +2203,8 @@
2160
2203
  "annotations": {
2161
2204
  "title": "List diagnoses of a violation",
2162
2205
  "readOnlyHint": true,
2163
- "destructiveHint": false,
2164
2206
  "idempotentHint": true,
2207
+ "destructiveHint": false,
2165
2208
  "openWorldHint": false
2166
2209
  },
2167
2210
  "execution": {
@@ -2170,7 +2213,7 @@
2170
2213
  },
2171
2214
  {
2172
2215
  "name": "getReport",
2173
- "description": "Retrieves one entire report as JSON, with all of its issues, violators, and diagnoses. Use this instead of drilling down through listIssues, listViolators, and listDiagnoses when the full report is needed in one call. Returns the complete report data for the specified timestamp and job identifier.",
2216
+ "description": "Retrieves one entire report as JSON, with all of its issues, violators, and diagnoses. Use this instead of drilling down through listIssues, listViolators, and listDiagnoses when the full report is needed in one call. Returns the complete report data for the specified report identifier. The size of the report is disclosed by the listIssues tool.",
2174
2217
  "inputSchema": {
2175
2218
  "type": "object",
2176
2219
  "properties": {
@@ -2426,8 +2469,8 @@
2426
2469
  "annotations": {
2427
2470
  "title": "Get a full report",
2428
2471
  "readOnlyHint": true,
2429
- "destructiveHint": false,
2430
2472
  "idempotentHint": true,
2473
+ "destructiveHint": false,
2431
2474
  "openWorldHint": false
2432
2475
  },
2433
2476
  "execution": {
@@ -2436,7 +2479,7 @@
2436
2479
  },
2437
2480
  {
2438
2481
  "name": "requestNewTest",
2439
- "description": "Submits a request to test a page that has no report yet; queued for manual approval. Use orderNewTest instead for immediate, automatically approved testing. Returns a request confirmation with disposition details.",
2482
+ "description": "Submits a request to test a page that has no report yet; queued for manager approval. Use orderNewTest instead for immediate, automatically approved testing. Returns a request confirmation with disposition details.",
2440
2483
  "inputSchema": {
2441
2484
  "type": "object",
2442
2485
  "properties": {
@@ -2793,8 +2836,8 @@
2793
2836
  "annotations": {
2794
2837
  "title": "Request testing of a new page",
2795
2838
  "readOnlyHint": false,
2796
- "destructiveHint": false,
2797
2839
  "idempotentHint": false,
2840
+ "destructiveHint": false,
2798
2841
  "openWorldHint": true
2799
2842
  },
2800
2843
  "execution": {
@@ -2803,7 +2846,7 @@
2803
2846
  },
2804
2847
  {
2805
2848
  "name": "requestRetest",
2806
- "description": "Submits a request to retest a page whose existing report is obsolete; queued for manual approval. Use orderRetest instead for immediate, automatically approved retesting. Returns a request confirmation with disposition details.",
2849
+ "description": "Submits a request to retest a page whose existing report is considered obsolete; queued for manager approval. Use orderRetest instead for immediate, automatically approved retesting. Returns a request confirmation with disposition details.",
2807
2850
  "inputSchema": {
2808
2851
  "type": "object",
2809
2852
  "properties": {
@@ -3145,8 +3188,8 @@
3145
3188
  "annotations": {
3146
3189
  "title": "Request a retest",
3147
3190
  "readOnlyHint": false,
3148
- "destructiveHint": false,
3149
3191
  "idempotentHint": false,
3192
+ "destructiveHint": false,
3150
3193
  "openWorldHint": true
3151
3194
  },
3152
3195
  "execution": {
@@ -3155,7 +3198,7 @@
3155
3198
  },
3156
3199
  {
3157
3200
  "name": "requestFeature",
3158
- "description": "Submits a request to add a new feature or improve an existing one; queued for manual review. There is no immediate-approval alternative for feature requests. Returns a request confirmation with disposition details.",
3201
+ "description": "Submits a request to add a new feature or improve an existing one; queued for manager review. There is no immediate-approval alternative for feature requests. Returns a request confirmation with disposition details.",
3159
3202
  "inputSchema": {
3160
3203
  "type": "object",
3161
3204
  "properties": {
@@ -3427,8 +3470,8 @@
3427
3470
  "annotations": {
3428
3471
  "title": "Request a feature",
3429
3472
  "readOnlyHint": false,
3430
- "destructiveHint": false,
3431
3473
  "idempotentHint": false,
3474
+ "destructiveHint": false,
3432
3475
  "openWorldHint": false
3433
3476
  },
3434
3477
  "execution": {
@@ -3765,8 +3808,8 @@
3765
3808
  "annotations": {
3766
3809
  "title": "Report disagreement with a diagnosis",
3767
3810
  "readOnlyHint": false,
3768
- "destructiveHint": false,
3769
3811
  "idempotentHint": false,
3812
+ "destructiveHint": false,
3770
3813
  "openWorldHint": false
3771
3814
  },
3772
3815
  "execution": {
@@ -3775,7 +3818,7 @@
3775
3818
  },
3776
3819
  {
3777
3820
  "name": "orderNewTest",
3778
- "description": "Orders testing of a page that has no report yet, with automated approval or rejection and immediate enqueueing if approved. Use this instead of requestNewTest when immediate testing is wanted; testing typically takes 2 to 4 minutes. Returns an order confirmation and disposition details, including, if approved, a report identifier (timestamp and job identifier) and a question about whether to wait for completion with the awaitTest tool.",
3821
+ "description": "Orders testing of a page that has no report yet, with automated approval or rejection and immediate enqueueing if approved. Use this instead of requestNewTest when immediate testing is wanted; testing typically takes 2 to 4 minutes. Returns an order confirmation and disposition details, including, if approved, a report identifier and a question about whether to wait for completion with the awaitTest tool.",
3779
3822
  "inputSchema": {
3780
3823
  "type": "object",
3781
3824
  "properties": {
@@ -4140,8 +4183,8 @@
4140
4183
  "annotations": {
4141
4184
  "title": "Order immediate testing of a new page",
4142
4185
  "readOnlyHint": false,
4143
- "destructiveHint": false,
4144
4186
  "idempotentHint": false,
4187
+ "destructiveHint": false,
4145
4188
  "openWorldHint": true
4146
4189
  },
4147
4190
  "execution": {
@@ -4150,7 +4193,7 @@
4150
4193
  },
4151
4194
  {
4152
4195
  "name": "orderRetest",
4153
- "description": "Orders a retest of a page whose existing report is considered obsolete, with automated approval or rejection and immediate enqueuing if approved. Use this instead of requestRetest when immediate retesting is wanted; testing typically takes 2 to 4 minutes. Returns an order confirmation and disposition details, including, if approved, a report identifier (timestamp and job identifier) and a question about whether to wait for completion with the awaitTest tool.",
4196
+ "description": "Orders a retest of a page whose existing report is considered obsolete, with automated approval or rejection and immediate enqueuing if approved. Use this instead of requestRetest when immediate retesting is wanted; testing typically takes 2 to 4 minutes. Returns an order confirmation and disposition details, including, if approved, a report identifier and a question about whether to wait for completion with the awaitTest tool.",
4154
4197
  "inputSchema": {
4155
4198
  "type": "object",
4156
4199
  "properties": {
@@ -4500,8 +4543,8 @@
4500
4543
  "annotations": {
4501
4544
  "title": "Order an immediate retest",
4502
4545
  "readOnlyHint": false,
4503
- "destructiveHint": false,
4504
4546
  "idempotentHint": false,
4547
+ "destructiveHint": false,
4505
4548
  "openWorldHint": true
4506
4549
  },
4507
4550
  "execution": {
@@ -4809,8 +4852,8 @@
4809
4852
  "annotations": {
4810
4853
  "title": "Wait for an ordered job to finish",
4811
4854
  "readOnlyHint": true,
4812
- "destructiveHint": false,
4813
4855
  "idempotentHint": false,
4856
+ "destructiveHint": false,
4814
4857
  "openWorldHint": false
4815
4858
  },
4816
4859
  "execution": {
@@ -4818,5 +4861,5 @@
4818
4861
  }
4819
4862
  }
4820
4863
  ],
4821
- "version": "63.1.8"
4864
+ "version": "63.2.0"
4822
4865
  }
package/llms.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  # Kilotest
2
2
 
3
- Kilotest integrates an ensemble of 12 rule engines, with about 1,300 rules, to test web pages for front-end quality (accessibility, usability, and web standards, including HTML, CSS, ARIA, and WCAG, including HTML, CSS, ARIA, and WCAG). Each test job generates a standardized report describing which elements violated which rules and grouping the violations into about 380 issues. Kilotest can list the tested pages, the reports about each page, the issues in each report, the violators of each issue, and the diagnoses of each violation. Kilotest also accepts requests to test pages not yet tested and to retest already tested pages. Kilotest features are available via a web UI, an API, and an MCP server with streamable HTTP and no authentication.
3
+ Kilotest integrates an ensemble of 12 rule engines, with about 1,300 rules, to test web pages for front-end quality (accessibility, usability, and web standards, including HTML, CSS, ARIA, and WCAG). Each test job generates a standardized report describing which elements violated which rules and grouping the violations into about 380 issues. Kilotest can list the tested pages, the reports about each page, the issues in each report, the violators of each issue, and the diagnoses of each violation. Kilotest also accepts requests to test pages not yet tested and to retest already tested pages. Kilotest features are available via a web UI, an API, and an MCP server with streamable HTTP and no authentication.
4
4
 
5
5
  ## API
6
6
 
package/mcp.ts CHANGED
@@ -22,6 +22,7 @@ import * as awaitTestAPI from './api/awaitTest.ts';
22
22
  import * as reportDisagreementAPI from './api/reportDisagreement.ts';
23
23
  import {version} from './api/version.ts';
24
24
  import {recordMetric} from './util.ts';
25
+ import {collectionDescription, sharedContext} from './content.ts';
25
26
 
26
27
  import {
27
28
  getReportSchema,
@@ -53,12 +54,6 @@ import {
53
54
 
54
55
  export const mcpPath = '/mcp';
55
56
 
56
- // Shared statement of the Kilotest domain and data hierarchy, repeated in the server
57
- // instructions and in the getKilotestOverview tool description (but not in the other tool
58
- // descriptions, to avoid diluting how models read each tool), because some MCP clients do
59
- // not propagate server-level instructions or the server description to their models.
60
- const sharedContext = 'Kilotest integrates an ensemble of 12 rule engines, with about 1,300 rules, to test web pages for front-end quality (accessibility, usability, and web standards, including HTML, CSS, ARIA, and WCAG). Each test job generates a standardized report describing which elements violated which rules and grouping the violations into about 380 issues. Kilotest can list the tested pages, the reports about each page, the issues in each report, the violators of each issue, and the diagnoses of each violation. Kilotest also accepts requests to test pages not yet tested and to retest already tested pages.';
61
-
62
57
  // Detailed overview of the Kilotest domain, data hierarchy, and tool workflow, returned by
63
58
  // the getKilotestOverview tool and served as the docs://kilotest/overview resource.
64
59
  const kilotestOverview = [
@@ -77,7 +72,7 @@ export const createMCPServer = (): McpServer => {
77
72
  const server = new McpServer({
78
73
  name: 'Kilotest',
79
74
  version,
80
- description: 'Tools that test web pages for front-end quality (accessibility, usability, and web standards, including HTML, CSS, ARIA, and WCAG) and make test results available'
75
+ description: collectionDescription
81
76
  },
82
77
  {
83
78
  instructions: `${sharedContext} Use the listPages tool to start. If it shows that the page you want facts about has been tested, drill down from its latest report with the listIssues, listViolators, and listDiagnoses tools. If not, use the requestNewTest tool to request that the page be tested, or the orderNewTest tool for immediate, automatically approved testing. If the latest report about the page is obsolete, use the requestRetest tool to request that the page be retested, or the orderRetest tool for immediate, automatically approved retesting. For detailed documentation, call the getKilotestOverview tool.`
package/openapi-3.0.yaml CHANGED
@@ -1,8 +1,8 @@
1
1
  openapi: 3.0.3
2
2
  info:
3
3
  title: Kilotest API
4
- description: 'Kilotest is an ensemble testing and reporting service for front-end web quality. It uses an ensemble of 12 rule engines to test web pages for front-end quality (accessibility, usability, and web standards, including HTML, CSS, ARIA, and WCAG). Kilotest acts as a web server for human users, an MCP server for AI platforms, and an API for programmatic access. With this API you can request that a page be tested or retested, discover available reports, incrementally retrieve facts from a report: from what reports are available, to what issues were reported in one report, to what elements violated one issue, to what diagnoses rule engines gave for one violation.'
5
- version: 63.1.8
4
+ description: 'Kilotest integrates an ensemble of 12 rule engines, with about 1,300 rules, to test web pages for front-end quality (accessibility, usability, and W3C web standards, including HTML, CSS, ARIA, and WCAG). Each test job generates a standardized report describing which elements violated which rules and grouping the violations into about 380 issues. Kilotest can list the tested pages, the reports about each page, the issues in each report, the violators of each issue, and the diagnoses of each violation. Kilotest also accepts requests to test pages not yet tested and to retest already tested pages. Kilotest acts as a web server for human users, an MCP server for AI platforms, and an API for programmatic access. With this API you can request that a page be tested or retested, discover available reports, and incrementally retrieve facts from a report: from what reports are available, to what issues were reported in one report, to what elements violated one issue, to what diagnoses rule engines gave for one violation.'
5
+ version: 63.2.0
6
6
  license:
7
7
  name: MIT
8
8
  url: https://github.com/jrpool/kilotest/blob/main/LICENSE
package/openapi.yaml CHANGED
@@ -1,8 +1,8 @@
1
1
  openapi: 3.1.0
2
2
  info:
3
3
  title: Kilotest API
4
- description: 'Kilotest is an ensemble testing and reporting service for front-end web quality. It uses an ensemble of 12 rule engines to test web pages for front-end quality (accessibility, usability, and web standards, including HTML, CSS, ARIA, and WCAG). Kilotest acts as a web server for human users, an MCP server for AI platforms, and an API for programmatic access. With this API you can request that a page be tested or retested, discover available reports, incrementally retrieve facts from a report: from what reports are available, to what issues were reported in one report, to what elements violated one issue, to what diagnoses rule engines gave for one violation.'
5
- version: 63.1.8
4
+ description: 'Kilotest integrates an ensemble of 12 rule engines, with about 1,300 rules, to test web pages for front-end quality (accessibility, usability, and W3C web standards, including HTML, CSS, ARIA, and WCAG). Each test job generates a standardized report describing which elements violated which rules and grouping the violations into about 380 issues. Kilotest can list the tested pages, the reports about each page, the issues in each report, the violators of each issue, and the diagnoses of each violation. Kilotest also accepts requests to test pages not yet tested and to retest already tested pages. Kilotest acts as a web server for human users, an MCP server for AI platforms, and an API for programmatic access. With this API you can request that a page be tested or retested, discover available reports, and incrementally retrieve facts from a report: from what reports are available, to what issues were reported in one report, to what elements violated one issue, to what diagnoses rule engines gave for one violation.'
5
+ version: 63.2.0
6
6
  license:
7
7
  name: MIT
8
8
  identifier: MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jrpool/kilotest",
3
- "version": "63.1.8",
3
+ "version": "63.2.0",
4
4
  "description": "Ensemble testing for front-end web quality",
5
5
  "type": "module",
6
6
  "main": "index.ts",
@@ -14,7 +14,7 @@
14
14
  "test": "c8 node --require ./test/setup.ts --experimental-test-module-mocks --test --test-concurrency=1 --test-randomize '**/*.test.ts'",
15
15
  "test:smoke": "node smokeTest.ts",
16
16
  "checkinfo": "node checkinfo.ts",
17
- "generate": "node scripts/generate-openapi.ts && node scripts/update-versions.ts",
17
+ "generate": "node scripts/generate-openapi.ts && node scripts/generate-lhm.ts && node scripts/update-versions.ts",
18
18
  "build:media": "node scripts/buildDemoVideo.ts",
19
19
  "publish:lhm": "npx -y @lobehub/market-cli plugin update --dir .",
20
20
  "prePR": "node scripts/prePR.ts",
@@ -0,0 +1,118 @@
1
+ /*
2
+ generate-lhm.ts
3
+ Regenerates the description and tool entries (description, inputSchema, outputSchema,
4
+ annotations) of lhm.plugin.json from the single sources of truth: content.ts for prose and
5
+ api/schemas.ts (via the same registrations mcp.ts makes) for schemas. Everything else in
6
+ lhm.plugin.json (author, cloudEndpoint, homepage, identifier, name, prompts, resources,
7
+ tags, version, and each tool's execution block) is left untouched, because it has no
8
+ canonical source elsewhere in the repository; version is kept in sync separately by
9
+ update-versions.ts.
10
+ */
11
+
12
+ // IMPORTS
13
+
14
+ import fs from 'node:fs';
15
+ import path from 'node:path';
16
+ import {z} from 'zod';
17
+ import {createMCPServer} from '../mcp.ts';
18
+ import {collectionDescription} from '../content.ts';
19
+ import {
20
+ listIssuesSchema,
21
+ listViolatorsSchema,
22
+ listDiagnosesSchema,
23
+ getReportSchema,
24
+ requestNewTestSchema,
25
+ requestRetestSchema,
26
+ requestFeatureSchema,
27
+ orderNewTestSchema,
28
+ orderRetestSchema,
29
+ awaitTestSchema,
30
+ reportDisagreementSchema,
31
+ listPagesResponseSchema,
32
+ listIssuesResponseSchema,
33
+ listViolatorsResponseSchema,
34
+ listDiagnosesResponseSchema,
35
+ getReportResponseSchema,
36
+ requestNewTestResponseSchema,
37
+ requestRetestResponseSchema,
38
+ requestFeatureResponseSchema,
39
+ orderNewTestResponseSchema,
40
+ orderRetestResponseSchema,
41
+ awaitTestResponseSchema,
42
+ reportDisagreementResponseSchema
43
+ } from '../api/schemas.ts';
44
+
45
+ // TYPES
46
+
47
+ type ZodRawShape = Record<string, z.ZodTypeAny>;
48
+
49
+ // CONSTANTS
50
+
51
+ const outputPath = path.join(import.meta.dirname, '..', 'lhm.plugin.json');
52
+ const jsonSchemaOptions = {target: 'draft-7' as const, unrepresentable: 'any' as const};
53
+
54
+ // The input and output schemas mcp.ts passes to registerTool for each tool, keyed by tool
55
+ // name, matching mcp.ts's own registerTool calls exactly (mcp.ts remains the source for
56
+ // descriptions and annotations, read at runtime below via createMCPServer).
57
+ const toolSchemas: Record<string, {input: ZodRawShape; output?: z.ZodTypeAny}> = {
58
+ getKilotestOverview: {input: {}},
59
+ listPages: {input: {}, output: listPagesResponseSchema},
60
+ listIssues: {input: listIssuesSchema, output: listIssuesResponseSchema},
61
+ listViolators: {input: listViolatorsSchema, output: listViolatorsResponseSchema},
62
+ listDiagnoses: {input: listDiagnosesSchema, output: listDiagnosesResponseSchema},
63
+ getReport: {input: getReportSchema, output: getReportResponseSchema},
64
+ requestNewTest: {input: requestNewTestSchema, output: requestNewTestResponseSchema},
65
+ requestRetest: {input: requestRetestSchema, output: requestRetestResponseSchema},
66
+ requestFeature: {input: requestFeatureSchema, output: requestFeatureResponseSchema},
67
+ reportDisagreement: {input: reportDisagreementSchema, output: reportDisagreementResponseSchema},
68
+ orderNewTest: {input: orderNewTestSchema, output: orderNewTestResponseSchema},
69
+ orderRetest: {input: orderRetestSchema, output: orderRetestResponseSchema},
70
+ awaitTest: {input: awaitTestSchema, output: awaitTestResponseSchema}
71
+ };
72
+
73
+ // FUNCTIONS
74
+
75
+ // Builds the lhm.plugin.json inputSchema for a tool: like outputSchema below, but LobeHub's
76
+ // existing input schemas never carry additionalProperties, so that key is dropped.
77
+ const buildInputSchema = (shape: ZodRawShape): Record<string, unknown> => {
78
+ const generated = z.toJSONSchema(z.object(shape), jsonSchemaOptions) as Record<string, unknown>;
79
+ delete generated.additionalProperties;
80
+ const {$schema, ...withoutSchemaKeyword} = generated;
81
+ return {...withoutSchemaKeyword, $schema};
82
+ };
83
+ // Builds the lhm.plugin.json outputSchema for a tool, reordering keys to match the file's
84
+ // existing convention ($schema placed just before additionalProperties and definitions).
85
+ const buildOutputSchema = (schema: z.ZodTypeAny): Record<string, unknown> => {
86
+ const generated = z.toJSONSchema(schema, jsonSchemaOptions) as Record<string, unknown>;
87
+ const {$schema, additionalProperties, definitions, ...rest} = generated;
88
+ return {
89
+ ...rest,
90
+ ...($schema !== undefined ? {$schema} : {}),
91
+ ...(additionalProperties !== undefined ? {additionalProperties} : {}),
92
+ ...(definitions !== undefined ? {definitions} : {})
93
+ };
94
+ };
95
+
96
+ // EXECUTION
97
+
98
+ const server = createMCPServer();
99
+ const registeredTools = (server as unknown as {_registeredTools: Record<string, {description: string; annotations: unknown}>})._registeredTools;
100
+ const manifest = JSON.parse(fs.readFileSync(outputPath, 'utf8'));
101
+ manifest.description = collectionDescription;
102
+ manifest.tools = manifest.tools.map((tool: {name: string; execution: unknown}) => {
103
+ const registered = registeredTools[tool.name];
104
+ const schemas = toolSchemas[tool.name];
105
+ if (!registered || !schemas) {
106
+ throw new Error(`No registration or schema mapping found for lhm.plugin.json tool "${tool.name}"`);
107
+ }
108
+ return {
109
+ name: tool.name,
110
+ description: registered.description,
111
+ inputSchema: buildInputSchema(schemas.input),
112
+ ...(schemas.output ? {outputSchema: buildOutputSchema(schemas.output)} : {}),
113
+ annotations: registered.annotations,
114
+ execution: tool.execution
115
+ };
116
+ });
117
+ fs.writeFileSync(outputPath, `${JSON.stringify(manifest, null, 2)}\n`);
118
+ console.log(`Wrote ${outputPath}`);
@@ -12,6 +12,7 @@ import {createDocument} from 'zod-openapi';
12
12
  import pkg from '../package.json' with {type: 'json'};
13
13
  import {routes} from '../api/routes.ts';
14
14
  import {version} from '../api/version.ts';
15
+ import {sharedContext} from '../content.ts';
15
16
 
16
17
  // CONSTANTS
17
18
 
@@ -137,7 +138,7 @@ const generate = () => {
137
138
  openapi: '3.1.0',
138
139
  info: {
139
140
  title: 'Kilotest API',
140
- description: 'Kilotest is an ensemble testing and reporting service for front-end web quality. It uses an ensemble of 12 rule engines to test web pages for front-end quality (accessibility, usability, and web standards, including HTML, CSS, ARIA, and WCAG). Kilotest acts as a web server for human users, an MCP server for AI platforms, and an API for programmatic access. With this API you can request that a page be tested or retested, discover available reports, incrementally retrieve facts from a report: from what reports are available, to what issues were reported in one report, to what elements violated one issue, to what diagnoses rule engines gave for one violation.',
141
+ description: `${sharedContext} Kilotest acts as a web server for human users, an MCP server for AI platforms, and an API for programmatic access. With this API you can request that a page be tested or retested, discover available reports, and incrementally retrieve facts from a report: from what reports are available, to what issues were reported in one report, to what elements violated one issue, to what diagnoses rule engines gave for one violation.`,
141
142
  version,
142
143
  license: {
143
144
  name: license,
package/server.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "url": "https://github.com/jrpool/kilotest",
7
7
  "source": "github"
8
8
  },
9
- "version": "63.1.8",
9
+ "version": "63.2.0",
10
10
  "remotes": [
11
11
  {
12
12
  "type": "streamable-http",