dependency-license-change-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/README.md +17 -0
- package/dist/core.d.ts +19 -0
- package/dist/core.js +28 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +4 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.js +22 -0
- package/package.json +42 -0
- package/server.json +20 -0
package/README.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# dependency-license-change-mcp
|
|
2
|
+
|
|
3
|
+
Dependency License Change compares license evidence across two local snapshots and reports category changes without exposing package names or license text.
|
|
4
|
+
|
|
5
|
+
## Quick start
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install
|
|
9
|
+
npm test
|
|
10
|
+
npm start
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Set `LICENSE_CHANGE_ROOT`, then call `compare_license_evidence` with `before` and `after` snapshot paths.
|
|
14
|
+
|
|
15
|
+
## Limits
|
|
16
|
+
|
|
17
|
+
Only aggregate categories and counts are returned. Classification is conservative and is not legal advice.
|
package/dist/core.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export declare class LicenseChangeError extends Error {
|
|
2
|
+
}
|
|
3
|
+
export declare function compareLicenseEvidence(input: {
|
|
4
|
+
before: string;
|
|
5
|
+
after: string;
|
|
6
|
+
}): Promise<{
|
|
7
|
+
beforeFiles: number;
|
|
8
|
+
afterFiles: number;
|
|
9
|
+
changedCategoryCount: number;
|
|
10
|
+
categoryChanges: {
|
|
11
|
+
[k: string]: {
|
|
12
|
+
before: number;
|
|
13
|
+
after: number;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
valueFree: boolean;
|
|
17
|
+
warning: string;
|
|
18
|
+
}>;
|
|
19
|
+
export declare function format(v: unknown): string;
|
package/dist/core.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { readdir, readFile, realpath, stat } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
export class LicenseChangeError extends Error {
|
|
4
|
+
}
|
|
5
|
+
;
|
|
6
|
+
const skip = new Set([".git", "node_modules", "dist", "build"]);
|
|
7
|
+
const classify = (t) => /apache\s*2|apache-2/i.test(t) ? "apache-2" : /mit license|spdx: mit|license: mit/i.test(t) ? "mit" : /gpl/i.test(t) ? "gpl-family" : /bsd/i.test(t) ? "bsd-family" : /isc/i.test(t) ? "isc" : /unlicense/i.test(t) ? "unlicense" : "unknown";
|
|
8
|
+
async function root(input) { const base = process.env.LICENSE_CHANGE_ROOT ?? path.join(process.cwd(), ".."); const root = await realpath(path.resolve(base)).catch(() => path.resolve(base)); const req = path.isAbsolute(input) ? path.resolve(input) : path.resolve(root, input); const target = await realpath(req).catch(() => req); const rel = path.relative(root, target); if (rel.startsWith("..") || path.isAbsolute(rel))
|
|
9
|
+
throw new LicenseChangeError("Snapshot path must stay inside the configured root"); if (!(await stat(target).catch(() => null)))
|
|
10
|
+
throw new LicenseChangeError("Snapshot path does not exist"); return target; }
|
|
11
|
+
async function scan(input) { const base = await root(input), counts = {}; let files = 0; async function walk(dir, d) { if (d > 5 || files > 500)
|
|
12
|
+
return; for (const e of await readdir(dir, { withFileTypes: true }).catch(() => [])) {
|
|
13
|
+
if (skip.has(e.name))
|
|
14
|
+
continue;
|
|
15
|
+
const full = path.join(dir, e.name);
|
|
16
|
+
if (e.isDirectory())
|
|
17
|
+
await walk(full, d + 1);
|
|
18
|
+
else if (/^(license|copying|notice)(?:\.|$)/i.test(e.name) || /package\.(json|lock)$/.test(e.name)) {
|
|
19
|
+
const b = await readFile(full).catch(() => Buffer.alloc(0));
|
|
20
|
+
if (b.byteLength > 1000000)
|
|
21
|
+
continue;
|
|
22
|
+
files++;
|
|
23
|
+
const k = classify(b.toString());
|
|
24
|
+
counts[k] = (counts[k] || 0) + 1;
|
|
25
|
+
}
|
|
26
|
+
} } await walk(base, 0); return { files, counts }; }
|
|
27
|
+
export async function compareLicenseEvidence(input) { const [before, after] = await Promise.all([scan(input.before), scan(input.after)]); const cats = [...new Set([...Object.keys(before.counts), ...Object.keys(after.counts)])].sort(); const changes = Object.fromEntries(cats.filter(k => (before.counts[k] || 0) !== (after.counts[k] || 0)).map(k => [k, { before: before.counts[k] || 0, after: after.counts[k] || 0 }])); return { beforeFiles: before.files, afterFiles: after.files, changedCategoryCount: Object.keys(changes).length, categoryChanges: changes, valueFree: true, warning: "Only aggregate license categories are returned. Package names, license text, paths, and source contents are never emitted; this is not legal advice." }; }
|
|
28
|
+
export function format(v) { return JSON.stringify(v, null, 2).slice(0, 12000); }
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
2
|
+
import { createServer } from "./server.js";
|
|
3
|
+
const main = async () => { const server = createServer(); await server.connect(new StdioServerTransport()); console.error("MCP server running on stdio"); };
|
|
4
|
+
main().catch((error) => { console.error("Fatal error:", error); process.exit(1); });
|
package/dist/server.d.ts
ADDED
package/dist/server.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { format, compareLicenseEvidence } from "./core.js";
|
|
4
|
+
const textError = (t) => ({ content: [{ type: "text", text: t }], isError: true });
|
|
5
|
+
const READ_ONLY = { readOnlyHint: true, openWorldHint: true };
|
|
6
|
+
const text = (value) => ({ content: [{ type: "text", text: value }] });
|
|
7
|
+
const errorText = (error) => text(`Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
8
|
+
export function createServer() {
|
|
9
|
+
const server = new McpServer({ name: "dependency-license-change-mcp", version: "1.0.0" });
|
|
10
|
+
server.registerTool("compare_license_evidence", {
|
|
11
|
+
title: "Compare license evidence",
|
|
12
|
+
description: "Compare local license evidence across two snapshots without returning package names, text, paths, or source.",
|
|
13
|
+
inputSchema: z.object({ before: z.string().min(1).max(1000), after: z.string().min(1).max(1000) }),
|
|
14
|
+
annotations: READ_ONLY,
|
|
15
|
+
}, async (input) => { try {
|
|
16
|
+
return text(format(await compareLicenseEvidence(input)));
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
return errorText(error);
|
|
20
|
+
} });
|
|
21
|
+
return server;
|
|
22
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dependency-license-change-mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Compare local license evidence across two snapshots without returning package names, text, paths, or source.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"mcpName": "io.github.mrfentmen/dependency-license-change-mcp",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/mrfentmen/dependency-license-change-mcp.git"
|
|
10
|
+
},
|
|
11
|
+
"bin": {
|
|
12
|
+
"dependency-license-change-mcp": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"main": "./dist/index.js",
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"server.json",
|
|
18
|
+
"README.md"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsc -p tsconfig.json",
|
|
22
|
+
"start": "node dist/index.js",
|
|
23
|
+
"test": "npm run build && node test.mjs"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"mcp",
|
|
27
|
+
"local-first",
|
|
28
|
+
"read-only"
|
|
29
|
+
],
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@modelcontextprotocol/sdk": "^1.0.4",
|
|
33
|
+
"zod": "^3.23.8"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@types/node": "^22.0.0",
|
|
37
|
+
"typescript": "^5.6.0"
|
|
38
|
+
},
|
|
39
|
+
"engines": {
|
|
40
|
+
"node": ">=20"
|
|
41
|
+
}
|
|
42
|
+
}
|
package/server.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
|
+
"name": "io.github.mrfentmen/dependency-license-change-mcp",
|
|
4
|
+
"description": "Compare local license evidence across two snapshots without returning package names, text,...",
|
|
5
|
+
"repository": {
|
|
6
|
+
"url": "https://github.com/mrfentmen/dependency-license-change-mcp",
|
|
7
|
+
"source": "github"
|
|
8
|
+
},
|
|
9
|
+
"version": "1.0.0",
|
|
10
|
+
"packages": [
|
|
11
|
+
{
|
|
12
|
+
"registryType": "npm",
|
|
13
|
+
"identifier": "dependency-license-change-mcp",
|
|
14
|
+
"version": "1.0.0",
|
|
15
|
+
"transport": {
|
|
16
|
+
"type": "stdio"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
]
|
|
20
|
+
}
|