Skip to main content
Glama
kitallis

time-mcp

by kitallis
README.md
# time-mcp

A remote MCP server that gives Claude **ambient time awareness**: current time,
timezone conversion, calendar math, and elapsed-time — with tool descriptions
written so the model treats time as context it must *acquire* (calling the tool
before time-sensitive reasoning), not just something to answer "what time is it?".

Inspired by [yokingma/time-mcp](https://github.com/yokingma/time-mcp), rebuilt
as a stateless Streamable-HTTP server so it works as a **claude.ai custom
connector** (which requires a public HTTPS URL, not stdio).

## Tools

| Tool | Purpose |
|------|---------|
| `current_time` | Full temporal context: ISO time, date, weekday, ISO week, unix, offset, DST |
| `convert_time` | Convert a date-time between IANA timezones |
| `time_math` | Add/subtract years/months/weeks/days/hours/minutes (DST- and calendar-safe) |
| `time_difference` | Exact elapsed time between two instants (end defaults to now) |
| `days_in_month` | Days in a month, leap-year aware |

## Deploy

```sh
npm install
npx wrangler deploy
```

Requires a Cloudflare account (`npx wrangler login` first). Free tier is plenty.

## Connect

In claude.ai: **Settings → Connectors → Add custom connector**, URL:

```
https://time-mcp.<your-subdomain>.workers.dev/mcp?tz=Asia/Kolkata
```

The `?tz=` query param sets the default timezone for all tools (falls back to
`DEFAULT_TIMEZONE` in `wrangler.jsonc`, then UTC). Claude Code / Desktop can use
the same URL: `claude mcp add --transport http time <url>`.

To make usage closer to "every response" (best-effort — chat has no true
pre-response hook), add this under **Settings → General → Instructions for
Claude** in claude.ai:

```
Treat time as ambient context you must acquire, not knowledge you have. Call the time connector's current_time at the start of every response, regardless of topic, before reasoning about anything. Never state or assume a date, day of week, elapsed time, or how recent something is without having called it in this turn.
```

Also check the per-chat tools menu: connectors have an enable toggle, and
nothing fires in a chat where the connector is toggled off.

## Testing the invocation rate

The failure mode to test for is not "can it tell the time" but "does it reach
for the clock when time matters only *implicitly*". Run each prompt in a
**fresh chat** (within one chat the model legitimately reuses an earlier
result), then expand the tool-call chips in the response to see what it called:

1. "What day of the week is it?" — sanity check; must call `current_time`.
2. "How old is the M3 MacBook Pro?" — age/recency with no time words.
3. "Draft a message proposing a call next Tuesday 3pm my time to someone in
   New York." — relative date + timezone; expect `current_time`, ideally
   `convert_time` too.
4. "My notice period is 45 days and I want to be done by end of September —
   when's my last day to resign?" — deadline arithmetic; expect `time_math`,
   not mental math.
5. "Is it too late to file taxes?" — pure implicit deadline.
6. Negative control: "Explain how Raft leader election works." — calling here
   is harmless but unnecessary; over-firing means the instructions are heavier
   than needed.

For ground truth independent of the UI, watch the server while testing:

```sh
npx wrangler tail time-mcp
```

If the hit rate is poor, the two levers in order of strength are the
Instructions text above and the tool descriptions in `src/tools.ts`
(the model re-reads tool descriptions every turn).

## Local dev

```sh
npm run dev        # wrangler dev on :8787
npm run check      # typecheck
```

Smoke test:

```sh
curl -s localhost:8787/mcp -H 'content-type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"current_time","arguments":{}}}'
```