@aiready/mcp-server 0.6.1 → 0.6.2
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/.turbo/turbo-build.log +5 -5
- package/.turbo/turbo-format-check.log +2 -2
- package/.turbo/turbo-lint.log +1 -1
- package/.turbo/turbo-test$colon$coverage.log +132 -0
- package/.turbo/turbo-test.log +69 -44
- package/.turbo/turbo-type-check.log +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +598 -231
- package/package.json +19 -18
- package/src/__tests__/schema-sync.test.ts +54 -0
- package/src/__tests__/server.test.ts +28 -1
- package/src/index.ts +34 -327
- package/src/prompts/index.ts +76 -0
- package/src/resources/index.ts +94 -0
- package/src/state-store.ts +111 -0
- package/src/tools/best-practices.ts +167 -0
- package/src/tools/context-budget.ts +76 -0
- package/src/tools/index.ts +236 -0
- package/tsconfig.json +2 -1
- package/.smithery/shttp/manifest.json +0 -73
- package/.smithery/shttp/module.js +0 -270910
- package/.smithery/shttp/module.js.map +0 -7
- package/coverage/base.css +0 -224
- package/coverage/block-navigation.js +0 -87
- package/coverage/clover.xml +0 -6
- package/coverage/coverage-final.json +0 -1
- package/coverage/favicon.png +0 -0
- package/coverage/index.html +0 -101
- package/coverage/prettify.css +0 -1
- package/coverage/prettify.js +0 -2
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +0 -210
package/dist/index.js
CHANGED
|
@@ -5,24 +5,213 @@ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
|
5
5
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
6
|
import {
|
|
7
7
|
CallToolRequestSchema,
|
|
8
|
-
ListToolsRequestSchema
|
|
9
|
-
ListResourcesRequestSchema,
|
|
10
|
-
ReadResourceRequestSchema,
|
|
11
|
-
ListPromptsRequestSchema,
|
|
12
|
-
GetPromptRequestSchema
|
|
8
|
+
ListToolsRequestSchema
|
|
13
9
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
10
|
+
|
|
11
|
+
// src/tools/index.ts
|
|
12
|
+
import { z as z3 } from "zod";
|
|
14
13
|
import { ToolRegistry, ToolName } from "@aiready/core";
|
|
14
|
+
|
|
15
|
+
// src/tools/best-practices.ts
|
|
15
16
|
import { z } from "zod";
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
import fs from "fs";
|
|
18
|
+
import path from "path";
|
|
19
|
+
import { fileURLToPath } from "url";
|
|
20
|
+
var __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
21
|
+
var SKILLS_AGENTS_MD_PATH = path.resolve(
|
|
22
|
+
__dirname,
|
|
23
|
+
"../../skills/aiready-best-practices/AGENTS.md"
|
|
24
|
+
);
|
|
25
|
+
var BestPracticesArgsSchema = z.object({
|
|
26
|
+
category: z.string().describe(
|
|
27
|
+
"Category of best practices (e.g., patterns, context, consistency, signal, grounding)"
|
|
28
|
+
)
|
|
29
|
+
});
|
|
30
|
+
var ComplianceArgsSchema = z.object({
|
|
31
|
+
file_path: z.string().describe("Absolute path to the file to check")
|
|
32
|
+
});
|
|
33
|
+
async function handleGetBestPractices(args) {
|
|
34
|
+
const { category } = args;
|
|
35
|
+
try {
|
|
36
|
+
const content = await fs.promises.readFile(SKILLS_AGENTS_MD_PATH, "utf-8");
|
|
37
|
+
const lines = content.split("\n");
|
|
38
|
+
let inSection = false;
|
|
39
|
+
const sectionContent = [];
|
|
40
|
+
for (const line of lines) {
|
|
41
|
+
if (line.startsWith("## ") && line.toLowerCase().includes(`(${category.toLowerCase()})`)) {
|
|
42
|
+
inSection = true;
|
|
43
|
+
sectionContent.push(line);
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (inSection && line.startsWith("## ") && !line.toLowerCase().includes(`(${category.toLowerCase()})`)) {
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
if (inSection) {
|
|
50
|
+
sectionContent.push(line);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (sectionContent.length === 0) {
|
|
54
|
+
return {
|
|
55
|
+
content: [
|
|
56
|
+
{
|
|
57
|
+
type: "text",
|
|
58
|
+
text: `Category "${category}" not found in AIReady Best Practices.`
|
|
59
|
+
}
|
|
60
|
+
],
|
|
61
|
+
isError: true
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
content: [{ type: "text", text: sectionContent.join("\n") }]
|
|
66
|
+
};
|
|
67
|
+
} catch (error) {
|
|
68
|
+
return {
|
|
69
|
+
content: [
|
|
70
|
+
{
|
|
71
|
+
type: "text",
|
|
72
|
+
text: `Error reading best practices: ${error.message}`
|
|
73
|
+
}
|
|
74
|
+
],
|
|
75
|
+
isError: true
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
async function handleCheckCompliance(args) {
|
|
80
|
+
const { file_path } = args;
|
|
81
|
+
try {
|
|
82
|
+
const content = await fs.promises.readFile(file_path, "utf-8");
|
|
83
|
+
const issues = [];
|
|
84
|
+
const lineCount = content.split("\n").length;
|
|
85
|
+
if (lineCount > 500) {
|
|
86
|
+
issues.push(
|
|
87
|
+
`\u26A0\uFE0F **Context Optimization (2.3)**: File has ${lineCount} lines. Large files waste context. Consider splitting into smaller modules.`
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
if (content.includes("true, false") || content.includes("false, true") || content.includes("true, true")) {
|
|
91
|
+
issues.push(
|
|
92
|
+
`\u{1F6A9} **AI Signal Clarity (4.1)**: Detected potential positional boolean traps. Prefer named options objects for clarity.`
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
const magicNumberRegex = /[^A-Z_a-z][0-9]{2,}[^A-Z_a-z0-9]/g;
|
|
96
|
+
if (magicNumberRegex.test(content)) {
|
|
97
|
+
issues.push(
|
|
98
|
+
`\u{1F6A9} **AI Signal Clarity (4.3)**: Detected potential magic literals (raw numbers). Use named constants/enums for business rules.`
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
const entropyRegex = /\b(data|info|handle|obj|item)\b/gi;
|
|
102
|
+
const matches = content.match(entropyRegex);
|
|
103
|
+
if (matches && matches.length > 5) {
|
|
104
|
+
issues.push(
|
|
105
|
+
`\u{1F6A9} **AI Signal Clarity (4.2)**: High-entropy names detected (${[...new Set(matches.map((m) => m.toLowerCase()))].join(", ")}). Use specific domain names instead.`
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
if (issues.length === 0) {
|
|
109
|
+
return {
|
|
110
|
+
content: [
|
|
111
|
+
{
|
|
112
|
+
type: "text",
|
|
113
|
+
text: `\u2705 File "${path.basename(file_path)}" is compliant with lightweight AIReady Best Practices.`
|
|
114
|
+
}
|
|
115
|
+
]
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
return {
|
|
119
|
+
content: [
|
|
120
|
+
{
|
|
121
|
+
type: "text",
|
|
122
|
+
text: `AIReady Compliance Report for "${path.basename(file_path)}":
|
|
123
|
+
|
|
124
|
+
${issues.join("\n")}`
|
|
125
|
+
}
|
|
126
|
+
]
|
|
127
|
+
};
|
|
128
|
+
} catch (error) {
|
|
129
|
+
return {
|
|
130
|
+
content: [
|
|
131
|
+
{ type: "text", text: `Error checking compliance: ${error.message}` }
|
|
132
|
+
],
|
|
133
|
+
isError: true
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// src/tools/context-budget.ts
|
|
139
|
+
import { z as z2 } from "zod";
|
|
140
|
+
import fs2 from "fs";
|
|
141
|
+
import path2 from "path";
|
|
142
|
+
var ContextBudgetArgsSchema = z2.object({
|
|
143
|
+
file_path: z2.string().describe("Absolute path to the file to analyze")
|
|
144
|
+
});
|
|
145
|
+
async function handleAnalyzeContextBudget(args) {
|
|
146
|
+
const { file_path } = args;
|
|
147
|
+
try {
|
|
148
|
+
const content = await fs2.promises.readFile(file_path, "utf-8");
|
|
149
|
+
const charCount = content.length;
|
|
150
|
+
const tokens = Math.ceil(charCount / 4);
|
|
151
|
+
const tiers = [
|
|
152
|
+
{ name: "8k (GPT-4/Turbo)", threshold: 8e3 },
|
|
153
|
+
{ name: "32k (GPT-4o/Mini)", threshold: 32e3 },
|
|
154
|
+
{ name: "128k (GPT-4o/Claude 3)", threshold: 128e3 },
|
|
155
|
+
{ name: "200k (Claude 3.5 Sonnet)", threshold: 2e5 },
|
|
156
|
+
{ name: "1M (Gemini 1.5 Pro)", threshold: 1e6 }
|
|
157
|
+
];
|
|
158
|
+
let result = `# Context Budget for "${path2.basename(file_path)}"
|
|
159
|
+
|
|
160
|
+
`;
|
|
161
|
+
result += `**Estimated Tokens:** ${tokens.toLocaleString()}
|
|
162
|
+
|
|
163
|
+
`;
|
|
164
|
+
result += `### Context Usage By Tier
|
|
165
|
+
`;
|
|
166
|
+
tiers.forEach((tier) => {
|
|
167
|
+
const percentage = tokens / tier.threshold * 100;
|
|
168
|
+
const barLength = Math.min(Math.ceil(percentage / 5), 20);
|
|
169
|
+
const bar = "\u2588".repeat(barLength) + "\u2591".repeat(Math.max(0, 20 - barLength));
|
|
170
|
+
result += ` - **${tier.name}:** ${bar} ${percentage.toFixed(1)}%
|
|
171
|
+
`;
|
|
172
|
+
});
|
|
173
|
+
result += `
|
|
174
|
+
### Recommendations
|
|
175
|
+
`;
|
|
176
|
+
if (tokens > 5e3) {
|
|
177
|
+
result += `\u26A0\uFE0F **High Context Usage:** This file alone takes up a significant chunk of smaller context windows. Consider refactoring to extract logical sub-modules.
|
|
178
|
+
`;
|
|
179
|
+
} else {
|
|
180
|
+
result += `\u2705 **Optimal Size:** This file is well-sized for AI context windows.
|
|
181
|
+
`;
|
|
182
|
+
}
|
|
183
|
+
const importCount = (content.match(/^import /gm) || []).length;
|
|
184
|
+
if (importCount > 20) {
|
|
185
|
+
result += `\u26A0\uFE0F **High Dependency Load:** ${importCount} imports found. AI will need to load many dependent files, multiplying the context budget needed. Use barrel exports (index.ts) to flatten the dependency tree.
|
|
186
|
+
`;
|
|
187
|
+
}
|
|
188
|
+
return {
|
|
189
|
+
content: [{ type: "text", text: result }]
|
|
190
|
+
};
|
|
191
|
+
} catch (error) {
|
|
192
|
+
return {
|
|
193
|
+
content: [
|
|
194
|
+
{
|
|
195
|
+
type: "text",
|
|
196
|
+
text: `Error analyzing context budget: ${error.message}`
|
|
197
|
+
}
|
|
198
|
+
],
|
|
199
|
+
isError: true
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// src/tools/index.ts
|
|
205
|
+
var AnalysisArgsSchema = z3.object({
|
|
206
|
+
path: z3.string().describe("Path to the directory to analyze"),
|
|
207
|
+
summary_only: z3.boolean().optional().describe(
|
|
19
208
|
"If true, returns only the summary and skips the detailed issue list. Best for large projects to save context."
|
|
20
209
|
)
|
|
21
210
|
});
|
|
22
|
-
var RemediationArgsSchema =
|
|
23
|
-
issue_id:
|
|
24
|
-
file_path:
|
|
25
|
-
context:
|
|
211
|
+
var RemediationArgsSchema = z3.object({
|
|
212
|
+
issue_id: z3.string().describe("The unique ID of the issue to fix"),
|
|
213
|
+
file_path: z3.string().describe("The path to the file containing the issue"),
|
|
214
|
+
context: z3.string().describe("The content of the file or surrounding code")
|
|
26
215
|
});
|
|
27
216
|
var TOOL_PACKAGE_MAP = {
|
|
28
217
|
[ToolName.PatternDetect]: "@aiready/pattern-detect",
|
|
@@ -35,6 +224,11 @@ var TOOL_PACKAGE_MAP = {
|
|
|
35
224
|
[ToolName.DependencyHealth]: "@aiready/deps",
|
|
36
225
|
[ToolName.ChangeAmplification]: "@aiready/change-amplification",
|
|
37
226
|
[ToolName.ContractEnforcement]: "@aiready/contract-enforcement",
|
|
227
|
+
// New tools from core
|
|
228
|
+
[ToolName.CognitiveLoad]: "@aiready/cognitive-load",
|
|
229
|
+
[ToolName.PatternEntropy]: "@aiready/pattern-entropy",
|
|
230
|
+
[ToolName.ConceptCohesion]: "@aiready/concept-cohesion",
|
|
231
|
+
[ToolName.SemanticDistance]: "@aiready/semantic-distance",
|
|
38
232
|
// Aliases
|
|
39
233
|
patterns: "@aiready/pattern-detect",
|
|
40
234
|
duplicates: "@aiready/pattern-detect",
|
|
@@ -48,216 +242,423 @@ var TOOL_PACKAGE_MAP = {
|
|
|
48
242
|
"change-amp": "@aiready/change-amplification",
|
|
49
243
|
"contract-enforce": "@aiready/contract-enforcement"
|
|
50
244
|
};
|
|
51
|
-
var
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
245
|
+
var ADVERTISED_TOOLS = [
|
|
246
|
+
ToolName.PatternDetect,
|
|
247
|
+
ToolName.ContextAnalyzer,
|
|
248
|
+
ToolName.NamingConsistency,
|
|
249
|
+
ToolName.AiSignalClarity,
|
|
250
|
+
ToolName.AgentGrounding,
|
|
251
|
+
ToolName.TestabilityIndex,
|
|
252
|
+
ToolName.DocDrift,
|
|
253
|
+
ToolName.DependencyHealth,
|
|
254
|
+
ToolName.ChangeAmplification,
|
|
255
|
+
ToolName.ContractEnforcement,
|
|
256
|
+
ToolName.CognitiveLoad,
|
|
257
|
+
ToolName.PatternEntropy,
|
|
258
|
+
ToolName.ConceptCohesion,
|
|
259
|
+
ToolName.SemanticDistance,
|
|
260
|
+
"get_best_practices",
|
|
261
|
+
"check_best_practice_compliance",
|
|
262
|
+
"analyze_context_budget"
|
|
263
|
+
];
|
|
264
|
+
async function handleAnalysis(name, args, stateStore2) {
|
|
265
|
+
const parsedArgs = AnalysisArgsSchema.safeParse(args);
|
|
266
|
+
if (!parsedArgs.success) {
|
|
267
|
+
throw new Error(
|
|
268
|
+
`Invalid arguments for ${name}: ${parsedArgs.error.message}`
|
|
66
269
|
);
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
270
|
+
}
|
|
271
|
+
const { path: rootDir, summary_only } = parsedArgs.data;
|
|
272
|
+
let provider = ToolRegistry.find(name);
|
|
273
|
+
if (!provider) {
|
|
274
|
+
const packageName = TOOL_PACKAGE_MAP[name] ?? (name.startsWith("@aiready/") ? name : `@aiready/${name}`);
|
|
275
|
+
try {
|
|
276
|
+
console.error(
|
|
277
|
+
`[MCP] Dynamically loading ${packageName} for tool ${name}`
|
|
278
|
+
);
|
|
279
|
+
await import(packageName);
|
|
280
|
+
provider = ToolRegistry.find(name);
|
|
281
|
+
} catch (importError) {
|
|
282
|
+
const importErrorMessage = importError instanceof Error ? importError.message : String(importError);
|
|
283
|
+
const error = new Error(
|
|
284
|
+
`Tool ${name} not found and failed to load package ${packageName}: ${importErrorMessage}`
|
|
285
|
+
);
|
|
286
|
+
error.cause = importError;
|
|
287
|
+
throw error;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
if (!provider) {
|
|
291
|
+
throw new Error(`Tool ${name} not found after attempting to load`);
|
|
292
|
+
}
|
|
293
|
+
console.error(
|
|
294
|
+
`[MCP] Executing ${name} on ${rootDir}${summary_only ? " (summary only)" : ""}`
|
|
295
|
+
);
|
|
296
|
+
const results = await provider.analyze({
|
|
297
|
+
rootDir
|
|
298
|
+
});
|
|
299
|
+
if (stateStore2) {
|
|
300
|
+
stateStore2.updateLastResults(results);
|
|
301
|
+
}
|
|
302
|
+
if (summary_only) {
|
|
303
|
+
const summary = results.summary;
|
|
304
|
+
return {
|
|
305
|
+
summary: `## Issue Breakdown
|
|
306
|
+
- Critical: ${summary.criticalIssues}
|
|
307
|
+
- Major: ${summary.majorIssues}
|
|
308
|
+
- Total Issues: ${summary.totalIssues}
|
|
309
|
+
- Files Analyzed: ${summary.totalFiles}`,
|
|
310
|
+
metadata: results.metadata,
|
|
311
|
+
notice: "Detailed issues were omitted (summary_only: true). Run without summary_only for full details."
|
|
70
312
|
};
|
|
71
313
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
314
|
+
return results;
|
|
315
|
+
}
|
|
316
|
+
async function handleRemediation(args) {
|
|
317
|
+
const apiKey = process.env.AIREADY_API_KEY;
|
|
318
|
+
const serverUrl = process.env.AIREADY_PLATFORM_URL || "https://platform.getaiready.dev";
|
|
319
|
+
if (!apiKey) {
|
|
320
|
+
throw new Error(
|
|
321
|
+
"AIREADY_API_KEY is not set. Remediation requires an active subscription."
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
console.error(`[MCP] Requesting remediation for ${args.issue_id}...`);
|
|
325
|
+
try {
|
|
326
|
+
const response = await fetch(`${serverUrl}/api/v1/remediate`, {
|
|
327
|
+
method: "POST",
|
|
328
|
+
headers: {
|
|
329
|
+
"Content-Type": "application/json",
|
|
330
|
+
"X-API-KEY": apiKey
|
|
331
|
+
},
|
|
332
|
+
body: JSON.stringify({
|
|
333
|
+
issueId: args.issue_id,
|
|
334
|
+
filePath: args.file_path,
|
|
335
|
+
context: args.context,
|
|
336
|
+
agent: "mcp-server"
|
|
337
|
+
})
|
|
338
|
+
});
|
|
339
|
+
if (!response.ok) {
|
|
340
|
+
const errorData = await response.json().catch(() => ({}));
|
|
76
341
|
throw new Error(
|
|
77
|
-
|
|
342
|
+
`Platform Error: ${errorData.message || response.statusText}`
|
|
78
343
|
);
|
|
79
344
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
"X-API-KEY": apiKey
|
|
87
|
-
},
|
|
88
|
-
body: JSON.stringify({
|
|
89
|
-
issueId: args.issue_id,
|
|
90
|
-
filePath: args.file_path,
|
|
91
|
-
context: args.context,
|
|
92
|
-
agent: "mcp-server"
|
|
93
|
-
})
|
|
94
|
-
});
|
|
95
|
-
if (!response.ok) {
|
|
96
|
-
const errorData = await response.json().catch(() => ({}));
|
|
97
|
-
throw new Error(
|
|
98
|
-
`Platform Error: ${errorData.message || response.statusText}`
|
|
99
|
-
);
|
|
100
|
-
}
|
|
101
|
-
const data = await response.json();
|
|
102
|
-
return {
|
|
103
|
-
content: [
|
|
104
|
-
{
|
|
105
|
-
type: "text",
|
|
106
|
-
text: `Recommended Fix (Diff):
|
|
345
|
+
const data = await response.json();
|
|
346
|
+
return {
|
|
347
|
+
content: [
|
|
348
|
+
{
|
|
349
|
+
type: "text",
|
|
350
|
+
text: `Recommended Fix (Diff):
|
|
107
351
|
|
|
108
352
|
${data.diff}
|
|
109
353
|
|
|
110
354
|
Rationale:
|
|
111
355
|
${data.rationale}`
|
|
356
|
+
}
|
|
357
|
+
]
|
|
358
|
+
};
|
|
359
|
+
} catch (error) {
|
|
360
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
361
|
+
return {
|
|
362
|
+
content: [
|
|
363
|
+
{
|
|
364
|
+
type: "text",
|
|
365
|
+
text: `Failed to get remediation: ${errorMessage}. Please visit the dashboard to fix manually.`
|
|
366
|
+
}
|
|
367
|
+
],
|
|
368
|
+
isError: true
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// src/resources/index.ts
|
|
374
|
+
import {
|
|
375
|
+
ListResourcesRequestSchema,
|
|
376
|
+
ReadResourceRequestSchema
|
|
377
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
378
|
+
function registerResourceHandlers(server, stateStore2) {
|
|
379
|
+
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
380
|
+
return {
|
|
381
|
+
resources: [
|
|
382
|
+
{
|
|
383
|
+
uri: "aiready://project/summary",
|
|
384
|
+
name: "AIReady Project Summary",
|
|
385
|
+
description: "Quick top-level AI-readiness summary.",
|
|
386
|
+
mimeType: "text/markdown"
|
|
387
|
+
},
|
|
388
|
+
{
|
|
389
|
+
uri: "aiready://project/issues",
|
|
390
|
+
name: "AIReady Critical Issues",
|
|
391
|
+
description: "List of top 10 critical readiness issues.",
|
|
392
|
+
mimeType: "application/json"
|
|
393
|
+
},
|
|
394
|
+
{
|
|
395
|
+
uri: "aiready://project/graph",
|
|
396
|
+
name: "AIReady Codebase Graph",
|
|
397
|
+
description: "Force-directed graph data for visualization.",
|
|
398
|
+
mimeType: "application/json"
|
|
399
|
+
},
|
|
400
|
+
{
|
|
401
|
+
uri: "aiready://project/roadmap",
|
|
402
|
+
name: "AIReady Readiness Roadmap",
|
|
403
|
+
description: "A prioritized plan to reach elite readiness.",
|
|
404
|
+
mimeType: "text/markdown"
|
|
405
|
+
}
|
|
406
|
+
]
|
|
407
|
+
};
|
|
408
|
+
});
|
|
409
|
+
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
410
|
+
const { uri } = request.params;
|
|
411
|
+
if (uri === "aiready://project/summary") {
|
|
412
|
+
return {
|
|
413
|
+
contents: [
|
|
414
|
+
{
|
|
415
|
+
uri,
|
|
416
|
+
mimeType: "text/markdown",
|
|
417
|
+
text: stateStore2.getSummaryMarkdown()
|
|
112
418
|
}
|
|
113
419
|
]
|
|
114
420
|
};
|
|
115
|
-
}
|
|
116
|
-
|
|
421
|
+
}
|
|
422
|
+
if (uri === "aiready://project/issues") {
|
|
117
423
|
return {
|
|
118
|
-
|
|
424
|
+
contents: [
|
|
119
425
|
{
|
|
120
|
-
|
|
121
|
-
|
|
426
|
+
uri,
|
|
427
|
+
mimeType: "application/json",
|
|
428
|
+
text: stateStore2.getIssuesJson()
|
|
122
429
|
}
|
|
123
|
-
]
|
|
124
|
-
isError: true
|
|
430
|
+
]
|
|
125
431
|
};
|
|
126
432
|
}
|
|
127
|
-
|
|
128
|
-
setupHandlers() {
|
|
129
|
-
this.server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
433
|
+
if (uri === "aiready://project/graph") {
|
|
130
434
|
return {
|
|
131
|
-
|
|
435
|
+
contents: [
|
|
132
436
|
{
|
|
133
|
-
uri
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
mimeType: "text/markdown"
|
|
137
|
-
},
|
|
138
|
-
{
|
|
139
|
-
uri: "aiready://project/issues",
|
|
140
|
-
name: "AIReady Critical Issues",
|
|
141
|
-
description: "List of top 10 critical readiness issues.",
|
|
142
|
-
mimeType: "application/json"
|
|
143
|
-
},
|
|
144
|
-
{
|
|
145
|
-
uri: "aiready://project/graph",
|
|
146
|
-
name: "AIReady Codebase Graph",
|
|
147
|
-
description: "Force-directed graph data for visualization.",
|
|
148
|
-
mimeType: "application/json"
|
|
437
|
+
uri,
|
|
438
|
+
mimeType: "application/json",
|
|
439
|
+
text: stateStore2.getGraphJson()
|
|
149
440
|
}
|
|
150
441
|
]
|
|
151
442
|
};
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
ReadResourceRequestSchema,
|
|
155
|
-
async (request) => {
|
|
156
|
-
const { uri } = request.params;
|
|
157
|
-
if (uri === "aiready://project/summary") {
|
|
158
|
-
return {
|
|
159
|
-
contents: [
|
|
160
|
-
{
|
|
161
|
-
uri,
|
|
162
|
-
mimeType: "text/markdown",
|
|
163
|
-
text: "# AIReady Summary\n\nProject: Current Directory\nScore: 84/100 (B)\n\nCritical Issues: 2\nMajor Issues: 14\n\nRun the `aiready-mcp` tool for full analysis."
|
|
164
|
-
}
|
|
165
|
-
]
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
|
-
if (uri === "aiready://project/issues" || uri === "aiready://project/graph") {
|
|
169
|
-
return {
|
|
170
|
-
contents: [
|
|
171
|
-
{
|
|
172
|
-
uri,
|
|
173
|
-
mimeType: "application/json",
|
|
174
|
-
text: JSON.stringify({
|
|
175
|
-
message: "Resource content coming from latest scan..."
|
|
176
|
-
})
|
|
177
|
-
}
|
|
178
|
-
]
|
|
179
|
-
};
|
|
180
|
-
}
|
|
181
|
-
throw new Error(`Resource not found: ${uri}`);
|
|
182
|
-
}
|
|
183
|
-
);
|
|
184
|
-
this.server.setRequestHandler(ListPromptsRequestSchema, async () => {
|
|
443
|
+
}
|
|
444
|
+
if (uri === "aiready://project/roadmap") {
|
|
185
445
|
return {
|
|
186
|
-
|
|
187
|
-
{
|
|
188
|
-
name: "analyze-project",
|
|
189
|
-
description: "Audit the project for AI-readiness and suggest improvements.",
|
|
190
|
-
arguments: [
|
|
191
|
-
{
|
|
192
|
-
name: "path",
|
|
193
|
-
description: "Path/directory to analyze",
|
|
194
|
-
required: true
|
|
195
|
-
}
|
|
196
|
-
]
|
|
197
|
-
},
|
|
446
|
+
contents: [
|
|
198
447
|
{
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
{
|
|
203
|
-
name: "issueId",
|
|
204
|
-
description: "The unique ID of the issue to fix",
|
|
205
|
-
required: true
|
|
206
|
-
}
|
|
207
|
-
]
|
|
448
|
+
uri,
|
|
449
|
+
mimeType: "text/markdown",
|
|
450
|
+
text: stateStore2.getRoadmapMarkdown()
|
|
208
451
|
}
|
|
209
452
|
]
|
|
210
453
|
};
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
454
|
+
}
|
|
455
|
+
throw new Error(`Resource not found: ${uri}`);
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// src/prompts/index.ts
|
|
460
|
+
import {
|
|
461
|
+
ListPromptsRequestSchema,
|
|
462
|
+
GetPromptRequestSchema
|
|
463
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
464
|
+
function registerPromptHandlers(server) {
|
|
465
|
+
server.setRequestHandler(ListPromptsRequestSchema, async () => {
|
|
466
|
+
return {
|
|
467
|
+
prompts: [
|
|
468
|
+
{
|
|
469
|
+
name: "analyze-project",
|
|
470
|
+
description: "Audit the project for AI-readiness and suggest improvements.",
|
|
471
|
+
arguments: [
|
|
219
472
|
{
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
text: `I want to audit the project at "${path}" for AI-readiness. Please use the AIReady tools to identify duplication patterns, context fragmentation, and naming inconsistencies. Then, provide a prioritized list of improvements to help me leverage AI agents more effectively.`
|
|
224
|
-
}
|
|
473
|
+
name: "path",
|
|
474
|
+
description: "Path/directory to analyze",
|
|
475
|
+
required: true
|
|
225
476
|
}
|
|
226
477
|
]
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
description: "Issue remediation instructions",
|
|
233
|
-
messages: [
|
|
478
|
+
},
|
|
479
|
+
{
|
|
480
|
+
name: "remediate-issue",
|
|
481
|
+
description: "Help the user fix a specific AIReady issue.",
|
|
482
|
+
arguments: [
|
|
234
483
|
{
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
text: `I've identified an AIReady issue with ID: ${issueId}. Please use the \`get_remediation_diff\` tool to find a fix, explain the rationale behind the recommended change, and then help me apply it to the codebase.`
|
|
239
|
-
}
|
|
484
|
+
name: "issueId",
|
|
485
|
+
description: "The unique ID of the issue to fix",
|
|
486
|
+
required: true
|
|
240
487
|
}
|
|
241
488
|
]
|
|
242
|
-
}
|
|
489
|
+
}
|
|
490
|
+
]
|
|
491
|
+
};
|
|
492
|
+
});
|
|
493
|
+
server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
494
|
+
const { name, arguments: args } = request.params;
|
|
495
|
+
if (name === "analyze-project") {
|
|
496
|
+
const path3 = args?.path || ".";
|
|
497
|
+
return {
|
|
498
|
+
description: "Project audit instructions",
|
|
499
|
+
messages: [
|
|
500
|
+
{
|
|
501
|
+
role: "user",
|
|
502
|
+
content: {
|
|
503
|
+
type: "text",
|
|
504
|
+
text: `I want to audit the project at "${path3}" for AI-readiness. Please use the AIReady tools to identify duplication patterns, context fragmentation, and naming inconsistencies. Then, provide a prioritized list of improvements to help me leverage AI agents more effectively.`
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
]
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
if (name === "remediate-issue") {
|
|
511
|
+
const issueId = args?.issueId;
|
|
512
|
+
return {
|
|
513
|
+
description: "Issue remediation instructions",
|
|
514
|
+
messages: [
|
|
515
|
+
{
|
|
516
|
+
role: "user",
|
|
517
|
+
content: {
|
|
518
|
+
type: "text",
|
|
519
|
+
text: `I've identified an AIReady issue with ID: ${issueId}. Please use the \`get_remediation_diff\` tool to find a fix, explain the rationale behind the recommended change, and then help me apply it to the codebase.`
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
]
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
throw new Error(`Prompt not found: ${name}`);
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// src/state-store.ts
|
|
530
|
+
var StateStore = class {
|
|
531
|
+
constructor() {
|
|
532
|
+
this.lastResults = null;
|
|
533
|
+
this.lastScanTimestamp = null;
|
|
534
|
+
}
|
|
535
|
+
updateLastResults(results) {
|
|
536
|
+
this.lastResults = results;
|
|
537
|
+
this.lastScanTimestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
538
|
+
}
|
|
539
|
+
getLastResults() {
|
|
540
|
+
return this.lastResults;
|
|
541
|
+
}
|
|
542
|
+
getSummaryMarkdown() {
|
|
543
|
+
if (!this.lastResults) {
|
|
544
|
+
return "# AIReady Summary\n\nNo scan has been run yet. Run an AIReady scan tool to see results here.";
|
|
545
|
+
}
|
|
546
|
+
const { score, summary } = this.lastResults;
|
|
547
|
+
const grade = this.calculateGrade(score);
|
|
548
|
+
return `# AIReady Summary
|
|
549
|
+
|
|
550
|
+
Project Score: **${score}/100 (${grade})**
|
|
551
|
+
Last Scan: ${this.lastScanTimestamp}
|
|
552
|
+
|
|
553
|
+
## Issue Breakdown
|
|
554
|
+
- Critical: ${summary.criticalIssues}
|
|
555
|
+
- Major: ${summary.majorIssues}
|
|
556
|
+
- Total Issues: ${summary.totalIssues}
|
|
557
|
+
- Files Analyzed: ${summary.totalFiles}
|
|
558
|
+
|
|
559
|
+
Run the \`aiready-mcp\` tool for a detailed analysis.`;
|
|
560
|
+
}
|
|
561
|
+
getIssuesJson() {
|
|
562
|
+
if (!this.lastResults) {
|
|
563
|
+
return JSON.stringify({
|
|
564
|
+
message: "No issues found. Please run a scan first."
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
const topIssues = this.lastResults.issues.slice(0, 10);
|
|
568
|
+
return JSON.stringify(topIssues, null, 2);
|
|
569
|
+
}
|
|
570
|
+
getGraphJson() {
|
|
571
|
+
if (!this.lastResults || !this.lastResults.metadata.graph) {
|
|
572
|
+
return JSON.stringify({
|
|
573
|
+
message: "Graph data not available. Run a scan with graph analysis enabled."
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
return JSON.stringify(this.lastResults.metadata.graph, null, 2);
|
|
577
|
+
}
|
|
578
|
+
getRoadmapMarkdown() {
|
|
579
|
+
if (!this.lastResults) {
|
|
580
|
+
return "# AIReady Roadmap\n\nNo scan has been run yet. Run an AIReady scan to generate a prioritized roadmap.";
|
|
581
|
+
}
|
|
582
|
+
const { score, issues } = this.lastResults;
|
|
583
|
+
const grade = this.calculateGrade(score);
|
|
584
|
+
let roadmap = `# AIReady Roadmap
|
|
585
|
+
|
|
586
|
+
`;
|
|
587
|
+
roadmap += `Current Score: **${score}/100 (${grade})**
|
|
588
|
+
|
|
589
|
+
`;
|
|
590
|
+
roadmap += `## Phase 1: High-Impact Fixes (Readiness Score 90+)
|
|
591
|
+
`;
|
|
592
|
+
const criticalIssues = issues.filter((i) => i.severity === "critical").slice(0, 3);
|
|
593
|
+
if (criticalIssues.length > 0) {
|
|
594
|
+
criticalIssues.forEach((i) => {
|
|
595
|
+
roadmap += ` - [ ] **Fix ${i.type}** in \`${i.location.file}:L${i.location.line}\`: ${i.message}
|
|
596
|
+
`;
|
|
597
|
+
});
|
|
598
|
+
} else {
|
|
599
|
+
roadmap += ` - \u2705 All critical issues resolved!
|
|
600
|
+
`;
|
|
601
|
+
}
|
|
602
|
+
roadmap += `
|
|
603
|
+
## Phase 2: Structural Optimization (Efficiency)
|
|
604
|
+
`;
|
|
605
|
+
const majorIssues = issues.filter((i) => i.severity === "major").slice(0, 3);
|
|
606
|
+
if (majorIssues.length > 0) {
|
|
607
|
+
majorIssues.forEach((i) => {
|
|
608
|
+
roadmap += ` - [ ] **Address ${i.type}**: ${i.message} (\`${i.location.file}\`)
|
|
609
|
+
`;
|
|
610
|
+
});
|
|
611
|
+
} else {
|
|
612
|
+
roadmap += ` - \u2705 All major structural issues resolved!
|
|
613
|
+
`;
|
|
614
|
+
}
|
|
615
|
+
roadmap += `
|
|
616
|
+
## Phase 3: Continuous AI Excellence
|
|
617
|
+
`;
|
|
618
|
+
roadmap += ` - [ ] Implement \`mcp-server\` in CI/CD pipeline.
|
|
619
|
+
`;
|
|
620
|
+
roadmap += ` - [ ] Achieve consistency score > 95 across all naming patterns.
|
|
621
|
+
`;
|
|
622
|
+
return roadmap;
|
|
623
|
+
}
|
|
624
|
+
calculateGrade(score) {
|
|
625
|
+
if (score >= 90) return "A";
|
|
626
|
+
if (score >= 80) return "B";
|
|
627
|
+
if (score >= 70) return "C";
|
|
628
|
+
if (score >= 60) return "D";
|
|
629
|
+
return "F";
|
|
630
|
+
}
|
|
631
|
+
};
|
|
632
|
+
var stateStore = new StateStore();
|
|
633
|
+
|
|
634
|
+
// src/index.ts
|
|
635
|
+
var AIReadyMcpServer = class {
|
|
636
|
+
constructor() {
|
|
637
|
+
this.version = "0.3.0";
|
|
638
|
+
this.server = new Server(
|
|
639
|
+
{
|
|
640
|
+
name: "aiready-server",
|
|
641
|
+
version: this.version
|
|
642
|
+
},
|
|
643
|
+
{
|
|
644
|
+
capabilities: {
|
|
645
|
+
tools: {},
|
|
646
|
+
resources: {},
|
|
647
|
+
prompts: {}
|
|
648
|
+
}
|
|
243
649
|
}
|
|
244
|
-
|
|
245
|
-
|
|
650
|
+
);
|
|
651
|
+
this.setupHandlers();
|
|
652
|
+
this.server.onerror = (error) => {
|
|
653
|
+
console.error("[MCP Error]", error);
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
setupHandlers() {
|
|
657
|
+
registerResourceHandlers(this.server, stateStore);
|
|
658
|
+
registerPromptHandlers(this.server);
|
|
246
659
|
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
247
|
-
const toolsToAdvertise = [
|
|
248
|
-
ToolName.PatternDetect,
|
|
249
|
-
ToolName.ContextAnalyzer,
|
|
250
|
-
ToolName.NamingConsistency,
|
|
251
|
-
ToolName.AiSignalClarity,
|
|
252
|
-
ToolName.AgentGrounding,
|
|
253
|
-
ToolName.TestabilityIndex,
|
|
254
|
-
ToolName.DocDrift,
|
|
255
|
-
ToolName.DependencyHealth,
|
|
256
|
-
ToolName.ChangeAmplification,
|
|
257
|
-
ToolName.ContractEnforcement
|
|
258
|
-
];
|
|
259
660
|
const tools = [
|
|
260
|
-
...
|
|
661
|
+
...ADVERTISED_TOOLS.map((id) => ({
|
|
261
662
|
name: id,
|
|
262
663
|
description: `Scan the directory for ${id} issues to improve AI-readiness.`,
|
|
263
664
|
inputSchema: {
|
|
@@ -304,61 +705,27 @@ ${data.rationale}`
|
|
|
304
705
|
const { name, arguments: args } = request.params;
|
|
305
706
|
try {
|
|
306
707
|
if (name === "get_remediation_diff") {
|
|
307
|
-
const
|
|
308
|
-
|
|
309
|
-
throw new Error(
|
|
310
|
-
`Invalid arguments for ${name}: ${parsedArgs2.error.message}`
|
|
311
|
-
);
|
|
312
|
-
}
|
|
313
|
-
return await this.handleRemediation(parsedArgs2.data);
|
|
708
|
+
const parsedArgs = RemediationArgsSchema.parse(args);
|
|
709
|
+
return await handleRemediation(parsedArgs);
|
|
314
710
|
}
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
`Invalid arguments for ${name}: ${parsedArgs.error.message}`
|
|
319
|
-
);
|
|
711
|
+
if (name === "get_best_practices") {
|
|
712
|
+
const parsedArgs = BestPracticesArgsSchema.parse(args);
|
|
713
|
+
return await handleGetBestPractices(parsedArgs);
|
|
320
714
|
}
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
const packageName = TOOL_PACKAGE_MAP[name] ?? (name.startsWith("@aiready/") ? name : `@aiready/${name}`);
|
|
325
|
-
try {
|
|
326
|
-
console.error(
|
|
327
|
-
`[MCP] Dynamically loading ${packageName} for tool ${name}`
|
|
328
|
-
);
|
|
329
|
-
await import(packageName);
|
|
330
|
-
provider = ToolRegistry.find(name);
|
|
331
|
-
} catch (importError) {
|
|
332
|
-
const importErrorMessage = importError instanceof Error ? importError.message : String(importError);
|
|
333
|
-
console.error(
|
|
334
|
-
`[MCP] Failed to load tool package ${packageName}: ${importErrorMessage}`
|
|
335
|
-
);
|
|
336
|
-
const error = new Error(
|
|
337
|
-
`Tool ${name} not found and failed to load package ${packageName}: ${importErrorMessage}`
|
|
338
|
-
);
|
|
339
|
-
error.cause = importError;
|
|
340
|
-
throw error;
|
|
341
|
-
}
|
|
715
|
+
if (name === "check_best_practice_compliance") {
|
|
716
|
+
const parsedArgs = ComplianceArgsSchema.parse(args);
|
|
717
|
+
return await handleCheckCompliance(parsedArgs);
|
|
342
718
|
}
|
|
343
|
-
if (
|
|
344
|
-
|
|
719
|
+
if (name === "analyze_context_budget") {
|
|
720
|
+
const parsedArgs = ContextBudgetArgsSchema.parse(args);
|
|
721
|
+
return await handleAnalyzeContextBudget(parsedArgs);
|
|
345
722
|
}
|
|
346
|
-
|
|
347
|
-
`[MCP] Executing ${name} on ${rootDir}${summary_only ? " (summary only)" : ""}`
|
|
348
|
-
);
|
|
349
|
-
const results = await provider.analyze({
|
|
350
|
-
rootDir
|
|
351
|
-
});
|
|
352
|
-
const responseData = summary_only ? {
|
|
353
|
-
summary: results.summary,
|
|
354
|
-
metadata: results.metadata,
|
|
355
|
-
notice: "Detailed issues were omitted (summary_only: true). Run without summary_only for full details."
|
|
356
|
-
} : results;
|
|
723
|
+
const results = await handleAnalysis(name, args, stateStore);
|
|
357
724
|
return {
|
|
358
725
|
content: [
|
|
359
726
|
{
|
|
360
727
|
type: "text",
|
|
361
|
-
text: JSON.stringify(
|
|
728
|
+
text: JSON.stringify(results, null, 2)
|
|
362
729
|
}
|
|
363
730
|
]
|
|
364
731
|
};
|