Every tool group registered on the han-solo MCP server — what each tool does and which backend it touches. Source of truth for Claude Code and Ren when navigating the tool surface.
han-solo-mcp.onrender.com/mcp via Streamable HTTP (stateless). Tool groups are registered at startup in han_solo/server.py — each group lives in its own file under han_solo/tools/. Ren's canonical tool set last updated: 2026-06-13.
The MCP bridge is a Render-hosted Python service built on FastMCP (the official Python SDK for the Model Context Protocol). It is the only path between Claude Code and Han Solo's memory layer — Letta, the Postgres database, GitHub, and the Anthropic API are all accessed exclusively through this server. Claude never calls those services directly.
FastMCP("han-solo") with stateless_http=True. Endpoint: /mcp (Streamable HTTP). DNS rebinding protection is disabled — bearer token auth is enforced instead. Render auto-deploys on every push to main.
Every request requires Authorization: Bearer $MCP_TOKEN. The bridge supports three users with separate tokens: USER_TOKEN_SCOTT (owner), USER_TOKEN_TED (collaborator), USER_TOKEN_REN (agent). Pattern: USER_TOKEN_{USER_ID.UPPER()}. Tokens must be set in two places: ~/.zshenv AND the LaunchAgent plist EnvironmentVariables — updating one but not the other causes intermittent auth failures depending on how Claude Code was launched. The same two-place rule applies to LETTA_API_KEY.
Auth-exempt paths: /health, /, /chat, /workspace, /api/jobs-status, /api/code/webhook. GET-exempt: /api/session-logs, /api/transcripts, /api/verify-runs/latest, /api/usage/stats, /api/session-summaries, and the /api/transcripts/, /api/session-summaries/, and /docs/ prefixes.
16 tool groups register at startup via memory.register(server), signals.register(server), etc. in han_solo/server.py. Deploying new tool code to Render does NOT automatically register those tools into Letta's agent registry — that requires a separate POST /api/admin/sync-mcp-tools call after every deploy.
Ren (the Letta agent) has 67 tools attached as of 2026-06-13. These are a curated subset of the full MCP surface — not every tool Claude Code can call is available to Ren. The canonical set is defined in han_solo/letta_client.py as CANONICAL_REN_TOOL_NAMES and restored on every server startup via ensure_ren_tools().
Two letta_core built-ins must be explicitly attached — they are not discovered automatically by the MCP registration flow: send_message and archival_memory_search. Despite being built-ins, they do not auto-attach when tools are restored. Both are in CANONICAL_REN_TOOL_NAMES and attached by ensure_ren_tools().
archival_memory_insert is intentionally absent from the canonical set — governance question open. Ren cannot insert archival passages directly. Comment in letta_client.py: "search first, insert later." Unresolved as of 2026-06-01.
On startup: an httpx.AsyncClient is initialised, Ren's agent ID is resolved, ensure_ren_tools() runs to restore any dropped tools, and the asyncpg DB pool is opened. The FastMCP session manager and these initialisation steps run as a combined lifespan via contextlib.asynccontextmanager.
Agent ID resolution order: checks REN_AGENT_ID env var first (fastest — skips Letta lookup entirely; pinned in Render env vars for ren-v2). If not set, falls back to get_or_create_ren_agent(REN_AGENT_NAME).
Startup failure is non-fatal. If agent ID resolution or ensure_ren_tools() fails, the server logs 'Failed to initialise Ren agent (will retry on next request)' but does NOT crash. /health can return ok or degraded even when Ren has no resolved agent ID — a green health check does not guarantee Ren is reachable.
CORS wraps auth so preflight OPTIONS requests are handled before auth. Allowed origins: han-solo-docs.pages.dev, han-solo-mcp.onrender.com, han-solo-jottings.pages.dev, localhost:5173. CORS is outermost; BearerAuthMiddleware is inner. BaseHTTPMiddleware was avoided — it breaks SSE streaming.
Note: allow_origins also includes the literal string 'null' (intentional). This permits file:// origin requests — browsers send Origin: null when opening local HTML files. Not a misconfiguration, but worth knowing during security review.
Understanding the request path helps diagnose failures and reason about tool behaviour under load.
han-solo-mcp.onrender.com/mcp → BearerAuthMiddleware validates token → FastMCP routes to registered tool function → tool calls Letta and/or asyncpg → response returned synchronously.
Circular dependency rule. Any MCP tool that calls back into Letta's message queue (/v1/agents/{id}/messages) while Letta is waiting for that tool's response will deadlock. Two tools fell into this trap in May 2026: get_session_brief (4 Letta API callbacks) and search_signals (called letta.search_passages()). Both were removed from Ren's canonical set. The stress-test signature is alternating success/failure at 1-second intervals — not gradual degradation. Any new tool that makes outbound calls to han-solo-letta.onrender.com must be treated as a potential deadlock risk.
Failsafe channel. POST /api/admin/failsafe-message is a completely separate code path from the normal tool pipeline. Commands: PING, STATUS, DUMP_MEMORY, RELOAD_BRIEF. If the main MCP pipeline is broken, the failsafe still works. The two paths must never be merged or routed through the same middleware.
Health check endpoint. GET /health — no auth required. Returns {"status": "ok", "ren_agent": "...", "db": {...}} when both the Letta agent ID is resolved AND the asyncpg pool is connected. Before 2026-05-26, this endpoint only checked the agent ID, giving false confidence when the DB pool was down. Now it checks both. Edge case: if startup agent init fails non-fatally, the server runs but may return degraded — a green health response does not guarantee Ren is reachable. check_system_health checks five services in parallel (letta, voyage_ai, anthropic, mcp_bridge, core_memory) and is the more complete diagnostic — use it over raw /health when diagnosing Ren-specific issues.
write_core_memory notification removal. An older version of write_core_memory called send_chat_message() after the write to notify Ren. That notification routed through Letta's message queue — the same circular path as get_session_brief and search_signals. It was removed. Standing rule: Claude Code uses the bridge (send_to_ren) to reach Ren before writing to her core blocks during a live session. Ren writes her own blocks. Never route notifications through write_core_memory.
Code search rate limit. GET /api/code/search embeds queries via OpenAI — uncapped, this burns quota fast. Rate limit: 20 requests per 60 seconds per client IP. In-memory only (no Redis). Intentional for a single-process Render service.
/health endpoint also hangs during cold start. Wait and retry; it is not a failure.
POST /api/admin/sync-mcp-tools returns 200 per tool, but tools do not appear in agent.tools until Letta's MCP discovery completes. Testing tool availability immediately after sync will show false negatives. Wait 10–15 seconds or check again before concluding registration failed.
han-solo-letta.onrender.com.
check_system_health tool fires a critical alert if fewer than the canonical number of tools (67 as of 2026-06-13) are attached to Ren. This threshold is ALERT_TOOL_THRESHOLD in han_solo/tools/health.py and should match the current canonical count. Silent tool drops happen after certain Letta PATCH operations — verify_agent_tools is the diagnostic.
core_memory_append and core_memory_replace are accepted in Letta PATCH responses but Letta does not actually store them. Only external_mcp tools and the send_message letta_core built-in persist reliably. This is why Ren's core memory access uses read_core_memory / write_core_memory (external_mcp) rather than letta_memory_core tools.
llm_config alongside tool_ids in the same Letta PATCH causes letta_memory_core tools to drop. Patching llm_config alone (no tool_ids) is safe — tools survive. This is what caused tool drops when the now-removed model-switch endpoint was in use.
archival_memory_search. If api.voyageai.com is unreachable, archival passage search fails silently. check_system_health surfaces this: "Voyage AI down — archival_memory_search will fail. Check voyageai.com status." This is an external third-party service with no fallback. The health check hits https://api.voyageai.com/v1/embeddings as a connectivity-only check — no tokens are burned.
POST /api/code/webhook) checks for GITHUB_WEBHOOK_SECRET before processing. If the env var is not set, every POST returns 403: "webhook not configured (GITHUB_WEBHOOK_SECRET missing)." Intentional fail-closed to prevent unauthenticated re-indexing that burns OpenAI quota. If the secret is ever rotated without updating Render env vars, the codebase index goes stale silently.
gemini-2.5-flash (switched 2026-06-13 via Google AI BYOK provider), context window 1,000,000 tokens. The model-switch endpoint was removed permanently. If model changes are ever needed: two-step PATCH only — llm_config first (no tool_ids), then tool_ids restore as a separate call. Including both in a single PATCH silently drops letta_memory_core tools.
get_session_brief and search_signals both made outbound calls back into Letta's API while Letta was holding the connection open waiting for their MCP response. Under load this exhausted the connection pool. Symptom: intermittent "Ren could not be reached" errors in the workspace UI, alternating success/failure pattern. Diagnosis took two full sessions — cold start was blamed first incorrectly. Fix: both tools removed from the canonical set. Impact: ~3 days of intermittent failures.
While trying to fix tool registration issues, all 16 Letta tools were deleted via the Letta API directly. Ren had zero capability. Recovery required manual re-addition of each tool via direct PATCH to the Letta API. Root cause: using the wrong endpoint. Correct path is POST /v1/tools/mcp/servers/{server}/{tool_name} per tool — not bulk delete and re-add.
delete_notecard, append_t4_entry, update_open_threads, delete_archival_passage were deployed to the MCP server but not registered into Letta's registry. Ren could not call them — no error, they just were not in her tool list. Found during SL-007 regression testing. Fix: built /api/admin/sync-mcp-tools, registered individually, confirmed via agent-info. Post-deploy sync step permanently added to docs/add-tool-checklist.md.
A combined search tool was added that merged archival memory search with code wiki search into a single MCP call. This unified tool triggered SEARCH FIRST logic but had no clear intent — it could not distinguish between searching code or searching memory, causing cascade triggering and spiral loops where each search result triggered another search. Fix: reverted to separate search_t4 and search_code tools. Standing rule established: different search types require different gating. Conversational research (search_t4, search_transcripts) is autonomous. Code search is collaborative and intentional. Never merge them.
Documented failure patterns and gotchas that have burned real time. Read this section before diagnosing any unexpected bridge or tool behaviour.
/health endpoint also hangs during cold start — so health checks give false confidence that the server is down when it is actually waking up. Cold start was incorrectly blamed for the circular dependency incident, costing two full sessions of diagnosis. Wait and retry — it is not a failure.
ensure_ren_tools() or agent ID resolution fails at startup, the server logs "Failed to initialise Ren agent (will retry on next request)" but does NOT crash. /health can return ok or degraded even when Ren has no resolved agent ID. A green health check does not guarantee Ren is reachable. The /health endpoint now checks both agent_id AND db_connected (fixed 2026-05-26 — prior to that it only checked agent_id, giving false confidence when the DB pool was down).
/v1/agents/{id}/messages) while Letta is holding the connection open waiting for that tool's response will deadlock. Stress-tested: failures at calls 5 and 7, DNS failure by call 8 at 1-second gap intervals. At 2.5-second gaps: all 8 clean. The signature is alternating success/failure — not gradual degradation. This is what distinguishes it from cold start. Any new tool that makes outbound calls to han-solo-letta.onrender.com must be treated as a potential deadlock risk before adding to the canonical set.
llm_config alongside tool_ids in the same Letta PATCH causes letta_memory_core tools to drop silently. Patching llm_config alone is safe. Patching tool_ids alone is safe. This is what broke the now-removed model-switch endpoint. If model changes are ever needed: two-step PATCH only — llm_config first (no tool_ids), then tool_ids restore as a separate call.
core_memory_append and core_memory_replace are accepted in Letta PATCH responses but Letta does not actually store them. Only external_mcp tools and the send_message letta_core built-in persist reliably. Confirmed via stress test: added them, ran model switch, they dropped. This is why Ren's core memory access uses read_core_memory / write_core_memory (external_mcp) rather than letta_memory_core tools. Silent drops are not visible until Ren tries to call a missing tool and gets a ToolConstraintError. verify_agent_tools is the diagnostic.
USER_TOKEN_REN / USER_TOKEN_SCOTT must be set in both ~/.zshenv AND the LaunchAgent plist EnvironmentVariables. If rotated, both must be updated. Intermittent auth failures — some Claude Code sessions succeed, others fail — is the diagnostic symptom. Same two-place rule applies to LETTA_API_KEY.
check_system_health fires a critical alert if fewer than the canonical count (67 as of 2026-06-13) are attached. Run verify_agent_tools at session start and after any tool sync to catch drops early.
POST /api/admin/failsafe-message (commands: PING, STATUS, DUMP_MEMORY, RELOAD_BRIEF) is a completely separate code path from the normal tool pipeline. If the main MCP pipeline is broken, the failsafe still works because it has no dependency on FastMCP routing or the MCP session manager. Routing it through the same middleware or merging its code path would eliminate the only recovery path when the main bridge is down.
api.voyageai.com) is a live runtime dependency. If it is unreachable, archival passage search fails. check_system_health surfaces this: "Voyage AI down — archival_memory_search will fail. Check voyageai.com status." No fallback. The health connectivity check hits /v1/embeddings but burns no tokens.
POST /api/code/webhook returns 403 if GITHUB_WEBHOOK_SECRET is not set: "webhook not configured (GITHUB_WEBHOOK_SECRET missing)." Intentional — prevents unauthenticated re-indexing that burns OpenAI quota. If the secret is ever rotated without updating Render env vars, the codebase index goes stale and code search returns outdated results with no visible error.
The post-deploy sync step has been skipped twice and caused silent capability gaps both times. Read docs/add-tool-checklist.md in full (9 steps) before starting. The mandatory steps after any deploy that adds new tools:
han_solo/tools/ and call server.tool() via the register() function — same pattern as every other tool in that file.CANONICAL_REN_TOOL_NAMES in han_solo/letta_client.py. If not adding to canonical, document why in a comment next to the name.POST /api/admin/sync-mcp-tools (with Authorization: Bearer $MCP_TOKEN header). This calls GET /v1/tools/mcp/servers/han-solo/tools (live MCP call), registers missing tools individually via POST /v1/tools/mcp/servers/han-solo/{tool_name}, then runs ensure_ren_tools(). A clean response looks like {missing_found: [], registered: [], errors: []} — meaning tools were already registered from a previous sync. Note: ensure_ren_tools() fires at startup and attaches everything in the canonical set that is already in Letta's registry — Letta does NOT automatically discover new MCP tools after initial server connection. New tools must be explicitly registered into Letta's registry before ensure_ren_tools() can attach them.scripts/verify.py: add the tool name to EXPECTED_HAN_SOLO_TOOLS. If the tool is destructive, also add it to ALLOWED_WRITE_TOOLS_ON_REN.GET /api/admin/agent-info — confirm the new tool name appears in the agent_tools list and the count is expected (67 + any newly added tools). If not present after 15 seconds, wait another 15 and re-check before concluding registration failed.send_to_ren before declaring done.Diagnostic order if tool calls silently fail or return empty from Claude Code: (1) check bridge health at /health → (2) check Letta is reachable → (3) verify token is valid → (4) confirm tool is registered via agent-info. In that order. Do not go straight to destructive Letta API operations.
Read and write Letta core memory blocks — the always-loaded context blocks that travel with every Ren prompt. Also manages archival passage lifecycle and open threads.
| Tool | What it does | Backend |
|---|---|---|
| read_core_memory | Read a named core memory block by label (e.g. always_loaded_core, pending_thoughts, project_state). Returns the block value as a string. |
Letta |
| write_core_memory | Full-replace a named core memory block. Protected blocks (user_registry, api_keys, etc.) require owner-level access and cannot be written by Claude. |
Letta |
| list_core_memory_blocks | List all core memory blocks with label, character count, and a 100-char preview. Used at session start to survey memory state. | Letta |
| check_memory_capacity | Check the pending_thoughts block fill level before writing. Returns {current_chars, block_limit, pct_full, status} where status is ok / warning / critical. Prevents silent overflows that break the nightly dream script. |
Letta |
| log_memory_access | Record every archival memory search for access pattern analysis (the Memory MRI). Logs the query, which passage IDs were returned, and whether any were used. Builds the dataset that surfaces cold passages and dry wells. | Postgres |
| delete_archival_passage | Delete an archival passage by Letta passage ID. Two-step: call with confirmed=False to flag, then confirmed=True after Scott approves. Permanent — no undo. |
Letta |
| update_open_threads | Add, close, or remove items from Ren's open_threads core memory block. Action values: add / close / remove. Matching on close/remove is case-insensitive substring. |
Letta |
| enrich_passage | Record context about how an archival passage was used during retrieval (memory reconsolidation). Context notes accumulate — each append is preserved so future retrievals carry richer context. | Postgres |
Write and search the signal taxonomy — typed observations about people and work that flow into portrait and archival memory. Four signal types: relational, directional, ren, texture. Five subjects: scott, ted, ren, project, framework.
| Tool | What it does | Backend |
|---|---|---|
| write_signal | Write a typed signal to Letta archival memory. Texture signals have no noise filter — write small things as they happen. Validates signal_type and subject before writing. Also dual-writes to the signals Postgres table for structured querying. |
Letta Postgres |
| search_signals | Semantic search across archival passages tagged as signals. Optional filters for signal_type and subject. Returns type, subject, date, and content for each match. | Letta |
| write_session_summary | Write a structured session-close summary to archival memory. The raw material the nightly dream script processes. Expected structure: date/participants, decisions, relational signals, texture signals, Ren self-observations, pending thoughts, open threads. | Letta |
The Solo Hook (framework-skill-inject.sh) calls get_session_brief before every Claude Code session, injecting Ren's pending thoughts and always-loaded context automatically.
| Tool | What it does | Backend |
|---|---|---|
| get_session_brief | Retrieve the session opening brief: always-loaded core context, pending thoughts from last session, the 5 most recent archival signals, and current date/time in CT. Called at session start by the hook — Claude begins every session already oriented. | Letta |
| write_pending_thoughts | Full-replace the pending_thoughts core block. Called at session close. Ren's concerns, ideas, and connections for the next session. Check capacity with check_memory_capacity before writing large entries. |
Letta |
| check_memory_health | Check memory system health: failed T1→T2/T2→T3 tier promotions in the last 24 hours and transcript capture status. Flag anything non-zero to Scott immediately. Call at every session start. | Postgres |
Phase gate tools — read and advance project state stored in the project_state core memory block as JSON. Gates block advancement until prerequisites are met.
| Tool | What it does | Backend |
|---|---|---|
| get_project_state | Read current project state: active phase, completed gates, artifact inventory, current slice, and open amendments. Source of truth for phase awareness. | Letta |
| check_phase_gate | Check whether a transition to a target phase is currently allowed. Returns {allowed, reason, missing}. Valid target phases: discover, tech_context, shape, prd_to_plan, build_slice_1, build_next, deploy. |
Letta |
| advance_phase | Advance project state to a target phase. Blocked if gate prerequisites are unmet — returns the blocking reason. Records phase history with actor and timestamp. | Letta |
| record_artifact | Record that a phase artifact exists, unlocking downstream phase gates. Example keys: brainstorm_artifact, backlog, shape_document, previous_slice_qa_cleared. |
Letta |
Living portrait tools — Ren's evolving interpretations of Scott, Ted, and herself, stored as core memory blocks with two layers: forming (emerging, held lightly) and trusted (confirmed across repeated observation).
| Tool | What it does | Backend |
|---|---|---|
| read_portrait | Read a portrait layer for a person. Returns Ren's prose interpretation, not a summary. person: scott | ted | ren. layer: forming | trusted. |
Letta |
| write_portrait | Full-replace a portrait layer. Prose in Ren's voice — not bullet points. Only update the trusted layer when a pattern has been observed enough times to confirm. | Letta |
| add_portrait_signal | Append a single dated observation to a person's forming portrait. One specific, non-obvious thing noticed this session. Also sends Ren a live system notification so she can incorporate the signal without waiting for the next session. | Letta |
| read_all_portraits | Read all portrait blocks for Scott, Ted, and Ren — both forming and trusted layers — in a single call. Returns {person: {layer: content}}. Used at session open for full relational context. |
Letta |
Low-ceremony mid-session captures. A notecard is not a task and not a thread — just text, who wrote it, when, and where it came from. Statuses: active, completed, archived.
| Tool | What it does | Backend |
|---|---|---|
| create_notecard | Create a notecard. Priority 1–5 (default 3). Source: chat (mid-session) or manual. Returns the card ID. |
Postgres |
| update_notecard | Update a notecard's status, text, and/or priority by ID. At least one field required. Status options: active, completed, archived. |
Postgres |
| delete_notecard | Delete a notecard with a two-step approval flow. Call with confirmed=False first — marks as pending_deletion and returns content for Scott to review. Call with confirmed=True to permanently delete. |
Postgres |
| list_notecards | List notecards by status. Omitting status returns active + completed. Returns id, text, creator, status, source, session_id, created_at. | Postgres |
T4 is the persistent project memory store — framework artifacts produced across the Solo Builder Framework build cycle, organized by project slug and entry type. Three write behaviors: write_once, upsert, append — derived from entry type, never specified by the caller.
| Tool | What it does | Backend |
|---|---|---|
| write_t4_entry | Write a T4 artifact. Hierarchy types (phase, deliverable, slice) require plain_language and technical_description. Slices accept four anchors (design, data, done, process) and a quality contract. Write behavior is auto-derived from entry type. |
Postgres |
| get_t4_entry | Fetch a T4 artifact by project, entry_type, and entry_id. Auto-ID types (e.g. tech_context, decisions_log) don't require an explicit entry_id. Returns full content or an error dict. |
Postgres |
| search_t4 | Keyword search across T4 artifacts for a project. Optional entry_type filter. Returns matching entries with an excerpt (first 300 chars) and updated_at timestamp. | Postgres |
| delete_t4_entry | Delete a T4 entry by project, type, and ID. Omitting entry_id deletes ALL entries of that type for the project. Returns the count of deleted rows. | Postgres |
| list_t4_projects | List all T4 projects with owner, visibility, current_phase, and slice progress counts (total/done). Use at session start before assuming what projects exist. | Postgres |
| append_t4_entry | Force-append to any T4 entry regardless of its normal write behavior. Each append is timestamped and separated with a divider. Creates the entry if it doesn't exist. Use for mid-session notes on handoffs or additions to decisions logs. | Postgres |
Framework skill tools — the skills table in Postgres is the authoritative source of Solo Builder Framework phase skill content, populated by the sync pipeline and injected into Claude Code sessions via the hook on every UserPromptSubmit.
| Tool | What it does | Backend |
|---|---|---|
| get_skill | Read a framework phase skill by slug. Returns {phase_slug, layer, content, updated_at}. Common slugs: discover, solo-build, design-sprint, deploy, and others. |
Postgres |
| write_skill | Create or update a framework phase skill. Minimum 50 chars — rejects suspiciously short content. Defaults to layer phase-active. Use after editing a SKILL.md in ~/Developer/Framework Vers1/skills/. |
Postgres |
| verify_skills_sync | Compare Framework Vers1 skill files on disk against Han Solo DB records using SHA-256 content hashes. Returns {in_sync, diverged, missing_from_db, missing_from_disk}. Flag diverged entries to Scott — never auto-fix. |
Postgres |
| list_skills | List all framework phase skills in Han Solo — slug, layer, content length, and last update. Use before calling get_skill to discover available slugs. |
Postgres |
Session log entries — what happened, decisions made, open threads, and commit references. Two levels: major (architecture changes, significant decisions) and minor (routine work).
| Tool | What it does | Backend |
|---|---|---|
| write_session_log | Write a session log entry to the Han Solo project logbook. Required: summary (3–5 sentences, plain language). Optional: decisions, open_threads, commit_refs (comma-separated), tags (comma-separated system names), level, session_date. |
Postgres |
Read-only search across parsed Claude Code session transcripts stored in Postgres by the parse_transcripts.py LaunchAgent. Sessions from the last 45 days. No Anthropic API calls — raw session history only.
| Tool | What it does | Backend |
|---|---|---|
| search_transcripts | Search parsed Claude Code session transcripts by keyword. All words must match. Returns session IDs, timestamps, entry counts, and a content preview. Limit default 10, max 50. | Postgres |
Synchronous communication from Claude Code to Ren. Messages are prefixed with [FROM CLAUDE CODE] so Ren knows the channel. The exchange lands in Ren's Letta conversation history — she remembers it. Token usage is logged to Postgres via db.write_usage_log("bridge", ...) as a background task. Does not appear in the workspace UI chat. Added 2026-05-24.
| Tool | What it does | Backend |
|---|---|---|
| send_to_ren | Send a message to Ren synchronously and return her response. Use for architecture decisions before implementing, assumption audits, continuity checks ("why did we build it this way?"), and session-close handoff notes. Token usage is logged. | Letta Anthropic API |
Codebase reading and semantic search. Embeddings are stored in the code_chunks Postgres table (OpenAI text-embedding-3-small), re-indexed automatically on push to main via a GitHub webhook (POST /api/code/webhook, HMAC-SHA256 signature validated). HTML files are excluded from search results — they are docs artifacts, not implementation code. The /api/code/search endpoint is rate-limited to 20 requests per 60 seconds per client IP (in-memory, intentional for single-process Render).
| Tool | What it does | Backend |
|---|---|---|
| read_github_file | Read a live file from the GitHub repo (not the indexed Code Wiki). Use to verify current implementation before making decisions, or read a file not yet indexed. Supports any public repo. Uses GITHUB_TOKEN if set (5,000 req/hr); falls back to unauthenticated (60 req/hr). |
GitHub API |
| fetch_url | Fetch live content from any public URL. Use for reading live docs pages, verifying a deploy is live, reading Letta release notes, or any public HTTP resource. Content truncated at 50,000 chars. No auth header support. | HTTP |
| search_code | Semantic search across the han-solo codebase by intent. Embeds the query via OpenAI, computes cosine similarity in-memory against indexed chunks. Use to understand how something works, find where a behavior is implemented, or trace impact of a change. Limit default 5, max 10. | Postgres OpenAI |
System health checks — five services checked in parallel: letta (agent reachable, all canonical tools attached), voyage_ai (embedding service connectivity), anthropic (API key configured, /v1/models reachable), mcp_bridge (external self-ping at han-solo-mcp.onrender.com/health — localhost self-ping fails on Render due to network isolation), and core_memory (reads pending_thoughts block from Letta). Each check has a 5-second timeout. Writes and resolves alerts in Postgres as a background task. Alert threshold: fewer than 67 canonical tools attached triggers a critical tool_count alert (as of 2026-06-13).
| Tool | What it does | Backend |
|---|---|---|
| check_system_health | Full health check across Letta, Voyage AI, Anthropic API, MCP bridge, and core memory. Checks run in parallel with a 5-second timeout per service. Returns {all_healthy, services, tools_attached, recommendations, timestamp}. If not all_healthy: surface to Scott immediately, pause work, troubleshoot together. Also writes/resolves alerts in Postgres as a background task. |
Letta Postgres Anthropic API |
| verify_agent_tools | Diff Ren's currently attached Letta tools against the canonical expected set. Returns {attached, missing, extra, canonical_count, attached_count, all_match}. Use at session start and after any tool sync operation — silent tool drops are not visible until Ren tries to call a missing tool. |
Letta |
Hub component health verification before slice gates. The hub tracks 9 system components, each seeded with vitals, incidents, danger zones, and assumptions from T4 knowledge base entries.
| Tool | What it does | Backend |
|---|---|---|
| check_hub_health | Verify all hub components are seeded with vitals, danger zones, and assumptions. Use before approving any slice gate — zero counts on danger_zones or assumptions means the component is not fully seeded and the gate should not open. Returns {components, all_seeded, unseeded}. |
Postgres |
Gate artifact tools — approve slice gates and generate permanent HTML snapshots. The approval marker is a prerequisite for artifact generation; the artifact is then served at a stable URL.
| Tool | What it does | Backend |
|---|---|---|
| approve_gate | Append a Scott approval marker to the T4 gate brief entry for a slice. Call immediately after Scott says yes in chat. The marker is required before write_gate_artifact will render an HTML artifact. Idempotent — safe to call if already approved. |
Postgres |
| write_gate_artifact | Generate a permanent HTML artifact from an approved T4 gate brief. Checks for the Scott approval marker before rendering. Stores artifact HTML as a gate_artifact T4 entry. Returns the stable URL at /api/gates/{project}/{slice_id}/artifact. |
Postgres |
Claude Plus session activation — fetches all 10 persistent blocks Claude Plus needs at session start to load its operating context, state machine, and pending work. All data lives in dedicated Postgres tables separate from Ren's memory.
| Tool | What it does | Backend |
|---|---|---|
| get_claude_plus_blocks | Fetch all 10 Claude Plus blocks in a single call: identity_and_role, operating_contract, hub_snapshot, session_shape, slice_sizing_log, state_machine, session_close, pending_decisions (open items only), failure_recovery, and framework_state. Returns each block's content and metadata. |
Postgres |
The same Render process that hosts the MCP server also serves a full Jottings REST API — approximately 20 endpoints. These are not accessible to Claude Code as MCP tools; they are called by the Jottings frontend (han-solo-jottings.pages.dev). Auth is required except as noted. S3 uploads are limited to 10 MB and accept jpeg, png, gif, and webp only.
| Endpoint | What it does | Backend |
|---|---|---|
| POST /jottings/topics | Create a new topic. | Postgres |
| GET /jottings/topics | List all active topics. | Postgres |
| GET /jottings/topics/{id}/pages | List all pages for a topic. | Postgres |
| POST /jottings/pages | Create a new page. | Postgres |
| GET /jottings/pages | List pages, optionally filtered by topic. | Postgres |
| PUT /jottings/pages/{id} | Autosave page content (title + body). Called on keystroke debounce from the editor. | Postgres |
| POST /jottings/pages/{id}/replies | Add a reply to a page. | Postgres |
| GET /jottings/pages/{id}/replies | List replies for a page. | Postgres |
| POST /jottings/upload | Upload an image to S3. 10 MB limit, jpeg/png/gif/webp only. Returns a public S3 URL for embedding in page content. | S3 |
| POST /jottings/search | Full-text search across pages and replies. | Postgres |
| PATCH /jottings/topics/{id} | Soft-archive a topic (sets archived flag, does not delete). | Postgres |
| PATCH /jottings/pages/{id} | Soft-archive a page. | Postgres |
| DELETE /jottings/topics/{id} | Hard-delete a topic including all associated S3 assets. Permanent. | Postgres S3 |
| DELETE /jottings/pages/{id} | Hard-delete a page including all associated S3 assets. Permanent. | Postgres S3 |
| Badge | What it means |
|---|---|
| Letta | Reads or writes Ren's Letta agent — core memory blocks, archival passages, or conversation history. Touches han-solo-letta.onrender.com. Every Letta call is a round-trip that may trigger Anthropic API inference internally. |
| Postgres | Reads or writes the han-solo-db PostgreSQL 16 database directly via the asyncpg connection pool. No Letta involvement. Covers T4, notecards, signals, transcripts, logbook, skills, notecards, code chunks, hub tables, and Claude Plus tables. |
| Anthropic API | Makes a live request to the Anthropic API — either a connectivity check or an actual model inference call (via Letta's bridge path). Pay-per-token. |
| OpenAI | Calls OpenAI's embeddings API (text-embedding-3-small) to embed a query for semantic search. Used by search_code. Requires OPENAI_API_KEY env var. |
| GitHub API | Reads file content from api.github.com. The han-solo repo is public so unauthenticated reads work (60 req/hr); GITHUB_TOKEN raises that to 5,000 req/hr. |
| S3 | AWS S3 bucket used by the Jottings API for image uploads and hard-delete cleanup. Requires AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and S3_BUCKET_NAME env vars. Not used by any MCP tools — Jottings REST API only. |