@jrpool/kilotest 37.0.0 → 38.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/alerts.js +1 -1
- package/diagnoses/index.js +39 -5
- package/eslint.config.mjs +4 -1
- package/index.js +27 -26
- package/issues/index.js +31 -10
- package/llms-full.txt +15 -15
- package/llms.txt +2 -2
- package/mcp.js +24 -18
- package/openapi.yaml +125 -70
- package/package.json +1 -1
- package/reannotate/index.js +10 -2
- package/reannotateForm/index.js +19 -4
- package/reportHideForm/index.js +20 -1
- package/reportIssue/api.js +28 -9
- package/reportIssue/index.js +39 -7
- package/reportIssues/index.js +26 -14
- package/reportIssues/util.js +27 -8
- package/reportList/api.js +36 -28
- package/reportSummary/api.js +181 -0
- package/reportUnhideForm/index.js +20 -2
- package/reportsExpungeForm/index.js +9 -1
- package/reportsPruneForm/index.js +11 -1
- package/reportsRewindForm/index.js +9 -1
- package/researchAgent.js +38 -29
- package/retestRec/index.js +11 -2
- package/retestRecForm/index.js +5 -0
- package/target/api.js +46 -35
- package/targets/index.js +20 -1
- package/testRecForm/api.js +2 -2
- package/tutorial/index.js +15 -5
- package/util.js +63 -48
- package/reportIssues/api.js +0 -129
package/alerts.js
CHANGED
|
@@ -19,7 +19,7 @@ const ALERT_FROM = process.env.ALERT_FROM;
|
|
|
19
19
|
// FUNCTIONS
|
|
20
20
|
|
|
21
21
|
// Sends an email alert to a manager.
|
|
22
|
-
exports.sendAlert = (subject, body) => new Promise(
|
|
22
|
+
exports.sendAlert = (subject, body) => new Promise(resolve => {
|
|
23
23
|
// If the alert configuration is complete:
|
|
24
24
|
if (MANAGER_EMAIL && ALERT_API_HOST && ALERT_API_PATH && ALERT_API_KEY && ALERT_FROM) {
|
|
25
25
|
const payload = JSON.stringify({
|
package/diagnoses/index.js
CHANGED
|
@@ -23,10 +23,27 @@ const path = require('path');
|
|
|
23
23
|
|
|
24
24
|
// Adds parameters to a query for the answer page.
|
|
25
25
|
const populateQuery = async (issueID, timeStamp, jobID, catalogIndex, pathID, query) => {
|
|
26
|
+
// Get descriptions of the page facts.
|
|
26
27
|
const pageDataStrings = await getPageDataStrings(timeStamp, jobID);
|
|
27
|
-
|
|
28
|
+
// If this failed:
|
|
29
|
+
if (pageDataStrings.error) {
|
|
30
|
+
// Populate the query with the reason.
|
|
31
|
+
query.error = pageDataStrings.error;
|
|
32
|
+
// Stop populating the query.
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const {testInfo, url, urlLink, what} = pageDataStrings;
|
|
36
|
+
// Otherwise, i.e. if it succeeded, get the report.
|
|
28
37
|
const report = await getReport(timeStamp, jobID);
|
|
29
38
|
const {acts, catalog} = report;
|
|
39
|
+
// If this failed:
|
|
40
|
+
if (report.error) {
|
|
41
|
+
// Populate the query with the reason.
|
|
42
|
+
query.error = report.error;
|
|
43
|
+
// Stop populating the query.
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
// Otherwise, i.e. if it succeeded, get the catalog item of the specified violator.
|
|
30
47
|
const catalogItem = catalog[catalogIndex] ?? {};
|
|
31
48
|
const {boxID, startTag, tagName, text} = catalogItem;
|
|
32
49
|
query.catalogIndex = catalogIndex;
|
|
@@ -45,7 +62,15 @@ const populateQuery = async (issueID, timeStamp, jobID, catalogIndex, pathID, qu
|
|
|
45
62
|
query.target = what;
|
|
46
63
|
query.urlLink = urlLink;
|
|
47
64
|
query.testInfo = testInfo;
|
|
48
|
-
query.issue = issues[issueID]
|
|
65
|
+
query.issue = issues[issueID]?.summary;
|
|
66
|
+
// If adding the issue summary failed:
|
|
67
|
+
if (! query.issue) {
|
|
68
|
+
// Populate the query with the reason.
|
|
69
|
+
query.error = 'Issue not found';
|
|
70
|
+
// Stop populating the query.
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
// Otherwise, i.e. if it succeeded, get the issue details.
|
|
49
74
|
const issue = issues[issueID];
|
|
50
75
|
const {wcag, weight, why} = issue;
|
|
51
76
|
query.why = why;
|
|
@@ -118,7 +143,15 @@ exports.answer = async (pageArgs, search) => {
|
|
|
118
143
|
const query = {};
|
|
119
144
|
// Create a query to replace the placeholders.
|
|
120
145
|
await populateQuery(issueID, timeStamp, jobID, catalogIndex, pathID, query);
|
|
121
|
-
// If
|
|
146
|
+
// If this failed:
|
|
147
|
+
if (query.error) {
|
|
148
|
+
// Return why.
|
|
149
|
+
return {
|
|
150
|
+
status: 'error',
|
|
151
|
+
message: query.error
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
// Otherwise, if it succeeded and the report facts were obtained:
|
|
122
155
|
if (query.testInfo) {
|
|
123
156
|
// Get the template.
|
|
124
157
|
let answerPage = await fs.readFile(path.join(__dirname, 'index.html'), 'utf8');
|
|
@@ -132,9 +165,10 @@ exports.answer = async (pageArgs, search) => {
|
|
|
132
165
|
answerPage
|
|
133
166
|
};
|
|
134
167
|
}
|
|
135
|
-
// Otherwise, report
|
|
168
|
+
// Otherwise, i.e. if the report facts were not obtained:
|
|
169
|
+
// Report this.
|
|
136
170
|
return {
|
|
137
171
|
status: 'error',
|
|
138
|
-
message: '
|
|
172
|
+
message: 'Report facts not obtained'
|
|
139
173
|
};
|
|
140
174
|
};
|
package/eslint.config.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import globals from "globals";
|
|
|
3
3
|
import json from "@eslint/json";
|
|
4
4
|
import markdown from "@eslint/markdown";
|
|
5
5
|
import css from "@eslint/css";
|
|
6
|
-
import {
|
|
6
|
+
import {defineConfig} from "eslint/config";
|
|
7
7
|
|
|
8
8
|
export default defineConfig([
|
|
9
9
|
{
|
|
@@ -18,6 +18,9 @@ export default defineConfig([
|
|
|
18
18
|
extends: ["js/recommended"],
|
|
19
19
|
languageOptions: {
|
|
20
20
|
globals: globals.node
|
|
21
|
+
},
|
|
22
|
+
rules: {
|
|
23
|
+
"no-control-regex": "off"
|
|
21
24
|
}
|
|
22
25
|
},
|
|
23
26
|
{
|
package/index.js
CHANGED
|
@@ -30,6 +30,7 @@ const {
|
|
|
30
30
|
} = require('./util');
|
|
31
31
|
const {handleMCP, mcpPath} = require('./mcp');
|
|
32
32
|
const fs = require('fs/promises');
|
|
33
|
+
const {handleComment} = require('./tutorial/index');
|
|
33
34
|
const http = require('http');
|
|
34
35
|
const https = require('https');
|
|
35
36
|
const path = require('path');
|
|
@@ -273,26 +274,30 @@ const requestHandler = async (request, response) => {
|
|
|
273
274
|
if (isTimeStamp(timeStamp) && isJobID(jobID)) {
|
|
274
275
|
const reportHidden = await isHidden(timeStamp, jobID);
|
|
275
276
|
// If the report exists and is hidden:
|
|
276
|
-
if (reportHidden
|
|
277
|
-
console.
|
|
277
|
+
if (reportHidden) {
|
|
278
|
+
console.error(`Hidden report ${timeStamp}-${jobID} requested`);
|
|
278
279
|
// Report this as suspected abuse.
|
|
279
280
|
await serveError(
|
|
280
|
-
getAbuseError(request, `
|
|
281
|
+
getAbuseError(request, `Hidden report ${timeStamp}-${jobID} requested`),
|
|
281
282
|
response,
|
|
282
283
|
true
|
|
283
284
|
);
|
|
284
285
|
}
|
|
285
|
-
// Otherwise, if the report
|
|
286
|
-
else if (typeof reportHidden === 'string') {
|
|
287
|
-
// Report this as suspected abuse.
|
|
288
|
-
await serveError(getAbuseError(request, reportHidden), response, true);
|
|
289
|
-
}
|
|
290
|
-
// Otherwise, i.e. if the report log is valid and not hidden:
|
|
286
|
+
// Otherwise, i.e. if the report is not hidden:
|
|
291
287
|
else {
|
|
292
|
-
// Get
|
|
288
|
+
// Get it.
|
|
293
289
|
const report = await getReport(timeStamp, jobID);
|
|
294
|
-
// If
|
|
295
|
-
if (
|
|
290
|
+
// If this failed:
|
|
291
|
+
if (report.error) {
|
|
292
|
+
// Report this as suspected abuse.
|
|
293
|
+
await serveError(
|
|
294
|
+
getAbuseError(request, `Nonexistent report ${timeStamp}-${jobID} requested`),
|
|
295
|
+
response,
|
|
296
|
+
true
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
// Otherwise, i.e. if it succeeded:
|
|
300
|
+
else {
|
|
296
301
|
// Serve response headers for a JSON download.
|
|
297
302
|
setHeaders('application/json', null, 'low');
|
|
298
303
|
response.setHeader(
|
|
@@ -301,11 +306,6 @@ const requestHandler = async (request, response) => {
|
|
|
301
306
|
// Download the report.
|
|
302
307
|
response.end(getJSON(report));
|
|
303
308
|
}
|
|
304
|
-
// Otherwise, i.e. if an error occurred:
|
|
305
|
-
else {
|
|
306
|
-
// Report it.
|
|
307
|
-
await serveError({message: report}, response, true);
|
|
308
|
-
}
|
|
309
309
|
}
|
|
310
310
|
}
|
|
311
311
|
// Otherwise, i.e. if the request is syntactically invalid:
|
|
@@ -350,10 +350,10 @@ const requestHandler = async (request, response) => {
|
|
|
350
350
|
setHeaders('application/json', null, 'ultra');
|
|
351
351
|
response.end(JSON.stringify(responseData));
|
|
352
352
|
}
|
|
353
|
-
// Otherwise, if the service is provision of
|
|
354
|
-
else if (service === '
|
|
353
|
+
// Otherwise, if the service is provision of a report summary:
|
|
354
|
+
else if (service === 'reportSummary') {
|
|
355
355
|
// Get the response (potentially error) data.
|
|
356
|
-
const responseData = await require(path.join(__dirname, '
|
|
356
|
+
const responseData = await require(path.join(__dirname, 'reportSummary', 'api'))
|
|
357
357
|
.response(specs);
|
|
358
358
|
// Send them.
|
|
359
359
|
setHeaders('application/json', null, 'high');
|
|
@@ -701,12 +701,12 @@ const requestHandler = async (request, response) => {
|
|
|
701
701
|
}
|
|
702
702
|
// Otherwise, if the first segment is the report finding service:
|
|
703
703
|
else if (segments[0] === 'target') {
|
|
704
|
-
const {
|
|
705
|
-
// If the payload contains a description
|
|
706
|
-
if (
|
|
704
|
+
const {description = '', hostname = ''} = postData;
|
|
705
|
+
// If the payload contains a description or hostname:
|
|
706
|
+
if (description || hostname) {
|
|
707
707
|
// Process the request and get the response data.
|
|
708
708
|
const responseData = await require(path.join(__dirname, 'target', 'api'))
|
|
709
|
-
.response([
|
|
709
|
+
.response([description, hostname]);
|
|
710
710
|
// Send them.
|
|
711
711
|
setHeaders('application/json', null, 'ultra');
|
|
712
712
|
response.end(JSON.stringify(responseData));
|
|
@@ -731,7 +731,8 @@ const requestHandler = async (request, response) => {
|
|
|
731
731
|
if (await isReportAvailable(what, url)) {
|
|
732
732
|
// Report this.
|
|
733
733
|
responseData = {
|
|
734
|
-
|
|
734
|
+
status: 'warning',
|
|
735
|
+
message: 'A report on the page is already available'
|
|
735
736
|
}
|
|
736
737
|
}
|
|
737
738
|
// Otherwise, i.e. if no report on the page is available:
|
|
@@ -762,7 +763,7 @@ const requestHandler = async (request, response) => {
|
|
|
762
763
|
else if (pageName === 'tutorialComment.html') {
|
|
763
764
|
const {content} = postData;
|
|
764
765
|
setHeaders('application/json', null, 'low');
|
|
765
|
-
const answerData = await
|
|
766
|
+
const answerData = await handleComment(content);
|
|
766
767
|
if (answerData.status === 'ok') {
|
|
767
768
|
response.end(JSON.stringify({status: 'ok'}));
|
|
768
769
|
}
|
package/issues/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/*
|
|
2
2
|
index.js
|
|
3
|
-
Answers the
|
|
3
|
+
Answers the issues question.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
// IMPORTS
|
|
@@ -24,6 +24,8 @@ const path = require('path');
|
|
|
24
24
|
|
|
25
25
|
// Gets summary data on the issues reported in a set of reports.
|
|
26
26
|
const getIssuesSummary = async logs => {
|
|
27
|
+
// Initialize the summary.
|
|
28
|
+
const summary = {};
|
|
27
29
|
// Initialize data for a summary.
|
|
28
30
|
const issuesData = {};
|
|
29
31
|
// For each log of a report to be inspected:
|
|
@@ -37,8 +39,14 @@ const getIssuesSummary = async logs => {
|
|
|
37
39
|
}
|
|
38
40
|
// Get the corresponding report.
|
|
39
41
|
const report = await getReport(timeStamp, jobID);
|
|
40
|
-
|
|
41
|
-
|
|
42
|
+
const {acts = [], error} = report;
|
|
43
|
+
// If this failed:
|
|
44
|
+
if (error) {
|
|
45
|
+
// Populate the summary with the reason.
|
|
46
|
+
summary.error = error;
|
|
47
|
+
}
|
|
48
|
+
// For each act in it (none if the report retrieval failed):
|
|
49
|
+
acts.forEach(act => {
|
|
42
50
|
// If it is a test act:
|
|
43
51
|
if (act.type === 'test') {
|
|
44
52
|
const {result, which} = act;
|
|
@@ -60,11 +68,9 @@ const getIssuesSummary = async logs => {
|
|
|
60
68
|
}
|
|
61
69
|
});
|
|
62
70
|
}
|
|
63
|
-
// Initialize the summary.
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
issues: []
|
|
67
|
-
};
|
|
71
|
+
// Initialize the summary properties.
|
|
72
|
+
summary.totalCount = 0;
|
|
73
|
+
summary.issues = [];
|
|
68
74
|
// For each issue:
|
|
69
75
|
Object.entries(issuesData).forEach(([issueID, data]) => {
|
|
70
76
|
const {count, reporters} = data;
|
|
@@ -107,7 +113,14 @@ const populateQuery = async query => {
|
|
|
107
113
|
const targetLogs = (await getLogs()).filter(log => ! log.superseded);
|
|
108
114
|
// Get summary data on the issues.
|
|
109
115
|
const issuesSummary = await getIssuesSummary(targetLogs);
|
|
110
|
-
//
|
|
116
|
+
// If this failed:
|
|
117
|
+
if (issuesSummary.error) {
|
|
118
|
+
// Populate the query with the reason.
|
|
119
|
+
query.error = issuesSummary.error;
|
|
120
|
+
// Stop populating the query.
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
// Otherwise, i.e. if it succeeded, initialize the lines.
|
|
111
124
|
const lines = [];
|
|
112
125
|
const margin = ' '.repeat(6);
|
|
113
126
|
// For each weight:
|
|
@@ -159,7 +172,15 @@ exports.answer = async () => {
|
|
|
159
172
|
const query = {};
|
|
160
173
|
// Create a query to replace placeholders.
|
|
161
174
|
await populateQuery(query);
|
|
162
|
-
//
|
|
175
|
+
// If the query reports an error:
|
|
176
|
+
if (query.error) {
|
|
177
|
+
// Return why.
|
|
178
|
+
return {
|
|
179
|
+
status: 'error',
|
|
180
|
+
message: query.error
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
// Otherwise, i.e. if the query does not report an error, get the template.
|
|
163
184
|
let answerPage = await fs.readFile(path.join(__dirname, 'index.html'), 'utf8');
|
|
164
185
|
// Replace its placeholders.
|
|
165
186
|
Object.keys(query).forEach(param => {
|
package/llms-full.txt
CHANGED
|
@@ -63,7 +63,7 @@ Kilotest offers a comprehensive suite of capabilities to users via its web UI:
|
|
|
63
63
|
- Get statistics about issues reported in one report: `https://kilotest.com/reportIssues.html/{timeStamp}/{jobID}`
|
|
64
64
|
- Get details about one issue reported in one report: `https://kilotest.com/reportIssue.html/{issueID}/{timeStamp}/{jobID}`
|
|
65
65
|
- Get diagnoses by rule engines of rule violations for one HTML element exhibiting one issue in one report: `https://kilotest.com/diagnoses.html/{issueID}/{timeStamp}/{jobID}/{catalogIndex}`
|
|
66
|
-
- [Recommend the testing of a not-yet-tested page](
|
|
66
|
+
- [Recommend the testing of a not-yet-tested page](https://kilotest.com/testRecForm.html)
|
|
67
67
|
- Recommend the retesting of a previously tested page: `https://kilotest.com/retestRecForm.html/{timeStamp}/{jobID}`
|
|
68
68
|
- [Get statistics about frequently reported issues across all pages](https://kilotest.com/issues.html)
|
|
69
69
|
- [Get a list of the operations that Kilotest managers are authorized to request](https://kilotest.com/manage.html)
|
|
@@ -73,28 +73,28 @@ Kilotest offers a comprehensive suite of capabilities to users via its web UI:
|
|
|
73
73
|
|
|
74
74
|
Kilotest is implementing a richer suite of capabilities optimized for language models. In the specifications below, the term `report` refers to the records of one session in which a web page is tested.
|
|
75
75
|
|
|
76
|
-
The implementation is currently in an alpha phase and offers
|
|
76
|
+
The implementation is currently in an alpha phase and offers these tools:
|
|
77
77
|
|
|
78
78
|
- `summarizeQualityOfMatchingWebPages`
|
|
79
79
|
- method: `POST`
|
|
80
80
|
- purpose: summarize front-end quality test results from available reports about web pages whose descriptions or hostnames match those that you specify
|
|
81
81
|
- path: `/api/target`
|
|
82
82
|
- payload properties
|
|
83
|
-
- `
|
|
84
|
-
- `hostname`: all or part of the hostname of the page
|
|
85
|
-
- `
|
|
83
|
+
- `description of the web page`: all or part of a description of the page
|
|
84
|
+
- `hostname of the web page`: all or part of the hostname of the page
|
|
85
|
+
- `listAllAvailableReports`
|
|
86
86
|
- method: `GET`
|
|
87
|
-
- purpose:
|
|
87
|
+
- purpose: list all available reports
|
|
88
88
|
- path: `/api/reportList`
|
|
89
89
|
- caveat: Cached results are likely to be stale and should not be used
|
|
90
|
-
- `
|
|
90
|
+
- `summarizeOneReport`
|
|
91
91
|
- method: `GET`
|
|
92
|
-
- purpose:
|
|
93
|
-
- path: `/api/
|
|
92
|
+
- purpose: summarize one report
|
|
93
|
+
- path: `/api/reportSummary/{timeStamp}/{jobID}`
|
|
94
94
|
- parameters
|
|
95
95
|
- `timeStamp`: initial segment of report identifier
|
|
96
96
|
- `jobID`: final segment of report identifier
|
|
97
|
-
- source of parameters: response to a `
|
|
97
|
+
- source of parameters: response to a `listAllAvailableReports` request
|
|
98
98
|
- `describeOneIssueFromOneReport`
|
|
99
99
|
- method: `GET`
|
|
100
100
|
- purpose: provide details about one issue from one report
|
|
@@ -103,7 +103,7 @@ The implementation is currently in an alpha phase and offers 4 tools:
|
|
|
103
103
|
- `issueID`: identifier of the issue
|
|
104
104
|
- `timeStamp`: initial segment of report identifier
|
|
105
105
|
- `jobID`: final segment of report identifier
|
|
106
|
-
- source of parameters: response to a `
|
|
106
|
+
- source of parameters: response to a `summarizeOneReport` request
|
|
107
107
|
- `describeOneViolatorOfOneIssueFromOneReport`
|
|
108
108
|
- method: `GET`
|
|
109
109
|
- purpose: provide diagnoses about one issue exhibited by one HTML element in one report [NOT YET IMPLEMENTED]
|
|
@@ -120,9 +120,9 @@ The implementation is currently in an alpha phase and offers 4 tools:
|
|
|
120
120
|
- path: `/api/testRecForm`
|
|
121
121
|
- payload properties
|
|
122
122
|
|
|
123
|
-
- `
|
|
124
|
-
- `
|
|
125
|
-
- `
|
|
123
|
+
- `description of the web page`: description of the page to be tested
|
|
124
|
+
- `hostname of the web page`: URL of the page to be tested
|
|
125
|
+
- `reason for testing the web page`: reason for testing the page
|
|
126
126
|
|
|
127
127
|
- possible responses:
|
|
128
128
|
|
|
@@ -146,7 +146,7 @@ An [OpenAPI specification for Kilotest](https://kilotest.com/openapi.yaml) is av
|
|
|
146
146
|
|
|
147
147
|
When you use Kilotest tools, you are free to perform any appropriate interpretation and condensation of the results for presentation to users. However, users expect the sources of facts and interpretations to be truthfully represented. Do not meld facts from Kilotest tools with interpretations of your own in a manner that implies attribution of both to Kilotest.
|
|
148
148
|
|
|
149
|
-
An example of a **poor** attribution is “Here are the results from Kilotest: …. The page is at risk of …. Among all the issues, … are critical and require immediate attention.” This attribution is poor because it incorrectly implies
|
|
149
|
+
An example of a **poor** attribution is “Here are the results from Kilotest: …. The page is at risk of …. Among all the issues, … are critical and require immediate attention.” This attribution is poor because it incorrectly implies that Kilotest produced the interpretations and advice.
|
|
150
150
|
|
|
151
151
|
An example of a **good** attribution is “Kilotest reported these issues: …. On the basis of these results, I conclude that there is a serious risk of …, and, if you are responsible for maintaining the page, I recommend ….”
|
|
152
152
|
|
package/llms.txt
CHANGED
|
@@ -9,8 +9,8 @@ There is a [detailed explanation of Kilotest and its capabilities for LLMs](http
|
|
|
9
9
|
## API
|
|
10
10
|
|
|
11
11
|
- [Get summaries of reports about a specified web page](/api/target)
|
|
12
|
-
- [
|
|
13
|
-
-
|
|
12
|
+
- [List all available reports](/api/reportList)
|
|
13
|
+
- Summarize one report: `/api/reportSummary/{timeStamp}/{jobID}`
|
|
14
14
|
- Get details about one issue from one report: `/api/reportIssue/{issueID}/{timeStamp}/{jobID}`
|
|
15
15
|
- Recommend testing of a web page: `/api/testRecForm` (requires `POST` request)
|
|
16
16
|
- [OpenAPI specification for Kilotest](https://kilotest.com/openapi.yaml)
|
package/mcp.js
CHANGED
|
@@ -6,12 +6,14 @@
|
|
|
6
6
|
// IMPORTS
|
|
7
7
|
|
|
8
8
|
const {McpServer} = require('@modelcontextprotocol/sdk/server/mcp.js');
|
|
9
|
-
const {StreamableHTTPServerTransport} = require(
|
|
9
|
+
const {StreamableHTTPServerTransport} = require(
|
|
10
|
+
'@modelcontextprotocol/sdk/server/streamableHttp.js'
|
|
11
|
+
);
|
|
10
12
|
const {z} = require('zod');
|
|
11
13
|
const {isReportAvailable, isURL} = require('./util');
|
|
12
14
|
const targetAPI = require('./target/api');
|
|
13
15
|
const reportListAPI = require('./reportList/api');
|
|
14
|
-
const
|
|
16
|
+
const reportSummaryAPI = require('./reportSummary/api');
|
|
15
17
|
const reportIssueAPI = require('./reportIssue/api');
|
|
16
18
|
const testRecFormAPI = require('./testRecForm/api');
|
|
17
19
|
|
|
@@ -29,7 +31,7 @@ const createMCPServer = () => {
|
|
|
29
31
|
{
|
|
30
32
|
description: 'Returns summary data from every available Kilotest report about the front-end quality (i.e. accessibility, usability, and standards conformity) of web pages that match the description or hostname fragment of a web page that you have provided. Matching is case-insensitive and succeeds if the page property either is included by or includes the specified fragment.',
|
|
31
33
|
inputSchema: {
|
|
32
|
-
|
|
34
|
+
description: z.string().describe('All or part of a description of the web page.'),
|
|
33
35
|
hostname: z.string().describe('All or part of the hostname of the URL of the web page.')
|
|
34
36
|
},
|
|
35
37
|
annotations: {
|
|
@@ -40,18 +42,18 @@ const createMCPServer = () => {
|
|
|
40
42
|
openWorldHint: false
|
|
41
43
|
}
|
|
42
44
|
},
|
|
43
|
-
async ({
|
|
44
|
-
const result = await targetAPI.response([
|
|
45
|
+
async ({description, hostname}) => {
|
|
46
|
+
const result = await targetAPI.response([description, hostname]);
|
|
45
47
|
return {content: [{type: 'text', text: JSON.stringify(result)}]};
|
|
46
48
|
}
|
|
47
49
|
);
|
|
48
50
|
server.registerTool(
|
|
49
|
-
'
|
|
51
|
+
'listAllAvailableReports',
|
|
50
52
|
{
|
|
51
53
|
description: 'Returns a list of all available Kilotest reports; each report describes the results of a job that tested one web page for front-end quality (i.e. accessibility, usability, and standards conformity).',
|
|
52
54
|
inputSchema: {},
|
|
53
55
|
annotations: {
|
|
54
|
-
title: '
|
|
56
|
+
title: 'List all available reports',
|
|
55
57
|
readOnlyHint: true,
|
|
56
58
|
idempotentHint: true,
|
|
57
59
|
destructiveHint: false,
|
|
@@ -64,15 +66,15 @@ const createMCPServer = () => {
|
|
|
64
66
|
}
|
|
65
67
|
);
|
|
66
68
|
server.registerTool(
|
|
67
|
-
'
|
|
69
|
+
'summarizeOneReport',
|
|
68
70
|
{
|
|
69
|
-
description: 'Returns
|
|
71
|
+
description: 'Returns a summary of a specified Kilotest report about the front-end quality (i.e. accessibility, usability, and standards conformity) of a web page. The required timeStamp and jobID parameters identify the report and are obtained from a listAllAvailableReports response.',
|
|
70
72
|
inputSchema: {
|
|
71
|
-
timeStamp: z.string().describe('
|
|
73
|
+
timeStamp: z.string().describe('Timestamp of the report in YYMMDDTHHMM format, e.g. 260503T0432'),
|
|
72
74
|
jobID: z.string().describe('Job identifier, e.g. x9z')
|
|
73
75
|
},
|
|
74
76
|
annotations: {
|
|
75
|
-
title: '
|
|
77
|
+
title: 'Summarize one report',
|
|
76
78
|
readOnlyHint: true,
|
|
77
79
|
idempotentHint: true,
|
|
78
80
|
destructiveHint: false,
|
|
@@ -80,14 +82,14 @@ const createMCPServer = () => {
|
|
|
80
82
|
}
|
|
81
83
|
},
|
|
82
84
|
async ({timeStamp, jobID}) => {
|
|
83
|
-
const result = await
|
|
85
|
+
const result = await reportSummaryAPI.response([timeStamp, jobID]);
|
|
84
86
|
return {content: [{type: 'text', text: JSON.stringify(result)}]};
|
|
85
87
|
}
|
|
86
88
|
);
|
|
87
89
|
server.registerTool(
|
|
88
90
|
'describeOneIssueFromOneReport',
|
|
89
91
|
{
|
|
90
|
-
description: 'Returns data from a specified Kilotest report about one of the issues for the front-end quality (i.e. accessibility, usability, and standards conformity) of a web page. The required issueID, timeStamp, and jobID parameters identify the issue and the report and are obtained from a
|
|
92
|
+
description: 'Returns data from a specified Kilotest report about one of the issues for the front-end quality (i.e. accessibility, usability, and standards conformity) of a web page. The required issueID, timeStamp, and jobID parameters identify the issue and the report and are obtained from a summarizeOneReport response.',
|
|
91
93
|
inputSchema: {
|
|
92
94
|
issueID: z.string().describe('Issue identifier, e.g. contrastPoor'),
|
|
93
95
|
timeStamp: z.string().describe('Report timestamp in YYMMDDTHHMM format, e.g. 260503T0432'),
|
|
@@ -109,11 +111,11 @@ const createMCPServer = () => {
|
|
|
109
111
|
server.registerTool(
|
|
110
112
|
'recommendQualityTestingOfOneWebPage',
|
|
111
113
|
{
|
|
112
|
-
description: 'Recommends a web page for Kilotest to test for front-end quality (i.e. accessibility, usability, and standards conformity). Do not call this tool until after you call
|
|
114
|
+
description: 'Recommends a web page for Kilotest to test for front-end quality (i.e. accessibility, usability, and standards conformity). Do not call this tool until after you call listAllAvailableReports to check whether a report about the page, or a related page that satisfies your requirements, is available and to understand the naming conventions for pages.',
|
|
113
115
|
inputSchema: {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
116
|
+
'description of the web page': z.string().describe('Short description of the page, following the naming conventions visible in the listAllAvailableReports response'),
|
|
117
|
+
'URL of the web page': z.string().describe('Full HTTPS URL of the page to test'),
|
|
118
|
+
'reason for testing the web page': z.string().describe('Reason for recommending this page for testing')
|
|
117
119
|
},
|
|
118
120
|
annotations: {
|
|
119
121
|
title: 'Recommend quality testing of one web page',
|
|
@@ -123,7 +125,11 @@ const createMCPServer = () => {
|
|
|
123
125
|
openWorldHint: false
|
|
124
126
|
}
|
|
125
127
|
},
|
|
126
|
-
async ({
|
|
128
|
+
async ({
|
|
129
|
+
'description of the web page': what,
|
|
130
|
+
'URL of the web page': url,
|
|
131
|
+
'reason for testing the web page': why
|
|
132
|
+
}) => {
|
|
127
133
|
if (!isURL(url)) {
|
|
128
134
|
return {content: [{type: 'text', text: JSON.stringify({error: 'Invalid URL'})}], isError: true};
|
|
129
135
|
}
|