Skip to content

Examples ​

Four everyday tasks, each with the one-time setup, the request the agent makes and the prompt you approve. They all start the same way: copy the token from the provider's web page and store it with --from-clipboard, so it never passes through the chat or your shell history.

After adding a secret

The MCP server reads the policy when it starts. After keymitt add or keymitt allow, restart the keymitt server in your AI client. In Claude Code: /mcp → keymitt → Reconnect.

Triage GitHub issues ​

"List the open bugs in my-org/my-app, oldest first, and suggest which ones to close."

Setup. Create a fine-grained token that can read issues, copy it, then:

sh
keymitt add github --description "GitHub token, issues read-only" --host api.github.com --from-clipboard

The agent calls http_request:

json
{
  "url": "https://api.github.com/repos/my-org/my-app/issues?labels=bug&state=open&direction=asc",
  "headers": {
    "Authorization": "Bearer {{secret:github}}",
    "Accept": "application/vnd.github+json"
  }
}

You approve:

text
send github → https://api.github.com | GET https://api.github.com/repos/my-org/my-app/issues?labels=bug&state=open&direction=asc | Accept: application/vnd.github+json | Authorization: Bearer {{secret:github}}

The agent gets the list of issues. It never sees the token, and it cannot send the token anywhere but api.github.com.

Deploy a site to Cloudflare Pages ​

"Build the docs and deploy them to the docs project on Cloudflare Pages."

Setup. Create an API token with the Cloudflare Pages: Edit permission, copy it, then:

sh
keymitt add cloudflare --description "Cloudflare API token, Pages edit" --command wrangler --env CLOUDFLARE_API_TOKEN --from-clipboard

keymitt prints the program it found, following symlinks to the real file, for example wrangler → /opt/homebrew/lib/node_modules/wrangler/bin/wrangler.js. Only that program can receive the token, and only as CLOUDFLARE_API_TOKEN.

The agent builds the site on its own (no secret needed), then calls run_with_secrets:

json
{
  "command": ["wrangler", "pages", "deploy", "dist", "--project-name", "docs"],
  "env": { "CLOUDFLARE_API_TOKEN": "{{secret:cloudflare}}" }
}

You approve:

text
use cloudflare → /opt/homebrew/lib/node_modules/wrangler/bin/wrangler.js (as wrangler) | args: pages deploy dist --project-name docs | env: CLOUDFLARE_API_TOKEN={{secret:cloudflare}} | dir: /Users/you/site

The agent reads wrangler's output, including the deployment URL. If the token were ever printed, it would arrive as [REDACTED:cloudflare].

Ask the staging database a question ​

"How many users signed up on staging each day this week?"

Setup. Copy the password of a read-only database user from your password manager, then:

sh
keymitt add pg-staging --description "staging DB, read-only user" --command psql --env PGPASSWORD --from-clipboard

The agent calls run_with_secrets:

json
{
  "command": ["psql", "-h", "staging-db.example.internal", "-U", "readonly", "-d", "app", "-c",
              "select date(created_at), count(*) from users where created_at > now() - interval '7 days' group by 1 order by 1"],
  "env": { "PGPASSWORD": "{{secret:pg-staging}}" }
}

You approve a prompt that contains the whole SQL statement, so you see exactly what runs:

text
use pg-staging → /opt/homebrew/Cellar/postgresql@18/18.6_1/bin/psql | args: -h staging-db.example.internal -U readonly -d app -c 'select date(created_at), count(*) from users …' | env: PGPASSWORD={{secret:pg-staging}} | dir: /Users/you/app

Use a read-only role anyway: keymitt protects the password, not the data behind it.

Debian and Ubuntu

There, psql is a link to pg_wrapper, a script that only works when started under the name psql. Allow the real program instead, for example --command /usr/lib/postgresql/16/bin/psql (the version varies). The agent sees that path in list_secrets and calls it directly.

After an upgrade

keymitt stores the real file's path, and Homebrew's include the version. When an upgrade moves the program, run keymitt allow pg-staging --command psql to point the policy at the new file.

When the agent needs a secret you haven't stored ​

"Post the release notes to #releases on Slack."

The agent calls list_secrets, finds nothing for Slack, and calls request_secret. You get a message like this, never a request to paste the token into the chat:

text
Please store this secret yourself by running in a terminal:
  keymitt add slack-bot --description 'Slack bot token for release notes' --command <program> --host <api-host>
Replace <program>/<api-host> with what it may be used for (intended use: slack.com chat.postMessage), or
drop the flags you don't need. keymitt will prompt for the value without echoing it; if you have copied
the value, add --from-clipboard to read it from the clipboard instead. …

Setup. Copy the bot token from your Slack app's OAuth & Permissions page and run the command with the placeholders filled in:

sh
keymitt add slack-bot --description "Slack bot token for release notes" --host slack.com --from-clipboard

Reconnect the keymitt server. The agent calls http_request:

json
{
  "method": "POST",
  "url": "https://slack.com/api/chat.postMessage",
  "headers": {
    "Authorization": "Bearer {{secret:slack-bot}}",
    "Content-Type": "application/json; charset=utf-8"
  },
  "body": "{\"channel\": \"#releases\", \"text\": \"keymitt 0.1.0 is out: …\"}"
}

The prompt shows the start of the message body as well, so you can check the text before it is posted.

Tips ​

  • Name and describe secrets well. The agent chooses a secret from list_secrets by its name and description.
  • Scope tokens narrowly. keymitt controls where a token goes, not what it may do there.
  • Look back. keymitt audit lists every use, with the approval and the outcome, never values.

Released under the MIT or Apache-2.0 license.