Imagine MCP
by Archive228
README.md
<p align="center">
<img src="./assets/hero.svg" alt="Imagine MCP - cost-aware xAI media tools over Streamable HTTP" width="100%">
</p>
<p align="center">
<strong>Generate media through xAI. Keep the URL, the receipt, and the ceiling.</strong><br>
Authenticated Streamable HTTP tools for images, video, and speech.
</p>
<p align="center">
<a href="https://github.com/Archive228/imagine-mcp/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/Archive228/imagine-mcp/actions/workflows/ci.yml/badge.svg"></a>
<img alt="MCP Streamable HTTP" src="https://img.shields.io/badge/MCP-Streamable_HTTP-c084fc?style=flat-square">
<img alt="xAI Imagine" src="https://img.shields.io/badge/xAI-Imagine-f472b6?style=flat-square">
<img alt="Daily spend cap" src="https://img.shields.io/badge/spend_cap-required-fb923c?style=flat-square">
<img alt="License: MIT" src="https://img.shields.io/badge/license-MIT-e5e7eb?style=flat-square">
</p>
Production-oriented remote MCP service for xAI image generation, image
editing, video generation, and text-to-speech. It exposes one Vercel Function
at `api/mcp.ts` using the MCP TypeScript SDK's Streamable HTTP handler.
Every billable result is copied from xAI into an immutable Vercel Blob object.
Clients receive the persistent public Blob URL, never the temporary xAI URL.
Tests use mocked HTTP and in-memory storage only; they make no external or
billable calls.
The service is designed as the render boundary for the
[`overnight`](https://github.com/Archive228/overnight) crew. Only that crew's
Render bot should receive this MCP connection.
## Tools
| Tool | Input | Structured output |
| --- | --- | --- |
| `generate_image` | `{prompt, model?, n?}` (`n`: 1–10) | `{urls,cost_usd,model,ms}` |
| `edit_image` | `{prompt,image_url}` | `{url,cost_usd,ms}` |
| `generate_video` | `{prompt,image_url?,duration_sec,model?}` (`duration_sec`: 1–15) | `{url,cost_usd,duration_sec,ms}` |
| `speak` | `{text,voice_id?,language?}` (15,000 Unicode code points max) | `{url,cost_usd,chars,ms}` |
Image models: `grok-imagine-image-2.0`, `grok-imagine-image`, and
`grok-imagine-image-quality`. Video models: `grok-imagine-video-1.5` and
`grok-imagine-video`.
Defaults are `grok-imagine-image-2.0`, `grok-imagine-video-1.5`, voice `eve`,
and language `auto`. Tool schemas allow only the documented model IDs shown in
the contracts above, so a newly introduced higher-priced model cannot bypass
the reservation table. API-reported usage replaces the estimate when present.
## Architecture
```mermaid
flowchart LR
B["Grok Bot<br/>Render only"] -->|x-mcp-secret| M["MCP endpoint<br/>auth + spend reserve"]
M --> X["xAI API<br/>image / video / TTS"]
X --> P[("Public Blob store<br/>persistent media")]
X --> L[("Private Blob store<br/>append-only ledger")]
P --> B
```
- `api/mcp.ts` — Vercel fetch entrypoint.
- `src/server.ts` — authentication, Origin policy, MCP registration, runtime
wiring.
- `src/xai.ts` — xAI REST client, response validation, video polling and
bounded retry.
- `src/tools/` — one module per MCP tool.
- `src/media.ts` — bounded media downloads and immutable persistence.
- `src/store.ts` — Vercel Blob and in-memory adapters; production wires one
public media store and one private ledger store.
- `src/ledger.ts` — append-only daily reservation/reconciliation events.
- `src/pricing.ts` — published fallback rates and usage-tick conversion.
- `src/config.ts` — fail-closed environment validation.
The MCP handler supports current per-request MCP traffic and the SDK's
stateless legacy Streamable HTTP fallback. No session state is stored in a
function instance.
## Environment
Copy `.env.example` to your secret manager or Vercel project settings. Do not
commit `.env`.
Required:
- `MCP_SHARED_SECRET`: 32–4096 characters. Every real MCP GET, POST, and
DELETE request must send it as `x-mcp-secret`.
- `XAI_API_KEY`: server-side xAI key.
- `DAILY_SPEND_LIMIT_USD`: positive UTC-day xAI generation ceiling.
- A **public** Vercel Blob store for returned media:
- `MEDIA_BLOB_READ_WRITE_TOKEN`, or
- `MEDIA_BLOB_STORE_ID` with `VERCEL_OIDC_TOKEN`.
- A separate **private** Vercel Blob store for spend-ledger events:
- `LEDGER_BLOB_READ_WRITE_TOKEN`, or
- `LEDGER_BLOB_STORE_ID` with `VERCEL_OIDC_TOKEN`.
Vercel fixes a store's public/private mode when it is created, so one store
cannot serve both roles. Reusing the same known token or store ID fails
configuration validation. The obsolete `BLOB_TOKEN` and unprefixed
`BLOB_READ_WRITE_TOKEN` variables are not used.
Optional:
- `MCP_ALLOWED_ORIGINS`: comma-separated exact HTTP(S) browser origins.
- `MAX_IMAGE_BYTES`, `MAX_VIDEO_BYTES`, `MAX_AUDIO_BYTES`: positive byte
limits. Defaults are 25 MiB, 128 MiB, and 50 MiB.
Validate configuration without printing values or making network requests:
```bash
npm run doctor
```
## Install and verify
Node.js 20 or newer:
```bash
./init.sh
./test.sh
```
Equivalent npm commands:
```bash
npm ci
npm run typecheck
npm test
npm run build
```
`stop.sh` is intentionally a no-op because this repository does not launch a
background daemon. Vercel owns the production function lifecycle.
## Deploy to Vercel
1. Create a Vercel project from this repository.
2. Connect two Vercel Blob stores: one **Public** store for media and one
**Private** store for ledger events. Give them the environment prefixes
`MEDIA` and `LEDGER`, or configure the explicit variables above.
3. configure every required environment variable above for the intended
deployment environments.
4. Keep `api/mcp.ts` at `/api/mcp`. `vercel.json` requests an 800-second
function, which requires Vercel Pro or Enterprise. This covers the
ten-minute xAI video polling deadline plus response handling.
5. Deploy through your normal reviewed workflow.
This repository does not run deployment commands. Vercel Hobby currently caps
functions at 300 seconds, so it is suitable for image/edit/TTS use but cannot
reliably satisfy this synchronous `generate_video` contract. Do not present a
Hobby deployment as video-ready.
### Vercel Host and Origin behavior
The service does not hardcode a `Host` allowlist. Vercel deployment, preview,
and custom-domain hostnames change independently, and Vercel validates routing
and TLS before invoking the function. `MCP_ALLOWED_ORIGINS` controls the
browser's `Origin` header, not the Vercel destination hostname.
Non-browser MCP clients normally omit `Origin` and are accepted after secret
authentication. A request that sends `Origin` is rejected unless its exact
origin is configured. This makes browser access opt-in. Allowed CORS
preflights do not invoke MCP or xAI; the subsequent MCP request still requires
`x-mcp-secret`.
## Connect and smoke-test
Set shell variables without putting secrets into command history:
```bash
export MCP_URL='https://your-project.vercel.app/api/mcp'
read -s MCP_SHARED_SECRET
export MCP_SHARED_SECRET
```
Streamable HTTP initialize smoke request:
```bash
curl --fail-with-body --no-buffer \
-X POST "$MCP_URL" \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-H "x-mcp-secret: $MCP_SHARED_SECRET" \
--data '{
"jsonrpc":"2.0",
"id":1,
"method":"initialize",
"params":{
"protocolVersion":"2025-06-18",
"capabilities":{},
"clientInfo":{"name":"curl-smoke","version":"1.0.0"}
}
}'
```
An unauthenticated check should return `401` without invoking a tool:
```bash
curl -i -X POST "$MCP_URL" \
-H 'Content-Type: application/json' \
--data '{"jsonrpc":"2.0","id":1,"method":"ping"}'
```
For MCP Inspector:
```bash
npx @modelcontextprotocol/inspector
```
In Inspector, choose **Streamable HTTP**, enter `$MCP_URL`, and add the custom
header `x-mcp-secret`. If Inspector sends requests directly from its browser
UI, add that UI's exact origin to `MCP_ALLOWED_ORIGINS`; if it uses its local
proxy, no browser Origin normally reaches the service. List tools before
calling one. Tool calls are billable when configured with a real xAI key.
MCP clients that support remote headers should be configured with the same URL
and a secret header sourced from their own environment or secret store. Never
paste the xAI API key into an MCP client; only the shared MCP secret belongs in
the client configuration.
## Security notes
- Shared-secret comparison hashes both values and uses
`crypto.timingSafeEqual`. Missing and malformed auth fail closed. Secrets,
request headers, and upstream bodies are not emitted to application logs.
- Media returned by this service is intentionally public. Do not generate
sensitive content. Reservation events in the separate private ledger store
retain the submitted prompt/text with tool, model, estimate, and timestamp;
reconciliation events add actual cost. Never put credentials in prompts.
- Caller-provided `image_url` values are passed to xAI as JSON and are never
fetched by this service.
- The service fetches only URLs obtained from successful xAI responses. It
requires HTTPS, rejects obvious local/private literal hosts, disables
redirects, validates media MIME types, and enforces header and streaming
size limits. DNS ownership of xAI-returned signed URLs remains part of the
upstream trust boundary.
- `DAILY_SPEND_LIMIT_USD` is always required. See [COST.md](./COST.md) for
reservation behavior and the documented serverless concurrency bound.
- Blob storage, egress, and Vercel execution charges are not reported by xAI
and are outside the xAI generation ceiling.
## Test policy
All tests inject mocked `fetch` implementations and an in-memory object store.
They cover authentication, pricing and usage ticks, image request shapes,
video polling/retry/timeout, binary TTS, media validation, Blob adapter options,
and spend-cap behavior. No test reads `XAI_API_KEY`, contacts xAI, writes
Vercel Blob, deploys, or incurs cost.
Report vulnerabilities through the private process in
[`SECURITY.md`](./SECURITY.md), not a public issue.
## License
MIT. Generated media remains subject to the provider terms and any rights in
its source material.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues