pns-server MCP Server
# pns-server
Backend service that converts natural language descriptions of production and queueing systems into Petri net models exported in PNML format (compatible with TINA, PetriObjModel, CPN Tools).
**Web UI**: [pns-ui-web](https://github.com/petri-net-sim/pns-ui-web)
---
## Stack
| Layer | Technology |
|---|---|
| Runtime | Python 3.13 |
| Web framework | FastAPI + Uvicorn |
| AI agent | LangGraph + LangChain |
| LLM providers | OpenAI, Anthropic, Ollama |
| Database | PostgreSQL (async via SQLAlchemy + asyncpg) |
| Session cache | Redis |
| Protocol | OpenAI-compatible REST + MCP (Model Context Protocol) |
| Migrations | Alembic |
---
## Quick start (Docker)
### Local — single command, auto-generated secrets
Runs PostgreSQL + backend. No Redis. Every user provides their own LLM API key via the Settings page.
```bash
docker compose -f docker-compose.local.yml up -d
# View auto-generated secrets on first start
docker compose -f docker-compose.local.yml logs app | grep -A 10 "AUTO-GENERATED"
# Health check
curl http://localhost:8000/health
```
### Production — with Redis, PostgreSQL, Nginx
```bash
cp .env.example .env
# Fill in all required values in .env
docker compose -f docker-compose.prod.yml up -d
docker compose -f docker-compose.prod.yml ps
```
**Nginx reverse proxy config:**
```nginx
server {
listen 80;
server_name your-domain.com;
location /api/ { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; }
location /health { proxy_pass http://127.0.0.1:8000; }
location /mcp {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_read_timeout 3600s;
proxy_buffering off;
proxy_cache off;
chunked_transfer_encoding on;
}
}
```
**Scale workers (default: 2):**
```bash
UVICORN_WORKERS=4 docker compose -f docker-compose.prod.yml up -d backend
```
---
## Local development
```bash
# Install dependencies (uv package manager)
uv sync --extra dev
# Start dependencies
docker compose up -d
# Apply migrations
uv run alembic upgrade head
# Start server with hot reload
uv run uvicorn main:app --reload --host 0.0.0.0 --port 8000
# Run tests
uv run pytest -v
```
---
## API
### OpenAI-compatible
| Method | Path | Description |
|--------|------|-------------|
| POST | `/api/v1/chat/completions` | Chat completion (stream/non-stream) |
| GET | `/api/v1/models` | List available models |
| GET | `/api/v1/files/{file_id}` | Download generated PNML file |
Auth: `Authorization: Bearer sk-...`
### Auth & API Keys
| Method | Path | Description |
|--------|------|-------------|
| POST | `/api/v1/auth/signup` | Register |
| POST | `/api/v1/auth/login` | Log in, returns JWT pair |
| POST | `/api/v1/auth/refresh` | Refresh tokens |
| GET | `/api/v1/auth/me` | Current user |
| POST | `/api/v1/auth/api-keys` | Create API key |
| GET | `/api/v1/auth/api-keys` | List API keys |
| DELETE | `/api/v1/auth/api-keys/{id}` | Revoke API key |
### LLM Credentials (BYOK)
| Method | Path | Description |
|--------|------|-------------|
| POST | `/api/v1/llm-credentials` | Store encrypted LLM API key |
| GET | `/api/v1/llm-credentials` | List profiles |
| PATCH | `/api/v1/llm-credentials/{id}` | Update profile |
| DELETE | `/api/v1/llm-credentials/{id}` | Delete profile |
| POST | `/api/v1/llm-credentials/{id}/test` | Verify connectivity |
### Sessions & Simulation
| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/v1/sessions` | List user sessions |
| GET | `/api/v1/sessions/{id}` | Get session |
| POST | `/api/v1/sessions/{id}/simulate` | Run simulation (SSE stream) |
| GET | `/api/v1/sessions/{id}/snapshots` | List net snapshots |
### Health
| Method | Path | Description |
|--------|------|-------------|
| GET | `/health` | Liveness probe |
| GET | `/health/ready` | Readiness probe (checks DB + Redis) |
---
## MCP Server (Model Context Protocol)
The MCP server is mounted at `/mcp` on the main port and also available standalone.
| Mode | Command | Endpoint |
|------|---------|----------|
| Unified (default) | `uvicorn main:app` | `http://host:8000/mcp` |
| Standalone HTTP | `uv run python mcp_server.py` | `http://host:8001/mcp` |
| Standalone stdio | `uv run python mcp_server.py --stdio` | — |
The MCP server exposes 50+ tools for Petri net construction, simulation, and export directly to MCP-compatible clients.
---
## Architecture
```
src/
├── api/ # Common API infrastructure (health, versioning, middleware)
├── openai_api/ # OpenAI-compatible protocol (chat completions, models, files)
├── core/ # Config, DI, logging, lifecycle
├── domain/ # Pure domain logic
│ ├── models/ # PetriNet, Place, Transition, Arc
│ ├── patterns/ # 29 reusable Petri net building blocks
│ └── layout/ # Sugiyama hierarchical layout algorithm
├── infrastructure/ # PostgreSQL (SQLAlchemy) + Redis
├── mcp/ # MCP server — tools, resources, prompts
└── services/
├── agent/ # LangGraph workflow (graph, nodes, tools)
├── chat/ # Chat orchestration, session management
├── petri/ # Builder, Optimizer, PNML exporter
├── simulation/ # Discrete-event Petri net simulator
├── llm/ # Per-credentials LLM factory (OpenAI / Anthropic / Ollama)
├── crypto/ # Fernet encryption for stored API keys
├── usage/ # Free-tier Redis counters
└── auth/ # JWT, API keys, OAuth
```
### Petri net patterns (29 total)
Core: Generator, Machine, Controller, Assembly, Router, Terminator, Buffer, BoundedQueue
Flow control: PriorityChoice, ThresholdActivation, Merge, Duplicate
Advanced: QualityCheck, FeedbackLoop, Conveyor, ConveyorWorkstation, TransportAgent
Batching: Batch, Unbatch, TimedBatch
Reliability: Breakdown, ScheduledAvailability, PreemptiveMachine, SetupMachine
Queueing: RenegingQueue, BalkingQueue, MultiResource, ServerVacation, NonstationaryGenerator
---
## Key environment variables
| Variable | Default | Description |
|----------|---------|-------------|
| `APP_ENV` | `local` | Environment (`local` / `stage` / `prod`) |
| `AGENT_LLM_PROVIDER` | `openai` | LLM provider (`openai` / `anthropic` / `ollama`) |
| `AGENT_LLM_MODEL` | `gpt-4o` | Model name |
| `OPENAI_API_KEY` | — | OpenAI API key |
| `ANTHROPIC_API_KEY` | — | Anthropic API key |
| `DB_HOST` / `DB_PORT` / `DB_NAME` | — | PostgreSQL connection |
| `REDIS_HOST` / `REDIS_PORT` | — | Redis connection |
| `REDIS_ENABLED` | `true` | Enable Redis |
| `AUTH_JWT_SECRET_KEY` | — | JWT signing key (min 32 chars) |
| `LLM_BYOK_REQUIRED` | `true` | Require user-provided LLM credentials |
| `CREDENTIALS_CIPHER` | `fernet` | Encryption mode (`fernet` / `noop`) |
| `CREDENTIALS_MASTER_KEY` | — | 32-byte base64 key for Fernet |
| `SIMULATION_REMOTE_URL` | — | Optional external simulation backend |
See `.env.example` for the full reference.
---
## CI/CD
Push to `main` → GitHub Actions builds Docker image → pushes to `ghcr.io` → deploys via SSH.
Required GitHub secrets: `SERVER_HOST`, `SERVER_USER`, `SERVER_SSH_KEY`, `DEPLOY_PATH`.
---
## License
MIT
TDQS
Scored across 66 tools
Most tools have clearly distinct purposes, with detailed descriptions that differentiate similar patterns (e.g., add_queue variants, add_conveyor vs add_conveyor_line). A few pairs like add_batch and add_timed_batch could cause minor confusion but are well-documented.
The naming is fully consistent, using snake_case throughout with a clear verb_noun pattern (add_*, connect_*, modify_*, remove_*, etc.). All tool names follow predictable conventions without mixing styles.
66 tools is high, but justified for a comprehensive Petri net modeling tool. The count reflects the many pattern variants and low-level operations needed. However, some users might find it overwhelming, so it's slightly above ideal.
The tool set is exceptionally complete, covering creation, modification, connection, layout, simulation, validation, and export. It includes a wide range of high-level patterns and low-level primitives, leaving no obvious gaps in the modeling workflow.