@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiready/mcp-server",
3
- "version": "0.6.1",
3
+ "version": "0.6.2",
4
4
  "description": "The AIReady Model Context Protocol (MCP) Server for Agentic Readiness. Optimize codebases for AI agents like Cursor, Windsurf, and Claude directly via MCP.",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -10,26 +10,26 @@
10
10
  "mcp-server": "./dist/index.js"
11
11
  },
12
12
  "dependencies": {
13
- "@modelcontextprotocol/sdk": "^1.0.0",
14
- "chalk": "^5.3.0",
13
+ "@modelcontextprotocol/sdk": "^1.29.0",
14
+ "chalk": "^5.6.2",
15
15
  "zod": "^4.3.6",
16
- "@aiready/agent-grounding": "0.14.16",
17
- "@aiready/ai-signal-clarity": "0.14.18",
18
- "@aiready/change-amplification": "0.14.16",
19
- "@aiready/consistency": "0.21.16",
20
- "@aiready/context-analyzer": "0.22.16",
21
- "@aiready/core": "0.24.19",
22
- "@aiready/deps": "0.14.16",
23
- "@aiready/doc-drift": "0.14.16",
24
- "@aiready/pattern-detect": "0.17.16",
16
+ "@aiready/agent-grounding": "0.14.17",
17
+ "@aiready/ai-signal-clarity": "0.14.19",
18
+ "@aiready/change-amplification": "0.14.17",
19
+ "@aiready/consistency": "0.21.17",
20
+ "@aiready/core": "0.24.20",
21
+ "@aiready/doc-drift": "0.14.17",
22
+ "@aiready/deps": "0.14.17",
23
+ "@aiready/context-analyzer": "0.22.17",
24
+ "@aiready/pattern-detect": "0.17.17",
25
25
  "@aiready/testability": "0.7.17"
26
26
  },
27
27
  "devDependencies": {
28
- "@types/node": "^24.0.0",
29
- "esbuild": "^0.27.3",
30
- "tsup": "^8.3.5",
31
- "typescript": "^5.9.3",
32
- "vitest": "^4.0.0"
28
+ "@types/node": "^24.12.2",
29
+ "esbuild": "^0.27.7",
30
+ "tsup": "^8.5.1",
31
+ "typescript": "^6.0.2",
32
+ "vitest": "^4.1.2"
33
33
  },
34
34
  "keywords": [
35
35
  "aiready",
@@ -57,6 +57,7 @@
57
57
  "type-check": "tsc --noEmit",
58
58
  "format-check": "prettier --check . --ignore-path ../../.prettierignore",
59
59
  "format": "prettier --write . --ignore-path ../../.prettierignore",
60
- "lint:fix": "eslint . --fix"
60
+ "lint:fix": "eslint . --fix",
61
+ "test:coverage": "vitest run --coverage"
61
62
  }
62
63
  }
@@ -0,0 +1,54 @@
1
+ import { describe, it, expect, vi } from 'vitest';
2
+ import { ToolName } from '@aiready/core';
3
+ import {
4
+ ADVERTISED_TOOLS,
5
+ TOOL_PACKAGE_MAP,
6
+ handleAnalysis,
7
+ } from '../tools/index.js';
8
+
9
+ describe('MCP Schema Sync Validation', () => {
10
+ it('every advertised tool should have a package mapping or a custom handler', () => {
11
+ // 1. Spoke tools that use handleAnalysis
12
+ const spokeTools = ADVERTISED_TOOLS.filter(
13
+ (t) =>
14
+ ![
15
+ 'get_best_practices',
16
+ 'check_best_practice_compliance',
17
+ 'analyze_context_budget',
18
+ ].includes(t)
19
+ );
20
+
21
+ for (const tool of spokeTools) {
22
+ expect(
23
+ TOOL_PACKAGE_MAP[tool],
24
+ `Tool "${tool}" is missing a mapping in TOOL_PACKAGE_MAP`
25
+ ).toBeDefined();
26
+ }
27
+ });
28
+
29
+ it('every package in TOOL_PACKAGE_MAP should be a valid @aiready package name', () => {
30
+ for (const [tool, pkg] of Object.entries(TOOL_PACKAGE_MAP)) {
31
+ expect(
32
+ pkg.startsWith('@aiready/'),
33
+ `Mapping for "${tool}" -> "${pkg}" must use a scoped @aiready package`
34
+ ).toBe(true);
35
+ }
36
+ });
37
+
38
+ it('handleAnalysis should correctly resolve and attempt to load packages from the map', async () => {
39
+ // Mock the dynamic import to verify it's called with the correct package
40
+ const importMock = vi.fn().mockRejectedValue(new Error('Stop here'));
41
+ vi.stubGlobal('import', importMock);
42
+
43
+ // Testing dynamic loading path
44
+ const toolName = ToolName.PatternDetect;
45
+ const expectedPackage = TOOL_PACKAGE_MAP[toolName];
46
+
47
+ try {
48
+ await handleAnalysis(toolName, { path: './test' });
49
+ } catch (e: any) {
50
+ // We expect it to fail at the import stage because of our mock
51
+ expect(e.message).toContain(`failed to load package ${expectedPackage}`);
52
+ }
53
+ });
54
+ });
@@ -142,7 +142,34 @@ describe('AIReady MCP Server Integration', () => {
142
142
  });
143
143
  expect(result.contents).toBeDefined();
144
144
  expect(result.contents[0].uri).toBe('aiready://project/summary');
145
- expect(result.contents[0].text).toContain('# AIReady Summary');
145
+ expect((result.contents[0] as any).text).toContain('# AIReady Summary');
146
+ });
147
+
148
+ it('should execute get_best_practices', async () => {
149
+ const result = await client.callTool({
150
+ name: 'get_best_practices',
151
+ arguments: { category: 'patterns' },
152
+ });
153
+ const typedResult = result as ToolCallResponse;
154
+ expect(typedResult.content[0].text).toContain('Pattern Detection');
155
+ });
156
+
157
+ it('should execute check_best_practice_compliance', async () => {
158
+ const result = await client.callTool({
159
+ name: 'check_best_practice_compliance',
160
+ arguments: { file_path: path.resolve(__dirname, '../index.ts') },
161
+ });
162
+ const typedResult = result as ToolCallResponse;
163
+ expect(typedResult.content[0].text).toContain('is compliant');
164
+ });
165
+
166
+ it('should execute analyze_context_budget', async () => {
167
+ const result = await client.callTool({
168
+ name: 'analyze_context_budget',
169
+ arguments: { file_path: path.resolve(__dirname, '../index.ts') },
170
+ });
171
+ const typedResult = result as ToolCallResponse;
172
+ expect(typedResult.content[0].text).toContain('Context Budget');
146
173
  });
147
174
 
148
175
  it('should list available prompts', async () => {
package/src/index.ts CHANGED
@@ -3,68 +3,29 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
3
3
  import {
4
4
  CallToolRequestSchema,
5
5
  ListToolsRequestSchema,
6
- ListResourcesRequestSchema,
7
- ReadResourceRequestSchema,
8
- ListPromptsRequestSchema,
9
- GetPromptRequestSchema,
10
6
  } from '@modelcontextprotocol/sdk/types.js';
11
- import { ToolRegistry, ToolName } from '@aiready/core';
12
- import { z } from 'zod';
13
-
14
- /**
15
- * Zod schemas for tool arguments
16
- */
17
- const AnalysisArgsSchema = z.object({
18
- path: z.string().describe('Path to the directory to analyze'),
19
- summary_only: z
20
- .boolean()
21
- .optional()
22
- .describe(
23
- 'If true, returns only the summary and skips the detailed issue list. Best for large projects to save context.'
24
- ),
25
- });
26
-
27
- const RemediationArgsSchema = z.object({
28
- issue_id: z.string().describe('The unique ID of the issue to fix'),
29
- file_path: z.string().describe('The path to the file containing the issue'),
30
- context: z.string().describe('The content of the file or surrounding code'),
31
- });
32
-
33
- /**
34
- * Mapping between tool names and @aiready/ package names.
35
- * Used for dynamic registration on-demand to minimize initial context budget.
36
- */
37
- const TOOL_PACKAGE_MAP: Record<string, string> = {
38
- [ToolName.PatternDetect]: '@aiready/pattern-detect',
39
- [ToolName.ContextAnalyzer]: '@aiready/context-analyzer',
40
- [ToolName.NamingConsistency]: '@aiready/consistency',
41
- [ToolName.AiSignalClarity]: '@aiready/ai-signal-clarity',
42
- [ToolName.AgentGrounding]: '@aiready/agent-grounding',
43
- [ToolName.TestabilityIndex]: '@aiready/testability',
44
- [ToolName.DocDrift]: '@aiready/doc-drift',
45
- [ToolName.DependencyHealth]: '@aiready/deps',
46
- [ToolName.ChangeAmplification]: '@aiready/change-amplification',
47
- [ToolName.ContractEnforcement]: '@aiready/contract-enforcement',
48
- // Aliases
49
- patterns: '@aiready/pattern-detect',
50
- duplicates: '@aiready/pattern-detect',
51
- context: '@aiready/context-analyzer',
52
- fragmentation: '@aiready/context-analyzer',
53
- consistency: '@aiready/consistency',
54
- 'ai-signal': '@aiready/ai-signal-clarity',
55
- grounding: '@aiready/agent-grounding',
56
- testability: '@aiready/testability',
57
- 'deps-health': '@aiready/deps',
58
- 'change-amp': '@aiready/change-amplification',
59
- 'contract-enforce': '@aiready/contract-enforcement',
60
- };
7
+ import {
8
+ handleAnalysis,
9
+ handleRemediation,
10
+ ADVERTISED_TOOLS,
11
+ RemediationArgsSchema,
12
+ BestPracticesArgsSchema,
13
+ handleGetBestPractices,
14
+ ComplianceArgsSchema,
15
+ handleCheckCompliance,
16
+ ContextBudgetArgsSchema,
17
+ handleAnalyzeContextBudget,
18
+ } from './tools/index.js';
19
+ import { registerResourceHandlers } from './resources/index.js';
20
+ import { registerPromptHandlers } from './prompts/index.js';
21
+ import { stateStore } from './state-store.js';
61
22
 
62
23
  /**
63
24
  * AIReady MCP Server Implementation
64
25
  */
65
26
  export class AIReadyMcpServer {
66
27
  private server: Server;
67
- private version: string = '0.2.10';
28
+ private version: string = '0.3.0';
68
29
 
69
30
  constructor() {
70
31
  this.server = new Server(
@@ -88,221 +49,17 @@ export class AIReadyMcpServer {
88
49
  };
89
50
  }
90
51
 
91
- private async handleRemediation(args: z.infer<typeof RemediationArgsSchema>) {
92
- const apiKey = process.env.AIREADY_API_KEY;
93
- const serverUrl =
94
- process.env.AIREADY_PLATFORM_URL || 'https://platform.getaiready.dev';
95
-
96
- if (!apiKey) {
97
- throw new Error(
98
- 'AIREADY_API_KEY is not set. Remediation requires an active subscription.'
99
- );
100
- }
101
-
102
- console.error(`[MCP] Requesting remediation for ${args.issue_id}...`);
103
-
104
- try {
105
- const response = await fetch(`${serverUrl}/api/v1/remediate`, {
106
- method: 'POST',
107
- headers: {
108
- 'Content-Type': 'application/json',
109
- 'X-API-KEY': apiKey,
110
- },
111
- body: JSON.stringify({
112
- issueId: args.issue_id,
113
- filePath: args.file_path,
114
- context: args.context,
115
- agent: 'mcp-server',
116
- }),
117
- });
118
-
119
- if (!response.ok) {
120
- const errorData = await response.json().catch(() => ({}));
121
- throw new Error(
122
- `Platform Error: ${errorData.message || response.statusText}`
123
- );
124
- }
125
-
126
- const data = await response.json();
127
-
128
- return {
129
- content: [
130
- {
131
- type: 'text',
132
- text: `Recommended Fix (Diff):\n\n${data.diff}\n\nRationale:\n${data.rationale}`,
133
- },
134
- ],
135
- };
136
- } catch (error: unknown) {
137
- const errorMessage =
138
- error instanceof Error ? error.message : String(error);
139
- return {
140
- content: [
141
- {
142
- type: 'text',
143
- text: `Failed to get remediation: ${errorMessage}. Please visit the dashboard to fix manually.`,
144
- },
145
- ],
146
- isError: true,
147
- };
148
- }
149
- }
150
-
151
52
  private setupHandlers() {
152
- // List available resources
153
- this.server.setRequestHandler(ListResourcesRequestSchema, async () => {
154
- return {
155
- resources: [
156
- {
157
- uri: 'aiready://project/summary',
158
- name: 'AIReady Project Summary',
159
- description: 'Quick top-level AI-readiness summary.',
160
- mimeType: 'text/markdown',
161
- },
162
- {
163
- uri: 'aiready://project/issues',
164
- name: 'AIReady Critical Issues',
165
- description: 'List of top 10 critical readiness issues.',
166
- mimeType: 'application/json',
167
- },
168
- {
169
- uri: 'aiready://project/graph',
170
- name: 'AIReady Codebase Graph',
171
- description: 'Force-directed graph data for visualization.',
172
- mimeType: 'application/json',
173
- },
174
- ],
175
- };
176
- });
53
+ // Register Resource Handlers
54
+ registerResourceHandlers(this.server, stateStore);
177
55
 
178
- // Read resource content
179
- this.server.setRequestHandler(
180
- ReadResourceRequestSchema,
181
- async (request) => {
182
- const { uri } = request.params;
183
-
184
- if (uri === 'aiready://project/summary') {
185
- return {
186
- contents: [
187
- {
188
- uri,
189
- mimeType: 'text/markdown',
190
- 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.',
191
- },
192
- ],
193
- };
194
- }
195
-
196
- if (
197
- uri === 'aiready://project/issues' ||
198
- uri === 'aiready://project/graph'
199
- ) {
200
- return {
201
- contents: [
202
- {
203
- uri,
204
- mimeType: 'application/json',
205
- text: JSON.stringify({
206
- message: 'Resource content coming from latest scan...',
207
- }),
208
- },
209
- ],
210
- };
211
- }
212
-
213
- throw new Error(`Resource not found: ${uri}`);
214
- }
215
- );
216
-
217
- // List available prompts
218
- this.server.setRequestHandler(ListPromptsRequestSchema, async () => {
219
- return {
220
- prompts: [
221
- {
222
- name: 'analyze-project',
223
- description:
224
- 'Audit the project for AI-readiness and suggest improvements.',
225
- arguments: [
226
- {
227
- name: 'path',
228
- description: 'Path/directory to analyze',
229
- required: true,
230
- },
231
- ],
232
- },
233
- {
234
- name: 'remediate-issue',
235
- description: 'Help the user fix a specific AIReady issue.',
236
- arguments: [
237
- {
238
- name: 'issueId',
239
- description: 'The unique ID of the issue to fix',
240
- required: true,
241
- },
242
- ],
243
- },
244
- ],
245
- };
246
- });
247
-
248
- // Get prompt content
249
- this.server.setRequestHandler(GetPromptRequestSchema, async (request) => {
250
- const { name, arguments: args } = request.params;
251
-
252
- if (name === 'analyze-project') {
253
- const path = args?.path || '.';
254
- return {
255
- description: 'Project audit instructions',
256
- messages: [
257
- {
258
- role: 'user',
259
- content: {
260
- type: 'text',
261
- 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.`,
262
- },
263
- },
264
- ],
265
- };
266
- }
267
-
268
- if (name === 'remediate-issue') {
269
- const issueId = args?.issueId;
270
- return {
271
- description: 'Issue remediation instructions',
272
- messages: [
273
- {
274
- role: 'user',
275
- content: {
276
- type: 'text',
277
- 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.`,
278
- },
279
- },
280
- ],
281
- };
282
- }
283
-
284
- throw new Error(`Prompt not found: ${name}`);
285
- });
56
+ // Register Prompt Handlers
57
+ registerPromptHandlers(this.server);
286
58
 
287
59
  // List available tools
288
60
  this.server.setRequestHandler(ListToolsRequestSchema, async () => {
289
- // Define canonical tool names to advertise to the client
290
- // These will be dynamically loaded on demand
291
- const toolsToAdvertise = [
292
- ToolName.PatternDetect,
293
- ToolName.ContextAnalyzer,
294
- ToolName.NamingConsistency,
295
- ToolName.AiSignalClarity,
296
- ToolName.AgentGrounding,
297
- ToolName.TestabilityIndex,
298
- ToolName.DocDrift,
299
- ToolName.DependencyHealth,
300
- ToolName.ChangeAmplification,
301
- ToolName.ContractEnforcement,
302
- ];
303
-
304
61
  const tools: any[] = [
305
- ...toolsToAdvertise.map((id) => ({
62
+ ...ADVERTISED_TOOLS.map((id) => ({
306
63
  name: id,
307
64
  description: `Scan the directory for ${id} issues to improve AI-readiness.`,
308
65
  inputSchema: {
@@ -355,82 +112,32 @@ export class AIReadyMcpServer {
355
112
 
356
113
  try {
357
114
  if (name === 'get_remediation_diff') {
358
- const parsedArgs = RemediationArgsSchema.safeParse(args);
359
- if (!parsedArgs.success) {
360
- throw new Error(
361
- `Invalid arguments for ${name}: ${parsedArgs.error.message}`
362
- );
363
- }
364
- return await this.handleRemediation(parsedArgs.data);
115
+ const parsedArgs = RemediationArgsSchema.parse(args);
116
+ return await handleRemediation(parsedArgs);
365
117
  }
366
118
 
367
- const parsedArgs = AnalysisArgsSchema.safeParse(args);
368
- if (!parsedArgs.success) {
369
- throw new Error(
370
- `Invalid arguments for ${name}: ${parsedArgs.error.message}`
371
- );
119
+ if (name === 'get_best_practices') {
120
+ const parsedArgs = BestPracticesArgsSchema.parse(args);
121
+ return await handleGetBestPractices(parsedArgs);
372
122
  }
373
- const { path: rootDir, summary_only } = parsedArgs.data;
374
123
 
375
- let provider = ToolRegistry.find(name);
376
-
377
- // Dynamic loading if not already registered (CLI pattern)
378
- if (!provider) {
379
- const packageName =
380
- TOOL_PACKAGE_MAP[name] ??
381
- (name.startsWith('@aiready/') ? name : `@aiready/${name}`);
382
-
383
- try {
384
- console.error(
385
- `[MCP] Dynamically loading ${packageName} for tool ${name}`
386
- );
387
- await import(packageName);
388
- provider = ToolRegistry.find(name);
389
- } catch (importError: unknown) {
390
- const importErrorMessage =
391
- importError instanceof Error
392
- ? importError.message
393
- : String(importError);
394
- console.error(
395
- `[MCP] Failed to load tool package ${packageName}: ${importErrorMessage}`
396
- );
397
- const error = new Error(
398
- `Tool ${name} not found and failed to load package ${packageName}: ${importErrorMessage}`
399
- );
400
- (error as { cause?: unknown }).cause = importError;
401
- throw error;
402
- }
124
+ if (name === 'check_best_practice_compliance') {
125
+ const parsedArgs = ComplianceArgsSchema.parse(args);
126
+ return await handleCheckCompliance(parsedArgs);
403
127
  }
404
128
 
405
- if (!provider) {
406
- throw new Error(`Tool ${name} not found after attempting to load`);
129
+ if (name === 'analyze_context_budget') {
130
+ const parsedArgs = ContextBudgetArgsSchema.parse(args);
131
+ return await handleAnalyzeContextBudget(parsedArgs);
407
132
  }
408
133
 
409
- console.error(
410
- `[MCP] Executing ${name} on ${rootDir}${
411
- summary_only ? ' (summary only)' : ''
412
- }`
413
- );
414
-
415
- const results = await provider.analyze({
416
- rootDir,
417
- });
418
-
419
- // Format results for the agent
420
- const responseData = summary_only
421
- ? {
422
- summary: results.summary,
423
- metadata: results.metadata,
424
- notice:
425
- 'Detailed issues were omitted (summary_only: true). Run without summary_only for full details.',
426
- }
427
- : results;
134
+ const results = await handleAnalysis(name, args, stateStore);
428
135
 
429
136
  return {
430
137
  content: [
431
138
  {
432
139
  type: 'text',
433
- text: JSON.stringify(responseData, null, 2),
140
+ text: JSON.stringify(results, null, 2),
434
141
  },
435
142
  ],
436
143
  };
@@ -0,0 +1,76 @@
1
+ import {
2
+ ListPromptsRequestSchema,
3
+ GetPromptRequestSchema,
4
+ } from '@modelcontextprotocol/sdk/types.js';
5
+
6
+ export function registerPromptHandlers(server: any) {
7
+ // List available prompts
8
+ server.setRequestHandler(ListPromptsRequestSchema, async () => {
9
+ return {
10
+ prompts: [
11
+ {
12
+ name: 'analyze-project',
13
+ description:
14
+ 'Audit the project for AI-readiness and suggest improvements.',
15
+ arguments: [
16
+ {
17
+ name: 'path',
18
+ description: 'Path/directory to analyze',
19
+ required: true,
20
+ },
21
+ ],
22
+ },
23
+ {
24
+ name: 'remediate-issue',
25
+ description: 'Help the user fix a specific AIReady issue.',
26
+ arguments: [
27
+ {
28
+ name: 'issueId',
29
+ description: 'The unique ID of the issue to fix',
30
+ required: true,
31
+ },
32
+ ],
33
+ },
34
+ ],
35
+ };
36
+ });
37
+
38
+ // Get prompt content
39
+ server.setRequestHandler(GetPromptRequestSchema, async (request: any) => {
40
+ const { name, arguments: args } = request.params;
41
+
42
+ if (name === 'analyze-project') {
43
+ const path = args?.path || '.';
44
+ return {
45
+ description: 'Project audit instructions',
46
+ messages: [
47
+ {
48
+ role: 'user',
49
+ content: {
50
+ type: 'text',
51
+ 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.`,
52
+ },
53
+ },
54
+ ],
55
+ };
56
+ }
57
+
58
+ if (name === 'remediate-issue') {
59
+ const issueId = args?.issueId;
60
+ return {
61
+ description: 'Issue remediation instructions',
62
+ messages: [
63
+ {
64
+ role: 'user',
65
+ content: {
66
+ type: 'text',
67
+ 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.`,
68
+ },
69
+ },
70
+ ],
71
+ };
72
+ }
73
+
74
+ throw new Error(`Prompt not found: ${name}`);
75
+ });
76
+ }