leftbrain
Provides image-to-base64 conversion that returns ready-made OpenAI image content blocks, with resizing and compression to a byte budget.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@leftbrainWhat's 15% of 200?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
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 lineWhy
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:
Deterministic only. Same input, same output. No LLM-in-a-tool.
Refuse ambiguity instead of guessing.
IST,03/04/2025,ton,oz,KB— each returns the concrete options rather than a silent assumption.Surface every interpretation. Each response carries
assumptions[]("read as DD/MM per locale IN") andwarnings[]("day clamped to month end").Exact and decimal, together.
sqrt(2)/2and0.7071…,7/4and1.75, so the model never re-rounds.Few tools, many modes. 13 core tools, each with a
modeparameter, so the tool list stays cheap on every turn.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… |
| eval, exact, simplify, expand, factor, solve, diff, integrate, limit, series, ode, matrix, stats, convert_form, plot_points | any arithmetic, |
| 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 |
| – | 4 → 7 servings, price per kg → per 250 g, 3 workers × 5 days → 12 workers ( |
| 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 |
| compare, round, format, allocate, sequence, parse, to_words, semver |
|
| 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% |
| 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 |
| 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 |
| json_schema, assert, id, email, url, phone, ip, sql_parse, regex, cidr | rule checks over JSON ( |
| uuid, int, float, pick, shuffle, token, bool, sample | real randomness: UUID v4/v7, seeded ints, secure tokens/OTPs, A/B buckets |
| tz_for_place, tz_for_coords, distance, country, zone_info | "Mumbai" → |
| hash, hmac, checksum, base64, hex, url, html, jwt_decode, json | SHA-256, HMAC, CRC32, base64 — models hallucinate all of these; |
| 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 |
| Open-Meteo |
| Frankfurter |
| Open-Meteo / Nominatim / OSRM |
| 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 |
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.
| when |
|
| the call is wrong — a bad value, a missing or mistyped parameter |
|
| two readings are both plausible; |
|
| the mode cannot do this at all |
|
| a pre-check refused it before any work started |
|
| it ran to its deadline and was stopped |
|
| a memory or CPU limit was hit rather than the clock |
|
| the key's scope does not include this tool or mode |
|
| the server was saturated; nothing was computed |
|
| something broke unexpectedly |
|
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 |
|
| wall-clock seconds before the worker is terminated |
|
| how long a call waits for a free worker before |
| CPU count | concurrent workers |
|
| calls before a worker is recycled (bounds SymPy's caches) |
|
|
|
| timeout + 2 |
|
|
| SIGTERM→SIGKILL grace; a C loop never runs a handler |
|
| set |
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-filesClaude 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=… leftbrainRailway / Render / Fly.io: point at the repo; the
Dockerfileand$PORTare 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-dbThe 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 personPOST /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. RequiresLEFTBRAIN_SECRETandLEFTBRAIN_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/scopelets a caller propose narrowing its own key. It returns202with 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 unlessLEFTBRAIN_OPEN_SIGNUP=1; with the web site, people sign in at/logininstead.Every request carries
X-RateLimit-Remaining-Today,X-RateLimit-Limit-Day,X-RateLimit-Limit-Minuteheaders;429withRetry-Afterwhen a limit is hit;403for a disabled key, and403 {"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/callthat 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-Todayandmeta.quota.remaining_todayare always the same number, and both count the call carrying them.Feedback:
POST /feedbackwith 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 unlessLEFTBRAIN_FEEDBACK_TOKENandLEFTBRAIN_FEEDBACK_REPOare set; without them it answersunsupportedand 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/mewith the key → owner, quota, used today,expires_at, andtools(the key's scope, ornullfor 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'stools/listshows only the tools it may call, and atools/calloutside 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 statsFree 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 |
|
"Convert 9:30 IST on 8 March to New York" | ±1 h around DST |
|
"Split ₹100 among 3 people" | 33.33 × 3 = 99.99 |
|
"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 |
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 testsLayout: 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/xlsxparsing infiles
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.
This server cannot be installed
Maintenance
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
Precision math engine for AI agents. 203 exact methods. Zero hallucination.
500+ deterministic tools for AI agents: math, conversion, validation, hashing, encoding, date/time.
60+ units, live FX, timezones, and date arithmetic for AI agents.
- SnipgetOAuthai.snipget
300+ deterministic data utilities for AI agents: validate, normalize, parse, match, redact.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceProvides 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.11MIT
- FlicenseNot gradedqualityDmaintenanceAI 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-
- AlicenseNot gradedqualityCmaintenanceProvides 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

Agent Toolsofficial
AlicenseNot gradedqualityCmaintenanceProvides deterministic tools for transforming, formatting, and inspecting structured data for AI agents.519Apache 2.0
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
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