# gnomon-mcp [![PyPI](https://img.shields.io/pypi/v/gnomon-mcp.svg)](https://pypi.org/project/gnomon-mcp/) [![Python](https://img.shields.io/pypi/pyversions/gnomon-mcp.svg)](https://pypi.org/project/gnomon-mcp/) [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) [![MCP Registry](https://img.shields.io/badge/MCP%20Registry-io.github.lihtness%2Fgnomon--mcp-7c3aed)](https://registry.modelcontextprotocol.io/v0/servers?search=gnomon) > The pointer on a sundial that turns shadow into time. A small MCP server for the boring-but-essential utilities every model needs: dates, calendars, arithmetic, unit conversion. Use it so your assistant stops "next-token guessing" math and date math. ## Why LLMs are bad at arithmetic and date math by default. They produce plausible answers that are often wrong by a small amount — exactly the kind of mistake that's hard to notice in a long response. `gnomon-mcp` exposes deterministic Python implementations through MCP so your model can compute instead of guess. ### When an agent should reach for gnomon Anywhere the next plausible token is not the right answer. Concretely: - **Math that matters** — anything beyond trivial mental arithmetic, anything with a decimal point, anything that compounds. Call `calc`. - **"What day is it" / "how long until" / "how long since"** — the model's training cutoff is not today. Call `now` for a snapshot; `calendar` with `until`/`since`/`diff` for elapsed time; `parse` for natural-language dates ("next thursday"). - **Date arithmetic across month/year boundaries** — adding 30 days, finding a quarter-end, counting business days. Models routinely off-by-one these. Call `calendar` with `add` / `business_days`. - **Unit conversion** — call `calc_convert`. Never eyeball "kg → lb" or "°C → °F". - **Table-row workloads** — when the same kind of computation needs to run on every row of a table, both batch tools (`calendar`, `calc`) take a list and return a list in order. One call, N results. The rule of thumb: if you'd ask a colleague to "just double-check that number," call gnomon instead. ### How this compares to other MCP servers Time and math already have several MCP servers — the official [`Time`](https://github.com/modelcontextprotocol/servers-archived/tree/main/src/time) reference (timezone-only), [`mcp-time`](https://github.com/TheoBrigitte/mcp-time) and [`mcp-datetime`](https://github.com/ZeparHyfar/mcp-datetime) (date formatting / timezone), [`calculator-server`](https://github.com/avisangle/calculator-server) (math + units, no dates), and bundles like [`agent-utils-mcp`](https://github.com/aparajithn/agent-utils-mcp) (regex / hashing / JWT). gnomon's lane is narrower: - **Batch-first.** `calendar(ops)` and `calc(expressions)` take lists; one tool call covers a whole table column instead of N calls. - **A real `now()`.** One call returns 18 fields — ISO week, quarter, fiscal year, day-of-year, `is_weekend`, … — instead of just `{iso, tz}`. - **Dates *and* math *and* units in one wiring.** No need to compose three separate servers. - **Natural-language dates baked in** (`"next thursday"`, `"in 3 hours"`) without a separate NLP server. If you only need timezone conversion, the official `Time` server is enough. If you want a broad utility bundle (regex, hashing, encoding, JWT), `agent-utils-mcp` is a better fit. gnomon is for the boring date-arithmetic-and-arithmetic core, batched. ## Tools ### Calendar Two tools: - **`now(tz?)`** — standalone. Returns a rich dict snapshot of the current moment. One call gets you everything about "right now". - **`calendar(ops)`** — batch dispatcher. Each item picks its own op. Designed for table-row workloads (e.g. one call computes time-elapsed for every row). **`now(tz?)`** returns: ```python { "iso": "2026-05-25T14:30:45+00:00", "date": "2026-05-25", "time": "14:30:45", "unix": 1779345045, "tz": "UTC", "year": 2026, "month": 5, "month_name": "May", "day": 25, "weekday": "Monday", "weekday_num": 0, # 0=Monday "day_of_year": 145, "week_of_year": 22, # ISO week "quarter": 2, "fiscal_year_us_gov": 2026, # FY starts Oct 1 "hour": 14, "minute": 30, "second": 45, "is_weekend": False, } ``` **`calendar(ops)`** operations: | Op | Params | Returns | |---|---|---| | `diff` | `start, end, unit` | `end - start` — time elapsed between two known dates | | `until` | `target, unit, tz?` | `target - now` — time left to a future point (negative if past) | | `since` | `source, unit, tz?` | `now - source` — time elapsed since a past point (negative if future) | | `add` | `date, n, unit` | ISO of `date + n units` (`seconds\|...\|weeks`, plus `months\|years` calendar-aware) | | `weekday` | `date` | `"Monday"`..`"Sunday"` | | `business_days` | `start, end` | count of Mon-Fri days (start inclusive, end exclusive) | | `parse` | `natural, tz?` | ISO from natural language ("next thursday", "in 3 hours") | | `format` | `date, fmt` | strftime-formatted string | Units for `diff`/`until`/`since`: `seconds`, `minutes`, `hours`, `days`, `weeks`. Example — compute several things in one call: ```python calendar([ {"op": "until", "target": "2026-12-31", "unit": "days"}, # days left in year {"op": "since", "source": "2026-01-01", "unit": "days"}, # days elapsed in year {"op": "diff", "start": "2026-01-01", "end": "2026-12-31", "unit": "days"}, {"op": "weekday", "date": "2026-05-25"}, # "Monday" {"op": "add", "date": "2026-05-25", "n": 1, "unit": "months"}, {"op": "parse", "natural": "next thursday", "tz": "America/Los_Angeles"}, ]) ``` ### Calculator | Tool | Purpose | |---|---| | `calc(expressions)` | Evaluate a list of Python expressions and return a list of results. Math (`sqrt`, `sin`, `log`, `pi`, `e`, ...), stats (`mean`, `median`, `stdev`, `variance`), and useful builtins (`abs`, `round`, `min`, `max`, `sum`, `range`, `sorted`, ...) are pre-loaded. Batch in / batch out, order preserved. | | `calc_convert(value, from_unit, to_unit)` | Unit conversion via Pint (`meter` → `foot`, `kg` → `lb`, `degC` → `degF`, etc.). | Examples: ```python calc(["2 + 3 * 4"]) # [14] calc(["sqrt(16)", "sin(pi/2)"]) # [4.0, 1.0] calc(["mean([1, 2, 3, 4])"]) # [2.5] calc(["sum(range(101))"]) # [5050] calc(["(25 / 100) * 100"]) # [25.0] ``` ## Future tools (sketches) The same logic — *if the model is likely to bluff it, expose a deterministic version* — points at several more primitives worth building. None of these are implemented yet; they are candidates, listed roughly in order of bang-for-buck: 1. **Text measurement** — `count(text, unit)` for chars / words / lines / sentences / LLM tokens. Agents constantly miscount "how long is this" and "will this fit in the context window." 2. **Regex match / replace** — `regex_find(pattern, text)` and `regex_sub(pattern, repl, text)`. Models hallucinate which substrings match a regex; a real engine ends the argument. 3. **Structured-data extraction** — `jq(path, json)` / `jsonpath(path, json)`. Reading values out of a nested blob by path, without typos. 4. **Hashing & encoding** — `hash(text, algo)` (sha256, md5, blake2), `encode(text, scheme)` / `decode(text, scheme)` (base64, hex, url, jwt-payload). All things models confidently invent wrong. 5. **Decimal money math** — `money(expr)` evaluated under Python's `Decimal` with explicit rounding. `calc` is float-based and quietly unsafe for currency. 6. **Holiday-aware business days** — extend `calendar.business_days` with a `country` (or `calendar`) parameter so US/UK/IN holidays are excluded. The current implementation only knows weekends. 7. **Cron describe / next-fire** — `cron_describe("0 9 * * 1-5")` → human English; `cron_next(expr, n)` → next N firing times. Models routinely misread cron fields. 8. **Token counting for a target model** — `count_tokens(text, model)` via tiktoken / Anthropic tokenizer. Lets an agent budget its own prompts and outputs instead of guessing. If you want one of these, open an issue (or a PR — each is a small self-contained module that fits the existing `tools/` layout). ## Install Recommended: no install — run on demand via [`uv`](https://docs.astral.sh/uv/): ```bash uvx gnomon-mcp # serves stdio MCP, ready for any client uvx gnomon-mcp --demo # call every tool once and print the results (no MCP client needed) ``` Or install globally: ```bash pip install gnomon-mcp ``` ## Wire it into your agent All recipes assume `uvx gnomon-mcp`. If you prefer a pinned install, swap the command for `gnomon-mcp` (with no `uvx`). ### Claude Code ```bash claude mcp add gnomon -- uvx gnomon-mcp ``` Or edit `~/.claude.json` / a project `.mcp.json`: ```json { "mcpServers": { "gnomon": { "command": "uvx", "args": ["gnomon-mcp"] } } } ``` ### Claude Desktop `claude_desktop_config.json`: ```json { "mcpServers": { "gnomon": { "command": "uvx", "args": ["gnomon-mcp"] } } } ``` ### Cursor `~/.cursor/mcp.json` (or `.cursor/mcp.json` in a project): ```json { "mcpServers": { "gnomon": { "command": "uvx", "args": ["gnomon-mcp"] } } } ``` ### Continue `~/.continue/config.yaml`: ```yaml mcpServers: - name: gnomon command: uvx args: ["gnomon-mcp"] ``` ### Any other client (generic stdio) Spawn `uvx gnomon-mcp` as a subprocess and speak MCP over stdin/stdout. That is the entire integration. ### Hosted / remote (HTTP transport) For team-shared instances or agents that can't spawn a local subprocess: ```bash uvx gnomon-mcp --transport streamable-http --host 0.0.0.0 --port 8000 # also supported: --transport sse ``` Then point your MCP client at `http://:8000/mcp` (or `/sse` for the SSE transport). ## Tell your agent to actually use it The MCP tool descriptions are intentionally terse to keep persistent context cost minimal (~150 tokens for all four tools). The richer "when to reach for gnomon" guidance lives in a Claude Code skill that loads on demand. ### Option A — Claude Code plugin (one command, recommended) The plugin wires both the MCP server *and* the skill in one shot. Inside Claude Code: ``` /plugin marketplace add lihtness/gnomon-mcp /plugin install gnomon@gnomon-mcp ``` That registers `gnomon` as an MCP server (auto-starts via `uvx`) and installs the on-demand skill. Skill body loads only when the task triggers it — persistent context stays ~150 tokens for the four tool descriptions plus ~40 tokens for the skill's name + summary. ### Option B — manual skill install (no plugin) If you've already wired the MCP server with `claude mcp add gnomon -- uvx gnomon-mcp` and only want the skill: ```bash mkdir -p ~/.claude/skills/gnomon curl -fsSL https://raw.githubusercontent.com/lihtness/gnomon-mcp/main/skills/gnomon/SKILL.md \ -o ~/.claude/skills/gnomon/SKILL.md ``` ### Option C — paste into your system prompt (non-Claude-Code agents) For agents without skill support, paste this short version into your system prompt or `CLAUDE.md`: ```text You have gnomon: deterministic tools for dates and math. Use them instead of guessing. - `now` — current moment (your training cutoff isn't today). - `calendar(ops)` — batch date math: diff/until/since/add/weekday/ business_days/parse (natural language)/format. - `calc(expressions)` — batch Python eval; math + statistics + common builtins pre-loaded. - `calc_convert(value, from, to)` — unit conversion via Pint. Both batch tools take a list and return a list. Prefer one batched call over many small ones. ``` ## Development ```bash git clone https://github.com/lihtness/gnomon-mcp cd gnomon-mcp pip install -e ".[dev]" pytest ``` ## License MIT