Zaid Bin Haris Available for new work
All work

Tasks & calendar MCP server

When answering 401 is the bug

One endpoint that lets an assistant read and write Google Tasks and Calendar, hardened against a specific client's probing behaviour.

  • Year
    2026
  • tools live
    35
  • Stack
    Python, FastMCP, Google APIs
  • Status
    Live

tasks.kaizenware.deLive, open it in a new tab

Tasks & calendar MCP serverdeterministicmodel
How the MCP server is exposed The assistant reaches a hostname at the edge. An outbound-only tunnel from the virtual machine connects to that edge, so no inbound port is open. Inside the machine the service binds loopback only. Requests without a valid key are answered 400 rather than 401. assistant claude.ai edge tasks.kaizenware.de virtual machine ยท only port 22 reachable server 127.0.0.1 only 35 tools outbound tunnel no inbound port no key โ†’ 400, not 401

The problem

Google Tasks and Google Calendar are separate APIs with separate quirks. An assistant asked to plan a day has to call both, reconcile them, and understand which of the two owns a given thing.

Exposing the raw endpoints and hoping the model composes them correctly does not work reliably. So the server exposes 35 tools shaped like the questions people actually ask: give me my day, find me a free slot, reschedule everything overdue.

The constraint

The machine is an always-free ARM instance with two cores and one publicly reachable port, which is SSH. Nothing else can be opened, so the service binds loopback and is published through an outbound-only tunnel. There is no inbound port to attack and no firewall rule to maintain.

SELinux is enforcing, which quietly rules out a whole class of layouts: a unit rooted in a home directory cannot read its own environment file. Everything installs under /opt and gets relabelled.

The decision that took longest to find

The client probes the endpoint without the headers it has been configured with. Answer 401 to that probe and it concludes the server wants a full interactive sign-in flow, then fails with a sign-in error that has nothing to do with the real problem.

It answers 400 instead. A missing or wrong key gets a bad request, not an unauthorised. Omitting the challenge header was not sufficient; the status code alone was the trigger. The well-known discovery paths answer 404 before the auth check runs, for the same reason.

This is the kind of thing that is invisible in documentation and obvious only from the failure.

Three Google behaviours worth writing down

The Tasks due-date filter is date-exclusive, so the obvious range query silently drops the last day.

An all-day calendar event's end date is also exclusive, which means a one-day event ends tomorrow.

Availability has to be computed in the calendar's own timezone rather than the server's, which is why the timezone database is pinned as a dependency rather than inherited from the host.

The result

Live and in daily use, with 35 tools across tasks and calendar, on a machine that costs nothing to run.

At a glance

Tools
35
Runs on
2 cores, 10 GiB, ARM
Exposure
Outbound tunnel only
Auth
Key on every request