Self-hosted internal API documentation platform. Teams push OpenAPI specs to Speculo; it stores them, renders interactive docs via Scalar, and exposes all APIs to AI assistants through the Model Context Protocol (MCP).
- OpenAPI catalog — upload Swagger 2.0 or OpenAPI 3.x specs (YAML or JSON), browse them by service and branch
- Interactive docs — Scalar-rendered UI at
/docs/:service/:branch - MCP server — connect Claude Desktop, Cursor, or any MCP client; query your entire API catalog with natural language
- LLM-friendly — machine-readable endpoint summaries at
/docs/:service/:branch/llms.txt - CI/CD sync — GitHub Actions template to auto-push specs on every commit
- Self-hosted — two Docker containers (app + PostgreSQL), no external dependencies
git clone https://github.com/testmind-hq/speculo.git
cd speculo
# Generate a secure JWT secret
openssl rand -base64 32
# Start with the generated secret
JWT_SECRET=<paste-generated-secret> docker compose upThe app is available at http://localhost:3000. A default admin account is created automatically on first startup with a randomly generated password printed once to the container logs:
docker compose logs app | grep -A4 "Default admin"
| Field | Value |
|---|---|
admin@example.com |
|
| Password | (random — see startup logs) |
Change the password after first login.
Prerequisites: Node.js 22+, pnpm, PostgreSQL 16
pnpm install
# Copy and fill in environment variables
cp packages/api/.env.example packages/api/.env
# Run API + web in parallel
pnpm devEnvironment variables for packages/api:
| Variable | Required | Default | Description |
|---|---|---|---|
DATABASE_URL |
✅ | — | PostgreSQL connection string |
JWT_SECRET |
✅ | — | Min 32 characters |
JWT_EXPIRY_DAYS |
7 |
Token lifetime in days | |
PORT |
3000 |
HTTP port | |
SECURE_COOKIES |
false |
Set to true when serving over HTTPS (behind TLS terminator) |
Apply database migrations:
cd packages/api
pnpm db:migrate- Open
http://localhost:3000, sign in - Go to Import and drag-drop your
openapi.yaml/openapi.json
# Using a write-scope MCP token
curl -X POST http://your-speculo/api/upload \
-H "Authorization: Bearer speculo_mcp_..." \
-H "Content-Type: application/json" \
-d '{
"service": "user-service",
"branch": "main",
"commitSha": "abc123",
"specContent": "<your openapi yaml or json string>"
}'Or with multipart form (file upload):
curl -X POST http://your-speculo/api/upload \
-H "Authorization: Bearer speculo_mcp_..." \
-F "service=user-service" \
-F "branch=main" \
-F "file=@openapi.yaml"Copy .github/workflows/speculo-sync.yml to your service repo, then set:
- Repository variable
SPECULO_URL— your Speculo instance URL - Repository secret
SPECULO_TOKEN— a write-scope MCP token from the Speculo UI
The workflow triggers automatically when openapi.yaml (or equivalent) changes on any branch.
Optionally create a .speculo-service file in your repo root to override the service name (defaults to the repository name).
Copy .gitlab-ci-template.yml from this repo to your service repo as .gitlab-ci.yml, then set CI/CD variables:
- Variable
SPECULO_URL— your Speculo instance URL - Masked secret
SPECULO_TOKEN— a write-scope MCP token
- Sign in to Speculo and go to MCP Tokens
- Create a read-scope token
- Copy the generated Claude Desktop / Cursor config snippet
Example config:
{
"mcpServers": {
"speculo": {
"url": "http://your-speculo/mcp",
"headers": {
"Authorization": "Bearer speculo_mcp_..."
}
}
}
}| Tool | Description |
|---|---|
list_services |
List all services and their branches |
search_endpoints |
Full-text search across all endpoints |
get_endpoint_detail |
Full dereferenced schema for one endpoint |
get_schema_detail |
A specific component schema |
get_service_markdown |
Entire service spec as Markdown |
Teams are the permission boundary: each service belongs to a team, and users belong to teams. Super admins manage all teams; team owners manage their own team's members, services, and grants.
| Role | Scope | Capabilities |
|---|---|---|
super_admin |
Global | Manage all teams, users, services |
team_owner |
Team | Manage members, services, cross-team grants |
team_member |
Team | Access all services owned by team |
guest |
Explicit grants only | Access only explicitly granted services |
A team owner can grant another team (or a specific user) access to one of their services, optionally scoped to specific branches and with an expiry date. Manage grants at /admin/teams/:id/grants.
| Method | Path | Auth | Description |
|---|---|---|---|
POST |
/auth/register |
JWT (super_admin) | Create account |
POST |
/auth/login |
— | Get JWT |
POST |
/auth/logout |
— | Clear session cookie |
GET |
/api/me |
JWT | Current user info + teams |
GET |
/api/tokens |
JWT | List MCP tokens |
POST |
/api/tokens |
JWT | Create MCP token |
DELETE |
/api/tokens/:id |
JWT | Revoke MCP token |
POST |
/api/upload |
JWT or write MCP token | Upload spec |
GET |
/api/catalog |
JWT | List all services (with team info) |
GET |
/api/search?q= |
JWT | Full-text endpoint search (tsvector) |
GET |
/api/specs/:service/:branch/openapi.json |
JWT | Raw spec JSON |
GET |
/docs/:service/:branch |
JWT cookie | Scalar docs UI |
GET |
/docs/:service/:branch/llms.txt |
JWT (Bearer/cookie) or read MCP token | LLM-readable summary |
POST |
/mcp |
read MCP token | MCP JSON-RPC |
GET |
/mcp |
read MCP token | MCP SSE stream |
GET |
/health |
— | Health check (used by CI/load balancers) |
GET |
/api/admin/teams |
JWT (super_admin) | List teams |
POST |
/api/admin/teams |
JWT (super_admin) | Create team |
GET |
/api/admin/teams/:id/members |
JWT (owner+) | List members |
POST |
/api/admin/teams/:id/members |
JWT (owner+) | Add member |
GET |
/api/admin/teams/:id/services |
JWT (owner+) | List team services |
GET |
/api/admin/teams/:id/grants |
JWT (owner+) | List grants |
POST |
/api/admin/teams/:id/grants |
JWT (owner+) | Create grant |
DELETE |
/api/admin/grants/:id |
JWT (owner+) | Revoke grant |
GET |
/api/admin/users |
JWT (super_admin) | List users |
PUT |
/api/admin/users/:id |
JWT (super_admin) | Update user role/status |
DELETE |
/api/admin/users/:id |
JWT (super_admin) | Delete user |
cd packages/api && pnpm testcp packages/e2e/.env.e2e.example packages/e2e/.env.e2e
# Fill in BASE_URL and ADMIN_PASSWORD
cd packages/e2e && pnpm testE2E tests cover login, spec upload, catalog browsing, MCP token management, and admin user operations. A CI workflow (.github/workflows/e2e.yml) runs them automatically on every push.
- Backend: Node.js 22, TypeScript, Hono, Drizzle ORM, PostgreSQL 16
- Frontend: React 19, Vite, Tailwind CSS, React Router v6
- Docs rendering: @scalar/api-reference
- Spec processing:
@scalar/openapi-upgrader,@stoplight/spectral-core,@apidevtools/swagger-parser - MCP:
@modelcontextprotocol/sdk
speculo composes the following open-source projects (consumed as standard npm dependencies; no source code embedded or modified):
- Stoplight Spectral — OpenAPI linting (Apache-2.0)
- Scalar — interactive docs UI (MIT)
- Hono — web framework (MIT)
- Drizzle ORM — database layer (Apache-2.0)
- Model Context Protocol SDK — MCP server (MIT)
- Swagger Parser — OpenAPI/Swagger parsing (MIT)
See NOTICE for the full attribution.
Apache 2.0 — see LICENSE.