mothpost 0.1.0 → 0.1.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.
Files changed (3) hide show
  1. package/README.md +21 -19
  2. package/package.json +1 -1
  3. package/src/admin.js +30 -9
package/README.md CHANGED
@@ -32,7 +32,7 @@ MCP prompts, resources, and notifications are intentionally out of scope for the
32
32
  A newly connected client has no tools available by default. An administrator can grant access to one tool:
33
33
 
34
34
  ```bash
35
- node src/cli.js admin grant-tool my-connection get_door_state
35
+ npx mothpost admin grant-tool my-connection get_door_state
36
36
  ```
37
37
 
38
38
  The client can then discover and invoke `get_door_state`. Revoking the permission prevents subsequent invocations, even if the agent still has the tool in a cached tool list.
@@ -51,15 +51,17 @@ These components can run on separate networks. No VPN or publicly exposed MCP en
51
51
 
52
52
  ### 1. Installation
53
53
 
54
- On each machine running `mothpost`:
54
+ `mothpost` requires Node.js 20 or later. No installation is needed: the commands below run it with `npx`, which downloads it on first use.
55
+
56
+ If you run it often (for example the Admin commands), you can install it globally and use `mothpost` instead of `npx mothpost`:
55
57
 
56
58
  ```bash
57
- git clone https://github.com/fcavallarin/mothpost.git
58
- cd mothpost
59
- npm install
59
+ npm install -g mothpost
60
60
  ```
61
61
 
62
- Each component reads `config.json` from its working directory. [`config.example.json`](config.example.json) lists every option; copy it and keep only the section for the component you run (`server`, `client`, or none for the Admin). `config.json` is git-ignored.
62
+ Each component reads `config.json` from its working directory, so give each one its own directory. [`config.example.json`](config.example.json) lists every option; copy the section for the component you run (`server`, `client`, or none for the Admin).
63
+
64
+ To run from source instead, clone the repository, run `npm install`, and replace `npx mothpost` with `node src/cli.js` (`config.json` is git-ignored).
63
65
 
64
66
  ### 2. Configure the server
65
67
 
@@ -80,7 +82,7 @@ Replace the URL with your MCP server's local endpoint and ensure that it is runn
80
82
  Start the bridge:
81
83
 
82
84
  ```bash
83
- node src/cli.js server
85
+ npx mothpost server
84
86
  ```
85
87
 
86
88
  On its first run, it displays Base64-encoded pairing data and prompts for a pairing PIN. Leave the terminal running; pairing will be completed in step 4.
@@ -102,19 +104,19 @@ On the machine running your AI agent, create `config.json`:
102
104
  Start the local MCP bridge:
103
105
 
104
106
  ```bash
105
- node src/cli.js client
107
+ npx mothpost client
106
108
  ```
107
109
 
108
110
  On first run, it also displays pairing data and asks for a PIN. Leave this terminal running.
109
111
 
110
- **Note:** The server, client, and Admin each need their own working directory and `config.json`. If you run all three on the same machine, use separate copies/directories to avoid conflicting configurations.
112
+ **Note:** The server, client, and Admin each need their own working directory and `config.json`, also when they run on the same machine: their identities and state are stored in that directory.
111
113
 
112
114
  ### 4. Connect the client and server
113
115
 
114
116
  In a third terminal, connect them using the pairing data displayed by each endpoint:
115
117
 
116
118
  ```bash
117
- node src/cli.js admin connect my-connection \
119
+ npx mothpost admin connect my-connection \
118
120
  "<CLIENT_PAIRING_DATA>" \
119
121
  --new-server "<SERVER_PAIRING_DATA>"
120
122
  ```
@@ -128,19 +130,19 @@ Once pairing completes, the endpoints have the permissions needed to exchange MC
128
130
  Inspect the connection and its available tools:
129
131
 
130
132
  ```bash
131
- node src/cli.js admin connection my-connection
133
+ npx mothpost admin connection my-connection
132
134
  ```
133
135
 
134
136
  Grant one tool:
135
137
 
136
138
  ```bash
137
- node src/cli.js admin grant-tool my-connection get_door_state
139
+ npx mothpost admin grant-tool my-connection get_door_state
138
140
  ```
139
141
 
140
142
  Replace `get_door_state` with a tool exposed by your MCP server. To grant all available tools:
141
143
 
142
144
  ```bash
143
- node src/cli.js admin grant-all my-connection
145
+ npx mothpost admin grant-all my-connection
144
146
  ```
145
147
 
146
148
  Tool permissions are enforced by the bridge's underlying access-control layer, independently of the MCP server's own authorization mechanisms.
@@ -160,19 +162,19 @@ The agent can discover and invoke only the tools permitted for its connection. R
160
162
  Revoke access to one tool:
161
163
 
162
164
  ```bash
163
- node src/cli.js admin revoke-tool my-connection get_door_state
165
+ npx mothpost admin revoke-tool my-connection get_door_state
164
166
  ```
165
167
 
166
168
  Or revoke all tools:
167
169
 
168
170
  ```bash
169
- node src/cli.js admin revoke-all my-connection
171
+ npx mothpost admin revoke-all my-connection
170
172
  ```
171
173
 
172
174
  Inspect the connection again:
173
175
 
174
176
  ```bash
175
- node src/cli.js admin connection my-connection
177
+ npx mothpost admin connection my-connection
176
178
  ```
177
179
 
178
180
  Revocation blocks subsequent calls even if the agent cached the old tool list.
@@ -188,9 +190,9 @@ Each agent gets its own connection, with its own tool permissions. There are two
188
190
  **Shared server bridge.** Start a new client for the agent (with its own `id` and `port`), find the server's device ID, and connect the client to the existing bridge:
189
191
 
190
192
  ```bash
191
- node src/cli.js admin connection my-connection # prints "Server: <SERVER_DEVICE_ID>"
193
+ npx mothpost admin connection my-connection # prints "Server: <SERVER_DEVICE_ID>"
192
194
 
193
- node src/cli.js admin connect my-second-agent \
195
+ npx mothpost admin connect my-second-agent \
194
196
  "<CLIENT_PAIRING_DATA>" \
195
197
  --existing-server <SERVER_DEVICE_ID>
196
198
  ```
@@ -320,7 +322,7 @@ This is planned work, **not a feature of the current prototype**.
320
322
 
321
323
  ## Status
322
324
 
323
- `mothpost` is an experimental prototype/demo and is not yet published on npm.
325
+ `mothpost` is an experimental prototype/demo. It is published on [npm](https://www.npmjs.com/package/mothpost), but its CLI and configuration may change between versions.
324
326
 
325
327
  ## Related
326
328
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mothpost",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Bridge AI agents and MCP servers across networks, with per-tool permissions, over the SEPT encrypted relay",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/admin.js CHANGED
@@ -4,6 +4,8 @@ import { Option } from "commander";
4
4
  import { sleep } from "./common.js"
5
5
  import { BaseCommand } from "./base-command.js";
6
6
 
7
+ const TOOL_CALL_PREFIX = "mcp.tools.call."
8
+
7
9
  export class MothpostAdmin extends BaseCommand {
8
10
  constructor(program) {
9
11
  super("admin", "Manage connections and tool permissions", program)
@@ -148,14 +150,22 @@ export class MothpostAdmin extends BaseCommand {
148
150
  return server?.tools ?? []
149
151
  }
150
152
 
153
+ async getEnabledTools(connection) {
154
+ const acl = await this.sept.getACL()
155
+ const edge = acl.find(a =>
156
+ a.srcDeviceId === connection.client &&
157
+ a.dstDeviceId === connection.server
158
+ )
159
+ return (edge?.policy?.allowedEventTypes ?? [])
160
+ .filter(type => type.startsWith(TOOL_CALL_PREFIX))
161
+ .map(type => type.slice(TOOL_CALL_PREFIX.length))
162
+ }
163
+
151
164
  async grantTool(connectionName, tool) {
152
165
  const connection = await this.getConnection(connectionName)
153
166
  await this.sept.grant(connection.client, connection.server, [`mcp.tools.call.${tool}`])
154
167
  }
155
168
 
156
- // The server reports its tools to admins only after pairing completes, so
157
- // right after `connect` the list may not have reached this admin yet.
158
- // Wait for it briefly instead of silently granting/revoking nothing.
159
169
  async getConnectionWithTools(connectionName, timeoutMs = 10000) {
160
170
  const connection = await this.getConnection(connectionName)
161
171
  const start = Date.now()
@@ -181,9 +191,15 @@ export class MothpostAdmin extends BaseCommand {
181
191
  }
182
192
 
183
193
  async revokeAll(connectionName) {
184
- const { connection, tools } = await this.getConnectionWithTools(connectionName)
185
- const eventTypes = tools.map(t => `mcp.tools.call.${t.name}`)
194
+ const connection = await this.getConnection(connectionName)
195
+ const enabled = await this.getEnabledTools(connection)
196
+ if (enabled.length === 0) {
197
+ console.log(`No tools granted on ${connectionName}`)
198
+ return
199
+ }
200
+ const eventTypes = enabled.map(name => `${TOOL_CALL_PREFIX}${name}`)
186
201
  await this.sept.revoke(connection.client, connection.server, eventTypes)
202
+ console.log(`Revoked: ${enabled.join(", ")}`)
187
203
  }
188
204
 
189
205
 
@@ -195,14 +211,19 @@ export class MothpostAdmin extends BaseCommand {
195
211
  async connection(connectionName) {
196
212
  const c = await this.getConnection(connectionName)
197
213
  const tools = await this.getServerTools(c.server)
198
- const acl = await this.sept.getACL()
199
- const enabledTools = acl.find(a => a.srcDeviceId === c.client && a.dstDeviceId === c.server)?.policy?.allowedEventTypes || []
214
+ const enabled = new Set(await this.getEnabledTools(c))
200
215
  console.log(`Client: ${c.client}`)
201
216
  console.log(`Server: ${c.server}`)
202
217
  console.log(`Tools:`)
203
218
  for (const t of tools) {
204
- const enabled = enabledTools.includes(`mcp.tools.call.${t.name}`)
205
- console.log(` ${enabled ? pc.green('✓') : pc.red('✗')} ${t.name}`)
219
+ console.log(` ${enabled.has(t.name) ? pc.green('✓') : pc.red('✗')} ${t.name}`)
220
+ }
221
+
222
+ const exposed = new Set(tools.map(t => t.name))
223
+ for (const name of enabled) {
224
+ if (!exposed.has(name)) {
225
+ console.log(` ${pc.yellow('!')} ${name} ${pc.dim('(granted, not exposed by the server)')}`)
226
+ }
206
227
  }
207
228
  }
208
229
  }