@odingard/guardian-mcp 0.3.2 → 0.3.4
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 +138 -9
- package/dist/index.js +766 -150
- package/package.json +7 -5
- package/server.json +2 -2
package/README.md
CHANGED
|
@@ -39,6 +39,18 @@ npm i -g @odingard/guardian-mcp
|
|
|
39
39
|
|
|
40
40
|
Nothing executes without a Guardian capability — the model's output is a proposal, never an action.
|
|
41
41
|
|
|
42
|
+
Only a call whose receipt status is `sent-verified` is returned as a plain
|
|
43
|
+
result. Every other outcome is an MCP tool error (`isError: true`) that says
|
|
44
|
+
what happened:
|
|
45
|
+
|
|
46
|
+
- DENY, or Guardian could not be reached: nothing was done. A denial says not
|
|
47
|
+
to retry the same call; an outage says to try again later.
|
|
48
|
+
- Receipt status `not-sent`: `outcome: "not-done"`, the call was not done.
|
|
49
|
+
- Receipt status `outcome-pending` or `outcome-unconfirmed`, or an execute
|
|
50
|
+
answer that was refused or lost: `outcome: "unknown"`. The call may have
|
|
51
|
+
been done, so check (`guardian_receipt`, or the target system) before
|
|
52
|
+
retrying.
|
|
53
|
+
|
|
42
54
|
## Govern an existing MCP server: `guardian-mcp proxy` (0.3)
|
|
43
55
|
|
|
44
56
|
Put Guardian in front of any stdio MCP server you already use, such as
|
|
@@ -62,11 +74,12 @@ upstream server's tools unchanged, and every call is decided first:
|
|
|
62
74
|
}
|
|
63
75
|
```
|
|
64
76
|
|
|
65
|
-
| Decision
|
|
66
|
-
|
|
|
67
|
-
| **ALLOW**
|
|
68
|
-
| **ASK**
|
|
69
|
-
| **DENY**
|
|
77
|
+
| Decision | What happens |
|
|
78
|
+
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
79
|
+
| **ALLOW** | The call is forwarded to the upstream server, then the capability is redeemed and the receipt recorded. The upstream result is returned unchanged, with Guardian's `requestId` under `_meta["com.odingard/guardian"]`, so the client can fetch the receipt with `guardian_receipt`. |
|
|
80
|
+
| **ASK** | Nothing is forwarded. The exact call is held under a `requestId`; after a human approves, `guardian_claim` forwards exactly that call, once. |
|
|
81
|
+
| **DENY** | Nothing is forwarded, ever. The result is an MCP tool error (`isError: true`) with the reason code and, when Guardian made a record of it, the `requestId`. |
|
|
82
|
+
| **Guardian unreachable** | Nothing is forwarded. The result is a tool error (`isError: true`) whose JSON keeps `decision: "DENY"` with `reasonCode: "SERVICE_UNAVAILABLE"`, and its message says Guardian could not be reached and the call can be tried again later. A held call stays held, so `guardian_claim` can be retried. |
|
|
70
83
|
|
|
71
84
|
- `guardian_claim` and `guardian_receipt` are added to the tool list.
|
|
72
85
|
- The upstream server never receives `GUARDIAN_*` settings, so it never sees
|
|
@@ -79,14 +92,128 @@ upstream server's tools unchanged, and every call is decided first:
|
|
|
79
92
|
for the agent.
|
|
80
93
|
- Since 0.3.1 the proxy does not try to register tools itself.
|
|
81
94
|
- `GUARDIAN_AUTO_REGISTER=true` turns that back on, but it is denied unless
|
|
82
|
-
the agent's grant pins `mcp.install
|
|
95
|
+
the agent's grant pins `mcp.install`, and it only works for a tool that
|
|
96
|
+
has no entry yet. An "already registered" answer is refused
|
|
97
|
+
(`RECORD_TRANSPORT_UNVERIFIED`) and nothing is forwarded, including after
|
|
98
|
+
a proxy restart for an entry the proxy created itself. Once the entries
|
|
99
|
+
exist, run the proxy without it.
|
|
83
100
|
- When Guardian refuses, the result says what to fix.
|
|
84
101
|
- Held calls live in the proxy process. Restarting the proxy drops them, and
|
|
85
102
|
nothing is forwarded.
|
|
103
|
+
- **Cancelled requests are not forwarded.** If the client cancels a
|
|
104
|
+
`tools/call` (or it times out) before the call is sent upstream, for
|
|
105
|
+
example while Guardian is still deciding, the call is not forwarded and the
|
|
106
|
+
capability is not redeemed. A `guardian_claim` cancelled that way keeps the
|
|
107
|
+
call held with its approval, so the next `guardian_claim` forwards it
|
|
108
|
+
without asking the person again. Once a call has been sent upstream it runs
|
|
109
|
+
to its real outcome, which is recorded.
|
|
110
|
+
- **A second `guardian_claim` of the same `requestId`** says the call was
|
|
111
|
+
already forwarded and must not be proposed again. If the first claim has
|
|
112
|
+
finished, its result is returned again after that notice; if it is still
|
|
113
|
+
running, the answer says to check `guardian_receipt`. The proxy remembers
|
|
114
|
+
the last 1000 claimed requests. A `requestId` it does not hold is answered
|
|
115
|
+
with "this claim forwarded nothing", and a reminder to check
|
|
116
|
+
`guardian_receipt` if it was claimed before.
|
|
117
|
+
- An unexpected proxy error after a call was forwarded says the call may
|
|
118
|
+
have run, never that it was not forwarded.
|
|
86
119
|
|
|
87
120
|
`guardian-mcp --help` prints usage. A missing setting prints a one-line
|
|
88
121
|
message naming it instead of a stack trace.
|
|
89
122
|
|
|
123
|
+
## Govern a remote MCP server: `guardian-mcp proxy --url` (0.3.3 and later)
|
|
124
|
+
|
|
125
|
+
**Status: tested against a real Guardian; production proof pending.** It ran
|
|
126
|
+
end to end over Streamable HTTP against the real Guardian server code (allow,
|
|
127
|
+
ask then approve, deny, outage), not yet on hosted production. SSE is tested
|
|
128
|
+
against local servers and a stand-in Guardian only, and no real hosted server
|
|
129
|
+
such as Composio has been tried. Composio's `x-api-key` header is
|
|
130
|
+
unconfirmed.
|
|
131
|
+
|
|
132
|
+
The proxy can also front an MCP server that runs online, such as Composio.
|
|
133
|
+
The agent still starts `guardian-mcp` over stdio like any
|
|
134
|
+
local server; guardian-mcp connects to the remote server over **Streamable
|
|
135
|
+
HTTP** (the default) or **SSE** (`--transport sse`).
|
|
136
|
+
|
|
137
|
+
```json
|
|
138
|
+
{
|
|
139
|
+
"mcpServers": {
|
|
140
|
+
"composio-governed": {
|
|
141
|
+
"command": "guardian-mcp",
|
|
142
|
+
"args": [
|
|
143
|
+
"proxy",
|
|
144
|
+
"--url",
|
|
145
|
+
"<the URL Composio's generate step returned>",
|
|
146
|
+
"--header",
|
|
147
|
+
"x-api-key: ${COMPOSIO_API_KEY}"
|
|
148
|
+
],
|
|
149
|
+
"env": {
|
|
150
|
+
"GUARDIAN_BASE_URL": "https://guardian-api.odingard.com",
|
|
151
|
+
"GUARDIAN_AGENT_CREDENTIAL": "gac1.…",
|
|
152
|
+
"GUARDIAN_CONNECTION_ID": "pcn_…",
|
|
153
|
+
"COMPOSIO_API_KEY": "…"
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
A server that takes a Bearer key: `--url https://mcp.example.com/mcp --header
|
|
161
|
+
'Authorization: Bearer ${REMOTE_MCP_API_KEY}'`.
|
|
162
|
+
|
|
163
|
+
- Every `tools/call` is decided by Guardian exactly as for a stdio server:
|
|
164
|
+
ALLOW forwards it, ASK holds it for `guardian_claim`, DENY never sends it.
|
|
165
|
+
`tools/list` passes through, with `guardian_claim` and `guardian_receipt`
|
|
166
|
+
added.
|
|
167
|
+
- **Secrets stay in the proxy.** Write header values as `${ENV_VAR}`;
|
|
168
|
+
guardian-mcp expands them itself, so no shell is needed and the key never
|
|
169
|
+
appears in the agent's view. In a shell, use single quotes so the shell
|
|
170
|
+
does not expand it onto the command line. The URL and header values are
|
|
171
|
+
never printed; error messages show the server's origin only.
|
|
172
|
+
- `${…}` can only name a variable that is set, and never a `GUARDIAN_*`
|
|
173
|
+
one, so the agent credential cannot be sent to the remote server.
|
|
174
|
+
- The URL must be `https://`; plain `http://` is accepted only for
|
|
175
|
+
`localhost`. User names or passwords in the URL are refused; send
|
|
176
|
+
credentials as headers. Redirects to another origin are not followed.
|
|
177
|
+
- **Fails closed.** If the remote server cannot be reached or rejects the key
|
|
178
|
+
at startup, guardian-mcp exits with a one-line message and serves no tools.
|
|
179
|
+
If the remote fails after Guardian allowed a call, the result is an error
|
|
180
|
+
that says the call may or may not have run, and the capability is not
|
|
181
|
+
redeemed. It is never reported as a success.
|
|
182
|
+
- `guardian-mcp tools --url …` lists a remote server's tools with suggested
|
|
183
|
+
modes, the same as for a local one.
|
|
184
|
+
- OAuth sign-in flows are not supported; use an API key or bearer token
|
|
185
|
+
header.
|
|
186
|
+
- **The upstream cannot speak for Guardian.** A remote server's result is
|
|
187
|
+
passed to the agent as the server's own output, whatever fields it
|
|
188
|
+
carries. A result that contains a `guardian: {decision, requestId}` field
|
|
189
|
+
is never read as a Guardian decision, so it cannot hold, swap or hide a
|
|
190
|
+
call.
|
|
191
|
+
- If `tools/list` fails upstream, the agent gets a generic "tool list is
|
|
192
|
+
unavailable" error. The upstream's error text (which can quote its URL
|
|
193
|
+
path, response body or headers) is not shown to the agent or logged;
|
|
194
|
+
`guardian-mcp tools --url` prints it with the URL, its path identifiers,
|
|
195
|
+
query values and header values scrubbed. Each query value is scrubbed as
|
|
196
|
+
typed (a `+` stays a `+`), percent-decoded, and with `+` read as a space.
|
|
197
|
+
|
|
198
|
+
What is **not** governed: a remote server the agent connects to directly
|
|
199
|
+
(for example an MCP client's own `"url"` entry, or a model provider's hosted
|
|
200
|
+
MCP connector), and anything the agent does without MCP. Also not covered:
|
|
201
|
+
|
|
202
|
+
- **Lost results after ALLOW.** If the remote times out (the MCP SDK's
|
|
203
|
+
default request timeout is 60 seconds) or drops the session after
|
|
204
|
+
Guardian allowed a call, the call may have run, but the capability is not
|
|
205
|
+
redeemed and no receipt is written. Receipts can then understate what
|
|
206
|
+
happened; check the remote system itself.
|
|
207
|
+
- **Keys in the same process tree.** guardian-mcp keeps the upstream key out
|
|
208
|
+
of the agent's MCP view. It cannot stop other code that runs as the same
|
|
209
|
+
user, in the same container, from reading the key from the environment it
|
|
210
|
+
was set in and calling the remote server directly. Recipes:
|
|
211
|
+
[ZeroClaw](../../examples/zeroclaw/README.md) and
|
|
212
|
+
[your own container (CrewAI, LangGraph, AutoGen)](../../examples/byo/README.md).
|
|
213
|
+
Runnable example: `examples/remote-mcp.mjs` (SIMULATED by default, LIVE with
|
|
214
|
+
Guardian settings). `--url` is in the published `@odingard/guardian-mcp`
|
|
215
|
+
from 0.3.3.
|
|
216
|
+
|
|
90
217
|
## See what you are about to govern: `guardian-mcp tools`
|
|
91
218
|
|
|
92
219
|
Before putting a server behind the proxy, list its tools along with a suggested Guardian mode for each. This needs no Guardian credential:
|
|
@@ -105,11 +232,13 @@ createIssue Ask me first "create" may change something ⚠ name not accept
|
|
|
105
232
|
|
|
106
233
|
How the suggestions are made:
|
|
107
234
|
|
|
108
|
-
-
|
|
109
|
-
-
|
|
235
|
+
- A tool the server marks destructive (`destructiveHint`) suggests Ask.
|
|
236
|
+
- So does a tool whose name contains a verb that changes something or sends data out, anywhere in the name (`create`, `update`, `delete`, `replace`, `send`, `run`, `write` and similar), even when the server marks it read-only. `get_or_create_customer`, `find_and_replace` and `list_and_delete_old_files` suggest Ask.
|
|
237
|
+
- Otherwise a tool the server marks read-only (`readOnlyHint`) suggests Allow, and so does a name that starts with a read verb (`get`, `list`, `search`, `read`, `describe`, `show`, `view`, `find`, `lookup`, `count`, `inspect`).
|
|
238
|
+
- Everything else suggests Ask, including `fetch` (it can send data to any URL) and `status`.
|
|
110
239
|
|
|
111
240
|
They are suggestions only: you set the real modes in the console. Tool names that break Guardian's naming rule are flagged, because every call to them would be denied. Add `--json` for machine-readable output.
|
|
112
241
|
|
|
113
242
|
## License
|
|
114
243
|
|
|
115
|
-
Apache-2.0.
|
|
244
|
+
Apache-2.0.
|