silicon-transfer-mcp-server 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.
@@ -0,0 +1,230 @@
1
+ /**
2
+ * Werkzeug-Registratur: alle Silicon-Transfer-Tools an den MCP-Server anmelden.
3
+ * Transfers liefern ihr Urteil (proof) direkt im Ergebnis mit.
4
+ */
5
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
6
+ import { z } from "zod";
7
+ import { ConnectionManager } from "../services/connection.js";
8
+ import { localSha256, judge, type ProofMode } from "../services/proof.js";
9
+
10
+ type ToolResult = {
11
+ content: Array<{ type: "text"; text: string }>;
12
+ structuredContent?: Record<string, unknown>;
13
+ isError?: boolean;
14
+ };
15
+
16
+ function ok(output: Record<string, unknown>): ToolResult {
17
+ return {
18
+ content: [{ type: "text", text: JSON.stringify(output, null, 2) }],
19
+ structuredContent: output,
20
+ };
21
+ }
22
+
23
+ function fail(err: unknown, hint: string): ToolResult {
24
+ const msg = err instanceof Error ? err.message : String(err);
25
+ const output = { error: msg, hint };
26
+ return {
27
+ content: [{ type: "text", text: JSON.stringify(output, null, 2) }],
28
+ structuredContent: output,
29
+ isError: true,
30
+ };
31
+ }
32
+
33
+ const proofSchema = z
34
+ .enum(["hash", "size", "none"])
35
+ .default("hash")
36
+ .describe("Beweisstufe: hash = SHA-256-Rueckvergleich (Standard), size = nur Groesse, none = ohne Urteil");
37
+
38
+ export function registerAll(server: McpServer, cm: ConnectionManager): void {
39
+ server.registerTool(
40
+ "silicon_connect",
41
+ {
42
+ title: "Verbindung aufbauen",
43
+ description:
44
+ "Verbindet zu einem FTP-, FTPS- oder SFTP-Server. SFTP unterstuetzt Passwort oder SSH-Key (privateKeyPath).",
45
+ inputSchema: {
46
+ protocol: z.enum(["ftp", "ftps", "sftp"]).describe("Uebertragungsprotokoll"),
47
+ host: z.string().describe("Hostname oder IP"),
48
+ port: z.number().int().optional().describe("Port (Standard: 21 fuer ftp/ftps, 22 fuer sftp)"),
49
+ username: z.string().describe("Benutzername"),
50
+ password: z.string().optional().describe("Passwort (bei SSH-Key optional)"),
51
+ privateKeyPath: z.string().optional().describe("Pfad zu einem privaten SSH-Key (nur sftp)"),
52
+ passphrase: z.string().optional().describe("Passphrase des SSH-Keys, falls verschluesselt"),
53
+ },
54
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
55
+ },
56
+ async (a) => {
57
+ try {
58
+ await cm.connect(a);
59
+ return ok({ status: "verbunden", target: cm.target });
60
+ } catch (e) {
61
+ return fail(e, "Host, Port, Zugangsdaten und Protokoll pruefen.");
62
+ }
63
+ }
64
+ );
65
+
66
+ server.registerTool(
67
+ "silicon_disconnect",
68
+ {
69
+ title: "Verbindung trennen",
70
+ description: "Trennt die aktive Verbindung sauber.",
71
+ inputSchema: {},
72
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
73
+ },
74
+ async () => {
75
+ const was = cm.target;
76
+ await cm.disconnect();
77
+ return ok({ status: "getrennt", war: was });
78
+ }
79
+ );
80
+
81
+ server.registerTool(
82
+ "silicon_status",
83
+ {
84
+ title: "Verbindungsstatus",
85
+ description: "Zeigt, ob und wohin aktuell eine Verbindung besteht.",
86
+ inputSchema: {},
87
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
88
+ },
89
+ async () => ok({ verbunden: cm.connected, target: cm.target })
90
+ );
91
+
92
+ server.registerTool(
93
+ "silicon_list_dir",
94
+ {
95
+ title: "Verzeichnis auflisten",
96
+ description: "Listet ein Remote-Verzeichnis mit Typ, Groesse und Aenderungsdatum.",
97
+ inputSchema: { path: z.string().describe("Remote-Pfad, z.B. /home/user/www") },
98
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
99
+ },
100
+ async ({ path }) => {
101
+ try {
102
+ const entries = await cm.list(path);
103
+ return ok({ path, count: entries.length, entries });
104
+ } catch (e) {
105
+ return fail(e, "Pfad pruefen; fuehrender / noetig? Verbindung aktiv?");
106
+ }
107
+ }
108
+ );
109
+
110
+ server.registerTool(
111
+ "silicon_upload_file",
112
+ {
113
+ title: "Datei hochladen (mit Beweis)",
114
+ description:
115
+ "Laedt eine lokale Datei hoch und liefert das Transfer-Urteil mit: PROVEN nur, wenn der SHA-256 der Remote-Datei nach dem Upload dem lokalen entspricht.",
116
+ inputSchema: {
117
+ localPath: z.string().describe("Lokaler Quellpfad"),
118
+ remotePath: z.string().describe("Remote-Zielpfad inkl. Dateiname"),
119
+ proof: proofSchema,
120
+ },
121
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
122
+ },
123
+ async ({ localPath, remotePath, proof }) => {
124
+ try {
125
+ const t0 = Date.now();
126
+ const local = await localSha256(localPath);
127
+ await cm.upload(localPath, remotePath);
128
+ const remote =
129
+ proof === "hash"
130
+ ? await cm.remoteSha256(remotePath)
131
+ : proof === "size"
132
+ ? { sha256: undefined, bytes: await cm.remoteSize(remotePath) }
133
+ : { sha256: undefined, bytes: undefined };
134
+ const urteil = judge(local, remote, proof as ProofMode);
135
+ return ok({ localPath, remotePath, duration_ms: Date.now() - t0, proof: urteil });
136
+ } catch (e) {
137
+ return fail(e, "Lokalen Pfad, Remote-Rechte und Verbindung pruefen.");
138
+ }
139
+ }
140
+ );
141
+
142
+ server.registerTool(
143
+ "silicon_download_file",
144
+ {
145
+ title: "Datei herunterladen (mit Beweis)",
146
+ description:
147
+ "Laedt eine Remote-Datei herunter und liefert das Transfer-Urteil mit: PROVEN nur bei identischem SHA-256 zwischen Remote-Quelle und lokaler Kopie.",
148
+ inputSchema: {
149
+ remotePath: z.string().describe("Remote-Quellpfad"),
150
+ localPath: z.string().describe("Lokaler Zielpfad inkl. Dateiname"),
151
+ proof: proofSchema,
152
+ },
153
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
154
+ },
155
+ async ({ remotePath, localPath, proof }) => {
156
+ try {
157
+ const t0 = Date.now();
158
+ await cm.download(remotePath, localPath);
159
+ const local = await localSha256(localPath);
160
+ const remote =
161
+ proof === "hash"
162
+ ? await cm.remoteSha256(remotePath)
163
+ : proof === "size"
164
+ ? { sha256: undefined, bytes: await cm.remoteSize(remotePath) }
165
+ : { sha256: undefined, bytes: undefined };
166
+ const urteil = judge(local, remote, proof as ProofMode);
167
+ return ok({ remotePath, localPath, duration_ms: Date.now() - t0, proof: urteil });
168
+ } catch (e) {
169
+ return fail(e, "Remote-Pfad, lokale Schreibrechte und Verbindung pruefen.");
170
+ }
171
+ }
172
+ );
173
+
174
+ server.registerTool(
175
+ "silicon_delete_file",
176
+ {
177
+ title: "Remote-Datei loeschen",
178
+ description: "Loescht eine Datei auf dem Server. Endgueltig - vorher mit silicon_list_dir pruefen.",
179
+ inputSchema: { remotePath: z.string().describe("Remote-Pfad der zu loeschenden Datei") },
180
+ annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
181
+ },
182
+ async ({ remotePath }) => {
183
+ try {
184
+ await cm.remove(remotePath);
185
+ return ok({ status: "geloescht", remotePath });
186
+ } catch (e) {
187
+ return fail(e, "Existiert die Datei? Rechte vorhanden?");
188
+ }
189
+ }
190
+ );
191
+
192
+ server.registerTool(
193
+ "silicon_make_dir",
194
+ {
195
+ title: "Remote-Verzeichnis anlegen",
196
+ description: "Legt ein Verzeichnis auf dem Server an (rekursiv moeglich).",
197
+ inputSchema: {
198
+ remotePath: z.string().describe("Anzulegender Remote-Pfad"),
199
+ recursive: z.boolean().default(true).describe("Elternverzeichnisse mit anlegen"),
200
+ },
201
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
202
+ },
203
+ async ({ remotePath, recursive }) => {
204
+ try {
205
+ await cm.mkdir(remotePath, recursive);
206
+ return ok({ status: "angelegt", remotePath });
207
+ } catch (e) {
208
+ return fail(e, "Rechte pruefen; existiert der Pfad bereits?");
209
+ }
210
+ }
211
+ );
212
+
213
+ server.registerTool(
214
+ "silicon_file_info",
215
+ {
216
+ title: "Datei-Steckbrief",
217
+ description: "Groesse und SHA-256 einer Remote-Datei - der Fingerabdruck ohne Download auf Platte.",
218
+ inputSchema: { remotePath: z.string().describe("Remote-Pfad der Datei") },
219
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
220
+ },
221
+ async ({ remotePath }) => {
222
+ try {
223
+ const r = await cm.remoteSha256(remotePath);
224
+ return ok({ remotePath, bytes: r.bytes, sha256: r.sha256 });
225
+ } catch (e) {
226
+ return fail(e, "Existiert die Datei? Verbindung aktiv?");
227
+ }
228
+ }
229
+ );
230
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "Node16",
5
+ "moduleResolution": "Node16",
6
+ "outDir": "./dist",
7
+ "rootDir": "./src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "declaration": false,
13
+ "sourceMap": false
14
+ },
15
+ "include": ["src/**/*"],
16
+ "exclude": ["node_modules", "dist"]
17
+ }