loading…
Search for a command to run...
loading…
An MCP server that enables interaction with CalDAV calendars to manage events and check availability through natural language or voice commands. It provides spe
An MCP server that enables interaction with CalDAV calendars to manage events and check availability through natural language or voice commands. It provides specific tools for listing, searching, and creating calendar entries using an OpenAI-compatible interface.

Voice + MCP calendar demo.
This project is a small Express app that:
public/) that records audio, transcribes it (Whisper API), and sends the resulting prompt to the MCP client endpoint/v1/chat/completions)/v1/audio/transcriptions)In this lab, OPENAI_PROXY_URL typically points to a proxy service (not OpenAI directly). The proxy is responsible for adding any required authentication (e.g. API keys) to outgoing requests.
Because auth is handled by the proxy, the server uses direct HTTP requests (via fetchData) instead of the official OpenAI SDK.
Install
npm install
Create .env
cp .env-sample .env
Then edit .env and set OPENAI_PROXY_URL to your proxy/base URL (e.g. http://localhost:1234 or your deployed proxy).
Run the dev server
npm run dev
Open the demo UI
http://localhost:3000/Create a .env file in the project root.
PORT (optional, default: 3000)NODE_ENV (optional, e.g. development)MCP_SERVER_URL (required)http://localhost:3000/api/v1/mcpOPENAI_PROXY_URL (required)/v1/chat/completions and /v1/audio/transcriptions under it)http://localhost:1234OPENAI_MODEL (optional, default: gpt-4o)OPENAI_TRANSCRIPTION_MODEL (optional, default: whisper-1)CALDAV_SERVER_URL (optional, default: http://localhost:5232/)CALDAV_USERNAME (optional, default: username)CALDAV_PASSWORD (optional, default: password)For local development you can run a minimal Radicale CalDAV server with no authentication.
python3 -m pip install --user radicale
mkdir -p radicale-data/collections
cat > radicale.config <<'EOF'
[server]
hosts = 127.0.0.1:5232
[auth]
type = none
[storage]
filesystem_folder = ./radicale-data/collections
EOF
radicale --config ./radicale.config
py -m pip install --user radicale
New-Item -ItemType Directory -Force -Path .\radicale-data\collections | Out-Null
@'
[server]
hosts = 127.0.0.1:5232
[auth]
type = none
[storage]
filesystem_folder = ./radicale-data/collections
'@ | Set-Content -Encoding UTF8 .\radicale.config
py -m radicale --config .\radicale.config
In .env:
CALDAV_SERVER_URL=http://localhost:5232/CALDAV_USERNAME=anything (ignored when auth=none)CALDAV_PASSWORD=anything (ignored when auth=none)npm run dev – run server with nodemon + ts-nodenpm run build – compile TypeScript to dist/npm start – run the compiled server (dist/index.js)Base path is /api/v1.
GET /api/v1/
Returns a simple JSON message.
POST /api/v1/mcp
Implements the MCP Streamable HTTP transport.
POST /api/v1/client
Accepts either:
curl -sS \
-H 'Content-Type: application/json' \
-d '{"prompt":"List my events","timezone":"Europe/Helsinki"}' \
http://localhost:3000/api/v1/client
multipart/form-data (audio upload)The field name must be audio.
curl -sS \
-F '[email protected]' \
-F 'timezone=Europe/Helsinki' \
http://localhost:3000/api/v1/client
Response:
{
"answer": "...",
"toolCalls": 2
}
The MCP server currently exposes:
listEvents – list events in the primary CalDAV calendargetEventsInTimeSlot – check availability for a time slot (relative date inputs)createEvent – create an event (relative date inputs + title + optional description/location)The MCP client instructs the model to use tools for all user requests and applies some workflow rules (e.g., check availability before creating events when the user asks “if the time is free”).
CALDAV_* values and that your user has at least one calendar.OPENAI_PROXY_URL must be the base URL; the app calls /v1/chat/completions and /v1/audio/transcriptions under it.uploads/ grows unexpectedly.POST /api/v1/clientRuns a user prompt through the MCP client. The server will call an OpenAI-compatible Chat Completions API, and the model can invoke MCP calendar tools (via the MCP server URL).
Supported content types:
application/json (text prompt)prompt (string, required) – the user command/questiontimezone (string, optional) – IANA timezone name (defaults to the server’s default timezone). See: List of tz database time zonesJSON request body example:
{
"prompt": "List my events",
"timezone": "Europe/Helsinki"
}
Example:
curl -sS \
-H 'Content-Type: application/json' \
-d '{"prompt":"List my events","timezone":"Europe/Helsinki"}' \
http://localhost:3000/api/v1/client
Option 2: multipart/form-data (audio)
audio (file, required) – audio file to transcribe (browser demo sends audio/webm)timezone (string, optional) – IANA timezone nameIn this mode the server transcribes the audio first and uses the transcription text as the prompt.
Example:
curl -sS \
-F '[email protected]' \
-F 'timezone=Europe/Helsinki' \
http://localhost:3000/api/v1/client
On success (HTTP 200):
{
"answer": "...",
"toolCalls": 2
}
answer (string) – final assistant outputtoolCalls (number) – total number of tool calls made during the run400 – invalid request body (e.g., missing prompt in JSON, invalid timezone)500 – transcription failures, OpenAI/MCP errors, or unexpected server errorsErrors are returned as JSON:
{
"message": "..."
}
mc
Добавь это в claude_desktop_config.json и перезапусти Claude Desktop.
{
"mcpServers": {
"mcp-labrat": {
"command": "npx",
"args": []
}
}
}