Find a meeting time across people and organizations without anyone sharing their calendar.
The organizer (or their AI agent) proposes candidate times that already work for them. Everyone invited votes preferred, if necessary or no on each time, from whichever MCP-capable agent they use (Claude Desktop, Claude Code, GitHub Copilot, RockBot, ...) or from a web page linked in the invitation email. The server never reads a calendar: each person's own agent checks their own calendar, and the server only ever learns the votes.
See docs/design.md for the full design, and docs/identity-setup.md for setting up sign-in on a deployed server.
Scheduler deliberately never sees a calendar, so each person's agent needs calendar tools of its own. Adjutant (calendar-mcp) is an MCP server that gives an agent email, calendar and contacts across Microsoft 365, Outlook.com, Google Workspace and IMAP accounts. With both connected, an agent can do the whole loop:
- Organizing: find free times with Adjutant's
find_available_times, then propose them with Scheduler'screate_event. Once a time is picked (finalize_event), create the real meeting with Adjutant'screate_eventand report it back withrecord_calendar_event. - Being invited: find Scheduler's invitation emails (subjects start with
[sched:CODE]) with Adjutant'ssearch_emails, check each proposed time againstget_calendar_events, thenvote, and accept the organizer's real invitation later withrespond_to_event.
Neither needs the other: any calendar MCP server works, and people without an agent vote from the link in the invitation email.
Version 1 is complete (roadmap steps 1 to 5 in the design doc) and runs in production:
- MCP endpoint (
/mcp) with every tool, both prompts and the server instructions - Sign-in: Microsoft and Google for the browser, OAuth for MCP clients (this app is its own authorization server, federating to the same providers), personal access tokens for headless agents such as RockBot, and an emailed link for addresses a provider doesn't verify
- Web UI: the voting page opened from the invitation email, the organizer's dashboard, compose and event pages (live as votes arrive), account and admin pages
- Email: plain-text and HTML messages through an outbox and any SMTP relay, one-click unsubscribe, and reminder, deadline, retention and time-zone jobs
- Rate limits, per-organizer limits, and the Kubernetes deployment (
k8s/, docs/deployment.md)
Next (v1.1 in the design doc): a long-running wait_for_update task, RockBot integration, and an .ics
invite for organizers without a calendar MCP server.
| Path | What |
|---|---|
src/Scheduler.Core |
Domain model and rules. No infrastructure dependencies. |
src/Scheduler.Data |
EF Core SchedulerDbContext and the ISchedulingService implementation |
src/Scheduler.Web |
ASP.NET Core host: Blazor UI, MCP endpoint, email, background workers |
src/Scheduler.ServiceDefaults |
Aspire defaults: OpenTelemetry, health checks, resilience |
src/Scheduler.AppHost |
Aspire AppHost for local development (PostgreSQL + Mailpit) |
tests/Scheduler.Core.Tests |
MSTest unit tests for the domain rules |
tests/Scheduler.Data.Tests |
Integration tests for SchedulingService against PostgreSQL (Testcontainers) |
tests/Scheduler.Web.Tests |
The MCP OAuth flow end to end with the MCP SDK client, the organizer pages (prerendered), the voting page, HTML emails and unsubscribing, emailed sign-in and address links, rate limits, plus the identity rules |
docs/design.md |
Design document |
docs/identity-setup.md |
Operations: Microsoft and Google app registrations, token certificates, Kubernetes settings |
docs/deployment.md |
Operations: deploying to lhotkalake (database, SMTP relay and DNS, secrets, build and deploy, checks) |
k8s/ |
Kubernetes manifests and the build and deploy scripts |
Requires the .NET 10 SDK (pinned by global.json).
dotnet build Scheduler.slnx
dotnet test Scheduler.slnxScheduler.Data.Tests and Scheduler.Web.Tests need Docker: each starts a PostgreSQL container and gives
each test its own freshly migrated database.
dotnet-ef is pinned in the repo's tool manifest.
dotnet tool restore
dotnet ef migrations add <Name> --project src/Scheduler.Data --startup-project src/Scheduler.Data --output-dir MigrationsIn Development the web app applies migrations at startup.
Requires Docker (Aspire starts PostgreSQL and Mailpit containers).
dotnet run --project src/Scheduler.AppHostThe Aspire dashboard links to the web app, pgAdmin and the Mailpit inbox. In Development:
- an MCP client can act as any address without signing in by sending an
X-Dev-Emailheader, and the browser can sign in as any address at/dev/signin(both ignored in every other environment). An MCP client without the header goes through the real OAuth flow, with the development sign-in offered on/signin, so connecting Claude Code or VS Code tohttp://localhost:<port>/mcpworks locally; - Microsoft and Google sign-in appear once
Identity:MicrosoftorIdentity:Googlehas aClientIdandClientSecret(user secrets), with/signin/callback/microsoftor/signin/callback/googleregistered as the redirect URI; - anyone may organize (
Scheduler:Organizersis["*"]inappsettings.Development.json), andadmin@localhost.testis an admin, so/admin/organizersand the admin MCP tools can be tried; - voting links use a random key that changes on every restart unless
Scheduler:MagicLinkKeyis set.
All text files are LF in the repo and in the working tree, on every platform (.gitattributes sets
eol=lf, overriding core.autocrlf; .editorconfig makes editors create LF files). Only .bat and
.cmd files are CRLF.
MIT © Marimer LLC