MCP Server with OpenAI-Compatible Model Support
# MCP Server with OpenAI-Compatible Model Support
A small, readable [Model Context Protocol](https://modelcontextprotocol.io) server that exposes a handful of tools to any MCP client (Goose, Claude Desktop, OpenAI Agents, etc.). It supports both **stdio** and **streamable HTTP** transports, and one of its tools streams output from an OpenAI-compatible model.
## Tools
| Tool | Needs an LLM? | Description |
|------|---------------|-------------|
| `server_health` | No | Reports server status and provider config |
| `list_models` | No | Returns the configured model metadata |
| `echo` | No | Returns the provided message back |
| `list_directory` | No | Lists files/folders at a path on the host machine |
| `read_file` | No | Reads the text contents of a file on the host machine |
| `generate_text` | Yes | Streams text from the configured OpenAI-compatible model |
The filesystem tools (`list_directory`, `read_file`) only make sense over **stdio**, because the server runs on the user's own machine and can reach their disk — the canonical "local" MCP use case.
## Architecture
- `src/server.ts` — bootstraps the server, registers tools, selects the transport
- `src/config.ts` — loads environment configuration
- `src/providers/` — OpenAI-compatible client
- `src/tools/` — one file per tool
## Transports
Set `MCP_TRANSPORT` to choose how clients reach the server:
| Value | Behavior |
|-------|----------|
| `stdio` (default) | The client launches the server as a subprocess and talks over stdin/stdout. Local, single-client. |
| `http` | The server listens on a port (`MCP_HTTP_PORT`, default `3000`) in **stateless** mode, so it scales horizontally behind a load balancer. |
| `both` | Runs stdio and HTTP simultaneously in one process. |
`generate_text` streams its tokens as `notifications/message`. Over HTTP these ride the single request's own SSE response, so the server stays stateless (no session affinity needed).
## Setup
```bash
npm install
cp .env.example .env # then fill in your provider settings
npm run build
npm start
```
Environment variables (see `.env.example`):
```bash
MCP_TRANSPORT=stdio # stdio | http | both
MCP_HTTP_HOST=127.0.0.1
MCP_HTTP_PORT=3000
OPENAI_API_KEY=... # only needed for generate_text
OPENAI_BASE_URL=https://api.openai.com/v1
OPENAI_MODEL=gpt-4o-mini
```
## Testing with Goose
[Goose](https://goose-docs.ai) is an open-source AI agent (by Block) that speaks MCP. It uses its own LLM as the "brain" to decide which of your tools to call. See the [Goose docs](https://goose-docs.ai) and [repo](https://github.com/block/goose).
### 1. Give Goose a brain (its own LLM)
Goose needs an LLM provider — this is **separate** from this server's `OPENAI_*` config. Using Anthropic as an example, run it inline so the values reach Goose:
```bash
export GOOSE_DISABLE_KEYRING=1 # read secrets from env instead of the OS keychain
export GOOSE_PROVIDER=anthropic
export GOOSE_MODEL=claude-sonnet-4-6
export ANTHROPIC_API_KEY=sk-ant-...
```
(Or run `goose configure` to store these in the keychain once.)
### 2. Launch a session with this server attached
```bash
npm run build
goose session --with-extension 'node /absolute/path/to/dist/server.js'
```
Goose calls MCP servers **extensions**; the tools appear namespaced (e.g. `echo`, `list_directory`).
To test the **HTTP** transport instead, start the server first and point Goose at the URL:
```bash
# terminal 1
MCP_TRANSPORT=http MCP_HTTP_PORT=3000 node dist/server.js
# terminal 2
goose session --with-streamable-http-extension 'http://127.0.0.1:3000/'
```
### 3. Ask in plain English — the brain picks the tool
You don't name the tool; Goose decides:
```
echo the words "it works"
list the files in /absolute/path/to/src/tools
read the package.json in this project and tell me the version
what model is the server configured to use?
is the server healthy?
```
> Note: the agent may sometimes prefer one of its **own** built-in tools over yours
> (e.g. it may run a shell `ls` instead of `list_directory`). That's the brain
> choosing — phrase the request toward your tool, or disable conflicting built-ins,
> if you want to force it.
### Screenshots
`echo` — Goose's brain calls the `echo` tool and reports the result:

Listing files in the project:

## Inspecting without an agent
To poke the server directly (no LLM needed):
```bash
# Browser UI:
npx @modelcontextprotocol/inspector node dist/server.js
# Raw JSON-RPC over HTTP (server in http mode):
curl -s -X POST http://127.0.0.1:3000/ \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
```
## Example stdio client config
```json
{
"mcpServers": {
"mcp-openai-server": {
"command": "node",
"args": ["/absolute/path/to/dist/server.js"],
"env": {
"OPENAI_API_KEY": "your-key",
"OPENAI_BASE_URL": "https://api.openai.com/v1",
"OPENAI_MODEL": "gpt-4o-mini"
}
}
}
}
```
TDQS
Scored across 6 tools
All six tools have clearly distinct purposes: echo is for testing, generate_text for model generation, list_directory and read_file for file operations, list_models for model metadata, and server_health for checking configuration. There is no overlap or ambiguity.
Most tools follow a verb_noun pattern (generate_text, list_directory, list_models, read_file), but echo is a single verb and server_health is a noun phrase, breaking consistency. The pattern is still readable but not uniform.
With six tools, the count is reasonable for an MCP server. However, the server's stated focus on OpenAI-compatible model support is somewhat diluted by the inclusion of file system tools (list_directory, read_file), which slightly reduces the sense of a well-scoped set.
For a server claiming OpenAI-compatible model support, the tool surface is notably sparse: only a single generate_text tool and a list_models tool. Missing capabilities like context management, streaming, or role-based chat severely limit model interaction. File access is also one-sided (read only).