193 lines
4.2 KiB
JavaScript
193 lines
4.2 KiB
JavaScript
import { spawnSync } from "node:child_process";
|
|
import { existsSync } from "node:fs";
|
|
import path from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
import { z } from "zod";
|
|
|
|
const __filename = fileURLToPath(import.meta.url);
|
|
const __dirname = path.dirname(__filename);
|
|
const defaultBinary = path.join(__dirname, "build", "m2pack");
|
|
|
|
function resolveBinary() {
|
|
const envBinary = process.env.M2PACK_BINARY;
|
|
if (envBinary && existsSync(envBinary)) {
|
|
return envBinary;
|
|
}
|
|
if (existsSync(defaultBinary)) {
|
|
return defaultBinary;
|
|
}
|
|
return "m2pack";
|
|
}
|
|
|
|
function runCli(args) {
|
|
const binary = resolveBinary();
|
|
const proc = spawnSync(binary, [...args, "--json"], {
|
|
cwd: __dirname,
|
|
encoding: "utf-8",
|
|
});
|
|
|
|
if (proc.error) {
|
|
throw new Error(`m2pack failed to start: ${proc.error.message}`);
|
|
}
|
|
|
|
if (proc.status !== 0) {
|
|
const detail = (proc.stderr || proc.stdout || `exit code ${proc.status}`).trim();
|
|
throw new Error(`m2pack failed: ${detail}`);
|
|
}
|
|
|
|
const stdout = proc.stdout.trim();
|
|
if (!stdout) {
|
|
return { ok: true };
|
|
}
|
|
|
|
try {
|
|
return JSON.parse(stdout);
|
|
} catch (error) {
|
|
throw new Error(`m2pack returned non-JSON output: ${stdout}`);
|
|
}
|
|
}
|
|
|
|
function toolResult(payload) {
|
|
return {
|
|
content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
|
|
structuredContent: payload,
|
|
};
|
|
}
|
|
|
|
const server = new McpServer({
|
|
name: "m2pack-secure",
|
|
version: "0.1.0",
|
|
});
|
|
|
|
server.tool(
|
|
"pack_keygen",
|
|
"Generate a new master content key and Ed25519 signing keypair.",
|
|
{
|
|
out_dir: z.string(),
|
|
},
|
|
async ({ out_dir }) => toolResult(runCli(["keygen", "--out-dir", out_dir])),
|
|
);
|
|
|
|
server.tool(
|
|
"pack_build",
|
|
"Build an .m2p archive from a source directory.",
|
|
{
|
|
input_dir: z.string(),
|
|
output_archive: z.string(),
|
|
key_file: z.string(),
|
|
signing_secret_key_file: z.string(),
|
|
},
|
|
async ({ input_dir, output_archive, key_file, signing_secret_key_file }) =>
|
|
toolResult(
|
|
runCli([
|
|
"build",
|
|
"--input",
|
|
input_dir,
|
|
"--output",
|
|
output_archive,
|
|
"--key",
|
|
key_file,
|
|
"--sign-secret-key",
|
|
signing_secret_key_file,
|
|
]),
|
|
),
|
|
);
|
|
|
|
server.tool(
|
|
"pack_list",
|
|
"List entries in an .m2p archive.",
|
|
{
|
|
archive: z.string(),
|
|
},
|
|
async ({ archive }) => toolResult(runCli(["list", "--archive", archive])),
|
|
);
|
|
|
|
server.tool(
|
|
"pack_verify",
|
|
"Verify archive manifest integrity and optionally decrypt all entries.",
|
|
{
|
|
archive: z.string(),
|
|
public_key_file: z.string().optional(),
|
|
key_file: z.string().optional(),
|
|
},
|
|
async ({ archive, public_key_file, key_file }) => {
|
|
const args = ["verify", "--archive", archive];
|
|
if (public_key_file) {
|
|
args.push("--public-key", public_key_file);
|
|
}
|
|
if (key_file) {
|
|
args.push("--key", key_file);
|
|
}
|
|
return toolResult(runCli(args));
|
|
},
|
|
);
|
|
|
|
server.tool(
|
|
"pack_extract",
|
|
"Extract an .m2p archive into a directory.",
|
|
{
|
|
archive: z.string(),
|
|
output_dir: z.string(),
|
|
key_file: z.string(),
|
|
},
|
|
async ({ archive, output_dir, key_file }) =>
|
|
toolResult(
|
|
runCli([
|
|
"extract",
|
|
"--archive",
|
|
archive,
|
|
"--output",
|
|
output_dir,
|
|
"--key",
|
|
key_file,
|
|
]),
|
|
),
|
|
);
|
|
|
|
server.tool(
|
|
"pack_export_client_config",
|
|
"Generate M2PackKeys.h for the Windows client tree.",
|
|
{
|
|
key_file: z.string(),
|
|
public_key_file: z.string(),
|
|
output_header: z.string(),
|
|
},
|
|
async ({ key_file, public_key_file, output_header }) =>
|
|
toolResult(
|
|
runCli([
|
|
"export-client-config",
|
|
"--key",
|
|
key_file,
|
|
"--public-key",
|
|
public_key_file,
|
|
"--output",
|
|
output_header,
|
|
]),
|
|
),
|
|
);
|
|
|
|
server.tool(
|
|
"pack_binary_info",
|
|
"Report which m2pack binary the server will execute.",
|
|
{},
|
|
async () =>
|
|
toolResult({
|
|
ok: true,
|
|
binary: resolveBinary(),
|
|
repo_root: __dirname,
|
|
}),
|
|
);
|
|
|
|
async function main() {
|
|
const transport = new StdioServerTransport();
|
|
await server.connect(transport);
|
|
}
|
|
|
|
main().catch((error) => {
|
|
console.error(error);
|
|
process.exit(1);
|
|
});
|