Skip to main content
Glama
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.

Maintenance

ActivityMaintained
ResponsivenessSyncing