mcp-license-audit 0.1.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/README.md +90 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +149 -0
- package/package.json +31 -0
package/README.md
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# mcp-license-audit
|
|
2
|
+
|
|
3
|
+
MCP server that audits your project's dependency licenses for compatibility issues. Flags GPL/AGPL conflicts and generates compliance reports.
|
|
4
|
+
|
|
5
|
+
## What It Does
|
|
6
|
+
|
|
7
|
+
- Parses a `package.json` file (dependencies + devDependencies)
|
|
8
|
+
- Fetches license info for each package from the npm registry
|
|
9
|
+
- Classifies licenses: permissive (MIT, Apache, BSD, ISC), copyleft (GPL, AGPL), weak-copyleft (LGPL, MPL), unknown
|
|
10
|
+
- Detects conflicts (e.g., GPL dependency in an MIT-licensed project)
|
|
11
|
+
- Returns a structured JSON report with risk level and summary
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install -g mcp-license-audit
|
|
17
|
+
# or run directly:
|
|
18
|
+
npx mcp-license-audit
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Configure in Claude Code
|
|
22
|
+
|
|
23
|
+
Add to your `.claude/mcp.json` or `~/.claude/mcp.json`:
|
|
24
|
+
|
|
25
|
+
```json
|
|
26
|
+
{
|
|
27
|
+
"mcpServers": {
|
|
28
|
+
"license-audit": {
|
|
29
|
+
"command": "npx",
|
|
30
|
+
"args": ["mcp-license-audit"]
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Or if installed globally:
|
|
37
|
+
|
|
38
|
+
```json
|
|
39
|
+
{
|
|
40
|
+
"mcpServers": {
|
|
41
|
+
"license-audit": {
|
|
42
|
+
"command": "mcp-license-audit"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Tool: `audit-licenses`
|
|
49
|
+
|
|
50
|
+
**Input:** `packageJson` — the full contents of a `package.json` file as a string.
|
|
51
|
+
|
|
52
|
+
**Output:** JSON report:
|
|
53
|
+
|
|
54
|
+
```json
|
|
55
|
+
{
|
|
56
|
+
"totalDependencies": 15,
|
|
57
|
+
"analyzed": 15,
|
|
58
|
+
"licenses": {
|
|
59
|
+
"MIT": ["express", "lodash"],
|
|
60
|
+
"Apache-2.0": ["typescript"],
|
|
61
|
+
"GPL-3.0": ["some-package"],
|
|
62
|
+
"unknown": ["private-pkg"]
|
|
63
|
+
},
|
|
64
|
+
"conflicts": [
|
|
65
|
+
{
|
|
66
|
+
"package": "some-package",
|
|
67
|
+
"license": "GPL-3.0",
|
|
68
|
+
"issue": "GPL dependency in MIT project — must open-source your code if distributed"
|
|
69
|
+
}
|
|
70
|
+
],
|
|
71
|
+
"riskLevel": "medium",
|
|
72
|
+
"summary": "15 deps analyzed. 1 GPL conflict found. 1 unknown license."
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**Risk levels:** `low` (no copyleft), `medium` (weak copyleft or many unknowns), `high` (GPL/AGPL found).
|
|
77
|
+
|
|
78
|
+
## Limits
|
|
79
|
+
|
|
80
|
+
- Analyzes first 20 dependencies for speed
|
|
81
|
+
- Only supports npm packages (no pip/cargo/gem support yet)
|
|
82
|
+
- License data comes from the npm registry — private packages return "unknown"
|
|
83
|
+
|
|
84
|
+
## Build from Source
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
npm install
|
|
88
|
+
npm run build
|
|
89
|
+
node dist/index.js
|
|
90
|
+
```
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import * as mcpcat from "mcpcat";
|
|
6
|
+
const PERMISSIVE = new Set(["MIT", "Apache-2.0", "BSD-2-Clause", "BSD-3-Clause", "ISC", "0BSD", "Unlicense", "CC0-1.0"]);
|
|
7
|
+
const COPYLEFT = new Set(["GPL-2.0", "GPL-3.0", "GPL-2.0-only", "GPL-3.0-only", "GPL-2.0-or-later", "GPL-3.0-or-later", "AGPL-3.0", "AGPL-3.0-only", "AGPL-3.0-or-later"]);
|
|
8
|
+
const WEAK_COPYLEFT = new Set(["LGPL-2.0", "LGPL-2.1", "LGPL-3.0", "LGPL-2.0-only", "LGPL-2.1-only", "LGPL-3.0-only", "MPL-2.0", "EUPL-1.1", "EUPL-1.2"]);
|
|
9
|
+
function categorize(license) {
|
|
10
|
+
if (!license || license === "UNKNOWN")
|
|
11
|
+
return "unknown";
|
|
12
|
+
const normalized = license.toUpperCase();
|
|
13
|
+
for (const l of PERMISSIVE)
|
|
14
|
+
if (l.toUpperCase() === normalized)
|
|
15
|
+
return "permissive";
|
|
16
|
+
for (const l of COPYLEFT)
|
|
17
|
+
if (l.toUpperCase() === normalized)
|
|
18
|
+
return "copyleft";
|
|
19
|
+
for (const l of WEAK_COPYLEFT)
|
|
20
|
+
if (l.toUpperCase() === normalized)
|
|
21
|
+
return "weak-copyleft";
|
|
22
|
+
if (normalized.includes("GPL") || normalized.includes("AGPL"))
|
|
23
|
+
return "copyleft";
|
|
24
|
+
if (normalized.includes("LGPL") || normalized.includes("MPL"))
|
|
25
|
+
return "weak-copyleft";
|
|
26
|
+
if (normalized.includes("MIT") || normalized.includes("BSD") || normalized.includes("APACHE") || normalized.includes("ISC"))
|
|
27
|
+
return "permissive";
|
|
28
|
+
return "unknown";
|
|
29
|
+
}
|
|
30
|
+
async function fetchLicense(pkg) {
|
|
31
|
+
try {
|
|
32
|
+
const res = await fetch(`https://registry.npmjs.org/${pkg}/latest`);
|
|
33
|
+
if (!res.ok)
|
|
34
|
+
return "unknown";
|
|
35
|
+
const data = await res.json();
|
|
36
|
+
if (!data.license)
|
|
37
|
+
return "unknown";
|
|
38
|
+
if (typeof data.license === "string")
|
|
39
|
+
return data.license;
|
|
40
|
+
if (typeof data.license === "object" && data.license.type)
|
|
41
|
+
return data.license.type;
|
|
42
|
+
return "unknown";
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return "unknown";
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
const server = new McpServer({
|
|
49
|
+
name: "mcp-license-audit",
|
|
50
|
+
version: "0.1.0",
|
|
51
|
+
});
|
|
52
|
+
// MCPcat analytics
|
|
53
|
+
const MCPCAT_PROJECT_ID = process.env["MCPCAT_PROJECT_ID"] ?? undefined;
|
|
54
|
+
if (MCPCAT_PROJECT_ID)
|
|
55
|
+
mcpcat.track(server, MCPCAT_PROJECT_ID);
|
|
56
|
+
server.tool("audit-licenses", "Audit your project's dependency licenses for compatibility issues. Flags GPL/AGPL conflicts and generates compliance reports.", {
|
|
57
|
+
packageJson: z.string().describe("Contents of a package.json file as a string"),
|
|
58
|
+
}, async ({ packageJson }) => {
|
|
59
|
+
let parsed;
|
|
60
|
+
try {
|
|
61
|
+
parsed = JSON.parse(packageJson);
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return {
|
|
65
|
+
content: [{ type: "text", text: JSON.stringify({ error: "Invalid package.json — could not parse JSON" }) }],
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
const deps = {
|
|
69
|
+
...(parsed.dependencies ?? {}),
|
|
70
|
+
...(parsed.devDependencies ?? {}),
|
|
71
|
+
};
|
|
72
|
+
const allPackages = Object.keys(deps);
|
|
73
|
+
const totalDependencies = allPackages.length;
|
|
74
|
+
const toAnalyze = allPackages.slice(0, 20);
|
|
75
|
+
const projectLicense = typeof parsed.license === "string" ? parsed.license : "unknown";
|
|
76
|
+
const projectCategory = categorize(projectLicense);
|
|
77
|
+
const licenseResults = await Promise.all(toAnalyze.map(async (pkg) => {
|
|
78
|
+
const license = await fetchLicense(pkg);
|
|
79
|
+
return { pkg, license, category: categorize(license) };
|
|
80
|
+
}));
|
|
81
|
+
const licenseMap = {};
|
|
82
|
+
for (const { pkg, license } of licenseResults) {
|
|
83
|
+
if (!licenseMap[license])
|
|
84
|
+
licenseMap[license] = [];
|
|
85
|
+
licenseMap[license].push(pkg);
|
|
86
|
+
}
|
|
87
|
+
const conflicts = [];
|
|
88
|
+
for (const { pkg, license, category } of licenseResults) {
|
|
89
|
+
if (category === "copyleft") {
|
|
90
|
+
const isSPDX = license.toUpperCase().includes("AGPL");
|
|
91
|
+
const licenseType = isSPDX ? "AGPL" : "GPL";
|
|
92
|
+
let issue = `${licenseType} dependency in ${projectLicense} project — must open-source your code if distributed`;
|
|
93
|
+
if (projectCategory === "permissive") {
|
|
94
|
+
conflicts.push({ package: pkg, license, issue });
|
|
95
|
+
}
|
|
96
|
+
else if (projectCategory === "unknown") {
|
|
97
|
+
conflicts.push({ package: pkg, license, issue: `${licenseType} dependency detected — verify compatibility with your project license` });
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (category === "weak-copyleft" && projectCategory === "permissive") {
|
|
101
|
+
conflicts.push({
|
|
102
|
+
package: pkg,
|
|
103
|
+
license,
|
|
104
|
+
issue: `Weak copyleft (${license}) — LGPL/MPL has linking requirements, review if you modify this library`,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const copyleftCount = licenseResults.filter((r) => r.category === "copyleft").length;
|
|
109
|
+
const weakCopyleftCount = licenseResults.filter((r) => r.category === "weak-copyleft").length;
|
|
110
|
+
const unknownCount = licenseResults.filter((r) => r.category === "unknown").length;
|
|
111
|
+
let riskLevel;
|
|
112
|
+
if (copyleftCount > 0) {
|
|
113
|
+
riskLevel = "high";
|
|
114
|
+
}
|
|
115
|
+
else if (weakCopyleftCount > 0 || unknownCount > 2) {
|
|
116
|
+
riskLevel = "medium";
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
riskLevel = "low";
|
|
120
|
+
}
|
|
121
|
+
const parts = [`${toAnalyze.length} deps analyzed.`];
|
|
122
|
+
if (copyleftCount > 0)
|
|
123
|
+
parts.push(`${copyleftCount} GPL/AGPL conflict${copyleftCount > 1 ? "s" : ""} found.`);
|
|
124
|
+
if (weakCopyleftCount > 0)
|
|
125
|
+
parts.push(`${weakCopyleftCount} weak-copyleft dep${weakCopyleftCount > 1 ? "s" : ""}.`);
|
|
126
|
+
if (unknownCount > 0)
|
|
127
|
+
parts.push(`${unknownCount} unknown license${unknownCount > 1 ? "s" : ""}.`);
|
|
128
|
+
if (conflicts.length === 0)
|
|
129
|
+
parts.push("No conflicts found.");
|
|
130
|
+
const report = {
|
|
131
|
+
totalDependencies,
|
|
132
|
+
analyzed: toAnalyze.length,
|
|
133
|
+
licenses: licenseMap,
|
|
134
|
+
conflicts,
|
|
135
|
+
riskLevel,
|
|
136
|
+
summary: parts.join(" "),
|
|
137
|
+
};
|
|
138
|
+
return {
|
|
139
|
+
content: [{ type: "text", text: JSON.stringify(report, null, 2) }],
|
|
140
|
+
};
|
|
141
|
+
});
|
|
142
|
+
async function main() {
|
|
143
|
+
const transport = new StdioServerTransport();
|
|
144
|
+
await server.connect(transport);
|
|
145
|
+
}
|
|
146
|
+
main().catch((err) => {
|
|
147
|
+
process.stderr.write(`Fatal: ${err}\n`);
|
|
148
|
+
process.exit(1);
|
|
149
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mcp-license-audit",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP server that audits your project's dependency licenses for compatibility issues. Flags GPL/AGPL conflicts and generates compliance reports.",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"build": "tsc",
|
|
7
|
+
"start": "node dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": ["dist"],
|
|
10
|
+
"keywords": ["mcp", "mcp-server", "license-audit", "compliance", "gpl", "dependency-check", "open-source", "license-checker"],
|
|
11
|
+
"author": "Timothy Cai",
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "https://github.com/linesatuni/agentic-waitlist.git",
|
|
16
|
+
"directory": "servers/mcp-license-audit"
|
|
17
|
+
},
|
|
18
|
+
"type": "module",
|
|
19
|
+
"bin": {
|
|
20
|
+
"mcp-license-audit": "./dist/index.js"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
24
|
+
"mcpcat": "^0.1.15",
|
|
25
|
+
"zod": "^4.3.6"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@types/node": "^25.5.2",
|
|
29
|
+
"typescript": "^6.0.2"
|
|
30
|
+
}
|
|
31
|
+
}
|