Panel
by mreyad
README.md
# Panel
Panel is a standalone remote MCP server that lets Claude call ChatGPT, DeepSeek, Kimi, and Gemini inside one persistent, shared discussion. It exposes exactly one tool:
```text
ask_model(provider, message, conversation_id?)
```
## Architecture
- Streamable HTTP MCP endpoint: `POST /mcp`
- Health endpoint: `GET /health`
- Static bearer authentication on every MCP request
- PostgreSQL source-of-truth transcript with one ordered log per `conversation_id`
- One OpenAI-compatible adapter configured three ways, plus a native Gemini adapter
- Request-time context compaction only; stored turns are never edited or deleted
The selected provider sees its own earlier turns as `assistant`/`model`. Claude and every other provider are rendered as `user` with `[Speaker]:` prefixes.
## Local setup
Requirements: Node.js 22+, a reachable PostgreSQL database, and API keys for the four providers.
1. Copy `.env.example` to `.env` and fill values locally. Never commit `.env`.
2. Run `npm ci`.
3. Run `npm run db:migrate` (the server also initializes the idempotent schema at startup).
4. Run `npm run dev`.
5. In another terminal, run `npm run manual`.
Useful checks:
```sh
npm run build
npm run lint
npm test
npm run test:integration
npm run verify:persistence
```
The integration and persistence commands require `DATABASE_URL`. The manual test requires the running server, all provider keys, and `PANEL_BEARER_TOKEN`. Set `PANEL_URL` when testing a deployed instance.
## Environment
Required server variables:
- `DATABASE_URL`
- `PANEL_BEARER_TOKEN` (use at least 32 random bytes)
- `OPENAI_API_KEY`
- `DEEPSEEK_API_KEY`
- `MOONSHOT_API_KEY`
- `GOOGLE_API_KEY`
Optional variables and defaults:
- `MAX_CONTEXT_CHARS=60000`
- `PORT=3000`
- `OPENAI_BASE_URL=https://api.openai.com/v1`
- `OPENAI_MODEL=gpt-4o-mini`
- `DEEPSEEK_BASE_URL=https://api.deepseek.com`
- `DEEPSEEK_MODEL=deepseek-v4-flash`
- `MOONSHOT_BASE_URL=https://api.moonshot.ai/v1`
- `MOONSHOT_MODEL=kimi-k2.5`
- `GEMINI_MODEL=gemini-2.5-flash`
## Deploy
### Render
Push this repository to GitHub, create a Blueprint from `render.yaml`, and enter every `sync: false` secret in Render's dashboard. Point `DATABASE_URL` at external Neon or Supabase Postgres. The service URL will be `https://<service>.onrender.com/mcp`.
### Railway
Push to GitHub, create a Railway project from the repository, and add the same variables. `railway.json` uses the included Dockerfile. Generate a public HTTPS domain and append `/mcp`.
## Connect to claude.ai
Current Claude connector documentation lists fixed request-header authentication (`static_headers`) as a beta option for custom connectors. Add the deployed `/mcp` URL under **Customize → Connectors → Add custom connector** (Team/Enterprise owners add it under organization connector settings). In advanced/request-header settings add:
```text
Authorization: Bearer <the exact PANEL_BEARER_TOKEN value>
```
Then connect/enable Panel in the target Claude conversation. Do not put the token in the URL.
If the request-header field is not exposed for your account, do not make this server public without auth. That account/UI has not enabled the beta static-header path; OAuth would then be a separate, explicitly required follow-up.
## Verification harness behavior
`npm run manual` invokes all four providers through MCP using one `conversation_id`. Its DeepSeek, Kimi, and Gemini prompts explicitly require reactions to earlier named providers, making shared transcript behavior visible. `npm run verify:persistence` writes through one PostgreSQL pool, closes it, creates a fresh pool, and reads the sentinel back. The compaction unit test confirms the request rendering is compacted while the original full turn array remains unchanged; the PostgreSQL transcript is only read during rendering.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessSyncing