@mrfentmen/technology-dependency-tree-index-mcp 0.0.0-stage → 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/dist/run.d.ts ADDED
@@ -0,0 +1,17 @@
1
+ export declare class ToolError extends Error {
2
+ constructor(message: string);
3
+ }
4
+ export declare function errorMessage(error: unknown): string;
5
+ export declare function text(value: string): {
6
+ content: {
7
+ type: "text";
8
+ text: string;
9
+ }[];
10
+ };
11
+ export declare function fail(message: string): {
12
+ isError: boolean;
13
+ content: {
14
+ type: "text";
15
+ text: string;
16
+ }[];
17
+ };
package/dist/run.js ADDED
@@ -0,0 +1,15 @@
1
+ export class ToolError extends Error {
2
+ constructor(message) {
3
+ super(message);
4
+ this.name = "ToolError";
5
+ }
6
+ }
7
+ export function errorMessage(error) {
8
+ return error instanceof Error ? error.message : String(error);
9
+ }
10
+ export function text(value) {
11
+ return { content: [{ type: "text", text: value }] };
12
+ }
13
+ export function fail(message) {
14
+ return { isError: true, content: [{ type: "text", text: message }] };
15
+ }
@@ -0,0 +1,3 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare const VERSION = "1.0.0";
3
+ export declare function createServer(): McpServer;
package/dist/server.js ADDED
@@ -0,0 +1,9 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { config } from "./config.js";
3
+ import { registerTools } from "./tools.js";
4
+ export const VERSION = "1.0.0";
5
+ export function createServer() {
6
+ const server = new McpServer({ name: config.name, version: VERSION });
7
+ registerTools(server);
8
+ return server;
9
+ }
@@ -0,0 +1,2 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerTools(server: McpServer): void;
package/dist/tools.js ADDED
@@ -0,0 +1,281 @@
1
+ import { z } from "zod";
2
+ import { analyze, changeSet, itemSchema, loadCollection, normalizeTarget, targetSchema, } from "./engine.js";
3
+ import { config } from "./config.js";
4
+ import { errorMessage, fail, text } from "./run.js";
5
+ const json = (value) => text(JSON.stringify(value, null, 2));
6
+ async function runAnalysis(args, build) {
7
+ try {
8
+ const loaded = await loadCollection(args);
9
+ const target = normalizeTarget(args.target);
10
+ const report = analyze(loaded, target);
11
+ return json(build(report, loaded, target));
12
+ }
13
+ catch (error) {
14
+ return fail(errorMessage(error));
15
+ }
16
+ }
17
+ const itemArray = z.array(itemSchema).max(500).optional().describe(`Items to audit as JSON objects. Use this, content, or path, not more than one.`);
18
+ const contentArg = z.string().max(2_000_000).optional().describe("Collection JSON as text. Must be an array or an object with an items array.");
19
+ const pathArg = z.string().min(1).max(2_000).optional().describe("Path to a collection JSON file. Must be an array or an object with an items array.");
20
+ const cwdArg = z.string().min(1).max(2_000).optional().describe("Base directory for path.");
21
+ const targetArg = targetSchema.optional().describe("Target overrides for item cap, budget, coverage and expiry window.");
22
+ const sourceShape = {
23
+ items: itemArray,
24
+ content: contentArg,
25
+ path: pathArg,
26
+ cwd: cwdArg,
27
+ target: targetArg,
28
+ };
29
+ const validateSourceShape = {
30
+ items: z.array(z.unknown()).max(500).optional().describe("Arbitrary collection JSON to validate. Invalid items are accepted so they can be reported."),
31
+ content: contentArg,
32
+ path: pathArg,
33
+ cwd: cwdArg,
34
+ };
35
+ const sourceObjectShape = {
36
+ items: itemArray,
37
+ content: contentArg,
38
+ path: pathArg,
39
+ cwd: cwdArg,
40
+ };
41
+ const sourceObjectSchema = z.object(sourceObjectShape);
42
+ const prefix = config.prefix;
43
+ const tool = {
44
+ inventory: `inventory_${prefix}`,
45
+ score: `score_${prefix}_readiness`,
46
+ coverage: `audit_${prefix}_coverage`,
47
+ gaps: `flag_${prefix}_gaps`,
48
+ risks: `rank_${prefix}_risks`,
49
+ constraints: `check_${prefix}_constraints`,
50
+ baseline: `compare_${prefix}_baseline`,
51
+ metric: `estimate_${prefix}_${config.metric.id}`,
52
+ quickWins: `find_${prefix}_quick_wins`,
53
+ dependencies: `map_${prefix}_dependencies`,
54
+ edgeCases: `test_${prefix}_edge_cases`,
55
+ findings: `summarize_${prefix}_findings`,
56
+ priorities: `prioritize_${prefix}_fixes`,
57
+ validate: `validate_${prefix}_inputs`,
58
+ duplicates: `detect_${prefix}_duplicates`,
59
+ variance: `measure_${prefix}_variance`,
60
+ benchmark: `benchmark_${prefix}_options`,
61
+ tradeoffs: `explain_${prefix}_tradeoffs`,
62
+ checklist: `draft_${prefix}_checklist`,
63
+ export: `export_${prefix}_audit`,
64
+ changes: `track_${prefix}_changes`,
65
+ history: `review_${prefix}_history`,
66
+ confidence: `score_${prefix}_confidence`,
67
+ next: `recommend_${prefix}_next_steps`,
68
+ questions: `open_${prefix}_questions`,
69
+ };
70
+ export function registerTools(server) {
71
+ server.tool(tool.inventory, `Count the ${config.entity} collection by category. Returns spend, quantity, freshness and ${config.metric.label.toLowerCase()}.`, sourceShape, async (args) => runAnalysis(args, (report) => ({
72
+ target: report.target,
73
+ items: report.itemCount,
74
+ categories: report.categoryCounts,
75
+ unknownCategories: report.unknownCategories,
76
+ totalCost: report.totalCost,
77
+ knownCostCount: report.knownCostCount,
78
+ totalQuantity: report.totalQuantity,
79
+ knownQuantityCount: report.knownQuantityCount,
80
+ expired: report.expired.length,
81
+ expiring: report.expiring.length,
82
+ metric: { id: config.metric.id, mean: report.metricMean, known: report.metricKnownCount },
83
+ })));
84
+ server.tool(tool.score, `Score ${config.entity} readiness from 0 to 100 for coverage, condition, data quality, freshness and ${config.metric.label.toLowerCase()}.`, sourceShape, async (args) => runAnalysis(args, (report) => ({
85
+ readiness: report.readiness,
86
+ confidence: report.confidence,
87
+ components: {
88
+ coverage: Number(report.coverageScore.toFixed(3)),
89
+ condition: Number(report.conditionScore.toFixed(3)),
90
+ data: Number(report.dataCompleteness.toFixed(3)),
91
+ metricPassRatio: report.metricKnownCount === 0 ? 0 : Number((report.metricPassCount / report.metricKnownCount).toFixed(3)),
92
+ },
93
+ benchmark: report.benchmark,
94
+ })));
95
+ server.tool(tool.coverage, `Show category coverage for the ${config.entity} collection as 0-1 ratios, plus unknown categories.`, sourceShape, async (args) => runAnalysis(args, (report) => ({
96
+ category: report.categoryCoverage,
97
+ unknownCategories: report.unknownCategories,
98
+ counts: report.categoryCounts,
99
+ })));
100
+ server.tool(tool.gaps, `List missing categories, expired items, expiring items and dependency gaps for the ${config.entity} collection.`, sourceShape, async (args) => runAnalysis(args, (report) => ({
101
+ count: report.gaps.length,
102
+ gaps: report.gaps,
103
+ })));
104
+ server.tool(tool.risks, `Rank risks: low coverage, expired stock, budget overruns, duplicates, worn items and missing data.`, sourceShape, async (args) => runAnalysis(args, (report) => ({
105
+ count: report.risks.length,
106
+ risks: report.risks,
107
+ })));
108
+ server.tool(tool.constraints, `Check the ${config.entity} collection against item cap, budget, coverage, expiry and metric targets.`, sourceShape, async (args) => runAnalysis(args, (report) => ({
109
+ pass: report.constraints.every((constraint) => constraint.pass),
110
+ constraints: report.constraints,
111
+ })));
112
+ server.tool(tool.baseline, `Compare category counts with the configured baseline and show surplus or shortfall.`, sourceShape, async (args) => runAnalysis(args, (report) => ({
113
+ current: report.categoryCounts,
114
+ baseline: Object.fromEntries(config.categories.map((category) => [category.id, category.baseline])),
115
+ deltas: Object.fromEntries(config.categories.map((category) => [
116
+ category.id,
117
+ (report.categoryCounts[category.id] ?? 0) - category.baseline,
118
+ ])),
119
+ readiness: report.readiness,
120
+ })));
121
+ server.tool(tool.metric, `Calculate ${config.metric.label.toLowerCase()} for every ${config.entity}, with mean and target pass count.`, sourceShape, async (args) => runAnalysis(args, (report) => ({
122
+ metric: config.metric,
123
+ mean: report.metricMean,
124
+ known: report.metricKnownCount,
125
+ passing: report.metricPassCount,
126
+ target: report.target,
127
+ rows: report.metricRows,
128
+ })));
129
+ server.tool(tool.quickWins, `Find low-effort fixes: small category gaps, missing data, expired items and duplicates.`, sourceShape, async (args) => runAnalysis(args, (report) => ({
130
+ count: report.quickWins.length,
131
+ quickWins: report.quickWins,
132
+ })));
133
+ server.tool(tool.dependencies, `Map collection dependencies: which configured groups are present, how many items cover them and what is missing.`, sourceShape, async (args) => runAnalysis(args, (report) => {
134
+ const dependencies = config.dependencies.map((dependency) => {
135
+ const counts = Object.fromEntries(dependency.categories.map((category) => [category, report.categoryCounts[category] ?? 0]));
136
+ const present = Object.values(counts).some((count) => count > 0);
137
+ return { id: dependency.id, label: dependency.label, categories: dependency.categories, counts, present };
138
+ });
139
+ return {
140
+ dependencies,
141
+ missing: dependencies.filter((dependency) => !dependency.present).map((dependency) => dependency.id),
142
+ };
143
+ }));
144
+ server.tool(tool.edgeCases, `Test edge cases: empty collection, one item, duplicate ids, zero cost, expired items, missing dates and unknown categories.`, sourceShape, async (args) => runAnalysis(args, (report, loaded) => ({
145
+ valid: loaded.issues.every((issue) => issue.severity !== "error"),
146
+ edgeCases: report.edgeCases,
147
+ dataIssues: loaded.issues,
148
+ })));
149
+ server.tool(tool.findings, `Summarize the ${config.entity} audit: counts, top gaps, top risks and data issues.`, sourceShape, async (args) => runAnalysis(args, (report) => ({
150
+ summary: report.summary,
151
+ topGaps: report.gaps.slice(0, 5),
152
+ topRisks: report.risks.slice(0, 5),
153
+ dataIssues: report.dataIssues,
154
+ })));
155
+ server.tool(tool.priorities, `Rank fixes by impact divided by effort. Returns a shortlist and the next five actions.`, sourceShape, async (args) => runAnalysis(args, (report) => ({
156
+ fixes: report.fixes,
157
+ nextSteps: report.nextSteps,
158
+ })));
159
+ server.tool(tool.validate, `Validate arbitrary ${config.entity} JSON, report schema errors and warnings, and count valid items.`, validateSourceShape, async (args) => {
160
+ try {
161
+ const loaded = await loadCollection(args);
162
+ const report = analyze(loaded);
163
+ return json({
164
+ valid: loaded.issues.every((issue) => issue.severity !== "error"),
165
+ rawCount: loaded.rawCount,
166
+ loadedCount: report.itemCount,
167
+ issues: loaded.issues,
168
+ duplicates: report.duplicates,
169
+ });
170
+ }
171
+ catch (error) {
172
+ return fail(errorMessage(error));
173
+ }
174
+ });
175
+ server.tool(tool.duplicates, `Group ${config.entity} items with the same category and normalized name. Shows ids and extra copies.`, sourceShape, async (args) => runAnalysis(args, (report) => ({
176
+ count: report.duplicates.length,
177
+ wastedItems: report.duplicates.reduce((total, group) => total + group.count - 1, 0),
178
+ duplicates: report.duplicates,
179
+ })));
180
+ server.tool(tool.variance, `Measure ${config.metric.label.toLowerCase()} spread, category-count variance and tag diversity.`, sourceShape, async (args) => runAnalysis(args, (report) => ({
181
+ variance: report.variance,
182
+ metricMean: report.metricMean,
183
+ readiness: report.readiness,
184
+ })));
185
+ server.tool(tool.benchmark, `Benchmark lean, balanced and full collection sizes, plus readiness, coverage, metric and data targets.`, sourceShape, async (args) => runAnalysis(args, (report) => ({
186
+ target: report.target,
187
+ benchmark: report.benchmark,
188
+ options: report.options,
189
+ })));
190
+ server.tool(tool.tradeoffs, `Explain the real tradeoffs: size vs coverage, cost vs use, freshness vs stock and data quality vs speed.`, sourceShape, async (args) => runAnalysis(args, (report) => ({
191
+ tradeoffs: report.tradeoffs,
192
+ })));
193
+ server.tool(tool.checklist, `Draft a practical checklist from gaps, high risks, missing data and budget.`, sourceShape, async (args) => runAnalysis(args, (report) => ({
194
+ count: report.checklist.length,
195
+ checklist: report.checklist,
196
+ })));
197
+ server.tool(tool.export, `Export the full ${config.entity} audit: target, metrics, gaps, risks, constraints, duplicates, benchmark and next steps.`, sourceShape, async (args) => runAnalysis(args, (report, loaded) => ({
198
+ ...report,
199
+ source: loaded.source,
200
+ rawCount: loaded.rawCount,
201
+ })));
202
+ server.tool(tool.changes, `Compare two ${config.entity} snapshots by item id and return added, removed and changed items.`, {
203
+ before: sourceObjectSchema.describe("The earlier collection snapshot."),
204
+ after: sourceObjectSchema.describe("The newer collection snapshot."),
205
+ }, async (args) => {
206
+ try {
207
+ const before = await loadCollection(args.before);
208
+ const after = await loadCollection(args.after);
209
+ return json(changeSet(before, after));
210
+ }
211
+ catch (error) {
212
+ return fail(errorMessage(error));
213
+ }
214
+ });
215
+ server.tool(tool.history, `Review dated ${config.entity} snapshots and return readiness, cost and metric trends from first to last.`, {
216
+ history: z
217
+ .array(z.object({
218
+ date: z.string().min(1).max(40),
219
+ items: z.array(itemSchema).max(500),
220
+ }))
221
+ .min(1)
222
+ .max(60),
223
+ target: targetArg,
224
+ }, async (args) => {
225
+ try {
226
+ const target = normalizeTarget(args.target);
227
+ const ordered = [...args.history].sort((left, right) => left.date.localeCompare(right.date));
228
+ const points = [];
229
+ for (const snapshot of ordered) {
230
+ const loaded = await loadCollection({ items: snapshot.items });
231
+ const report = analyze(loaded, target);
232
+ points.push({
233
+ date: snapshot.date,
234
+ items: report.itemCount,
235
+ readiness: report.readiness,
236
+ confidence: report.confidence,
237
+ totalCost: report.totalCost,
238
+ metricMean: report.metricMean,
239
+ gaps: report.gaps.length,
240
+ risks: report.risks.length,
241
+ });
242
+ }
243
+ const first = points[0];
244
+ const last = points[points.length - 1];
245
+ return json({
246
+ points,
247
+ delta: points.length > 1
248
+ ? {
249
+ items: last.items - first.items,
250
+ readiness: last.readiness - first.readiness,
251
+ confidence: last.confidence - first.confidence,
252
+ totalCost: Number((last.totalCost - first.totalCost).toFixed(2)),
253
+ metricMean: Number((last.metricMean - first.metricMean).toFixed(2)),
254
+ gaps: last.gaps - first.gaps,
255
+ risks: last.risks - first.risks,
256
+ }
257
+ : null,
258
+ });
259
+ }
260
+ catch (error) {
261
+ return fail(errorMessage(error));
262
+ }
263
+ });
264
+ server.tool(tool.confidence, `Score how much the audit can be trusted from data completeness, item count, cost coverage and quantity coverage.`, sourceShape, async (args) => runAnalysis(args, (report) => ({
265
+ confidence: report.confidence,
266
+ dataCompleteness: report.dataCompleteness,
267
+ itemCount: report.itemCount,
268
+ knownCostCount: report.knownCostCount,
269
+ knownQuantityCount: report.knownQuantityCount,
270
+ coverageScore: report.coverageScore,
271
+ conditionScore: report.conditionScore,
272
+ })));
273
+ server.tool(tool.next, `Recommend the next five actions from the ranked fix list, with quick wins alongside.`, sourceShape, async (args) => runAnalysis(args, (report) => ({
274
+ nextSteps: report.nextSteps,
275
+ quickWins: report.quickWins.slice(0, 3),
276
+ })));
277
+ server.tool(tool.questions, `List open questions that block a better audit: budget, data, expiry, duplicates and unknown categories.`, sourceShape, async (args) => runAnalysis(args, (report) => ({
278
+ questions: report.questions,
279
+ openEdgeCases: report.edgeCases,
280
+ })));
281
+ }
package/package.json CHANGED
@@ -1,6 +1,54 @@
1
1
  {
2
2
  "name": "@mrfentmen/technology-dependency-tree-index-mcp",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.0.0",
4
+ "description": "Archive dependency tree by category, cost, coverage and dependency gaps. Real local JSON analysis, 25 MCP tools, no paywall.",
5
+ "type": "module",
6
+ "mcpName": "io.github.mrfentmen/technology-dependency-tree-index-mcp",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/mrfentmen/awesome-servers.git",
10
+ "directory": "mcp/servers/technology-dependency-tree-index-mcp"
11
+ },
12
+ "bin": {
13
+ "technology-dependency-tree-index-mcp": "./dist/index.js"
14
+ },
15
+ "main": "./dist/index.js",
16
+ "files": [
17
+ "dist",
18
+ "server.json",
19
+ "README.md"
20
+ ],
21
+ "scripts": {
22
+ "build": "tsc -p tsconfig.json",
23
+ "test": "npm run build && node --test tests/tools.test.mjs",
24
+ "prepack": "npm run build",
25
+ "start": "node dist/index.js"
26
+ },
27
+ "keywords": [
28
+ "mcp",
29
+ "model-context-protocol",
30
+ "audit",
31
+ "dependencies",
32
+ "packages",
33
+ "supply-chain",
34
+ "licence",
35
+ "developer-tools",
36
+ "archive"
37
+ ],
38
+ "author": "mrfentmen",
39
+ "license": "MIT",
40
+ "engines": {
41
+ "node": ">=18"
42
+ },
43
+ "dependencies": {
44
+ "@modelcontextprotocol/sdk": "^1.0.4",
45
+ "zod": "^3.23.8"
46
+ },
47
+ "devDependencies": {
48
+ "@types/node": "^22.0.0",
49
+ "typescript": "^5.6.0"
50
+ },
51
+ "publishConfig": {
52
+ "access": "public"
53
+ }
54
+ }
package/server.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
3
+ "name": "io.github.mrfentmen/technology-dependency-tree-index-mcp",
4
+ "title": "Dependency Tree Archive",
5
+ "description": "Archive dependency tree by category, cost, coverage and dependency gaps. Real local JSON analysis, 25 MCP tools, no paywall.",
6
+ "version": "1.0.0",
7
+ "repository": {
8
+ "url": "https://github.com/mrfentmen/awesome-servers",
9
+ "source": "github"
10
+ },
11
+ "packages": [
12
+ {
13
+ "registryType": "npm",
14
+ "identifier": "@mrfentmen/technology-dependency-tree-index-mcp",
15
+ "version": "1.0.0",
16
+ "transport": {
17
+ "type": "stdio"
18
+ }
19
+ }
20
+ ]
21
+ }