ratchet-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/LICENSE ADDED
@@ -0,0 +1,17 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ Licensed under the Apache License, Version 2.0 (the "License");
6
+ you may not use this file except in compliance with the License.
7
+ You may obtain a copy of the License at
8
+
9
+ http://www.apache.org/licenses/LICENSE-2.0
10
+
11
+ Unless required by applicable law or agreed to in writing, software
12
+ distributed under the License is distributed on an "AS IS" BASIS,
13
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ See the License for the specific language governing permissions and
15
+ limitations under the License.
16
+
17
+ Full text: https://www.apache.org/licenses/LICENSE-2.0.txt
package/README.md ADDED
@@ -0,0 +1,71 @@
1
+ # ratchet-mcp
2
+
3
+ MCP server for [Ratchet](https://ratchetgate.com) — an effect gate for AI agents.
4
+
5
+ Agents retry. LLM control flow is non-deterministic, network calls fail ambiguously, and processes
6
+ die mid-action, so the same logical action gets attempted zero, one, or several times and nothing
7
+ knows which. Ratchet is a gate you ask **before** acting, and it answers durably.
8
+
9
+ ## Setup
10
+
11
+ Get a key at <https://ratchetgate.com/console> (free, no card), then:
12
+
13
+ ```json
14
+ {
15
+ "mcpServers": {
16
+ "ratchet": {
17
+ "command": "npx",
18
+ "args": ["-y", "ratchet-mcp"],
19
+ "env": { "RATCHET_API_KEY": "rk_live_..." }
20
+ }
21
+ }
22
+ }
23
+ ```
24
+
25
+ Put the key in `env`, never in `args` — arguments are visible in process listings.
26
+
27
+ Works with Claude Desktop, Claude Code, Cursor, and any MCP client that spawns a stdio server.
28
+
29
+ ## Tools
30
+
31
+ | Tool | What it does |
32
+ |---|---|
33
+ | `ratchet_begin_effect` | Ask permission before a side effect. Returns `execute`, `duplicate`, `in_flight`, `blocked`, `approval_required`, or `denied` |
34
+ | `ratchet_report_effect` | Report the outcome after acting |
35
+ | `ratchet_check_effect` | Ask "did I already do this?" without reserving anything |
36
+ | `ratchet_resolve_effect` | Settle an effect whose outcome was unknown, after verifying |
37
+ | `ratchet_list_effects` | Review recent effects; filter by `indeterminate` to find unresolved work |
38
+ | `ratchet_get_policy` | Read the retry and budget policy for an effect type |
39
+ | `ratchet_usage` | Plan, allowance, credit balance, and today's spend |
40
+
41
+ Only `execute` authorises the model to act. Every other decision returns a `next_step` beginning
42
+ with `STOP`.
43
+
44
+ ## The part that matters
45
+
46
+ If your agent dies between "go" and "done", Ratchet does **not** quietly let the next caller retry.
47
+ The lease expires, the effect becomes `indeterminate` — a known unknown — and your configured
48
+ policy for that effect type decides what happens: block (the default), retry, or verify first.
49
+
50
+ Exactly-once delivery is not achievable and is not claimed. What is guaranteed is at-most-once
51
+ initiation, a recorded outcome that later callers replay, and an explicit state for the case most
52
+ systems bury.
53
+
54
+ ## Configuration
55
+
56
+ | Variable | Default | Purpose |
57
+ |---|---|---|
58
+ | `RATCHET_API_KEY` | *(required)* | Your scoped API key |
59
+ | `RATCHET_BASE_URL` | `https://ratchetgate.com` | Point at your own instance |
60
+ | `RATCHET_TIMEOUT_MS` | `15000` | Request timeout |
61
+
62
+ This package holds no database connection and no server secret — only your key, which is scoped
63
+ and revocable. It is a thin bridge to the HTTP API, with zero dependencies.
64
+
65
+ If the gate is unreachable it returns a JSON-RPC error rather than crashing, so your agent can
66
+ apply its fail-open or fail-closed policy. Decide which **before** integrating:
67
+ <https://ratchetgate.com/docs>
68
+
69
+ ## License
70
+
71
+ Apache-2.0
@@ -0,0 +1,104 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Ratchet MCP server (stdio transport).
4
+ *
5
+ * A thin bridge: it reads line-delimited JSON-RPC on stdin, forwards each
6
+ * message to a Ratchet instance's /mcp endpoint over HTTPS, and writes the
7
+ * reply to stdout.
8
+ *
9
+ * Deliberately holds no database connection and no server secret. The only
10
+ * credential is the caller's own API key, which is scoped and revocable. An
11
+ * MCP server that required its users to hold the service's database
12
+ * credentials would be a much larger thing to trust than the service itself.
13
+ *
14
+ * Zero dependencies — Node built-ins only.
15
+ *
16
+ * RATCHET_API_KEY=rk_live_... npx ratchet-mcp
17
+ */
18
+ import { createInterface } from 'node:readline';
19
+
20
+ const BASE = (process.env.RATCHET_BASE_URL ?? 'https://ratchetgate.com').replace(/\/+$/, '');
21
+ const KEY = process.env.RATCHET_API_KEY;
22
+ const TIMEOUT_MS = Number.parseInt(process.env.RATCHET_TIMEOUT_MS ?? '15000', 10);
23
+
24
+ // stdout carries the protocol and nothing else; a stray log line there
25
+ // corrupts the stream and the client fails in confusing ways.
26
+ const log = (m) => process.stderr.write(`[ratchet-mcp] ${m}\n`);
27
+ const send = (o) => process.stdout.write(`${JSON.stringify(o)}\n`);
28
+
29
+ if (!KEY) {
30
+ log('RATCHET_API_KEY is not set.');
31
+ log('Add it to the "env" block of your MCP client config — not "args", which');
32
+ log('is visible in process listings. Get a key at ' + BASE + '/console');
33
+ process.exit(1);
34
+ }
35
+
36
+ const rpcError = (id, code, message, data) => ({
37
+ jsonrpc: '2.0', id: id ?? null,
38
+ error: { code, message, ...(data ? { data } : {}) },
39
+ });
40
+
41
+ async function forward(msg) {
42
+ const controller = new AbortController();
43
+ const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
44
+ try {
45
+ const res = await fetch(`${BASE}/mcp`, {
46
+ method: 'POST',
47
+ headers: {
48
+ authorization: `Bearer ${KEY}`,
49
+ 'content-type': 'application/json',
50
+ accept: 'application/json',
51
+ },
52
+ body: JSON.stringify(msg),
53
+ signal: controller.signal,
54
+ });
55
+
56
+ // A notification is answered with 202 and no body, by design.
57
+ if (res.status === 202) return null;
58
+
59
+ const text = await res.text();
60
+ if (!text) return null;
61
+
62
+ try {
63
+ return JSON.parse(text);
64
+ } catch {
65
+ return rpcError(msg.id, -32603, `Ratchet returned a non-JSON response (HTTP ${res.status})`);
66
+ }
67
+ } catch (err) {
68
+ // Surface transport failures as JSON-RPC errors rather than dying: the
69
+ // client can then tell its model the gate is unreachable, which is a
70
+ // decision it needs to make rather than a crash it cannot see.
71
+ const aborted = err?.name === 'AbortError';
72
+ return rpcError(msg.id, -32001,
73
+ aborted ? `Ratchet did not respond within ${TIMEOUT_MS}ms` : `Cannot reach Ratchet at ${BASE}`,
74
+ { hint: 'If your agent cannot reach the gate, apply your configured fail-open or '
75
+ + 'fail-closed policy. See ' + BASE + '/docs' });
76
+ } finally {
77
+ clearTimeout(timer);
78
+ }
79
+ }
80
+
81
+ log(`bridging stdio → ${BASE}/mcp`);
82
+
83
+ const rl = createInterface({ input: process.stdin, crlfDelay: Infinity });
84
+
85
+ for await (const line of rl) {
86
+ const trimmed = line.trim();
87
+ if (!trimmed) continue;
88
+
89
+ let msg;
90
+ try {
91
+ msg = JSON.parse(trimmed);
92
+ } catch {
93
+ send(rpcError(null, -32700, 'Parse error'));
94
+ continue;
95
+ }
96
+
97
+ try {
98
+ const reply = await forward(msg);
99
+ if (reply) send(reply);
100
+ } catch (err) {
101
+ log(`unexpected: ${err?.message ?? err}`);
102
+ send(rpcError(msg?.id, -32603, 'Internal error in the stdio bridge'));
103
+ }
104
+ }
package/package.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "ratchet-mcp",
3
+ "version": "0.1.0",
4
+ "description": "MCP server for Ratchet — the effect gate for AI agents. Ask before you act, so the same side effect happens at most once.",
5
+ "type": "module",
6
+ "bin": { "ratchet-mcp": "bin/ratchet-mcp.mjs" },
7
+ "files": ["bin", "README.md", "LICENSE"],
8
+ "engines": { "node": ">=18" },
9
+ "license": "Apache-2.0",
10
+ "keywords": [
11
+ "mcp", "model-context-protocol", "ai-agents", "idempotency",
12
+ "at-most-once", "side-effects", "agent-infrastructure", "reliability"
13
+ ],
14
+ "repository": { "type": "git", "url": "git+https://github.com/thearchitect0x-glitch/ratchet.git", "directory": "packages/ratchet-mcp" },
15
+ "homepage": "https://ratchetgate.com",
16
+ "publishConfig": { "access": "public" }
17
+ }