@intactfile/mcp 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/dist/index.js +146 -0
- package/package.json +28 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* IntactFile MCP server (stdio). Lets an AI agent repair corrupted files that
|
|
4
|
+
* stay on the user's machine — the engines run in-process, nothing is uploaded.
|
|
5
|
+
*
|
|
6
|
+
* Discovery is free and safe by construction: `detect_file` and `diagnose_file`
|
|
7
|
+
* are LOCAL and read-only (no network, no upload), so there is no server-side
|
|
8
|
+
* abuse surface and no rate limit is needed here. The `repair_file` tool is
|
|
9
|
+
* gated on an active Business subscription and fails closed when it can't be
|
|
10
|
+
* verified. The license token is read from the config the CLI writes
|
|
11
|
+
* (~/.config/intactfile/config.json); it is never printed or logged.
|
|
12
|
+
*
|
|
13
|
+
* NOTE: stdout is the JSON-RPC channel — never write to it directly.
|
|
14
|
+
*/
|
|
15
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
16
|
+
import { basename, dirname, join, parse } from "node:path";
|
|
17
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
18
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
19
|
+
import { z } from "zod";
|
|
20
|
+
import { ApiError, balance, consume, diagnosePath, EntitlementError, getToken, repairPath, requireBusiness, setToken, UnsupportedError, } from "@intactfile/node-core";
|
|
21
|
+
const text = (t) => ({ content: [{ type: "text", text: t }] });
|
|
22
|
+
const errText = (t) => ({ content: [{ type: "text", text: t }], isError: true });
|
|
23
|
+
const OUT_EXT = {
|
|
24
|
+
zip: ".zip",
|
|
25
|
+
pdf: ".pdf",
|
|
26
|
+
video: ".mp4",
|
|
27
|
+
jpeg: ".jpg",
|
|
28
|
+
png: ".png",
|
|
29
|
+
sqlite: ".sqlite",
|
|
30
|
+
archive: "",
|
|
31
|
+
};
|
|
32
|
+
function writeOut(input, out, r) {
|
|
33
|
+
if (r.kind === "archive") {
|
|
34
|
+
const dir = out ?? join(parse(input).dir, `${parse(input).name}.recovered`);
|
|
35
|
+
mkdirSync(dir, { recursive: true });
|
|
36
|
+
const written = [];
|
|
37
|
+
for (const f of r.files) {
|
|
38
|
+
if (f.status !== "ok")
|
|
39
|
+
continue;
|
|
40
|
+
const dest = join(dir, basename(f.name.replace(/[/\\]+/g, "_")) || "file");
|
|
41
|
+
mkdirSync(dirname(dest), { recursive: true });
|
|
42
|
+
writeFileSync(dest, f.data);
|
|
43
|
+
written.push(dest);
|
|
44
|
+
}
|
|
45
|
+
return written;
|
|
46
|
+
}
|
|
47
|
+
const p = parse(input);
|
|
48
|
+
const dest = out ?? join(p.dir, `${p.name}.repaired${p.ext || OUT_EXT[r.family]}`);
|
|
49
|
+
writeFileSync(dest, r.bytes);
|
|
50
|
+
return [dest];
|
|
51
|
+
}
|
|
52
|
+
async function meter() {
|
|
53
|
+
const token = getToken();
|
|
54
|
+
if (!token)
|
|
55
|
+
return;
|
|
56
|
+
try {
|
|
57
|
+
const res = await consume(token);
|
|
58
|
+
if (res.token)
|
|
59
|
+
setToken(res.token);
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
/* best effort — a delivered result is already the user's */
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const server = new McpServer({ name: "intactfile", version: "0.1.0" });
|
|
66
|
+
/* ------------------------------ free tools ----------------------------- */
|
|
67
|
+
server.tool("detect_file", "Identify a file's format and whether IntactFile can repair it. Free, fully local — no upload, no sign-in.", { path: z.string().describe("Absolute path to the file on disk") }, async ({ path }) => {
|
|
68
|
+
try {
|
|
69
|
+
const d = await diagnosePath(path);
|
|
70
|
+
return text(`Format: ${d.format}${d.family !== "unknown" ? ` (${d.family})` : ""}\n` +
|
|
71
|
+
`Confidence: ${d.confidence}\n` +
|
|
72
|
+
`Repairable: ${d.family !== "unknown" ? "yes — use repair_file (Business plan)" : "no"}`);
|
|
73
|
+
}
|
|
74
|
+
catch (e) {
|
|
75
|
+
return errText(e instanceof Error ? e.message : String(e));
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
server.tool("diagnose_file", "Diagnose a possibly-corrupted file: format, confidence, and notes on what's wrong. Free, fully local — no upload, no sign-in.", { path: z.string().describe("Absolute path to the file on disk") }, async ({ path }) => {
|
|
79
|
+
try {
|
|
80
|
+
const d = await diagnosePath(path);
|
|
81
|
+
const lines = [
|
|
82
|
+
`File: ${basename(path)}`,
|
|
83
|
+
`Format: ${d.format}${d.family !== "unknown" ? ` (${d.family})` : ""}`,
|
|
84
|
+
`Confidence: ${d.confidence}`,
|
|
85
|
+
d.family !== "unknown"
|
|
86
|
+
? "Verdict: repairable format — use repair_file (Business plan)."
|
|
87
|
+
: "Verdict: not a format IntactFile can repair.",
|
|
88
|
+
...d.notes.slice(0, 6).map((n) => `- ${n}`),
|
|
89
|
+
];
|
|
90
|
+
return text(lines.join("\n"));
|
|
91
|
+
}
|
|
92
|
+
catch (e) {
|
|
93
|
+
return errText(e instanceof Error ? e.message : String(e));
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
server.tool("account_status", "Show the signed-in IntactFile plan and remaining monthly repairs.", {}, async () => {
|
|
97
|
+
const token = getToken();
|
|
98
|
+
if (!token)
|
|
99
|
+
return text("Not signed in. Run `intactfile login` in a terminal.");
|
|
100
|
+
try {
|
|
101
|
+
const res = await balance(token);
|
|
102
|
+
if (res.token)
|
|
103
|
+
setToken(res.token);
|
|
104
|
+
const s = res.subscription;
|
|
105
|
+
return text(s
|
|
106
|
+
? `Plan: ${s.tier} · ${s.monthlyRemaining} repairs left this month · active until ${s.activeUntil.slice(0, 10)}`
|
|
107
|
+
: "No active subscription on this account.");
|
|
108
|
+
}
|
|
109
|
+
catch (e) {
|
|
110
|
+
return errText(e instanceof Error ? e.message : String(e));
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
/* --------------------------- business tool ---------------------------- */
|
|
114
|
+
server.tool("repair_file", "Repair a corrupted file locally and write the fixed file to disk. Requires an active IntactFile Business subscription (run `intactfile login` first). Files never leave this machine.", {
|
|
115
|
+
path: z.string().describe("Absolute path to the corrupted file"),
|
|
116
|
+
out: z.string().optional().describe("Output path (or directory for archives)"),
|
|
117
|
+
reference: z
|
|
118
|
+
.string()
|
|
119
|
+
.optional()
|
|
120
|
+
.describe("Healthy reference clip/photo (missing-moov video, JPEG donor)"),
|
|
121
|
+
password: z.string().optional().describe("Archive password (RAR/7z)"),
|
|
122
|
+
}, async ({ path, out, reference, password }) => {
|
|
123
|
+
try {
|
|
124
|
+
const sub = await requireBusiness();
|
|
125
|
+
if (sub.monthlyRemaining <= 0) {
|
|
126
|
+
return errText("Monthly repair allowance exhausted — it resets on your billing cycle.");
|
|
127
|
+
}
|
|
128
|
+
const r = await repairPath(path, { referencePath: reference, password });
|
|
129
|
+
if (!r.usable) {
|
|
130
|
+
return errText(`Couldn't produce a usable result (outcome: ${String(r.report.outcome ?? "failed")}). Nothing was charged.`);
|
|
131
|
+
}
|
|
132
|
+
const written = writeOut(path, out, r);
|
|
133
|
+
await meter();
|
|
134
|
+
return text(`Repaired (${r.family}, outcome ${String(r.report.outcome ?? "ok")}). Wrote:\n${written
|
|
135
|
+
.map((w) => `- ${w}`)
|
|
136
|
+
.join("\n")}`);
|
|
137
|
+
}
|
|
138
|
+
catch (e) {
|
|
139
|
+
if (e instanceof EntitlementError || e instanceof UnsupportedError || e instanceof ApiError) {
|
|
140
|
+
return errText(e.message);
|
|
141
|
+
}
|
|
142
|
+
return errText(e instanceof Error ? e.message : String(e));
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
const transport = new StdioServerTransport();
|
|
146
|
+
await server.connect(transport);
|
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@intactfile/mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "IntactFile MCP server — repair corrupted files from your AI agent, locally. Discovery + diagnosis are free; repair requires an active Business subscription. Files never leave your machine.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "UNLICENSED",
|
|
7
|
+
"bin": {
|
|
8
|
+
"intactfile-mcp": "./dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist"
|
|
12
|
+
],
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": ">=22"
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
18
|
+
"zod": "^3.23.8",
|
|
19
|
+
"@intactfile/node-core": "0.1.0"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@types/node": "^22.15.0",
|
|
23
|
+
"typescript": "^5.9.3"
|
|
24
|
+
},
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "tsc -p tsconfig.json"
|
|
27
|
+
}
|
|
28
|
+
}
|