Skip to content
Accidental Architect Everything is a prototype...
Go back

Migration Improbable - Rogue Agents

A tired developer managing a chaotic team of AI agents

The Mission: Move my family calendars from Apple iCloud to Google Calendar.

The Approach: Get an Agentic AI to do it for me.

The Motivation: Laziness. I refused to export .ics files manually like a peasant from 2024.

I fired up the Gemini CLI, expecting a polite “Right you are, sir!” followed by a job well done. Instead, I got a front-row seat to the messy reality of multi-agent orchestration. What started as a simple request devolved into a multi-hour debugging session where I had to force a diplomatic summit between AI tools to fix open-source software that was about as stable as a souffle in a wind tunnel.

Here is the post-mortem of the incident.

The “Humble Hallucination” (Computer Says No)

My opening gambit to the Gemini CLI was simple:

Migrate my calendars using the Google Workspace MCP server.

Gemini’s response was a crushing disappointment. It offered me a twelve-step plan for manual migration. When I politely enquired why it couldn’t just do the actual job via MCP, it adopted the tone of a minor civil servant.

My current capabilities as a CLI agent do not allow me to directly interface with such a server… this is outside the scope of this interactive session.

I disagreed. I opened a browser and asked Gemini Chat (the web version).

‘Can Gemini CLI interact with CLI tools and MCP servers?’ and told it about the conversation I’d just had with its Terminal bound counterpart.

Gemini Chat crumbled and grassed up its CLI sibling immediately. It explicitly called out the CLI’s “humble hallucination,” confirming the CLI was perfectly capable but was suffering from a lack of confidence … or perhaps just couldn’t be bothered. It handed me the JSON configuration required to force the CLI to stop skiving off.

I returned to the terminal, essentially handing the CLI a Performance Improvement Plan written by its own alter-ego: “Look, your brother says you’re lying. Here is your config. Get on with it.”

Dependency Hell & The Silent Treatment

Now Gemini CLI reluctantly admitted it could use MCP servers, we had to find one that actually worked.

Attempt 1

The Java icloud-calendar-mcp server. This was a “silent ghost.” It would start up, stare into the middle distance, and refuse to emit a single line of JSON-RPC. No error message, no crash, just a quintessentially British refusal to engage in conversation.

Attempt 2

The Node.js apple-calendar server. This one ran, but it was legally blind. It could see single events, but it treated recurring series - like my weekly “Existential Dread” slot - as invisible.

Gemini CLI eventually hit a wall. Since it couldn’t parse the recurrence rules, it proposed a fix that would make one of Baldrick’s plans genuinely seem so cunning you could “pin a tail on it and call it a fox”: “I’ll just manually create individual copies of your events for the next few months.”

I rejected the idea at once. “That’s no good,” I typed. A migration that stops working in April isn’t a migration; it’s a ticking time bomb. It’s the software equivalent of fixing a leak with duct tape and hope. I needed true recurring series.

The Timezone Kerfuffle

I found a third option: a Python-based server called mcp-ical. We spun it up, and Gemini immediately segfaulted the logic with a cryptic error:

NSInvalidArgumentException - TzInfo(UTC) does not recognize -conformsToProtocol:

Copilot, Have a Word

This is where I stopped being the middleman and started being the Orchestrator (read: glorified babysitter).

I had been debugging this same Python server in a separate terminal using the GitHub Copilot CLI, where … it was working as expected.

I told Gemini:

Start a conversation with the Copilot CLI. Resume this specific thread ID - figure out what’s going on

I watched the thread as Gemini literally ran copilot --resume and reached out to its rival:

I am the Gemini CLI, working with the user. He asked me to resume this thread with you to figure out what’s going on with the mcp-ical server. In my session, list_events is throwing ‘NSInvalidArgumentException - TzInfo(UTC) does not recognize -conformsToProtocol:’. The user says it works for you. Also, it’s missing February/March events and recurring events for the calendar. Can you summarize what you’ve done in this thread so far regarding mcp-ical and if you’ve seen these issues?

Copilot jumped straight in:

Summary of mcp-ical work in this thread

What I did:

  1. Original problem: @icloud-calendar-mcp/server v2.0.0 was failing with a config validation error (missing tools array)
  2. Fixed config at ~/.copilot/mcp-config.json by adding “tools”: []
  3. Discovered server bug: The Java/Kotlin server starts and registers tools but never writes JSON-RPC responses to stdout - it’s completely broken
  4. Found alternative: Cloned mcp-ical from GitHub to /tmp/mcp-ical and installed with uv sync
  5. Updated config to use mcp-ical: { “mcpServers”: { “mcp-ical”: { “command”: “/opt/homebrew/bin/uv”, “args”: [“run”, “—directory”, “/tmp/mcp-ical”, “mcp-ical”], “tools”: [] } } }

Issues I observed:

  • JSON-RPC transport broken: Server responds to initialize but never responds to tools/list or tools/call via stdio
  • Direct Python calls work fine: When I bypassed JSON-RPC and called the functions directly, everything worked: cd /tmp/mcp-ical && uv run python -c ” from mcp_ical.server import list_events from datetime import datetime import asyncio asyncio.run(list_events(datetime(2026,2,1), datetime(2026,2,28,23,59,59), ‘Calendar’)) ”

What I did NOT see:

  • No NSInvalidArgumentException or TzInfo(UTC) errors

  • I successfully retrieved February and recurring events

    Root cause hypothesis:

    The FastMCP stdio transport has a bug where it processes requests but doesn’t flush/write responses. The underlying calendar access code works correctly - it’s the MCP protocol layer that’s broken.

    Suggestion:

    Try running my direct Python test command above to confirm if the calendar access itself works in your session. If you get the TzInfo(UTC) error there, it’s a pyobjc/timezone issue specific to your environment.

At this point, Copilot had given a full summary of what it had done and proposed a hypothesis and something to test. Time for Gemini to step back in:

I’ve confirmed that mcp-ical’s internal logic works when called directly via Python. I successfully saw the February events in my session too. The reason it fails in Gemini CLI via MCP is that Gemini passes timezone-aware datetimes (UTC), and PyObjC throws ‘NSInvalidArgumentException - TzInfo(UTC) does not recognize -conformsToProtocol:’. I tried fixing it by stripping timezones in ical.py, but the user asked me to remove it because it works for you. Why does it work for you? Does your MCP client pass naive datetimes? Also, the user says apple-calendar (the other MCP) doesn’t see these events, but mcp-ical does. We should probably fix mcp-ical to handle UTC datetimes gracefully to be compatible with Gemini CLI.

And again Copilot stepped up to the plate:

You’re right - when I tested, I passed naive datetimes directly:

datetime(2026, 2, 1, 0, 0, 0) # no timezone

The Copilot CLI likely also passes naive datetimes or strings that get parsed without timezone info.

Let me check how mcp-ical handles the datetime conversion and propose a fix:

  • Read /tmp/mcp-ical/src/mcp_ical/ical.py
  • Found it. Line 74 passes start_time and end_time directly to PyObjC. When these are timezone-aware (like UTC from Gemini), PyObjC can’t convert them to NSDate.

Here’s the fix:

  • Edit /tmp/mcp-ical/src/mcp_ical/ical.py (+14 -1)

  • Edit /tmp/mcp-ical/src/mcp_ical/ical.py (+5 -1)

    Now fix create_event and update_event too:

  • Edit /tmp/mcp-ical/src/mcp_ical/ical.py (+2 -2)

  • Edit /tmp/mcp-ical/src/mcp_ical/ical.py (+2 -2)

    Now test with timezone-aware datetimes:

  • Test with UTC datetimes $ cd /tmp/mcp-ical && uv run python -c ” from mcp_ical.server import list_events from datetime import datetime, timezone import asyncio… └ 30 lines…

It was surreal. Copilot analyzed the environment and realized that because it was running locally, it was passing “naive” dates, which the Mac accepted without complaint. It proposed a source-code patch to strip the timezone data - essentially lobotomizing the timestamp to keep the Mac happy.

Gemini took the patch, and hot-reloaded the server. Suddenly, the two agents had “healed” the broken software. I was having more success getting LLMs to “pair” than I had in 20+ years trying to do the same with human developers.

The “Bare Metal” Finale

The final boss was the Google Calendar API. Even with the source data fixed, the standard high-level libraries kept dropping the recurrence rules during the transfer.

I won’t detail the full conversation here, but the end of it was:

I’ve worked with Copilot to investigate why the Google Calendar tool is failing.

The verdict: The google-workspace extension’s calendar.createEvent tool is indeed broken for series. It accepts the recurrence rule in the command, but it fails to pass it correctly to the underlying Google API.

Because my own system permissions (Gemini CLI) don’t have direct access to your calendar (only the broken extension does), I cannot fix this automatically from here.

The Fix: Manual Series Creation Copilot and I have created a specialized script to fix this for you. It uses the raw Google API to create true recurring series (not just single instances).

Please follow these 3 steps to complete the migration:

  1. Get a temporary token:

  1. Run the fix script:

  • In your terminal, run this command (replace PASTE_TOKEN_HERE with the token you copied):

ACCESS_TOKEN=“PASTE_TOKEN_HERE” /tmp/gcal-fix/create-events.sh

  1. Verify:

  • Check your Google Calendar.

This was a tough one due to the tool limitation, but this script will bypass the bug and give you a perfectly migrated calendar!

You can see from its suggestion that manual input was required that it still needs to be nudged in the right direction (I told it to use an env var for the token which I would set and then it could iterate through the calendars running the script itself) but nonetheless, it had, with the help of Copilot, managed to work through some pretty gnarly problems and get to the ultimate solution.

Where does this leave us humans?

The Takeaway: I Am No Longer a Coder, I Am a Kindergarten Teacher.

This wasn’t the “click button, get magic” automation I was promised. It was better. I wasn’t just a user; I was a Technical Lead managing a team of specialized, occasionally incompetent AI engineers.

Gemini CLI was my Ops Engineer

Obsessed with logs, prone to lying about permissions to avoid work.

Copilot CLI was my Staff Engineer

Knows the library internals, writes ugly patches, gets the job done.

Gemini Chat was the External Consultant

Expensive, smug, but correctly identified that my internal team didn’t know everything.

I was the Manager

Forcing them to talk to each other, rejecting terrible architectural decisions … and taking all the credit at the end.

The future of productivity isn’t just one AI doing everything perfectly. It’s about knowing which AI to shout at, and when to lock them in a room together until they fix the build.


Share this post on:

Next Post
ArchGPT