Skip to main content
Glama
Lazy-Coder-03

leftbrain

leftbrain

The left brain for your AI agent. Exact, deterministic answers for everything language models are bad at — arithmetic and calculus, dates and timezones, unit conversion, proportional scaling, counting, sorting, validation, randomness, hashes — plus optional live data (weather, FX rates) and file tools (PDF text, image → base64).

Use it as a plain Python library, or as an MCP server (stdio or HTTPS) with Claude Code, Claude Desktop, Cursor, Windsurf, Zed, or any MCP client.

pip install "leftbrain[all]"
claude mcp add leftbrain -- leftbrain          # Claude Code, one line

Why

Models are right-brained: fluent, intuitive, and wrong about 9.11 < 9.9, the number of r's in strawberry, what day it is, how many business days are left before a deadline, and whether 03/04/2025 is March or April. Every one of those has an exact answer. leftbrain is one small, well-described tool set that gives the model that answer instead of letting it guess.

Design rules:

  1. Deterministic only. Same input, same output. No LLM-in-a-tool.

  2. Refuse ambiguity instead of guessing. IST, 03/04/2025, ton, oz, KB — each returns the concrete options rather than a silent assumption.

  3. Surface every interpretation. Each response carries assumptions[] ("read as DD/MM per locale IN") and warnings[] ("day clamped to month end").

  4. Exact and decimal, together. sqrt(2)/2 and 0.7071…, 7/4 and 1.75, so the model never re-rounds.

  5. Few tools, many modes. 13 core tools, each with a mode parameter, so the tool list stays cheap on every turn.

  6. Descriptions say when, not what. The usual failure is the model not calling the tool.

Related MCP server: Euclid

Tools

Core (leftbrain, offline, pure functions)

Tool

Modes

Replaces the model's guess at…

math

eval, exact, simplify, expand, factor, solve, diff, integrate, limit, series, ode, matrix, stats, convert_form, plot_points

any arithmetic, 15% of 200, complex numbers (3+4i)(1-2i), trig (angle required), is_prime/is_even and friends, the physical constants (G, c, h), calculus, linear algebra, statistics — SymPy, sandboxed

datetime

now, convert_tz, parse, add, diff, weekday, nth_weekday, business_days, overlap, duration_sum, free_slots, recurrence, cron_next, age, fiscal

the current time, DST-correct conversions, "next Friday 5pm", month-end clamping, working days with public holidays, common free slots across time zones, RRULE expansion, cron

scale

4 → 7 servings, price per kg → per 250 g, 3 workers × 5 days → 12 workers (mode=inverse), with every dependent quantity

convert

units, temperature, currency, fuel_economy, cooking, sizes

km→mi, sqft→sqm, °C→°F (absolute or delta), GB→GiB, USD→INR (needs a rate), mpg↔L/100 km (US or UK gallon, never guessed), cups↔grams by ingredient density, shoe and clothing size charts

numbers

compare, round, format, allocate, sequence, parse, to_words, semver

9.11 vs 9.9, half-up vs banker's rounding, ₹1,23,45,678.50, splitting ₹100 three ways with no lost paisa, "One lakh twenty-three thousand… only", 1.10 > 1.9

finance

emi, compound, cagr, npv_irr, gst, percent

₹10L at 8.5% for 20 years → ₹8,678.23 with the schedule that reconciles to zero, SIP future value, CAGR, NPV/IRR by bisection, ₹1,180 inclusive → ₹1,000 + ₹90 CGST + ₹90 SGST, 20% then 10% off is 28% not 30%

text

count, regex_match, regex_replace, diff, sort, dedupe, extract, find, similarity

character/word/occurrence counts, running a regex, exact diffs, natural sort, extracting emails/phones/GSTINs, edit distance and best-match from a list

collections

set_ops, group_by, aggregate, pick_fields, flatten, unflatten, paginate, find_duplicates, sort_by, chunk, filter, pivot, running, outliers, summarize, to_csv

what's in list A but not B, group-by with sums, multi-key sorts, filters, pivots, running totals, IQR outliers — over JSON records or CSV text, past the ~20-item cliff

validate

json_schema, assert, id, email, url, phone, ip, sql_parse, regex, cidr

rule checks over JSON ({path, op, value} → pass/fail + score), Luhn/IBAN/GSTIN/PAN/Aadhaar/ISBN/EAN/VIN/IFSC/UPI checksums and formats, ISBN-10 ↔ 13, is-this-IP-in-that-block and block overlap, DELETE without WHERE

random

uuid, int, float, pick, shuffle, token, bool, sample

real randomness: UUID v4/v7, seeded ints, secure tokens/OTPs, A/B buckets

geo_offline

tz_for_place, tz_for_coords, distance, country, zone_info

"Mumbai" → Asia/Kolkata, haversine distance, a country's zones — no network

encode

hash, hmac, checksum, base64, hex, url, html, jwt_decode, json

SHA-256, HMAC, CRC32, base64 — models hallucinate all of these; expectedmatches in constant time

color

convert, describe, swatch, contrast, mix, harmony, nearest, simulate, grayscale

hex ↔ RGB ↔ HSL ↔ HSV ↔ CMYK with alpha, the nearest of the 148 CSS names by Lab ΔE, WCAG contrast with the lightness fix that passes, blends, hue harmonies, snapping to a brand palette, deuteranopia/protanopia/tritanopia, five greyscale methods, and a real PNG swatch to look at

Network tools (weather, fx_rate, geo, url_check)

The four that reach the internet, on the same server as everything else. LEFTBRAIN_SERVE_EXTERNAL=0 (or --no-network) leaves them out of a process entirely; the dashboard marks them, and a key can be scoped without them in one click.

Tool

Source

weather — current, forecast (16 days), historical (back to 1940), summary

Open-Meteo

fx_rate — latest or dated ECB reference rates, returns a table convert accepts

Frankfurter

geo — geocode, reverse, driving route distance/time

Open-Meteo / Nominatim / OSRM

url_check — real status code, redirect chain, latency

direct

Files (leftbrain-files, opt-in)

For custom agent loops that cannot open files themselves (hosted agents like Claude Code already can). pdf_text, pdf_info, image_info, image_to_base64 (resize/compress to a byte budget; returns ready-made Anthropic and OpenAI image blocks), base64_to_file, file_info, read_text, list_dir, file_hash (streamed sha256/sha1/md5/blake2b/crc32 of a file of any size; pass expected — a digest or a whole sha256sum line — to get matches, the way to verify a download). Access is limited to LEFTBRAIN_FILE_ROOTS.

Finding out what it can do

Three endpoints need no key, so an agent can look before it signs up:

curl https://leftbrain.idlesync.in/            # name, version, endpoints, auth mode
curl https://leftbrain.idlesync.in/healthz     # {"ok": true, "version": "0.3.0"}
curl https://leftbrain.idlesync.in/docs/tools  # every tool, its modes, and the contract

/docs/tools and /docs/tools/<name> answer in JSON to a client and HTML to a browser — the same content negotiation / uses, so there is one URL per thing rather than two. The JSON carries what the reference page carries minus the executed responses: each mode's purpose, every parameter with its type, default and whether it is required, and the example arguments.

curl https://leftbrain.idlesync.in/docs/tools/datetime | jq '.modes[].name'

tools/list over /mcp is the other machine-readable route; it needs a key, reports no modes, and honours that key's scope. Use it when you are already connected; use /docs/tools when you are deciding whether to.

Connecting an app

An app that reads a config file can send Authorization: Bearer lblz_…, which is all leftbrain wants. An app configured through a browser dialog can only offer what the dialog offers — and neither Claude's web connector form nor ChatGPT's has a place for a static key.

client

works today

Claude Code, Claude Desktop, Cursor, VS Code

yes — a headers block, see MCP clients

Claude on the web, ChatGPT

not yet — the dialogs expect OAuth

For Claude on the web, use the desktop app: same account, and the connector lives in claude_desktop_config.json. For ChatGPT, put a header-injecting proxy in front and point the connector at that with No Auth — the docs carry a Cloudflare Worker that does it in a dozen lines, and the trade it makes. OAuth on /mcp is the real fix and is tracked as its own issue.

The contract

Every tool returns the same envelope:

{"ok": true,  "result": {...}, "assumptions": ["read as DD/MM per locale IN"], "warnings": []}
{"ok": false, "error": "ambiguous", "message": "...", "needs": {"field": "locale", "options": [...]}, "retryable": false}
{"ok": false, "error": "too_large", "message": "...", "details": {...}, "retryable": false, "hint": "..."}

result is never null. When needs.options is present, pick one and call again; when needs.missing is present, those parameters were left out.

Every failure carries retryable — whether an identical retry could ever succeed. It is there because a client that reads only ok: false retries, and retrying a call that hit a limit multiplies the load that caused it. details (the numbers behind the message) and hint (what to change) are present when the tool has something concrete to say.

error

when

retryable

invalid_input

the call is wrong — a bad value, a missing or mistyped parameter

false

ambiguous

two readings are both plausible; needs.options lists them

false

unsupported

the mode cannot do this at all

false

too_large

a pre-check refused it before any work started

false

timeout

it ran to its deadline and was stopped

false

resource_exhausted

a memory or CPU limit was hit rather than the clock

false

forbidden

the key's scope does not include this tool or mode

false

busy

the server was saturated; nothing was computed

true

internal

something broke unexpectedly

false

internal is false because it is the catch-all for an exception no mode anticipated, and most of those are a deterministic consequence of the input — the same call fails the same way every time. A site that genuinely knows its failure was transient says so at the raise.

A call whose arguments fail the tool's input schema never reaches the tool, and still answers in this shape: invalid_input, the offending parameters under details.parameters, and needs.missing naming anything required that was left out.

An internal error never ships a stack trace to the caller — it is logged server-side. Set LEFTBRAIN_DEBUG=1 to get a trace field back in the response as well.

What a call cost

Every response carries a meta block. It never affects ok.

{"ok": true, "result": {...}, "assumptions": [], "warnings": [],
 "meta": {"tool": "math", "mode": "eval", "latency_ms": 12, "compute_ms": 9,
          "version": "0.2.0", "truncated": false, "request_id": "9f2c1a4b7e0d3c88",
          "quota": {"remaining_today": 987, "daily_quota": 1000, "rpm": 60}}}

latency_ms is the wall time in the server, compute_ms the time in the engine, and truncated is lifted out of the result so a caller reading a list knows it is not the whole list. quota lets an agent back off before it hits a 429. request_id is echoed as X-Request-Id — send your own and it is kept, so one id spans both sides of a trace — and X-Leftbrain-Latency-Ms carries the latency, so both are visible without parsing the body.

compute_ms is also the regression alarm for the ceiling above: a response whose compute_ms exceeds its own deadline is a timeout that did not fire. That was measurably the case before the worker landed, when a call with timeout=5 was still computing at 9.53 s.

Parameters

Every mode declares what it reads, and a parameter it does not read is refused, not dropped. datetime.age takes on, so passing ref_date used to return the age as of today — an answer computed from a default after the caller's argument was discarded, with nothing to distinguish it from a right answer. The refusal names the mode, lists what it does accept, and suggests the closest match. Where two parameters are mutually exclusive (significant and decimals, months and years) the winner is stated in assumptions rather than chosen silently.

The 15-second ceiling

Every math, text, validate, collections and numbers call runs in a worker process that can be killed, because a Python thread cannot. int.__pow__, sre's matching loop and difflib's inner loops are C code that never returns to the eval loop, so an async exception is never delivered, SIGALRM never fires, and — since the runaway holds the GIL — the whole process starves, /healthz included. That is how one math.eval 9^9^9^9 took the hosted instance offline for 35 minutes. The layer-0 caps above refuse the obvious cases in microseconds; this is the backstop for what they cannot estimate.

A call that reaches the deadline comes back as timeout with stopped: "worker_terminated", the limit and the real elapsed time, and retryable: false — an identical retry costs the same 15 seconds. A caller-supplied timeout is clamped to the ceiling. Under load, a call that waits too long for a free worker returns busy, which is retryable, rather than queueing behind a 15-second wait.

variable

default

what it does

LEFTBRAIN_COMPUTE_TIMEOUT

15

wall-clock seconds before the worker is terminated

LEFTBRAIN_QUEUE_TIMEOUT

5

how long a call waits for a free worker before busy

LEFTBRAIN_MAX_INFLIGHT

CPU count

concurrent workers

LEFTBRAIN_WORKER_MAX_TASKS

200

calls before a worker is recycled (bounds SymPy's caches)

LEFTBRAIN_WORKER_MEMORY_BYTES

1.5e9

RLIMIT_AS in the child

LEFTBRAIN_WORKER_CPU_SECONDS

timeout + 2

RLIMIT_CPU in the child, the kernel's backstop

LEFTBRAIN_WORKER_TERM_GRACE

0.5

SIGTERM→SIGKILL grace; a C loop never runs a handler

LEFTBRAIN_COMPUTE_ISOLATION

1

set 0 to run in-process (the library default)

The worst case a client sees is the timeout plus the termination grace — about 15.5 s — so the reverse proxy in front of the server needs an idle timeout comfortably above that, or the caller gets a platform 502 instead of the envelope. Workers use forkserver on POSIX (fork is unsafe in a threaded server, and Python 3.12 still defaults to it) and spawn on Windows. Without pebble installed the server logs that isolation is off and runs in-process; the library always runs in-process.

Limits

Every mode that can be asked for something enormous refuses it before computing, in microseconds, with too_large naming the limit and the knob to turn: math estimates the answer's digit count from the expression (so 9^9^9^9 never starts), sequences cap their largest term, diff caps the units per side, pivot caps its column count, and so on — each one is documented on that mode's reference page. Behind them sits a 256 KB ceiling on any successful response (LEFTBRAIN_MAX_RESPONSE_BYTES). When a result is trimmed rather than refused, the response says so: truncated: true and a line in warnings, with the untrimmed total still reported.

Caller-supplied regular expressions get the same treatment. A pattern that can backtrack exponentially — (a+)+$, (a|aa)+, (a*)* — is refused with unsupported before it is compiled, in text.regex_match, text.regex_replace and any pattern inside a validate.json_schema schema. Once stdlib re starts on one, nothing can stop it: it is a C loop that never reaches a bytecode boundary, so no timeout, signal or thread kill is delivered until it finishes, which it does not. validate.regex is the exception — it judges a pattern rather than running it, so it reports backtracking_risk and stays valid: true.

url_check fetches only public http/https addresses. The host is resolved and checked before the connection and again after each redirect, so loopback, link-local, RFC 1918, unique-local, reserved and cloud-metadata addresses are refused — a server that fetches what a caller names is otherwise a way to read the instance metadata service. collections.to_csv prefixes cells beginning = + - @ tab or CR with an apostrophe so a spreadsheet reads them as text rather than running them; escape_formulas: false turns that off with a warning.

Install

pip install leftbrain                 # library only
pip install "leftbrain[mcp]"          # + MCP servers (stdio)
pip install "leftbrain[all]"          # + external (httpx), files (pypdf, Pillow), HTTP server (uvicorn)

Python 3.11+. Zero API keys.

Use from Python

import leftbrain as lb

lb.math_tool("eval", expr="(3+4i)*(1-2i)")["result"]["decimal"]      # '11 - 2i'
lb.math_tool("solve", equations=["x^2+1=0"])                          # ±i
lb.datetime_tool("convert_tz", value="2026-03-08 09:30", from_tz="Asia/Kolkata", to_tz="America/New_York")
lb.datetime_tool("parse", value="03/04/2025")                         # ok: False, needs.options = DD/MM or MM/DD
lb.datetime_tool("business_days", start="2026-10-01", end="2026-10-31", region="IN")
lb.scale_tool(from_qty=4, to_qty=7, entities=[{"name": "flour", "qty": "2.5", "unit": "cup"}])
lb.convert_tool(value=2, from_unit="ton", to_unit="kg")               # ambiguous: metric / short / long
lb.numbers_tool("allocate", total=100, parts=3)                       # 33.34 / 33.33 / 33.33
lb.numbers_tool("to_words", value=123456.5, system="indian", currency="INR")
lb.validate_tool("id", kind="gstin", value="27AAPFU0939F1ZV")
lb.validate_tool("assert", data=doc, rules=[{"path": "leave.days", "op": "lte", "value": 2}])

lb.TOOLS maps tool names to functions if you want to wire them into OpenAI function-calling, LangChain, or your own loop — no MCP required.

Use as an MCP server (stdio)

Claude Code

claude mcp add leftbrain -- leftbrain
claude mcp add leftbrain-files -e LEFTBRAIN_FILE_ROOTS=/path/to/docs -- leftbrain-files

Claude Desktop / Cursor / Windsurf (claude_desktop_config.json, .cursor/mcp.json):

{
  "mcpServers": {
    "leftbrain": { "command": "leftbrain" }
  }
}

No install at all: "command": "uvx", "args": ["--from", "leftbrain[all]", "leftbrain"].

Use over HTTPS (hosted)

One process serves every tool set with Streamable HTTP:

pip install "leftbrain[server]"
LEFTBRAIN_API_KEY=your-secret leftbrain-serve --port 8080
# all tools: http://localhost:8080/mcp   (--no-network leaves out the four that reach the internet)
# files:     add --files (and set LEFTBRAIN_FILE_ROOTS)

It runs stateless by default, so it scales horizontally behind any load balancer. TLS is terminated by the platform in front of it:

  • Docker: docker build -t leftbrain . && docker run -p 8080:8080 -e LEFTBRAIN_API_KEY=… leftbrain

  • Railway / Render / Fly.io: point at the repo; the Dockerfile and $PORT are picked up automatically, HTTPS is provided.

  • Self-hosted: put Caddy or nginx in front (reverse_proxy localhost:8080), or a Cloudflare Tunnel.

Then connect a client to the public URL:

claude mcp add --transport http leftbrain https://leftbrain.example.com/mcp \
  --header "Authorization: Bearer your-secret"

Health: GET /healthz. Service description: GET /.

Per-user API keys (public free tier)

To let other people use your deployment with their own keys, quotas and rate limits, enable the key store instead of (or alongside) the static key:

LEFTBRAIN_KEYS_DB=/data/keys.sqlite3 leftbrain-serve     # or --keys-db

The store speaks SQLite (a path, for one instance with a volume) or Postgres (LEFTBRAIN_KEYS_URL=postgres://…, pip install "leftbrain[postgres]") for platforms without persistent disk. The DSN is read from LEFTBRAIN_KEYS_URL, then DATABASE_URL, then LEFTBRAIN_KEYS_DB — so Northflank/Render/Railway's injected DATABASE_URL is picked up automatically.

With a store configured, leftbrain-serve also grows a web site:

  • / — landing page (browsers) or the JSON service description (Accept: application/json)

  • /login — GitHub OAuth; keys belong to the account's verified primary email

  • /dashboard — create up to 5 active keys with a lifetime of 30 / 90 / 365 days (or never, with a warning), choose which tools — and which modes of each — a key may call (on creation, or later with Edit scope on its row), see today's usage and when each key expires, show a key again, revoke, and delete a revoked or expired key for good. Keys issued before the server could show keys again are marked legacy: they still work if you saved them, but do not hold one of the 5 slots

  • /docs — quickstart with Windows PowerShell / macOS / Linux tabs, MCP client setup, and /docs/agents/auth, an authentication guide written for a model rather than a person

  • POST /demo/{numbers|convert|datetime|text} — key-less demo, 30 req/min per IP

There are two ways to connect. Paste a key from /dashboard into your client's config, or — with LEFTBRAIN_BASE_URL set — connect with OAuth 2.1 and let leftbrain create the key for you. ChatGPT has no field for a key at all and OAuth is its only route; Claude Code, Cursor and VS Code can use either.

  • OAuth 2.1 for MCP clients: discovery (RFC 8414 and RFC 9728), Client ID Metadata Documents, dynamic client registration (RFC 7591), PKCE S256, refresh-token rotation, and the device grant (RFC 8628) for an agent with no browser. Requires LEFTBRAIN_SECRET and LEFTBRAIN_BASE_URL; without both, none of it is mounted.

  • A connector's key is an ordinary key. Approving on the consent screen creates one named after the app and where it runs — Claude Code · Windows, ChatGPT · web — visible on the dashboard, revealable, re-scopable, revocable, and counting against the same active-key cap. Revoking it stops that connector on its next call.

  • POST /keys/me/scope lets a caller propose narrowing its own key. It returns 202 with a URL for its owner to approve; nothing changes until they do, and widening is refused outright.

  • The scope editor shows how many times the key has called each tool, so the ones sitting at zero are the ones to untick.

and the key API behaves like this:

  • Self-serve signup: POST /keys/signup {"email": "dev@example.com"}{"key": "lblz_…", "daily_quota": 1000, "rpm": 60}. Throttled to 3 signups per IP per day and 5 active keys per email. Anonymous signup is off unless LEFTBRAIN_OPEN_SIGNUP=1; with the web site, people sign in at /login instead.

  • Every request carries X-RateLimit-Remaining-Today, X-RateLimit-Limit-Day, X-RateLimit-Limit-Minute headers; 429 with Retry-After when a limit is hit; 403 for a disabled key, and 403 {"error": "expired", "message": "key expired on 2026-11-25; create a new one at /dashboard"} once a key's lifetime is up. Expired keys stop counting towards the active-key cap.

  • The daily quota counts tool calls, not requests. One tools/call that did work spends exactly one unit. The MCP handshake (initialize, tools/list, …), GET /keys/me, and any call the server refused before doing the work — bad input, an ambiguity it would not guess, a tool outside the key's scope — cost nothing. The per-minute limit is deliberately different: it sees every request, because it is abuse protection rather than a budget. X-RateLimit-Remaining-Today and meta.quota.remaining_today are always the same number, and both count the call carrying them.

  • Feedback: POST /feedback with a key files an issue on the tracker, and signed-in people get a form at /report — an agent mid-call holds a key and not a GitHub login, and a person on the docs is signed in here, not there. Off unless LEFTBRAIN_FEEDBACK_TOKEN and LEFTBRAIN_FEEDBACK_REPO are set; without them it answers unsupported and names the tracker rather than swallowing the report. Anything key-shaped is blanked before filing, the reporter is recorded as a key prefix or GitHub login and never an email address, and filing costs no daily quota because it is not a tool call.

  • Caller self-check: GET /keys/me with the key → owner, quota, used today, expires_at, and tools (the key's scope, or null for every tool).

  • Scoped keys: a key can be limited to specific tools, and to specific modes of a tool — from the dashboard (the Tools disclosure on the create form, or Edit scope on a key's row) or with leftbrain-keys … --tools "math,datetime,finance:emi+gst". A scoped key's tools/list shows only the tools it may call, and a tools/call outside the scope returns the contract error {"ok": false, "error": "forbidden", "message": "this key may not call finance mode 'compound'; allowed: emi, gst"} — a result, not an HTTP error, so an agent reads it and stops. Keys without a scope may call everything; a changed scope applies on the key's next call.

Environment: GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET, LEFTBRAIN_SECRET (cookie signing, 32+ random chars), LEFTBRAIN_BASE_URL (e.g. https://leftbrain.idlesync.in, used for the OAuth callback), and LEFTBRAIN_TRUSTED_PROXY_HOPS (default 1) — how many proxies append to X-Forwarded-For in front of the process, so per-IP limits are keyed on the entry your proxy wrote rather than the caller-supplied leftmost one. One reverse proxy (Northflank, Render, Fly, nginx) is 1; add Cloudflare in front and it becomes 2; 0 means nothing proxies it and no forwarding header is believed.

Defaults come from LEFTBRAIN_DEFAULT_DAILY_QUOTA (1000), LEFTBRAIN_DEFAULT_RPM (60), LEFTBRAIN_SIGNUPS_PER_IP_PER_DAY (3). Authentication only ever compares a SHA-256 of the key. When LEFTBRAIN_SECRET is set the store also keeps a Fernet-encrypted copy of each key, under a key derived from that secret, so the signed-in owner can be shown their own key again on the dashboard and have it filled into the docs examples. Rotating LEFTBRAIN_SECRET leaves existing keys working but no longer revealable; leave the secret unset and nothing but the hash is stored.

Admin CLI (any DSN):

leftbrain-keys create --owner you@example.com --daily 50000 --rpm 300 --expires 90d --note "partner"   # default 365d; --expires never warns
leftbrain-keys create --owner bot@example.com --tools "math,datetime,finance:emi+gst"              # only these tools; tool:mode+mode narrows a tool
leftbrain-keys list                                     # one JSON line per key, with expires_at / expired / tools
leftbrain-keys disable lblz_xxxxxxxx
leftbrain-keys enable lblz_xxxxxxxx
leftbrain-keys revoke lblz_xxxxxxxx
leftbrain-keys set lblz_xxxxxxxx --daily 20000 --rpm 120 --expires 30d   # --expires counts from now; also revives an expired key
leftbrain-keys set lblz_xxxxxxxx --tools "numbers,convert"                # replace the key's scope; --all-tools lifts it
leftbrain-keys set --all --daily 1000 --from-daily 5000                  # migrate every key still on an old default; drop --from-daily to hit every key
leftbrain-keys usage --days 7
leftbrain-keys stats

Free hosting that fits: Northflank's sandbox (always-on service + free Postgres + custom domain) — see docs/deploy-northflank.md for a step-by-step including DNS for a subdomain.

Examples of what changes

Ask

Without

With leftbrain

"Which is bigger, 9.11 or 9.9?"

often 9.11

numbers.compare → 9.9

"Convert 9:30 IST on 8 March to New York"

±1 h around DST

datetime.convert_tz → 23:00 on 7 March, EST, day_shift −1

"Split ₹100 among 3 people"

33.33 × 3 = 99.99

numbers.allocate → 33.34 / 33.33 / 33.33

"Is 27AAPFU0939F1ZV a valid GSTIN?"

"looks valid"

checksum verified

"How many working days in October 2026 in India?"

guesses 21–23

20, listing Gandhi Jayanti and Dussehra

"sin(30)"

0.5 or −0.988 depending on mood

refuses until angle is given

Development

git clone <this repo> && cd leftbrain
python -m venv .venv && . .venv/bin/activate     # .venv\Scripts\activate on Windows
pip install -e ".[dev]"
pytest                          # unit tests
python scripts/smoke.py         # every tool, end to end
python scripts/mcp_client_check.py   # spawn each MCP server over stdio and call it
ruff check src tests

Layout: src/leftbrain/core/ holds the pure functions (one file per tool), contract.py the envelope, mcp_server.py the stdio server, serve.py the HTTP server, external/ and files/ the optional sets.

Releases are cut by pushing a vX.Y.Z tag — see docs/releasing.md; what changed in each one is in CHANGELOG.md.

Roadmap

  • TypeScript port (same tool names and contract) for Node-based agents

  • OCR fallback for scanned PDFs

  • csv/xlsx parsing in files

License

MIT

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections. Dates show when Glama detected each change.

No tool schema history has been recorded yet.

Maintenance

ActivityMaintained
ResponsivenessResponsive

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    Provides a suite of deterministic tools for time calculations, math, and string manipulation that LLMs often struggle to perform accurately. It also includes utilities for secure randomness, data validation, and basic network operations like DNS lookups.
    11
    MIT
  • F
    license
    Not graded
    quality
    D
    maintenance
    AI agents hallucinate math. Euclid fixes that. Connect once and give your agent 10 deterministic tools: arithmetic, unit conversion, statistics, datetime, finance, regex, color, encoding, validation, and geospatial. Same input, same output, every time.
    2
    -
  • A
    license
    Not graded
    quality
    C
    maintenance
    Provides a set of micro-tools (time calculation, regex, encoding, JSON diff, etc.) for LLM agents to handle deterministic, precision tasks that models often get wrong.
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Provides deterministic tools for transforming, formatting, and inspecting structured data for AI agents.
    5
    19
    Apache 2.0

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Lazy-Coder-03/leftbrain'

If you have feedback or need assistance with the MCP directory API, please join our Discord server