my-first-server
Click on "Deploy 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., "@my-first-serverWhat's the current time in Tokyo?"
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.
My First MCP Server
A minimal Model Context Protocol server that
exposes a single tool, get_current_time, over stdio. Built with the
TypeScript SDK.
Use it as a learning reference for how an MCP server is structured and how it connects to hosts like Claude Code and the Claude desktop app.
π See
docs/transports.mdfor notes on stdio vs HTTP transports, "Streamable HTTP," and the stateless spec update (2026-07-28) that removedMcp-Session-Idand the handshake.
How it works
An MCP host (Claude Code, the Claude desktop app) launches this server
as a child process and talks to it over stdin/stdout using newline-delimited
JSON-RPC. The server advertises its tools; when the model decides to use one,
the host sends a tools/call request, the handler runs, and the result flows
back into the model's context.
Claude (host) <--- JSON-RPC over stdio ---> node dist/index.js (this server)stdio gotcha: stdout is the JSON-RPC channel. Never
console.login a stdio server β it corrupts the protocol stream. Log to stderr withconsole.error(visible viaclaude --debugor the Inspector).
Key files:
File | Purpose |
| The tool definition ( |
| stdio entrypoint (what Claude Code / desktop launch). |
| Streamable HTTP entrypoint (Express, stateless, |
| Demo client β connects to the HTTP server, lists + calls the tool. |
| Registers the server with Claude Code (project scope, committed). Uses a relative path so it works on any machine. |
|
|
| Compiled output. Git-ignored β regenerated by the build. |
| Notes on stdio vs HTTP transports and the stateless spec update. |
Related MCP server: datetime-mcp
Setup
The Node version is pinned in .nvmrc. Select it first (installs it if needed):
nvm use # reads .nvmrc; run `nvm install` first if that version is missing
npm install # installs deps AND builds dist/ (via the "prepare" script)nvm use + npm install is all a fresh clone needs before the server is
runnable.
Use cases
1. Local development β fast edit/test loop
Run straight from TypeScript source, no build step, using tsx:
npm run devOr compile and run the built output (what the hosts actually execute):
npm run build # tsc -> dist/
npm start # node dist/index.jsRebuild after editing src/ if you're testing through a host, since hosts run
the compiled dist/index.js.
Tip: for a zero-build inner loop, you can point
.mcp.jsonat"command": "npx", "args": ["tsx", "src/index.ts"]so the host runs the TypeScript directly. Switch back todist/index.jsfor a "production" run.
2. Run the HTTP server + demo client
The same tool, served over Streamable HTTP instead of stdio. Start the server (it runs on its own; a host connects by URL), then run the client in a second terminal:
npm run start:http # -> http://localhost:3000/mcp (or npm run dev:http)
npm run client # connects, lists tools, calls get_current_timeThe server is stateless (no Mcp-Session-Id): each POST /mcp builds a
fresh server + transport. See docs/transports.md for why.
By default it replies over SSE so it can stream progress notifications
(see the stream_load tool). Set MCP_JSON=1 for single plain-JSON replies
(easier for curl, but no streaming):
MCP_JSON=1 npm run start:http
curl -s -X POST http://localhost:3000/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'To watch the raw SSE stream (progress events, then the final result), run the
server in default SSE mode and see the streaming example in
docs/transports.md.
To connect Claude Code to the HTTP server instead of stdio:
claude mcp add --transport http my-http-server http://localhost:3000/mcp3. Inspect & debug with the MCP Inspector
The official Inspector is a standalone host with a UI β the fastest way to see your tools, call them, and read stderr logs without involving Claude:
npx @modelcontextprotocol/inspector node dist/index.jsIt opens a local web UI where you can run the initialize handshake, browse
tools/list, and invoke get_current_time with arguments.
4. Test the raw protocol by hand
This is exactly what a host does β feed it the three startup messages and a call:
printf '%s\n%s\n%s\n' \
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"t","version":"1"}}}' \
'{"jsonrpc":"2.0","method":"notifications/initialized"}' \
'{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"get_current_time","arguments":{"timezone":"Asia/Kolkata"}}}' \
| node dist/index.js 2>/dev/null5. Connect to Claude Code
.mcp.json is committed, so Claude Code picks the server up automatically:
cd <this-project>
claude # start a session in the project dirOn first launch, approve the my-first-server prompt (remembered per project).
Then inside the session:
/mcp # verify: my-first-server -> connected, 1 tool
what time is it in Tokyo right now? # triggers get_current_timeDebug connection issues with:
claude --debug # shows the handshake and the server's stderr logs6. Connect to the Claude desktop app
The desktop app's config is machine-local (not in this repo). On macOS it lives at:
~/Library/Application Support/Claude/claude_desktop_config.jsonAdd an entry under mcpServers pointing at this machine's absolute path to
dist/index.js:
{
"mcpServers": {
"my-first-server": {
"command": "node",
"args": ["/absolute/path/to/this/repo/dist/index.js"]
}
}
}Then fully quit (Cmd+Q) and reopen the app β config is only read at startup. Look for the tools icon and ask the same time question.
After you push to GitHub
Committed: src/, package.json, package-lock.json, tsconfig.json,
.mcp.json, README.md, .gitignore.
Not committed (regenerated per machine): node_modules/, dist/, and the
Claude desktop config (it's outside the repo).
So a teammate cloning the repo just runs:
git clone <repo> && cd <repo>
npm install # builds dist/ automatically
claude # Claude Code auto-detects .mcp.json; approve onceThe desktop app is the only piece they re-register locally (use case 6), because its config uses an absolute path unique to their machine.
Publishing to npm (@srav/mcp-time-server)
Publishing lets anyone add the server with a single npx line β no clone, no
build, no absolute paths.
Prerequisites already in place:
binmapsmy-mcp-server β dist/index.js, andsrc/index.tsstarts with#!/usr/bin/env node, so the published package is directly runnable.files: ["dist"]ensures the compiled output ships in the tarball. (Without it, npm falls back to.gitignoreβ which excludesdist/β and would publish an empty package.)preparebuildsdist/automatically onnpm publish.publishConfig.access = "public"publishes the scoped package publicly.
Verify, then publish:
npm pack --dry-run # confirm dist/index.js is in the tarball
npm login # once
npm publish # runs prepare (build) then uploadsAfter publishing, switch .mcp.json (or use claude mcp add) from the local
path to the published package:
// local dev (current β keep while iterating)
{ "command": "node", "args": ["dist/index.js"] }
// published (after npm publish)
{ "command": "npx", "args": ["-y", "@srav/mcp-time-server"] }# or register from the CLI, user scope = available in every project
claude mcp add --scope user my-first-server -- npx -y @srav/mcp-time-servernpx downloads the package to its cache on first run, then executes the bin
entry β from there the stdio JSON-RPC flow is identical to running locally.
Distribution models at a glance
Model |
| When |
Local path |
| You, developing on this machine |
Cloned repo |
| Teammates who clone + |
Published npm |
| Anyone, anywhere β no clone needed |
Available Tools
2 toolsget_current_timeGet current timeA
Returns the current date and time. Optionally pass an IANA timezone (e.g. 'Asia/Kolkata', 'America/New_York') to get the time there.
| Name | Required | Description | Default |
|---|---|---|---|
| timezone | No | IANA timezone name, e.g. 'Asia/Kolkata'. Defaults to the server's local time. |
Output Schema
| Name | Required | Description |
|---|---|---|
| iso | Yes | ISO 8601 timestamp |
| timezone | Yes | The IANA timezone the result is expressed in |
| unixSeconds | Yes | Seconds since the Unix epoch |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the burden of behavioral disclosure. It conveys the core read-only behavior and optional timezone behavior, but does not mention edge cases like invalid timezone handling or output format. The output schema presumably covers return structure, but some informational context is still missing.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is a single, front-loaded sentence: the main purpose comes first, and the optional parameter detail follows. Every word earns its place, with no filler or repetition.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple tool with one optional parameter and an output schema, the description provides the essential information to invoke it correctly. It covers the default behavior and the optional timezone usage. Error behavior for invalid timezone is not described, but that is a minor gap given the tool's simplicity.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the schema already documents the timezone parameter. The description adds helpful IANA examples and clarifies the optionality and purpose of the parameter, but it does not provide substantial meaning beyond the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the operation ('Returns the current date and time') and the resource, making the tool's purpose unmistakable. It does not explicitly differentiate from sibling stream_load, but the purpose is precise enough that no confusion is likely.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description implies when to use the tool: whenever the current date/time is needed. It also explains the optional timezone parameter. However, it does not explicitly state exclusions or compare against alternatives beyond that implied context.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
stream_loadStreaming load demoA
Simulates loading in steps, streaming one progress notification per step, then returns a final message.
| Name | Required | Description | Default |
|---|---|---|---|
| steps | No | Number of progress steps to emit (default 5). |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the full burden. It discloses that the tool only simulates loading, streams one notification per step, and ends with a final message. It does not discuss side effects directly, but 'simulates' strongly implies no real-world impact.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single concise sentence that front-loads the main action and then explains the streaming behavior and final result. Every word earns its place, and there is no redundant repetition of the schema.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a single-parameter demo tool, the description plus schema is nearly sufficient: steps is fully documented and the simulation/streaming behavior is clear. However, with no output schema, the exact content of the final message and the shape of progress notifications remain unspecified.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The input schema already documents steps with its type, range, and default value, giving 100% schema coverage. The description adds no extra parameter-level meaning, so the baseline of 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description uses a specific verb ('Simulates') and names the resource ('loading in steps') along with the exact behavior: streaming one progress notification per step and returning a final message. This clearly distinguishes it from the unrelated sibling get_current_time.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The word 'Simulates' and the title 'Streaming load demo' imply this is for demonstration or testing rather than real loading, but the description never explicitly states when to use it or when to avoid it. No alternatives or exclusions are mentioned.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
2 tool updates
v1.0.0- First observed
get_current_time - First observed
stream_load
TDQS
Scored across 2 tools
The two tools are completely unrelated in functionβone returns time and the other simulates streamingβso there is zero ambiguity about which to choose. No overlap or boundary confusion exists.
Both tool names follow a clear snake_case verb_noun pattern: get_current_time and stream_load. The naming is predictable and consistent, even if the verbs are not from the same domain.
With only two tools, the set feels thin and barely qualifies as a server. It's on the borderline of the acceptable range for a minimal utility or demo server.
The tools do not belong to a shared domain, so there is no meaningful coverage to assess. Each tool is an isolated snippet with no related operations, leaving the server's purpose unclear and incomplete.
Maintenance
Related MCP Connectors
Timezone MCP β wraps WorldTimeAPI (free, no auth)
A real clock for AI agents: current time, timezone conversion, and DST facts from the IANA tzdb.
Current time, timezone conversion & date math for AI agents. On Cloudflare Workers.
Current time in any IANA time zone, plus the full time-zone list. Via timeapi.io.
Related MCP Servers
- FlicenseNot gradedqualityDmaintenanceA hosted MCP server providing current time, date, and timezone conversion tools specifically designed for Claude Code remote sessions. It enables users to fetch localized time data and convert times between IANA timezones through a centralized API.-
- AlicenseDqualityCmaintenanceA lightweight MCP server that provides date and time tools, including the ability to retrieve current timestamps and parse date strings with IANA timezone support. It enables AI models to interact with the host OS clock and perform temporal calculations via stdio transport.38 npm7MIT
- AlicenseAqualityDmaintenanceProvides accurate system time to LLM applications with multi-timezone support via a simple tool call.1MIT
- AlicenseNot gradedqualityCmaintenanceA remote MCP server exposing date/time tools over Streamable HTTP, enabling retrieval of current time for IANA timezones and listing available timezones, designed for use with web clients like Claude.ai.MIT