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 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
@@ -0,0 +1,3 @@
1
+ // in-memory cache
2
+ export const cache = new Map();
3
+ export const CACHE_TTL = 3600000; // 1 hour
@@ -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
+ }