
Company News
Socket Joins New OpenJS Program to Fund Node.js Security Work
Socket is joining the OpenJS Security Stewardship Program to fund Node.js vulnerability research, maintainer remediation, and security releases.
atomic-clock-mcp
Advanced tools
MCP server that returns UTC time from NTP atomic clocks. Zero dependencies, raw JSON-RPC over stdio, fully synchronous.
An MCP server that returns current UTC time from NTP atomic clocks. Zero dependencies, fully synchronous, raw JSON-RPC 2.0 over stdio -- no MCP library, no asyncio.
pip install atomic-clock-mcp
Edit ~/Library/Application Support/Claude/claude_desktop_config.json:
{
"mcpServers": {
"atomic-clock": {
"command": "atomic-clock-mcp"
}
}
}
Or run the script directly:
{
"mcpServers": {
"atomic-clock": {
"command": "python3",
"args": ["-m", "atomic_clock_mcp"]
}
}
}
Restart Claude Desktop and ask "what time is it?" -- Claude will call
get_atomic_time, which queries time.cloudflare.com over UDP port 123.
get_atomic_time -- returns UTC time, NTP stratum, and the server
queried. Optional argument: server (default time.cloudflare.com).The whole server is atomic_clock_mcp/server.py, ~150 lines:
initialize, tools/list, tools/call,
ping, notifications, and the standard error codes (-32700, -32601).0x1B + 47 zero bytes) sent via
socket.sendto to UDP 123; the transmit timestamp is unpacked with
struct.unpack("!I", ...) and converted from the 1900 NTP epoch to Unix
time by subtracting 2208988800.flush() after every write (subprocess stdout is
block-buffered).client.py is a test harness that plays the role of an MCP host -- it
spawns the server and performs the real handshake, printing every raw
frame:
python3 client.py # tests server.py (stage 1)
python3 client.py server_atomic.py # tests the standalone server
python3 client.py atomic_clock_mcp/server.py # tests the package
server.py and server_atomic.py are the from-scratch learning artifacts;
atomic_clock_mcp/ is the same code packaged for PyPI.
The GitHub Action in .github/workflows/publish-mcp.yml runs on version
tags (git tag v0.1.0 && git push origin v0.1.0) and does two things:
PYPI_API_TOKEN
repository secret (or configure Trusted Publishing on PyPI and remove
the password line).mcp-publisher with
GitHub OIDC (id-token: write), no secret needed. The server name
io.github.theoddden/atomic-clock is bound to the GitHub account; the
mcp-name HTML comment at the top of this README is the PyPI ownership
verification marker.Build an MCP server with no library, one concept at a time. By the end you will have written every line yourself and the official MCP SDK becomes a convenience you could discard.
server.py -- the entire protocol in ~100 lines: sys.stdin -> json ->
dispatch -> sys.stdout -> flush().client.py -- plays the role of Claude Desktop. Spawns the server and
performs the real handshake, printing every raw frame.Run it:
python3 client.py
Things to notice in the output:
initialize returns protocolVersion, capabilities, serverInfo.notifications/initialized has no id and gets no response.tools/list returns the manifest; inputSchema is plain JSON Schema.tools/call results are {"content": [{"type": "text", ...}]}.-32601.isError: true (so the model can
read the failure and recover).-32700.Two rules that will bite you if ignored:
print() corrupts the
stream. Log to stderr only.Rewrite the stdin loop as an async coroutine:
async def main() + asyncio.run(main())loop.run_in_executor(None, sys.stdin.readline) or
asyncio.StreamReader hooked to stdin via loop.connect_read_pipe.await each handler.The payoff comes in stage 4 -- for now it is the same server with a different engine.
Replace the stub get_time with a real query.
pip install ntplib, then
ntplib.NTPClient().request('pool.ntp.org', version=3).0x1B (LI=0, VN=3,
Mode=3), rest zeros. Send to port 123, read 48 bytes back, unpack the
transmit timestamp (bytes 40-43, seconds since 1900) with
struct.unpack('!I', ...). Subtract 2208988800 to get Unix time.
ntplib is ~200 lines of exactly this -- read its source once.The lesson you learn by breaking it:
ntplib directly inside your async def handler.ping from the client.
Watch it hang -- the single-threaded event loop is frozen.await loop.run_in_executor(None, blocking_ntp_call).
Blocking work goes to the thread pool; async work gets awaited.Rule of thumb: ntplib, requests, file I/O = blocking. aiohttp,
httpx (async mode), asyncpg = not blocking.
Edit ~/Library/Application Support/Claude/claude_desktop_config.json:
{
"mcpServers": {
"ntp-scratch": {
"command": "/usr/bin/python3",
"args": ["/Users/theowolfenden/CascadeProjects/mcp-from-scratch/server.py"]
}
}
}
Restart Claude Desktop, then ask it "what tools do you have?" -- get_time
should appear. If it doesn't, check the logs at
~/Library/Logs/Claude/mcp*.log -- a stray print or missing flush is the
usual culprit.
>>> {"jsonrpc":"2.0","id":1,"method":"initialize","params":{...}}
<<< {"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2024-11-05",...}}
>>> {"jsonrpc":"2.0","method":"notifications/initialized"} (no reply)
>>> {"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":"..."}]}}
That is the whole thing. Everything else is plumbing.
FAQs
MCP server that returns UTC time from NTP atomic clocks. Zero dependencies, raw JSON-RPC over stdio, fully synchronous.
The pypi package atomic-clock-mcp receives a total of 193 weekly downloads. As such, atomic-clock-mcp popularity was classified as not popular.
We found that atomic-clock-mcp demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 0 open source maintainers collaborating on the project.

Company News
Socket is joining the OpenJS Security Stewardship Program to fund Node.js vulnerability research, maintainer remediation, and security releases.

Security News
Two compromised GitHub Actions were re-enabled with malicious tags intact, exposing thousands of downstream repositories to Mini Shai-Hulud.

Research
/Security News
A malicious Firefox extension fetches its payload after installation to evade detection, steal Google session cookies, and automate account takeover.