# Server-Side Implementation for an OpenAI Plugin: Architecture and Code Examples

> Learn the server-side implementation for an OpenAI plugin using a manifest, OpenAPI spec, and skill scripts. Explore architecture and code examples from the official openai/plugins repository.

- Repository: [OpenAI/plugins](https://github.com/openai/plugins)
- Tags: architecture
- Published: 2026-06-26

---

**The server-side implementation for an OpenAI plugin requires three core artifacts: a JSON manifest located at [`.codex-plugin/plugin.json`](https://github.com/openai/plugins/blob/main/.codex-plugin/plugin.json), an OpenAPI specification defining your API contract, and executable skill scripts that handle incoming HTTP requests.**

The `openai/plugins` repository provides reference implementations showing how to build a lightweight, modular server that exposes plugin capabilities to the OpenAI platform. This architecture separates the plugin declaration from the business logic, allowing the platform to discover and invoke your endpoints through a standardized protocol.

## Core Server Components

### The Plugin Manifest ([`.codex-plugin/plugin.json`](https://github.com/openai/plugins/blob/main/.codex-plugin/plugin.json))

Every OpenAI plugin server must serve a manifest file at the [`.codex-plugin/plugin.json`](https://github.com/openai/plugins/blob/main/.codex-plugin/plugin.json) path. This JSON file declares the plugin's identity, required authentication scopes, and the location of the OpenAPI specification.

In the Zoom plugin example located at [`plugins/zoom/.codex-plugin/plugin.json`](https://github.com/openai/plugins/blob/main/plugins/zoom/.codex-plugin/plugin.json), the manifest references the API specification and defines available skills. The evaluation harness in [`plugins/plugin-eval/src/evaluators/plugin.js`](https://github.com/openai/plugins/blob/main/plugins/plugin-eval/src/evaluators/plugin.js) validates this structure by reading the manifest and checking for required fields:

```javascript
const manifestPath = path.join(pluginRoot, ".codex-plugin", "plugin.json");
// Validation logic checks manifest fields and discovers skill directories
const skillDirs = await discoverPluginSkillDirectories(pluginRoot, manifest);

```

Key manifest fields include:
- **name** and **description** – Human-readable plugin metadata
- **api.spec** – Path to the OpenAPI YAML file (e.g., `"openapi.yaml"`)
- **auth.type** – Authentication flow (e.g., `"oauth"`)
- **skills** – Array of available capabilities

### The OpenAPI Specification

The server must serve an OpenAPI document (typically [`openapi.yaml`](https://github.com/openai/plugins/blob/main/openapi.yaml) or [`openapi.json`](https://github.com/openai/plugins/blob/main/openapi.json)) that formally describes every HTTP endpoint the plugin exposes. The Zoom plugin stores this at [`plugins/zoom/openapi.yaml`](https://github.com/openai/plugins/blob/main/plugins/zoom/openapi.yaml), referenced from the manifest's `api.spec` field.

This specification allows the OpenAI platform to understand:
- Available routes and HTTP methods
- Request/response schemas
- Required parameters and authentication headers

### Skill Scripts and Runtime Code

The actual business logic lives in **skill scripts** – modular, language-agnostic handlers that the server invokes based on the request path. The repository organizes these under `skills/{skill_name}/scripts/`.

For example, the Zoom *scribe* skill implements its handler at `plugins/zoom/skills/scribe/scripts/handler.mjs`. Authentication logic similarly resides in dedicated scripts like `plugins/zoom/skills/auth/scripts/oauth.mjs`.

These scripts process the HTTP request, interact with external APIs, and return JSON responses conforming to the OpenAPI schema.

## How the Server Processes Requests

The server implementation follows a specific lifecycle to bridge the OpenAI platform with your custom logic:

1. **Manifest Loading** – On startup, the server reads [`.codex-plugin/plugin.json`](https://github.com/openai/plugins/blob/main/.codex-plugin/plugin.json) to determine available capabilities and authentication requirements.

2. **OpenAPI Serving** – The server exposes the OpenAPI specification at the URL declared in the manifest, typically at [`/openapi.yaml`](https://github.com/openai/plugins/blob/main//openapi.yaml) or the root path.

3. **Dynamic Routing** – Incoming requests are routed to the appropriate skill script based on the URL path. The generic server framework maps OpenAPI operations to the corresponding files in the `skills/` directory.

4. **Authentication Handling** – If the manifest specifies `"auth": { "type": "oauth" }`, the server implements `GET /auth` for the initial redirect and `POST /token` for token exchange, as demonstrated in the Zoom plugin's OAuth scripts.

5. **Response Serialization** – Skill scripts return JSON payloads that the server formats according to the OpenAPI schema before sending back to the OpenAI runtime.

## Complete Server Implementation Examples

Below are production-ready skeleton implementations following the patterns found in the `openai/plugins` repository.

### Node.js with Express

This implementation loads the manifest, serves the OpenAPI spec, and dynamically routes requests to skill scripts:

```javascript
// server.js
import express from "express";
import fs from "fs";
import path from "path";

const app = express();
app.use(express.json());

// Load the plugin manifest from .codex-plugin/plugin.json
const manifest = JSON.parse(
  fs.readFileSync(path.join(process.cwd(), ".codex-plugin", "plugin.json"), "utf8")
);

// Serve the OpenAPI specification referenced in the manifest
app.get("/openapi.yaml", (req, res) => {
  const specPath = path.join(process.cwd(), manifest.api?.spec || "openapi.yaml");
  res.type("yaml").send(fs.readFileSync(specPath, "utf8"));
});

// Generic route handler that forwards to skill scripts
app.all("*", async (req, res) => {
  const skillPath = req.path.replace(/^\//, "");
  const scriptPath = path.join(process.cwd(), "skills", skillPath, "scripts", "handler.mjs");
  
  try {
    const { default: handler } = await import(`file://${scriptPath}`);
    const result = await handler(req);
    res.json(result);
  } catch (error) {
    res.status(404).json({ error: "Skill not found" });
  }
});

// OAuth endpoints (implemented only if manifest requires auth)
if (manifest.auth?.type === "oauth") {
  app.get("/auth", (req, res) => {
    // Redirect to OAuth provider authorization URL
  });
  
  app.post("/token", (req, res) => {
    // Exchange authorization code for access token
  });
}

app.listen(3000, () => console.log("Plugin server listening on port 3000"));

```

### Python with FastAPI

This FastAPI implementation provides equivalent functionality using Python's async framework:

```python

# server.py

import json
from pathlib import Path
from fastapi import FastAPI, Request, HTTPException
from fastapi.responses import JSONResponse, PlainTextResponse
import importlib.util

app = FastAPI()

# Load plugin manifest from .codex-plugin/plugin.json

manifest_path = Path(__file__).parent / ".codex-plugin" / "plugin.json"
manifest = json.loads(manifest_path.read_text())

# Serve OpenAPI specification

@app.get("/openapi.yaml")
def openapi():
    spec_path = Path(__file__).parent / manifest.get("api", {}).get("spec", "openapi.yaml")
    return PlainTextResponse(spec_path.read_text(), media_type="application/yaml")

# Generic route handler for all skill endpoints

@app.api_route("/{full_path:path}", methods=["GET", "POST", "PUT", "DELETE"])
async def skill_handler(request: Request, full_path: str):
    script_path = Path(__file__).parent / "skills" / full_path / "scripts" / "handler.py"
    
    if not script_path.exists():
        raise HTTPException(status_code=404, detail="Skill not found")
    
    # Dynamically import and execute the skill handler

    spec = importlib.util.spec_from_file_location("handler", script_path)
    module = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(module)
    
    result = await module.handle(request)
    return JSONResponse(result)

# OAuth endpoints (conditional on manifest configuration)

if manifest.get("auth", {}).get("type") == "oauth":
    @app.get("/auth")
    async def authorize():
        # Handle OAuth authorization redirect

        pass
    
    @app.post("/token")
    async def token_exchange():
        # Handle authorization code exchange

        pass

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)

```

## Summary

The server-side implementation for an OpenAI plugin centers on three interconnected components:

- **[`.codex-plugin/plugin.json`](https://github.com/openai/plugins/blob/main/.codex-plugin/plugin.json)** – The manifest that declares your plugin's capabilities, authentication requirements, and API specification location
- **OpenAPI specification** – The formal contract describing available endpoints and data schemas, typically served as [`openapi.yaml`](https://github.com/openai/plugins/blob/main/openapi.yaml)
- **Skill scripts** – Modular handler files (e.g., `plugins/zoom/skills/scribe/scripts/handler.mjs`) containing the actual implementation logic

The generic server framework loads the manifest, exposes the OpenAPI document for platform discovery, routes requests to the appropriate skill scripts, and handles OAuth flows when configured.

## Frequently Asked Questions

### What are the minimum required files for an OpenAI plugin server?

You need at least three files: a **manifest** at [`.codex-plugin/plugin.json`](https://github.com/openai/plugins/blob/main/.codex-plugin/plugin.json) declaring the plugin metadata, an **OpenAPI specification** (referenced by the manifest) describing your endpoints, and a **skill script** implementing the logic for at least one endpoint. The evaluation harness in [`plugins/plugin-eval/src/evaluators/plugin.js`](https://github.com/openai/plugins/blob/main/plugins/plugin-eval/src/evaluators/plugin.js) validates that these files exist and that the manifest references valid skill directories.

### How does the server handle authentication with the OpenAI platform?

If the manifest specifies `"auth": { "type": "oauth" }`, your server must implement two specific endpoints: `GET /auth` to initiate the OAuth flow and redirect to the provider, and `POST /token` to exchange the authorization code for an access token. The Zoom plugin example demonstrates this pattern in `plugins/zoom/skills/auth/scripts/oauth.mjs`.

### Can I build the server in any programming language?

Yes. While the `openai/plugins` repository provides examples in **Node.js** and **Python**, the protocol only requires that your server serves valid JSON manifests, YAML/JSON OpenAPI specifications, and handles HTTP requests according to that specification. The skill scripts can be written in any language, provided the server can execute them and return JSON responses.

### How does request routing work between the OpenAI platform and skill scripts?

The OpenAI platform reads your OpenAPI specification to determine available paths, then sends requests to your server's corresponding URLs. Your server implementation must map these incoming requests to the correct skill scripts in the `skills/{name}/scripts/` directory. The generic handlers in the repository examples demonstrate dynamic routing based on the request path, loading the appropriate handler module (e.g., `handler.mjs` or [`handler.py`](https://github.com/openai/plugins/blob/main/handler.py)) at runtime.