@otskit/mcp 0.1.7 → 0.2.1
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/README.md +106 -98
- package/dist/chunk-4ZDPWS7C.js +123 -0
- package/dist/chunk-OMNHFTJW.js +265 -0
- package/dist/chunk-YFSUDT24.js +24 -0
- package/dist/claude-QTEB3Z5V.js +54 -0
- package/dist/claude-code-THJIWEHG.js +48 -0
- package/dist/cli-D7G2H6UT.js +98 -0
- package/dist/codex-P7EDO5GY.js +41 -0
- package/dist/index.js +77 -0
- package/dist/install-BUSOMBB2.js +37 -0
- package/dist/install-claude-UKSS65BW.js +44 -0
- package/dist/remove-BGFQRDX3.js +17 -0
- package/dist/scheduler-MTE6OUSV.js +27 -0
- package/dist/server-IDRHOKL7.js +221 -0
- package/dist/status-M6A2RG7G.js +17 -0
- package/dist/watch-7NPUWR6I.js +41 -0
- package/package.json +49 -48
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// src/setup/claude-code.ts
|
|
2
|
+
import { readFileSync, writeFileSync, copyFileSync, mkdirSync, existsSync } from "fs";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import { homedir } from "os";
|
|
5
|
+
function getConfigPath() {
|
|
6
|
+
return join(homedir(), ".claude.json");
|
|
7
|
+
}
|
|
8
|
+
function setupClaudeCode() {
|
|
9
|
+
const configPath = getConfigPath();
|
|
10
|
+
const configDir = join(configPath, "..");
|
|
11
|
+
if (!existsSync(configDir)) {
|
|
12
|
+
mkdirSync(configDir, { recursive: true });
|
|
13
|
+
}
|
|
14
|
+
let config = {};
|
|
15
|
+
if (existsSync(configPath)) {
|
|
16
|
+
try {
|
|
17
|
+
config = JSON.parse(readFileSync(configPath, "utf8"));
|
|
18
|
+
} catch {
|
|
19
|
+
process.stderr.write(` Aviso: no se pudo parsear la config existente, se crear\xE1 nueva.
|
|
20
|
+
`);
|
|
21
|
+
}
|
|
22
|
+
const existing = config.mcpServers ?? {};
|
|
23
|
+
if ("otskit" in existing) {
|
|
24
|
+
process.stdout.write(` ots-mcp ya est\xE1 configurado en ${configPath}
|
|
25
|
+
`);
|
|
26
|
+
process.stdout.write(` No se hizo ning\xFAn cambio.
|
|
27
|
+
`);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const backupPath = configPath + ".bak";
|
|
31
|
+
copyFileSync(configPath, backupPath);
|
|
32
|
+
process.stdout.write(` Backup guardado en ${backupPath}
|
|
33
|
+
`);
|
|
34
|
+
}
|
|
35
|
+
const mcpServers = config.mcpServers ?? {};
|
|
36
|
+
mcpServers["otskit"] = { command: "npx", args: ["-y", "@otskit/mcp", "serve"] };
|
|
37
|
+
config.mcpServers = mcpServers;
|
|
38
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2), "utf8");
|
|
39
|
+
process.stdout.write(`OTSkit MCP configurado para Claude Code CLI
|
|
40
|
+
`);
|
|
41
|
+
process.stdout.write(` Config: ${configPath}
|
|
42
|
+
`);
|
|
43
|
+
process.stdout.write(` Reinicia Claude Code para aplicar los cambios.
|
|
44
|
+
`);
|
|
45
|
+
}
|
|
46
|
+
export {
|
|
47
|
+
setupClaudeCode
|
|
48
|
+
};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createTimestamp,
|
|
3
|
+
listPending,
|
|
4
|
+
upgradeTimestamp,
|
|
5
|
+
verifyTimestamp
|
|
6
|
+
} from "./chunk-OMNHFTJW.js";
|
|
7
|
+
import {
|
|
8
|
+
backupDb,
|
|
9
|
+
getDb,
|
|
10
|
+
loadConfig
|
|
11
|
+
} from "./chunk-4ZDPWS7C.js";
|
|
12
|
+
import "./chunk-YFSUDT24.js";
|
|
13
|
+
|
|
14
|
+
// src/cli.ts
|
|
15
|
+
async function runCli(command, args) {
|
|
16
|
+
const config = loadConfig();
|
|
17
|
+
const db = getDb();
|
|
18
|
+
switch (command) {
|
|
19
|
+
case "stamp": {
|
|
20
|
+
const hash = args[0];
|
|
21
|
+
if (!hash) {
|
|
22
|
+
process.stderr.write("Usage: ots-mcp stamp <sha256-hash>\n");
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
const result = await createTimestamp({ hash }, db, config);
|
|
26
|
+
if ("error" in result) {
|
|
27
|
+
process.stderr.write(`Error: ${result.error} \u2014 ${result.details}
|
|
28
|
+
`);
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
case "upgrade": {
|
|
35
|
+
const id = args[0];
|
|
36
|
+
if (!id) {
|
|
37
|
+
process.stderr.write("Usage: ots-mcp upgrade <id>\n");
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
const result = await upgradeTimestamp({ id }, db, config);
|
|
41
|
+
if ("error" in result) {
|
|
42
|
+
process.stderr.write(`Error: ${result.error} \u2014 ${result.details}
|
|
43
|
+
`);
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
case "verify": {
|
|
50
|
+
const id = args[0];
|
|
51
|
+
if (!id) {
|
|
52
|
+
process.stderr.write("Usage: ots-mcp verify <id>\n");
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
const result = await verifyTimestamp({ id }, db, config);
|
|
56
|
+
if ("error" in result) {
|
|
57
|
+
process.stderr.write(`Error: ${result.error} \u2014 ${result.details}
|
|
58
|
+
`);
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
case "list": {
|
|
65
|
+
const status = args[0] ?? "pending";
|
|
66
|
+
const result = listPending({ status }, db, config);
|
|
67
|
+
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
case "check-pending": {
|
|
71
|
+
const { items } = listPending({ status: "pending", limit: 200, due_now: true }, db, config);
|
|
72
|
+
process.stderr.write(`Processing ${items.length} pending stamps...
|
|
73
|
+
`);
|
|
74
|
+
for (const record of items) {
|
|
75
|
+
const result = await upgradeTimestamp({ id: record.id }, db, config);
|
|
76
|
+
const statusStr = "status" in result ? result.status : `error:${result.error}`;
|
|
77
|
+
process.stderr.write(`${record.id.slice(0, 8)}: ${statusStr}
|
|
78
|
+
`);
|
|
79
|
+
}
|
|
80
|
+
process.exit(0);
|
|
81
|
+
}
|
|
82
|
+
case "backup": {
|
|
83
|
+
const dest = args[0] ?? `ots-mcp-backup-${Date.now()}.sqlite`;
|
|
84
|
+
backupDb(dest);
|
|
85
|
+
process.stdout.write(`Backup saved to ${dest}
|
|
86
|
+
`);
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
case "scheduler": {
|
|
90
|
+
const { runScheduler } = await import("./scheduler-MTE6OUSV.js");
|
|
91
|
+
await runScheduler(args);
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
export {
|
|
97
|
+
runCli
|
|
98
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// src/setup/codex.ts
|
|
2
|
+
import { readFileSync, writeFileSync, copyFileSync, mkdirSync, existsSync } from "fs";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import { homedir } from "os";
|
|
5
|
+
var BLOCK = `
|
|
6
|
+
[mcp_servers.ots-mcp]
|
|
7
|
+
command = "npx"
|
|
8
|
+
args = ["-y", "@otskit/mcp", "serve"]
|
|
9
|
+
`;
|
|
10
|
+
function setupCodex() {
|
|
11
|
+
const configPath = join(homedir(), ".codex", "config.toml");
|
|
12
|
+
const configDir = join(configPath, "..");
|
|
13
|
+
if (!existsSync(configDir)) {
|
|
14
|
+
mkdirSync(configDir, { recursive: true });
|
|
15
|
+
}
|
|
16
|
+
let content = "";
|
|
17
|
+
if (existsSync(configPath)) {
|
|
18
|
+
content = readFileSync(configPath, "utf8");
|
|
19
|
+
if (content.includes("[mcp_servers.ots-mcp]")) {
|
|
20
|
+
process.stdout.write(` ots-mcp ya est\xE1 configurado en ${configPath}
|
|
21
|
+
`);
|
|
22
|
+
process.stdout.write(` No se hizo ning\xFAn cambio.
|
|
23
|
+
`);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const backupPath = configPath + ".bak";
|
|
27
|
+
copyFileSync(configPath, backupPath);
|
|
28
|
+
process.stdout.write(` Backup guardado en ${backupPath}
|
|
29
|
+
`);
|
|
30
|
+
}
|
|
31
|
+
writeFileSync(configPath, content.trimEnd() + "\n" + BLOCK, "utf8");
|
|
32
|
+
process.stdout.write(`OTSkit MCP configurado para Codex
|
|
33
|
+
`);
|
|
34
|
+
process.stdout.write(` Config: ${configPath}
|
|
35
|
+
`);
|
|
36
|
+
process.stdout.write(` Reinicia Codex para aplicar los cambios.
|
|
37
|
+
`);
|
|
38
|
+
}
|
|
39
|
+
export {
|
|
40
|
+
setupCodex
|
|
41
|
+
};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
var [, , command, ...args] = process.argv;
|
|
5
|
+
if (!command || command === "--help" || command === "help") {
|
|
6
|
+
process.stderr.write(`Usage: ots-mcp <command>
|
|
7
|
+
Commands:
|
|
8
|
+
serve Start MCP server (stdio transport)
|
|
9
|
+
setup <target> Configure MCP for an agent (claude | claude-code | codex)
|
|
10
|
+
watch [interval] Watch pending stamps in real-time (default: 5 min)
|
|
11
|
+
stamp <hash> Stamp a SHA-256 hash
|
|
12
|
+
upgrade <id> Upgrade a pending stamp
|
|
13
|
+
verify <id> Verify a stamp
|
|
14
|
+
list [status] List stamps (default: pending)
|
|
15
|
+
check-pending Run pending upgrades (for scheduler)
|
|
16
|
+
backup [dest] Backup the SQLite database
|
|
17
|
+
scheduler Manage OS scheduler (install|remove|status)
|
|
18
|
+
`);
|
|
19
|
+
process.exit(command ? 0 : 1);
|
|
20
|
+
}
|
|
21
|
+
switch (command) {
|
|
22
|
+
case "serve": {
|
|
23
|
+
const { runServer } = await import("./server-IDRHOKL7.js");
|
|
24
|
+
await runServer();
|
|
25
|
+
break;
|
|
26
|
+
}
|
|
27
|
+
case "setup": {
|
|
28
|
+
const target = args[0];
|
|
29
|
+
if (!target || target !== "claude" && target !== "claude-code" && target !== "codex") {
|
|
30
|
+
process.stderr.write(`Usage: ots-mcp setup <claude|claude-code|codex>
|
|
31
|
+
`);
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
if (target === "claude") {
|
|
35
|
+
const { setupClaude } = await import("./claude-QTEB3Z5V.js");
|
|
36
|
+
setupClaude();
|
|
37
|
+
} else if (target === "claude-code") {
|
|
38
|
+
const { setupClaudeCode } = await import("./claude-code-THJIWEHG.js");
|
|
39
|
+
setupClaudeCode();
|
|
40
|
+
} else {
|
|
41
|
+
const { setupCodex } = await import("./codex-P7EDO5GY.js");
|
|
42
|
+
setupCodex();
|
|
43
|
+
}
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
case "install-claude": {
|
|
47
|
+
const { installClaude } = await import("./install-claude-UKSS65BW.js");
|
|
48
|
+
installClaude();
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
case "watch": {
|
|
52
|
+
const { watchPending } = await import("./watch-7NPUWR6I.js");
|
|
53
|
+
const parsed = args[0] ? parseInt(args[0], 10) : NaN;
|
|
54
|
+
const interval = isNaN(parsed) || parsed < 1 ? 5 : parsed;
|
|
55
|
+
if (args[0] && (isNaN(parsed) || parsed < 1))
|
|
56
|
+
process.stderr.write(`Argumento inv\xE1lido "${args[0]}", usando intervalo por defecto: 5 min
|
|
57
|
+
`);
|
|
58
|
+
await watchPending(interval);
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
case "stamp":
|
|
62
|
+
case "upgrade":
|
|
63
|
+
case "verify":
|
|
64
|
+
case "list":
|
|
65
|
+
case "check-pending":
|
|
66
|
+
case "backup":
|
|
67
|
+
case "scheduler": {
|
|
68
|
+
const { runCli } = await import("./cli-D7G2H6UT.js");
|
|
69
|
+
await runCli(command, args);
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
default: {
|
|
73
|
+
process.stderr.write(`Unknown command: ${command}. Run 'ots-mcp help' for usage.
|
|
74
|
+
`);
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import {
|
|
2
|
+
which
|
|
3
|
+
} from "./chunk-YFSUDT24.js";
|
|
4
|
+
|
|
5
|
+
// src/scheduler/install.ts
|
|
6
|
+
import { execFileSync } from "child_process";
|
|
7
|
+
import { writeFileSync } from "fs";
|
|
8
|
+
async function installScheduler(args) {
|
|
9
|
+
const intervalIdx = args.indexOf("--interval");
|
|
10
|
+
const interval = intervalIdx !== -1 ? parseInt(args[intervalIdx + 1] ?? "30") : 30;
|
|
11
|
+
const bin = which("ots-mcp") ?? process.argv[1];
|
|
12
|
+
if (process.platform === "win32") {
|
|
13
|
+
const xmlPath = `${process.env.TEMP}\\ots-mcp-task.xml`;
|
|
14
|
+
writeFileSync(xmlPath, `<?xml version="1.0"?>
|
|
15
|
+
<Task xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
|
|
16
|
+
<Triggers><TimeTrigger>
|
|
17
|
+
<Repetition><Interval>PT${interval}M</Interval><StopAtDurationEnd>false</StopAtDurationEnd></Repetition>
|
|
18
|
+
<StartBoundary>2020-01-01T00:00:00</StartBoundary><Enabled>true</Enabled>
|
|
19
|
+
</TimeTrigger></Triggers>
|
|
20
|
+
<Actions><Exec>
|
|
21
|
+
<Command>${bin}</Command>
|
|
22
|
+
<Arguments>check-pending</Arguments>
|
|
23
|
+
</Exec></Actions>
|
|
24
|
+
</Task>`);
|
|
25
|
+
execFileSync("schtasks", ["/create", "/tn", "ots-mcp-check-pending", "/xml", xmlPath, "/f"]);
|
|
26
|
+
process.stdout.write(`Scheduler installed: runs every ${interval} minutes
|
|
27
|
+
`);
|
|
28
|
+
} else {
|
|
29
|
+
process.stdout.write(`Add to crontab (run: crontab -e):
|
|
30
|
+
`);
|
|
31
|
+
process.stdout.write(`*/${interval} * * * * "${bin}" check-pending
|
|
32
|
+
`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export {
|
|
36
|
+
installScheduler
|
|
37
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// src/install-claude.ts
|
|
2
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
function getConfigPath() {
|
|
5
|
+
if (process.platform === "win32") {
|
|
6
|
+
return join(process.env.APPDATA ?? "", "Claude", "claude_desktop_config.json");
|
|
7
|
+
} else if (process.platform === "darwin") {
|
|
8
|
+
return join(process.env.HOME ?? "", "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
9
|
+
} else {
|
|
10
|
+
return join(process.env.HOME ?? "", ".config", "Claude", "claude_desktop_config.json");
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function installClaude() {
|
|
14
|
+
const configPath = getConfigPath();
|
|
15
|
+
const configDir = join(configPath, "..");
|
|
16
|
+
if (!existsSync(configDir)) {
|
|
17
|
+
mkdirSync(configDir, { recursive: true });
|
|
18
|
+
}
|
|
19
|
+
let config = {};
|
|
20
|
+
if (existsSync(configPath)) {
|
|
21
|
+
try {
|
|
22
|
+
config = JSON.parse(readFileSync(configPath, "utf8"));
|
|
23
|
+
} catch {
|
|
24
|
+
process.stderr.write(`Warning: could not parse existing config, creating new one
|
|
25
|
+
`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const mcpServers = config.mcpServers ?? {};
|
|
29
|
+
mcpServers["otskit"] = {
|
|
30
|
+
command: "npx",
|
|
31
|
+
args: ["-y", "@otskit/mcp", "serve"]
|
|
32
|
+
};
|
|
33
|
+
config.mcpServers = mcpServers;
|
|
34
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2), "utf8");
|
|
35
|
+
process.stdout.write(`\u2713 OTSkit MCP installed in Claude Desktop
|
|
36
|
+
`);
|
|
37
|
+
process.stdout.write(` Config: ${configPath}
|
|
38
|
+
`);
|
|
39
|
+
process.stdout.write(` Restart Claude Desktop to apply changes.
|
|
40
|
+
`);
|
|
41
|
+
}
|
|
42
|
+
export {
|
|
43
|
+
installClaude
|
|
44
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// src/scheduler/remove.ts
|
|
2
|
+
import { execFileSync } from "child_process";
|
|
3
|
+
async function removeScheduler() {
|
|
4
|
+
if (process.platform === "win32") {
|
|
5
|
+
try {
|
|
6
|
+
execFileSync("schtasks", ["/delete", "/tn", "ots-mcp-check-pending", "/f"]);
|
|
7
|
+
process.stdout.write("Scheduler task removed\n");
|
|
8
|
+
} catch {
|
|
9
|
+
process.stderr.write("Task not found or could not be removed\n");
|
|
10
|
+
}
|
|
11
|
+
} else {
|
|
12
|
+
process.stdout.write("Remove the ots-mcp entry from crontab: crontab -e\n");
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export {
|
|
16
|
+
removeScheduler
|
|
17
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// src/scheduler/index.ts
|
|
2
|
+
async function runScheduler(args) {
|
|
3
|
+
const [sub, ...rest] = args;
|
|
4
|
+
switch (sub) {
|
|
5
|
+
case "install": {
|
|
6
|
+
const { installScheduler } = await import("./install-BUSOMBB2.js");
|
|
7
|
+
await installScheduler(rest);
|
|
8
|
+
break;
|
|
9
|
+
}
|
|
10
|
+
case "remove": {
|
|
11
|
+
const { removeScheduler } = await import("./remove-BGFQRDX3.js");
|
|
12
|
+
await removeScheduler();
|
|
13
|
+
break;
|
|
14
|
+
}
|
|
15
|
+
case "status": {
|
|
16
|
+
const { statusScheduler } = await import("./status-M6A2RG7G.js");
|
|
17
|
+
await statusScheduler();
|
|
18
|
+
break;
|
|
19
|
+
}
|
|
20
|
+
default:
|
|
21
|
+
process.stderr.write("Usage: ots-mcp scheduler install [--interval N] | remove | status\n");
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export {
|
|
26
|
+
runScheduler
|
|
27
|
+
};
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createTimestamp,
|
|
3
|
+
getStamp,
|
|
4
|
+
listPending,
|
|
5
|
+
upgradeTimestamp,
|
|
6
|
+
verifyTimestamp
|
|
7
|
+
} from "./chunk-OMNHFTJW.js";
|
|
8
|
+
import {
|
|
9
|
+
getDb,
|
|
10
|
+
loadConfig
|
|
11
|
+
} from "./chunk-4ZDPWS7C.js";
|
|
12
|
+
import "./chunk-YFSUDT24.js";
|
|
13
|
+
|
|
14
|
+
// src/server.ts
|
|
15
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
16
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
17
|
+
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
18
|
+
|
|
19
|
+
// src/tools/inspect-timestamp.ts
|
|
20
|
+
import { readFileSync, statSync } from "fs";
|
|
21
|
+
import { DetachedTimestampFile } from "@otskit/core";
|
|
22
|
+
function inspectTimestamp(input, db, _config) {
|
|
23
|
+
const record = getStamp(db, input.id);
|
|
24
|
+
if (!record) return { error: "not_found", details: `No stamp with id ${input.id}` };
|
|
25
|
+
if (!record.proof_path) return { error: "proof_missing", details: "No proof file on record" };
|
|
26
|
+
let proofBytes;
|
|
27
|
+
let proofSize;
|
|
28
|
+
try {
|
|
29
|
+
proofSize = statSync(record.proof_path).size;
|
|
30
|
+
proofBytes = readFileSync(record.proof_path);
|
|
31
|
+
} catch {
|
|
32
|
+
return { error: "proof_missing", details: `Cannot read proof: ${record.proof_path}` };
|
|
33
|
+
}
|
|
34
|
+
let calendarAttestations = 0;
|
|
35
|
+
let bitcoinAttestations = 0;
|
|
36
|
+
let bitcoinBlock = null;
|
|
37
|
+
try {
|
|
38
|
+
const proof = DetachedTimestampFile.deserialize(new Uint8Array(proofBytes));
|
|
39
|
+
const attestations = proof.timestamp.getAttestations();
|
|
40
|
+
bitcoinAttestations = attestations.filter((a) => a.kind === "bitcoin").length;
|
|
41
|
+
calendarAttestations = attestations.filter((a) => a.kind !== "bitcoin").length;
|
|
42
|
+
if (bitcoinAttestations > 0) {
|
|
43
|
+
const blocks = attestations.filter((a) => a.kind === "bitcoin").map((a) => a.height);
|
|
44
|
+
bitcoinBlock = blocks.length > 0 ? Math.min(...blocks) : null;
|
|
45
|
+
}
|
|
46
|
+
} catch {
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
id: record.id,
|
|
50
|
+
hash: record.hash,
|
|
51
|
+
status: record.status,
|
|
52
|
+
created_at: record.created_at,
|
|
53
|
+
proof_path: record.proof_path,
|
|
54
|
+
proof_size_bytes: proofSize,
|
|
55
|
+
calendar_attestations: calendarAttestations,
|
|
56
|
+
bitcoin_attestations: bitcoinAttestations,
|
|
57
|
+
bitcoin_confirmed: bitcoinAttestations > 0,
|
|
58
|
+
bitcoin_block: bitcoinBlock
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// src/tools/watch-window.ts
|
|
63
|
+
import { exec } from "child_process";
|
|
64
|
+
function openWatchWindow(intervalMinutes = 5) {
|
|
65
|
+
const minutes = Math.max(1, Math.floor(intervalMinutes));
|
|
66
|
+
const cmd = `start powershell.exe -NoExit -Command "ots-mcp watch ${minutes}"`;
|
|
67
|
+
let errorMsg;
|
|
68
|
+
exec(cmd, { shell: "cmd" }, (err) => {
|
|
69
|
+
if (err) errorMsg = err.message;
|
|
70
|
+
});
|
|
71
|
+
return { opened: true, interval_minutes: minutes, ...errorMsg ? { error: errorMsg } : {} };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// src/tool-definitions.ts
|
|
75
|
+
var TOOL_DEFINITIONS = [
|
|
76
|
+
{
|
|
77
|
+
name: "create_timestamp",
|
|
78
|
+
description: "Stamps a SHA-256 hash against public OpenTimestamps calendars. IMPORTANT: the digest is sent to external calendar servers (alice.btc, bob.btc, finney, catallaxy).",
|
|
79
|
+
inputSchema: {
|
|
80
|
+
type: "object",
|
|
81
|
+
properties: { hash: { type: "string", description: "SHA-256 hex digest (64 chars)" } },
|
|
82
|
+
required: ["hash"]
|
|
83
|
+
},
|
|
84
|
+
annotations: {
|
|
85
|
+
readOnlyHint: false,
|
|
86
|
+
destructiveHint: false,
|
|
87
|
+
idempotentHint: false,
|
|
88
|
+
openWorldHint: true
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
name: "upgrade_timestamp",
|
|
93
|
+
description: "Checks if a pending stamp has been confirmed in Bitcoin. Use the id returned by create_timestamp.",
|
|
94
|
+
inputSchema: {
|
|
95
|
+
type: "object",
|
|
96
|
+
properties: { id: { type: "string", description: "UUID from the stamp record" } },
|
|
97
|
+
required: ["id"]
|
|
98
|
+
},
|
|
99
|
+
annotations: {
|
|
100
|
+
readOnlyHint: false,
|
|
101
|
+
destructiveHint: false,
|
|
102
|
+
idempotentHint: true,
|
|
103
|
+
openWorldHint: true
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
name: "verify_timestamp",
|
|
108
|
+
description: "Verifies a stamp against Bitcoin. Does NOT affirm document authorship or truth \u2014 only proves the hash existed before a given Bitcoin block.",
|
|
109
|
+
inputSchema: {
|
|
110
|
+
type: "object",
|
|
111
|
+
properties: { id: { type: "string", description: "UUID from the stamp record" } },
|
|
112
|
+
required: ["id"]
|
|
113
|
+
},
|
|
114
|
+
annotations: {
|
|
115
|
+
readOnlyHint: true,
|
|
116
|
+
destructiveHint: false,
|
|
117
|
+
idempotentHint: true,
|
|
118
|
+
openWorldHint: true
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
name: "inspect_timestamp",
|
|
123
|
+
description: "Inspects a stored proof file without network calls. Returns calendar_attestations (promises from OTS servers, NOT Bitcoin confirmation) and bitcoin_attestations (actual Bitcoin blocks). A stamp is only truly confirmed when bitcoin_attestations > 0 and bitcoin_confirmed is true.",
|
|
124
|
+
inputSchema: {
|
|
125
|
+
type: "object",
|
|
126
|
+
properties: { id: { type: "string", description: "UUID from the stamp record" } },
|
|
127
|
+
required: ["id"]
|
|
128
|
+
},
|
|
129
|
+
annotations: {
|
|
130
|
+
readOnlyHint: true,
|
|
131
|
+
destructiveHint: false,
|
|
132
|
+
idempotentHint: true,
|
|
133
|
+
openWorldHint: false
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
name: "list_pending",
|
|
138
|
+
description: "Lists stamp records with status and retry info.",
|
|
139
|
+
inputSchema: {
|
|
140
|
+
type: "object",
|
|
141
|
+
properties: {
|
|
142
|
+
status: { type: "string", enum: ["pending", "confirmed", "failed", "timeout"] },
|
|
143
|
+
limit: { type: "number", maximum: 200 },
|
|
144
|
+
offset: { type: "number" },
|
|
145
|
+
older_than_hours: { type: "number" }
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
annotations: {
|
|
149
|
+
readOnlyHint: true,
|
|
150
|
+
destructiveHint: false,
|
|
151
|
+
idempotentHint: true,
|
|
152
|
+
openWorldHint: false
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
name: "watch",
|
|
157
|
+
description: "Opens a new terminal window that monitors pending stamps in real-time, polling at the given interval. The window stays open so the user can watch progress.",
|
|
158
|
+
inputSchema: {
|
|
159
|
+
type: "object",
|
|
160
|
+
properties: {
|
|
161
|
+
interval_minutes: { type: "number", description: "Polling interval in minutes (default: 5, minimum: 1)" }
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
annotations: {
|
|
165
|
+
readOnlyHint: true,
|
|
166
|
+
destructiveHint: false,
|
|
167
|
+
idempotentHint: false,
|
|
168
|
+
openWorldHint: false
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
];
|
|
172
|
+
|
|
173
|
+
// src/server.ts
|
|
174
|
+
async function runServer() {
|
|
175
|
+
const config = loadConfig();
|
|
176
|
+
const db = getDb();
|
|
177
|
+
const server = new Server(
|
|
178
|
+
{ name: "ots-mcp", version: "0.1.0" },
|
|
179
|
+
{ capabilities: { tools: {} } }
|
|
180
|
+
);
|
|
181
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
182
|
+
tools: TOOL_DEFINITIONS
|
|
183
|
+
}));
|
|
184
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
185
|
+
const { name, arguments: args } = request.params;
|
|
186
|
+
try {
|
|
187
|
+
let result;
|
|
188
|
+
switch (name) {
|
|
189
|
+
case "create_timestamp":
|
|
190
|
+
result = await createTimestamp(args, db, config);
|
|
191
|
+
break;
|
|
192
|
+
case "upgrade_timestamp":
|
|
193
|
+
result = await upgradeTimestamp(args, db, config);
|
|
194
|
+
break;
|
|
195
|
+
case "verify_timestamp":
|
|
196
|
+
result = await verifyTimestamp(args, db, config);
|
|
197
|
+
break;
|
|
198
|
+
case "inspect_timestamp":
|
|
199
|
+
result = inspectTimestamp(args, db, config);
|
|
200
|
+
break;
|
|
201
|
+
case "list_pending":
|
|
202
|
+
result = listPending(args, db, config);
|
|
203
|
+
break;
|
|
204
|
+
case "watch":
|
|
205
|
+
result = openWatchWindow(args?.interval_minutes);
|
|
206
|
+
break;
|
|
207
|
+
default:
|
|
208
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: "unknown_tool", tool: name }) }], isError: true };
|
|
209
|
+
}
|
|
210
|
+
const isError = Boolean(result && typeof result === "object" && "error" in result);
|
|
211
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }], isError };
|
|
212
|
+
} catch (e) {
|
|
213
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: "internal_error", details: String(e) }) }], isError: true };
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
const transport = new StdioServerTransport();
|
|
217
|
+
await server.connect(transport);
|
|
218
|
+
}
|
|
219
|
+
export {
|
|
220
|
+
runServer
|
|
221
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// src/scheduler/status.ts
|
|
2
|
+
import { execFileSync } from "child_process";
|
|
3
|
+
async function statusScheduler() {
|
|
4
|
+
if (process.platform === "win32") {
|
|
5
|
+
try {
|
|
6
|
+
const out = execFileSync("schtasks", ["/query", "/tn", "ots-mcp-check-pending", "/fo", "LIST"]).toString();
|
|
7
|
+
process.stdout.write(out + "\n");
|
|
8
|
+
} catch {
|
|
9
|
+
process.stdout.write("Scheduler task not found\n");
|
|
10
|
+
}
|
|
11
|
+
} else {
|
|
12
|
+
process.stdout.write("Check with: crontab -l | grep ots-mcp\n");
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export {
|
|
16
|
+
statusScheduler
|
|
17
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getDb,
|
|
3
|
+
loadConfig
|
|
4
|
+
} from "./chunk-4ZDPWS7C.js";
|
|
5
|
+
|
|
6
|
+
// src/tools/watch.ts
|
|
7
|
+
async function watchPending(intervalMinutes = 5) {
|
|
8
|
+
const config = loadConfig();
|
|
9
|
+
const db = getDb(config);
|
|
10
|
+
process.stdout.write(`Watching pending stamps every ${intervalMinutes} min. Ctrl+C to stop.
|
|
11
|
+
|
|
12
|
+
`);
|
|
13
|
+
function tick() {
|
|
14
|
+
const rows = db.prepare(
|
|
15
|
+
`SELECT id, hash, status, attempt_count, bitcoin_block, confirmed_at FROM stamps WHERE status != 'confirmed' ORDER BY created_at DESC`
|
|
16
|
+
).all();
|
|
17
|
+
const confirmed = db.prepare(`SELECT COUNT(*) as n FROM stamps WHERE status = 'confirmed'`).get();
|
|
18
|
+
process.stdout.write(`${now()} \u2014 ${rows.length} pendientes, ${confirmed.n} confirmados
|
|
19
|
+
`);
|
|
20
|
+
for (const row of rows) {
|
|
21
|
+
process.stdout.write(` ${row.id.slice(0, 8)} ${row.status} (${row.attempt_count} intentos)
|
|
22
|
+
`);
|
|
23
|
+
}
|
|
24
|
+
if (rows.length === 0) {
|
|
25
|
+
process.stdout.write(` (ning\xFAn sello pendiente)
|
|
26
|
+
`);
|
|
27
|
+
}
|
|
28
|
+
process.stdout.write("\n");
|
|
29
|
+
}
|
|
30
|
+
function now() {
|
|
31
|
+
return (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19);
|
|
32
|
+
}
|
|
33
|
+
function loop() {
|
|
34
|
+
tick();
|
|
35
|
+
setTimeout(loop, Math.max(6e4, intervalMinutes * 60 * 1e3));
|
|
36
|
+
}
|
|
37
|
+
loop();
|
|
38
|
+
}
|
|
39
|
+
export {
|
|
40
|
+
watchPending
|
|
41
|
+
};
|