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.
- package/README.md +21 -19
- package/package.json +1 -1
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
133
|
+
npx mothpost admin connection my-connection
|
|
132
134
|
```
|
|
133
135
|
|
|
134
136
|
Grant one tool:
|
|
135
137
|
|
|
136
138
|
```bash
|
|
137
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
171
|
+
npx mothpost admin revoke-all my-connection
|
|
170
172
|
```
|
|
171
173
|
|
|
172
174
|
Inspect the connection again:
|
|
173
175
|
|
|
174
176
|
```bash
|
|
175
|
-
|
|
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
|
-
|
|
193
|
+
npx mothpost admin connection my-connection # prints "Server: <SERVER_DEVICE_ID>"
|
|
192
194
|
|
|
193
|
-
|
|
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
|
|
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
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
|
|
185
|
-
const
|
|
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
|
|
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
|
-
|
|
205
|
-
|
|
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
|
}
|