mcp-openai-platform
by cyclomax-x
README.md
# mcp-openai-platform
A local MCP (Model Context Protocol) server exposing the OpenAI platform REST API as tools — files, fine-tuning, inference, images, audio, batch, and organization usage/costs.
Built for this project after doing a full fine-tuning run by hand with `curl`; the tool descriptions encode the gotchas found along the way (see [Notable behaviours](#notable-behaviours)) so they don't have to be rediscovered.
## What this is (and isn't)
**This is:** a stdio MCP server that *your MCP client* (Claude Code, etc.) runs locally. The client calls these tools; the tools call OpenAI's REST API.
```
Claude Code (MCP client)
→ mcp-openai-platform (local stdio server)
→ https://api.openai.com/v1/...
```
**This is not:** a server for OpenAI's models to call. OpenAI's own docs on [MCP and connectors](https://developers.openai.com/api/docs/guides/tools-connectors-mcp) and [Secure MCP Tunnels](https://developers.openai.com/api/docs/guides/secure-mcp-tunnels) describe the opposite direction — the Responses API consuming a remote MCP server over the network, with OAuth and tunnels. None of that applies here; this is a plain local server and needs no OAuth, no tunnel, no public URL.
## Install
Requires **Node.js 18+** (uses built-in `fetch` and `FormData`; developed on Node 24).
```bash
cd mcp-openai
npm install
npm run build # compiles src/ → dist/
```
Verify the build produced `dist/index.js`:
```bash
ls dist/ # index.js, openai-client.js
```
The server is now runnable but does nothing on its own — it speaks MCP over stdio and needs a client to drive it. Register it using one of the methods below.
## Adding it to an agent / MCP client
The server is a stdio MCP server, so registration is the same shape everywhere: **command** = `node`, **args** = absolute path to `dist/index.js`, **env** = your API keys.
Use the **absolute path** — MCP clients don't reliably resolve relative paths, and the working directory at launch isn't guaranteed.
### Claude Code — CLI (quickest)
```bash
claude mcp add openai-platform \
--env OPENAI_API_KEY=sk-proj-... \
--env OPENAI_ADMIN_API_KEY=sk-admin-... \
-- node /absolute/path/to/mcp-openai/dist/index.js
```
On Windows, use the Windows-style absolute path:
```bash
claude mcp add openai-platform ^
--env OPENAI_API_KEY=sk-proj-... ^
-- node E:\wamp64\www\Ticketing_new\mcp-openai\dist\index.js
```
Add `--scope project` to write it into the project's `.mcp.json` (shared with the repo) instead of your personal config. Then:
```bash
claude mcp list # confirm it's registered
claude mcp get openai-platform
```
### Claude Code — project config file
Edit `.mcp.json` at the project root (this is what this project uses):
```json
{
"mcpServers": {
"openai-platform": {
"type": "stdio",
"command": "node",
"args": ["E:\\wamp64\\www\\Ticketing_new\\mcp-openai\\dist\\index.js"],
"env": {
"OPENAI_API_KEY": "sk-proj-...",
"OPENAI_ADMIN_API_KEY": "sk-admin-..."
}
}
}
}
```
Note the escaped backslashes — `.mcp.json` is JSON, so Windows paths need `\\`. Forward slashes also work on Windows if you prefer.
### Claude Desktop
Edit the config file directly, then fully quit and reopen the app:
- **Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
- **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
Same `mcpServers` block as above.
### Other MCP clients
Anything that speaks MCP over stdio works — Cursor, Windsurf, Zed, Continue, or a custom client built on the MCP SDK. The config key names vary slightly by app, but all of them need the same three things:
```
command: node
args: ["<absolute path>/mcp-openai/dist/index.js"]
env: { OPENAI_API_KEY: "...", OPENAI_ADMIN_API_KEY: "..." }
```
### Verifying it works
After registering, restart the client and ask it to list your fine-tuning jobs, or check the tool list directly. In Claude Code:
```
/mcp
```
You should see `openai-platform` as connected with 31 tools. If it shows as failed, see [Troubleshooting](#troubleshooting).
**Restart the client after every rebuild** — the server process is spawned at client startup, so a recompiled `dist/` won't be picked up until it relaunches.
## Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| Server shows "failed" / exits immediately | `OPENAI_API_KEY` not set | The server exits at startup without it. Check the `env` block spelling |
| `Cannot find module .../dist/index.js` | Not built, or wrong path | Run `npm run build`; use an absolute path in the config |
| Tools missing after a code change | Client still running the old process | Restart the MCP client |
| `403 Missing scopes: api.usage.read` | Using a project key for org tools | Set `OPENAI_ADMIN_API_KEY` to an Admin key — see [Authentication](#authentication--two-keys-two-tiers) |
| `429 insufficient_quota` on every call | Account balance/billing, not code | Check `platform.openai.com/settings/organization/billing` |
| Windows path errors in JSON | Unescaped backslashes | Use `\\` or forward slashes in `.mcp.json` |
### Why a local path instead of npm
Deliberate. Publishing would mean a version bump + republish for every tweak, and `npx -y <pkg>` re-resolves from the registry on each launch. A direct `node dist/index.js` path skips all of that, and the project-specific guidance baked into tool descriptions isn't meaningful to anyone else. If this ever needs to run on other machines, publishing is a small step to take then.
## Authentication — two keys, two tiers
| Env var | Used for | Where to get it |
|---|---|---|
| `OPENAI_API_KEY` | Files, fine-tuning, inference, images, audio, batch | Project API key, `platform.openai.com/api-keys` |
| `OPENAI_ADMIN_API_KEY` | `openai_get_org_costs`, `openai_get_org_usage*` | **Admin** key, `platform.openai.com/settings/organization/admin-keys`, needs the `api.usage.read` scope |
`OPENAI_API_KEY` is required — the server exits at startup without it. `OPENAI_ADMIN_API_KEY` is optional; only the three org tools need it, and they return a clear error explaining what's missing if it isn't set.
**These are genuinely different credential types.** A project key — even one with full permissions inside its project — gets `403 Missing scopes: api.usage.read` on the `/organization/*` endpoints. That's by design, not a misconfiguration.
## Tools (31)
### Files
| Tool | Notes |
|---|---|
| `openai_upload_file` | Local path + purpose (`fine-tune`, `batch`, `assistants`, …) → file ID |
| `openai_list_files` | Optional purpose filter |
| `openai_get_file` | Metadata for one file |
| `openai_download_file_content` | Pass `decode_base64: true` for fine-tuning result files |
| `openai_delete_file` | **Irreversible** |
### Models
| Tool | Notes |
|---|---|
| `openai_list_models` | `filter` does a case-insensitive substring match on model IDs |
| `openai_get_model` | Details for one model |
### Fine-tuning
| Tool | Notes |
|---|---|
| `openai_create_finetune_job` | **Costs real money** — see [Cost](#cost) |
| `openai_get_finetune_job` | Status, `trained_tokens`, `fine_tuned_model`, `result_files` |
| `openai_list_finetune_jobs` | |
| `openai_cancel_finetune_job` | |
| `openai_pause_finetune_job` / `openai_resume_finetune_job` | |
| `openai_list_finetune_events` | Step-by-step log — the place to look when a job fails |
| `openai_list_finetune_checkpoints` | Intermediate models, each a usable model ID |
### Inference
| Tool | Notes |
|---|---|
| `openai_chat_completion` | Works with fine-tuned models (`ft:...`). Supports `response_format` structured outputs. No streaming — see below |
| `openai_create_embedding` | |
| `openai_create_moderation` | `input` accepts a string or an array of text/image parts |
### Images
`openai_generate_image`, `openai_edit_image` (local file paths + optional mask), `openai_create_image_variation` (dall-e-2 only, per OpenAI).
### Audio
| Tool | Notes |
|---|---|
| `openai_create_speech` | Writes the audio to `output_path`, returns path + byte count |
| `openai_transcribe_audio` | `timestamp_granularities` requires `response_format: "verbose_json"` |
| `openai_translate_audio` | Translates to English |
### Batch
`openai_create_batch`, `openai_get_batch`, `openai_list_batches`, `openai_cancel_batch`. Discounted vs. synchronous calls; results arrive within the completion window (default 24h).
### Organization — requires admin key
| Tool | Notes |
|---|---|
| `openai_get_org_costs` | Daily aggregate spend |
| `openai_get_org_usage_completions` | Token usage, optionally grouped by model |
| `openai_get_org_usage` | Usage for embeddings / images / moderations / audio / vector stores / etc. |
## Notable behaviours
Things that cost real debugging time and are now encoded in the tool descriptions:
- **No fine-tuning usage endpoint exists.** OpenAI provides `/organization/usage/{completions,embeddings,images,moderations,audio_*,vector_stores,…}` — but nothing for fine-tuning training. Training cost only appears folded into the daily total from `openai_get_org_costs`, so you infer it from a spike on the training day.
- **Fine-tuning result files may come back base64-encoded.** The training-metrics CSV downloaded raw looks like garbage and `wc -l` reports 0 lines. `openai_download_file_content` takes `decode_base64: true` for this.
- **Bare model aliases can reject fine-tuning.** `gpt-4o-mini` returned `model_not_available`, while the dated snapshot `gpt-4o-mini-2024-07-18` was fine. Use `openai_list_models` with a `ft:` filter to see which base models already have working fine-tunes on the account.
- **`insufficient_quota` (429) is an account problem, not a code problem.** If `openai_list_models` works but every inference call 429s, it's billing/balance — not scopes, not rate limits. A single fine-tuning job can exhaust a small balance and take unrelated production traffic down with it.
- **Streaming is intentionally not exposed.** An MCP tool result is a single payload, so a streamed body would only be reassembled before returning.
- **TLS note (PHP callers, not this server).** Node's `fetch` uses the system trust store and works fine. The PHP scripts in `../pnr_finetune/` needed an explicit CA bundle because that install has `curl.cainfo` unset — the fix there was `CURLOPT_CAINFO`, *not* disabling verification.
## Cost
`openai_create_finetune_job` spends money. Confirmed against a real bill: fine-tuning `gpt-4o-2024-08-06` is **$25 per 1M trained tokens** — a 1,603,698-token job produced a $40.09 charge against a $40.15 daily total (the remainder being that day's inference).
Check the account's balance/limit *before* starting a job. Inference tools cost per-token at each model's own rate.
## Development
```bash
npm run build # compile once
npm run dev # tsc --watch
```
Adding a tool means four edits, all in the obvious places:
1. A method on `OpenAIClient` in `src/openai-client.ts`
2. A zod schema in `src/index.ts`
3. An entry in the `TOOLS` array
4. A `case` in `dispatch()`
Rebuild and restart the MCP client to pick up changes.
### Manual smoke test
```bash
node -e "
const {spawn}=require('child_process');
process.env.OPENAI_API_KEY='sk-...';
const p=spawn('node',['dist/index.js'],{env:process.env});
let b=''; p.stdout.on('data',d=>b+=d);
setTimeout(()=>p.stdin.write(JSON.stringify({jsonrpc:'2.0',id:1,method:'initialize',params:{protocolVersion:'2024-11-05',capabilities:{},clientInfo:{name:'t',version:'1'}}})+'\n'),300);
setTimeout(()=>p.stdin.write(JSON.stringify({jsonrpc:'2.0',id:2,method:'tools/list',params:{}})+'\n'),800);
setTimeout(()=>{console.log(b);p.kill();},2000);
"
```
## Security
- **`.mcp.json` holds keys in plaintext.** It matches how the other servers in this project are configured, but treat the file accordingly and keep it out of anywhere it'd be shared.
- **Never paste an API key — especially an Admin key — into a chat or terminal session that gets logged.** If that happens, rotate it immediately at the admin-keys page; assume it's compromised regardless of how briefly it was used.
- `openai_delete_file` and `openai_cancel_*` are destructive and take effect immediately.
## Related
- `../pnr_finetune/` — the dataset builder and evaluation scripts this server was extracted from
- `../docs/openai-fine-tuning-guide.md` — full walkthrough of the fine-tuning run, with curl equivalents
- `../docs/openai-fine-tuning-general-guide.md` — task-agnostic fine-tuning playbook
This server cannot be deployed
Maintenance
ActivityStale
ResponsivenessNo issues