Skip to main content
Glama
theoddden

io.github.theoddden/stamp

README.md
# Stamp

<!-- mcp-name: io.github.theoddden/stamp -->

The concierge MCP for agentic workflows: the small, high-frequency tools
agents reach for constantly -- NTP time and clock drift, UUIDs, diffs,
safe arithmetic, and tamper-evident attestation -- behind one endpoint.

A single NTP query tells you where your clock is right now. Drift history
tells you where it is going: a clock that is consistently 200 ms fast and
accelerating is a different problem from one that is stable at 200 ms
fast. `get_time` takes the measurement; every call appends to a local
log; `get_drift` reads the log and reports the trend.

**Hosted endpoint (no sign-in, public):**
`https://stamp-mcp.terradev.cloud/mcp`

**PyPI:** `pip install stamp-mcp` &nbsp;|&nbsp; **License:** Apache 2.0

---

## Quick start

### Remote (streamable HTTP — no install needed)

Add to your MCP client config:

```json
{
  "mcpServers": {
    "stamp": {
      "url": "https://stamp-mcp.terradev.cloud/mcp"
    }
  }
}
```

### Local (stdio)

```bash
pip install stamp-mcp
```

Claude Desktop (`~/Library/Application Support/Claude/claude_desktop_config.json`):

```json
{
  "mcpServers": {
    "stamp": {
      "command": "stamp-mcp"
    }
  }
}
```

Restart Claude Desktop, then ask it *"what tools do you have?"* — `get_time`
should appear. If it doesn't, check `~/Library/Logs/Claude/mcp*.log`.

---

## Tools

- **`get_time`** — one NTP query: UTC time, this clock's offset in ms,
  network delay, stratum. Appends the sample to the drift log. Optional:
  `server` (default `time.cloudflare.com`), `timezone` (IANA name for a
  `local` field, e.g. `America/New_York`).

- **`get_drift`** — analyzes the drift log: sample count, timespan,
  current/mean/stddev offset, drift rate in ms/day (least-squares fit),
  first-half vs. second-half rates, and a verdict: `stable`, `drifting`,
  or `accelerating`. Optional: `server` to filter by NTP host.

- **`generate_uuid`** — random UUIDv4s for records, sessions, and
  identifiers. Optional: `count` (1–1000, default 1).

- **`diff`** — unified diff between `text_a` and `text_b`, with
  added/removed line counts and an `identical` flag. Optional: `context`
  (lines of context, default 3).

- **`calculate`** — safe math evaluator: `+ - * / // % **`, parentheses,
  functions (`abs round min max sqrt floor ceil exp log log2 log10 pow
  sin cos tan`), constants `pi e tau inf`. Parsed to an AST; only
  whitelisted nodes are evaluated — no `eval()`, no arbitrary code.
  Required: `expression`.

- **`attest`** — wrap any JSON payload in a tamper-evident record: a UUID,
  an NTP-verified timestamp (falls back to local clock; `time_source`
  says which), and a `sha256` over the canonical record. Required:
  `payload`. Optional: `server`.

- **`verify`** — recompute an attested record's hash and compare. Returns
  `valid` plus a reason; any modified field — payload, timestamp, id —
  breaks it. Required: `attested`.

---

## The drift log

Every `get_time` call appends one JSON line to `~/.stamp/drift.jsonl`
(override with `STAMP_DRIFT_LOG`). The file is capped at 10,000 samples.
Call `get_time` periodically — a cron job, a heartbeat, or just asking
Claude "check the clock" — and `get_drift` turns the accumulated offsets
into a trend.

---

## HTTP transport

`stamp_mcp/http_server.py` serves the same dispatch logic over async HTTP
via [aiohttp](https://docs.aiohttp.org/):

```bash
stamp-mcp-http                      # binds 127.0.0.1:8000
STAMP_PORT=9000 stamp-mcp-http      # custom port
```

| Endpoint | Method | Description |
|---|---|---|
| `/mcp` | `POST` | JSON-RPC 2.0 — single or batch; notifications → 202 |
| `/mcp` | `GET` | SSE channel for server-to-client notifications (keep-alive) |
| `/` | `GET` | Service identity: name, version, endpoint map |
| `/health` | `GET` | `{"status":"ok"}` for proxies and monitors |
| `/.well-known/oauth-protected-resource` | `GET` | RFC 9728 metadata — public, no auth servers |
| `/.well-known/agent-card.json` | `GET` | A2A agent card |

TLS is terminated by a reverse proxy. The production layout is two
containers on one AWS instance (`docker-compose.yml`):

- **`stamp`** — aiohttp server built from `Dockerfile`, internal network
  only. Drift log persists in the `stamp-data` volume.
- **`caddy`** — terminates HTTPS at `stamp-mcp.terradev.cloud` (automatic
  Let's Encrypt) and reverse-proxies to `stamp:8000`.

Concurrency is capped at 100 simultaneous POST /mcp requests via
`asyncio.Semaphore`; `/health` and `/` are exempt.

---

## Self-hosting

```bash
git clone https://github.com/theoddden/Stamp-MCP.git
cd Stamp-MCP
docker compose up -d --build
```

Caddy handles TLS automatically once your DNS A record points at the host.
See `deploy/Caddyfile` and `deploy/stamp-mcp.service` (systemd, bare-metal
alternative).

Deployment to AWS is automated via GitHub Actions (`deploy.yml`) using SSM
Run Command — no inbound SSH needed.

---

## Wire protocol

```
>>> {"jsonrpc":"2.0","id":1,"method":"initialize","params":{...}}
<<< {"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2024-11-05",...}}
>>> {"jsonrpc":"2.0","method":"notifications/initialized"}
>>> {"jsonrpc":"2.0","id":2,"method":"tools/list"}
<<< {"jsonrpc":"2.0","id":2,"result":{"tools":[...]}}
>>> {"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"get_time","arguments":{}}}
<<< {"jsonrpc":"2.0","id":3,"result":{"content":[{"type":"text","text":"..."}]}}
```

---

## License

Copyright 2024 theoddden. Licensed under the
[Apache License, Version 2.0](LICENSE).

---

*DISCLAIMER: Stamp queries public NTP infrastructure (Cloudflare, stratum 3)
and is suitable for general agentic workflows. It is not intended for use
cases requiring certified atomic precision, legal timestamp authority, or
regulated audit trails. Use in production systems is at the implementer's
risk.*

TDQS

A4.2/5.0

Scored across 2 tools

Disambiguation5/5

get_time is responsible for sampling a new NTP timestamp and appending it to the drift log, while get_drift is responsible for analyzing the accumulated log. Although both mention offset, their roles are clearly separated: one collects data, the other interprets it.

Naming Consistency5/5

Both tools follow a consistent get_<noun> naming pattern, making the tool surface predictable and easy to understand. There is no mixing of styles or vague verbs.

Tool Count3/5

With only two tools, the server is on the thin side, but the two tools cover the core sampling and analysis workflow of a narrow domain. The count is understandable but still feels minimal.

Completeness4/5

The server covers the essential workflow: sample time and analyze drift. Minor gaps exist, such as no way to inspect raw log entries or clear/reset the drift log, but these are not fatal for the stated purpose.

Maintenance

ActivityMaintained
ResponsivenessNo issues