openai-mcp-server
# openai-mcp-server
An [MCP](https://modelcontextprotocol.io) server that puts the OpenAI API into any MCP client — Claude Desktop, Claude Code, Cowork, Cursor, or anything else that speaks the protocol.
Nine tools: text generation, chat completions, model discovery, image generation and editing, transcription, speech synthesis, embeddings, and moderation.
## Why this exists
There is no official OpenAI plugin in the Claude plugin catalogue. This server is the equivalent, built as a normal open-source project you own and can extend.
## Tools
| Tool | What it does | Read-only |
| --- | --- | --- |
| `openai_generate_text` | Generate text via the Responses API — instructions, reasoning effort, forced JSON, response chaining | no |
| `openai_chat_completion` | Send an explicit message history via Chat Completions | no |
| `openai_list_models` | List the model IDs your key can use, filtered and paginated | yes |
| `openai_generate_image` | Create images from a prompt, written to disk | no |
| `openai_edit_image` | Edit or combine existing images, optionally with a mask | no |
| `openai_transcribe_audio` | Transcribe a local audio file | no |
| `openai_text_to_speech` | Synthesize speech to an audio file | no |
| `openai_create_embeddings` | Embed texts for semantic search, written to JSON | no |
| `openai_moderate_content` | Check text against OpenAI's moderation policy | yes |
Every tool takes `response_format: "markdown" | "json"` — markdown for reading, JSON for processing. All tools also return `structuredContent`, so clients that understand output schemas get typed data without parsing.
## Install
### As a Claude Desktop extension (easiest)
Download `openai-mcp-server-<version>.mcpb` from the [releases page](https://github.com/piorkowskim79/openai-mcp-server/releases), double-click it, and paste your API key into the field Claude Desktop shows. Nothing else to install — the bundle carries its dependencies and runs on Claude Desktop's own Node runtime, so the machine needs no Node, no toolchain and no hand-edited config file.
The same dialog also offers the output folder, the folders the server may read from, a default text model and an alternative API base URL. All of them are optional.
To build the bundle yourself:
```bash
npm install
npm run pack:mcpb # writes build/openai-mcp-server-<version>.mcpb
```
### From source
For Claude Code, Cursor or any other MCP client:
```bash
git clone https://github.com/piorkowskim79/openai-mcp-server.git
cd openai-mcp-server
npm install
npm run build
```
Requires Node.js 20 or newer and an OpenAI API key with available quota.
Verify the build:
```bash
node dist/index.js --version # prints 1.0.0
node dist/index.js --help # lists all environment variables
```
## Configure your MCP client
Skip this section if you installed the `.mcpb` extension — Claude Desktop wires it up for you.
The server speaks MCP over **stdio**, so the client launches it as a subprocess.
### Claude Desktop
Edit `claude_desktop_config.json`:
- macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
- Windows: `%APPDATA%\Claude\claude_desktop_config.json`
```json
{
"mcpServers": {
"openai": {
"command": "node",
"args": ["/absolute/path/to/openai-mcp-server/dist/index.js"],
"env": {
"OPENAI_API_KEY": "sk-proj-...",
"OPENAI_MCP_OUTPUT_DIR": "/Users/you/openai-mcp-output"
}
}
}
}
```
Restart Claude Desktop afterwards.
### Claude Code
```bash
claude mcp add openai \
--env OPENAI_API_KEY=sk-proj-... \
-- node /absolute/path/to/openai-mcp-server/dist/index.js
```
### Any other MCP client
Point it at `node /absolute/path/to/dist/index.js` with `OPENAI_API_KEY` in the environment.
## Configuration
Only `OPENAI_API_KEY` is required. See `.env.example` for a copyable template.
| Variable | Default | Purpose |
| --- | --- | --- |
| `OPENAI_API_KEY` | — | **Required.** Your OpenAI API key |
| `OPENAI_BASE_URL` | OpenAI's default | Alternative endpoint (Azure, gateway, proxy) |
| `OPENAI_ORG_ID` | — | Organization ID |
| `OPENAI_PROJECT_ID` | — | Project ID |
| `OPENAI_MCP_OUTPUT_DIR` | `<tmp>/openai-mcp` | Where generated files are written |
| `OPENAI_MCP_ALLOWED_DIRS` | output dir only | Colon-separated absolute dirs the server may **read** from |
| `OPENAI_MCP_TIMEOUT_MS` | `120000` | Per-request timeout |
| `OPENAI_MCP_MAX_RETRIES` | `2` | Retries for transient failures |
| `OPENAI_DEFAULT_TEXT_MODEL` | `gpt-5.6-terra` | Default text model |
| `OPENAI_DEFAULT_IMAGE_MODEL` | `gpt-image-2` | Default image model |
| `OPENAI_DEFAULT_EMBEDDING_MODEL` | `text-embedding-3-small` | Default embedding model |
| `OPENAI_DEFAULT_TRANSCRIPTION_MODEL` | `gpt-transcribe` | Default transcription model |
| `OPENAI_DEFAULT_SPEECH_MODEL` | `gpt-4o-mini-tts` | Default speech model |
| `OPENAI_DEFAULT_MODERATION_MODEL` | `omni-moderation-latest` | Default moderation model |
Any **positional argument** is also treated as a readable directory and merged into `OPENAI_MCP_ALLOWED_DIRS`:
```bash
node dist/index.js /Users/you/Documents/audio /Users/you/Pictures
```
This exists because a bundle manifest expands a multi-value directory setting into several argv entries; a single colon-separated environment variable cannot express that list unambiguously. Arguments must be absolute paths.
**Model IDs change.** OpenAI adds, renames and retires models, and access differs per project. Every default is overridable, and `openai_list_models` reports what your key can actually reach — if a call fails with "model not found", start there.
## Security model
Two deliberate constraints:
**The filesystem is sandboxed.** Tools that read local files (`openai_edit_image`, `openai_transcribe_audio`) accept only absolute paths inside `OPENAI_MCP_ALLOWED_DIRS`. Paths are canonicalised with `realpath` before the check, so symlinks and `../` traversal cannot escape. The output directory is always allowed; nothing else is, until you add it. Keep that list narrow.
**Binary output never enters the conversation.** Images, audio and embedding vectors are written to disk and only their paths are returned. A single base64 PNG or a 3072-float vector would otherwise flood the model's context window.
The API key is read from the environment only — it never appears in a tool argument, a log line, or an error message.
## Examples
Ask your MCP client in plain language; it picks the tool.
> "Use the OpenAI server to summarise this text in three sentences."
→ `openai_generate_text`
> "Which OpenAI embedding models can I use?"
→ `openai_list_models` with `filter="embedding"`
> "Generate a transparent PNG logo of a blue fox."
→ `openai_generate_image` with `background="transparent"`
> "Transcribe ~/Documents/audio/interview.m4a in German."
→ `openai_transcribe_audio` with `language="de"` — requires that directory in `OPENAI_MCP_ALLOWED_DIRS`
> "Embed these 40 product descriptions so I can cluster them."
→ `openai_create_embeddings`, then read the JSON file it reports
## Development
```bash
npm run dev # watch mode via tsx
npm run typecheck # tsc --noEmit, strict
npm test # unit tests, no network calls
npm run build # compile to dist/
npm run pack:mcpb # build the Claude Desktop extension bundle
```
The test suite covers configuration parsing, the filesystem sandbox (including symlink escape and traversal), error formatting and response shaping. It never contacts the OpenAI API.
### Project layout
```
manifest.json Claude Desktop extension manifest (tools, user config)
scripts/
└── pack-mcpb.sh builds the .mcpb bundle
src/
├── index.ts entry point, server assembly, CLI flags
├── config.ts environment parsing and validation
├── client.ts OpenAI client construction
├── constants.ts defaults, limits, response formats
├── errors.ts API errors → actionable agent messages
├── files.ts sandboxed read/write
├── format.ts tool result shaping, character limit
└── tools/
├── text.ts generate_text, chat_completion
├── models.ts list_models
├── images.ts generate_image, edit_image
├── audio.ts transcribe_audio, text_to_speech
└── analysis.ts create_embeddings, moderate_content
```
### Adding a tool
1. Write a Zod schema with `.strict()` and a `.describe()` on every field.
2. Register it with `server.registerTool(name, config, handler)` — include `title`, `description`, `inputSchema`, `outputSchema` and `annotations`.
3. Return via `toolResult(...)` so markdown/JSON handling and the character limit stay consistent; catch errors with `errorResult(...)`.
4. Add the registration call in `src/index.ts` and a test in `test/`.
## Troubleshooting
| Symptom | Cause |
| --- | --- |
| Client shows no tools | Wrong path in the config, or the project was not built (`npm run build`) |
| `Configuration error: OPENAI_API_KEY is not set` (exit 78) | The key is missing from the client's `env` block |
| `Error: Access to ... is not permitted` | The path is outside `OPENAI_MCP_ALLOWED_DIRS` |
| `Error: Not found` on a generation | The model ID does not exist for your key — run `openai_list_models` |
| `Error: Rate limit or quota exceeded` | Retry later, or check billing on the project |
The server logs to stderr; stdout carries the JSON-RPC stream and must stay clean.
## License
MIT — see [LICENSE](LICENSE).
TDQS
Scored across 9 tools
Each tool maps to a distinct OpenAI capability (text, chat history, images, audio, embeddings, moderation, model discovery). openai_generate_text and openai_chat_completion are the only potentially confusable pair, but their descriptions clearly separate single-prompt/chaining from explicit message histories.
All tools share an openai_ prefix and snake_case, with mostly verb_noun names like generate_text, edit_image, list_models. openai_chat_completion and openai_text_to_speech break the verb_noun pattern slightly because they mirror API endpoint names, but the convention remains predictable.
9 tools is appropriate for an OpenAI API surface: one tool per major modality (text, image, audio, embeddings, moderation) plus model discovery. No tool feels redundant or missing at the count level.
The set covers text generation, chat completions, image generation/editing, transcription, speech synthesis, embeddings, and moderation, which are the core OpenAI workflows. Minor gaps exist—notably no vision/analysis of image inputs and no fine-tuning/batch management—but agents can accomplish typical tasks without dead ends.