Lesson 7 of 8IntermediateΒ·18 min read

Hands-On: Build a Claude Code Plugin, an OpenAI App, and a Gemini Agent

Three walkthroughs, one weekend: package skills, commands and MCP servers into a Claude Code plugin; ship a ChatGPT app (and Codex plugin) on top of an MCP server; build and deploy a Gemini agent with ADK. Each ends with a shippable artifact.

After this lesson you can

  • βœ“Scaffold, test and distribute a Claude Code plugin from a git repo
  • βœ“Turn an MCP server into a ChatGPT app with a rendered UI, and reuse it as a Codex plugin
  • βœ“Build a Gemini ADK agent with tools and sessions, run it locally, and deploy it to Vertex AI Agent Engine

Three platforms, one mental model

Each ecosystem has a way to extend its agent surface with your own capabilities, and they rhyme. A Claude Code plugin is a git-distributable folder bundling skills, slash commands, sub-agents, hooks and MCP servers. An OpenAI app is an MCP server plus UI metadata that ChatGPT renders inline (and the same server plugs into Codex). A Gemini agent built with ADK is a Python or Java object graph of agents and tools with a local dev UI and a managed deployment target. In all three, MCP is the tool boundary and a markdown instructions file is the knowledge boundary.

Claude Code pluginOpenAI app / Codex pluginGemini ADK agent
What you shipA folder in a git repoAn MCP server (HTTP) + widget resourcesA Python/Java package (or container)
Manifest.claude-plugin/plugin.jsonServer metadata + tool _meta (openai/*)agent.py exposing root_agent; deployment config
Bundlesskills/, commands/, agents/, hooks/, .mcp.jsonTools, resources (HTML widgets), authLlmAgent tree, tools, callbacks, sessions
Install / run/plugin marketplace add Β· /plugin installDeveloper mode β†’ add connector by URLadk web (local) Β· adk deploy agent_engine
DistributionMarketplace JSON in any git repoApp submission / directory; org-internal by URLAgent Engine endpoint; A2A card

Part 1 β€” A Claude Code plugin

A plugin is the unit of distribution for everything Claude Code can be taught. Skills teach procedure; commands give users a /slash entry point; agents define sub-agents with their own prompt and tool allowlist; hooks run shell commands on lifecycle events (pre/post tool use, session start); .mcp.json attaches tool servers. Only the manifest is required β€” start with one skill and grow.

Testing loop: run claude --plugin-dir ./my-plugin to load it without installing, invoke the command, iterate. Distribution: add a marketplace.json to any git repo (your plugin repo can be its own marketplace), then teammates run /plugin marketplace add your-org/your-repo and /plugin install release-tools@your-org. Version the plugin in plugin.json and Claude Code handles updates.

A minimal Claude Code plugin: manifest, a skill, a slash command, a sub-agent, a hook and an MCP server.
text
release-tools/
β”œβ”€β”€ .claude-plugin/
β”‚   └── plugin.json
β”œβ”€β”€ skills/
β”‚   └── release-notes/
β”‚       └── SKILL.md              # from lesson 2 β€” unchanged
β”œβ”€β”€ commands/
β”‚   └── ship.md                   # becomes /release-tools:ship
β”œβ”€β”€ agents/
β”‚   └── changelog-reviewer.md     # a sub-agent
β”œβ”€β”€ hooks/
β”‚   └── hooks.json
└── .mcp.json

# .claude-plugin/plugin.json
{
  "name": "release-tools",
  "version": "1.0.0",
  "description": "Release notes, ship checklist and changelog review.",
  "author": { "name": "Your Team" }
}

# commands/ship.md
---
description: Run the ship checklist for the current branch
allowed-tools: Bash(git *), Read
---
Run the release checklist: confirm CI is green for $ARGUMENTS, load the
release-notes skill, draft notes from `git log main..HEAD`, and stop for
approval before tagging.

# agents/changelog-reviewer.md
---
name: changelog-reviewer
description: Reviews draft release notes for missing breaking changes. Use after drafting notes.
tools: Read, Grep
model: haiku
---
You review release notes against the diff. Flag any public API change not
listed under Breaking. Return a checklist, nothing else.

# hooks/hooks.json β€” block tagging without notes
{
  "hooks": {
    "PreToolUse": [{
      "matcher": "Bash",
      "hooks": [{ "type": "command",
                  "command": "${CLAUDE_PLUGIN_ROOT}/scripts/guard-tag.sh" }]
    }]
  }
}

# .mcp.json β€” tools the plugin brings with it
{ "mcpServers": { "orders": { "type": "http", "url": "https://mcp.example.com/orders" } } }

# Try it:      claude --plugin-dir ./release-tools
# Distribute:  add .claude-plugin/marketplace.json listing the plugin, push, then
#              /plugin marketplace add your-org/release-tools
#              /plugin install release-tools@your-org

Part 2 β€” An OpenAI app (and Codex plugin)

The original ChatGPT plugins (OpenAPI manifests, 2023) were retired. Today’s path is the Apps SDK: you build an MCP server, and ChatGPT becomes the client. Tools work as normal MCP tools; the addition is metadata under the openai/* namespace in each tool’s _meta that points at an HTML resource ChatGPT renders inline as a widget β€” a table of orders, a map, a form. The widget talks back to your server through the same MCP connection.

Development loop: run the server locally, expose it over HTTPS (ngrok or a tunnel), enable developer mode in ChatGPT, add the server as a connector by URL, and chat. Authentication for real users is OAuth 2.1 with dynamic client registration β€” the MCP standard β€” so the same server is installable in Codex, Claude and Gemini CLI with no code change. A Codex plugin wraps that server with skills and prompts for the coding harness, exactly as the Claude plugin does.

An MCP server that doubles as a ChatGPT app: one tool with a rendered widget. Python, FastMCP.
python
# The same server is a plain MCP server for Claude β€” the openai/* metadata
# is simply ignored. Attach it in Claude Code:
#
#   claude mcp add --transport http orders https://your-tunnel.example/mcp
#
# or in a plugin's .mcp.json (Part 1), or via the Messages API mcp_servers
# connector (lesson 5). The widget HTML is not rendered; the model uses the
# structured tool result, which is why the tool must return real data and
# not only a UI.

Part 3 β€” A Gemini agent with ADK

ADK is Google’s code-first agent framework: an LlmAgent has a model, instructions, tools and optional sub-agents; workflow agents (SequentialAgent, ParallelAgent, LoopAgent) compose them deterministically; a Runner executes an agent against a SessionService that persists state across turns. The killer feature for learning is adk web β€” a local UI that shows every event, tool call and state change in the loop.

Project layout is a package with an agent.py exporting root_agent. Run adk web from the parent directory and pick your agent. Deployment: adk deploy agent_engine pushes it to Vertex AI Agent Engine, which gives you a managed endpoint, sessions and memory. Expose it to other agents with the A2A card (to_a2a) when another team’s agent needs to call yours.

A Gemini ADK agent with a tool, a memory-backed session and a deploy command. Compared with the equivalent Claude Agent SDK and OpenAI Agents SDK scaffolds.
python
# pip install claude-agent-sdk
import asyncio
from claude_agent_sdk import (ClaudeSDKClient, ClaudeAgentOptions,
                              tool, create_sdk_mcp_server)

@tool("order_status", "Look up the shipping status of an order by ID (ORD-123456).",
      {"order_id": str})
async def order_status(args):
    status = lookup(args["order_id"])               # your data layer
    return {"content": [{"type": "text", "text": f"{args['order_id']}: {status}"}]}

tools_server = create_sdk_mcp_server("support", tools=[order_status])

options = ClaudeAgentOptions(
    system_prompt="You are a support agent. Use order_status for any order query.",
    mcp_servers={"support": tools_server},
    allowed_tools=["mcp__support__order_status"],
    model="claude-sonnet-4-5",
)

async def main():
    async with ClaudeSDKClient(options=options) as client:   # one session, many turns
        await client.query("Where is ORD-482913?")
        async for m in client.receive_response(): print(m)
        await client.query("And has that order been paid?")  # context carries over
        async for m in client.receive_response(): print(m)

asyncio.run(main())

# Hosting: run this anywhere Python runs, or use Anthropic's Managed Agents
# for a server-hosted sandboxed runtime.

What to build this weekend

Pick one internal procedure your team repeats β€” release notes, on-call triage, a data-quality check. Write it as a SKILL.md. Wrap the systems it touches in one MCP server. Then package it three ways: a Claude Code plugin, a ChatGPT app / Codex plugin, and an ADK agent with a Gemini CLI extension. You will end with one skill, one server and three thin wrappers β€” and a very concrete answer to every "have you built agents?" interview question.

  • β€’Day 1: SKILL.md + MCP server with two tools; test in Claude Code with --plugin-dir and in Gemini CLI via settings.json
  • β€’Day 2 morning: plugin manifest + marketplace.json; ChatGPT developer-mode connector with one widget
  • β€’Day 2 afternoon: ADK agent using the same server via MCPToolset; adk web, then adk deploy agent_engine
  • β€’Write down what differed between the three β€” that list is your interview story

Interview questions this lesson prepares you for

  1. What goes into a Claude Code plugin, and how does it get to your teammates’ machines?
  2. How does a ChatGPT app differ from an ordinary MCP server? What is the role of _meta?
  3. Describe the ADK object model: agents, tools, runner, session service. Where does state live?
  4. You built a tool server once. How do you make it usable from all three ecosystems?