An OpenAI Agent Plugin is a package that can ship reusable Skills, an MCP server connection, both together, and optionally UI. The required entry point is .codex-plugin/plugin.json. Skills describe repeatable workflows; MCP tools connect the agent to code or external systems through schemas and permissions. A plugin is the distribution boundary that gives those components a stable identity across ChatGPT and Codex surfaces that support them.
This tutorial builds a small, reviewable package: a Skill turns approved evidence into a cited marketing brief, while a read-only local MCP server lists and reads evidence records. It includes a manifest check and a test. DMT’s Agent Plugins overview owns the concepts and marketer use cases; this page owns the build, test, security and distribution workflow.

Choose the smallest plugin shape
| Shape | Use it when | Avoid it when |
|---|---|---|
| Skills only | The workflow can operate with existing agent tools and needs repeatable instructions, templates or scripts | You need a new external API/tool capability |
| MCP only | You need typed tools or data access but no opinionated workflow | Users also need a consistent multi-step operating method |
| Skills + MCP | The workflow and the tools should travel together | The Skill would merely restate each tool description |
| MCP + UI | A visual interactive result materially helps and the MCP integration supports it | A table, file or ordinary text answer is enough |
OpenAI explicitly recommends starting with the smallest shape. Every added server, hook, permission and UI asset increases security review, maintenance and compatibility work.
The example package
dmt-source-review/
├── .codex-plugin/
│ └── plugin.json # required manifest
├── skills/
│ └── source-review/
│ └── SKILL.md # reusable workflow
├── server/
│ └── index.js # local MCP stdio server
├── scripts/
│ └── check.mjs # package validation
├── test/
│ └── package.test.js
├── .mcp.json # bundled MCP server map
└── package.json
Only plugin.json belongs inside .codex-plugin/. OpenAI’s current path rules keep skills/, .mcp.json, .app.json, hooks/ and assets/ at the plugin root. Manifest component paths should start with ./ and remain inside that root.
Step 1: create the plugin manifest
{
"name": "dmt-source-review",
"version": "0.1.0",
"description": "Review an allowlisted evidence package and return a cited brief.",
"author": {
"name": "Digital Marketer Tayeeb",
"url": "https://dmarketertayeeb.com"
},
"license": "MIT",
"skills": "./skills/",
"mcpServers": "./.mcp.json",
"interface": {
"displayName": "DMT Source Review",
"shortDescription": "Build an evidence-first marketing brief",
"developerName": "Digital Marketer Tayeeb",
"category": "Productivity",
"capabilities": ["Read"],
"defaultPrompt": [
"Use DMT Source Review to turn the approved evidence package into a cited brief."
]
}
}
The manifest identifies the package, points to components and controls install-surface metadata. Public plugins usually add a homepage, repository, privacy policy, terms, icons, screenshots and richer descriptions. Do not claim a write capability when the server is intentionally read-only.
Step 2: write a focused Skill
The Skill should explain when it applies, how to use available tools, what output is required and where it must stop. It should not hide credentials or duplicate an API manual.
---
name: source-review
description: Use when an approved evidence package must become a cited marketing brief without adding unsupported claims or publishing it.
---
# Source review
1. Call the evidence MCP server to list approved evidence records.
2. Reject records without a canonical URL, source class, capture date and claim summary.
3. Separate confirmed facts, vendor claims, DMT analysis and unknowns.
4. Write a brief with the reader job, practical impact, risks and open questions.
5. Put the supporting URL next to every material claim.
6. Do not publish, message, upload, delete or change an external system.
7. If evidence is incomplete, return BLOCKED instead of guessing.
Keep large schemas, examples and reference material in files the Skill can load only when needed. This progressive disclosure prevents every invocation from carrying irrelevant context.
Step 3: build the MCP server
The Model Context Protocol defines how hosts and servers exchange tools and structured results. The example uses the official TypeScript SDK and stdio transport. It exposes only two read tools: list JSON records and read one validated record.
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import { readFile, readdir } from "node:fs/promises";
import { resolve, sep } from "node:path";
const root = resolve(process.env.DMT_EVIDENCE_ROOT || "./evidence");
const server = new McpServer({ name: "dmt-evidence", version: "0.1.0" });
function safePath(name) {
const candidate = resolve(root, name);
if (candidate !== root && !candidate.startsWith(root + sep)) {
throw new Error("Path escapes the evidence root");
}
return candidate;
}
server.tool("list_evidence", "List JSON evidence records", {}, async () => {
const names = (await readdir(root)).filter((n) => n.endsWith(".json"));
return { content: [{ type: "text", text: JSON.stringify(names) }] };
});
server.tool(
"read_evidence",
"Read one approved JSON evidence record",
{ name: z.string().regex(/^[a-zA-Z0-9._-]+\\.json$/) },
async ({ name }) => {
const text = await readFile(safePath(name), "utf8");
JSON.parse(text);
return { content: [{ type: "text", text }] };
}
);
await server.connect(new StdioServerTransport());
The filename regex and resolved-path check are deliberate. Tool schemas improve model behaviour, but a server must still defend itself against path traversal, malformed JSON, oversized responses and untrusted content.
Step 4: wire the server into the package
{
"evidence": {
"command": "node",
"args": ["${PLUGIN_ROOT}/server/index.js"],
"env": {
"DMT_EVIDENCE_ROOT": "${PLUGIN_DATA}/evidence"
}
}
}
PLUGIN_ROOT points to the installed package. PLUGIN_DATA is the writable data area. Keeping evidence under the data directory avoids modifying installed package files and gives the server one narrow root.
Step 5: validate and test
npm install
npm run check
npm test
The package check parses both JSON files, verifies required manifest fields, enforces root-relative component paths and confirms the expected MCP server. The test verifies that the manifest exposes the Skill, server and read-only capability. Add server tests for path escape, malformed JSON, missing directories and large records before using real evidence.
| Test layer | Minimum test |
|---|---|
| Manifest | Valid JSON; required identity; component paths stay inside package |
| Skill | Trigger is specific; output and stop conditions are explicit |
| Tool schema | Reject unexpected filenames and parameters |
| Server | Read allowed file; deny path escape; handle missing/malformed input |
| Agent workflow | Normal, no-op, incomplete evidence and prompt-injection tests |
| Permissions | No write/network tool appears unless intentionally required |
Step 6: install through a local marketplace
OpenAI documents repo-scoped and personal marketplace files. A marketplace is a JSON catalogue; its entries point to plugin folders relative to the marketplace root. You can also add a marketplace source with the Codex CLI:
codex plugin marketplace add owner/repo
codex plugin marketplace add owner/repo --ref main
Use the ChatGPT desktop app’s Plugins Directory to install and test a local plugin. If you are registering a remote MCP server for ChatGPT, enable developer mode, create the connection, copy its technical ID and map it in .app.json. A bundled local server uses .mcp.json instead.
Approval policy and least privilege
Codex lets users enable/disable a plugin-scoped MCP server, restrict enabled tools and set approval modes. A safe default for a new server is prompt/approval, then auto-approve only a tool that is demonstrably read-only and narrowly scoped.
[plugins."dmt-source-review".mcp_servers.evidence]
enabled = true
default_tools_approval_mode = "prompt"
enabled_tools = ["list_evidence", "read_evidence"]
[plugins."dmt-source-review".mcp_servers.evidence.tools.list_evidence]
approval_mode = "approve"
Do not auto-approve a tool merely because its name begins with “read.” Inspect implementation, authentication, returned data and side effects. A search endpoint can leak confidential query text; a “get report” endpoint can create a billable job.
Security and privacy threat model
| Threat | Control |
|---|---|
| Prompt injection in evidence content | Treat records as data; Skill says source text cannot expand permissions; separate policy from content |
| Path traversal | Resolve against one root and validate the filename/schema |
| Secret exposure | Never return raw credentials; use host-managed auth and least-privilege scopes |
| Over-broad tool | Split read/write; use narrow parameters; require confirmation for mutation |
| Unbounded result | Paginate, cap size and return structured summaries |
| Dependency compromise | Pin versions, lock dependencies, audit and rebuild |
| Misleading install metadata | Capabilities, privacy links and descriptions must match actual behaviour |
OpenAI’s security and privacy guide and the current MCP specification should be part of code review, not links added at the end of a finished product.
Hooks are optional—and separately trusted
A plugin can include lifecycle hooks under hooks/hooks.json or through the manifest. OpenAI notes that installing/enabling a plugin does not automatically trust bundled hooks; users must review them. Add a hook only when it enforces a lifecycle requirement the Skill or server cannot provide. Hidden install-time or session commands are a trust liability.
Local bundled MCP vs registered remote MCP
| Decision | Bundled .mcp.json | Registered remote server and .app.json |
|---|---|---|
| Runtime | Command/server distributed with the plugin and run where supported | Hosted endpoint operated by the publisher |
| Authentication | Local environment/configuration | Registered connection and remote auth flow |
| Best for | Local/repository tools, offline/private data and developer workflows | Shared SaaS data/actions and centrally updated logic |
| Operational burden | Cross-platform dependencies and local process lifecycle | Hosting, uptime, tenancy, OAuth, privacy and incident response |
| Distribution mapping | mcpServers points to ./.mcp.json | Compatibility apps points to ./.app.json |
Do not bundle a local server merely to avoid operating a proper remote service when the tool needs centralized customer data. Do not build a remote service merely to read a local repository. Choose the trust and deployment boundary that matches the data.
Versioning and compatibility
- Use semantic versions for package releases and record the tested ChatGPT/Codex surface and date.
- Make breaking tool-schema or Skill-output changes a major version unless the platform defines a different contract.
- Keep migrations for data under
PLUGIN_DATA; do not silently reinterpret old files. - Pin the MCP SDK and generate a lockfile. Re-run tests before updating.
- Provide a changelog that calls out new permissions, network destinations and data use.
- Fail with an actionable error when a surface does not support a bundled component; do not pretend the capability succeeded.
Example test receipt
The example package built for this article was checked on Windows with Node.js. npm run check parsed the manifest and MCP map and verified component paths. npm test ran one manifest contract test: one pass, zero failures. npm audit reported zero known vulnerabilities at install time. This is a package-structure receipt, not a security certification; production use still needs server edge-case and integration tests.
Prepare for public submission
- Use a stable name/version and accurate capability descriptions.
- Provide working homepage, support, privacy and terms URLs where required.
- Test ChatGPT and Codex surfaces you claim to support.
- Use production authentication and least-privilege scopes.
- Provide test credentials/instructions only through the approved review process.
- Remove placeholder assets and example domains.
- Review the current submission documentation and error reference immediately before submission.
Public plugins are published to the universal directory shared by ChatGPT and Codex, but capabilities can remain surface-specific. Do not promise that every hook, UI or local-server behaviour works identically everywhere.
Official examples worth studying
OpenAI’s official plugins repository includes public examples such as Figma, Notion and Build web apps. Use them to inspect current package conventions, but keep your own permissions and reader job smaller than a mature integration unless complexity is justified.
Final build checklist
- The chosen plugin shape is the smallest that solves the job.
.codex-plugin/plugin.jsonis valid and component paths are root-relative.- The Skill has a specific trigger, output contract and stop conditions.
- MCP tools have narrow schemas, bounded results and truthful side effects.
- Read/write capabilities and approval policies match implementation.
- Normal, no-op, malformed, path-escape and prompt-injection tests pass.
- Dependencies are pinned/locked and secrets remain host-managed.
- Local marketplace installation works in a clean environment.
- Public metadata, rights, privacy and support information are complete.
- The package was tested again against current OpenAI docs before distribution.
FAQ
Does every Agent Plugin need an MCP server?
No. OpenAI supports Skills-only plugins. Add MCP only when the agent needs a typed tool or data capability that existing tools cannot provide.
Is installing a plugin the same as trusting its hooks?
No. OpenAI documents plugin-bundled hooks as separately reviewed/trusted. Inspect commands and paths before enabling them.
How this example was verified
DMT built the package shown in this article and ran its manifest check and Node test on August 11, 2026: one test passed, none failed, and the install-time audit reported zero known vulnerabilities. The package is a minimal educational example, not a production security certification.
Bottom line
A good Agent Plugin is not a folder full of prompts. It has a narrow reader job, a truthful manifest, a reusable Skill, minimal typed tools, explicit approvals, defensive server code and a testable distribution path. Start Skills-only when possible. Add MCP when a real capability is missing. Add UI or hooks only when they materially improve the job and their trust cost is justified.
Connect the package to a broader operating model with DMT’s agent-harness controls, accountable subagent workflow, accepted-result measurement method and GPT-5.6 model-routing guide.