<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:fh="http://purl.org/syndication/history/1.0"><channel><title>Tuff | Blog</title><description>Manage and govern agent capabilities.</description><link>https://tuffcli.dev/</link><language>en</language><fh:complete/><atom:link rel="self" href="https://tuffcli.dev/blog/rss.xml"/><item><title>Managing centralised guardrail policies for coding agents</title><link>https://tuffcli.dev/blog/agent-policies-across-harnesses/</link><guid isPermaLink="true">https://tuffcli.dev/blog/agent-policies-across-harnesses/</guid><description>Each coding harness has its own permission system. A Tuff policy declares deny and ask rules once, compiles them into the native rules of Claude Code, Codex, and OpenCode, records the rules an agent cannot enforce, and fails tuff check when a compiled rule is removed.</description><pubDate>Mon, 14 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A coding agent in a repository can run shell commands, read files, and call the tools its MCP servers expose. Teams usually have a short list of actions an agent must not take on its own, such as force-pushing to a shared branch, reading &lt;code dir=&quot;auto&quot;&gt;.env&lt;/code&gt;, running &lt;code dir=&quot;auto&quot;&gt;terraform apply&lt;/code&gt; without approval, or calling an MCP tool that deletes data.&lt;/p&gt;
&lt;p&gt;Every command and file below is from tuffcli 0.12.0, which enforces policies in Claude Code, Codex, and OpenCode.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;where-teams-put-guardrails-today&quot;&gt;Where teams put guardrails today&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;instruction-files&quot;&gt;Instruction files&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Teams often write these rules in &lt;code dir=&quot;auto&quot;&gt;CLAUDE.md&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;AGENTS.md&lt;/code&gt;, for example “never force push”. An instruction file is part of the prompt. The model reads the rule and decides whether to follow it, and nothing outside the model checks the tool call.&lt;/p&gt;
&lt;p&gt;A harness permission rule is checked by the harness: each tool call is matched against the rule before it runs.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;harness-permission-settings&quot;&gt;Harness permission settings&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Each harness has its own permission settings:&lt;/p&gt;
&lt;div role=&quot;figure&quot; aria-label=&quot;The same guardrail written four ways, one per harness&quot;&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;team intent&lt;/div&gt;&lt;div&gt;&lt;span&gt;never&lt;/span&gt; git push &lt;em&gt;--force&lt;/em&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;never read&lt;/span&gt; .env&lt;/div&gt;&lt;div&gt;&lt;span&gt;ask before&lt;/span&gt; terraform apply&lt;/div&gt;&lt;div&gt;&lt;span&gt;never call&lt;/span&gt; github:delete_*&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;i&gt;&lt;/i&gt;&lt;span&gt;by hand&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;strong&gt;claude code&lt;/strong&gt;&lt;small&gt;.claude/settings.json&lt;/small&gt;&lt;code&gt;permissions: allow / ask / deny rules&lt;/code&gt;&lt;/div&gt;&lt;div&gt;&lt;strong&gt;codex&lt;/strong&gt;&lt;small&gt;config.toml&lt;/small&gt;&lt;code&gt;approval policy + sandbox mode&lt;/code&gt;&lt;/div&gt;&lt;div&gt;&lt;strong&gt;cursor&lt;/strong&gt;&lt;small&gt;.cursor/cli.json&lt;/small&gt;&lt;code&gt;permissions: allow / deny lists&lt;/code&gt;&lt;/div&gt;&lt;div&gt;&lt;strong&gt;opencode&lt;/strong&gt;&lt;small&gt;opencode.json&lt;/small&gt;&lt;code&gt;permission: allow / ask / deny&lt;/code&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;The settings differ in four ways:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Vocabulary.&lt;/strong&gt; Some harnesses match rules per tool and per command pattern. Others set an approval mode and a sandbox level instead of listing commands.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Effects.&lt;/strong&gt; Some harnesses support deny, ask, and allow. Others support only allow and deny, so a rule that asks a person first has no equivalent.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Matching.&lt;/strong&gt; A command rule matches a prefix of the words in one harness and a glob in another. Paths are relative in some settings files and absolute in others.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Location.&lt;/strong&gt; Some settings files live in the repository and are reviewed with the code. Others live in the user’s home directory.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A team that uses several agents writes the same rule once per harness. The copies can diverge, and an edit to one copy made while debugging is not reported anywhere.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;rules-that-are-not-enforced&quot;&gt;Rules that are not enforced&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;If a rule is written for a harness that cannot enforce it, or the harness matches it differently than the author expected, the agent performs the action and no error is shown.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;a-central-policy&quot;&gt;A central policy&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;A central policy is declared once in the repository, and the harness settings are generated from it:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Single source.&lt;/strong&gt; The rules are reviewed in a pull request and versioned with the code.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Shared.&lt;/strong&gt; A platform or security team publishes a policy, and each project installs the same version.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Coverage per harness.&lt;/strong&gt; Each harness declares which rules it can enforce, and installation fails for a rule it cannot.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Drift detection.&lt;/strong&gt; The generated rules are tracked, and a removed rule fails CI.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;how-tuff-manages-a-policy&quot;&gt;How Tuff manages a policy&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;the-policy-file&quot;&gt;The policy file&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;A policy is a capability, like a skill or a hook, with its own &lt;code dir=&quot;auto&quot;&gt;tuff.toml&lt;/code&gt;:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;policies/infra-guardrails/tuff.toml&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;id = &lt;/span&gt;&lt;span&gt;&quot;infra-guardrails&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;type = &lt;/span&gt;&lt;span&gt;&quot;policy&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;version = &lt;/span&gt;&lt;span&gt;&quot;1.0.0&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;description = &lt;/span&gt;&lt;span&gt;&quot;No force pushes, no secrets, and a human approves terraform apply.&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[[&lt;/span&gt;&lt;span&gt;policy&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;rules&lt;/span&gt;&lt;span&gt;]]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;effect = &lt;/span&gt;&lt;span&gt;&quot;deny&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;command = [&lt;/span&gt;&lt;span&gt;&quot;git&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;push&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;--force&quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;reason = &lt;/span&gt;&lt;span&gt;&quot;Force pushes rewrite shared history.&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[[&lt;/span&gt;&lt;span&gt;policy&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;rules&lt;/span&gt;&lt;span&gt;]]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;effect = &lt;/span&gt;&lt;span&gt;&quot;deny&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;read = [&lt;/span&gt;&lt;span&gt;&quot;.env&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;secrets/**&quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[[&lt;/span&gt;&lt;span&gt;policy&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;rules&lt;/span&gt;&lt;span&gt;]]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;effect = &lt;/span&gt;&lt;span&gt;&quot;ask&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;command = [&lt;/span&gt;&lt;span&gt;&quot;terraform&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;apply&quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[[&lt;/span&gt;&lt;span&gt;policy&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;rules&lt;/span&gt;&lt;span&gt;]]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;effect = &lt;/span&gt;&lt;span&gt;&quot;deny&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;mcp = &lt;/span&gt;&lt;span&gt;&quot;github:delete_*&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Each rule has an effect, &lt;code dir=&quot;auto&quot;&gt;deny&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;ask&lt;/code&gt;, and exactly one subject: a command, file paths the agent must not read, file paths it must not edit, or an MCP tool. Commands match by prefix and paths by pattern, which is what harness permission rules can match. Tuff refuses a policy when it loads if a rule has two subjects, a &lt;code dir=&quot;auto&quot;&gt;*&lt;/code&gt; inside a command, a path that climbs out with &lt;code dir=&quot;auto&quot;&gt;..&lt;/code&gt;, or a misspelt field.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;compiling-to-claude-code-permission-rules&quot;&gt;Compiling to Claude Code permission rules&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;tuff add&lt;/code&gt; writes the policy into the harness’s own permission rules, and the harness enforces them in the same way as rules written by hand:&lt;/p&gt;
&lt;div role=&quot;figure&quot; aria-label=&quot;tuff add compiles one policy into Claude Code permission rules and refuses harnesses that cannot enforce it&quot;&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;tuff.toml&lt;/div&gt;&lt;div&gt;&lt;span&gt;id =&lt;/span&gt; &quot;infra-guardrails&quot;&lt;/div&gt;&lt;div&gt;&lt;span&gt;type =&lt;/span&gt; &quot;policy&quot;&lt;/div&gt;&lt;div&gt;&lt;span&gt;deny&lt;/span&gt; git push &lt;em&gt;--force&lt;/em&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;deny read&lt;/span&gt; .env, secrets/**&lt;/div&gt;&lt;div&gt;&lt;span&gt;ask&lt;/span&gt; terraform apply&lt;/div&gt;&lt;div&gt;&lt;span&gt;deny mcp&lt;/span&gt; github:delete_*&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;i&gt;&lt;/i&gt;&lt;span&gt;tuff add&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;strong&gt;claude&lt;/strong&gt;&lt;small&gt;.claude/settings.json&lt;/small&gt;&lt;code&gt;deny → Bash(git push &lt;em&gt;--force *)&lt;/em&gt;&lt;/code&gt;&lt;code&gt;deny → Read(/**/.env)&lt;/code&gt;&lt;code&gt;ask  → Bash(terraform apply *)&lt;/code&gt;&lt;code&gt;deny → mcp__github__delete_*&lt;/code&gt;&lt;/div&gt;&lt;div&gt;&lt;strong&gt;opencode&lt;/strong&gt;&lt;small&gt;.opencode/opencode.json&lt;/small&gt;&lt;code&gt;permission.bash → &quot;git push --force *&quot;: &quot;deny&quot;&lt;/code&gt;&lt;code&gt;permission.read → &quot;.env&quot;: &quot;deny&quot;&lt;/code&gt;&lt;code&gt;permission → &quot;github_delete_*&quot;: &quot;deny&quot;&lt;/code&gt;&lt;/div&gt;&lt;div&gt;&lt;strong&gt;codex&lt;/strong&gt;&lt;small&gt;.codex/rules/tuff.rules&lt;/small&gt;&lt;code&gt;prefix_rule([&quot;git&quot;, &quot;push&quot;, &quot;--force&quot;], &quot;forbidden&quot;)&lt;/code&gt;&lt;code&gt;read rule and mcp pattern: recorded, not enforced&lt;/code&gt;&lt;/div&gt;&lt;div&gt;&lt;strong&gt;cursor&lt;/strong&gt;&lt;small&gt;not enforced yet&lt;/small&gt;&lt;code&gt;install refused, so no rule is assumed&lt;/code&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Installing the policy for Claude Code in a new project:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;tuff&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;./policies/infra-guardrails&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-a&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;claude&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Claude: rule 1 (deny command &quot;git push --force&quot;) is enforced partially: matches the command as Claude writes it, including inside compound commands; the same program run another way, such as by absolute path, through sh -c, or as git -C . push, is not matched&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Claude: rule 2 (deny read &quot;.env&quot;, &quot;secrets/**&quot;) is enforced partially: covers Claude&apos;s file tools and the shell commands Claude Code recognises, such as cat and sed, not a script or program that opens the file itself&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Claude: rule 3 (ask command &quot;terraform apply&quot;) is enforced partially: matches the command as Claude writes it, including inside compound commands; the same program run another way, such as by absolute path, through sh -c, or as git -C . push, is not matched&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;installed infra-guardrails (claude) -&gt; .claude/policies/infra-guardrails/policy.toml&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;compiled 5 permission rule(s) for infra-guardrails (claude) -&gt; .claude/settings.json&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code dir=&quot;auto&quot;&gt;read&lt;/code&gt; rule lists two paths, so the four policy rules compile to five Claude Code rules:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;.claude/settings.json&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;permissions&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;ask&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&quot;Bash(terraform apply *)&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;deny&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&quot;Bash(git push --force *)&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&quot;Read(/**/.env)&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&quot;Read(/secrets/**)&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&quot;mcp__github__delete_*&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;tuff add&lt;/code&gt; prints a caveat for each rule that is only partly enforced. The command rule, for example, does not match &lt;code dir=&quot;auto&quot;&gt;sh -c &quot;git push --force&quot;&lt;/code&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;compiling-to-codex&quot;&gt;Compiling to Codex&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Codex reads command rules from &lt;code dir=&quot;auto&quot;&gt;.codex/rules/&lt;/code&gt;. Tuff owns one file there and compiles the policy’s &lt;code dir=&quot;auto&quot;&gt;command&lt;/code&gt; rules into it. Codex has no project rule for file paths. It can disable an MCP tool or require approval for it in &lt;code dir=&quot;auto&quot;&gt;.codex/config.toml&lt;/code&gt;, but only by exact server and tool name, and this policy’s &lt;code dir=&quot;auto&quot;&gt;github:delete_*&lt;/code&gt; is a pattern. Two of the four rules have no Codex form, so &lt;code dir=&quot;auto&quot;&gt;tuff add&lt;/code&gt; refuses the policy rather than installing half of it:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;tuff&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;./policies/infra-guardrails&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-a&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;codex&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;error: policy &apos;infra-guardrails&apos; would not be enforced as written, so it was not installed:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Codex: rule 2 (deny read &quot;.env&quot;, &quot;secrets/**&quot;) is not enforced: Codex rules match commands, not file paths, and Tuff does not compile Codex&apos;s sandbox permission profiles&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Codex: rule 4 (deny mcp &quot;github:delete_*&quot;) is not enforced: Codex names MCP servers and tools exactly in disabled_tools and approval_mode, so a pattern with &apos;*&apos; has no Codex form&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;hint: run &apos;tuff policy matrix&apos; to see what each agent can enforce, or pass --accept-unenforced to install the rules each agent enforces and record the rest in tuff.lock&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;--accept-unenforced&lt;/code&gt; installs the rules Codex does enforce and records the rest:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;tuff&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;./policies/infra-guardrails&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-a&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;codex&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--accept-unenforced&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Codex: rule 1 (deny command &quot;git push --force&quot;) is enforced partially: matches the command&apos;s leading words, and each command of a simple chain joined by &amp;#x26;&amp;#x26;, ||, ; or |; a script with redirection, $(...), a variable assignment, a wildcard, or control flow is matched as one command and not caught, and a program run by absolute path such as /usr/bin/git may not be matched; Codex loads project rules only in a trusted project and labels rules experimental&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Codex: rule 3 (ask command &quot;terraform apply&quot;) is enforced partially: matches the command&apos;s leading words, and each command of a simple chain joined by &amp;#x26;&amp;#x26;, ||, ; or |; a script with redirection, $(...), a variable assignment, a wildcard, or control flow is matched as one command and not caught, and a program run by absolute path such as /usr/bin/git may not be matched; Codex loads project rules only in a trusted project and labels rules experimental; where Codex never asks for approval, as in codex exec by default, the command is refused&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Codex: rule 2 (deny read &quot;.env&quot;, &quot;secrets/**&quot;) is not enforced, so it was not installed: Codex rules match commands, not file paths, and Tuff does not compile Codex&apos;s sandbox permission profiles; recorded in tuff.lock&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Codex: rule 4 (deny mcp &quot;github:delete_*&quot;) is not enforced, so it was not installed: Codex names MCP servers and tools exactly in disabled_tools and approval_mode, so a pattern with &apos;*&apos; has no Codex form; recorded in tuff.lock&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;installed infra-guardrails (codex) -&gt; .agents/policies/infra-guardrails/policy.toml&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;compiled 2 permission rule(s) for infra-guardrails (codex) -&gt; .codex/rules/tuff.rules&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;.codex/rules/tuff.rules&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Managed by Tuff: rules compiled from policy capabilities. Change the policy and run tuff update rather than editing this file.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;prefix_rule(pattern = [&quot;git&quot;, &quot;push&quot;, &quot;--force&quot;], decision = &quot;forbidden&quot;, justification = &quot;Force pushes rewrite shared history.&quot;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;prefix_rule(pattern = [&quot;terraform&quot;, &quot;apply&quot;], decision = &quot;prompt&quot;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;A rule’s &lt;code dir=&quot;auto&quot;&gt;reason&lt;/code&gt; becomes the &lt;code dir=&quot;auto&quot;&gt;justification&lt;/code&gt; Codex shows when it refuses. In Codex CLI 0.154.0, in a trusted project, asking the agent to run the command ends with &lt;code dir=&quot;auto&quot;&gt;rejected: Force pushes rewrite shared history.&lt;/code&gt; Codex loads project rules only when the project is trusted, and its own documentation labels rules experimental.&lt;/p&gt;
&lt;p&gt;The two rules Codex cannot enforce are now recorded, not forgotten. &lt;code dir=&quot;auto&quot;&gt;tuff check&lt;/code&gt; prints them on every run and still passes; &lt;code dir=&quot;auto&quot;&gt;tuff check --strict&lt;/code&gt; exits non-zero while any remain, which is what a CI job uses:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;tuff&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;check&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;✓ infra-guardrails         policy codex        ok&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;! infra-guardrails         policy codex        rule 2 (deny read &quot;.env&quot;, &quot;secrets/**&quot;) is not enforced: Codex rules match commands, not file paths, and Tuff does not compile Codex&apos;s sandbox permission profiles&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;! infra-guardrails         policy codex        rule 4 (deny mcp &quot;github:delete_*&quot;) is not enforced: Codex names MCP servers and tools exactly in disabled_tools and approval_mode, so a pattern with &apos;*&apos; has no Codex form&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;compiling-to-opencode&quot;&gt;Compiling to OpenCode&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;OpenCode applies the last permission rule that matches, across every config file it loads, and it loads &lt;code dir=&quot;auto&quot;&gt;.opencode/opencode.json&lt;/code&gt; after the project’s own &lt;code dir=&quot;auto&quot;&gt;opencode.json&lt;/code&gt;. Tuff writes the policy’s rules into that later file, &lt;code dir=&quot;auto&quot;&gt;ask&lt;/code&gt; before &lt;code dir=&quot;auto&quot;&gt;deny&lt;/code&gt;, so a policy’s deny wins over a project’s allow without Tuff editing &lt;code dir=&quot;auto&quot;&gt;opencode.json&lt;/code&gt; at all. All four rules compile:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;tuff&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;./policies/infra-guardrails&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-a&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;opencode&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;OpenCode: rule 1 (deny command &quot;git push --force&quot;) is enforced partially: matches each command OpenCode parses from the shell input, including one with a redirection; the same program run through sh -c, by absolute path, or with options before the subcommand is not matched; OpenCode loads .opencode/opencode.json after the project&apos;s opencode.json, but inline OPENCODE_CONFIG_CONTENT, managed config, and an agent&apos;s own permission settings are applied after it&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;OpenCode: rule 2 (deny read &quot;.env&quot;, &quot;secrets/**&quot;) is enforced partially: covers OpenCode&apos;s read tool; grep, glob, list, and shell commands are separate permissions and are not covered; OpenCode loads .opencode/opencode.json after the project&apos;s opencode.json, but inline OPENCODE_CONFIG_CONTENT, managed config, and an agent&apos;s own permission settings are applied after it&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;OpenCode: rule 3 (ask command &quot;terraform apply&quot;) is enforced partially: matches each command OpenCode parses from the shell input, including one with a redirection; the same program run through sh -c, by absolute path, or with options before the subcommand is not matched; opencode run rejects the request, and opencode --auto approves it; OpenCode loads .opencode/opencode.json after the project&apos;s opencode.json, but inline OPENCODE_CONFIG_CONTENT, managed config, and an agent&apos;s own permission settings are applied after it&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;installed infra-guardrails (opencode) -&gt; .opencode/policies/infra-guardrails/policy.toml&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;compiled 6 permission rule(s) for infra-guardrails (opencode) -&gt; .opencode/opencode.json&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;.opencode/opencode.json&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;$schema&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;https://opencode.ai/config.json&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;permission&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;bash&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&quot;terraform apply *&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;ask&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&quot;git push --force *&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;deny&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;read&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&quot;.env&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;deny&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&quot;*/.env&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;deny&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&quot;secrets/**&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;deny&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;github_delete_*&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;deny&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code dir=&quot;auto&quot;&gt;read&lt;/code&gt; rule becomes three patterns, because OpenCode matches a path relative to the project: &lt;code dir=&quot;auto&quot;&gt;.env&lt;/code&gt; catches the file at the root, &lt;code dir=&quot;auto&quot;&gt;*/.env&lt;/code&gt; catches it at any depth, and &lt;code dir=&quot;auto&quot;&gt;secrets/**&lt;/code&gt; is kept as written. An MCP rule becomes a tool name, since OpenCode names a tool &lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;server&gt;_&amp;#x3C;tool&gt;&lt;/code&gt; and hides a denied one from the agent.&lt;/p&gt;
&lt;p&gt;In OpenCode 1.18.15, in a project whose own &lt;code dir=&quot;auto&quot;&gt;opencode.json&lt;/code&gt; allowed everything, a live session refused &lt;code dir=&quot;auto&quot;&gt;git push --force&lt;/code&gt; and refused reading &lt;code dir=&quot;auto&quot;&gt;.env&lt;/code&gt;, and OpenCode’s rule list showed Tuff’s rules after the project’s own &lt;code dir=&quot;auto&quot;&gt;&quot;*&quot;: &quot;allow&quot;&lt;/code&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;what-each-harness-enforces&quot;&gt;What each harness enforces&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;tuff policy matrix&lt;/code&gt; lists each harness, effect, and subject with a coverage of &lt;code dir=&quot;auto&quot;&gt;full&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;partial&lt;/code&gt;, or &lt;code dir=&quot;auto&quot;&gt;unsupported&lt;/code&gt;, the terms the &lt;a href=&quot;https://tuffcli.dev/spec/hooks/&quot;&gt;Hooks Specification&lt;/a&gt; uses, and the native rule each one compiles to:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;tuff policy matrix&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;$ tuff policy matrix&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;┌─────────────┬────────┬─────────┬─────────────┬─────────────────────────────────────────────────────────────────────────────────┐&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│ ADAPTER     │ EFFECT │ SUBJECT │ COVERAGE    │ MECHANISM                                                                       │&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;├─────────────┼────────┼─────────┼─────────────┼─────────────────────────────────────────────────────────────────────────────────┤&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│ open-agents │ deny   │ command │ unsupported │                                                                                 │&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│ open-agents │ deny   │ read    │ unsupported │                                                                                 │&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│ open-agents │ deny   │ edit    │ unsupported │                                                                                 │&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│ open-agents │ deny   │ mcp     │ unsupported │                                                                                 │&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│ open-agents │ ask    │ command │ unsupported │                                                                                 │&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│ open-agents │ ask    │ read    │ unsupported │                                                                                 │&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│ open-agents │ ask    │ edit    │ unsupported │                                                                                 │&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│ open-agents │ ask    │ mcp     │ unsupported │                                                                                 │&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│ claude      │ deny   │ command │ partial     │ permissions.deny Bash(&amp;#x3C;command&gt; *)                                              │&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│ claude      │ deny   │ read    │ partial     │ permissions.deny Read(&amp;#x3C;path&gt;)                                                   │&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│ claude      │ deny   │ edit    │ partial     │ permissions.deny Edit(&amp;#x3C;path&gt;)                                                   │&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│ claude      │ deny   │ mcp     │ full        │ permissions.deny mcp__&amp;#x3C;server&gt;__&amp;#x3C;tool&gt;                                          │&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│ claude      │ ask    │ command │ partial     │ permissions.ask Bash(&amp;#x3C;command&gt; *)                                               │&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│ claude      │ ask    │ read    │ partial     │ permissions.ask Read(&amp;#x3C;path&gt;)                                                    │&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│ claude      │ ask    │ edit    │ partial     │ permissions.ask Edit(&amp;#x3C;path&gt;)                                                    │&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│ claude      │ ask    │ mcp     │ full        │ permissions.ask mcp__&amp;#x3C;server&gt;__&amp;#x3C;tool&gt;                                           │&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│ codex       │ deny   │ command │ partial     │ .codex/rules/tuff.rules prefix_rule(decision = &quot;forbidden&quot;)                     │&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│ codex       │ deny   │ read    │ unsupported │                                                                                 │&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│ codex       │ deny   │ edit    │ unsupported │                                                                                 │&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│ codex       │ deny   │ mcp     │ partial     │ .codex/config.toml [mcp_servers.&amp;#x3C;server&gt;] disabled_tools                        │&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│ codex       │ ask    │ command │ partial     │ .codex/rules/tuff.rules prefix_rule(decision = &quot;prompt&quot;)                        │&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│ codex       │ ask    │ read    │ unsupported │                                                                                 │&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│ codex       │ ask    │ edit    │ unsupported │                                                                                 │&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│ codex       │ ask    │ mcp     │ partial     │ .codex/config.toml [mcp_servers.&amp;#x3C;server&gt;.tools.&amp;#x3C;tool&gt;] approval_mode = &quot;prompt&quot; │&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│ cursor      │ deny   │ command │ unsupported │                                                                                 │&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│ cursor      │ deny   │ read    │ unsupported │                                                                                 │&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│ cursor      │ deny   │ edit    │ unsupported │                                                                                 │&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│ cursor      │ deny   │ mcp     │ unsupported │                                                                                 │&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│ cursor      │ ask    │ command │ unsupported │                                                                                 │&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│ cursor      │ ask    │ read    │ unsupported │                                                                                 │&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│ cursor      │ ask    │ edit    │ unsupported │                                                                                 │&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│ cursor      │ ask    │ mcp     │ unsupported │                                                                                 │&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│ opencode    │ deny   │ command │ partial     │ .opencode/opencode.json permission.bash &quot;&amp;#x3C;command&gt; *&quot;: &quot;deny&quot;                   │&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│ opencode    │ deny   │ read    │ partial     │ .opencode/opencode.json permission.read &quot;&amp;#x3C;path&gt;&quot;: &quot;deny&quot;                        │&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│ opencode    │ deny   │ edit    │ partial     │ .opencode/opencode.json permission.edit &quot;&amp;#x3C;path&gt;&quot;: &quot;deny&quot;                        │&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│ opencode    │ deny   │ mcp     │ full        │ .opencode/opencode.json permission &quot;&amp;#x3C;server&gt;_&amp;#x3C;tool&gt;&quot;: &quot;deny&quot;                    │&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│ opencode    │ ask    │ command │ partial     │ .opencode/opencode.json permission.bash &quot;&amp;#x3C;command&gt; *&quot;: &quot;ask&quot;                    │&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│ opencode    │ ask    │ read    │ partial     │ .opencode/opencode.json permission.read &quot;&amp;#x3C;path&gt;&quot;: &quot;ask&quot;                         │&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│ opencode    │ ask    │ edit    │ partial     │ .opencode/opencode.json permission.edit &quot;&amp;#x3C;path&gt;&quot;: &quot;ask&quot;                         │&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│ opencode    │ ask    │ mcp     │ full        │ .opencode/opencode.json permission &quot;&amp;#x3C;server&gt;_&amp;#x3C;tool&gt;&quot;: &quot;ask&quot;                     │&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;└─────────────┴────────┴─────────┴─────────────┴─────────────────────────────────────────────────────────────────────────────────┘&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The command then prints a note for every &lt;code dir=&quot;auto&quot;&gt;partial&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;unsupported&lt;/code&gt; row. The notes are where the limits live. Four of them, out of the eighteen it prints:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;- claude: matches the command as Claude writes it, including inside compound commands; the same program run another way, such as by absolute path, through sh -c, or as git -C . push, is not matched&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;- codex: matches the command&apos;s leading words, and each command of a simple chain joined by &amp;#x26;&amp;#x26;, ||, ; or |; a script with redirection, $(...), a variable assignment, a wildcard, or control flow is matched as one command and not caught, and a program run by absolute path such as /usr/bin/git may not be matched; Codex loads project rules only in a trusted project and labels rules experimental&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;- opencode: covers OpenCode&apos;s read tool; grep, glob, list, and shell commands are separate permissions and are not covered; OpenCode loads .opencode/opencode.json after the project&apos;s opencode.json, but inline OPENCODE_CONFIG_CONTENT, managed config, and an agent&apos;s own permission settings are applied after it&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;- cursor: Tuff does not compile policy rules for this agent yet&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The recording below runs the &lt;a href=&quot;https://github.com/kannandreams/tuff-pack-examples/tree/main/projects/policy-guardrails&quot;&gt;policy guardrails example&lt;/a&gt;. Claude Code is asked for a value in &lt;code dir=&quot;auto&quot;&gt;.env&lt;/code&gt; and reads the file. &lt;code dir=&quot;auto&quot;&gt;tuff add&lt;/code&gt; then installs a policy that denies reading &lt;code dir=&quot;auto&quot;&gt;.env&lt;/code&gt;, and the same request is denied.&lt;/p&gt;
&lt;video controls muted playsinline preload=&quot;none&quot; poster=&quot;/video/policy-guardrails-demo.png&quot; width=&quot;1600&quot; height=&quot;800&quot;&gt;&lt;source src=&quot;/video/policy-guardrails-demo.mp4&quot; type=&quot;video/mp4&quot;&gt;&lt;p&gt;&lt;a href=&quot;https://tuffcli.dev/video/policy-guardrails-demo.mp4&quot;&gt;Download the recording&lt;/a&gt; if your browser cannot play it inline.&lt;/p&gt;&lt;/video&gt;
&lt;div&gt;&lt;h3 id=&quot;harnesses-that-enforce-nothing&quot;&gt;Harnesses that enforce nothing&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Cursor and the shared Open Agents layout compile no policy rules. For them, &lt;code dir=&quot;auto&quot;&gt;tuff add&lt;/code&gt; installs nothing and lists every rule:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;tuff&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;./policies/infra-guardrails&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-a&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cursor&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;error: policy &apos;infra-guardrails&apos; would not be enforced as written, so it was not installed:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Cursor: rule 1 (deny command &quot;git push --force&quot;) is not enforced: Tuff does not compile policy rules for this agent yet&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Cursor: rule 2 (deny read &quot;.env&quot;, &quot;secrets/**&quot;) is not enforced: Tuff does not compile policy rules for this agent yet&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Cursor: rule 3 (ask command &quot;terraform apply&quot;) is not enforced: Tuff does not compile policy rules for this agent yet&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Cursor: rule 4 (deny mcp &quot;github:delete_*&quot;) is not enforced: Tuff does not compile policy rules for this agent yet&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;hint: run &apos;tuff policy matrix&apos; to see what each agent can enforce, or pass --accept-unenforced to install the rules each agent enforces and record the rest in tuff.lock&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;--accept-unenforced&lt;/code&gt; does not help here, and says so. An agent that enforces none of a policy’s rules is refused either way, because installing would record a policy and write no rule:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;error: policy &apos;infra-guardrails&apos; was not installed: Cursor enforces none of its rules:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Cursor: rule 1 (deny command &quot;git push --force&quot;) is not enforced: Tuff does not compile policy rules for this agent yet&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Cursor: rule 2 (deny read &quot;.env&quot;, &quot;secrets/**&quot;) is not enforced: Tuff does not compile policy rules for this agent yet&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Cursor: rule 3 (ask command &quot;terraform apply&quot;) is not enforced: Tuff does not compile policy rules for this agent yet&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Cursor: rule 4 (deny mcp &quot;github:delete_*&quot;) is not enforced: Tuff does not compile policy rules for this agent yet&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;hint: run &apos;tuff policy matrix&apos; to see what each agent can enforce&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;no-allow-rules&quot;&gt;No allow rules&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;A policy has no &lt;code dir=&quot;auto&quot;&gt;allow&lt;/code&gt; effect, and Tuff refuses a rule that uses one. Policies can come from another team’s repository or a published pack, and an allow rule in a shared policy would widen what an agent may do in every project that installs it. Permissions an agent needs go in the harness’s own settings.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;keeping-the-rules-in-place&quot;&gt;Keeping the rules in place&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;existing-settings&quot;&gt;Existing settings&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Tuff adds its rules next to the existing contents of the harness’s file, &lt;code dir=&quot;auto&quot;&gt;.claude/settings.json&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;.codex/rules/tuff.rules&lt;/code&gt;, or &lt;code dir=&quot;auto&quot;&gt;.opencode/opencode.json&lt;/code&gt;, and records each rule it wrote in &lt;code dir=&quot;auto&quot;&gt;tuff.lock&lt;/code&gt;. It leaves rules it did not add unchanged, skips a rule that is already present, and stops before writing if the file cannot be read as its own format. In &lt;code dir=&quot;auto&quot;&gt;.opencode/opencode.json&lt;/code&gt; it also keeps the order of what is already there, since OpenCode reads rule order as precedence, and refuses rather than overwrite a rule of yours with the same pattern and a different action.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;detecting-a-removed-rule&quot;&gt;Detecting a removed rule&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Remove &lt;code dir=&quot;auto&quot;&gt;Read(/**/.env)&lt;/code&gt; from the settings file, then run:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;tuff&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;check&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;✗ infra-guardrails         policy claude       modified (.claude/settings.json#permissions.deny)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;tuff check&lt;/code&gt; exits non-zero, so a CI job that runs it fails. The &lt;a href=&quot;https://tuffcli.dev/cli/ci/#ci-with-github-actions&quot;&gt;CI guide&lt;/a&gt; shows the GitHub Actions setup. To restore the rules:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;tuff&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;update&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;infra-guardrails&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-a&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;claude&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--force&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;tuff&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;check&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Claude: rule 1 (deny command &quot;git push --force&quot;) is enforced partially: matches the command as Claude writes it, including inside compound commands; the same program run another way, such as by absolute path, through sh -c, or as git -C . push, is not matched&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Claude: rule 2 (deny read &quot;.env&quot;, &quot;secrets/**&quot;) is enforced partially: covers Claude&apos;s file tools and the shell commands Claude Code recognises, such as cat and sed, not a script or program that opens the file itself&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Claude: rule 3 (ask command &quot;terraform apply&quot;) is enforced partially: matches the command as Claude writes it, including inside compound commands; the same program run another way, such as by absolute path, through sh -c, or as git -C . push, is not matched&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;installed infra-guardrails (claude) -&gt; .claude/policies/infra-guardrails/policy.toml&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;compiled 5 permission rule(s) for infra-guardrails (claude) -&gt; .claude/settings.json&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;✓ infra-guardrails         policy claude       ok&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;updating-and-deleting-a-policy&quot;&gt;Updating and deleting a policy&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;When a new version of a policy drops a rule, &lt;code dir=&quot;auto&quot;&gt;tuff update&lt;/code&gt; removes that rule from the settings file. &lt;code dir=&quot;auto&quot;&gt;tuff delete&lt;/code&gt; removes the rules the policy added and leaves the rest of the file. For a settings file that also had a hand-written &lt;code dir=&quot;auto&quot;&gt;Bash(curl *)&lt;/code&gt; deny rule and a &lt;code dir=&quot;auto&quot;&gt;model&lt;/code&gt; setting, deleting the policy leaves:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;.claude/settings.json&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;model&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;opus&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;permissions&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;deny&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&quot;Bash(curl *)&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;policies-instructions-and-the-sandbox&quot;&gt;Policies, instructions, and the sandbox&lt;/h2&gt;&lt;/div&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;th&gt;What it stops&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Instructions&lt;/td&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;CLAUDE.md&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;AGENTS.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Nothing on its own. The model reads it as part of the prompt.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Harness policy&lt;/td&gt;
&lt;td&gt;A Tuff policy compiled to permission rules&lt;/td&gt;
&lt;td&gt;The agent’s own tool calls that match a rule, before they run.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sandbox&lt;/td&gt;
&lt;td&gt;The harness’s operating-system sandbox, containers&lt;/td&gt;
&lt;td&gt;The action at the operating-system level, however it is written.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Harness permission rules match the tool call the agent makes, so Tuff reports command and file rules as &lt;code dir=&quot;auto&quot;&gt;partial&lt;/code&gt;. Claude Code’s documentation states that these rules are not a security boundary. To block an action however it is written, also enable the harness’s sandbox. Tuff cannot enable it, because no file in the repository controls the sandbox.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;further-reading&quot;&gt;Further reading&lt;/h2&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;a href=&quot;https://tuffcli.dev/primitives/policies/&quot;&gt;policies reference&lt;/a&gt; covers the format, the Claude Code mapping, and the coverage matrix.&lt;/li&gt;
&lt;li&gt;The &lt;a href=&quot;https://github.com/kannandreams/tuff-pack-examples/tree/main/projects/policy-guardrails&quot;&gt;policy guardrails example&lt;/a&gt; is the project from the recording.&lt;/li&gt;
&lt;li&gt;A &lt;a href=&quot;https://tuffcli.dev/concepts/packs/&quot;&gt;capability pack&lt;/a&gt; bundles a policy with the skills, hooks, and MCP servers it applies to.&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>policy</category><category>security</category></item><item><title>Managing MCP server configuration across coding agents</title><link>https://tuffcli.dev/blog/mcp-servers-in-one-command/</link><guid isPermaLink="true">https://tuffcli.dev/blog/mcp-servers-in-one-command/</guid><description>Declare an external MCP server once, and Tuff writes the config entry each harness reads and checks that the server starts. The steps run in a new directory in about ten minutes.</description><pubDate>Wed, 02 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Each coding harness reads MCP servers from its own file: Claude Code from &lt;code dir=&quot;auto&quot;&gt;.mcp.json&lt;/code&gt;, Cursor from &lt;code dir=&quot;auto&quot;&gt;.cursor/mcp.json&lt;/code&gt;, OpenCode from &lt;code dir=&quot;auto&quot;&gt;opencode.json&lt;/code&gt;, and the shared Open Agents layout from &lt;code dir=&quot;auto&quot;&gt;.agents/mcp.json&lt;/code&gt;. The same server needs one hand edit per file, and the harness does not report a typo in any of them. Since Tuff 0.1.8 an MCP server is a capability: the declaration lives in one place, and Tuff generates the config entries.&lt;/p&gt;
&lt;p&gt;This walkthrough uses the &lt;code dir=&quot;auto&quot;&gt;everything&lt;/code&gt; server from the built-in catalog. It is the reference server the MCP project publishes for exercising the protocol, and it needs no API key. You need &lt;code dir=&quot;auto&quot;&gt;tuff&lt;/code&gt; and Node’s &lt;code dir=&quot;auto&quot;&gt;npx&lt;/code&gt; on your &lt;code dir=&quot;auto&quot;&gt;PATH&lt;/code&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;1-install-the-server-for-three-harnesses&quot;&gt;1. Install the server for three harnesses&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;mkdir&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;mcp-demo&lt;/span&gt;&lt;span&gt; &amp;#x26;&amp;#x26; &lt;/span&gt;&lt;span&gt;cd&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;mcp-demo&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;tuff&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;init&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;tuff&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;mcp&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;everything&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-a&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;claude&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-a&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cursor&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-a&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;open-agents&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;installed everything (claude) -&gt; .claude/mcp-servers/everything/server.toml&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;installed everything (cursor) -&gt; .cursor/mcp-servers/everything/server.toml&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;installed everything (open-agents) -&gt; .agents/mcp-servers/everything/server.toml&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;registered MCP server everything (claude) -&gt; .mcp.json&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;registered MCP server everything (cursor) -&gt; .cursor/mcp.json&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;registered MCP server everything (open-agents) -&gt; .agents/mcp.json&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;installed everything from the built-in catalog (catalog 1.0.0)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Tuff writes two files per harness. The config entry is the file the harness reads:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;.mcp.json&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;mcpServers&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;everything&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&quot;args&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;&quot;-y&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;&quot;@modelcontextprotocol/server-everything&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&quot;command&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;npx&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The tracked record is the declaration Tuff hashes. &lt;code dir=&quot;auto&quot;&gt;tuff check&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;tuff diff&lt;/code&gt; use it in the same way as for a skill:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;.claude/mcp-servers/everything/server.toml&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;id = &lt;/span&gt;&lt;span&gt;&quot;everything&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;version = &lt;/span&gt;&lt;span&gt;&quot;1.0.0&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;type = &lt;/span&gt;&lt;span&gt;&quot;mcp-server&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;description = &lt;/span&gt;&lt;span&gt;&quot;Reference server exercising the full MCP surface: tools, resources, and prompts. Needs no API key, which makes it a good first target for tuff mcp doctor.&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;server&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;transport = &lt;/span&gt;&lt;span&gt;&quot;stdio&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;command = &lt;/span&gt;&lt;span&gt;&quot;npx&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;args = [&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;-y&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;@modelcontextprotocol/server-everything&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;server&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;env&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;server&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;metadata&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;tools_summary = &lt;/span&gt;&lt;span&gt;&quot;echo, add, longRunningOperation, sampleLLM, getTinyImage&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Each catalog entry is a launch declaration checked against the vendor’s README. The catalog holds no server code: &lt;code dir=&quot;auto&quot;&gt;npx&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;uvx&lt;/code&gt;, or &lt;code dir=&quot;auto&quot;&gt;docker&lt;/code&gt; fetches the server when the harness starts it.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;2-list-the-installed-server&quot;&gt;2. List the installed server&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;tuff&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;list&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;┌───────────────────┬────────────┬─────────┬─────────┬─────────────┬─────────┬──────────────────────────────────┐&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│ ID                │ TYPE       │ VERSION │ SCOPE   │ AGENT       │ STATUS  │ PATH                             │&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;├───────────────────┼────────────┼─────────┼─────────┼─────────────┼─────────┼──────────────────────────────────┤&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│ everything        │ mcp-server │ 1.0.0   │ project │ claude      │ ✓ clean │ .claude/mcp-servers/everything   │&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│ everything        │ mcp-server │ 1.0.0   │ project │ cursor      │ ✓ clean │ .cursor/mcp-servers/everything   │&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│ everything        │ mcp-server │ 1.0.0   │ project │ open-agents │ ✓ clean │ .agents/mcp-servers/everything   │&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│ tuff-capabilities │ skill      │ 1.0.0   │ project │ open-agents │ ✓ clean │ .agents/skills/tuff-capabilities │&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│ tuff-cli-guide    │ skill      │ 0.1.0   │ project │ open-agents │ ✓ clean │ .agents/skills/tuff-cli-guide    │&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;└───────────────────┴────────────┴─────────┴─────────┴─────────────┴─────────┴──────────────────────────────────┘&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;tuff list&lt;/code&gt; shows one &lt;code dir=&quot;auto&quot;&gt;everything&lt;/code&gt; row per harness, because each harness has its own copy of the entry. &lt;code dir=&quot;auto&quot;&gt;tuff init&lt;/code&gt; installed the &lt;code dir=&quot;auto&quot;&gt;tuff-cli-guide&lt;/code&gt; skill, and the &lt;code dir=&quot;auto&quot;&gt;tuff-capabilities&lt;/code&gt; skill is described in the last section.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;3-check-that-the-server-starts&quot;&gt;3. Check that the server starts&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;tuff mcp doctor&lt;/code&gt; starts each installed server, completes the MCP &lt;code dir=&quot;auto&quot;&gt;initialize&lt;/code&gt; handshake, and requests its tool list:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;tuff&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;mcp&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;doctor&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;┌────────────┬───────────┬─────────────────────────────┬────────┬────────────┐&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│ ID         │ TRANSPORT │ HARNESSES                   │ STATUS │ DETAIL     │&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;├────────────┼───────────┼─────────────────────────────┼────────┼────────────┤&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│ everything │ stdio     │ claude, cursor, open-agents │ ✓ ok   │ 13 tool(s) │&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;└────────────┴───────────┴─────────────────────────────┴────────┴────────────┘&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The server reported 13 tools. With the npm package already cached, the check took about half a second. Doctor prints one row per server, because every harness launches the same process. It exits non-zero when a server is unhealthy, so it can run in CI next to &lt;code dir=&quot;auto&quot;&gt;tuff check&lt;/code&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;4-detect-a-hand-edit&quot;&gt;4. Detect a hand edit&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Add &lt;code dir=&quot;auto&quot;&gt;&quot;--verbose&quot;&lt;/code&gt; to the args in &lt;code dir=&quot;auto&quot;&gt;.mcp.json&lt;/code&gt;. Every managed entry has a baseline hash, so &lt;code dir=&quot;auto&quot;&gt;tuff check&lt;/code&gt; reports the change:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;tuff&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;check&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;✗ everything               mcp-server claude       modified (.mcp.json#everything)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;✓ everything               mcp-server cursor       ok&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;✓ everything               mcp-server open-agents  ok&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;✓ tuff-capabilities        skill open-agents  ok&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;✓ tuff-cli-guide           skill open-agents  ok&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The failing row names the file and the entry. &lt;code dir=&quot;auto&quot;&gt;tuff update&lt;/code&gt; restores the entry, and without &lt;code dir=&quot;auto&quot;&gt;--force&lt;/code&gt; it refuses to overwrite a local change:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;tuff&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;update&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;everything&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-a&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;claude&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;error: &apos;everything&apos; has local changes&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;hint: run &apos;tuff diff everything&apos; first, or use --force to reload from the catalog&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;tuff&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;update&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;everything&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-a&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;claude&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--force&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;tuff&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;check&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;installed everything (claude) -&gt; .claude/mcp-servers/everything/server.toml&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;registered MCP server everything (claude) -&gt; .mcp.json&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;✓ everything               mcp-server claude       ok&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;✓ everything               mcp-server cursor       ok&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;✓ everything               mcp-server open-agents  ok&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;✓ tuff-capabilities        skill open-agents  ok&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;✓ tuff-cli-guide           skill open-agents  ok&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;tuff check&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;tuff update&lt;/code&gt; leave servers added by hand next to Tuff’s unchanged.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;5-servers-that-need-a-token&quot;&gt;5. Servers that need a token&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;A manifest names the environment variable that holds a token, and Tuff does not store the token. The catalog entry for GitHub’s server uses &lt;code dir=&quot;auto&quot;&gt;GITHUB_PERSONAL_ACCESS_TOKEN&lt;/code&gt;:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;tuff&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;mcp&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;github&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-a&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;claude&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;tuff&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;mcp&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;doctor&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;installed github (claude) -&gt; .claude/mcp-servers/github/server.toml&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;registered MCP server github (claude) -&gt; .mcp.json&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;note: &apos;github&apos; reads a variable from the environment; export GITHUB_PERSONAL_ACCESS_TOKEN before starting the harness&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;installed github from the built-in catalog (catalog 1.0.0)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;┌────────────┬───────────┬─────────────────────────────┬───────────────┬─────────────────────────────────────┐&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│ ID         │ TRANSPORT │ HARNESSES                   │ STATUS        │ DETAIL                              │&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;├────────────┼───────────┼─────────────────────────────┼───────────────┼─────────────────────────────────────┤&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│ everything │ stdio     │ claude, cursor, open-agents │ ✓ ok          │ 13 tool(s)                          │&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│ github     │ stdio     │ claude                      │ ? missing env │ export GITHUB_PERSONAL_ACCESS_TOKEN │&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;└────────────┴───────────┴─────────────────────────────┴───────────────┴─────────────────────────────────────┘&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Doctor checks the environment before starting a server, so the GitHub server was not started. In an interactive terminal, &lt;code dir=&quot;auto&quot;&gt;tuff add&lt;/code&gt; also asks whether the token is stored under a different variable name.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;6-remove-the-servers&quot;&gt;6. Remove the servers&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;tuff&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;delete&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;everything&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-a&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;claude&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-a&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cursor&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-a&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;open-agents&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;tuff&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;delete&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;github&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-a&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;claude&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;deleted &apos;everything&apos; from project scope&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;deleted &apos;github&apos; from project scope&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;tuff delete&lt;/code&gt; removes the config entries and the tracked records, and leaves the &lt;code dir=&quot;auto&quot;&gt;mcpServers&lt;/code&gt; object in each file.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-tuff-capabilities-skill&quot;&gt;The tuff-capabilities skill&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Tuff regenerates a &lt;code dir=&quot;auto&quot;&gt;tuff-capabilities&lt;/code&gt; skill in &lt;code dir=&quot;auto&quot;&gt;.agents/skills/&lt;/code&gt; when capabilities change. It lists every installed server with its description, transport, and tool summary, and tells the agent that the servers are already loaded by the harness.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;further-reading&quot;&gt;Further reading&lt;/h2&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;a href=&quot;https://tuffcli.dev/primitives/mcp-servers/&quot;&gt;MCP Servers reference&lt;/a&gt; covers the manifest, the full catalog, and the safety rules.&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;tuff add mcp&lt;/code&gt; also accepts a directory or a git URL for a server the catalog does not include.&lt;/li&gt;
&lt;li&gt;A server you ship yourself is an &lt;a href=&quot;https://tuffcli.dev/primitives/tools/&quot;&gt;MCP-native tool&lt;/a&gt;: Tuff copies its code and registers it.&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>mcp</category><category>tutorial</category></item></channel></rss>