accessflow-mcp-server 1.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.
Files changed (75) hide show
  1. package/TOOL_REFERENCE.md +126 -0
  2. package/bin/flow-mcp +4 -0
  3. package/build/index.d.ts +3 -0
  4. package/build/index.d.ts.map +1 -0
  5. package/build/index.js +95 -0
  6. package/build/index.js.map +1 -0
  7. package/build/prompts/formatUrgentIssues.d.ts +4 -0
  8. package/build/prompts/formatUrgentIssues.d.ts.map +1 -0
  9. package/build/prompts/formatUrgentIssues.js +78 -0
  10. package/build/prompts/formatUrgentIssues.js.map +1 -0
  11. package/build/prompts/index.d.ts +4 -0
  12. package/build/prompts/index.d.ts.map +1 -0
  13. package/build/prompts/index.js +12 -0
  14. package/build/prompts/index.js.map +1 -0
  15. package/build/prompts/types.d.ts +22 -0
  16. package/build/prompts/types.d.ts.map +1 -0
  17. package/build/prompts/types.js +2 -0
  18. package/build/prompts/types.js.map +1 -0
  19. package/build/rules.json +4591 -0
  20. package/build/services/accessflow.d.ts +6 -0
  21. package/build/services/accessflow.d.ts.map +1 -0
  22. package/build/services/accessflow.js +8 -0
  23. package/build/services/accessflow.js.map +1 -0
  24. package/build/tools/getIssueRemediation.d.ts +4 -0
  25. package/build/tools/getIssueRemediation.d.ts.map +1 -0
  26. package/build/tools/getIssueRemediation.js +165 -0
  27. package/build/tools/getIssueRemediation.js.map +1 -0
  28. package/build/tools/getMostUrgentIssues.d.ts +4 -0
  29. package/build/tools/getMostUrgentIssues.d.ts.map +1 -0
  30. package/build/tools/getMostUrgentIssues.js +106 -0
  31. package/build/tools/getMostUrgentIssues.js.map +1 -0
  32. package/build/tools/index.d.ts +9 -0
  33. package/build/tools/index.d.ts.map +1 -0
  34. package/build/tools/index.js +19 -0
  35. package/build/tools/index.js.map +1 -0
  36. package/build/tools/types.d.ts +19 -0
  37. package/build/tools/types.d.ts.map +1 -0
  38. package/build/tools/types.js +2 -0
  39. package/build/tools/types.js.map +1 -0
  40. package/build/utils/config.d.ts +8 -0
  41. package/build/utils/config.d.ts.map +1 -0
  42. package/build/utils/config.js +26 -0
  43. package/build/utils/config.js.map +1 -0
  44. package/build/utils/database.d.ts +13 -0
  45. package/build/utils/database.d.ts.map +1 -0
  46. package/build/utils/database.js +64 -0
  47. package/build/utils/database.js.map +1 -0
  48. package/build/utils/domain.d.ts +2 -0
  49. package/build/utils/domain.d.ts.map +1 -0
  50. package/build/utils/domain.js +11 -0
  51. package/build/utils/domain.js.map +1 -0
  52. package/build/utils/logger.d.ts +18 -0
  53. package/build/utils/logger.d.ts.map +1 -0
  54. package/build/utils/logger.js +51 -0
  55. package/build/utils/logger.js.map +1 -0
  56. package/build/utils/responses.d.ts +19 -0
  57. package/build/utils/responses.d.ts.map +1 -0
  58. package/build/utils/responses.js +41 -0
  59. package/build/utils/responses.js.map +1 -0
  60. package/build/utils/validation.d.ts +7 -0
  61. package/build/utils/validation.d.ts.map +1 -0
  62. package/build/utils/validation.js +47 -0
  63. package/build/utils/validation.js.map +1 -0
  64. package/mcp-config.json +11 -0
  65. package/package.json +33 -0
  66. package/src/index.ts +125 -0
  67. package/src/rules.json +4591 -0
  68. package/src/services/accessflow.ts +9 -0
  69. package/src/tools/getIssueRemediation.ts +218 -0
  70. package/src/tools/getMostUrgentIssues.ts +144 -0
  71. package/src/tools/index.ts +33 -0
  72. package/src/tools/types.ts +20 -0
  73. package/src/utils/domain.ts +13 -0
  74. package/test.mjs +63 -0
  75. package/tsconfig.json +20 -0
@@ -0,0 +1,9 @@
1
+ export class AccessflowService {
2
+ private authCookie: string;
3
+
4
+ constructor(authCookie: string) {
5
+ this.authCookie = authCookie;
6
+ }
7
+
8
+ async fetchIssue(displayName: string, domain: string) {}
9
+ }
@@ -0,0 +1,218 @@
1
+ import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js";
2
+ import { ToolDefinition, ToolHandler, ToolContext } from "./types.js";
3
+
4
+ export const getIssueRemediationDefinition: ToolDefinition = {
5
+ name: "getIssueRemediation",
6
+ description:
7
+ "Get detailed remediation guidance for an accessibility issue by issue display name",
8
+ inputSchema: {
9
+ type: "object",
10
+ properties: {
11
+ issueDisplayName: {
12
+ type: "string",
13
+ description:
14
+ "The accessibility issue display name (e.g., Decorative-Content-6d277a13ba)",
15
+ },
16
+ },
17
+ required: ["issueDisplayName"],
18
+ },
19
+ };
20
+
21
+ export const getIssueRemediationHandler: ToolHandler = async (
22
+ args,
23
+ context
24
+ ) => {
25
+ const { issueDisplayName } = args;
26
+ const { environment, headers } = context;
27
+
28
+ if (!issueDisplayName) {
29
+ throw new McpError(
30
+ ErrorCode.InvalidParams,
31
+ "Issue display name is required"
32
+ );
33
+ }
34
+
35
+ let issue: any;
36
+
37
+ try {
38
+ const response = await fetch(
39
+ `${environment}/api/v6/mcp/${issueDisplayName}`,
40
+ {
41
+ method: "GET",
42
+ headers: headers,
43
+ }
44
+ );
45
+
46
+ if (!response.ok) {
47
+ if (response.status === 404) {
48
+ return {
49
+ content: [
50
+ {
51
+ type: "text",
52
+ text: `**Issue Not Found**\n\nNo accessibility issue found with display name: ${issueDisplayName}\n\nPlease verify the issue display name and try again.`,
53
+ },
54
+ ],
55
+ };
56
+ }
57
+
58
+ throw new McpError(
59
+ ErrorCode.InternalError,
60
+ `API request failed: ${response.status} ${response.statusText}`
61
+ );
62
+ }
63
+
64
+ issue = await response.json();
65
+ } catch (error) {
66
+ if (error instanceof McpError) {
67
+ throw error;
68
+ }
69
+
70
+ throw new McpError(
71
+ ErrorCode.InternalError,
72
+ `Failed to fetch issue data: ${
73
+ error instanceof Error ? error.message : "Unknown error"
74
+ }`
75
+ );
76
+ }
77
+
78
+ if (!issue) {
79
+ return {
80
+ content: [
81
+ {
82
+ type: "text",
83
+ text: `**Issue Not Found**\n\nNo accessibility issue found with display name: ${issueDisplayName}\n\nPlease verify the issue display name and try again.`,
84
+ },
85
+ ],
86
+ };
87
+ }
88
+
89
+ // Find the related rule
90
+ // Handle MongoDB ObjectId - convert to string for comparison
91
+ let relatedRuleId = issue.relatedRuleId;
92
+
93
+ if (relatedRuleId && typeof relatedRuleId === "object") {
94
+ if (relatedRuleId.$oid) {
95
+ relatedRuleId = relatedRuleId.$oid;
96
+ } else if (
97
+ relatedRuleId.toString &&
98
+ typeof relatedRuleId.toString === "function"
99
+ ) {
100
+ relatedRuleId = relatedRuleId.toString();
101
+ }
102
+ }
103
+
104
+ const rule = relatedRuleId
105
+ ? context.rules.find((r) => r._id?.$oid === relatedRuleId)
106
+ : null;
107
+
108
+ // Format the remediation response
109
+ const response = formatRemediationResponse(issue, rule);
110
+
111
+ return {
112
+ content: [
113
+ {
114
+ type: "text",
115
+ text: response,
116
+ },
117
+ ],
118
+ };
119
+ };
120
+
121
+ function formatRemediationResponse(issue: any, rule: any = null): string {
122
+ // 1. Short summary of the problem
123
+ const summary =
124
+ rule?.shortDescription ||
125
+ `Accessibility issue detected: ${issue.displayName}`;
126
+
127
+ // 2. WCAG reference & severity
128
+ const wcagLevel = issue.WCAGLevel || "Unknown";
129
+ const severity = issue.severity?.toUpperCase() || "Unknown";
130
+ const criteria = issue.criteria || "Unknown";
131
+ const wcagLink = rule?.issueWCAGLink || "";
132
+
133
+ // 3. Code-level fix suggestions
134
+ let codeFix = "\n## Code-Level Fix\n\n";
135
+
136
+ if (rule) {
137
+ codeFix += rule.shortDescription
138
+ ? `**Solution:** ${rule.shortDescription}\n\n`
139
+ : "";
140
+
141
+ // Show the current problematic code
142
+ if (issue.HTML) {
143
+ codeFix += "**Current code:**\n```html\n" + issue.HTML + "\n```\n\n";
144
+ }
145
+
146
+ // Extract code examples from the HTML resolution if available
147
+ if (rule.issueResolution) {
148
+ const htmlResolution = rule.issueResolution;
149
+ const codeBlocks =
150
+ htmlResolution.match(/<pre[^>]*><code[^>]*>(.*?)<\/code><\/pre>/gs) ||
151
+ [];
152
+
153
+ if (codeBlocks.length > 0) {
154
+ // Extract and clean up code examples
155
+ codeBlocks.forEach((block: string, index: number) => {
156
+ const cleanCode = block
157
+ .replace(/<pre[^>]*><code[^>]*>/, "")
158
+ .replace(/<\/code><\/pre>/, "")
159
+ .replace(/&lt;/g, "<")
160
+ .replace(/&gt;/g, ">")
161
+ .replace(/&quot;/g, '"')
162
+ .replace(/&amp;/g, "&")
163
+ .trim();
164
+
165
+ codeFix += `**Suggested fix ${
166
+ index + 1
167
+ }:**\n\`\`\`html\n${cleanCode}\n\`\`\`\n\n`;
168
+ });
169
+ }
170
+ }
171
+
172
+ // Add suggested fix details if available
173
+ if (rule.suggestedFix && rule.suggestedFix.length > 0) {
174
+ codeFix += "**Quick Fix Suggestions:**\n";
175
+ rule.suggestedFix.forEach((fix: any, index: number) => {
176
+ codeFix += `${index + 1}. **${fix.suggestedFixType}**: `;
177
+ if (fix.suggestedFixKey) {
178
+ codeFix += `Add attribute \`${fix.suggestedFixKey}="${fix.suggestedFixValue}"\`\n`;
179
+ } else {
180
+ codeFix += `${fix.suggestedFixValue}\n`;
181
+ }
182
+ });
183
+ codeFix += "\n";
184
+ }
185
+ } else {
186
+ codeFix += "No specific rule found for this issue.\n\n";
187
+ }
188
+
189
+ // 4. Links back to remediation panel
190
+ const remediationLinks = `
191
+ ## 📚 Additional Resources
192
+
193
+ - **WCAG Reference:** ${
194
+ wcagLink
195
+ ? `[${wcagLevel} - ${criteria}](${wcagLink})`
196
+ : `${wcagLevel} - ${criteria}`
197
+ }
198
+ - **Tutorial:** ${
199
+ rule?.issueTutorialLink || "Contact your AccessFlow administrator"
200
+ }
201
+ - **Issue Location:** ${issue.webpath} (Selector: \`${issue.selector}\`)
202
+ - **AccessFlow Panel:** View full remediation details in your AccessFlow dashboard
203
+
204
+ ---
205
+ **Issue ID:** ${issue.displayName} | **Detected:** ${new Date(
206
+ issue.date?.$date || issue.modified?.$date || Date.now()
207
+ ).toLocaleDateString()} | **Confidence:** ${issue.confidence}%`;
208
+
209
+ return `# 🚨 ${summary}
210
+
211
+ ## 📊 WCAG Reference & Severity
212
+ - **Severity:** ${severity}
213
+ - **WCAG Level:** ${wcagLevel}
214
+ - **Criteria:** ${criteria}
215
+ - **Occurrences:** ${issue.occurrences} time(s) on this page
216
+ ${codeFix}
217
+ ${remediationLinks}`;
218
+ }
@@ -0,0 +1,144 @@
1
+ import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js";
2
+ import { ToolDefinition, ToolHandler, ToolContext } from "./types.js";
3
+
4
+ export const getMostUrgentIssuesDefinition: ToolDefinition = {
5
+ name: "getMostUrgentIssues",
6
+ description:
7
+ "Get the most urgent accessibility issues as structured JSON data, ordered by severity (extreme > high > medium > low), then by site occurrences, then by page occurrences. Use the formatUrgentIssues prompt to format the JSON response into user-friendly markdown.",
8
+ inputSchema: {
9
+ type: "object",
10
+ properties: {},
11
+ required: [],
12
+ },
13
+ };
14
+
15
+ export const getMostUrgentIssuesHandler: ToolHandler = async (
16
+ args,
17
+ context
18
+ ) => {
19
+ const { environment, headers } = context;
20
+
21
+ // Validate limit parameter
22
+ const issueLimit = 3;
23
+
24
+ try {
25
+ const response = await fetch(
26
+ `${environment}/api/v6/mcp/urgent-issues?limit=${issueLimit}`,
27
+ {
28
+ method: "GET",
29
+ headers: headers,
30
+ }
31
+ );
32
+
33
+ if (!response.ok) {
34
+ throw new McpError(
35
+ ErrorCode.InternalError,
36
+ `API request failed: ${response.status} ${response.statusText}`
37
+ );
38
+ }
39
+
40
+ const apiResponse = await response.json();
41
+
42
+ // Extract the issues array from the response
43
+ let issues: any[];
44
+ if (Array.isArray(apiResponse)) {
45
+ issues = apiResponse;
46
+ } else if (apiResponse && Array.isArray(apiResponse.issues)) {
47
+ issues = apiResponse.issues;
48
+ } else {
49
+ throw new McpError(
50
+ ErrorCode.InternalError,
51
+ `Invalid API response format. Expected array or object with 'issues' array. Received: ${JSON.stringify(
52
+ apiResponse,
53
+ null,
54
+ 2
55
+ )}`
56
+ );
57
+ }
58
+
59
+ // The API returns issues already sorted by severity, site occurrences, and page occurrences
60
+ const sortedIssues = issues.slice(0, issueLimit);
61
+
62
+ if (sortedIssues.length === 0) {
63
+ return {
64
+ content: [
65
+ {
66
+ type: "text",
67
+ text: JSON.stringify(
68
+ {
69
+ totalIssues: 0,
70
+ showing: 0,
71
+ issues: [],
72
+ hasMore: false,
73
+ },
74
+ null,
75
+ 2
76
+ ),
77
+ },
78
+ ],
79
+ };
80
+ }
81
+
82
+ // Format the issues data for structured response
83
+ const formattedIssues = sortedIssues.map((issue: any, index: number) => ({
84
+ rank: index + 1,
85
+ displayName: issue.displayName,
86
+ severity: issue.severity?.toUpperCase() || "UNKNOWN",
87
+ severityEmoji: getSeverityEmoji(issue.severity),
88
+ wcagLevel: issue.WCAGLevel || "Unknown",
89
+ criteria: issue.criteria || "Unknown",
90
+ siteOccurrences: issue.siteOccurrences || 0,
91
+ pageOccurrences: issue.occurrences || 0,
92
+ }));
93
+
94
+ const responseData = {
95
+ totalIssues: issues.length,
96
+ showing: sortedIssues.length,
97
+ limit: issueLimit,
98
+ issues: formattedIssues,
99
+ hasMore: issues.length > issueLimit,
100
+ nextSteps: {
101
+ suggestion:
102
+ "Use getIssueRemediation with issueDisplayName for detailed fix guidance",
103
+ instructions:
104
+ "List to the user these issues with all the data for every one of them: severity, wcag, etc. For every issue, show the displayName to the user. After listing all the issues, suggest the user to get full issue remediation data. Do not fix the issues unless explicitly asked to fix",
105
+ },
106
+ };
107
+
108
+ return {
109
+ content: [
110
+ {
111
+ type: "text",
112
+ text: JSON.stringify(responseData, null, 2),
113
+ },
114
+ ],
115
+ };
116
+ } catch (error) {
117
+ if (error instanceof McpError) {
118
+ throw error;
119
+ }
120
+
121
+ // Enhanced error with more context
122
+ const errorMessage =
123
+ error instanceof Error ? error.message : "Unknown error";
124
+ throw new McpError(
125
+ ErrorCode.InternalError,
126
+ `Failed to fetch urgent issues: ${errorMessage}. This may be due to an unexpected API response format or network issue.`
127
+ );
128
+ }
129
+ };
130
+
131
+ function getSeverityEmoji(severity: string): string {
132
+ switch (severity?.toLowerCase()) {
133
+ case "extreme":
134
+ return "🔴";
135
+ case "high":
136
+ return "🟠";
137
+ case "medium":
138
+ return "🟡";
139
+ case "low":
140
+ return "🟢";
141
+ default:
142
+ return "⚪";
143
+ }
144
+ }
@@ -0,0 +1,33 @@
1
+ import { ToolDefinition, ToolHandler } from "./types.js";
2
+ import {
3
+ getIssueRemediationDefinition,
4
+ getIssueRemediationHandler,
5
+ } from "./getIssueRemediation.js";
6
+ import {
7
+ getMostUrgentIssuesDefinition,
8
+ getMostUrgentIssuesHandler,
9
+ } from "./getMostUrgentIssues.js";
10
+
11
+ export interface Tool {
12
+ definition: ToolDefinition;
13
+ handler: ToolHandler;
14
+ }
15
+
16
+ export const tools: Record<string, Tool> = {
17
+ getIssueRemediation: {
18
+ definition: getIssueRemediationDefinition,
19
+ handler: getIssueRemediationHandler,
20
+ },
21
+ getMostUrgentIssues: {
22
+ definition: getMostUrgentIssuesDefinition,
23
+ handler: getMostUrgentIssuesHandler,
24
+ },
25
+ };
26
+
27
+ export const getToolDefinitions = (): ToolDefinition[] => {
28
+ return Object.values(tools).map((tool) => tool.definition);
29
+ };
30
+
31
+ export const getToolHandler = (name: string): ToolHandler | undefined => {
32
+ return tools[name]?.handler;
33
+ };
@@ -0,0 +1,20 @@
1
+ export interface ToolContext {
2
+ rules: any[];
3
+ environment: string;
4
+ apiKey: string;
5
+ headers: Record<string, string>;
6
+ }
7
+
8
+ export interface ToolDefinition {
9
+ name: string;
10
+ description: string;
11
+ inputSchema: {
12
+ type: "object";
13
+ properties: Record<string, any>;
14
+ required: string[];
15
+ };
16
+ }
17
+
18
+ export interface ToolHandler {
19
+ (args: any, context: ToolContext): Promise<any>;
20
+ }
@@ -0,0 +1,13 @@
1
+ export function sanitizeDomain(domain: string): string {
2
+ if (!domain) {
3
+ return domain;
4
+ }
5
+
6
+ // Remove protocol (http:// or https://)
7
+ let sanitized = domain.replace(/^https?:\/\//, "");
8
+
9
+ // Remove trailing slash
10
+ sanitized = sanitized.replace(/\/$/, "");
11
+
12
+ return sanitized;
13
+ }
package/test.mjs ADDED
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { exec } from "child_process";
4
+ import { promisify } from "util";
5
+
6
+ const execAsync = promisify(exec);
7
+
8
+ /**
9
+ * Simple test script to verify the MCP server can start and respond
10
+ */
11
+ async function testMCPServer() {
12
+ console.log("Testing AccessFlow MCP Server...\n");
13
+
14
+ // Test 1: Check if the server can be built
15
+ console.log("1. Building the server...");
16
+ try {
17
+ await execAsync("npm run build");
18
+ console.log("✅ Build successful\n");
19
+ } catch (error) {
20
+ console.error("❌ Build failed:", error);
21
+ return;
22
+ }
23
+
24
+ // Test 2: Check if required environment variables are set
25
+ console.log("2. Checking environment variables...");
26
+ if (!process.env.MONGO_URI) {
27
+ console.log("⚠️ MONGO_URI not set. Please set it for full functionality.");
28
+ console.log(
29
+ ' Example: export MONGO_URI="mongodb://localhost:27017/accessflow"'
30
+ );
31
+ } else {
32
+ console.log("✅ MONGO_URI is set");
33
+ }
34
+ console.log("");
35
+
36
+ // Test 3: Check if the server starts (just syntax check)
37
+ console.log("3. Checking server syntax...");
38
+ try {
39
+ // Quick syntax check by importing the module
40
+ const fs = await import("fs/promises");
41
+ const path = await import("path");
42
+
43
+ const serverPath = "./build/index.js";
44
+ const stats = await fs.stat(serverPath);
45
+ if (stats.isFile()) {
46
+ console.log("✅ Server file exists and is readable");
47
+ }
48
+ } catch (error) {
49
+ console.error("❌ Server file check failed:", error);
50
+ return;
51
+ }
52
+
53
+ console.log("\n🎉 Basic tests passed!");
54
+ console.log("\nNext steps:");
55
+ console.log("1. Set MONGO_URI environment variable");
56
+ console.log("2. Add the server to your VS Code MCP configuration");
57
+ console.log(
58
+ '3. Test with: @copilot /use accessflow getIssueRemediation issueDisplayName="AF-12345"'
59
+ );
60
+ }
61
+
62
+ // Run the tests
63
+ testMCPServer().catch(console.error);
package/tsconfig.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "Node18",
5
+ "moduleResolution": "Node16",
6
+ "outDir": "./build",
7
+ "rootDir": "./src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "allowSyntheticDefaultImports": true,
11
+ "skipLibCheck": true,
12
+ "forceConsistentCasingInFileNames": true,
13
+ "declaration": true,
14
+ "declarationMap": true,
15
+ "sourceMap": true,
16
+ "resolveJsonModule": true
17
+ },
18
+ "include": ["src/**/*"],
19
+ "exclude": ["node_modules", "build"]
20
+ }