Skip to content

Commit 4ad701e

Browse files
author
Su Fu
committed
docs(codex): default to daemon-backed enforcement
1 parent 175d798 commit 4ad701e

11 files changed

Lines changed: 329 additions & 126 deletions

File tree

README.md

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<a href="LICENSE"><img alt="License: Apache 2.0" src="https://img.shields.io/badge/License-Apache%202.0-blue.svg"></a>
88
<img alt="Rust 1.85+" src="https://img.shields.io/badge/rust-1.85%2B-orange">
99

10-
> Permit0 integrates with **Claude Code** and **OpenClaw** — more integrations to come. The pilot `email` taxonomy is ready, with Outlook and Gmail packs. [Open an issue to request the next taxonomy domain or tool pack →](https://github.com/permit0-ai/permit0/issues/new?template=new-pack.md) and ⭐ star the project.
10+
> Permit0 integrates with **Claude Code**, **Codex**, and **OpenClaw**. The pilot `email` taxonomy is ready, with Outlook and Gmail packs. [Open an issue to request the next taxonomy domain or tool pack →](https://github.com/permit0-ai/permit0/issues/new?template=new-pack.md) and ⭐ star the project.
1111
1212
---
1313

@@ -23,9 +23,9 @@ The vocabulary is published. The first pack is shipped. The rest is the work.
2323

2424
---
2525

26-
## Try it — Claude Code + Outlook/Gmail in 5 minutes
26+
## Try it — Claude Code or Codex + Outlook/Gmail in 5 minutes
2727

28-
> **On OpenClaw?** Skip to [`integrations/permit0-openclaw/`](integrations/permit0-openclaw/) — wrap a skill with `permit0Skill(...)` and gate every dispatch through the same daemon. The rest of this section is the Claude Code path.
28+
> **On OpenClaw?** Skip to [`integrations/permit0-openclaw/`](integrations/permit0-openclaw/) — wrap a skill with `permit0Skill(...)` and gate every dispatch through the same daemon.
2929
3030
```bash
3131
# 1. Build
@@ -74,6 +74,22 @@ Restart Claude Code. Ask it: *"list recent emails and archive any newsletters,"*
7474

7575
Shadow mode (`permit0 hook --shadow`) logs decisions without blocking, if you want to observe before enforcing.
7676

77+
For Codex, use the daemon-backed managed-preferences installer:
78+
79+
```bash
80+
PERMIT0_URL=http://127.0.0.1:9090 bash integrations/permit0-codex/examples/install-managed-prefs.sh
81+
```
82+
83+
Then add the MCP server to your Codex config:
84+
85+
```toml
86+
[mcp_servers.permit0-gmail]
87+
command = "/abs/path/to/permit0-gmail-mcp"
88+
```
89+
90+
See [`integrations/permit0-codex/`](integrations/permit0-codex/) for the
91+
full Codex setup, including unattended install and cleanup.
92+
7793
---
7894

7995
## What makes it different
@@ -99,7 +115,7 @@ The taxonomy is the canonical, append-only vocabulary for *what agents do* — `
99115

100116
22 domains, 159 verbs are defined today in [`crates/permit0-types/src/taxonomy.rs`](crates/permit0-types/src/taxonomy.rs) and documented at [`docs/taxonomy.md`](docs/taxonomy.md). The engine fails closed on any tool call that doesn't normalize to a covered action — unknown actions queue for human approval, they don't auto-run.
101117

102-
**Today (v0.1):** engine, signed audit, admin UI, CLI, one reference pack (`email`, 16 verbs for Gmail + Outlook), two integrations (Claude Code, OpenClaw).
118+
**Today (v0.1):** engine, signed audit, admin UI, CLI, one reference pack (`email`, 16 verbs for Gmail + Outlook), three integrations (Claude Code, Codex, OpenClaw).
103119

104120
**Roadmap:** packs for Slack, Notion, Linear, Stripe, Postgres, Bash, GitHub; framework adapters for LangChain, CrewAI, AutoGen, OpenAI Agents; pre-built CLI binaries.
105121

@@ -158,7 +174,7 @@ This preserves the determinism guarantee. The LLM is allowed to be fallible in o
158174

159175
**5. Can I run it fully offline / air-gapped?** Yes. Default install uses SQLite and in-memory storage, no external dependencies. The LLM reviewer is optional — disable it in regulated environments where every medium-risk decision must go to a human.
160176

161-
**6. Why is only the email pack shipped?** Phase 1 focus: prove the engine end-to-end on one domain that everyone has (their inbox), with two channels (Outlook + Gmail), through a real agent host (Claude Code). The taxonomy is the moat; the packs are linear work the community can parallelize. Yours next.
177+
**6. Why is only the email pack shipped?** Phase 1 focus: prove the engine end-to-end on one domain that everyone has (their inbox), with two channels (Outlook + Gmail), through real agent hosts (Claude Code, Codex, and OpenClaw). The taxonomy is the moat; the packs are linear work the community can parallelize. Yours next.
162178

163179
---
164180

clients/gmail-mcp/README.md

Lines changed: 83 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,28 @@
11
# permit0-gmail-mcp
22

3-
MCP server that exposes Gmail tools (Gmail API v1) to Claude Code, **each
4-
call gated by permit0**. Mirrors `clients/outlook-mcp` — same 13 tools, same
5-
norm actions, same risk rules.
3+
MCP server that exposes Gmail tools (Gmail API v1) to agent hosts. With
4+
Codex and Claude Code, permit0 enforcement happens at the host
5+
`PreToolUse` hook layer: the host proposes
6+
`mcp__permit0-gmail__gmail_send`, the permit0 hook sends it to the
7+
daemon, and the Gmail API call only runs when the hook allows it.
8+
Mirrors `clients/outlook-mcp` — same 13 tools, same norm actions, same
9+
risk rules.
610

711
## Architecture
812

13+
```text
14+
Agent host ──PreToolUse hook──▶ permit0 hook --client <host> --remote :9090
15+
│ │
16+
│ └─▶ permit0 daemon/dashboard
17+
│ (allow/deny/human)
18+
19+
└─MCP/stdio, only if allowed──────▶ permit0-gmail-mcp
20+
└─▶ Gmail API
921
```
10-
Claude Code ──MCP/stdio──▶ permit0-gmail-mcp
11-
12-
├─▶ @permit0.guard("email.X")
13-
│ └─ POST /api/v1/check_action ──▶ permit0 daemon
14-
│ (allow/deny/human)
15-
16-
└─▶ Gmail API (gmail.googleapis.com/gmail/v1)
17-
(only on allow)
18-
```
22+
23+
For hosts without a hook layer, add an equivalent pre-tool gate before
24+
starting this MCP server. The server itself is plain MCP and does not
25+
call permit0 internally.
1926

2027
## Tools exposed
2128

@@ -72,28 +79,76 @@ and silent-refreshes after that.
7279
python -c "from permit0_gmail_mcp.auth import get_token; print(bool(get_token()))"
7380
```
7481

75-
### 4. Wire into Claude Code
82+
### 4. Wire into Codex or Claude Code
83+
84+
Both Codex and Claude Code enforce Gmail tools through a host-level
85+
`PreToolUse` hook. The MCP server config only exposes the tools; the hook
86+
is what calls permit0 before Gmail runs.
87+
88+
#### Codex
89+
90+
Start the permit0 daemon and install the Codex hook in remote mode:
91+
92+
```bash
93+
cd /path/to/permit0
94+
cargo run -p permit0-cli -- serve --ui --port 9090
95+
PERMIT0_URL=http://127.0.0.1:9090 bash integrations/permit0-codex/examples/install-managed-prefs.sh
96+
```
97+
98+
Add the Gmail MCP server to your Codex config:
99+
100+
```toml
101+
[mcp_servers.permit0-gmail]
102+
command = "/absolute/path/to/permit0-gmail-mcp"
103+
```
104+
105+
Restart Codex. The 13 `gmail_*` tools appear, and every Gmail MCP tool
106+
call is enforced by the daemon-backed Codex hook before the MCP server
107+
is called.
108+
109+
#### Claude Code
110+
111+
Start the same daemon:
112+
113+
```bash
114+
cd /path/to/permit0
115+
cargo run -p permit0-cli -- serve --ui --port 9090
116+
```
117+
118+
Add the permit0 hook to Claude Code, for example in
119+
`~/.claude/settings.json`:
120+
121+
```json
122+
{
123+
"hooks": {
124+
"PreToolUse": [{ "hooks": [{
125+
"type": "command",
126+
"command": "/absolute/path/to/permit0/target/release/permit0 hook --remote http://127.0.0.1:9090 --unknown defer"
127+
}]}]
128+
}
129+
}
130+
```
76131

77-
Add to `~/.claude.json` under top-level `mcpServers`:
132+
If you are using Claude Code, wire the server under top-level
133+
`mcpServers`:
78134

79135
```json
80136
{
81137
"mcpServers": {
82138
"permit0-gmail": {
83-
"command": "permit0-gmail-mcp",
84-
"env": {
85-
"PERMIT0_URL": "http://localhost:9090"
86-
}
139+
"command": "permit0-gmail-mcp"
87140
}
88141
}
89142
}
90143
```
91144

92-
Restart Claude Code. The 13 `gmail_*` tools appear.
145+
Restart Claude Code. The same `gmail_*` tools appear, and the hook gates
146+
each call before it reaches Gmail.
93147

94148
You can have **both** `permit0-outlook` and `permit0-gmail` configured at
95-
the same time — both gate through the same permit0 daemon, both lower to
96-
the same `email.*` norm actions, same risk rules.
149+
the same time. With the host's permit0 hook configured, both lower to the
150+
same `email.*` norm actions and are evaluated by the same daemon, using
151+
the same risk rules.
97152

98153
## Try it
99154

@@ -102,7 +157,8 @@ In Claude Code:
102157
> Search my Gmail for "newsletter" emails from the past 7 days and archive all results.
103158
104159
Claude Code will call `gmail_search(query="newsletter newer_than:7d")`
105-
permit0 evaluates each result's `gmail_archive(message_id=...)`.
160+
the host hook evaluates each `gmail_archive(message_id=...)` before it
161+
reaches the MCP server.
106162

107163
> Read that entire thread.
108164
@@ -123,12 +179,12 @@ conversationId stitching needed).
123179

124180
| Variable | Default | Purpose |
125181
|----------|---------|---------|
126-
| `PERMIT0_URL` | `http://localhost:9090` | permit0 daemon URL |
127182
| `GMAIL_CREDENTIALS` | `~/.permit0/gmail_credentials.json` | OAuth app credentials |
128183

129184
## Cross-backend invariant
130185

131-
If you have both Gmail and Outlook MCP servers configured, **both** lower
132-
to the same `email.*` norm actions. permit0 sees one unified IR. The
133-
dashboard's audit log shows `channel=gmail` vs `channel=outlook`, but the
134-
risk rule and the human reviewer's decision applies regardless.
186+
If you have both Gmail and Outlook MCP servers configured behind a
187+
permit0 hook, **both** lower to the same `email.*` norm actions. permit0
188+
sees one unified IR. The dashboard's audit log shows `channel=gmail` vs
189+
`channel=outlook`, but the risk rule and the human reviewer's decision
190+
applies regardless.

clients/outlook-mcp/README.md

Lines changed: 71 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,26 @@
11
# permit0-outlook-mcp
22

3-
MCP server that exposes Outlook tools (Microsoft Graph) to Claude Code, **each
4-
call gated by permit0** so policy is enforced before any email is sent, moved,
5-
or deleted.
3+
MCP server that exposes Outlook tools (Microsoft Graph) to agent hosts.
4+
With Codex and Claude Code, permit0 enforcement happens at the host
5+
`PreToolUse` hook layer: the host proposes
6+
`mcp__permit0-outlook__outlook_send`, the permit0 hook sends it to the
7+
daemon, and the Graph API call only runs when the hook allows it.
68

79
## Architecture
810

11+
```text
12+
Agent host ──PreToolUse hook──▶ permit0 hook --client <host> --remote :9090
13+
│ │
14+
│ └─▶ permit0 daemon/dashboard
15+
│ (allow/deny/human)
16+
17+
└─MCP/stdio, only if allowed──────▶ permit0-outlook-mcp
18+
└─▶ Microsoft Graph API
919
```
10-
Claude Code ──MCP/stdio──▶ permit0-outlook-mcp
11-
12-
├─▶ @permit0.guard("email.X")
13-
│ └─ POST /api/v1/check_action ──▶ permit0 daemon
14-
│ (allow/deny/human)
15-
16-
└─▶ Microsoft Graph API
17-
(only on allow)
18-
```
20+
21+
For hosts without a hook layer, add an equivalent pre-tool gate before
22+
starting this MCP server. The server itself is plain MCP and does not
23+
call permit0 internally.
1924

2025
## Tools exposed
2126

@@ -61,43 +66,77 @@ Visit the Microsoft device-login URL it prints, sign in with your personal
6166
Outlook account, approve `Mail.ReadWrite` + `Mail.Send`. Token caches to
6267
`~/.permit0/outlook_token.json` and is reused by this MCP server.
6368

64-
### 4. Wire into Claude Code
69+
### 4. Wire into Codex or Claude Code
70+
71+
Both Codex and Claude Code enforce Outlook tools through a host-level
72+
`PreToolUse` hook. The MCP server config only exposes the tools; the hook
73+
is what calls permit0 before Microsoft Graph runs.
74+
75+
#### Codex
76+
77+
Install the daemon-backed Codex hook:
78+
79+
```bash
80+
PERMIT0_URL=http://127.0.0.1:9090 bash integrations/permit0-codex/examples/install-managed-prefs.sh
81+
```
82+
83+
For Codex, add the Outlook MCP server to your Codex config:
84+
85+
```toml
86+
[mcp_servers.permit0-outlook]
87+
command = "/absolute/path/to/permit0-outlook-mcp"
88+
```
89+
90+
#### Claude Code
91+
92+
Add the permit0 hook to Claude Code, for example in
93+
`~/.claude/settings.json`:
94+
95+
```json
96+
{
97+
"hooks": {
98+
"PreToolUse": [{ "hooks": [{
99+
"type": "command",
100+
"command": "/absolute/path/to/permit0/target/release/permit0 hook --remote http://127.0.0.1:9090 --unknown defer"
101+
}]}]
102+
}
103+
}
104+
```
65105

66-
Add to your Claude Code config (typically `~/.claude.json` or a project-local
67-
`.mcp.json`):
106+
For Claude Code, add it to `~/.claude.json` or project-local `.mcp.json`:
68107

69108
```json
70109
{
71110
"mcpServers": {
72111
"permit0-outlook": {
73-
"command": "permit0-outlook-mcp",
74-
"env": {
75-
"PERMIT0_URL": "http://localhost:9090"
76-
}
112+
"command": "permit0-outlook-mcp"
77113
}
78114
}
79115
}
80116
```
81117

82-
Restart Claude Code. The 9 tools above appear in its tool list.
118+
Restart the host. The 9 tools above appear in its tool list, and each
119+
mutating Outlook MCP call is enforced by the daemon-backed host hook
120+
before the MCP server is called.
83121

84122
## Try it
85123

86-
In Claude Code:
124+
In your agent host:
87125

88126
> List the 5 most recent emails in my inbox and archive all promotional emails from last week.
89127
90-
Claude Code will call `outlook_list` (no permit0 check), then for each
91-
candidate message, call `outlook_archive(message_id=...)`. Each archive call
92-
hits permit0 — you'll see the decisions live in the dashboard at
93-
http://localhost:9090/ui/ under the **Audit** tab.
128+
The host will call `outlook_search` or `outlook_list`, then for each
129+
candidate message, call `outlook_archive(message_id=...)`. Each archive
130+
call is evaluated by the hook before it reaches the MCP server; you'll
131+
see the decisions live in the dashboard at <http://localhost:9090/ui/>.
94132

95133
> Send an email to bob@example.com with subject "test" and body "hi".
96134
97-
Claude Code calls `outlook_send(to=..., subject=..., body=...)`. permit0
98-
evaluates `email.send` against its risk rule. A clean send → ALLOW. Embedding
99-
something like `password is hunter2` in the body → DENY (the SDK raises
100-
`permit0.Denied`, the MCP layer reports the block to Claude Code).
135+
The host calls `outlook_send(to=..., subject=..., body=...)`. permit0
136+
evaluates `email.send` against its risk rule. A clean send can run.
137+
Sensitive content or risky recipients return a deny envelope from the
138+
hook, so the host blocks the tool call before the MCP layer reaches
139+
Microsoft Graph.
101140

102141
## Combining with shadow mode
103142

@@ -107,16 +146,14 @@ If you want to **observe** without enforcement while you tune the risk rules:
107146
export PERMIT0_SHADOW=1 # for `permit0 hook` use
108147
```
109148

110-
Note: this env var is read by the **`permit0 hook`** subcommand
111-
(Claude Code PreToolUse). The MCP server itself enforces directly via
112-
`@permit0.guard` — there's no built-in shadow flag yet. If you want shadow
113-
mode for MCP tools too, ask and we'll add it.
149+
Note: this env var is read by the **`permit0 hook`** subcommand. The MCP
150+
server itself is plain MCP, so shadow/enforcement behavior is controlled
151+
by the host hook command.
114152

115153
## Configuration
116154

117155
| Variable | Default | Purpose |
118156
|----------|---------|---------|
119-
| `PERMIT0_URL` | `http://localhost:9090` | permit0 daemon URL |
120157
| `MSGRAPH_CLIENT_ID` | Microsoft Graph PowerShell public client | Override with your own Azure App reg |
121158

122159
## What this is NOT
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
"""permit0-outlook-mcp: MCP server exposing Outlook tools, each gated by permit0."""
1+
"""permit0-outlook-mcp: plain MCP server exposing Outlook tools."""
22

33
__version__ = "0.1.0"

clients/outlook-mcp/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
55
[project]
66
name = "permit0-outlook-mcp"
77
version = "0.1.0"
8-
description = "MCP server exposing Outlook (Microsoft Graph) tools, each gated by permit0"
8+
description = "Plain MCP server exposing Outlook (Microsoft Graph) tools for host-level permit0 hooks"
99
readme = "README.md"
1010
requires-python = ">=3.10"
1111
license = {text = "MIT"}

0 commit comments

Comments
 (0)