08 / 10Operator52 min lesson + lab

Build the Extension Stack

Turn repeated expertise into skills, distribute broader capabilities through plugins, connect live systems with MCP, and enforce deterministic boundaries with hooks.

By the end

A tested extension design that assigns knowledge, packaging, external access, and enforcement to distinct mechanisms with explicit permissions and owners.

01

Start from the job, not the mechanism

Extensions solve different problems. A skill teaches a repeatable way to perform a task. A plugin packages capabilities so they can be installed and shared. MCP connects Codex to live tools or data. A hook runs deterministic logic around lifecycle events. Choosing the mechanism before naming the job produces unnecessary infrastructure and hidden behavior.

Begin with a successful manual workflow. Identify the judgment that should be reusable, the live access it needs, the safeguards that must always run, and the audience that will install it. Package only the parts that have proven stable enough to deserve reuse.

02

Skills capture method

A skill is appropriate when the work requires a repeatable sequence, domain-specific judgment, references, templates, or helper scripts. A repository skill lives under `.agents/skills/<skill-name>/SKILL.md`; a personal skill lives under `~/.agents/skills/`. `SKILL.md` begins with YAML frontmatter containing `name` and a precise `description`, followed by imperative instructions. Optional `scripts/`, `references/`, and `assets/` folders keep deterministic work and supporting material separate.

Codex can invoke a skill implicitly when the task matches its description, or explicitly when you mention `$skill-name` or use `/skills` in CLI/IDE. Keep progressive detail outside the main instruction when possible. Test an ordinary trigger, an edge case, and a near-miss prompt so it activates when useful and stays quiet otherwise.

03

Plugins package capability

A plugin is useful when a capability must be installed or distributed as a cohesive bundle rather than copied file by file. It may bring skills and other supporting components together. The plugin boundary introduces product responsibilities: versioning, documentation, dependency ownership, compatibility, update behavior, and a safe uninstall path.

Do not create a plugin merely because one person has a useful prompt. Prove the workflow as local guidance or a skill first. Package it when multiple projects or users need the same governed capability and the maintenance owner is clear.

04

MCP connects live systems

MCP is appropriate when Codex needs current data or actions from another system. Local clients support STDIO servers and streamable HTTP servers, and the desktop app, CLI, and IDE extension share MCP configuration on the same host. User servers live in `~/.codex/config.toml`; trusted-project servers may live in `.codex/config.toml`. Add a STDIO server with `codex mcp add`, list it with `codex mcp list`, and use `codex mcp login <name>` only when the server supports OAuth.

Start read-only. Inspect every exposed tool and test with non-sensitive data. Prefer purpose-built narrow tools over a generic execute-anything endpoint. Keep tokens out of committed TOML and prompts: pass approved environment-variable names or use the supported authentication flow. If a task depends on a server, mark and test that dependency so the run fails visibly rather than silently continuing with missing context.

05

Hooks enforce deterministic policy

A hook is appropriate when a rule must run reliably at a lifecycle boundary rather than depend on the model remembering prose. Codex discovers `hooks.json` or inline `[hooks]` next to user and trusted-project config. `PreToolUse` can inspect and deny a shell, patch, MCP, or other supported local tool before it runs. Every command hook receives JSON on standard input. A denial returns the documented `hookSpecificOutput` shape or exits with code 2 and writes the reason to standard error.

Project hooks are reviewed by hash before they run; changed hooks require review again, and `/hooks` is the CLI place to inspect or trust them. A hook is not a substitute for broad judgment. Keep it small, fast, deterministic, and clear about failure. Test allowed, denied, malformed-input, and missing-dependency cases. Remember that hosted tools can fall outside local hook coverage, so hooks are a guardrail rather than the entire security boundary.

Working reference

Commands and patterns

Repository skill: verification receipt

# .agents/skills/verification-receipt/SKILL.md
---
name: verification-receipt
description: Use after a code or configuration change to reconcile acceptance criteria with commands, user-flow evidence, environment, and remaining risk. Do not use for work that has not been implemented.
---

1. Read the original acceptance criteria and final diff.
2. Run the smallest checks that prove each changed boundary.
3. Observe the critical user flow when an interface changed.
4. Mark every criterion proven, disproven, or unverified.
5. Return changed files, commands and results, environment, rollback, and remaining risk.

Create the directory and file in the repository root, then invoke `$verification-receipt` explicitly. The description controls discovery; the body defines the reusable method.

Connect and inspect a documentation MCP server

codex mcp add context7 -- npx -y @upstash/context7-mcp
codex mcp list
codex mcp --help

This is the current official STDIO example. `npx` may need network approval and downloads third-party code, so review the package and use a disposable or approved environment for the lab.

Equivalent MCP configuration

# ~/.codex/config.toml or trusted-project .codex/config.toml
[mcp_servers.context7]
command = "npx"
args = ["-y", "@upstash/context7-mcp"]

Use project config only when the dependency is appropriate for everyone who trusts the repository. Do not add secret values to committed configuration.

Project hook that checks shell commands

# .codex/hooks.json
{
  "description": "Prevent accidental pushes during the training lab.",
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "^Bash$",
        "hooks": [
          {
            "type": "command",
            "command": "/usr/bin/python3 \"$(git rev-parse --show-toplevel)/.codex/hooks/block_push.py\"",
            "timeout": 30,
            "statusMessage": "Checking command policy"
          }
        ]
      }
    ]
  }
}

Codex resolves the script from the Git root even when the chat starts in a subdirectory. Open `/hooks` and review the exact hook before trusting it.

Deterministic hook handler

# .codex/hooks/block_push.py
import json
import sys

payload = json.load(sys.stdin)
command = payload.get("tool_input", {}).get("command", "")
if "git push" in command:
    print(json.dumps({
        "hookSpecificOutput": {
            "hookEventName": "PreToolUse",
            "permissionDecision": "deny",
            "permissionDecisionReason": "Training repository: git push is blocked by project hook."
        }
    }))

This training policy blocks any Bash command containing `git push` before it runs. Production policy needs stronger command parsing and tests; this intentionally small example teaches the wire shape.

When the happy path breaks

Failure modes

01

Symptom

A reusable workflow becomes a giant skill that triggers for unrelated tasks.

Likely cause

The skill has a vague purpose, excessive scope, and no explicit non-trigger cases.

Recovery

Narrow the job, inputs, trigger description, workflow, and acceptance checks; move optional reference material out of the main instruction.

02

Symptom

A plugin is difficult to update or uninstall because its components and dependencies are unclear.

Likely cause

Packaging happened before ownership, compatibility, and lifecycle behavior were designed.

Recovery

Document the bundle, versions, dependencies, settings, data, update path, and removal procedure; keep experimental workflows unbundled until stable.

03

Symptom

An MCP server gives Codex more data or write authority than the task requires.

Likely cause

The connection was treated as a capability toggle rather than a scoped security boundary.

Recovery

Disconnect or reduce scope, start with read-only tools, review authentication and token storage, and expose narrow actions with clear descriptions and confirmation boundaries.

04

Symptom

A hook blocks normal work, loops, or fails without an understandable message.

Likely cause

The hook contains complex nondeterministic logic or was not tested across success and failure paths.

Recovery

Reduce it to one deterministic policy, bound execution time, return actionable output, and test allowed, denied, malformed, and unavailable-dependency cases.

Hands-on lab

Build one governed extension chain

Take a proven manual workflow and implement the minimum skill, MCP access, and hook needed to repeat it safely.

  1. 01In a disposable Git repository, create `.agents/skills/verification-receipt/SKILL.md` from the example. Start a fresh session, open `/skills`, and invoke `$verification-receipt` on a tiny completed change.
  2. 02Test three prompts: a completed change that should trigger the skill, an unfinished task that should not, and an explicit `$verification-receipt` invocation. Record which path Codex takes.
  3. 03Review the Context7 package and, if approved, run the MCP add command. Use `codex mcp list`, start a fresh session, inventory its exposed tools, and make one read-only documentation request with non-sensitive input.
  4. 04Create the hook JSON and Python handler. Open `/hooks`, inspect its source and hash, and trust it for this disposable repository.
  5. 05Ask Codex to run a harmless command such as `pwd` and confirm the hook allows it. Then ask Codex to run `git push --dry-run`; confirm the hook denies it before Git runs, and save the reason.
  6. 06Break the hook's JSON input handling in a temporary copy, observe the failure, restore the valid script, and rerun both allowed and denied cases.
  7. 07Run the skill, read-only MCP lookup, and hook-protected receipt workflow end to end. Decide whether the proven capability needs a plugin or should remain project-local.

Deliverable: A discovered and explicitly invoked skill, scoped MCP inventory and read-only call, trusted hook with allowed and denied evidence, and extension decision record with no hidden credentials.

Reusable artifact

Extension decision record

A reusable design record for choosing, securing, and maintaining Codex extensions.

User job: [repeatable outcome]
Why ordinary prompting or AGENTS.md is insufficient: [reason]
Skill responsibility: [judgment and workflow]
Plugin responsibility: [bundle and audience, or not yet justified]
MCP responsibility: [live data/actions and scopes]
Hook responsibility: [deterministic policy and lifecycle event]
Authentication and secret storage: [approved route]
Read/draft/write boundary: [per tool]
Failure behavior: [clear and safe response]
Tests: [trigger, non-trigger, tool, hook, end-to-end]
Owner and review date: [person/team + date]
Uninstall and data cleanup: [procedure]

Before you move on

Receipt checklist

  • The extension design begins from a proven user job rather than a preferred mechanism.
  • The skill activates for intended tasks and stays out of unrelated work.
  • Every MCP tool and its read, draft, or write authority is inventoried.
  • The hook passes allowed cases and blocks an intentional prohibited case with an actionable message.
  • No token, secret, or sensitive payload appears in source, config committed to Git, logs, or artifacts.

Primary sources

Continue with the source

Home
Book
Blog
Calugaru Labs
GitHub
LinkedIn
Email
Theme