Build a Todo App
This tutorial walks you through building a personal task manager with NimbleBrain Upjack. By the end, you’ll have a working MCP server that any AI agent can use to create, search, and manage your tasks.
What you’ll build: A todo app with tasks, projects, and labels — complete with priority scoring, daily review skills, and seed data.
What you need: Python >= 3.13 and uv (or pip).
Step 1: Install upjack
Section titled “Step 1: Install upjack”uv add upjack# or: pip install upjackStep 2: Scaffold the app
Section titled “Step 2: Scaffold the app”upjack init --name todo --entity taskThis creates a todo/ directory with a manifest, a starter schema, a server entry point, and seed data. We’ll customize each file.
Step 3: Define the task schema
Section titled “Step 3: Define the task schema”Open todo/schemas/task.schema.json and replace its contents with:
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Todo Task", "description": "A task to be completed", "allOf": [ { "$ref": "https://upjack.dev/schemas/v1/upjack-entity.schema.json" } ], "properties": { "title": { "type": "string", "maxLength": 256, "description": "Short summary of what needs to be done" }, "description": { "type": "string", "description": "Detailed notes about the task" }, "priority": { "type": "string", "enum": ["critical", "high", "medium", "low", "none"], "default": "none", "description": "Task priority level" }, "due_date": { "type": "string", "format": "date", "description": "Date by which the task should be completed" }, "effort": { "type": "string", "enum": ["trivial", "small", "medium", "large", "epic"], "description": "Estimated effort to complete" } }, "required": ["title"]}What’s happening: The allOf composition layers your task fields on top of Upjack’s base entity schema, which adds id, type, version, created_at, updated_at, status, and tags automatically. You only define the fields specific to your domain.
The required: ["title"] means a task must have a title — Upjack validates this on every create_entity and update_entity call.
Step 4: Update the manifest
Section titled “Step 4: Update the manifest”Open todo/manifest.json and update the upjack extension:
{ "manifest_version": "0.4", "name": "todo", "version": "0.1.0", "title": "Todo List", "description": "A personal task manager", "server": { "type": "python", "entry_point": "server", "mcp_config": { "command": "python", "args": ["server.py"] } }, "_meta": { "ai.nimblebrain/upjack": { "upjack_version": "0.1", "namespace": "apps/todo", "entities": [ { "name": "task", "plural": "tasks", "schema": "schemas/task.schema.json", "prefix": "tsk", "index": true } ], "context": "context.md", "seed": { "data": "seed/", "run_on_install": true } } }}Key fields:
namespace: "apps/todo"— entity files are stored atworkspace/apps/todo/data/tasks/prefix: "tsk"— generated IDs look liketsk_01HZ3QKB...context— points to a Markdown file with domain knowledge for the agent
Step 5: Write domain knowledge
Section titled “Step 5: Write domain knowledge”Open todo/context.md and write what your agent needs to know:
# Todo List
You are managing a personal todo list. Help the user stay organized and prioritize effectively.
## Task Lifecycle
1. Created (status: active, priority assigned)2. Worked on (updated with progress notes)3. Completed (status: archived)
## Prioritization
- **Critical**: Urgent and important — do first- **High**: Important but not urgent — schedule time- **Medium**: Moderately important — fit in when possible- **Low**: Nice to have — do if time permits
## Rules
- Keep task titles short and actionable (start with a verb)- One task per action — break large tasks into smaller ones- Archive completed tasks, don't delete them (preserves history)This file is served to the agent as a resource (upjack://context). The agent reads it to understand how your app works — like an onboarding doc for a new hire.
Step 6: Add seed data
Section titled “Step 6: Add seed data”Update todo/seed/sample-tasks.json:
[ { "type": "task", "title": "Review project proposal", "priority": "high", "effort": "medium", "due_date": "2026-03-01" }, { "type": "task", "title": "Fix login bug", "priority": "critical", "effort": "small", "tags": ["bug"] }, { "type": "task", "title": "Write onboarding docs", "priority": "medium", "effort": "large" }]Seed data loads when the agent calls the seed_data tool. Upjack generates IDs, timestamps, and validates each record against your schema.
Step 7: Run it
Section titled “Step 7: Run it”cd todouv run python server.pyYour MCP server is running. It exposes these tools:
| Tool | What it does |
|---|---|
create_task | Create a new task (validates against your schema) |
get_task | Get a task by ID (tsk_...) |
update_task | Update task fields (merges by default) |
list_tasks | List active tasks (newest first) |
search_tasks | Search tasks by text or structured filters |
delete_task | Soft-delete a task (set status to “deleted”) |
seed_data | Load sample data from seed/ |
And these resources:
| Resource | What it provides |
|---|---|
upjack://context | Your context.md domain knowledge |
Step 8: Try it in Python
Section titled “Step 8: Try it in Python”You don’t need an MCP server to use Upjack. Use UpjackApp directly:
from upjack import UpjackApp
app = UpjackApp.from_manifest("manifest.json")
# Create a tasktask = app.create_entity("task", { "title": "Ship todo app", "priority": "critical", "effort": "small", "due_date": "2026-03-01",})print(task["id"]) # tsk_01HZ3QKB9YWVJ0RPFA7MT8C5Xprint(task["status"]) # active
# Search for itresults = app.search_entities("task", query="ship")print(len(results)) # 1
# Update priorityapp.update_entity("task", task["id"], {"priority": "high"})
# List all active taskstasks = app.list_entities("task")for t in tasks: print(f"{t['id']}: {t['title']} [{t.get('priority', 'none')}]")Every operation validates against your JSON Schema. Try creating a task with an invalid priority:
# This raises jsonschema.ValidationErrorapp.create_entity("task", {"title": "Bad task", "priority": "URGENT"})# ValidationError: 'URGENT' is not one of ['critical', 'high', 'medium', 'low', 'none']Going further
Section titled “Going further”Add more entity types. Want projects and labels? Add entries to the entities array in the manifest and create their schemas. Upjack generates 6 MCP tools per entity type — so 3 entity types gives you 18 tools.
Add skills. Write a skills/task-management/SKILL.md with prioritization rubrics, daily review procedures, or escalation criteria. Reference it in the manifest and it becomes a upjack://skills/task-management resource the agent can read.
Add hooks and schedules. Declare a hook that fires task-management when a new task is created, or a schedule that runs a daily review at 9 AM. These are declarative metadata — the NimbleBrain platform executes them, or you can read them from the manifest and wire them up yourself.
Use structured search. Upjack supports comparison operators:
# Find high-priority tasks due this weekapp.search_entities("task", filter={ "priority": {"$in": ["critical", "high"]}, "due_date": {"$lte": "2026-03-07"},})See the full examples/todo/ directory for a complete implementation with projects, labels, skills, hooks, schedules, and views.