Python Library
Schema-driven entity management for AI-native applications.
Define entities as JSON Schemas + a manifest. The library handles CRUD, validation, ID generation, storage, and search. Add FastMCP to serve it as an MCP server.
Install
Section titled “Install”# Core library (entity management only)uv add upjack
# With MCP server supportuv add upjack[mcp]Usage: Entity Management
Section titled “Usage: Entity Management”from upjack import UpjackApp
app = UpjackApp.from_manifest("manifest.json")
# Create an entitycontact = app.create_entity("contact", { "first_name": "Sarah", "last_name": "Chen", "email": "sarah@example.com",})
# List entitiescontacts = app.list_entities("contact")
# Update an entityapp.update_entity("contact", contact["id"], {"lead_score": 85})
# Get a single entitycontact = app.get_entity("contact", contact["id"])
# Delete an entity (soft delete by default)app.delete_entity("contact", contact["id"])Usage: MCP Server
Section titled “Usage: MCP Server”from upjack.server import create_server
mcp = create_server("manifest.json")mcp.run()This generates CRUD tools for every entity type in the manifest, exposes context and skills as MCP resources, and handles validation automatically. See the CRM example for a complete example.
Configuration
Section titled “Configuration”The workspace root (where entity data is stored) is resolved in this order:
UPJACK_ROOTenvironment variable--rootCLI argument (when usingupjack serve).upjackin the current directory (default)
Runners like mpak or NimbleBrain set UPJACK_ROOT automatically to ensure data persists outside the bundle cache.
Tool Visibility
Section titled “Tool Visibility”Entity definitions can include a tools array to control which operations appear in tools/list. All tools remain callable via tools/call. See Manifest Reference: Entity Definition for options.
Relationship Queries
Section titled “Relationship Queries”Query the relationship graph to traverse connections between entities. These methods use a reverse index that Upjack maintains automatically at write time.
from upjack import UpjackApp
app = UpjackApp.from_manifest("manifest.json")
# Find all contacts at a company (reverse lookup)contacts = app.query_by_relationship("contact", "company", "co_01JQ3KM...")
# With filtering and limitsenior = app.query_by_relationship( "contact", "company", "co_01JQ3KM...", filter={"role": "VP"}, limit=10,)
# Follow edges forward: get entities a contact links torelated = app.get_related("ct_01JQ3KM...", direction="forward")# → [{"id": "co_01JQ3KM...", "name": "Acme Corp", ...}, ...]
# Follow edges in reverse: get entities that link to a companyreferrers = app.get_related("co_01JQ3KM...", direction="reverse")# → [{"id": "ct_01JQ3KM...", "first_name": "Sarah", ...}, ...]
# Filter by relationship namedeals = app.get_related("co_01JQ3KM...", rel="company", direction="reverse")
# Load an entity with all related entities in one callcomposite = app.get_composite("contact", "ct_01JQ3KM...", depth=1)# {# "id": "ct_01JQ3KM...",# "first_name": "Sarah",# "company": "co_01JQ3KM...",# "_related": {# "company": [{"id": "co_01JQ3KM...", "name": "Acme Corp", ...}],# "~company": [{"id": "dl_01JQ3KM...", "title": "Enterprise Plan", ...}]# }# }In _related, forward relationships use the relationship name (e.g. "company") and reverse relationships use a ~ prefix (e.g. "~company" for deals that reference this contact via their company field).
Activity Tracking
Section titled “Activity Tracking”Log timestamped activities against any entity. Requires "activities": true in the manifest’s upjack extension:
{ "_meta": { "ai.nimblebrain/upjack": { "activities": true } }}Once enabled, Upjack registers a built-in activity entity type (prefix act) and exposes two methods:
from upjack import UpjackApp
app = UpjackApp.from_manifest("manifest.json")
# Log an activity against a contactapp.log_activity("ct_01JQ3KM...", "email_sent", detail={ "subject": "Follow-up on proposal", "channel": "email",})
# Log a deal stage changeapp.log_activity("dl_01JQ3KM...", "stage_changed", detail={ "from": "qualification", "to": "proposal",})
# Get all activities for an entityactivities = app.get_activities("ct_01JQ3KM...")
# Filter by actionemails = app.get_activities("ct_01JQ3KM...", action="email_sent")
# Limit resultsrecent = app.get_activities("ct_01JQ3KM...", limit=5)Activities are stored as regular entities and linked to their subject via the relationship index, so they also appear in get_composite results.
Requirements
Section titled “Requirements”- Python >= 3.13
- FastMCP >= 3.0 (only for
upjack[mcp])
API Differences from TypeScript
Section titled “API Differences from TypeScript”| Aspect | Python | TypeScript |
|---|---|---|
| Method naming | snake_case (create_entity) | camelCase (createEntity) |
| Server framework | FastMCP | @modelcontextprotocol/sdk |
| MCP extra | upjack[mcp] (optional) | @modelcontextprotocol/sdk (peer dep) |
| Schema validation | jsonschema (Draft202012Validator) | AJV (2020-12) |
| ID generation | python-ulid | ulidx |
| Error types | ValueError, FileNotFoundError, ValidationError | Generic Error with descriptive messages |
Looking for TypeScript? See the TypeScript Library page.