@wolpertingerlabs/drawlatch 1.0.0-alpha.1 → 1.0.0-alpha.12.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/README.md +309 -486
- package/bin/drawlatch.js +1336 -0
- package/dist/cli/generate-keys.d.ts +4 -4
- package/dist/cli/generate-keys.js +30 -25
- package/dist/connections/{anthropic.json → ai/anthropic.json} +19 -1
- package/dist/connections/{devin.json → ai/devin.json} +7 -1
- package/dist/connections/{google-ai.json → ai/google-ai.json} +7 -1
- package/dist/connections/{openai.json → ai/openai.json} +7 -1
- package/dist/connections/{openrouter.json → ai/openrouter.json} +7 -1
- package/dist/connections/developer-tools/github-events-poll.json +78 -0
- package/dist/connections/developer-tools/github.json +129 -0
- package/dist/connections/{hex.json → developer-tools/hex.json} +7 -1
- package/dist/connections/developer-tools/linear.json +75 -0
- package/dist/connections/{lichess.json → gaming/lichess.json} +7 -1
- package/dist/connections/messaging/discord-bot.json +114 -0
- package/dist/connections/{discord-oauth.json → messaging/discord-oauth.json} +7 -1
- package/dist/connections/messaging/slack.json +75 -0
- package/dist/connections/messaging/telegram.json +66 -0
- package/dist/connections/{google.json → productivity/google.json} +7 -1
- package/dist/connections/productivity/notion.json +75 -0
- package/dist/connections/productivity/stripe.json +74 -0
- package/dist/connections/productivity/trello.json +113 -0
- package/dist/connections/{bluesky.json → social-media/bluesky.json} +41 -0
- package/dist/connections/social-media/mastodon.json +65 -0
- package/dist/connections/social-media/reddit.json +80 -0
- package/dist/connections/{twitch.json → social-media/twitch.json} +40 -0
- package/dist/connections/social-media/x.json +67 -0
- package/dist/mcp/server.js +544 -31
- package/dist/remote/ingestors/base-ingestor.d.ts +19 -3
- package/dist/remote/ingestors/base-ingestor.js +27 -6
- package/dist/remote/ingestors/discord/discord-gateway.d.ts +2 -2
- package/dist/remote/ingestors/discord/discord-gateway.js +29 -6
- package/dist/remote/ingestors/e2e/setup.d.ts +69 -0
- package/dist/remote/ingestors/e2e/setup.js +147 -0
- package/dist/remote/ingestors/manager.d.ts +91 -10
- package/dist/remote/ingestors/manager.js +395 -42
- package/dist/remote/ingestors/poll/poll-ingestor.d.ts +4 -2
- package/dist/remote/ingestors/poll/poll-ingestor.js +26 -5
- package/dist/remote/ingestors/registry.d.ts +2 -2
- package/dist/remote/ingestors/registry.js +2 -2
- package/dist/remote/ingestors/slack/socket-mode.d.ts +2 -2
- package/dist/remote/ingestors/slack/socket-mode.js +28 -6
- package/dist/remote/ingestors/types.d.ts +25 -0
- package/dist/remote/ingestors/webhook/base-webhook-ingestor.d.ts +46 -7
- package/dist/remote/ingestors/webhook/base-webhook-ingestor.js +115 -10
- package/dist/remote/ingestors/webhook/github-webhook-ingestor.d.ts +19 -0
- package/dist/remote/ingestors/webhook/github-webhook-ingestor.js +34 -2
- package/dist/remote/ingestors/webhook/lifecycle-types.d.ts +63 -0
- package/dist/remote/ingestors/webhook/lifecycle-types.js +12 -0
- package/dist/remote/ingestors/webhook/stripe-webhook-ingestor.js +2 -2
- package/dist/remote/ingestors/webhook/trello-webhook-ingestor.d.ts +17 -5
- package/dist/remote/ingestors/webhook/trello-webhook-ingestor.js +32 -26
- package/dist/remote/ingestors/webhook/webhook-lifecycle-manager.d.ts +46 -0
- package/dist/remote/ingestors/webhook/webhook-lifecycle-manager.js +261 -0
- package/dist/remote/server.d.ts +21 -1
- package/dist/remote/server.js +1132 -51
- package/dist/remote/triggers/rule-engine.d.ts +40 -0
- package/dist/remote/triggers/rule-engine.js +228 -0
- package/dist/remote/triggers/types.d.ts +69 -0
- package/dist/remote/triggers/types.js +10 -0
- package/dist/remote/tunnel.d.ts +40 -0
- package/dist/remote/tunnel.js +116 -0
- package/dist/shared/config.d.ts +92 -25
- package/dist/shared/config.js +50 -34
- package/dist/shared/connections.d.ts +21 -5
- package/dist/shared/connections.js +56 -14
- package/dist/shared/crypto/index.d.ts +1 -0
- package/dist/shared/crypto/index.js +1 -0
- package/dist/shared/crypto/key-manager.d.ts +67 -0
- package/dist/shared/crypto/key-manager.js +152 -0
- package/dist/shared/env-utils.d.ts +42 -0
- package/dist/shared/env-utils.js +150 -0
- package/dist/shared/listener-config.d.ts +157 -0
- package/dist/shared/listener-config.js +10 -0
- package/dist/shared/protocol/handshake.js +14 -1
- package/dist/shared/protocol/index.d.ts +2 -0
- package/dist/shared/protocol/index.js +2 -0
- package/dist/shared/protocol/sync-client.d.ts +52 -0
- package/dist/shared/protocol/sync-client.js +99 -0
- package/dist/shared/protocol/sync.d.ts +71 -0
- package/dist/shared/protocol/sync.js +176 -0
- package/package.json +23 -6
- package/dist/connections/discord-bot.json +0 -24
- package/dist/connections/github.json +0 -25
- package/dist/connections/linear.json +0 -29
- package/dist/connections/mastodon.json +0 -25
- package/dist/connections/notion.json +0 -33
- package/dist/connections/reddit.json +0 -28
- package/dist/connections/slack.json +0 -23
- package/dist/connections/stripe.json +0 -25
- package/dist/connections/telegram.json +0 -26
- package/dist/connections/trello.json +0 -25
- package/dist/connections/x.json +0 -27
package/README.md
CHANGED
|
@@ -1,685 +1,508 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Drawlatch
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> **Alpha Software:** Expect breaking changes between updates.
|
|
4
4
|
|
|
5
|
-
Drawlatch
|
|
5
|
+
Drawlatch is a config-driven proxy that gives AI agents authenticated access to external APIs. Define your connections and secrets in a single config file — agents get structured, allowlisted access to 22 pre-built APIs without ever seeing your credentials.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
**Using [Callboard](https://github.com/WolpertingerLabs/callboard)?** Drawlatch is built in — Callboard manages connections, secrets, and agent identities through its UI. You don't need to set up drawlatch separately.
|
|
8
|
+
|
|
9
|
+
## Key Features
|
|
10
|
+
|
|
11
|
+
- **22 pre-built connections** — GitHub, Slack, Discord, Stripe, Notion, Linear, OpenAI, and [more](CONNECTIONS.md)
|
|
12
|
+
- **Endpoint allowlisting** — agents can only reach explicitly configured URL patterns
|
|
13
|
+
- **Per-caller access control** — each agent identity sees only its assigned connections
|
|
14
|
+
- **Real-time event ingestion** — WebSocket, webhook, and polling listeners for incoming events ([details](INGESTORS.md))
|
|
15
|
+
- **Two operating modes** — remote (secrets on a separate server with E2EE) or local (in-process library)
|
|
9
16
|
|
|
10
17
|
## How It Works
|
|
11
18
|
|
|
12
|
-
|
|
19
|
+
Drawlatch runs in two modes depending on your trust model:
|
|
13
20
|
|
|
14
|
-
|
|
21
|
+
### Remote Mode — Secrets Never Leave the Server
|
|
15
22
|
|
|
16
|
-
|
|
17
|
-
2. **Remote Secure Server** — holds all secrets (API keys, tokens, etc.) and only communicates through encrypted channels after mutual authentication. It injects secrets into outgoing HTTP requests on the proxy's behalf.
|
|
23
|
+
The local MCP proxy holds no secrets. It encrypts requests and forwards them to a remote server that injects credentials and makes the actual API calls.
|
|
18
24
|
|
|
19
25
|
```
|
|
20
|
-
┌──────────────┐
|
|
21
|
-
│ Claude Code
|
|
22
|
-
│ │
|
|
23
|
-
└──────────────┘
|
|
24
|
-
No secrets here Injects API keys,
|
|
25
|
-
tokens, headers
|
|
26
|
+
┌──────────────┐ ┌──────────────────┐ ┌──────────────┐
|
|
27
|
+
│ Claude Code │◄── stdio ──► MCP Proxy │◄── HTTP + E2EE ──► Remote Server │── HTTPS ────►│ External API │
|
|
28
|
+
│ │ (no secrets) │ │ (holds secrets) │ │ │
|
|
29
|
+
└──────────────┘ └──────────────────┘ └──────────────┘
|
|
26
30
|
```
|
|
27
31
|
|
|
28
|
-
The crypto layer uses
|
|
32
|
+
The crypto layer uses Ed25519 signatures for mutual authentication and X25519 ECDH to derive AES-256-GCM session keys — all built on Node.js native `crypto` with zero external dependencies.
|
|
29
33
|
|
|
30
|
-
### Local Mode
|
|
34
|
+
### Local Mode — In-Process Library
|
|
31
35
|
|
|
32
|
-
|
|
36
|
+
No server, no encryption. Your application imports drawlatch directly and calls the same `executeProxyRequest()` function the remote server uses. Secrets come from `process.env` on the same machine.
|
|
33
37
|
|
|
34
38
|
```
|
|
35
|
-
┌──────────────────────────────────────────┐
|
|
36
|
-
│ Your Application
|
|
37
|
-
│ ┌──────────┐ in-process ┌────────┐ │
|
|
38
|
-
│ │ Agent │◄──
|
|
39
|
-
│ │ │ │ routes │ │ local env / config
|
|
39
|
+
┌──────────────────────────────────────────┐ ┌──────────────┐
|
|
40
|
+
│ Your Application │── HTTPS ──────────►│ External API │
|
|
41
|
+
│ ┌──────────┐ in-process ┌────────┐ │ │ │
|
|
42
|
+
│ │ Agent │◄── call ──────►│ drawl. │ │ └──────────────┘
|
|
40
43
|
│ └──────────┘ └────────┘ │
|
|
41
44
|
└──────────────────────────────────────────┘
|
|
42
|
-
Secrets are on the same machine
|
|
43
45
|
```
|
|
44
46
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
**What you don't get:** Secret isolation from the agent. When running locally, secrets live in `process.env` on the same machine. The value proposition shifts from cryptographic secret hiding to **convenience and structured access** — a single config file managing many API connections with consistent patterns.
|
|
47
|
+
You still get config-driven route resolution, endpoint allowlisting, per-caller access control, and ingestor support — just without cryptographic secret isolation.
|
|
48
48
|
|
|
49
|
-
> **When to use which
|
|
49
|
+
> **When to use which:** Remote mode when secrets must be hidden from the agent's machine (shared servers, CI, untrusted environments). Local mode when running on your own machine and you want convenience without a separate server.
|
|
50
50
|
|
|
51
51
|
## Quick Start
|
|
52
52
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
This repo is structured as a **Claude Code plugin** with a marketplace. Install it directly:
|
|
56
|
-
|
|
57
|
-
```shell
|
|
58
|
-
# Add the marketplace (from a local clone)
|
|
59
|
-
/plugin marketplace add ./path/to/drawlatch
|
|
60
|
-
|
|
61
|
-
# Install the plugin
|
|
62
|
-
/plugin install drawlatch@drawlatch
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
Or load it directly during development:
|
|
66
|
-
|
|
67
|
-
```shell
|
|
68
|
-
claude --plugin-dir ./path/to/drawlatch
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
Before using, set the `MCP_CONFIG_DIR` environment variable so the proxy can find its config and keys:
|
|
53
|
+
Get from zero to working in three commands:
|
|
72
54
|
|
|
73
55
|
```bash
|
|
74
|
-
|
|
75
|
-
|
|
56
|
+
# Install globally
|
|
57
|
+
npm install -g @wolpertingerlabs/drawlatch
|
|
76
58
|
|
|
77
|
-
|
|
59
|
+
# Set up keys, config, and .env in one step
|
|
60
|
+
drawlatch init --connections github
|
|
78
61
|
|
|
79
|
-
|
|
62
|
+
# Set your API token (edit the file or run this)
|
|
63
|
+
echo "GITHUB_TOKEN=ghp_your_token_here" >> ~/.drawlatch/.env
|
|
80
64
|
|
|
81
|
-
|
|
65
|
+
# Start the remote server
|
|
66
|
+
drawlatch start
|
|
67
|
+
```
|
|
82
68
|
|
|
83
|
-
|
|
69
|
+
Verify your setup:
|
|
84
70
|
|
|
85
71
|
```bash
|
|
86
|
-
|
|
72
|
+
drawlatch doctor # Validate full setup
|
|
73
|
+
drawlatch status # Check server is running
|
|
74
|
+
drawlatch config # View configuration and secret status
|
|
87
75
|
```
|
|
88
76
|
|
|
89
|
-
The
|
|
77
|
+
The `init` command generates keys, creates configs, exchanges public keys, and scaffolds the `.env` file. All steps are idempotent — safe to re-run.
|
|
90
78
|
|
|
91
|
-
|
|
79
|
+
### Connect to Claude Code
|
|
92
80
|
|
|
93
|
-
|
|
81
|
+
**Option 1: Claude Code Plugin (Recommended)**
|
|
94
82
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
git clone <repo-url>
|
|
99
|
-
cd drawlatch
|
|
100
|
-
npm install
|
|
101
|
-
npm run build
|
|
83
|
+
```shell
|
|
84
|
+
# Install the plugin
|
|
85
|
+
/plugin install drawlatch@drawlatch
|
|
102
86
|
```
|
|
103
87
|
|
|
104
|
-
|
|
88
|
+
The plugin's MCP server starts automatically. The proxy uses `~/.drawlatch/` by default — see [Advanced Configuration](#advanced-configuration) to use a custom path.
|
|
105
89
|
|
|
106
|
-
|
|
90
|
+
**Option 2: Auto-Discovery**
|
|
107
91
|
|
|
108
|
-
|
|
109
|
-
~/.drawlatch/
|
|
110
|
-
├── proxy.config.json # Local proxy config
|
|
111
|
-
├── remote.config.json # Remote server config
|
|
112
|
-
└── keys/
|
|
113
|
-
├── local/ # MCP proxy keypairs (one per alias)
|
|
114
|
-
│ └── my-laptop/ # Alias-named subdirectory
|
|
115
|
-
│ ├── signing.pub.pem # Ed25519 public key (share this)
|
|
116
|
-
│ ├── signing.key.pem # Ed25519 private key (keep secret)
|
|
117
|
-
│ ├── exchange.pub.pem # X25519 public key (share this)
|
|
118
|
-
│ └── exchange.key.pem # X25519 private key (keep secret)
|
|
119
|
-
├── remote/ # Remote server keypair
|
|
120
|
-
│ ├── signing.pub.pem
|
|
121
|
-
│ ├── signing.key.pem
|
|
122
|
-
│ ├── exchange.pub.pem
|
|
123
|
-
│ └── exchange.key.pem
|
|
124
|
-
└── peers/
|
|
125
|
-
├── alice/ # One subdirectory per caller
|
|
126
|
-
│ ├── signing.pub.pem # Caller's public signing key
|
|
127
|
-
│ └── exchange.pub.pem # Caller's public exchange key
|
|
128
|
-
├── bob/ # Another caller
|
|
129
|
-
│ ├── signing.pub.pem
|
|
130
|
-
│ └── exchange.pub.pem
|
|
131
|
-
└── remote-server/ # Remote server's public keys (for proxy)
|
|
132
|
-
├── signing.pub.pem
|
|
133
|
-
└── exchange.pub.pem
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
### Step 1: Generate Keys
|
|
92
|
+
This repo includes a `.mcp.json` file, so Claude Code automatically discovers the MCP proxy when you open the project. Approve the server when prompted.
|
|
137
93
|
|
|
138
|
-
|
|
94
|
+
**Option 3: Manual Registration**
|
|
139
95
|
|
|
140
96
|
```bash
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
# Or use the default alias
|
|
145
|
-
npm run generate-keys -- local
|
|
146
|
-
|
|
147
|
-
# Generate remote server keypair
|
|
148
|
-
npm run generate-keys -- remote
|
|
97
|
+
claude mcp add drawlatch \
|
|
98
|
+
-e MCP_CONFIG_DIR=~/.drawlatch \
|
|
99
|
+
-- node /path/to/drawlatch/dist/mcp/server.js
|
|
149
100
|
```
|
|
150
101
|
|
|
151
|
-
|
|
102
|
+
> **Note:** Auto-discovery and manual registration use `dist/mcp/server.js`. The `dist/` directory is built automatically via `npm install` (prepare script). Rebuild manually with `npm run build` if needed.
|
|
152
103
|
|
|
153
|
-
|
|
104
|
+
### Manual Setup
|
|
154
105
|
|
|
155
|
-
|
|
106
|
+
For custom setups (different aliases, multiple callers, different machines), you can configure everything manually instead of using `drawlatch init`.
|
|
156
107
|
|
|
157
|
-
|
|
158
|
-
npm run generate-keys -- --dir /path/to/custom/keys
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
Or inspect the fingerprint of an existing keypair:
|
|
108
|
+
**1. Generate keys:**
|
|
162
109
|
|
|
163
110
|
```bash
|
|
164
|
-
|
|
111
|
+
drawlatch generate-keys caller my-laptop
|
|
112
|
+
drawlatch generate-keys server
|
|
165
113
|
```
|
|
166
114
|
|
|
167
|
-
|
|
115
|
+
**2. Exchange public keys** — on separate machines, copy `*.pub.pem` files to the matching `keys/callers/<alias>/` or `keys/server/` directory on the other machine. See [Key Exchange](#key-exchange) for details.
|
|
168
116
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
**From local to remote** — copy the proxy's public keys into a caller directory on the remote server. Since local keys are now stored per-alias, the alias directory name naturally matches the peer directory:
|
|
117
|
+
**3. Create configs** — copy the example files and edit:
|
|
172
118
|
|
|
173
119
|
```bash
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
cp ~/.drawlatch/keys/local/my-laptop/signing.pub.pem \
|
|
177
|
-
~/.drawlatch/keys/peers/my-laptop/signing.pub.pem
|
|
178
|
-
|
|
179
|
-
cp ~/.drawlatch/keys/local/my-laptop/exchange.pub.pem \
|
|
180
|
-
~/.drawlatch/keys/peers/my-laptop/exchange.pub.pem
|
|
120
|
+
cp remote.config.example.json ~/.drawlatch/remote.config.json
|
|
121
|
+
cp proxy.config.example.json ~/.drawlatch/proxy.config.json
|
|
181
122
|
```
|
|
182
123
|
|
|
183
|
-
**
|
|
124
|
+
**4. Create a `.env` file** with your API secrets:
|
|
184
125
|
|
|
185
126
|
```bash
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
cp ~/.drawlatch/keys/remote/exchange.pub.pem \
|
|
192
|
-
~/.drawlatch/keys/peers/remote-server/exchange.pub.pem
|
|
127
|
+
cat > ~/.drawlatch/.env << 'EOF'
|
|
128
|
+
# GITHUB_TOKEN=ghp_your_token_here
|
|
129
|
+
# DISCORD_BOT_TOKEN=your_bot_token_here
|
|
130
|
+
EOF
|
|
193
131
|
```
|
|
194
132
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
### Step 3: Create the Local Proxy Config
|
|
198
|
-
|
|
199
|
-
Copy the example and edit the paths to match your setup:
|
|
133
|
+
**5. Start the server:**
|
|
200
134
|
|
|
201
135
|
```bash
|
|
202
|
-
|
|
136
|
+
drawlatch start
|
|
137
|
+
drawlatch doctor # Validate full setup
|
|
203
138
|
```
|
|
204
139
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
```json
|
|
208
|
-
{
|
|
209
|
-
"remoteUrl": "http://127.0.0.1:9999",
|
|
210
|
-
"localKeyAlias": "my-laptop",
|
|
211
|
-
"remotePublicKeysDir": "~/.drawlatch/keys/peers/remote-server",
|
|
212
|
-
"connectTimeout": 10000,
|
|
213
|
-
"requestTimeout": 30000
|
|
214
|
-
}
|
|
215
|
-
```
|
|
216
|
-
|
|
217
|
-
| Field | Description | Default |
|
|
218
|
-
| --------------------- | ----------------------------------------------------------------------------------------------- | ------------------------------------- |
|
|
219
|
-
| `remoteUrl` | URL of the remote secure server | `http://localhost:9999` |
|
|
220
|
-
| `localKeyAlias` | Key alias — resolved to `keys/local/<alias>/`. Overridden by `MCP_KEY_ALIAS` env var at runtime | _(none)_ |
|
|
221
|
-
| `localKeysDir` | Absolute path to the proxy's own keypair directory. Ignored when `localKeyAlias` is set | `~/.drawlatch/keys/local/default` |
|
|
222
|
-
| `remotePublicKeysDir` | Absolute path to the remote server's public keys | `~/.drawlatch/keys/peers/remote-server` |
|
|
223
|
-
| `connectTimeout` | Handshake timeout in milliseconds | `10000` (10s) |
|
|
224
|
-
| `requestTimeout` | Request timeout in milliseconds | `30000` (30s) |
|
|
225
|
-
|
|
226
|
-
**Alias resolution priority:**
|
|
227
|
-
|
|
228
|
-
1. `MCP_KEY_ALIAS` env var (highest — set per agent at spawn time)
|
|
229
|
-
2. `localKeyAlias` in `proxy.config.json`
|
|
230
|
-
3. `localKeysDir` in `proxy.config.json` (explicit full path for custom deployments)
|
|
231
|
-
4. Default: `keys/local/default`
|
|
232
|
-
|
|
233
|
-
### Step 4: Create the Remote Server Config
|
|
234
|
-
|
|
235
|
-
Copy the example and edit it to match your setup:
|
|
140
|
+
## MCP Tools
|
|
236
141
|
|
|
237
|
-
|
|
238
|
-
cp remote.config.example.json ~/.drawlatch/remote.config.json
|
|
239
|
-
```
|
|
142
|
+
Once connected, agents get these tools:
|
|
240
143
|
|
|
241
|
-
|
|
144
|
+
| Tool | Description |
|
|
145
|
+
|------|-------------|
|
|
146
|
+
| `secure_request` | Make authenticated HTTP requests. Route-level headers (auth tokens, API keys) are injected automatically — the agent never sees secret values. Supports JSON and multipart/form-data file uploads. |
|
|
147
|
+
| `list_routes` | Discover available APIs with metadata, docs links, allowed endpoints, and available secret placeholders. |
|
|
148
|
+
| `poll_events` | Retrieve buffered events from ingestors (Discord messages, GitHub webhooks, etc.) with cursor-based pagination. |
|
|
149
|
+
| `ingestor_status` | Get connection state, buffer sizes, event counts, and errors for all active ingestors. |
|
|
150
|
+
| `test_connection` | Verify API credentials with a pre-configured read-only request. |
|
|
151
|
+
| `control_listener` | Start, stop, or restart an event listener. |
|
|
152
|
+
| `list_listener_configs` | Get configurable fields for event listeners. |
|
|
153
|
+
| `set_listener_params` | Configure listener parameters (filters, buffer sizes, etc.). |
|
|
154
|
+
| `get_listener_params` | Read current listener parameter overrides. |
|
|
155
|
+
| `resolve_listener_options` | Fetch dynamic options for listener config fields (e.g., list of Trello boards). |
|
|
156
|
+
| `list_listener_instances` | List instances of a multi-instance listener. |
|
|
157
|
+
| `delete_listener_instance` | Remove a multi-instance listener instance. |
|
|
158
|
+
| `test_ingestor` | Test event listener configuration and credentials. |
|
|
242
159
|
|
|
243
|
-
|
|
160
|
+
## Configuration Reference
|
|
244
161
|
|
|
245
|
-
|
|
162
|
+
### Remote Server Config (`remote.config.json`)
|
|
246
163
|
|
|
247
164
|
```json
|
|
248
165
|
{
|
|
249
166
|
"host": "0.0.0.0",
|
|
250
167
|
"port": 9999,
|
|
251
|
-
"
|
|
252
|
-
"callers": {
|
|
253
|
-
"my-laptop": {
|
|
254
|
-
"name": "Personal Laptop",
|
|
255
|
-
"peerKeyDir": "~/.drawlatch/keys/peers/my-laptop",
|
|
256
|
-
"connections": ["github"]
|
|
257
|
-
}
|
|
258
|
-
},
|
|
168
|
+
"connectors": [],
|
|
169
|
+
"callers": {},
|
|
259
170
|
"rateLimitPerMinute": 60
|
|
260
171
|
}
|
|
261
172
|
```
|
|
262
173
|
|
|
263
|
-
|
|
174
|
+
| Field | Description | Default |
|
|
175
|
+
|-------|-------------|---------|
|
|
176
|
+
| `host` | Network interface to bind | `127.0.0.1` |
|
|
177
|
+
| `port` | Listen port | `9999` |
|
|
178
|
+
| `connectors` | Custom connector definitions (see below) | `[]` |
|
|
179
|
+
| `callers` | Per-caller access control (see below) | `{}` |
|
|
180
|
+
| `rateLimitPerMinute` | Max requests per minute per session | `60` |
|
|
264
181
|
|
|
265
|
-
|
|
182
|
+
Server keys are always loaded from `keys/server/` inside the config directory.
|
|
183
|
+
|
|
184
|
+
### Callers
|
|
185
|
+
|
|
186
|
+
Each caller is identified by their public key and declares which connections they can access:
|
|
266
187
|
|
|
267
188
|
```json
|
|
268
189
|
{
|
|
269
|
-
"host": "0.0.0.0",
|
|
270
|
-
"port": 9999,
|
|
271
|
-
"localKeysDir": "~/.drawlatch/keys/remote",
|
|
272
|
-
"connectors": [
|
|
273
|
-
{
|
|
274
|
-
"alias": "internal-api",
|
|
275
|
-
"name": "Internal Admin API",
|
|
276
|
-
"headers": { "Authorization": "Bearer ${ADMIN_KEY}" },
|
|
277
|
-
"secrets": { "ADMIN_KEY": "${INTERNAL_ADMIN_KEY}" },
|
|
278
|
-
"allowedEndpoints": ["https://admin.internal.com/**"]
|
|
279
|
-
}
|
|
280
|
-
],
|
|
281
190
|
"callers": {
|
|
282
191
|
"alice": {
|
|
283
192
|
"name": "Alice (senior engineer)",
|
|
284
|
-
"
|
|
285
|
-
"
|
|
193
|
+
"connections": ["github", "stripe", "internal-api"],
|
|
194
|
+
"env": {
|
|
195
|
+
"GITHUB_TOKEN": "${ALICE_GITHUB_TOKEN}"
|
|
196
|
+
}
|
|
286
197
|
},
|
|
287
198
|
"ci-server": {
|
|
288
199
|
"name": "GitHub Actions CI",
|
|
289
|
-
"peerKeyDir": "/keys/peers/ci-server",
|
|
290
200
|
"connections": ["github"]
|
|
291
201
|
}
|
|
292
|
-
}
|
|
293
|
-
"rateLimitPerMinute": 60
|
|
202
|
+
}
|
|
294
203
|
}
|
|
295
204
|
```
|
|
296
205
|
|
|
297
|
-
|
|
206
|
+
Caller public keys are loaded automatically from `keys/callers/<alias>/` — no path configuration needed.
|
|
207
|
+
|
|
208
|
+
| Field | Required | Description |
|
|
209
|
+
|-------|----------|-------------|
|
|
210
|
+
| `connections` | Yes | Array of connection names (built-in or custom connector aliases) |
|
|
211
|
+
| `name` | No | Human-readable name for audit logs |
|
|
212
|
+
| `env` | No | Per-caller env var overrides — redirect secret resolution per caller |
|
|
213
|
+
| `ingestorOverrides` | No | Per-caller ingestor config overrides ([details](INGESTORS.md#caller-level-ingestor-overrides)) |
|
|
298
214
|
|
|
299
|
-
|
|
215
|
+
The `env` map lets multiple callers share the same connection with different credentials:
|
|
216
|
+
- Keys are the env var names connectors reference (e.g., `GITHUB_TOKEN`)
|
|
217
|
+
- Values are `"${REAL_ENV_VAR}"` (redirect) or literal strings (direct injection)
|
|
218
|
+
- Checked before prefixed env vars during secret resolution
|
|
300
219
|
|
|
301
|
-
|
|
220
|
+
Without an explicit `env` mapping, secrets resolve via prefixed env vars (e.g., caller "alice" + `GITHUB_TOKEN` → `ALICE_GITHUB_TOKEN`).
|
|
221
|
+
|
|
222
|
+
### Custom Connectors
|
|
223
|
+
|
|
224
|
+
Define reusable route templates for APIs not covered by built-in connections:
|
|
302
225
|
|
|
303
226
|
```json
|
|
304
227
|
{
|
|
305
|
-
"
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
"
|
|
311
|
-
"
|
|
312
|
-
"connections": ["github"],
|
|
313
|
-
"env": {
|
|
314
|
-
"GITHUB_TOKEN": "${ALICE_GITHUB_TOKEN}"
|
|
315
|
-
}
|
|
316
|
-
},
|
|
317
|
-
"bob": {
|
|
318
|
-
"name": "Bob",
|
|
319
|
-
"peerKeyDir": "/keys/peers/bob",
|
|
320
|
-
"connections": ["github", "stripe"],
|
|
321
|
-
"env": {
|
|
322
|
-
"GITHUB_TOKEN": "${BOB_GITHUB_TOKEN}",
|
|
323
|
-
"STRIPE_SECRET_KEY": "sk_test_bob_dev_key"
|
|
324
|
-
}
|
|
228
|
+
"connectors": [
|
|
229
|
+
{
|
|
230
|
+
"alias": "internal-api",
|
|
231
|
+
"name": "Internal Admin API",
|
|
232
|
+
"allowedEndpoints": ["https://admin.internal.com/**"],
|
|
233
|
+
"headers": { "Authorization": "Bearer ${ADMIN_KEY}" },
|
|
234
|
+
"secrets": { "ADMIN_KEY": "${INTERNAL_ADMIN_KEY}" }
|
|
325
235
|
}
|
|
326
|
-
|
|
327
|
-
"rateLimitPerMinute": 60
|
|
236
|
+
]
|
|
328
237
|
}
|
|
329
238
|
```
|
|
330
239
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
240
|
+
| Field | Required | Description |
|
|
241
|
+
|-------|----------|-------------|
|
|
242
|
+
| `alias` | Yes | Unique name for referencing from caller `connections` lists |
|
|
243
|
+
| `allowedEndpoints` | Yes | Glob patterns for allowed URLs |
|
|
244
|
+
| `name` | No | Human-readable name |
|
|
245
|
+
| `description` | No | Short description |
|
|
246
|
+
| `docsUrl` | No | URL to API documentation |
|
|
247
|
+
| `headers` | No | Headers to auto-inject (`${VAR}` placeholders resolved from `secrets`) |
|
|
248
|
+
| `secrets` | No | Key-value pairs — literal strings or `${ENV_VAR}` references |
|
|
249
|
+
| `resolveSecretsInBody` | No | Resolve `${VAR}` in request bodies (default: `false`) |
|
|
340
250
|
|
|
341
|
-
|
|
342
|
-
| -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ |
|
|
343
|
-
| `host` | Network interface to bind to. Use `0.0.0.0` for all interfaces or `127.0.0.1` for local only | `127.0.0.1` |
|
|
344
|
-
| `port` | Port to listen on | `9999` |
|
|
345
|
-
| `localKeysDir` | Absolute path to the remote server's own keypair | `~/.drawlatch/keys/remote` |
|
|
346
|
-
| `connectors` | Array of custom connector definitions, each with an `alias` for referencing from callers (see [Connector Definition](#connector-definition)) | `[]` |
|
|
347
|
-
| `callers` | Per-caller access control. Keys are caller aliases used in audit logs (see [Caller Definition](#caller-definition)) | `{}` |
|
|
348
|
-
| `rateLimitPerMinute` | Max requests per minute per session | `60` |
|
|
251
|
+
Custom connectors with an `alias` matching a built-in connection name take precedence.
|
|
349
252
|
|
|
350
|
-
|
|
253
|
+
### Proxy Config (`proxy.config.json`)
|
|
351
254
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
| Field | Required | Description |
|
|
355
|
-
| ---------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------ |
|
|
356
|
-
| `alias` | Yes | Unique name for referencing this connector from caller `connections` lists |
|
|
357
|
-
| `allowedEndpoints` | Yes | Array of glob patterns for allowed URLs (e.g., `https://api.example.com/**`) |
|
|
358
|
-
| `name` | No | Human-readable name (e.g., `"Internal Admin API"`) |
|
|
359
|
-
| `description` | No | Short description of what the connector provides |
|
|
360
|
-
| `docsUrl` | No | URL to API documentation |
|
|
361
|
-
| `openApiUrl` | No | URL to OpenAPI/Swagger spec |
|
|
362
|
-
| `headers` | No | Headers to auto-inject. Values may contain `${VAR}` placeholders resolved from `secrets` |
|
|
363
|
-
| `secrets` | No | Key-value pairs. Values can be literal strings or `${ENV_VAR}` references resolved from environment variables at startup |
|
|
364
|
-
| `resolveSecretsInBody` | No | Whether to resolve `${VAR}` placeholders in request bodies. Default: `false` |
|
|
365
|
-
|
|
366
|
-
#### Caller Definition
|
|
367
|
-
|
|
368
|
-
| Field | Required | Description |
|
|
369
|
-
| ------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
370
|
-
| `peerKeyDir` | Yes | Path to this caller's public key files (`signing.pub.pem` + `exchange.pub.pem`) |
|
|
371
|
-
| `connections` | Yes | Array of connection names — references built-in templates (e.g., `"github"`) or custom connector aliases |
|
|
372
|
-
| `name` | No | Human-readable name for audit logs |
|
|
373
|
-
| `env` | No | Per-caller environment variable overrides (see [env overrides example](#example-per-caller-env-overrides-shared-connector-different-credentials)) |
|
|
374
|
-
| `ingestorOverrides` | No | Per-caller ingestor config overrides keyed by connection alias. Override event filters, buffer sizes, intents, or disable ingestors entirely. See **[INGESTORS.md](INGESTORS.md#caller-level-ingestor-overrides)** for full reference |
|
|
375
|
-
|
|
376
|
-
#### How Secrets Work
|
|
377
|
-
|
|
378
|
-
Secret values in the `secrets` map are resolved at session establishment time (per-caller):
|
|
379
|
-
|
|
380
|
-
- **Literal values** — used as-is: `"API_TOKEN": "sk_live_abc123"`
|
|
381
|
-
- **Environment variable references** — resolved from the server's environment: `"API_TOKEN": "${API_TOKEN}"`
|
|
382
|
-
- **Per-caller overrides** — when a caller has an `env` entry for a variable name, that value is used instead of `process.env`
|
|
383
|
-
|
|
384
|
-
Header values can reference secrets using `${VAR}` placeholders:
|
|
255
|
+
Used by the local MCP proxy to connect to the remote server:
|
|
385
256
|
|
|
386
257
|
```json
|
|
387
|
-
|
|
388
|
-
"
|
|
258
|
+
{
|
|
259
|
+
"remoteUrl": "http://127.0.0.1:9999",
|
|
260
|
+
"connectTimeout": 10000,
|
|
261
|
+
"requestTimeout": 30000
|
|
389
262
|
}
|
|
390
263
|
```
|
|
391
264
|
|
|
392
|
-
|
|
265
|
+
| Field | Description | Default |
|
|
266
|
+
|-------|-------------|---------|
|
|
267
|
+
| `remoteUrl` | URL of the remote server | `http://localhost:9999` |
|
|
268
|
+
| `connectTimeout` | Handshake timeout (ms) | `10000` |
|
|
269
|
+
| `requestTimeout` | Request timeout (ms) | `30000` |
|
|
393
270
|
|
|
394
|
-
|
|
271
|
+
Key paths are derived automatically — no configuration needed:
|
|
272
|
+
- Caller keys: `keys/callers/{MCP_KEY_ALIAS || "default"}/`
|
|
273
|
+
- Server public keys: `keys/server/`
|
|
395
274
|
|
|
396
|
-
|
|
275
|
+
### Advanced Configuration
|
|
397
276
|
|
|
398
|
-
|
|
399
|
-
{
|
|
400
|
-
"callers": {
|
|
401
|
-
"my-laptop": {
|
|
402
|
-
"peerKeyDir": "/keys/peers/my-laptop",
|
|
403
|
-
"connections": ["github", "stripe"]
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
```
|
|
277
|
+
#### `MCP_CONFIG_DIR`
|
|
408
278
|
|
|
409
|
-
|
|
279
|
+
By default, all config and key files live in `~/.drawlatch/`. Override with:
|
|
410
280
|
|
|
411
|
-
|
|
281
|
+
```bash
|
|
282
|
+
export MCP_CONFIG_DIR=/custom/path/to/config
|
|
283
|
+
```
|
|
412
284
|
|
|
413
|
-
|
|
285
|
+
Useful for CI environments or running multiple independent setups on the same machine.
|
|
414
286
|
|
|
415
|
-
|
|
287
|
+
## Connections
|
|
416
288
|
|
|
417
|
-
|
|
418
|
-
# Development (with hot reload via tsx)
|
|
419
|
-
npm run dev:remote
|
|
289
|
+
22 pre-built connection templates ship with drawlatch. Reference them by name in a caller's `connections` list:
|
|
420
290
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
291
|
+
| Connection | API | Required Env Var(s) |
|
|
292
|
+
|------------|-----|---------------------|
|
|
293
|
+
| `anthropic` | Anthropic Claude API | `ANTHROPIC_API_KEY` |
|
|
294
|
+
| `bluesky` | Bluesky (AT Protocol) | `BLUESKY_ACCESS_TOKEN` |
|
|
295
|
+
| `devin` | Devin AI API | `DEVIN_API_KEY` |
|
|
296
|
+
| `discord-bot` | Discord Bot API | `DISCORD_BOT_TOKEN` |
|
|
297
|
+
| `discord-oauth` | Discord OAuth2 API | `DISCORD_OAUTH_TOKEN` |
|
|
298
|
+
| `github` | GitHub REST API | `GITHUB_TOKEN` |
|
|
299
|
+
| `google` | Google Workspace APIs | `GOOGLE_API_TOKEN` |
|
|
300
|
+
| `google-ai` | Google AI (Gemini) | `GOOGLE_AI_API_KEY` |
|
|
301
|
+
| `hex` | Hex API | `HEX_TOKEN` |
|
|
302
|
+
| `lichess` | Lichess API | `LICHESS_API_TOKEN` |
|
|
303
|
+
| `linear` | Linear GraphQL API | `LINEAR_API_KEY` |
|
|
304
|
+
| `mastodon` | Mastodon API | `MASTODON_ACCESS_TOKEN` |
|
|
305
|
+
| `notion` | Notion API | `NOTION_API_KEY` |
|
|
306
|
+
| `openai` | OpenAI API | `OPENAI_API_KEY` |
|
|
307
|
+
| `openrouter` | OpenRouter API | `OPENROUTER_API_KEY` |
|
|
308
|
+
| `reddit` | Reddit API | `REDDIT_ACCESS_TOKEN` |
|
|
309
|
+
| `slack` | Slack Web API | `SLACK_BOT_TOKEN` |
|
|
310
|
+
| `stripe` | Stripe Payments API | `STRIPE_SECRET_KEY` |
|
|
311
|
+
| `telegram` | Telegram Bot API | `TELEGRAM_BOT_TOKEN` |
|
|
312
|
+
| `trello` | Trello API | `TRELLO_API_KEY`, `TRELLO_TOKEN` |
|
|
313
|
+
| `twitch` | Twitch Helix API | `TWITCH_ACCESS_TOKEN`, `TWITCH_CLIENT_ID` |
|
|
314
|
+
| `x` | X (Twitter) API v2 | `X_BEARER_TOKEN` |
|
|
424
315
|
|
|
425
|
-
**
|
|
316
|
+
See **[CONNECTIONS.md](CONNECTIONS.md)** for auth details, optional env vars, and usage notes per connection.
|
|
426
317
|
|
|
427
|
-
|
|
318
|
+
## Event Ingestion
|
|
428
319
|
|
|
429
|
-
|
|
320
|
+
Drawlatch can collect real-time events from external services and buffer them for agents to poll. Three ingestor types are supported:
|
|
430
321
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
322
|
+
| Type | How It Works | Connections |
|
|
323
|
+
|------|-------------|-------------|
|
|
324
|
+
| **WebSocket** | Persistent connections to event gateways | Discord Gateway, Slack Socket Mode |
|
|
325
|
+
| **Webhook** | HTTP receivers with signature verification | GitHub, Stripe, Trello |
|
|
326
|
+
| **Poll** | Interval-based HTTP requests | Notion, Linear, Reddit, X, Bluesky, Mastodon, Telegram, Twitch |
|
|
434
327
|
|
|
435
|
-
|
|
328
|
+
Events are stored in per-caller ring buffers (default 200, max 1000) with monotonic IDs for cursor-based pagination. Agents retrieve events via `poll_events` and check status via `ingestor_status`.
|
|
436
329
|
|
|
437
|
-
|
|
330
|
+
For webhook ingestors, the remote server must be publicly accessible (or behind a tunnel). Use `drawlatch start --tunnel` to automatically start a Cloudflare tunnel.
|
|
438
331
|
|
|
439
|
-
|
|
440
|
-
claude mcp add secure-proxy \
|
|
441
|
-
--transport stdio --scope local \
|
|
442
|
-
-e MCP_CONFIG_DIR=~/.drawlatch \
|
|
443
|
-
-- node /absolute/path/to/drawlatch/dist/mcp/server.js
|
|
444
|
-
```
|
|
332
|
+
See **[INGESTORS.md](INGESTORS.md)** for full configuration reference.
|
|
445
333
|
|
|
446
|
-
|
|
334
|
+
## Key Exchange
|
|
447
335
|
|
|
448
|
-
|
|
336
|
+
Remote mode requires mutual authentication via Ed25519/X25519 keypairs. Each identity gets four PEM files (signing + exchange, public + private). The `drawlatch init` command handles this automatically for single-machine setups.
|
|
449
337
|
|
|
450
|
-
|
|
338
|
+
**Directory structure:**
|
|
451
339
|
|
|
452
|
-
|
|
340
|
+
```
|
|
341
|
+
~/.drawlatch/keys/
|
|
342
|
+
├── callers/
|
|
343
|
+
│ ├── default/ # Default caller keypair
|
|
344
|
+
│ └── alice/ # Additional caller keypair
|
|
345
|
+
└── server/ # Server keypair
|
|
346
|
+
```
|
|
453
347
|
|
|
454
|
-
|
|
455
|
-
2. Point the external service's webhook URL to `https://<your-server>/webhooks/<path>` (e.g., `https://example.com/webhooks/github`)
|
|
456
|
-
3. Set the webhook signing secret as an environment variable on the remote server (e.g., `GITHUB_WEBHOOK_SECRET`, `STRIPE_WEBHOOK_SECRET`)
|
|
348
|
+
Both sides (caller and server) store their keys in the same directory tree. On a single machine, `drawlatch init` generates both and they can authenticate immediately. On separate machines, copy the `*.pub.pem` files to the corresponding directory on the other machine.
|
|
457
349
|
|
|
458
|
-
|
|
350
|
+
**Using [Callboard](https://github.com/WolpertingerLabs/callboard)?** Use `drawlatch sync` to exchange keys automatically via a double-code approval flow — no manual file copying needed.
|
|
459
351
|
|
|
460
|
-
### Multiple
|
|
352
|
+
### Multiple Agent Identities
|
|
461
353
|
|
|
462
|
-
|
|
354
|
+
Generate a keypair per agent and set `MCP_KEY_ALIAS` at spawn time:
|
|
463
355
|
|
|
464
356
|
```bash
|
|
465
|
-
|
|
466
|
-
|
|
357
|
+
drawlatch generate-keys caller alice
|
|
358
|
+
drawlatch generate-keys caller bob
|
|
467
359
|
```
|
|
468
360
|
|
|
469
|
-
Each agent's MCP server config specifies its alias via the `MCP_KEY_ALIAS` env var:
|
|
470
|
-
|
|
471
361
|
```json
|
|
472
362
|
{
|
|
473
363
|
"mcpServers": {
|
|
474
|
-
"
|
|
364
|
+
"drawlatch": {
|
|
475
365
|
"command": "node",
|
|
476
366
|
"args": ["dist/mcp/server.js"],
|
|
477
|
-
"env": {
|
|
478
|
-
"MCP_CONFIG_DIR": "~/.drawlatch",
|
|
479
|
-
"MCP_KEY_ALIAS": "alice"
|
|
480
|
-
}
|
|
367
|
+
"env": { "MCP_CONFIG_DIR": "~/.drawlatch", "MCP_KEY_ALIAS": "alice" }
|
|
481
368
|
}
|
|
482
369
|
}
|
|
483
370
|
}
|
|
484
371
|
```
|
|
485
372
|
|
|
486
|
-
|
|
373
|
+
Register each agent as a separate caller in `remote.config.json`.
|
|
487
374
|
|
|
488
|
-
##
|
|
489
|
-
|
|
490
|
-
Once connected, Claude Code gets access to four tools:
|
|
491
|
-
|
|
492
|
-
### `secure_request`
|
|
493
|
-
|
|
494
|
-
Make an authenticated HTTP request through the proxy. Route-level headers (e.g., `Authorization`) are injected automatically — the agent never sees the secret values.
|
|
495
|
-
|
|
496
|
-
```
|
|
497
|
-
method: GET | POST | PUT | PATCH | DELETE
|
|
498
|
-
url: Full URL (may contain ${VAR} placeholders)
|
|
499
|
-
headers: Optional additional headers
|
|
500
|
-
body: Optional request body
|
|
501
|
-
```
|
|
502
|
-
|
|
503
|
-
### `list_routes`
|
|
504
|
-
|
|
505
|
-
List all available routes for the current caller. Returns metadata (name, description, docs link), allowed endpoint patterns, available secret placeholder names (not values), and auto-injected header names. Different callers may see different routes based on their `connections` configuration.
|
|
506
|
-
|
|
507
|
-
### `poll_events`
|
|
508
|
-
|
|
509
|
-
Poll for new events from ingestors (Discord messages, GitHub webhooks, Notion updates, etc.). Returns events received since the given cursor.
|
|
375
|
+
## CLI Reference
|
|
510
376
|
|
|
511
377
|
```
|
|
512
|
-
|
|
513
|
-
after_id: Optional — cursor; returns events with id > after_id
|
|
514
|
-
```
|
|
515
|
-
|
|
516
|
-
Pass `after_id` from the last event you received to get only new events. Omit to get all buffered events. See **[INGESTORS.md](INGESTORS.md)** for details on configuring event sources.
|
|
517
|
-
|
|
518
|
-
### `ingestor_status`
|
|
378
|
+
drawlatch [command] [options]
|
|
519
379
|
|
|
520
|
-
|
|
380
|
+
Commands:
|
|
381
|
+
init Set up drawlatch (keys, config, .env) in one step
|
|
382
|
+
start Start the remote server (background daemon)
|
|
383
|
+
stop Stop the remote server
|
|
384
|
+
restart Restart the remote server
|
|
385
|
+
status Show server status (PID, port, uptime, health, sessions)
|
|
386
|
+
logs View server logs
|
|
387
|
+
config Show effective configuration and secret status
|
|
388
|
+
doctor Validate setup and diagnose issues
|
|
389
|
+
generate-keys Generate Ed25519 + X25519 keypairs
|
|
390
|
+
sync Exchange keys with a callboard instance
|
|
521
391
|
|
|
522
|
-
|
|
392
|
+
Options:
|
|
393
|
+
-h, --help Show help
|
|
394
|
+
-v, --version Show version
|
|
523
395
|
|
|
524
|
-
|
|
396
|
+
Init options:
|
|
397
|
+
--connections <list> Comma-separated connections to enable (e.g., github,slack)
|
|
398
|
+
--alias <name> Caller alias (default: "default")
|
|
525
399
|
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
400
|
+
Start options:
|
|
401
|
+
-f, --foreground Run in foreground
|
|
402
|
+
-t, --tunnel Start a Cloudflare tunnel for webhooks
|
|
403
|
+
--port <number> Override configured port
|
|
404
|
+
--host <address> Override configured host
|
|
529
405
|
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
resolveCallerRoutes,
|
|
534
|
-
resolveRoutes,
|
|
535
|
-
resolveSecrets,
|
|
536
|
-
} from "drawlatch/shared/config";
|
|
406
|
+
Logs options:
|
|
407
|
+
-n, --lines <num> Number of lines (default: 50)
|
|
408
|
+
--follow Tail the log output
|
|
537
409
|
|
|
538
|
-
|
|
539
|
-
|
|
410
|
+
Generate-keys subcommands:
|
|
411
|
+
caller [alias] Generate caller keypair (default alias: "default")
|
|
412
|
+
server Generate server keypair
|
|
413
|
+
show <path> Show fingerprint of existing keypair
|
|
414
|
+
--dir <path> Generate to custom directory
|
|
540
415
|
|
|
541
|
-
|
|
542
|
-
|
|
416
|
+
Sync options:
|
|
417
|
+
--ttl <seconds> Session timeout (default: 300)
|
|
543
418
|
```
|
|
544
419
|
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
| Export Path | Description |
|
|
548
|
-
| ---------------------------- | ------------------------------------------------------------------ |
|
|
549
|
-
| `drawlatch` | MCP proxy server (stdio transport) — the default entry point |
|
|
550
|
-
| `drawlatch/remote/server` | Remote server functions including `executeProxyRequest()` |
|
|
551
|
-
| `drawlatch/remote/ingestors` | `IngestorManager` and all ingestor types |
|
|
552
|
-
| `drawlatch/shared/config` | Config loading, caller/route resolution, secret resolution |
|
|
553
|
-
| `drawlatch/shared/connections`| Connection template loading |
|
|
554
|
-
| `drawlatch/shared/crypto` | Key generation, encrypted channel, key serialization |
|
|
555
|
-
| `drawlatch/shared/protocol` | Handshake protocol, message types |
|
|
420
|
+
## Library Usage (Local Mode)
|
|
556
421
|
|
|
557
|
-
|
|
422
|
+
Import drawlatch directly for in-process use — no server, no encryption:
|
|
558
423
|
|
|
559
424
|
```typescript
|
|
560
425
|
import { loadRemoteConfig, resolveCallerRoutes, resolveRoutes, resolveSecrets } from "drawlatch/shared/config";
|
|
561
426
|
import { executeProxyRequest } from "drawlatch/remote/server";
|
|
562
427
|
|
|
563
|
-
// Load config and resolve routes for a specific caller
|
|
564
428
|
const config = loadRemoteConfig();
|
|
565
429
|
const callerRoutes = resolveCallerRoutes(config, "my-laptop");
|
|
566
430
|
const callerEnv = resolveSecrets(config.callers["my-laptop"]?.env ?? {});
|
|
567
431
|
const routes = resolveRoutes(callerRoutes, callerEnv);
|
|
568
432
|
|
|
569
|
-
// Make a request — same function the remote server uses
|
|
570
433
|
const result = await executeProxyRequest(
|
|
571
434
|
{ method: "GET", url: "https://api.github.com/user" },
|
|
572
435
|
routes,
|
|
573
436
|
);
|
|
574
437
|
```
|
|
575
438
|
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
## Development
|
|
439
|
+
### Available Exports
|
|
579
440
|
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
441
|
+
| Export Path | Description |
|
|
442
|
+
|-------------|-------------|
|
|
443
|
+
| `drawlatch` | MCP proxy server (stdio transport) |
|
|
444
|
+
| `drawlatch/remote/server` | `executeProxyRequest()` and server functions |
|
|
445
|
+
| `drawlatch/remote/ingestors` | `IngestorManager` and ingestor types |
|
|
446
|
+
| `drawlatch/shared/config` | Config loading, route/secret resolution |
|
|
447
|
+
| `drawlatch/shared/connections` | Connection template loading |
|
|
448
|
+
| `drawlatch/shared/env-utils` | Environment variable and secret utilities |
|
|
449
|
+
| `drawlatch/shared/crypto` | Key generation, encrypted channel |
|
|
450
|
+
| `drawlatch/shared/protocol` | Handshake protocol, message types |
|
|
583
451
|
|
|
584
|
-
|
|
585
|
-
npm run test:watch
|
|
452
|
+
## Security Model
|
|
586
453
|
|
|
587
|
-
|
|
588
|
-
npm run test:coverage
|
|
454
|
+
### Both Modes
|
|
589
455
|
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
456
|
+
- **Endpoint allowlisting** — requests only proxied to explicitly configured URL patterns
|
|
457
|
+
- **Per-caller access control** — each caller only sees their assigned connections
|
|
458
|
+
- **Per-caller credential isolation** — same connector, different credentials via `env` overrides
|
|
459
|
+
- **Rate limiting** — configurable per-session (default: 60/min)
|
|
460
|
+
- **Audit logging** — all operations logged with caller identity, session ID, timestamps
|
|
593
461
|
|
|
594
|
-
|
|
595
|
-
npm run format
|
|
596
|
-
npm run format:check
|
|
597
|
-
```
|
|
462
|
+
### Remote Mode Only
|
|
598
463
|
|
|
599
|
-
|
|
464
|
+
- **Zero secrets on the client** — the MCP proxy never sees API keys or tokens
|
|
465
|
+
- **Mutual authentication** — Ed25519 signatures before any data exchange
|
|
466
|
+
- **End-to-end encryption** — AES-256-GCM with X25519 ECDH session keys
|
|
467
|
+
- **Replay protection** — monotonic counters on all encrypted messages
|
|
468
|
+
- **Session isolation** — unique session keys per handshake, 30-minute TTL
|
|
469
|
+
- **File permissions** — private keys `0600`, key directories `0700`
|
|
600
470
|
|
|
601
|
-
|
|
471
|
+
## Development
|
|
602
472
|
|
|
603
|
-
|
|
473
|
+
```bash
|
|
474
|
+
npm test # Run tests
|
|
475
|
+
npm run test:watch # Watch mode
|
|
476
|
+
npm run test:coverage # Coverage report
|
|
477
|
+
npm run lint # Lint
|
|
478
|
+
npm run format # Format
|
|
604
479
|
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
├── .claude-plugin/ # Plugin metadata
|
|
608
|
-
│ ├── plugin.json # Plugin manifest (name, version, description)
|
|
609
|
-
│ └── marketplace.json # Marketplace catalog for distribution
|
|
610
|
-
├── .mcp.json # MCP server config (used by plugin system + auto-discovery)
|
|
611
|
-
├── dist/ # Compiled JavaScript (built via `npm run build` or `prepare`)
|
|
612
|
-
│ └── mcp/server.js # MCP proxy entrypoint
|
|
613
|
-
└── src/ # TypeScript source
|
|
480
|
+
npm run dev:remote # Remote server with hot reload
|
|
481
|
+
npm run dev:mcp # MCP proxy with hot reload
|
|
614
482
|
```
|
|
615
483
|
|
|
616
|
-
### Source
|
|
484
|
+
### Source Structure
|
|
617
485
|
|
|
618
486
|
```
|
|
619
487
|
src/
|
|
620
|
-
├── cli/
|
|
621
|
-
|
|
622
|
-
├──
|
|
623
|
-
│ ├── github.json # GitHub REST API
|
|
624
|
-
│ ├── stripe.json # Stripe Payments API
|
|
625
|
-
│ └── ... # 22 templates total
|
|
626
|
-
├── mcp/
|
|
627
|
-
│ └── server.ts # Local MCP proxy server (stdio transport)
|
|
488
|
+
├── cli/ # Key generation CLI
|
|
489
|
+
├── connections/ # 22 pre-built route templates (JSON)
|
|
490
|
+
├── mcp/server.ts # Local MCP proxy (stdio transport)
|
|
628
491
|
├── remote/
|
|
629
|
-
│ ├── server.ts
|
|
630
|
-
│
|
|
631
|
-
│
|
|
632
|
-
│
|
|
633
|
-
│ ├──
|
|
634
|
-
│
|
|
635
|
-
│ ├── manager.ts # Lifecycle management, per-caller routing
|
|
636
|
-
│ ├── registry.ts # Factory registry for ingestor types
|
|
637
|
-
│ ├── types.ts # Shared types and config interfaces
|
|
638
|
-
│ ├── discord/ # Discord Gateway WebSocket (v10)
|
|
639
|
-
│ ├── slack/ # Slack Socket Mode WebSocket
|
|
640
|
-
│ ├── webhook/ # Webhook receivers (GitHub, Stripe, Trello)
|
|
641
|
-
│ └── poll/ # Interval-based HTTP polling (Notion, Linear, etc.)
|
|
492
|
+
│ ├── server.ts # Remote secure server (Express)
|
|
493
|
+
│ └── ingestors/ # Event ingestion system
|
|
494
|
+
│ ├── discord/ # Discord Gateway WebSocket
|
|
495
|
+
│ ├── slack/ # Slack Socket Mode WebSocket
|
|
496
|
+
│ ├── webhook/ # GitHub, Stripe, Trello webhooks
|
|
497
|
+
│ └── poll/ # Interval-based HTTP polling
|
|
642
498
|
└── shared/
|
|
643
|
-
├── config.ts
|
|
644
|
-
├── connections.ts
|
|
645
|
-
├──
|
|
646
|
-
├── crypto/
|
|
647
|
-
|
|
648
|
-
│ ├── channel.ts # AES-256-GCM encrypted channel
|
|
649
|
-
│ └── index.ts # Re-exports
|
|
650
|
-
└── protocol/
|
|
651
|
-
├── handshake.ts # Mutual auth (Noise NK-inspired)
|
|
652
|
-
├── messages.ts # Application-layer message types
|
|
653
|
-
└── index.ts # Re-exports
|
|
499
|
+
├── config.ts # Config loading, route resolution
|
|
500
|
+
├── connections.ts # Connection template loading
|
|
501
|
+
├── env-utils.ts # Environment variable utilities
|
|
502
|
+
├── crypto/ # Ed25519/X25519 keys, AES-256-GCM channel
|
|
503
|
+
└── protocol/ # Handshake, message types
|
|
654
504
|
```
|
|
655
505
|
|
|
656
|
-
## Security Model
|
|
657
|
-
|
|
658
|
-
### Both Modes
|
|
659
|
-
|
|
660
|
-
These protections apply regardless of whether you use remote or local mode:
|
|
661
|
-
|
|
662
|
-
- **Per-caller access control** — each caller only sees and can use the connections explicitly assigned to them
|
|
663
|
-
- **Per-caller credential isolation** — callers sharing the same connector can have different credentials via `env` overrides
|
|
664
|
-
- **Endpoint allowlisting** — requests are only proxied to explicitly configured URL patterns
|
|
665
|
-
- **Rate limiting** — configurable per-session request rate limiting (default: 60/min)
|
|
666
|
-
- **Audit logging** — all operations are logged with caller identity, session ID, and timestamps
|
|
667
|
-
|
|
668
|
-
### Remote Mode Only
|
|
669
|
-
|
|
670
|
-
These additional protections apply when running the two-component remote architecture:
|
|
671
|
-
|
|
672
|
-
- **Zero secrets on the client** — the local MCP proxy never sees API keys or tokens
|
|
673
|
-
- **Mutual authentication** — both sides prove their identity using Ed25519 signatures before any data is exchanged
|
|
674
|
-
- **End-to-end encryption** — all requests/responses are encrypted with AES-256-GCM session keys derived via X25519 ECDH
|
|
675
|
-
- **Replay protection** — monotonic counters prevent replay attacks
|
|
676
|
-
- **Session isolation** — each handshake produces unique session keys with a 30-minute TTL
|
|
677
|
-
- **File permissions** — private keys are saved with `0600`, directories with `0700`
|
|
678
|
-
|
|
679
|
-
### Local Mode Caveat
|
|
680
|
-
|
|
681
|
-
When using drawlatch as an in-process library (local mode), secrets are resolved from `process.env` on the same machine as the agent. The encryption and mutual authentication layers are not used. The security value in local mode comes from **structured access control** (endpoint allowlisting, per-caller route isolation) rather than cryptographic secret isolation.
|
|
682
|
-
|
|
683
506
|
## License
|
|
684
507
|
|
|
685
508
|
MIT
|