Skip to main content
Glama

Duebook

An MCP server that keeps a household's hard deadlines (visa, school, lease, insurance) in a markdown vault. Built for the Amazon Developer Hackathon 2026, Alexa+ track.

  • Transport: Streamable HTTP, MCP spec 2025-11-25 (mcp==1.30.0)

  • Storage: vault/*.md. One file per deadline, YAML front-matter. The files are the database; nothing lives in memory between requests.

Tools

Tool

What it does

list_due(window_days=30)

Overdue open deadlines first, then the next N days; includes source and confidence.

add_deadline(title, due, kind, source, confidence=0.8)

Writes vault/<title-slug>-<due>.md, returns its path. Refuses a duplicate title + due. due is YYYY-MM-DD; kind is hard or soft.

find_conflicts(window_days=7)

Pairs of open deadlines due within N days of each other, each explaining why the clash matters.

ingest_document(path_or_text, hint=None)

Extract a document deadline through Bedrock; save or ask for clarification.

Related MCP server: big-brain

Prerequisites

  • uv (brew install uv). It fetches Python 3.12 itself.

  • Node.js 18+ (for npx). Needed only for the MCP Inspector and for the Claude Desktop bridge.

Run the server

uv sync
uv run duebook

The server listens on http://127.0.0.1:8000/mcp. Leave it running in its own terminal.

Options (environment variables):

Variable

Default

DUEBOOK_VAULT

<repo>/vault

Vault directory. Point it at a copy to experiment without touching the seed data.

DUEBOOK_PORT

8000

DUEBOOK_HOST

127.0.0.1

Keep it on localhost; there is no auth in this phase.

Run the tests and linter:

uv run pytest
uv run ruff check . && uv run ruff format --check .

Connect from MCP Inspector

With the server running, in a second terminal:

npx @modelcontextprotocol/inspector@2.7.0

In the Inspector page that opens:

  1. Transport Type: Streamable HTTP

  2. URL: http://127.0.0.1:8000/mcp

  3. Click Connect, then Tools → List Tools. You should see list_due, add_deadline, find_conflicts and ingest_document.

  4. Run find_conflicts with the default window. It should report the visa renewal colliding with the school fee.

Connect from Claude Desktop

Claude Desktop's config file only starts stdio servers, and its Custom Connectors don't accept localhost URLs. To reach this local Streamable HTTP server, it launches mcp-remote, a small stdio↔HTTP bridge.

  1. Start the server (uv run duebook) and leave it running in its own terminal.

  2. Find where your Node.js is installed:

    dirname "$(which npx)"

    Claude Desktop doesn't inherit your shell's PATH, so a bare npx often isn't found, especially if Node came from nvm, asdf or volta. The config below uses this directory (<NODE_BIN>) for both the npx path and a PATH that includes node, since npx is itself a #!/usr/bin/env node script.

  3. Quit Claude Desktop fully (⌘Q). If you edit the config while the app is running, it can overwrite your changes when it quits.

  4. With the app closed, open the config:

    open -e ~/Library/Application\ Support/Claude/claude_desktop_config.json

    Add an mcpServers key next to whatever is already in the file (keep existing keys such as preferences), replacing <NODE_BIN> with the output of step 2:

    "mcpServers": {
      "duebook": {
        "command": "<NODE_BIN>/npx",
        "args": ["-y", "mcp-remote@0.14.3", "http://127.0.0.1:8000/mcp"],
        "env": {
          "PATH": "<NODE_BIN>:/usr/bin:/bin"
        }
      }
    }
  5. Check the edit was saved (this should print the duebook entry, not None):

    python3 -c "import json,os;print(json.load(open(os.path.expanduser('~/Library/Application Support/Claude/claude_desktop_config.json'))).get('mcpServers'))"
  6. Open Claude Desktop, start a new chat, and open the + menu → Connectors. duebook should be listed with its four tools.

If it doesn't connect, rerun the step 5 check (if it prints None, the app overwrote the file, so quit it and add the entry again), then check the logs:

tail -n 50 -f ~/Library/Logs/Claude/mcp*.log

Vault format

---
title: Visa renewal — Parent A
due: 2026-11-15
window_start: 2026-11-10        # optional
kind: hard                      # hard | soft
source: "Immigration department letter, 2026-08-30"
confidence: 0.95                # 0–1
status: open                    # open | done | cancelled
---
Free-text notes.

The five seeded files in vault/ are made-up examples with no real personal data.

Licence

MIT. See LICENSE.

Document ingestion (Week 2)

ingest_document(path_or_text, hint=None) reads a local PDF, UTF-8 .txt/.eml, or pasted text and sends extracted text to Amazon Bedrock. It returns either:

  • saved: path and extracted entry with title, due, window, classification reason, source quote, confidence and notes. Duplicate title/date entries are refused.

  • needs_confirmation: candidate and one question. Ask the user that question; call again with the same document and their answer in hint.

An invalid response or AWS error fails without saving. Scans need pasted text; no OCR. The insurance fixture needs a receipt date: I received it on 2026-10-01. produces 2026-10-31. Self-reported model confidence is a heuristic, not a calibrated probability. Schema and quote checks cannot prove that a semantically wrong date is correct.

AWS setup

From the repository root (use /usr/local/bin/aws if the Homebrew CLI is broken):

uv sync
/usr/local/bin/aws login --profile duebook
export AWS_PROFILE=duebook
export AWS_REGION=ap-southeast-1
export DUEBOOK_MODEL_ID=apac.amazon.nova-lite-v1:0

Then run uv run duebook in that same terminal. boto3[crt] supports the browser-login credential chain. Refresh expired sessions with the login command above. Clients that start the server themselves must pass AWS_PROFILE and AWS_REGION in its environment. The default model is APAC Nova Lite, using cross-region inference within APAC.

Nova returns the extract_deadline tool's JSON-schema payload through Converse; we do not parse prose or Markdown-fenced JSON. Application checks require strict ISO calendar dates, a 0–1 finite confidence, allowed fields, and a source quote found in the submitted document (whitespace normalized). Missing dates, confidence below 0.7, ambiguities or clarification questions prevent writes. Renewal windows and notes persist.

Cost and input limits

One Converse request per ingestion attempt (at most two HTTP attempts on transient failures). The document plus hint is capped at 12,000 characters before JSON serialization; fixed prompt/schema overhead is additional. Hints are limited to 2,000 characters. Long documents use head + tail and always require clarification with a shorter relevant section. Output is capped at 1,400 tokens. Input/output token counts are logged to stderr without document content. Calls are billable; the $20 AWS budget is an alert, not a spending cap. Credentials stay in the standard AWS credential chain, never the repository.

Verify without changing the demo vault

uv run pytest
uv run ruff check .
uv run ruff format --check .
# One opt-in billable live test, immigration fixture only:
DUEBOOK_LIVE=1 uv run pytest tests/test_ingest.py -k live
# Four billable calls, three synthetic documents + clarification; temporary vault only:
uv run python scripts/check_ingestion.py

Offline regressions replay actual Nova Lite responses from synthetic fixtures in tests/fixtures/bedrock/. To deliberately refresh these recordings, run uv run python scripts/check_ingestion.py --record and review the resulting JSON changes. Real personal documents must never be used to generate committed recordings.

Reference: Nova tool choice for structured extraction.

For the supported within N days of receipt pattern, the application requires an explicit receipt date in the hint and calculates the deadline itself. Missing year evidence also requires confirmation. Opening windows require explicit opening language in the document; discount and payment-plan paragraphs are retained as context. These guards cover known failure cases, not every natural-language date expression. The school fixture's late-payment penalty led Nova to classify it as hard; an invitation to request a payment plan does not prove that an alternative deadline has been approved.

list_due and conflict summaries also return persisted notes, window_start and a confidence_note. Notes distinguish user-supplied clarification from the source quote and preserve date calculations. A confidence score does not independently verify the receipt date or other user-supplied facts. Clients should show this provenance when explaining calculated dates. Existing records are returned without rewriting them.

Local Alexa+ simulation (Week 3)

A text-based web experience with real Strands/Bedrock responses and real MCP tool calls. It is clearly labelled as a simulation; it has no connection to Alexa's backend.

uv sync --extra demo
# Refresh only when your AWS session has expired:
/usr/local/bin/aws login --profile duebook
export AWS_PROFILE=duebook
export AWS_REGION=ap-southeast-1
uv run --extra demo duebook-demo

Open http://127.0.0.1:8080. The launcher starts its own local MCP subprocess on an available port, so an existing OpenWork server on port 8000 can keep running. Ctrl+C stops the demo and its child server. Use --port 8081 if 8080 is occupied.

Try the demo

  1. Ask What's due soon? Strands calls list_due and find_conflicts through MCP; Checks performed shows the actual calls. The cards use a 60-day horizon and a seven-day gap to flag clashes. Chat also fixes the clash gap to seven days.

  2. Choose the synthetic Insurance sample. The notice lacks a receipt date; Duebook asks one question and saves nothing.

  3. Enter 2026-10-01 in the clarification control (a made-up test input), then confirm. The resulting deadline is 2026-10-31. Expand Source & calculation to see the exact quote, user clarification and date calculation separately.

  4. Choose New conversation, then ask what Duebook remembers. Saved deadlines are read afresh from files; the prior conversation is not sent to the model.

The optional demo extra installs Strands Agents. Chat uses APAC Nova Lite by default; DUEBOOK_AGENT_MODEL_ID overrides only the conversation model. Extraction still uses DUEBOOK_MODEL_ID. The agent receives only the two read tools; upload and clarification controls call ingest_document directly, so chat cannot invent a receipt-date answer and save it. The question text and documents still require human judgment.

Browser → local demo service → Strands Agent → Amazon Bedrock (Nova Lite)
                                ↓ tool calls
                         Streamable HTTP MCP → markdown vault
Browser upload/clarification → MCP ingest_document → Bedrock → validate → vault

Data, limits and scope

  • .duebook-demo/vault/ is a separate persistent synthetic vault, seeded once with five examples dated October–December 2026. The normal vault/ is not changed. For a fresh run use --data-dir /tmp/duebook-demo-fresh; choose a new directory each time.

  • Conversation history and pending uploads last only for this process; saved deadlines survive restarts. Uploads are temporary and removed after saving/cancelling or shutdown. A new conversation leaves the previous one in memory until shutdown (maximum 50).

  • PDF/TXT/EML uploads are limited to 2 MiB. Extracted text goes to Bedrock in APAC. Use synthetic files. Credentials remain in the standard local AWS chain.

  • Each chat turn is limited to five model calls, 500 output tokens per call, twelve prior messages, and a 2,000-character question. HTTP retries are capped at two attempts. Ingestion has the separate limits above. These are bounds, not a dollar spending cap.

  • Loopback only, same-origin request checks, no external frontend assets, and no HTML rendering of model/document text. This is a single-user local demo, not an authenticated public service. Do not expose it through a tunnel.

  • No voice, Alexa backend, AgentCore deployment or calendar sync. Opt-in email reminders now run locally; see the setup and execution limits below. Open deadlines and future dates are demo facts, not real household obligations.

The rules and organizer clarification allow a custom text-based simulated experience. The AWS mini-challenge accepts documented Bedrock/Strands use; AgentCore is not required. MIT licensing this repository alone does not meet the separate open-source mini-challenge contribution requirement.

Date-provenance safeguard: English questions mentioning receipt, calculation, source, evidence or where a date came from use a fresh MCP read and a deterministic rendering of stored fields, with zero model calls. Name the deadline (or refer to a title in your earlier question). If no title matches, the demo asks you to choose one. It does not reverse-calculate missing receipt dates or treat prior chat as evidence. The view covers open records through 365 days; records outside that view are not asserted absent from the vault. This safeguard is intent-based; general chat remains model-generated and needs review against the cards.

Opt-in email reminders

The demo now has a local background worker that sends through Amazon SES. It runs while duebook-demo is running and the Mac is awake, even with the browser closed. It is not an always-on AWS scheduler. AWS login must remain valid; expired credentials pause the affected plan and show an error. Email acceptance by SES does not prove inbox delivery.

  1. Verify the sender in SES in ap-southeast-1. If the account is in the SES sandbox, verify the recipient in that same region too. The same address can fill both roles. Use the SES console's email identity verification and click the link in your inbox.

  2. Configure your local environment before starting the demo:

    export AWS_PROFILE=duebook
    export AWS_REGION=ap-southeast-1
    export DUEBOOK_EMAIL_FROM='your-verified-address@example.com'
    export DUEBOOK_EMAIL_TO='your-verified-address@example.com'
    uv run --extra demo duebook-demo

    Alternatively, place {"from":"...","to":"..."} in email.json inside the selected --data-dir. Restart after changing it. Keep that directory inside ignored .duebook-demo/; never commit email settings or reminder state. Environment values override the file. No new dependencies or AWS keys are needed.

  3. Click Preview email on an open deadline. Review the exact recipient, sender, subject, source/context and schedule before choosing Enable these email reminders. Send this email once is a separate explicit action, not automatic enrollment.

  4. Future sends are at 09:00 Asia/Hong_Kong, seven days before, one day before and on the due date. Past trigger dates are skipped at enrollment. If none remain, only the one-time preview send is available.

  5. Snooze 24 hours postpones the next pending send by 24 hours (or 24 hours from now if already overdue), coalescing any intervening triggers. Cancel reminders leaves the deadline open. Mark done updates its vault status, stops its reminders, and removes it from open lists/conflicts; the UI retains it under Completed deadlines.

The email body is fixed from the approved preview, including clashes as they were at preview time. Source/notes changes pause the plan for another preview. Changing the due or title invalidates the old plan because it identifies a different record. Dates and sources in emails are rendered from stored fields, not generated by a language model. There are no remote action links: use the Mac app to snooze, cancel or mark done.

Schedules, consent time, job statuses and SES acceptance IDs persist in private reminders.json beside the vault. Atomic writes and a file lock serialize local workers and controls. Before a send, the worker records an attempt; SDK retries are disabled. A timeout or interrupted send becomes an unknown outcome and pauses automatic sends, rather than risking duplicates. Check your inbox before explicitly previewing/re-enabling. This favors avoiding duplicate emails over guaranteed delivery. Cancelling cannot recall an email already submitted to SES. Deleting reminder state destroys duplicate protection.

Limits: one test attempt per deadline per five minutes, 20 email attempts per Hong Kong calendar day per data directory, at most five due sends per worker tick. Repeated missed triggers after a restart coalesce into one email per deadline. Separate vaults have separate limits; these are not an account-wide dollar cap. Sending incurs SES usage. The worker checks every 30 seconds; scheduled times are approximate, with delayed catch-up if the Mac was asleep. No overdue daily escalation or cloud deployment in this version.

References: SES verification, SES SendEmail.

AWS reminder delivery (optional, authorized October 5)

infra/build_template.py generates a CloudFormation template for a private S3 state bucket, a Lambda worker, and EventBridge Scheduler (every minute). No public endpoint is created. Only explicitly previewed/approved emails are uploaded. The vault and chat stay local. AWS resources incur usage charges; remove the stack when the demo is retired. The state bucket is retained on deletion to preserve the delivery audit; delete it separately only when that audit is no longer needed.

Deploy with a verified SES address in the same region:

python infra/build_template.py > /tmp/duebook-cloud.json
aws cloudformation deploy --template-file /tmp/duebook-cloud.json \
  --stack-name duebook-reminders --parameter-overrides Email=YOUR_VERIFIED_EMAIL \
  --capabilities CAPABILITY_IAM --profile duebook --region ap-southeast-1

Keep email.json as above. Put {"function":"FUNCTION_ARN_FROM_STACK_OUTPUT"} in cloud.json inside the chosen ignored .duebook-demo/ data directory, then restart. Cancel any active local plans first; startup refuses to run cloud mode alongside them. Existing local plans are never silently copied or enrolled. Old local attempt history is retained in its file; cloud delivery has a separate history.

In cloud mode the UI says AWS delivery ready. Preview, enable, snooze, cancel and done use the authenticated Lambda service; the local background watcher only pauses cloud plans when local files change. A cloud failure cannot silently mark a local deadline done. The Mac and browser may be closed after approval. AWS uses the last approved email body; offline edits to the markdown vault do not change cloud plans. Reopen the app online to reconcile, or cancel a plan explicitly before editing. A cloud request can be busy while another is running: refresh before retrying an uncertain mutation. Cancel cannot recall an email already claimed for delivery.

The worker's reserved concurrency must remain one: all commands and scheduled checks share this single serialized writer. S3 state is persisted before SES is called. Unknown outcomes pause the plan and are not automatically retried. A crash before the actual send can therefore skip an email; avoiding duplicates takes priority. SES acceptance is not proof of inbox delivery. Missed triggers coalesce; checks run roughly once per minute, not at an exact second. The daily cap is 20 attempts for this cloud deployment, five per tick. Use CloudWatch errors and the private state's last_tick to diagnose scheduling failures. This is a demo service, without bounce monitoring, public hosting, or remote email actions.

AWS references: scheduled Lambda invocation and Scheduler timing.

Related MCP Connectors

Related MCP Servers