thuban-mcp 0.4.5
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/README.md +154 -0
- package/package.json +15 -0
- package/src/server.js +319 -0
- package/src/tools.js +185 -0
package/README.md
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# thuban-mcp
|
|
2
|
+
|
|
3
|
+
MCP Server for **Thuban AI Code Verification** — let AI agents scan, fix, and verify code quality through the [Model Context Protocol](https://modelcontextprotocol.io/).
|
|
4
|
+
|
|
5
|
+
Thuban catches what linters miss: hallucinated APIs, dead code, copy-paste clones, taint flows, and 84 detection rules across security, reliability, performance, and maintainability.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g thuban-mcp
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
The MCP server shells out to the Thuban CLI, so you also need:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install -g thuban
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Or use `npx` — the server automatically falls back to `npx thuban` if the global binary is not found.
|
|
20
|
+
|
|
21
|
+
## Configuration
|
|
22
|
+
|
|
23
|
+
### Claude Desktop / Verdent
|
|
24
|
+
|
|
25
|
+
Add to your MCP configuration file (`mcp.json` or `claude_desktop_config.json`):
|
|
26
|
+
|
|
27
|
+
```json
|
|
28
|
+
{
|
|
29
|
+
"mcpServers": {
|
|
30
|
+
"thuban": {
|
|
31
|
+
"command": "npx",
|
|
32
|
+
"args": ["thuban-mcp"]
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Cursor
|
|
39
|
+
|
|
40
|
+
Add to `.cursor/mcp.json` in your project root:
|
|
41
|
+
|
|
42
|
+
```json
|
|
43
|
+
{
|
|
44
|
+
"mcpServers": {
|
|
45
|
+
"thuban": {
|
|
46
|
+
"command": "npx",
|
|
47
|
+
"args": ["thuban-mcp"]
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### VS Code Copilot
|
|
54
|
+
|
|
55
|
+
Add to your VS Code `settings.json`:
|
|
56
|
+
|
|
57
|
+
```json
|
|
58
|
+
{
|
|
59
|
+
"mcp": {
|
|
60
|
+
"servers": {
|
|
61
|
+
"thuban": {
|
|
62
|
+
"command": "npx",
|
|
63
|
+
"args": ["thuban-mcp"]
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Windsurf
|
|
71
|
+
|
|
72
|
+
Add to `~/.codeium/windsurf/mcp_config.json`:
|
|
73
|
+
|
|
74
|
+
```json
|
|
75
|
+
{
|
|
76
|
+
"mcpServers": {
|
|
77
|
+
"thuban": {
|
|
78
|
+
"command": "npx",
|
|
79
|
+
"args": ["thuban-mcp"]
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Available Tools
|
|
86
|
+
|
|
87
|
+
| Tool | Description |
|
|
88
|
+
|---|---|
|
|
89
|
+
| `thuban_scan` | Scan a file or directory for code-quality and security issues |
|
|
90
|
+
| `thuban_fix` | Auto-fix issues in a file (safe, deterministic fixes) |
|
|
91
|
+
| `thuban_hallucinate` | Check for AI-hallucinated APIs, imports, and options |
|
|
92
|
+
| `thuban_ghosts` | Find dead/unused code — exports, functions, variables, files |
|
|
93
|
+
| `thuban_clones` | Detect copy-paste duplicate code clusters |
|
|
94
|
+
| `thuban_health` | Get codebase health score and grade (A–F) |
|
|
95
|
+
| `thuban_taint` | Run cross-file taint analysis (source → sink tracing) |
|
|
96
|
+
|
|
97
|
+
## Available Resources
|
|
98
|
+
|
|
99
|
+
| Resource URI | Description |
|
|
100
|
+
|---|---|
|
|
101
|
+
| `thuban://rules` | List all 84 detection rules with IDs, severities, and descriptions |
|
|
102
|
+
| `thuban://config` | Current scanner configuration |
|
|
103
|
+
|
|
104
|
+
## Example Usage
|
|
105
|
+
|
|
106
|
+
Once configured, ask your AI agent:
|
|
107
|
+
|
|
108
|
+
> "Scan this project for security issues"
|
|
109
|
+
|
|
110
|
+
The agent will call `thuban_scan` with your project path and return a structured report of issues, trust score, and recommendations.
|
|
111
|
+
|
|
112
|
+
> "Check if any of the APIs I'm using are hallucinated"
|
|
113
|
+
|
|
114
|
+
The agent calls `thuban_hallucinate` to verify that every import, method call, and configuration option actually exists in the referenced packages.
|
|
115
|
+
|
|
116
|
+
> "Find dead code in src/"
|
|
117
|
+
|
|
118
|
+
The agent calls `thuban_ghosts` to identify unused exports, uncalled functions, and orphan files.
|
|
119
|
+
|
|
120
|
+
> "What's the health score of this codebase?"
|
|
121
|
+
|
|
122
|
+
The agent calls `thuban_health` and returns a grade (A–F) with category breakdowns for security, reliability, performance, and maintainability.
|
|
123
|
+
|
|
124
|
+
## How It Works
|
|
125
|
+
|
|
126
|
+
The MCP server acts as a bridge between AI agents and the Thuban CLI:
|
|
127
|
+
|
|
128
|
+
1. AI agent sends a tool call via the MCP protocol (JSON-RPC over stdio)
|
|
129
|
+
2. `thuban-mcp` translates the call into a Thuban CLI command
|
|
130
|
+
3. The CLI runs the analysis and returns structured JSON
|
|
131
|
+
4. `thuban-mcp` forwards the results back to the AI agent
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
AI Agent ←→ MCP Protocol (stdio) ←→ thuban-mcp ←→ thuban CLI
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Detection Rules
|
|
138
|
+
|
|
139
|
+
Thuban ships with 84 detection rules across 7 categories:
|
|
140
|
+
|
|
141
|
+
- **Security** (20 rules) — SQL injection, XSS, SSRF, hardcoded secrets, and more
|
|
142
|
+
- **Taint Analysis** (10 rules) — Cross-file data flow from user input to sensitive sinks
|
|
143
|
+
- **AI Hallucination** (10 rules) — Phantom imports, non-existent APIs, wrong signatures
|
|
144
|
+
- **Dead Code** (10 rules) — Unused exports, uncalled functions, orphan files
|
|
145
|
+
- **Clone Detection** (8 rules) — Exact duplicates, near-duplicates, copy-paste errors
|
|
146
|
+
- **Reliability** (10 rules) — Unhandled promises, null derefs, race conditions
|
|
147
|
+
- **Maintainability** (8 rules) — God functions, deep nesting, circular dependencies
|
|
148
|
+
- **Performance** (8 rules) — N+1 queries, sync I/O, memory leaks
|
|
149
|
+
|
|
150
|
+
Use the `thuban://rules` resource to get the full list programmatically.
|
|
151
|
+
|
|
152
|
+
## License
|
|
153
|
+
|
|
154
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "thuban-mcp",
|
|
3
|
+
"version": "0.4.5",
|
|
4
|
+
"description": "MCP Server for Thuban AI Code Verification — let AI agents scan, fix, and verify code quality",
|
|
5
|
+
"main": "src/server.js",
|
|
6
|
+
"bin": { "thuban-mcp": "./src/server.js" },
|
|
7
|
+
"type": "module",
|
|
8
|
+
"keywords": ["mcp", "thuban", "code-quality", "security", "AI-verification", "static-analysis"],
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
12
|
+
"zod": "^3.23.0"
|
|
13
|
+
},
|
|
14
|
+
"engines": { "node": ">=18.0.0" }
|
|
15
|
+
}
|
package/src/server.js
ADDED
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// Thuban MCP Server
|
|
5
|
+
// Exposes the Thuban AI Code Verification CLI as MCP tools & resources so that
|
|
6
|
+
// any MCP-compatible AI agent (Claude, Copilot, Cursor, Verdent, …) can scan,
|
|
7
|
+
// fix, and verify code quality.
|
|
8
|
+
//
|
|
9
|
+
// IMPORTANT: All logging uses console.error() — stdout is reserved for the
|
|
10
|
+
// MCP JSON-RPC protocol.
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
|
|
13
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
14
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
15
|
+
import { z } from "zod";
|
|
16
|
+
|
|
17
|
+
import {
|
|
18
|
+
thubanScan,
|
|
19
|
+
thubanFix,
|
|
20
|
+
thubanHallucinate,
|
|
21
|
+
thubanGhosts,
|
|
22
|
+
thubanClones,
|
|
23
|
+
thubanHealth,
|
|
24
|
+
thubanTaint,
|
|
25
|
+
} from "./tools.js";
|
|
26
|
+
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
// Detection rules catalogue (84 rules)
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
|
|
31
|
+
const RULES = [
|
|
32
|
+
// Security (20)
|
|
33
|
+
{ id: "SEC001", name: "sql-injection", severity: "critical", category: "security", description: "Detects unsanitised SQL query construction" },
|
|
34
|
+
{ id: "SEC002", name: "xss-vulnerability", severity: "critical", category: "security", description: "Detects cross-site scripting vectors" },
|
|
35
|
+
{ id: "SEC003", name: "path-traversal", severity: "critical", category: "security", description: "Detects directory traversal in file operations" },
|
|
36
|
+
{ id: "SEC004", name: "command-injection", severity: "critical", category: "security", description: "Detects OS command injection via unsanitised input" },
|
|
37
|
+
{ id: "SEC005", name: "hardcoded-secret", severity: "high", category: "security", description: "Detects hardcoded passwords, API keys, and tokens" },
|
|
38
|
+
{ id: "SEC006", name: "insecure-random", severity: "medium", category: "security", description: "Detects use of Math.random() for security-sensitive operations" },
|
|
39
|
+
{ id: "SEC007", name: "eval-usage", severity: "high", category: "security", description: "Detects use of eval() and equivalent dynamic code execution" },
|
|
40
|
+
{ id: "SEC008", name: "prototype-pollution", severity: "high", category: "security", description: "Detects prototype pollution via unguarded object merges" },
|
|
41
|
+
{ id: "SEC009", name: "open-redirect", severity: "medium", category: "security", description: "Detects unvalidated redirect URLs" },
|
|
42
|
+
{ id: "SEC010", name: "insecure-deserialization", severity: "high", category: "security", description: "Detects unsafe deserialization of untrusted data" },
|
|
43
|
+
{ id: "SEC011", name: "missing-csrf", severity: "medium", category: "security", description: "Detects missing CSRF protection on state-changing endpoints" },
|
|
44
|
+
{ id: "SEC012", name: "insecure-cookie", severity: "medium", category: "security", description: "Detects cookies without Secure/HttpOnly/SameSite flags" },
|
|
45
|
+
{ id: "SEC013", name: "weak-crypto", severity: "high", category: "security", description: "Detects use of weak cryptographic algorithms (MD5, SHA1, DES)" },
|
|
46
|
+
{ id: "SEC014", name: "ssrf", severity: "critical", category: "security", description: "Detects server-side request forgery via user-controlled URLs" },
|
|
47
|
+
{ id: "SEC015", name: "nosql-injection", severity: "critical", category: "security", description: "Detects NoSQL injection in MongoDB/Mongoose queries" },
|
|
48
|
+
{ id: "SEC016", name: "regex-dos", severity: "medium", category: "security", description: "Detects catastrophic backtracking in regular expressions" },
|
|
49
|
+
{ id: "SEC017", name: "jwt-none-alg", severity: "critical", category: "security", description: "Detects JWT verification that accepts 'none' algorithm" },
|
|
50
|
+
{ id: "SEC018", name: "cors-wildcard", severity: "medium", category: "security", description: "Detects overly permissive CORS configuration" },
|
|
51
|
+
{ id: "SEC019", name: "unsafe-file-upload", severity: "high", category: "security", description: "Detects file uploads without type/size validation" },
|
|
52
|
+
{ id: "SEC020", name: "info-exposure", severity: "medium", category: "security", description: "Detects stack traces or debug info leaked to clients" },
|
|
53
|
+
|
|
54
|
+
// Taint analysis (10)
|
|
55
|
+
{ id: "TAINT001", name: "taint-source-unvalidated", severity: "high", category: "taint", description: "User input reaches sensitive sink without validation" },
|
|
56
|
+
{ id: "TAINT002", name: "taint-db-query", severity: "critical", category: "taint", description: "Tainted data flows into database query" },
|
|
57
|
+
{ id: "TAINT003", name: "taint-file-path", severity: "critical", category: "taint", description: "Tainted data used in file system path" },
|
|
58
|
+
{ id: "TAINT004", name: "taint-html-render", severity: "high", category: "taint", description: "Tainted data rendered as HTML without escaping" },
|
|
59
|
+
{ id: "TAINT005", name: "taint-shell-exec", severity: "critical", category: "taint", description: "Tainted data passed to shell execution" },
|
|
60
|
+
{ id: "TAINT006", name: "taint-redirect", severity: "medium", category: "taint", description: "Tainted data used in HTTP redirect" },
|
|
61
|
+
{ id: "TAINT007", name: "taint-log-injection", severity: "medium", category: "taint", description: "Tainted data written to logs without sanitisation" },
|
|
62
|
+
{ id: "TAINT008", name: "taint-header-injection", severity: "high", category: "taint", description: "Tainted data injected into HTTP response headers" },
|
|
63
|
+
{ id: "TAINT009", name: "taint-deserialization", severity: "critical", category: "taint", description: "Tainted data passed to deserializer" },
|
|
64
|
+
{ id: "TAINT010", name: "taint-crypto-key", severity: "critical", category: "taint", description: "Tainted data used as cryptographic key material" },
|
|
65
|
+
|
|
66
|
+
// AI hallucination (10)
|
|
67
|
+
{ id: "HALL001", name: "phantom-import", severity: "high", category: "hallucination", description: "Import references a package that does not exist in registry" },
|
|
68
|
+
{ id: "HALL002", name: "phantom-api", severity: "high", category: "hallucination", description: "Call to a method/function that does not exist on the target object" },
|
|
69
|
+
{ id: "HALL003", name: "phantom-option", severity: "medium", category: "hallucination", description: "Configuration option that does not exist in the library's API" },
|
|
70
|
+
{ id: "HALL004", name: "version-mismatch", severity: "medium", category: "hallucination", description: "API usage matches a different version than installed" },
|
|
71
|
+
{ id: "HALL005", name: "phantom-event", severity: "medium", category: "hallucination", description: "Event listener for an event that the emitter never fires" },
|
|
72
|
+
{ id: "HALL006", name: "phantom-type", severity: "medium", category: "hallucination", description: "TypeScript type reference that does not exist in the package" },
|
|
73
|
+
{ id: "HALL007", name: "deprecated-api", severity: "low", category: "hallucination", description: "Usage of an API that has been deprecated or removed" },
|
|
74
|
+
{ id: "HALL008", name: "wrong-signature", severity: "high", category: "hallucination", description: "Function called with incorrect argument count or types" },
|
|
75
|
+
{ id: "HALL009", name: "phantom-cli-flag", severity: "medium", category: "hallucination", description: "CLI flag that does not exist for the target command" },
|
|
76
|
+
{ id: "HALL010", name: "phantom-env-var", severity: "low", category: "hallucination", description: "Environment variable referenced but never defined" },
|
|
77
|
+
|
|
78
|
+
// Dead code / ghosts (10)
|
|
79
|
+
{ id: "GHOST001", name: "unused-export", severity: "low", category: "ghost", description: "Exported symbol is never imported anywhere" },
|
|
80
|
+
{ id: "GHOST002", name: "unused-function", severity: "low", category: "ghost", description: "Function is declared but never called" },
|
|
81
|
+
{ id: "GHOST003", name: "unused-variable", severity: "low", category: "ghost", description: "Variable is assigned but never read" },
|
|
82
|
+
{ id: "GHOST004", name: "unreachable-code", severity: "medium", category: "ghost", description: "Code after return/throw/break that can never execute" },
|
|
83
|
+
{ id: "GHOST005", name: "dead-branch", severity: "low", category: "ghost", description: "Conditional branch that can never be true" },
|
|
84
|
+
{ id: "GHOST006", name: "orphan-file", severity: "low", category: "ghost", description: "Source file not imported by any other file" },
|
|
85
|
+
{ id: "GHOST007", name: "unused-dependency", severity: "low", category: "ghost", description: "Package in dependencies but never imported" },
|
|
86
|
+
{ id: "GHOST008", name: "unused-css-class", severity: "low", category: "ghost", description: "CSS class defined but never referenced in templates" },
|
|
87
|
+
{ id: "GHOST009", name: "empty-catch", severity: "medium", category: "ghost", description: "Catch block that silently swallows errors" },
|
|
88
|
+
{ id: "GHOST010", name: "commented-code", severity: "low", category: "ghost", description: "Large blocks of commented-out code" },
|
|
89
|
+
|
|
90
|
+
// Clone detection (8)
|
|
91
|
+
{ id: "CLONE001", name: "exact-duplicate", severity: "medium", category: "clone", description: "Identical code block duplicated across files" },
|
|
92
|
+
{ id: "CLONE002", name: "near-duplicate", severity: "medium", category: "clone", description: "Code block with only cosmetic differences (variable names, whitespace)" },
|
|
93
|
+
{ id: "CLONE003", name: "structural-clone", severity: "low", category: "clone", description: "Same AST structure with different identifiers" },
|
|
94
|
+
{ id: "CLONE004", name: "logic-clone", severity: "low", category: "clone", description: "Semantically equivalent logic expressed differently" },
|
|
95
|
+
{ id: "CLONE005", name: "copy-paste-error", severity: "high", category: "clone", description: "Duplicated code with inconsistent modifications (likely bug)" },
|
|
96
|
+
{ id: "CLONE006", name: "duplicate-test", severity: "low", category: "clone", description: "Test cases that verify the same behaviour" },
|
|
97
|
+
{ id: "CLONE007", name: "duplicate-config", severity: "low", category: "clone", description: "Configuration values duplicated across files" },
|
|
98
|
+
{ id: "CLONE008", name: "extractable-util", severity: "low", category: "clone", description: "Repeated pattern that should be extracted to a utility" },
|
|
99
|
+
|
|
100
|
+
// Reliability (10)
|
|
101
|
+
{ id: "REL001", name: "unhandled-promise", severity: "high", category: "reliability", description: "Promise without .catch() or try/catch in async context" },
|
|
102
|
+
{ id: "REL002", name: "null-deref", severity: "high", category: "reliability", description: "Property access on potentially null/undefined value" },
|
|
103
|
+
{ id: "REL003", name: "race-condition", severity: "high", category: "reliability", description: "Shared mutable state accessed without synchronisation" },
|
|
104
|
+
{ id: "REL004", name: "infinite-loop", severity: "critical", category: "reliability", description: "Loop with no reachable exit condition" },
|
|
105
|
+
{ id: "REL005", name: "resource-leak", severity: "medium", category: "reliability", description: "Opened resource (file, connection) never closed" },
|
|
106
|
+
{ id: "REL006", name: "type-coercion", severity: "medium", category: "reliability", description: "Implicit type coercion that may produce unexpected results" },
|
|
107
|
+
{ id: "REL007", name: "missing-await", severity: "high", category: "reliability", description: "Async function called without await" },
|
|
108
|
+
{ id: "REL008", name: "array-mutation", severity: "medium", category: "reliability", description: "Array mutated during iteration" },
|
|
109
|
+
{ id: "REL009", name: "error-swallow", severity: "medium", category: "reliability", description: "Error caught and re-thrown without original stack" },
|
|
110
|
+
{ id: "REL010", name: "floating-point", severity: "low", category: "reliability", description: "Floating-point comparison that may fail due to precision" },
|
|
111
|
+
|
|
112
|
+
// Maintainability (8)
|
|
113
|
+
{ id: "MAINT001", name: "god-function", severity: "medium", category: "maintainability", description: "Function exceeds complexity/length thresholds" },
|
|
114
|
+
{ id: "MAINT002", name: "deep-nesting", severity: "medium", category: "maintainability", description: "Control flow nested beyond 4 levels" },
|
|
115
|
+
{ id: "MAINT003", name: "magic-number", severity: "low", category: "maintainability", description: "Numeric literal used without named constant" },
|
|
116
|
+
{ id: "MAINT004", name: "long-param-list", severity: "low", category: "maintainability", description: "Function with more than 5 parameters" },
|
|
117
|
+
{ id: "MAINT005", name: "circular-dependency", severity: "high", category: "maintainability", description: "Circular import chain between modules" },
|
|
118
|
+
{ id: "MAINT006", name: "inconsistent-naming", severity: "low", category: "maintainability", description: "Naming convention inconsistency within a module" },
|
|
119
|
+
{ id: "MAINT007", name: "missing-error-type", severity: "medium", category: "maintainability", description: "Catch block without specific error type" },
|
|
120
|
+
{ id: "MAINT008", name: "implicit-global", severity: "medium", category: "maintainability", description: "Variable used without declaration (implicit global)" },
|
|
121
|
+
|
|
122
|
+
// Performance (8)
|
|
123
|
+
{ id: "PERF001", name: "n-plus-one", severity: "high", category: "performance", description: "Database query inside a loop (N+1 query pattern)" },
|
|
124
|
+
{ id: "PERF002", name: "unbounded-query", severity: "medium", category: "performance", description: "Database query without LIMIT or pagination" },
|
|
125
|
+
{ id: "PERF003", name: "sync-io", severity: "medium", category: "performance", description: "Synchronous I/O in async context (blocks event loop)" },
|
|
126
|
+
{ id: "PERF004", name: "memory-leak", severity: "high", category: "performance", description: "Growing data structure that is never pruned" },
|
|
127
|
+
{ id: "PERF005", name: "expensive-re-render", severity: "medium", category: "performance", description: "React component re-renders on every parent render" },
|
|
128
|
+
{ id: "PERF006", name: "unindexed-lookup", severity: "medium", category: "performance", description: "Array linear search where a Map/Set would be O(1)" },
|
|
129
|
+
{ id: "PERF007", name: "large-bundle-import", severity: "low", category: "performance", description: "Importing entire library when only a sub-module is needed" },
|
|
130
|
+
{ id: "PERF008", name: "blocking-main-thread", severity: "high", category: "performance", description: "CPU-intensive work on the main/UI thread" },
|
|
131
|
+
];
|
|
132
|
+
|
|
133
|
+
// ---------------------------------------------------------------------------
|
|
134
|
+
// Default scanner config
|
|
135
|
+
// ---------------------------------------------------------------------------
|
|
136
|
+
|
|
137
|
+
const DEFAULT_CONFIG = {
|
|
138
|
+
version: "0.4.5",
|
|
139
|
+
severity: "low",
|
|
140
|
+
include: ["**/*.js", "**/*.ts", "**/*.jsx", "**/*.tsx", "**/*.py", "**/*.go", "**/*.rs", "**/*.java"],
|
|
141
|
+
exclude: ["node_modules/**", "dist/**", "build/**", ".git/**", "vendor/**", "__pycache__/**"],
|
|
142
|
+
rules: {
|
|
143
|
+
enabled: RULES.map((r) => r.id),
|
|
144
|
+
disabled: [],
|
|
145
|
+
},
|
|
146
|
+
output: {
|
|
147
|
+
format: "json",
|
|
148
|
+
colors: true,
|
|
149
|
+
verbose: false,
|
|
150
|
+
},
|
|
151
|
+
limits: {
|
|
152
|
+
maxFileSize: "1MB",
|
|
153
|
+
maxFiles: 10000,
|
|
154
|
+
timeout: 60,
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
// ---------------------------------------------------------------------------
|
|
159
|
+
// Server setup
|
|
160
|
+
// ---------------------------------------------------------------------------
|
|
161
|
+
|
|
162
|
+
const server = new McpServer({
|
|
163
|
+
name: "thuban",
|
|
164
|
+
version: "0.4.5",
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
// ---------------------------------------------------------------------------
|
|
168
|
+
// Tool registrations
|
|
169
|
+
// ---------------------------------------------------------------------------
|
|
170
|
+
|
|
171
|
+
server.tool(
|
|
172
|
+
"thuban_scan",
|
|
173
|
+
"Scan a file or directory for code-quality and security issues. Returns a list of issues, file count, trust score, and summary.",
|
|
174
|
+
{
|
|
175
|
+
path: z.string().describe("Absolute or relative path to the file or directory to scan"),
|
|
176
|
+
options: z
|
|
177
|
+
.object({
|
|
178
|
+
severity: z
|
|
179
|
+
.enum(["low", "medium", "high", "critical"])
|
|
180
|
+
.optional()
|
|
181
|
+
.describe("Minimum severity level to report"),
|
|
182
|
+
})
|
|
183
|
+
.optional()
|
|
184
|
+
.describe("Optional scan configuration"),
|
|
185
|
+
},
|
|
186
|
+
async ({ path, options }) => {
|
|
187
|
+
const result = await thubanScan({ path, options });
|
|
188
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
189
|
+
},
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
server.tool(
|
|
193
|
+
"thuban_fix",
|
|
194
|
+
"Auto-fix code-quality issues in a file. Applies safe, deterministic fixes and returns the count of fixes applied.",
|
|
195
|
+
{
|
|
196
|
+
path: z.string().describe("Absolute or relative path to the file to fix"),
|
|
197
|
+
ruleId: z.string().optional().describe("Specific rule ID to fix (e.g. SEC005). Omit to fix all auto-fixable issues."),
|
|
198
|
+
},
|
|
199
|
+
async ({ path, ruleId }) => {
|
|
200
|
+
const result = await thubanFix({ path, ruleId });
|
|
201
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
202
|
+
},
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
server.tool(
|
|
206
|
+
"thuban_hallucinate",
|
|
207
|
+
"Check for AI-hallucinated APIs — imports, methods, options, or types that do not actually exist in the referenced packages.",
|
|
208
|
+
{
|
|
209
|
+
path: z.string().describe("Absolute or relative path to the file or directory to check"),
|
|
210
|
+
},
|
|
211
|
+
async ({ path }) => {
|
|
212
|
+
const result = await thubanHallucinate({ path });
|
|
213
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
214
|
+
},
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
server.tool(
|
|
218
|
+
"thuban_ghosts",
|
|
219
|
+
"Find dead and unused code — exports nobody imports, functions never called, variables never read, orphan files.",
|
|
220
|
+
{
|
|
221
|
+
path: z.string().describe("Absolute or relative path to the file or directory to analyse"),
|
|
222
|
+
},
|
|
223
|
+
async ({ path }) => {
|
|
224
|
+
const result = await thubanGhosts({ path });
|
|
225
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
226
|
+
},
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
server.tool(
|
|
230
|
+
"thuban_clones",
|
|
231
|
+
"Detect copy-paste duplicate code clusters — exact duplicates, near-duplicates, and structural clones.",
|
|
232
|
+
{
|
|
233
|
+
path: z.string().describe("Absolute or relative path to the file or directory to analyse"),
|
|
234
|
+
},
|
|
235
|
+
async ({ path }) => {
|
|
236
|
+
const result = await thubanClones({ path });
|
|
237
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
238
|
+
},
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
server.tool(
|
|
242
|
+
"thuban_health",
|
|
243
|
+
"Get the overall codebase health and trust score — a single grade (A–F) with category breakdowns.",
|
|
244
|
+
{
|
|
245
|
+
path: z.string().describe("Absolute or relative path to the codebase root"),
|
|
246
|
+
},
|
|
247
|
+
async ({ path }) => {
|
|
248
|
+
const result = await thubanHealth({ path });
|
|
249
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
250
|
+
},
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
server.tool(
|
|
254
|
+
"thuban_taint",
|
|
255
|
+
"Run cross-file taint analysis — trace user input from sources (req.body, req.query, etc.) to sensitive sinks (SQL, shell, file system).",
|
|
256
|
+
{
|
|
257
|
+
path: z.string().describe("Absolute or relative path to the file or directory to analyse"),
|
|
258
|
+
},
|
|
259
|
+
async ({ path }) => {
|
|
260
|
+
const result = await thubanTaint({ path });
|
|
261
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
262
|
+
},
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
// ---------------------------------------------------------------------------
|
|
266
|
+
// Resource registrations
|
|
267
|
+
// ---------------------------------------------------------------------------
|
|
268
|
+
|
|
269
|
+
server.resource(
|
|
270
|
+
"rules",
|
|
271
|
+
"thuban://rules",
|
|
272
|
+
{
|
|
273
|
+
description: "List all 84 Thuban detection rules with IDs, names, severities, and descriptions",
|
|
274
|
+
mimeType: "application/json",
|
|
275
|
+
},
|
|
276
|
+
async () => ({
|
|
277
|
+
contents: [
|
|
278
|
+
{
|
|
279
|
+
uri: "thuban://rules",
|
|
280
|
+
mimeType: "application/json",
|
|
281
|
+
text: JSON.stringify(RULES, null, 2),
|
|
282
|
+
},
|
|
283
|
+
],
|
|
284
|
+
}),
|
|
285
|
+
);
|
|
286
|
+
|
|
287
|
+
server.resource(
|
|
288
|
+
"config",
|
|
289
|
+
"thuban://config",
|
|
290
|
+
{
|
|
291
|
+
description: "Current Thuban scanner configuration (severity thresholds, include/exclude globs, enabled rules)",
|
|
292
|
+
mimeType: "application/json",
|
|
293
|
+
},
|
|
294
|
+
async () => ({
|
|
295
|
+
contents: [
|
|
296
|
+
{
|
|
297
|
+
uri: "thuban://config",
|
|
298
|
+
mimeType: "application/json",
|
|
299
|
+
text: JSON.stringify(DEFAULT_CONFIG, null, 2),
|
|
300
|
+
},
|
|
301
|
+
],
|
|
302
|
+
}),
|
|
303
|
+
);
|
|
304
|
+
|
|
305
|
+
// ---------------------------------------------------------------------------
|
|
306
|
+
// Start
|
|
307
|
+
// ---------------------------------------------------------------------------
|
|
308
|
+
|
|
309
|
+
async function main() {
|
|
310
|
+
console.error("[thuban-mcp] Starting Thuban MCP Server v0.4.5 …");
|
|
311
|
+
const transport = new StdioServerTransport();
|
|
312
|
+
await server.connect(transport);
|
|
313
|
+
console.error("[thuban-mcp] Server connected and ready.");
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
main().catch((err) => {
|
|
317
|
+
console.error("[thuban-mcp] Fatal error:", err);
|
|
318
|
+
process.exit(1);
|
|
319
|
+
});
|
package/src/tools.js
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
|
|
4
|
+
const execFileAsync = promisify(execFile);
|
|
5
|
+
|
|
6
|
+
const TIMEOUT_MS = 60_000;
|
|
7
|
+
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
// Helpers
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Resolve the thuban CLI binary. Tries the global `thuban` first, then falls
|
|
14
|
+
* back to `npx thuban`. Returns { command, prefixArgs } so callers can build
|
|
15
|
+
* the full argv.
|
|
16
|
+
*/
|
|
17
|
+
async function resolveThuban() {
|
|
18
|
+
const isWin = process.platform === "win32";
|
|
19
|
+
const cmd = isWin ? "where" : "which";
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
await execFileAsync(cmd, ["thuban"], { timeout: 5_000 });
|
|
23
|
+
return { command: "thuban", prefixArgs: [] };
|
|
24
|
+
} catch {
|
|
25
|
+
// Global binary not found — fall back to npx
|
|
26
|
+
return { command: "npx", prefixArgs: ["thuban"] };
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Run a thuban CLI command and return parsed JSON output.
|
|
32
|
+
*
|
|
33
|
+
* @param {string[]} args CLI arguments *after* the `thuban` binary name.
|
|
34
|
+
* @returns {object} Parsed JSON from stdout.
|
|
35
|
+
*/
|
|
36
|
+
async function runThuban(args) {
|
|
37
|
+
const { command, prefixArgs } = await resolveThuban();
|
|
38
|
+
const fullArgs = [...prefixArgs, ...args];
|
|
39
|
+
|
|
40
|
+
console.error(`[thuban-mcp] exec: ${command} ${fullArgs.join(" ")}`);
|
|
41
|
+
|
|
42
|
+
const { stdout } = await execFileAsync(command, fullArgs, {
|
|
43
|
+
timeout: TIMEOUT_MS,
|
|
44
|
+
maxBuffer: 10 * 1024 * 1024, // 10 MB
|
|
45
|
+
windowsHide: true,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
return JSON.parse(stdout);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
// Tool implementations
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Scan a file or directory for code-quality / security issues.
|
|
57
|
+
*/
|
|
58
|
+
export async function thubanScan({ path, options }) {
|
|
59
|
+
try {
|
|
60
|
+
const args = ["scan", path, "--json"];
|
|
61
|
+
if (options?.severity) {
|
|
62
|
+
args.push("--severity", options.severity);
|
|
63
|
+
}
|
|
64
|
+
const result = await runThuban(args);
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
issues: result.issues ?? [],
|
|
68
|
+
filesScanned: result.filesScanned ?? result.files_scanned ?? 0,
|
|
69
|
+
trustScore: result.trustScore ?? result.trust_score ?? null,
|
|
70
|
+
summary: result.summary ?? {},
|
|
71
|
+
};
|
|
72
|
+
} catch (err) {
|
|
73
|
+
return { error: `thuban scan failed: ${err.message}` };
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Auto-fix issues in a file.
|
|
79
|
+
*/
|
|
80
|
+
export async function thubanFix({ path, ruleId }) {
|
|
81
|
+
try {
|
|
82
|
+
const args = ["fix", path, "--fix", "--json"];
|
|
83
|
+
if (ruleId) {
|
|
84
|
+
args.push("--rule", ruleId);
|
|
85
|
+
}
|
|
86
|
+
const result = await runThuban(args);
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
fixed: result.fixed ?? 0,
|
|
90
|
+
issues: result.issues ?? [],
|
|
91
|
+
};
|
|
92
|
+
} catch (err) {
|
|
93
|
+
return { error: `thuban fix failed: ${err.message}` };
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Check for AI-hallucinated APIs (non-existent methods, packages, etc.).
|
|
99
|
+
*/
|
|
100
|
+
export async function thubanHallucinate({ path }) {
|
|
101
|
+
try {
|
|
102
|
+
const result = await runThuban(["hallucinate", path, "--json"]);
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
hallucinations: result.hallucinations ?? [],
|
|
106
|
+
count: result.count ?? (result.hallucinations?.length ?? 0),
|
|
107
|
+
};
|
|
108
|
+
} catch (err) {
|
|
109
|
+
return { error: `thuban hallucinate failed: ${err.message}` };
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Find dead / unused code (ghost code).
|
|
115
|
+
*/
|
|
116
|
+
export async function thubanGhosts({ path }) {
|
|
117
|
+
try {
|
|
118
|
+
const result = await runThuban(["ghosts", path, "--json"]);
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
ghosts: result.ghosts ?? [],
|
|
122
|
+
count: result.count ?? (result.ghosts?.length ?? 0),
|
|
123
|
+
};
|
|
124
|
+
} catch (err) {
|
|
125
|
+
return { error: `thuban ghosts failed: ${err.message}` };
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Detect copy-paste duplicate code clusters.
|
|
131
|
+
*/
|
|
132
|
+
export async function thubanClones({ path }) {
|
|
133
|
+
try {
|
|
134
|
+
const result = await runThuban(["clones", path, "--json"]);
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
clusters: result.clusters ?? [],
|
|
138
|
+
count: result.count ?? (result.clusters?.length ?? 0),
|
|
139
|
+
};
|
|
140
|
+
} catch (err) {
|
|
141
|
+
return { error: `thuban clones failed: ${err.message}` };
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Get codebase health / trust score.
|
|
147
|
+
*/
|
|
148
|
+
export async function thubanHealth({ path }) {
|
|
149
|
+
try {
|
|
150
|
+
const result = await runThuban(["health", path, "--json"]);
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
score: result.score ?? null,
|
|
154
|
+
grade: result.grade ?? null,
|
|
155
|
+
details: result.details ?? {},
|
|
156
|
+
};
|
|
157
|
+
} catch (err) {
|
|
158
|
+
return { error: `thuban health failed: ${err.message}` };
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Run cross-file taint analysis.
|
|
164
|
+
*
|
|
165
|
+
* Uses `thuban scan` and filters for TAINT* rules.
|
|
166
|
+
*/
|
|
167
|
+
export async function thubanTaint({ path }) {
|
|
168
|
+
try {
|
|
169
|
+
const result = await runThuban(["scan", path, "--json"]);
|
|
170
|
+
const allIssues = result.issues ?? [];
|
|
171
|
+
const taintFlows = allIssues.filter(
|
|
172
|
+
(i) =>
|
|
173
|
+
(i.ruleId ?? i.rule_id ?? i.rule ?? "")
|
|
174
|
+
.toUpperCase()
|
|
175
|
+
.startsWith("TAINT"),
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
flows: taintFlows,
|
|
180
|
+
count: taintFlows.length,
|
|
181
|
+
};
|
|
182
|
+
} catch (err) {
|
|
183
|
+
return { error: `thuban taint failed: ${err.message}` };
|
|
184
|
+
}
|
|
185
|
+
}
|