ucp-server — self-hosted UCP generation service
One process, two interfaces. Point it at GitHub/Jira credentials and get Universal Context Packages on demand:
- REST API (
/v1) — generate and fetch packages withcurlor any HTTP client; - MCP over Streamable HTTP (
/mcp) — plug it straight into Cursor, Claude Code, or any MCP-capable agent.
Configuration is environment-only (12-factor), generated packages are cached on disk with a TTL, and authentication is one env var away. Install it, start it, forget it.
PyPI package name:
ucpcore-server(the nameucp-serverwas taken). The command it installs is stillucp-server.
Quickstart
Docker (one command)
docker run --rm -p 8080:8080 -e GITHUB_TOKEN=ghp_yourtoken \
ghcr.io/ucpcore/ucp-server:latest
uvx / pipx (no Docker)
uvx --from ucpcore-server ucp-server
# or: pipx run --spec ucpcore-server ucp-server
Then generate a package from any public GitHub issue:
curl -s -X POST http://localhost:8080/v1/generate \
-H 'Content-Type: application/json' \
-d '{"source": "github", "ref": "pallets/flask#5961"}' | head
Interactive API docs: http://localhost:8080/docs.
Browser demo (ucpcore.org/try)
For the public try page, enable the unauthenticated demo endpoint (GitHub only, rate-limited, CORS for ucpcore.org):
docker run --rm -p 8080:8080 \
-e UCP_DEMO_ENABLED=1 \
-e GITHUB_TOKEN=ghp_yourtoken \
ghcr.io/ucpcore/ucp-server:latest
curl -s -X POST http://localhost:8080/v1/demo/generate \
-H 'Content-Type: application/json' \
-d '{"ref": "microsoft/vscode#519"}' | jq '.stats'
Deploy at demo.ucpcore.org and point the try page manifest to that host.
Connect an agent (MCP)
The MCP endpoint speaks Streamable HTTP at http://localhost:8080/mcp.
Cursor / Claude Code (mcp.json):
{
"mcpServers": {
"ucp": {
"url": "http://localhost:8080/mcp"
}
}
}
If the server runs with UCP_SERVER_API_KEY, add the header:
{
"mcpServers": {
"ucp": {
"url": "http://localhost:8080/mcp",
"headers": { "Authorization": "Bearer YOUR_KEY" }
}
}
}
Tools exposed:
| Tool | Purpose |
|---|---|
generate_context(source, ref, llm=False) |
Build a UCP from a GitHub issue (owner/repo#123) or Jira ticket (PROJ-123) |
list_contexts() |
List cached packages: id, entity, title, freshness |
get_context(id) |
Full UCP JSON for a cached package |
get_context_markdown(id, token_budget?) |
Canonical Markdown rendering (SPEC §7), optionally truncated by salience |
Chat commands
Once the MCP server is connected, you can drive it from the chat input — no plugins required.
MCP prompts (built into the server)
The server exposes two MCP prompts that clients surface as slash commands automatically:
| Prompt | What it does |
|---|---|
ucp_context(ref, llm=false) |
Generate a package for ref and use it as the authoritative task context |
ucp_catchup(ref) |
Generate a package and brief you: what's decided, what conflicts, what's still open |
The source is detected from the shape of the reference: owner/repo#123
is GitHub, PROJ-123 is Jira.
In Claude Code they appear as /mcp__ucp__ucp_context and
/mcp__ucp__ucp_catchup (assuming the server is named ucp in your
config):
/mcp__ucp__ucp_context pallets/flask#5961
/mcp__ucp__ucp_catchup PROJ-123
In Cursor the server's prompts are available to the agent through the
MCP connection; for a first-class /ucp command use the file below.
/ucp slash command (copy a file into your project)
Ready-made command files live in clients/:
# Cursor
mkdir -p .cursor/commands && cp clients/cursor/ucp.md .cursor/commands/
# Claude Code
mkdir -p .claude/commands && cp clients/claude-code/ucp.md .claude/commands/
Then in either client:
/ucp pallets/flask#5961
/ucp PROJ-123
The command tells the agent to call generate_context on the ucp MCP
server and treat the returned package as the authoritative task context.
REST API
| Method & path | Purpose |
|---|---|
POST /v1/generate |
Generate a package. Body: {"source": "github"|"jira", "ref": "...", "llm": false, "since": null, "audience": null}. Requires Content-Type: application/json. Returns the UCP JSON; headers X-UCP-Package-Id and X-UCP-Cache: hit|miss. With since (ISO timestamp), adds context_diff and the ucp-temporal profile. |
GET /v1/packages |
Cached packages: id, title, entity, generated_at |
GET /v1/packages/{id} |
Full UCP JSON |
GET /v1/packages/{id}/markdown?token_budget=1500 |
Canonical Markdown rendering |
GET /healthz, GET /readyz |
Liveness / readiness probes (never authenticated) |
GET /docs, GET /openapi.json |
OpenAPI documentation |
Errors are RFC 9457 problem documents (application/problem+json):
{"type": "https://ucpcore.org/problems/invalid-ref", "title": "Invalid Reference",
"status": 400, "detail": "invalid GitHub reference 'x': expected owner/repo#number..."}
Examples:
# Jira (needs JIRA_* env on the server)
curl -s -X POST http://localhost:8080/v1/generate \
-H 'Content-Type: application/json' \
-d '{"source": "jira", "ref": "PROJ-123"}'
# LLM-enhanced (needs UCP_LLM_* env on the server)
curl -s -X POST http://localhost:8080/v1/generate \
-H 'Content-Type: application/json' \
-d '{"source": "github", "ref": "microsoft/vscode#519", "llm": true}'
# Catch-up diff since a baseline (adds context_diff + ucp-temporal)
curl -s -X POST http://localhost:8080/v1/generate \
-H 'Content-Type: application/json' \
-d '{"source": "github", "ref": "pallets/flask#5961", "since": "2026-01-01T00:00:00Z"}'
# Rendered Markdown under a token budget
curl -s "http://localhost:8080/v1/packages/github-pallets-flask-5961/markdown?token_budget=1500"
Configuration
Everything is optional; the server starts with zero configuration and reports clearly when a credential is missing for a requested source.
| Variable | Default | Purpose |
|---|---|---|
UCP_SERVER_HOST |
127.0.0.1 (Docker image: 0.0.0.0) |
Bind address |
UCP_SERVER_PORT |
8080 |
Bind port |
UCP_SERVER_API_KEY |
(unset — auth disabled) | Service Bearer key. When set (or when personal tokens exist), endpoints require Authorization: Bearer … |
Personal tokens (alpha.12.1)
Admins create scoped tokens via POST /v1/admin/tokens (requires the service API key).
Tokens use the ctx_ prefix and map principal to the token name — personal tokens
ignore the audience field on /v1/generate.
| Scope | Allows |
|---|---|
generate |
POST /v1/generate, GET /v1/packages*, MCP /mcp |
receipt |
POST /v1/receipt |
admin:read |
GET /v1/admin/* (except token CRUD) |
Token CRUD and sync triggers require UCP_SERVER_API_KEY (service principal).
# Create a token for a teammate (service key)
curl -s -X POST http://localhost:8080/v1/admin/tokens \
-H "Authorization: Bearer $UCP_SERVER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"alice","scopes":["generate","receipt"]}'
# Use in Cursor mcp.json
# "headers": { "Authorization": "Bearer ctx_…" }
Access by personal tokens is logged to GET /v1/admin/access-log (principal = token name).
Hosted pilot (0.4.0)
For dedicated-tenant deployments set UCP_TENANT_SLUG, UCP_PUBLIC_BASE_URL, and
UCP_HOSTED_MODE=1. Public MCP URL becomes {base}/v1/{slug}/mcp. See
deploy/pilot/README.md and Cursor template
clients/cursor/ucp-hosted.md.
| Variable | Default | Purpose |
|---|---|---|
UCP_TENANT_SLUG |
(unset) | Tenant slug in public URLs (acme-corp) |
UCP_PUBLIC_BASE_URL |
(unset) | External base URL for landing + setup JSON |
UCP_HOSTED_MODE |
false |
When true, block legacy /mcp and /v1/* without slug |
UCP_CACHE_DIR |
~/.cache/ucp-server |
Disk cache for generated packages |
UCP_CACHE_TTL |
900 (15 min) |
Cache TTL in seconds; 0 disables caching |
UCP_DEMO_ENABLED |
0 |
Enable POST /v1/demo/generate (public browser demo) |
UCP_DEMO_RATE_LIMIT_PER_HOUR |
30 |
Per-IP rate limit for demo endpoint |
UCP_DEMO_CORS_ORIGINS |
https://ucpcore.org,… |
Allowed browser origins for demo CORS |
GITHUB_TOKEN / GH_TOKEN |
(unset) | GitHub token (public issues work without it, at a low rate limit) |
JIRA_BASE_URL |
(unset) | e.g. https://yourcompany.atlassian.net |
JIRA_EMAIL |
(unset) | Jira Cloud email (Basic auth); omit for Server/DC PAT |
JIRA_API_TOKEN |
(unset) | Jira API token or PAT |
UCP_LLM_BASE_URL |
https://api.openai.com/v1 |
OpenAI-compatible endpoint for llm: true |
UCP_LLM_API_KEY |
(unset) | LLM API key (falls back to OPENAI_API_KEY) |
UCP_LLM_MODEL |
gpt-4o-mini |
LLM model name |
UCP_LOG_JSON |
false |
1/true switches to JSON-lines logs |
UCP_LOG_LEVEL |
INFO |
Log level |
Hosted Rangor (multi-tenant pilot)
For Rangor hosted stack (0.5.0-beta), use tenant-scoped URLs — slug is your
organization id, not a global constant:
https://mcp.rangor.io/v1/{tenant_slug}/mcp
https://api.rangor.io/v1/{tenant_slug}/generate
Cursor (mcp.json):
{
"mcpServers": {
"rangor": {
"url": "https://mcp.rangor.io/v1/acme/mcp"
}
}
}
Click Authenticate in Cursor → portal login at app.rangor.io.
Deploy and local dev: deploy/pilot/README.md.
Cursor hosted guide: clients/cursor/ucp-hosted.md.
Roles (UCP_SERVER_ROLE)
| Role | Use |
|---|---|
full |
Monolith local dev (API + Portal in one process) |
api |
rangor-api container — REST, MCP, webhooks |
portal |
Static SPA only (nginx) |
Security
- Set
UCP_SERVER_API_KEYfor any non-localhost deployment. Without it anyone who can reach the port can spend your GitHub/Jira/LLM quota — unless you rely solely on personal tokens (ctx_…). The service key is compared in constant time; health probes and/adminlogin shell stay open. - Bind is
127.0.0.1by default when run directly. The Docker image setsUCP_SERVER_HOST=0.0.0.0deliberately — the container boundary is the isolation there; publish the port consciously (-p 127.0.0.1:8080:8080keeps it local). - No client-supplied URLs. Clients pass references (
owner/repo#123,PROJ-123) to the two predefined connectors; the server never fetches an arbitrary URL on a client's behalf (no SSRF surface). - Request bodies are limited to 64 KiB and validated strictly (unknown fields rejected). Tokens are masked in logs. The Docker image runs as a non-root user.
Development
python3 -m venv .venv && . .venv/bin/activate
pip install -e ".[dev]"
pytest
Docker image:
docker build -t ucp-server .
docker run --rm -p 8080:8080 ucp-server
Metadata
Release files for ucpcore-server 0.2.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| ucpcore_server-0.2.1.tar.gz | 103.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| ucpcore_server-0.2.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 206.7 kB
Release files / ucpcore_server-0.2.1.tar.gz
| Download URL | ucpcore_server-0.2.1.tar.gz |
|---|---|
| Size | 103.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
95752ef8a766b507de457564f131dbe76e9e537c89880968edf9e73980fba038
|
|
BLAKE2b-256 checksum How to use checksums |
f53ff6aeaa5ea1d69e1e19b4d4baf155fb6cc3a990e811c0bdabd99867178fc7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 10, 2026.
Transparency logRelease files / ucpcore_server-0.2.1-py3-none-any.whl
| Download URL | ucpcore_server-0.2.1-py3-none-any.whl |
|---|---|
| Size | 103.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
35d34cb87bca1a8e9dc23b1ac7ff2c0bed458992e80d61710da27408bf5c070b
|
|
BLAKE2b-256 checksum How to use checksums |
28a7fb7df711dded7473d32eb9bf3d8f30efa2aab318824e665747a6199cf0d8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 10, 2026.
Transparency log