dura-mcp 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.
- package/LICENSE +21 -0
- package/README.md +374 -0
- package/handlers/breakingChange.js +58 -0
- package/handlers/fullAnalysis.js +39 -0
- package/handlers/highRiskFilter.js +62 -0
- package/handlers/riskSummary.js +54 -0
- package/package.json +47 -0
- package/server.js +176 -0
- package/utils/cache.js +3 -0
- package/utils/helper.js +265 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Archie Tansaria
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
# DURA MCP Server
|
|
2
|
+
|
|
3
|
+
MCP (Model Context Protocol) server for [DURA](https://github.com/ArchieTansaria/dura) - Dependency Update Risk Analyzer.
|
|
4
|
+
|
|
5
|
+
Integrates DURA's dependency analysis capabilities with AI assistants like Cline, Claude Desktop, and any MCP-compatible client.
|
|
6
|
+
|
|
7
|
+
## Quick Start
|
|
8
|
+
|
|
9
|
+
### Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install -g dura-mcp
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
This automatically makes `dura-mcp` available globally. The server will use `dura-kit` via npx when needed.
|
|
16
|
+
|
|
17
|
+
### Usage with Cline (VS Code)
|
|
18
|
+
|
|
19
|
+
1. **Install the MCP server:**
|
|
20
|
+
```bash
|
|
21
|
+
npm install -g dura-mcp
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
2. **Configure Cline:**
|
|
25
|
+
|
|
26
|
+
Open VS Code Settings (JSON):
|
|
27
|
+
- Press `Cmd+Shift+P` (Mac) or `Ctrl+Shift+P` (Windows/Linux)
|
|
28
|
+
- Type: "Preferences: Open User Settings (JSON)"
|
|
29
|
+
- Add the following:
|
|
30
|
+
|
|
31
|
+
```json
|
|
32
|
+
{
|
|
33
|
+
"cline.mcpServers": {
|
|
34
|
+
"dura": {
|
|
35
|
+
"command": "dura-mcp"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
3. **Restart VS Code**
|
|
42
|
+
|
|
43
|
+
4. **Use it with Cline:**
|
|
44
|
+
|
|
45
|
+
Open Cline and ask natural questions:
|
|
46
|
+
- "Analyze dependencies for https://github.com/expressjs/express"
|
|
47
|
+
- "What are the high-risk dependencies in React?"
|
|
48
|
+
- "Show me breaking changes in Next.js"
|
|
49
|
+
- "Is it safe to update my dependencies?"
|
|
50
|
+
|
|
51
|
+
### Usage with Claude Desktop
|
|
52
|
+
|
|
53
|
+
Add to your Claude Desktop configuration file:
|
|
54
|
+
|
|
55
|
+
**macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
56
|
+
|
|
57
|
+
**Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
|
|
58
|
+
|
|
59
|
+
```json
|
|
60
|
+
{
|
|
61
|
+
"mcpServers": {
|
|
62
|
+
"dura": {
|
|
63
|
+
"command": "dura-mcp"
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Restart Claude Desktop and start asking about dependencies!
|
|
70
|
+
|
|
71
|
+
## Available Tools
|
|
72
|
+
|
|
73
|
+
The DURA MCP server provides four specialized tools that AI assistants can use:
|
|
74
|
+
|
|
75
|
+
### 1. `analyze_repository`
|
|
76
|
+
|
|
77
|
+
Complete dependency analysis with detailed risk assessment and actionable recommendations.
|
|
78
|
+
|
|
79
|
+
**Parameters:**
|
|
80
|
+
- `repoUrl` (required): GitHub repository URL (e.g., `https://github.com/facebook/react`)
|
|
81
|
+
- `branch` (optional): Git branch to analyze (default: `main`)
|
|
82
|
+
- `useCache` (optional): Use cached results if available (default: `true`)
|
|
83
|
+
|
|
84
|
+
**Returns:**
|
|
85
|
+
- Health score and risk summary
|
|
86
|
+
- Critical issues requiring attention
|
|
87
|
+
- Breaking changes with evidence
|
|
88
|
+
- Prioritized action items
|
|
89
|
+
- Suggested update order
|
|
90
|
+
|
|
91
|
+
**Example:**
|
|
92
|
+
```
|
|
93
|
+
User: "Analyze dependencies for https://github.com/expressjs/express"
|
|
94
|
+
|
|
95
|
+
AI receives:
|
|
96
|
+
- Health Score: 68/100
|
|
97
|
+
- 3 high-risk dependencies
|
|
98
|
+
- 2 breaking changes
|
|
99
|
+
- Specific actions to take
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### 2. `get_high_risk_dependencies`
|
|
103
|
+
|
|
104
|
+
Get only dependencies with high risk that need immediate attention.
|
|
105
|
+
|
|
106
|
+
**Parameters:**
|
|
107
|
+
- `repoUrl` (required): GitHub repository URL
|
|
108
|
+
- `branch` (optional): Git branch (default: `main`)
|
|
109
|
+
|
|
110
|
+
**Returns:**
|
|
111
|
+
- List of high-risk dependencies only
|
|
112
|
+
- Why each is high-risk
|
|
113
|
+
- Specific recommendations for each
|
|
114
|
+
- Update priority guidance
|
|
115
|
+
|
|
116
|
+
**Example:**
|
|
117
|
+
```
|
|
118
|
+
User: "What are the risky dependencies in React?"
|
|
119
|
+
|
|
120
|
+
AI gets only the critical dependencies that need review.
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### 3. `get_breaking_changes`
|
|
124
|
+
|
|
125
|
+
Get only dependencies with confirmed breaking changes.
|
|
126
|
+
|
|
127
|
+
**Parameters:**
|
|
128
|
+
- `repoUrl` (required): GitHub repository URL
|
|
129
|
+
- `branch` (optional): Git branch (default: `main`)
|
|
130
|
+
|
|
131
|
+
**Returns:**
|
|
132
|
+
- Dependencies with confirmed breaking changes
|
|
133
|
+
- Confidence scores
|
|
134
|
+
- Evidence from release notes
|
|
135
|
+
- Migration planning guidance
|
|
136
|
+
|
|
137
|
+
**Example:**
|
|
138
|
+
```
|
|
139
|
+
User: "Show me breaking changes in Next.js"
|
|
140
|
+
|
|
141
|
+
AI gets specific breaking changes and how to handle them.
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### 4. `get_risk_summary`
|
|
145
|
+
|
|
146
|
+
Quick health check and risk overview without detailed data.
|
|
147
|
+
|
|
148
|
+
**Parameters:**
|
|
149
|
+
- `repoUrl` (required): GitHub repository URL
|
|
150
|
+
- `branch` (optional): Git branch (default: `main`)
|
|
151
|
+
|
|
152
|
+
**Returns:**
|
|
153
|
+
- Health score (0-100)
|
|
154
|
+
- Risk distribution (high/medium/low)
|
|
155
|
+
- Quick status assessment
|
|
156
|
+
- Overall recommendation
|
|
157
|
+
|
|
158
|
+
**Example:**
|
|
159
|
+
```
|
|
160
|
+
User: "Is my project safe to update?"
|
|
161
|
+
|
|
162
|
+
AI gets instant health score and quick answer.
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Features
|
|
166
|
+
|
|
167
|
+
- **Smart Caching**: Analysis results cached for 1 hour to speed up repeated queries
|
|
168
|
+
- **Actionable Output**: Prioritized recommendations, not just data dumps
|
|
169
|
+
- **Breaking Change Detection**: Scrapes GitHub releases to find confirmed breaking changes
|
|
170
|
+
- **Risk Scoring**: Intelligent risk assessment based on version changes and breaking signals
|
|
171
|
+
- **Multiple Tools**: Specialized tools for different use cases (overview, deep-dive, specific filters)
|
|
172
|
+
|
|
173
|
+
## Usage Examples
|
|
174
|
+
|
|
175
|
+
### Quick Health Check
|
|
176
|
+
|
|
177
|
+
```
|
|
178
|
+
User: "Is express safe to update?"
|
|
179
|
+
|
|
180
|
+
AI: [calls get_risk_summary]
|
|
181
|
+
"Express has a health score of 72/100. There are 3 high-risk
|
|
182
|
+
dependencies that need attention. Would you like details?"
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Detailed Analysis
|
|
186
|
+
|
|
187
|
+
```
|
|
188
|
+
User: "Analyze all dependencies in my project"
|
|
189
|
+
|
|
190
|
+
AI: [calls analyze_repository]
|
|
191
|
+
"I found 2 critical issues:
|
|
192
|
+
1. eslint (breaking changes confirmed)
|
|
193
|
+
2. webpack (major version jump)
|
|
194
|
+
|
|
195
|
+
Here's the recommended update order..."
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### Focus on Critical Issues
|
|
199
|
+
|
|
200
|
+
```
|
|
201
|
+
User: "What breaking changes do I need to worry about?"
|
|
202
|
+
|
|
203
|
+
AI: [calls get_breaking_changes]
|
|
204
|
+
"Found 2 dependencies with breaking changes:
|
|
205
|
+
- eslint: Config syntax changed in v9
|
|
206
|
+
- react: New JSX transform required
|
|
207
|
+
|
|
208
|
+
I recommend updating eslint first..."
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### Repository Comparison
|
|
212
|
+
|
|
213
|
+
```
|
|
214
|
+
User: "Compare dependency health of express vs fastify"
|
|
215
|
+
|
|
216
|
+
AI: [calls get_risk_summary for both]
|
|
217
|
+
"Express: Health score 72/100, 3 high-risk deps
|
|
218
|
+
Fastify: Health score 89/100, 0 high-risk deps
|
|
219
|
+
|
|
220
|
+
Fastify is in better shape for updates."
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
## How It Works
|
|
224
|
+
|
|
225
|
+
1. **AI Assistant** receives a question about dependencies
|
|
226
|
+
2. **MCP Server** provides tools the AI can call
|
|
227
|
+
3. **AI chooses** the appropriate tool (full analysis, high-risk filter, etc.)
|
|
228
|
+
4. **MCP Server** runs `dura-kit` CLI to analyze the repository
|
|
229
|
+
5. **Results** are formatted and returned to the AI
|
|
230
|
+
6. **AI presents** findings in natural language to the user
|
|
231
|
+
|
|
232
|
+
The MCP server acts as a bridge between AI assistants and DURA's analysis engine.
|
|
233
|
+
|
|
234
|
+
## Caching Behavior
|
|
235
|
+
|
|
236
|
+
Analysis results are cached for 1 hour to improve performance:
|
|
237
|
+
|
|
238
|
+
- First query: Fetches fresh data (5-10 seconds)
|
|
239
|
+
- Subsequent queries: Returns cached data instantly
|
|
240
|
+
- Cache expires: After 1 hour, fresh data fetched automatically
|
|
241
|
+
- Force refresh: Set `useCache: false` in parameters
|
|
242
|
+
|
|
243
|
+
Cache is shared across all tools, so analyzing a repository once makes all other tools fast.
|
|
244
|
+
|
|
245
|
+
## Requirements
|
|
246
|
+
|
|
247
|
+
- **Node.js**: 18.0.0 or higher
|
|
248
|
+
- **Internet**: Required for GitHub and npm API access
|
|
249
|
+
- **Public Repositories**: Currently only supports public GitHub repositories
|
|
250
|
+
|
|
251
|
+
## Troubleshooting
|
|
252
|
+
|
|
253
|
+
### Command not found: dura-mcp
|
|
254
|
+
|
|
255
|
+
**Solution:** Ensure the package is installed globally:
|
|
256
|
+
```bash
|
|
257
|
+
npm install -g dura-mcp
|
|
258
|
+
which dura-mcp # Should show installation path
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### Cline doesn't see the MCP server
|
|
262
|
+
|
|
263
|
+
**Solutions:**
|
|
264
|
+
1. Restart VS Code completely (close all windows)
|
|
265
|
+
2. Verify settings are saved correctly
|
|
266
|
+
3. Check for errors in VS Code Developer Tools: `Help → Toggle Developer Tools → Console`
|
|
267
|
+
|
|
268
|
+
### MCP server not connecting
|
|
269
|
+
|
|
270
|
+
**Solutions:**
|
|
271
|
+
1. Test the server manually: Run `dura-mcp` in terminal - it should say "Server running"
|
|
272
|
+
2. Check Node.js version: `node --version` (must be 18+)
|
|
273
|
+
3. Reinstall if needed: `npm uninstall -g dura-mcp && npm install -g dura-mcp`
|
|
274
|
+
|
|
275
|
+
### Analysis fails or times out
|
|
276
|
+
|
|
277
|
+
**Common causes:**
|
|
278
|
+
- Invalid or private repository URL
|
|
279
|
+
- Network connectivity issues
|
|
280
|
+
- Repository doesn't contain package.json
|
|
281
|
+
|
|
282
|
+
**Solutions:**
|
|
283
|
+
1. Verify repository is public: Visit the URL in a browser
|
|
284
|
+
2. Check internet connection
|
|
285
|
+
3. Test with DURA CLI directly: `npx dura-kit <repo-url>`
|
|
286
|
+
4. Check repository has package.json in the root
|
|
287
|
+
|
|
288
|
+
### "Repository not found" error
|
|
289
|
+
|
|
290
|
+
**Solutions:**
|
|
291
|
+
- Ensure URL format is correct: `https://github.com/owner/repo`
|
|
292
|
+
- Repository must be public (private repos not supported)
|
|
293
|
+
- Check repository actually exists
|
|
294
|
+
|
|
295
|
+
### Rate limiting
|
|
296
|
+
|
|
297
|
+
GitHub API rate limits may affect analysis:
|
|
298
|
+
- Unauthenticated: 60 requests/hour
|
|
299
|
+
- Authenticated: 5000 requests/hour (future feature)
|
|
300
|
+
|
|
301
|
+
**Solution:** Wait a few minutes before retrying.
|
|
302
|
+
|
|
303
|
+
## Development
|
|
304
|
+
|
|
305
|
+
### Running from Source
|
|
306
|
+
|
|
307
|
+
```bash
|
|
308
|
+
git clone https://github.com/ArchieTansaria/dura.git
|
|
309
|
+
cd dura/mcp
|
|
310
|
+
npm install
|
|
311
|
+
node server.js
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
The server will start and wait for MCP protocol messages on stdin.
|
|
315
|
+
|
|
316
|
+
### Testing
|
|
317
|
+
|
|
318
|
+
Use the MCP Inspector to test the server:
|
|
319
|
+
|
|
320
|
+
```bash
|
|
321
|
+
npm install -g @modelcontextprotocol/inspector
|
|
322
|
+
npx @modelcontextprotocol/inspector node server.js
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
Then test each tool in the web interface.
|
|
326
|
+
|
|
327
|
+
### Local Development with Cline
|
|
328
|
+
|
|
329
|
+
```bash
|
|
330
|
+
cd dura/mcp
|
|
331
|
+
npm link
|
|
332
|
+
|
|
333
|
+
# Add to VS Code settings:
|
|
334
|
+
{
|
|
335
|
+
"cline.mcpServers": {
|
|
336
|
+
"dura": {
|
|
337
|
+
"command": "dura-mcp"
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
## Links
|
|
344
|
+
|
|
345
|
+
- **DURA CLI**: [dura-kit on npm](https://www.npmjs.com/package/dura-kit)
|
|
346
|
+
- **GitHub Repository**: [ArchieTansaria/dura](https://github.com/ArchieTansaria/dura)
|
|
347
|
+
- **MCP Documentation**: [Model Context Protocol](https://modelcontextprotocol.io)
|
|
348
|
+
- **Report Issues**: [GitHub Issues](https://github.com/ArchieTansaria/dura/issues)
|
|
349
|
+
|
|
350
|
+
## Related Packages
|
|
351
|
+
|
|
352
|
+
- **dura-kit** - The core CLI tool for dependency analysis
|
|
353
|
+
```bash
|
|
354
|
+
npm install -g dura-kit
|
|
355
|
+
dura https://github.com/expressjs/express
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
## Contributing
|
|
359
|
+
|
|
360
|
+
Contributions welcome! See the [main repository](https://github.com/ArchieTansaria/dura) for contribution guidelines.
|
|
361
|
+
|
|
362
|
+
## License
|
|
363
|
+
|
|
364
|
+
MIT License - see [LICENSE](https://github.com/ArchieTansaria/dura/blob/main/LICENSE) file for details.
|
|
365
|
+
|
|
366
|
+
## Support
|
|
367
|
+
|
|
368
|
+
- Documentation: [GitHub Repository](https://github.com/ArchieTansaria/dura)
|
|
369
|
+
- Issues: [GitHub Issues](https://github.com/ArchieTansaria/dura/issues)
|
|
370
|
+
- Discussions: [GitHub Discussions](https://github.com/ArchieTansaria/dura/discussions)
|
|
371
|
+
|
|
372
|
+
---
|
|
373
|
+
|
|
374
|
+
**Built with Model Context Protocol for seamless AI integration**
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { getOrFetchAnalysis } from "../utils/helper.js";
|
|
2
|
+
|
|
3
|
+
export async function handleBreakingChanges(args) {
|
|
4
|
+
const { repoUrl, branch = "main" } = args;
|
|
5
|
+
|
|
6
|
+
console.error(`[Breaking Changes] ${repoUrl}`);
|
|
7
|
+
|
|
8
|
+
const result = await getOrFetchAnalysis(repoUrl, branch);
|
|
9
|
+
const dependencies = result.dependencies || result;
|
|
10
|
+
const breaking = dependencies.filter(
|
|
11
|
+
(d) =>
|
|
12
|
+
d.breakingChange?.breaking === "confirmed" ||
|
|
13
|
+
d.breakingChange?.breaking === "likely"
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
if (breaking.length === 0) {
|
|
17
|
+
return {
|
|
18
|
+
content: [
|
|
19
|
+
{
|
|
20
|
+
type: "text",
|
|
21
|
+
text: "No confirmed breaking changes detected in dependency updates.",
|
|
22
|
+
},
|
|
23
|
+
],
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let output = `# Breaking Changes Detected (${breaking.length})\n\n`;
|
|
28
|
+
|
|
29
|
+
breaking.forEach((dep, idx) => {
|
|
30
|
+
output += `${idx + 1}. **${dep.name}** (${dep.type})\n`;
|
|
31
|
+
output += ` - Current: ${dep.currentResolved}\n`;
|
|
32
|
+
output += ` - Latest: ${dep.latest}\n`;
|
|
33
|
+
output += ` - Confidence: ${(dep.breakingChange.confidenceScore * 100).toFixed(0)}%\n`;
|
|
34
|
+
|
|
35
|
+
if (dep.breakingChange.signals?.strong?.length > 0) {
|
|
36
|
+
output += ` - Evidence: "${dep.breakingChange.signals.strong[0].substring(0, 100)}..."\n`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
output += ` - GitHub: ${dep.githubRepoUrl}\n`;
|
|
40
|
+
output += `\n`;
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
output += `\n## Migration Planning\n\n`;
|
|
44
|
+
output += `These dependencies have confirmed breaking changes. Before updating:\n\n`;
|
|
45
|
+
output += `1. Read the migration guide for each dependency\n`;
|
|
46
|
+
output += `2. Identify affected code in your project\n`;
|
|
47
|
+
output += `3. Plan incremental updates\n`;
|
|
48
|
+
output += `4. Allocate time for testing and fixes\n`;
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
content: [
|
|
52
|
+
{
|
|
53
|
+
type: "text",
|
|
54
|
+
text: output,
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
};
|
|
58
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { getCachedResult, setCachedResult, formatFullAnalysis } from "../utils/helper.js";
|
|
2
|
+
import { analyzeRepository } from "dura-kit";
|
|
3
|
+
|
|
4
|
+
export async function handleFullAnalysis(args) {
|
|
5
|
+
const { repoUrl, branch = "main", useCache = true } = args;
|
|
6
|
+
|
|
7
|
+
console.error(`[Full Analysis] ${repoUrl} (${branch})`);
|
|
8
|
+
|
|
9
|
+
// check cache
|
|
10
|
+
if (useCache) {
|
|
11
|
+
const cached = getCachedResult(repoUrl, branch);
|
|
12
|
+
if (cached) {
|
|
13
|
+
console.error("Using cached result");
|
|
14
|
+
return {
|
|
15
|
+
content: [
|
|
16
|
+
{
|
|
17
|
+
type: "text",
|
|
18
|
+
text: formatFullAnalysis(cached, true)
|
|
19
|
+
},
|
|
20
|
+
],
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Fetch fresh data
|
|
26
|
+
const result = await analyzeRepository({ repoUrl, branch });
|
|
27
|
+
|
|
28
|
+
// Cache result
|
|
29
|
+
setCachedResult(repoUrl, branch, result);
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
content: [
|
|
33
|
+
{
|
|
34
|
+
type: "text",
|
|
35
|
+
text: formatFullAnalysis(result, false),
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
};
|
|
39
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { getOrFetchAnalysis } from "../utils/helper.js";
|
|
2
|
+
|
|
3
|
+
export async function handleHighRiskFilter(args) {
|
|
4
|
+
const { repoUrl, branch = "main" } = args;
|
|
5
|
+
|
|
6
|
+
console.error(`[High Risk Filter] ${repoUrl}`);
|
|
7
|
+
|
|
8
|
+
const result = await getOrFetchAnalysis(repoUrl, branch);
|
|
9
|
+
const dependencies = result.dependencies || result;
|
|
10
|
+
const highRisk = dependencies.filter((d) => d.riskLevel === "high");
|
|
11
|
+
|
|
12
|
+
if (highRisk.length === 0) {
|
|
13
|
+
return {
|
|
14
|
+
content: [
|
|
15
|
+
{
|
|
16
|
+
type: "text",
|
|
17
|
+
text: "No high-risk dependencies found! All dependencies are safe to update.",
|
|
18
|
+
},
|
|
19
|
+
],
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
let output = `# High-Risk Dependencies (${highRisk.length})\n\n`;
|
|
24
|
+
if (highRisk.length > 1) {
|
|
25
|
+
output += `Found ${highRisk.length} dependencies that require careful review:\n\n`;
|
|
26
|
+
} else {
|
|
27
|
+
output += `Found ${highRisk.length} dependency that requires careful review:\n\n`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
highRisk.forEach((dep, idx) => {
|
|
31
|
+
output += `${idx + 1}. **${dep.name}** (${dep.type})\n`;
|
|
32
|
+
output += ` - Current: ${dep.currentResolved}\n`;
|
|
33
|
+
output += ` - Latest: ${dep.latest}\n`;
|
|
34
|
+
output += ` - Update Type: ${dep.diff}\n`;
|
|
35
|
+
output += ` - Risk Score: ${dep.riskScore}/100\n`;
|
|
36
|
+
if (dep.breakingChange?.breaking === "confirmed") {
|
|
37
|
+
output += ` - Breaking changes confirmed\n`;
|
|
38
|
+
}
|
|
39
|
+
output += `\n`;
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
output += `\n## Recommendations\n\n`;
|
|
43
|
+
if (highRisk.length > 1){
|
|
44
|
+
output += `1. Review migration guides for each dependency\n`;
|
|
45
|
+
output += `2. Test updates in a staging environment\n`;
|
|
46
|
+
output += `3. Update one dependency at a time\n`;
|
|
47
|
+
output += `4. Check for related dependencies that may also need updates\n`;
|
|
48
|
+
} else {
|
|
49
|
+
output += `1. Review migration guides for this dependency\n`;
|
|
50
|
+
output += `2. Test updates in a staging environment\n`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
content: [
|
|
56
|
+
{
|
|
57
|
+
type: "text",
|
|
58
|
+
text: output,
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
};
|
|
62
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { getOrFetchAnalysis, calculateHealthScore } from "../utils/helper.js";
|
|
2
|
+
|
|
3
|
+
export async function handleRiskSummary(args) {
|
|
4
|
+
const { repoUrl, branch = "main" } = args;
|
|
5
|
+
|
|
6
|
+
console.error(`[Risk Summary] ${repoUrl}`);
|
|
7
|
+
|
|
8
|
+
const result = await getOrFetchAnalysis(repoUrl, branch);
|
|
9
|
+
const dependencies = result.dependencies || result;
|
|
10
|
+
|
|
11
|
+
const high = dependencies.filter((d) => d.riskLevel === "high").length;
|
|
12
|
+
const medium = dependencies.filter((d) => d.riskLevel === "medium").length;
|
|
13
|
+
const low = dependencies.filter((d) => d.riskLevel === "low").length;
|
|
14
|
+
const breaking = dependencies.filter(
|
|
15
|
+
(d) => d.breakingChange?.breaking === "confirmed"
|
|
16
|
+
).length;
|
|
17
|
+
|
|
18
|
+
let output = `# Dependency Health Summary\n\n`;
|
|
19
|
+
output += `**Repository**: ${repoUrl}\n`;
|
|
20
|
+
output += `**Branch**: ${branch}\n`;
|
|
21
|
+
output += `**Total Dependencies**: ${dependencies.length}\n\n`;
|
|
22
|
+
|
|
23
|
+
output += `## Risk Distribution\n\n`;
|
|
24
|
+
output += `- High Risk: ${high}\n`;
|
|
25
|
+
output += `- Medium Risk: ${medium}\n`;
|
|
26
|
+
output += `- Low Risk: ${low}\n`;
|
|
27
|
+
if (breaking > 0) {
|
|
28
|
+
output += `- Breaking Changes: ${breaking}\n`;
|
|
29
|
+
}
|
|
30
|
+
output += `\n`;
|
|
31
|
+
|
|
32
|
+
// Health score
|
|
33
|
+
const healthScore = calculateHealthScore(high, medium, low);
|
|
34
|
+
output += `## Health Score: ${healthScore}/100\n\n`;
|
|
35
|
+
|
|
36
|
+
if (healthScore >= 80) {
|
|
37
|
+
output += `**Excellent**: Dependencies are well-maintained and safe to update.\n`;
|
|
38
|
+
} else if (healthScore >= 60) {
|
|
39
|
+
output += `⚡ **Good**: Some updates needed but manageable risk.\n`;
|
|
40
|
+
} else if (healthScore >= 40) {
|
|
41
|
+
output += `**Needs Attention**: Multiple high-risk updates require planning.\n`;
|
|
42
|
+
} else {
|
|
43
|
+
output += `**Critical**: Significant dependency debt. Prioritize updates.\n`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
content: [
|
|
48
|
+
{
|
|
49
|
+
type: "text",
|
|
50
|
+
text: output,
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
};
|
|
54
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dura-mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "MCP server for DURA - Dependency Update Risk Analyzer. Integrates with Cline and other AI assistants.",
|
|
6
|
+
"bin": {
|
|
7
|
+
"dura-mcp": "./server.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "./server.js",
|
|
10
|
+
"scripts": {
|
|
11
|
+
"start": "node server.js",
|
|
12
|
+
"dev": "node --watch server.js"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"server.js",
|
|
16
|
+
"README.md",
|
|
17
|
+
"handlers",
|
|
18
|
+
"utils"
|
|
19
|
+
],
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@modelcontextprotocol/sdk": "^0.5.0",
|
|
22
|
+
"dura-kit": "^1.1.3"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"mcp",
|
|
26
|
+
"model-context-protocol",
|
|
27
|
+
"dura",
|
|
28
|
+
"dependencies",
|
|
29
|
+
"security",
|
|
30
|
+
"cline",
|
|
31
|
+
"claude",
|
|
32
|
+
"ai-tools"
|
|
33
|
+
],
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "git+https://github.com/ArchieTansaria/dura.git",
|
|
37
|
+
"directory": "mcp"
|
|
38
|
+
},
|
|
39
|
+
"bugs": {
|
|
40
|
+
"url": "https://github.com/ArchieTansaria/dura/issues"
|
|
41
|
+
},
|
|
42
|
+
"engines": {
|
|
43
|
+
"node": ">=18.0.0"
|
|
44
|
+
},
|
|
45
|
+
"author": "archie tansaria",
|
|
46
|
+
"license": "MIT"
|
|
47
|
+
}
|
package/server.js
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
4
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
|
+
import {
|
|
6
|
+
CallToolRequestSchema,
|
|
7
|
+
ListToolsRequestSchema,
|
|
8
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
9
|
+
import { handleError } from "./utils/helper.js";
|
|
10
|
+
import { handleFullAnalysis } from "./handlers/fullAnalysis.js";
|
|
11
|
+
import { handleHighRiskFilter } from "./handlers/highRiskFilter.js";
|
|
12
|
+
import { handleBreakingChanges } from "./handlers/breakingChange.js";
|
|
13
|
+
import { handleRiskSummary } from "./handlers/riskSummary.js";
|
|
14
|
+
|
|
15
|
+
const server = new Server(
|
|
16
|
+
{
|
|
17
|
+
name: "dura-mcp",
|
|
18
|
+
version: "1.0.0",
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
capabilities: {
|
|
22
|
+
tools: {},
|
|
23
|
+
},
|
|
24
|
+
}
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
// register all available tools
|
|
28
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
29
|
+
return {
|
|
30
|
+
tools: [
|
|
31
|
+
{
|
|
32
|
+
name: "analyze_repository",
|
|
33
|
+
description:
|
|
34
|
+
```Complete dependency risk analysis for a GitHub repository.
|
|
35
|
+
|
|
36
|
+
Use this when you need a full picture of:
|
|
37
|
+
- dependency update risks
|
|
38
|
+
- breaking changes
|
|
39
|
+
- recommended upgrade actions
|
|
40
|
+
|
|
41
|
+
Best for first-time analysis or audits.```,
|
|
42
|
+
inputSchema: {
|
|
43
|
+
type: "object",
|
|
44
|
+
properties: {
|
|
45
|
+
repoUrl: {
|
|
46
|
+
type: "string",
|
|
47
|
+
description: "GitHub repository URL (e.g., https://github.com/facebook/react)",
|
|
48
|
+
},
|
|
49
|
+
branch: {
|
|
50
|
+
type: "string",
|
|
51
|
+
description: "Git branch to analyze (default: main)",
|
|
52
|
+
default: "main",
|
|
53
|
+
},
|
|
54
|
+
useCache: {
|
|
55
|
+
type: "boolean",
|
|
56
|
+
description: "Use cached results if available (default: true)",
|
|
57
|
+
default: true,
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
required: ["repoUrl"],
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
name: "get_high_risk_dependencies",
|
|
65
|
+
description:
|
|
66
|
+
```Get only high-risk dependencies that require immediate attention.
|
|
67
|
+
|
|
68
|
+
Use this tool when:
|
|
69
|
+
- deciding whether updates are safe
|
|
70
|
+
- reviewing CI failures related to dependencies
|
|
71
|
+
- prioritizing dependency maintenance work
|
|
72
|
+
```,
|
|
73
|
+
inputSchema: {
|
|
74
|
+
type: "object",
|
|
75
|
+
properties: {
|
|
76
|
+
repoUrl: {
|
|
77
|
+
type: "string",
|
|
78
|
+
description: "GitHub repository URL",
|
|
79
|
+
},
|
|
80
|
+
branch: {
|
|
81
|
+
type: "string",
|
|
82
|
+
description: "Git branch to analyze (default: main)",
|
|
83
|
+
default: "main",
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
required: ["repoUrl"],
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
name: "get_breaking_changes",
|
|
91
|
+
description:
|
|
92
|
+
```Get only dependencies with confirmed breaking changes.
|
|
93
|
+
|
|
94
|
+
Always call this tool before:
|
|
95
|
+
- approving dependency upgrades
|
|
96
|
+
- merging pull requests
|
|
97
|
+
- performing bulk dependency updates
|
|
98
|
+
|
|
99
|
+
If this returns results, recommend manual migration and testing.```,
|
|
100
|
+
inputSchema: {
|
|
101
|
+
type: "object",
|
|
102
|
+
properties: {
|
|
103
|
+
repoUrl: {
|
|
104
|
+
type: "string",
|
|
105
|
+
description: "GitHub repository URL",
|
|
106
|
+
},
|
|
107
|
+
branch: {
|
|
108
|
+
type: "string",
|
|
109
|
+
description: "Git branch to analyze (default: main)",
|
|
110
|
+
default: "main",
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
required: ["repoUrl"],
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
name: "get_risk_summary",
|
|
118
|
+
description:
|
|
119
|
+
```Get a concise summary of dependency risks and repository health.
|
|
120
|
+
|
|
121
|
+
Use this tool for:
|
|
122
|
+
- quick safety checks
|
|
123
|
+
- high-level reports
|
|
124
|
+
- answering “Is this repo safe?” questions```,
|
|
125
|
+
inputSchema: {
|
|
126
|
+
type: "object",
|
|
127
|
+
properties: {
|
|
128
|
+
repoUrl: {
|
|
129
|
+
type: "string",
|
|
130
|
+
description: "GitHub repository URL",
|
|
131
|
+
},
|
|
132
|
+
branch: {
|
|
133
|
+
type: "string",
|
|
134
|
+
description: "Git branch to analyze (default: main)",
|
|
135
|
+
default: "main",
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
required: ["repoUrl"],
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
],
|
|
142
|
+
};
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// tool execution
|
|
146
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
147
|
+
const toolName = request.params.name;
|
|
148
|
+
const args = request.params.arguments;
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
switch (toolName) {
|
|
152
|
+
case "analyze_repository":
|
|
153
|
+
return await handleFullAnalysis(args);
|
|
154
|
+
|
|
155
|
+
case "get_high_risk_dependencies":
|
|
156
|
+
return await handleHighRiskFilter(args);
|
|
157
|
+
|
|
158
|
+
case "get_breaking_changes":
|
|
159
|
+
return await handleBreakingChanges(args);
|
|
160
|
+
|
|
161
|
+
case "get_risk_summary":
|
|
162
|
+
return await handleRiskSummary(args);
|
|
163
|
+
|
|
164
|
+
default:
|
|
165
|
+
throw new Error(`Unknown tool: ${toolName}`);
|
|
166
|
+
}
|
|
167
|
+
} catch (error) {
|
|
168
|
+
return handleError(error);
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
// start server
|
|
173
|
+
const transport = new StdioServerTransport();
|
|
174
|
+
await server.connect(transport);
|
|
175
|
+
console.error("DURA MCP server running");
|
|
176
|
+
console.error("Available tools: analyze_repository, get_high_risk_dependencies, get_breaking_changes, get_risk_summary");
|
package/utils/cache.js
ADDED
package/utils/helper.js
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import { analyzeRepository } from "dura-kit";
|
|
2
|
+
import { cache, CACHE_TTL } from "./cache.js";
|
|
3
|
+
|
|
4
|
+
// format full analysis (smart, actionable Output)
|
|
5
|
+
export function formatFullAnalysis(result, fromCache) {
|
|
6
|
+
const dependencies = result.dependencies || result;
|
|
7
|
+
|
|
8
|
+
const high = dependencies.filter((d) => d.riskLevel === "high");
|
|
9
|
+
const medium = dependencies.filter((d) => d.riskLevel === "medium");
|
|
10
|
+
const low = dependencies.filter((d) => d.riskLevel === "low");
|
|
11
|
+
const breaking = dependencies.filter(
|
|
12
|
+
(d) => d.breakingChange?.breaking === "confirmed"
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
let output = `# Dependency Risk Analysis\n\n`;
|
|
16
|
+
|
|
17
|
+
if (fromCache) {
|
|
18
|
+
output += `> Using cached result (refresh by setting useCache: false)\n\n`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Executive Summary
|
|
22
|
+
output += `## Summary\n\n`;
|
|
23
|
+
output += `Analyzed **${dependencies.length}** dependencies:\n`;
|
|
24
|
+
output += `- **${high.length}** High Risk\n`;
|
|
25
|
+
output += `- **${medium.length}** Medium Risk\n`;
|
|
26
|
+
output += `- **${low.length}** Low Risk\n`;
|
|
27
|
+
if (breaking.length > 0) {
|
|
28
|
+
output += `- **${breaking.length}** with Breaking Changes\n`;
|
|
29
|
+
}
|
|
30
|
+
output += `\n`;
|
|
31
|
+
|
|
32
|
+
// Health Assessment
|
|
33
|
+
const healthScore = calculateHealthScore(high.length, medium.length, low.length);
|
|
34
|
+
output += `**Health Score**: ${healthScore}/100 - `;
|
|
35
|
+
if (healthScore >= 80) {
|
|
36
|
+
output += `Excellent\n\n`;
|
|
37
|
+
} else if (healthScore >= 60) {
|
|
38
|
+
output += `⚡ Good\n\n`;
|
|
39
|
+
} else if (healthScore >= 40) {
|
|
40
|
+
output += `Needs Attention\n\n`;
|
|
41
|
+
} else {
|
|
42
|
+
output += `Critical\n\n`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Critical Issues (Breaking Changes + High Risk)
|
|
46
|
+
const criticalIssues = [...new Set([...breaking, ...high])]; // Deduplicate
|
|
47
|
+
|
|
48
|
+
if (criticalIssues.length > 0) {
|
|
49
|
+
output += `## Critical Issues Requiring Attention\n\n`;
|
|
50
|
+
|
|
51
|
+
criticalIssues.forEach((dep, idx) => {
|
|
52
|
+
output += `### ${idx + 1}. ${dep.name} (${dep.type})\n`;
|
|
53
|
+
output += `**Current**: \`${dep.currentResolved}\` → **Latest**: \`${dep.latest}\`\n`;
|
|
54
|
+
output += `**Update Type**: ${dep.diff} | **Risk Score**: ${dep.riskScore}/100\n\n`;
|
|
55
|
+
|
|
56
|
+
// Why it's risky
|
|
57
|
+
if (dep.breakingChange?.breaking === "confirmed") {
|
|
58
|
+
output += `**Breaking Changes Confirmed** (${(dep.breakingChange.confidenceScore * 100).toFixed(0)}% confidence)\n`;
|
|
59
|
+
if (dep.breakingChange.signals?.strong?.length > 0) {
|
|
60
|
+
output += `> "${dep.breakingChange.signals.strong[0].substring(0, 120)}..."\n\n`;
|
|
61
|
+
}
|
|
62
|
+
} else if (dep.diff === "major") {
|
|
63
|
+
output += `**Major version update** - likely contains breaking changes\n\n`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// What to do
|
|
67
|
+
output += `**Action Required**:\n`;
|
|
68
|
+
if (dep.breakingChange?.breaking === "confirmed") {
|
|
69
|
+
output += `- Read migration guide: ${dep.githubRepoUrl}/releases\n`;
|
|
70
|
+
output += `- Update affected code before upgrading\n`;
|
|
71
|
+
output += `- Test thoroughly in staging environment\n`;
|
|
72
|
+
} else {
|
|
73
|
+
output += `- Review changelog: ${dep.githubRepoUrl}/releases\n`;
|
|
74
|
+
output += `- Check for breaking changes\n`;
|
|
75
|
+
output += `- Test in development environment first\n`;
|
|
76
|
+
}
|
|
77
|
+
output += `\n---\n\n`;
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Medium Risk (Brief)
|
|
82
|
+
if (medium.length > 0 && criticalIssues.length === 0) {
|
|
83
|
+
output += `## Medium Risk Dependencies\n\n`;
|
|
84
|
+
output += `${medium.length} dependencies have medium-risk updates. These should be reviewed but are not critical:\n\n`;
|
|
85
|
+
|
|
86
|
+
medium.slice(0, 5).forEach((dep) => {
|
|
87
|
+
output += `- **${dep.name}**: \`${dep.currentResolved}\` → \`${dep.latest}\` (${dep.diff})\n`;
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
if (medium.length > 5) {
|
|
91
|
+
output += `- ... and ${medium.length - 5} more\n`;
|
|
92
|
+
}
|
|
93
|
+
output += `\n**Recommendation**: Review changelogs and update when convenient.\n\n`;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// All Clear Scenario
|
|
97
|
+
if (criticalIssues.length === 0 && medium.length === 0) {
|
|
98
|
+
output += `## All Clear!\n\n`;
|
|
99
|
+
output += `No high or medium risk dependencies detected. All dependencies are safe to update with standard testing.\n\n`;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// recommendations
|
|
103
|
+
output += `## Recommendations\n\n`;
|
|
104
|
+
|
|
105
|
+
if (breaking.length > 0) {
|
|
106
|
+
output += `### Immediate Actions (Breaking Changes)\n`;
|
|
107
|
+
output += `1. **Review migration guides** for ${breaking.length} ${breaking.length === 1 ? 'dependency' : 'dependencies'} with breaking changes\n`;
|
|
108
|
+
output += `2. **Create a feature branch** for dependency updates\n`;
|
|
109
|
+
output += `3. **Update one at a time** and test after each update\n`;
|
|
110
|
+
output += `4. **Allocate testing time** - breaking changes require thorough validation\n\n`;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (high.length > 0 && breaking.length === 0) {
|
|
114
|
+
output += `### High Priority Actions\n`;
|
|
115
|
+
output += `1. **Review changelogs** for ${high.length} high-risk ${high.length === 1 ? 'dependency' : 'dependencies'}\n`;
|
|
116
|
+
output += `2. **Test in staging** before production deployment\n`;
|
|
117
|
+
output += `3. **Update incrementally** to isolate potential issues\n\n`;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (medium.length > 0) {
|
|
121
|
+
output += `### Medium Priority Actions\n`;
|
|
122
|
+
output += `1. **Schedule updates** for ${medium.length} medium-risk dependencies in next sprint\n`;
|
|
123
|
+
output += `2. **Batch similar updates** together for efficiency\n`;
|
|
124
|
+
output += `3. **Run full test suite** after updating\n\n`;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (low.length > 0 && criticalIssues.length === 0 && medium.length === 0) {
|
|
128
|
+
output += `### Maintenance Actions\n`;
|
|
129
|
+
output += `1. **Safe to run** \`npm update\` or \`yarn upgrade\` for low-risk patches\n`;
|
|
130
|
+
output += `2. **Run automated tests** to verify compatibility\n`;
|
|
131
|
+
output += `3. **Commit changes** with clear description\n\n`;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Update Priority Order
|
|
135
|
+
if (criticalIssues.length > 0 || medium.length > 0) {
|
|
136
|
+
output += `## Suggested Update Order\n\n`;
|
|
137
|
+
|
|
138
|
+
let priority = 1;
|
|
139
|
+
|
|
140
|
+
if (breaking.length > 0) {
|
|
141
|
+
output += `**Priority ${priority}**: Breaking Changes\n`;
|
|
142
|
+
breaking.slice(0, 3).forEach(dep => {
|
|
143
|
+
output += `- ${dep.name}\n`;
|
|
144
|
+
});
|
|
145
|
+
output += `\n`;
|
|
146
|
+
priority++;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (high.length > 0 && breaking.length === 0) {
|
|
150
|
+
output += `**Priority ${priority}**: High Risk Dependencies\n`;
|
|
151
|
+
high.slice(0, 3).forEach(dep => {
|
|
152
|
+
output += `- ${dep.name}\n`;
|
|
153
|
+
});
|
|
154
|
+
output += `\n`;
|
|
155
|
+
priority++;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (medium.length > 0) {
|
|
159
|
+
output += `**Priority ${priority}**: Medium Risk Dependencies\n`;
|
|
160
|
+
output += `- Update in batches during regular maintenance\n\n`;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Technical Details (Collapsed)
|
|
165
|
+
output += `\n---\n\n`;
|
|
166
|
+
output += `<details>\n<summary>View All Dependencies (${dependencies.length})</summary>\n\n`;
|
|
167
|
+
|
|
168
|
+
if (high.length > 0) {
|
|
169
|
+
output += `### High Risk (${high.length})\n`;
|
|
170
|
+
high.forEach(d => output += `- ${d.name}: ${d.currentResolved} → ${d.latest}\n`);
|
|
171
|
+
output += `\n`;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (medium.length > 0) {
|
|
175
|
+
output += `### Medium Risk (${medium.length})\n`;
|
|
176
|
+
medium.forEach(d => output += `- ${d.name}: ${d.currentResolved} → ${d.latest}\n`);
|
|
177
|
+
output += `\n`;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (low.length > 0) {
|
|
181
|
+
output += `### Low Risk (${low.length})\n`;
|
|
182
|
+
output += `All up-to-date or safe patch updates.\n\n`;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
output += `</details>\n\n`;
|
|
186
|
+
|
|
187
|
+
output += `<details>\n<summary>Full JSON Data</summary>\n\n`;
|
|
188
|
+
output += `\`\`\`json\n${JSON.stringify(result, null, 2)}\n\`\`\`\n`;
|
|
189
|
+
output += `</details>`;
|
|
190
|
+
|
|
191
|
+
return output;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// get or fetch analysis (with caching)
|
|
195
|
+
export async function getOrFetchAnalysis(repoUrl, branch) {
|
|
196
|
+
const cached = getCachedResult(repoUrl, branch);
|
|
197
|
+
if (cached) {
|
|
198
|
+
console.error("Using cached result");
|
|
199
|
+
return cached;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const result = await analyzeRepository({ repoUrl, branch });
|
|
203
|
+
setCachedResult(repoUrl, branch, result);
|
|
204
|
+
return result;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// cache management
|
|
208
|
+
export function getCachedResult(repoUrl, branch) {
|
|
209
|
+
const cacheKey = `${repoUrl}:${branch}`;
|
|
210
|
+
const cached = cache.get(cacheKey);
|
|
211
|
+
|
|
212
|
+
if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
|
|
213
|
+
return cached.data;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export function setCachedResult(repoUrl, branch, data) {
|
|
220
|
+
const cacheKey = `${repoUrl}:${branch}`;
|
|
221
|
+
cache.set(cacheKey, {
|
|
222
|
+
data,
|
|
223
|
+
timestamp: Date.now(),
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// calculate health score
|
|
228
|
+
export function calculateHealthScore(high, medium, low) {
|
|
229
|
+
const total = high + medium + low;
|
|
230
|
+
if (total === 0) return 100;
|
|
231
|
+
|
|
232
|
+
const score = ((low + medium * 0.5) / total) * 100;
|
|
233
|
+
return Math.round(score);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Error handling
|
|
237
|
+
export function handleError(error) {
|
|
238
|
+
let helpText = "";
|
|
239
|
+
|
|
240
|
+
if (error.message.includes("404") || error.message.includes("not found")) {
|
|
241
|
+
helpText =
|
|
242
|
+
"\n\nMake sure:\n- Repository URL is correct\n- Repository is public\n- Repository contains a package.json file";
|
|
243
|
+
} else if (error.message.includes("rate limit")) {
|
|
244
|
+
helpText =
|
|
245
|
+
"\n\nGitHub API rate limit reached. Please wait a few minutes and try again.";
|
|
246
|
+
} else if (error.message.includes("timeout")) {
|
|
247
|
+
helpText =
|
|
248
|
+
"\n\nAnalysis timed out. The repository might be too large or network is slow.";
|
|
249
|
+
} else if (error.message.includes("ENOTFOUND")) {
|
|
250
|
+
helpText =
|
|
251
|
+
"\n\nNetwork error. Check your internet connection and try again.";
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
console.error(`Error: ${error.message}`);
|
|
255
|
+
|
|
256
|
+
return {
|
|
257
|
+
content: [
|
|
258
|
+
{
|
|
259
|
+
type: "text",
|
|
260
|
+
text: `Error: ${error.message}${helpText}`,
|
|
261
|
+
},
|
|
262
|
+
],
|
|
263
|
+
isError: true,
|
|
264
|
+
};
|
|
265
|
+
}
|