Add Node MCP server

This commit is contained in:
server
2026-04-14 11:57:13 +02:00
parent 127d670ebd
commit d270d4d739
6 changed files with 1384 additions and 0 deletions

1
.gitignore vendored
View File

@@ -1,6 +1,7 @@
/build/
/.idea/
/.vscode/
/node_modules/
*.tmp
*.swp
keys/

View File

@@ -24,6 +24,44 @@ with.
- `m2pack extract`
- `m2pack export-client-config`
## MCP Server
The repository also ships a Linux-friendly MCP server that wraps the `m2pack`
CLI for AI agents and automation.
Files:
- `mcp_server.mjs`
- `package.json`
Install:
```bash
npm install
```
Run:
```bash
node mcp_server.mjs
```
If the `m2pack` binary is not at `build/m2pack`, set:
```bash
export M2PACK_BINARY=/absolute/path/to/m2pack
```
Exposed tools:
- `pack_keygen`
- `pack_build`
- `pack_list`
- `pack_verify`
- `pack_extract`
- `pack_export_client_config`
- `pack_binary_info`
## Build
```bash

95
docs/mcp.md Normal file
View File

@@ -0,0 +1,95 @@
# MCP server
`mcp_server.mjs` exposes the `m2pack` CLI as MCP tools over stdio.
This keeps the packing logic in the compiled CLI and uses MCP only as an
automation layer for bots and local tooling.
## Why this layout
- the C++ binary remains the single source of truth
- no duplicate archive logic in Python
- Linux-native development workflow
- works well with Codex, Claude Desktop, Inspector, and other MCP hosts
## Setup
```bash
cd /path/to/m2pack-secure
npm install
cmake -S . -B build
cmake --build build -j
node mcp_server.mjs
```
## Environment
- `M2PACK_BINARY`
Use this when `m2pack` is not located at `build/m2pack`.
## Tool contract
### `pack_keygen`
Inputs:
- `out_dir`
### `pack_build`
Inputs:
- `input_dir`
- `output_archive`
- `key_file`
- `signing_secret_key_file`
### `pack_list`
Inputs:
- `archive`
### `pack_verify`
Inputs:
- `archive`
- `public_key_file` optional
- `key_file` optional
### `pack_extract`
Inputs:
- `archive`
- `output_dir`
- `key_file`
### `pack_export_client_config`
Inputs:
- `key_file`
- `public_key_file`
- `output_header`
### `pack_binary_info`
No input. Returns the active `m2pack` binary path.
## Claude Desktop style config example
```json
{
"mcpServers": {
"m2pack-secure": {
"command": "node",
"args": ["/absolute/path/to/m2pack-secure/mcp_server.mjs"],
"env": {
"M2PACK_BINARY": "/absolute/path/to/m2pack-secure/build/m2pack"
}
}
}
}
```

192
mcp_server.mjs Normal file
View File

@@ -0,0 +1,192 @@
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);
});

1048
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

10
package.json Normal file
View File

@@ -0,0 +1,10 @@
{
"name": "m2pack-secure-mcp",
"version": "0.1.0",
"private": true,
"type": "module",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.29.0",
"zod": "^3.25.76"
}
}