datetime-mcp-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., "@datetime-mcp-serverwhat's the current date and 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.
datetime MCP Server
A Model Context Protocol server providing current date/time queries, written in TypeScript, designed to be deployed on AWS Lambda and served over Streamable HTTP.
Features
Two MCP tools:
get_date,get_datetime, output in ISO 8601 formatOptional timezone parameter: supports IANA timezone names (
Asia/Shanghai) and UTC offsets (+08:00), defaults to UTCZero third-party time libraries: built entirely on Node.js built-in
Intl(ICU), no moment/luxon/dayjs dependenciesServerless deployment: single-file bundle (~2MB) + Lambda Web Adapter, millisecond responses on warm instances after cold start
Compliant with the latest MCP standard: Streamable HTTP transport, stateless operation mode, protocol version negotiation handled by the official SDK
Related MCP server: date-today-mcp
Architecture
MCP Client ──HTTP POST /mcp──▶ Function URL ──▶ Lambda 函数
│
├─ LWA Layer (AWS_LAMBDA_EXEC_WRAPPER=/opt/bootstrap)
│ └─ 执行 zip 根目录 bootstrap → node dist/index.js
│
└─ Express app (端口 8080)
└─ POST /mcp → StreamableHTTPServerTransport
(stateless:每请求新建 transport,
enableJsonResponse 返回普通 JSON)Key mechanisms:
Component | Description |
Streamable HTTP | The only HTTP transport currently recommended by MCP; HTTP+SSE is deprecated |
Stateless mode | Each request is self-contained with no session maintained, fitting Lambda's freeze/thaw model |
| Responses go over plain JSON instead of SSE streams, avoiding Lambda response buffering limits |
Lambda Web Adapter (LWA) | AWS official public Layer that translates Lambda invokes into real HTTP requests to localhost:8080 |
| The actual process entry point after LWA takes over ( |
MCP Tools
get_date
Returns the current calendar date, ISO 8601 YYYY-MM-DD.
Parameter | Type | Required | Description |
| string | No | IANA timezone name or UTC offset, defaults to |
{"result":{"content":[{"type":"text","text":"2026-08-24"}]}}get_datetime
Returns the current date and time, ISO 8601 with an offset suffix (Z for UTC output).
{"result":{"content":[{"type":"text","text":"2026-08-23T14:30:00+08:00"}]}}Error Behavior
Missing or blank timezone → treated as UTC
Non-empty but invalid timezone → returns an
isError: trueresult with a hint message:
Error: Invalid IANA timezone "Mars/Olympus". Use a name like "UTC", "Asia/Shanghai" or "America/New_York".Protocol-level errors (e.g., missing Accept header) are returned by the SDK as standard JSON-RPC errors
Time Source and Precision
Time is read from the runtime environment's system clock (new Date()). Locally, that is the machine clock (relying on NTP calibration); on Lambda, the host clock is automatically synchronized by the Amazon Time Sync Service (GPS/atomic clock source, PTP/NTP), with sub-millisecond error and no configuration required.
External NTP queries are deliberately not performed on every call: the AWS clock is already continuously synchronized, and external lookups would only add latency (+10~100ms), introduce network failure surfaces and rate-limit issues, without improving accuracy.
Output resolution is precise to the second (milliseconds are truncated). Timezone conversion is pure arithmetic — all timezones receive the same absolute instant, only the wall-clock representation differs.
Local Development
Requires Node.js ≥ 22.
npm install # 安装依赖
npm run dev # 构建 + 启动(默认 http://localhost:3000/mcp)
npm test # vitest 单测(14 个用例)
npm run typecheck # tsc --noEmit
npm run lint # eslint
npm run build # esbuild 打包为 CJS 单文件 dist/index.jsLocal manual testing (the Accept header must declare both media types; this is the SDK's protocol validation):
curl -s http://localhost:3000/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"get_datetime","arguments":{"timezone":"Asia/Shanghai"}}}'You can also use the official inspector: npx @modelcontextprotocol/inspector, selecting Streamable HTTP as the connection type.
Note: if the
http_proxyenvironment variable is set on your machine, curl needs--noproxy '*', otherwise localhost requests will be intercepted by the proxy and return 502.
Project Structure
├── src/
│ ├── time.ts # 核心纯函数:ISO 格式化、时区校验(可独立单测)
│ ├── server.ts # McpServer 实例 + 工具注册
│ └── index.ts # Express app + stateless transport 挂载 + 进程入口
├── test/
│ └── time.test.ts # 固定时间戳验证多时区/DST/半小时偏移/错误输入
├── build.mjs # esbuild 构建脚本(bundle: platform=node22, format=cjs)
├── bootstrap # LWA 进入口(zip 根目录,需可执行位 755)
├── deploy.sh # CLI 一键部署脚本
├── function.zip # 部署包(构建产物,不入库)
└── dist/index.js # 打包产物(约 2MB,零运行时依赖安装)Technical Implementation Highlights
All timezone formatting is done through Intl.DateTimeFormat. Core techniques:
Date: the
en-CAlocale naturally outputsYYYY-MM-DD, no manual assembly neededTime with offset: use
formatToPartsto get the target timezone's wall-clock fields → feed them intoDate.UTC()as if they were UTC → subtract from the original instant to get a minute-level offset → format as±HH:MMorZhourCycle: "h23": avoids the"24"hour boundary bug thathour12: falsecan produce in some localesDST and special timezones: the offset is computed at the request instant, so DST transitions are naturally correct (New York
-04:00→-05:00); half-hour and quarter-hour timezones (India+05:30, Nepal+05:45, Chatham+12:45) are all covered by unit testsFormatter caching:
Intl.DateTimeFormatinstances are cached per timezone, so repeated calls on warm instances have zero construction overheadTimezone validation: construct
Intl.DateTimeFormatin a try/catch; a thrownRangeErrorfor invalid values means invalid. The newer ECMA-402 makes ICU also accept UTC offset strings (e.g.,+08:00); the full chain has been verified to output correctly and is covered by tests
Deploying to AWS Lambda
Prerequisites: AWS CLI configured with credentials; an execution role with AWSLambdaBasicExecutionRole permission (if you don't have one, create it with the command below):
aws iam create-role --role-name datetime-mcp-role \
--assume-role-policy-document '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"Service":"lambda.amazonaws.com"},"Action":"sts:AssumeRole"}]}'
aws iam attach-role-policy --role-name datetime-mcp-role \
--policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRoleOption 1: CLI Script
ROLE_ARN=arn:aws:iam::<账号ID>:role/datetime-mcp-role ./deploy.shOptional environment variables: AWS_REGION (default us-east-1), FUNCTION_NAME (default datetime-mcp), ARCH (arm64/x86_64, default arm64).
Script flow: dynamically queries the latest LWA Layer version for the region → esbuild build → bsdtar packaging (for systems without the zip command) → create/update function (nodejs22.x, 512MB, 10s timeout) → create an unauthenticated Function URL and grant public invocation → output endpoint. Safe to re-run; automatically takes the update path.
Option 2: AWS Console
Package locally:
npm run build && bsdtar --format zip -cf function.zip bootstrap dist/index.jsLambda console → Create function → Author from scratch
Runtime: Node.js 22.x; Architecture: arm64
Permissions: Create a new role with basic Lambda permissions
Code tab → Upload from → .zip file → select
function.zip(the internal structure must be a root-levelbootstrap+dist/index.js)Layers section → Add a layer → AWS layers → LambdaAdapterLayerArm64 (select the latest version)
Configuration → Environment variables:
Key
Value
AWS_LAMBDA_EXEC_WRAPPER/opt/bootstrapPORT8080Configuration → General configuration → Memory 512 MB, Timeout 10 s
Configuration → Function URL → Create → Auth type NONE
When creating a NONE-type Function URL in the console, the public invocation resource policy is added automatically, so no
add-permissionis needed (this differs from the CLI).
A browser directly accessing the Function URL returning 405 is normal (only POST is allowed). To update code later: rebuild and repackage, then re-upload in the Code tab.
MCP Client Integration
Using opencode as an example, add a remote MCP server to the config:
{
"mcp": {
"datetime": {
"type": "remote",
"url": "https://<identifier>.lambda-url.<region>.on.aws/mcp"
}
}
}Design Decision Record
Decision | Conclusion | Rationale |
Language | TypeScript |
|
Transport | Streamable HTTP + stateless + JSON response | Lambda has no persistent process, so stdio is not viable; no session state naturally matches the freeze/thaw model; plain JSON avoids SSE streaming limits |
SDK version | Stable | Correctly implements protocol version negotiation, backward compatible with older clients; new features in the 2026-07-28 revision (TTL caching, etc.) are meaningless for a static tool list; beta split-package API is not stable |
Timezone implementation |
| Zero dependencies, no need to install tzdata on Lambda, sufficient precision |
Time source | System clock rather than per-call NTP | AWS already does sub-millisecond synchronization; external NTP lookups add latency/failure surface/cost with no accuracy benefit |
Deployment | Manual CLI/console + LWA Layer | Avoids the upfront investment of SAM/CDK/Terraform; deploy.sh is just command batching, not IaC |
Troubleshooting
Symptom | Cause and Solution |
| Request is missing the Accept header. curl tests need |
| Forgot to attach the LWA Layer or missed setting |
GET /mcp returns 405 | Expected behavior. Stateless mode does not support server-push streams; only POST is accepted |
curl local test returns 502 with an extremely fast response | A system proxy intercepted localhost. Add |
First request to the Function URL after cold start is slow | Caused by Lambda's freeze/thaw mechanism; subsequent requests are millisecond-level |
| The zip package is not installed on the machine (on Arch it is a separate package from unzip). This project uses |
Known Limitations and Future Improvements
Defensive handler not added: if run bare on the default Node.js runtime (without LWA), the error message is obscure. A placeholder handler with a hint could be exported to improve the error experience on misconfiguration, with zero impact on the normal path
Output is only precise to the second; milliseconds are not exposed
No authentication or rate limiting — a public endpoint could be abused to inflate call volume (though the per-call cost approaches zero)
The alias table of
Intl.supportedValuesOf("timeZone")is not used for parameter hints; error messages only give three examplesMigration from the stable 1.x SDK to the split packages (supporting the 2026-07-28 revision) will require tracking API changes
License
This server cannot be deployed
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 in any IANA time zone, plus the full time-zone list. Via timeapi.io.
Current time, timezone conversion & date math for AI agents. On Cloudflare Workers.
Related MCP Servers
- FlicenseAqualityNot gradedmaintenanceProvides timezone-aware date and time information with configurable time formats and timezone support. Enables users to get current date and time in their preferred timezone and format through simple MCP tools.2-
- FlicenseAqualityDmaintenanceProvides the current date in multiple formats (e.g., European, ISO, US) via a simple MCP tool.1-
- FlicenseAqualityDmaintenanceA simple MCP server that returns the current date and time with timezone information in ISO 8601, Unix timestamp, and human-readable formats.1-
- AlicenseNot gradedqualityDmaintenanceProvides time-related tools including current time retrieval, timezone conversion, time formatting, and timezone info via MCP.6 npmMIT