aos
Connects a user's Telegram account so approval requests and drafts reach their phone with Approve/Reject buttons, and handles their responses through the Telegram bot.
This server does not currently have a deployable release on Glama. Follow the repository's setup instructions to install and configure it for your MCP client, including any required credentials and local dependencies.
aos
A multi-user version of the aos core that anyone adds to the AI assistant they already use (Claude, ChatGPT, any MCP client) by pasting one link. Nothing to install, works on Mac, Windows, Linux and phones.
It grew out of aos control, Ayush's personal setup on his own Linux PC (private, not in this repo). This is the version for everyone, with its own database; the two share no data and no code at runtime.
Start using it
Why it works on Mac, Windows, Linux and phones: nothing runs on your computer. aos is a website plus a connector link. Your AI assistant (the Claude or ChatGPT app you already have, on any system) calls aos over the internet, so whatever runs your assistant runs aos.
Open https://aos-connect.vercel.app and sign in with Google.
On your page, copy the connector link:
https://aos-connect.vercel.app/api/mcpAdd it to your assistant:
Claude (claude.ai, the desktop app on Mac or Windows, or Claude Code): Settings → Connectors → Add custom connector → paste the link → Connect, then press Allow on the aos page that opens. Connectors added on claude.ai also show up in the Claude phone app.
ChatGPT: Settings → Connectors (or Apps) → add a custom connector with the same link. Custom connectors need a plan and settings that allow them.
Any other MCP client that supports remote servers with OAuth: give it the same link.
Ask your assistant: "what's on my aos agenda today?"
Optional: on your aos page, connect Telegram so drafts reach your phone with Approve / Reject buttons, and connect your mailbox so approved emails send by themselves.
Mac | Windows | Linux | Phone | |
aos connect (this folder): tasks, drafts you approve, goals, sealed email send, Telegram | ✅ | ✅ | ✅ | ✅ |
aos control (private, not in this repo): the agent that works on its own on a PC, desk control, power-on, Discord | ❌ | ❌ | ✅ (Ayush's setup: systemd, tmux, Hyprland) | via Discord |
Status (11 Nov 2026): the app, the connector link and the OAuth flow up to the sign-in page are live and checked. A full run by a person (Google sign-in → add the connector in Claude → first tool call) has not been done yet; until it has, treat this as an early preview.
person's assistant (Claude / ChatGPT)
│ 1. add connector: https://<app>/api/mcp
│ 2. 401 + link to /.well-known/oauth-protected-resource
▼
Supabase Auth (OAuth 2.1 server, sign-in with Google)
│ 3. person signs in, sees /oauth/consent, presses Allow
▼
/api/mcp ── every tool call runs with that person's own token ──► Postgres (row level security)
/app ── the person's page: approve or reject drafts, goals, today, connected appsRelated MCP server: M365 MCP Server
What a connected assistant can do
18 tools: agenda, task_list, task_add, task_update, task_done, task_delete, approval_request, approval_list, approval_send, approval_mark_sent, api_request, activity_log, activity_recent, goal_set, goal_list, goal_progress, event_add, settings.
There is no tool to approve a draft. The assistant writes the text with approval_request; the person presses yes or no on /app; the assistant then sends the approved text and calls approval_mark_sent.
Where the rules live
Every user, and every app they connect, holds a real Supabase token and could call the database API directly. So the rules are in the database (supabase/migrations/), not only in this app:
Rule | How |
A user reads and writes only their own rows | Row level security on every table, |
A row cannot point at another user's task or goal | Composite foreign keys |
An assistant can never approve its own draft | Trigger |
An approved text cannot be changed, and only an approved draft can be marked sent | Same trigger |
Nothing is hard-deleted | No delete policy, delete privilege revoked; |
One user cannot fill the database | Row quotas per table, size checks on every text column |
Rate limit (120 tool calls a minute per user) |
|
An assistant cannot swap a draft's text after asking | Trigger |
A saved login cannot be read back by anyone |
|
Only the person can save or remove a login |
|
An email is sent at most once, and at most 50 a day |
|
A saved API key can only go to the host the person entered | The host is stored with the key by |
A request that changes something runs only after the person's yes, exactly as shown |
|
An assistant cannot turn a stand-in back into a value, or switch masking off |
|
Only the person can link a phone |
|
A phone decides only its own person's drafts |
|
Reads and writes of a person's data always run with that person's own token. The Supabase secret key is used through three small files, each a short list of database functions: the Telegram webhook (lib/channel-db.ts), sending an approved email or request (lib/send-db.ts), and masking plus reading a saved key (lib/mask-db.ts: handles_mask, handles_resolve, api_key_read). The MCP endpoint accepts only tokens issued to a connected app; the person's own website session never passes through an assistant.
Tests
bun test (134 tests):
tests/db.test.tsruns the real migration on an in-process Postgres (PGlite) and attacks it as a second user and as a connected app. It also checks that every column and database function the code names exists in the schema.tests/vault.test.tscovers sealing (wrong key, wrong owner and changed values do not open), the mail rules, a real SMTP exchange against a local server (login check, the approved text arriving word for word, header injection, a refused recipient), and the whole sealed send with its failure paths.tests/privacy.test.tscovers finding contact details (and not mistaking ids, amounts or dates for phone numbers), masking and putting values back, the privacy levels, which addresses a key may be sent to, real HTTP exchanges against a local server (key header, body word for word, no redirects followed, size cap), and a read for an assistant end to end.tests/bot.test.tscovers the Telegram bot: commands, yes/no, buttons, the webhook secret check, unlinked chats, group chats ignored, and that only our own error messages reach the chat.tests/lib.test.tscovers time zones (including daylight-saving days), repeating tasks, the agenda, goal pace, the redirect guard and the tool wrapper.
Checked by hand on a local build with placeholder settings: the landing page, the OAuth metadata document, 401 with the discovery header for no token, a garbage token and a forged unsigned token, and the redirects to sign-in.
Not tested yet: a real Google sign-in, tool calls through Supabase's REST API, and connecting from Claude and ChatGPT (see "Live" below).
Live
App: https://aos-connect.vercel.app (Vercel project
aos-connect, deployed from this folder withvercel deploy --prod).Connector link:
https://aos-connect.vercel.app/api/mcpDatabase and sign-in: Supabase project
aos-connect(refperuwdunukezbyojebvb, Mumbai). Separate fromaos-control.
How it was set up (10 Nov 2026), so it can be repeated:
supabase projects create aos-connect --region ap-south-1, thensupabase linkandsupabase db push. The database password is in.env.supabase.local(git-ignored, never uploaded: see.vercelignore).Sign-in settings live in
supabase/config.tomland are applied withnpx supabase@3.1.0 config push(the 2.x CLI cannot push the OAuth server settings). They turn on the OAuth server (consent page/oauth/consent, dynamic client registration), turn off email sign-up, and allow the redirectshttps://aos-connect.vercel.app/auth/callbackandhttp://localhost:3000/auth/callback.Google sign-in: a Google Cloud OAuth client (type Web) with the redirect
https://peruwdunukezbyojebvb.supabase.co/auth/v1/callback, entered in Supabase → Authentication → Sign In / Providers → Google. Basic scopes only, so no Google review.Token signing uses the project's default asymmetric key (ES256), so tokens are checked without a network call.
Vercel variables (production):
NEXT_PUBLIC_SUPABASE_URL,NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY,NEXT_PUBLIC_SITE_URL=https://aos-connect.vercel.app, plus the server-only ones for the phone and the vault:SUPABASE_SECRET_KEY,TELEGRAM_BOT_TOKEN,TELEGRAM_WEBHOOK_SECRET,NEXT_PUBLIC_TELEGRAM_BOT,VAULT_KEY.VAULT_KEYis also in.env.local; if it is lost, saved logins cannot be opened and people reconnect their email.
Checked live: the landing page, 401 with the discovery header, the protected-resource document, a fake token refused, /app redirecting to sign-in, and a full app registration → authorize → consent page → sign-in redirect. Row level security, the no-delete rule and the approval guard were re-checked on the live database inside a rolled-back transaction.
Still to try by a person: sign in with Google, add the connector link in Claude (Settings → Connectors → Add custom connector), press Allow, and ask "what is on my aos agenda today?".
The vault and sealed sending
The rule: the model works with names and drafts; the server holds the secrets and does the sending.
On
/app, under Email sending, the person picks their mail provider and enters their address and an app password. The server checks the login with the provider, seals it (AES-256-GCM, keyVAULT_KEYin the server environment, bound to that user) and stores only the sealed value. It is never shown again, to anyone.An assistant can learn one thing: that email is connected, with a hint like
a***@example.com(settingstool). Its instructions tell it never to ask for a password or key in chat.When the person approves an email draft (on
/appor with the Telegram button), aos sends it right away from their own mailbox, word for word, and logs it. The assistant takes no part.approval_sendlets an assistant ask aos to send an email that is already approved (for example a scheduled one whose time has come). It returns one line; no login, no mail server reply.A draft is sent by aos only if its channel is
email, its recipient holds exactly one address and it has a subject. Anything else stays approved for the assistant to send as before.If the provider refuses the login or the recipient, nothing was sent: the draft stays approved and can be sent again. If the connection breaks mid-send, the draft is marked failed and is never retried by itself.
The mail server's address always comes from the fixed provider list in
lib/mail.ts, never from input.
Connected services (API keys)
On
/app, under Connected services, the person saves a key with a short name, the service address (for exampleapi.razorpay.com) and how the service wants the key (Bearer, Basic, or a header of its own). It is sealed like the mail login.An assistant sees the names and hosts (
settingstool) and callsapi_requestwith a name and a path. It never sees the key and cannot choose the host.GET runs at once. The reply comes back with the key scrubbed out (also in encoded forms), contact details masked, compact, cut at 12,000 characters, and reduced to the
fieldsasked for.Anything else waits for a yes. POST, PUT, PATCH and DELETE become an approval that shows the exact request. It runs when the person approves, on
/appor from Telegram, and only if they ticked "allow requests that change something" for that key. The first part of the answer is stored with the approval.Guards: public hosts only (checked when saved, and again on the socket's own DNS lookup, so a name cannot be re-pointed at a private address), HTTPS only, redirects not followed, replies read up to 256 KB, 30 reads a minute, 200 approved requests a day, 20 keys per person.
Masking
An email address or phone number in a reply from a connected service reaches the assistant as a stand-in:
[EMAIL_12],[PHONE_3]. The same value always gets the same stand-in for that person, so the assistant can still follow who is who across conversations.When the assistant uses a stand-in (a draft's recipient, a request path or body), the server puts the real value back before it stores or sends. The person sees real values on
/appand on their phone.Once a value has a stand-in, it is masked in every tool result, so it cannot be read back by saving it in a task and listing the task.
The person chooses the level on
/appunder Privacy: services only (default), also their own tasks, drafts and log, or off. The same section has a "Who is this?" box that turns a stand-in back into the value, for the person only.If masking a service reply fails, the reply is not returned.
Limits, plainly: text the person types into their assistant's chat reaches the model before aos sees it; names in free text are not masked; a service that returns a value in a changed form (encoded, split) is not caught; phone numbers are found by pattern, so an unusual format can slip through and a ten-digit number in free text can be masked by mistake.
Not built yet: a timer that sends scheduled emails by itself, a per-recipient cap, a look-up of stand-ins from Telegram.
Phone (Telegram)
The person presses Connect Telegram on /app, Telegram opens, they press Start, and the chat is linked. From then on:
every draft their assistant queues arrives in that chat with Approve and Reject buttons;
/todayshows what is due, drafts waiting and goals;/draftslists drafts with buttons;yes 12/no 12also work.
Approving on the phone only marks the draft approved. The assistant still sends it, the next time it checks its drafts.
Telegram rather than WhatsApp or Discord for the first channel: free, no business verification, works entirely by webhook on Vercel, and the bot can message anyone who pressed Start. WhatsApp (Meta verification, paid conversations) and Discord (a bot can only DM people it shares a server with) can be added behind the same channel_* functions.
Setup:
In Telegram, message @BotFather, send
/newbot, pick a name and a username ending inbot.Paste the token after
TELEGRAM_BOT_TOKEN=in.env.local(never in a chat).TELEGRAM_WEBHOOK_SECRETis already there and matches Vercel.bun scripts/telegram-setup.ts https://aos-connect.vercel.appsets the webhook and command menu and prints the bot username.Add
TELEGRAM_BOT_TOKENandNEXT_PUBLIC_TELEGRAM_BOT(the username) to Vercel and redeploy.SUPABASE_SECRET_KEYandTELEGRAM_WEBHOOK_SECRETare already set.
Until the token is set, /api/telegram answers 503 and /app hides the Connect Telegram button.
Live since 10 Nov 2026: bot @aos_connect_bot (name "aos"), webhook https://aos-connect.vercel.app/api/telegram with only message and callback_query updates. Checked from Telegram: /start and /today from an unlinked chat answer with how to link (the second goes through the database with the secret key).
Run locally
cp .env.example .env.local, fill it in, then npm run dev. npm run build works without the variables; requests then answer that the server is not configured.
Not in this first cut
Email sign-in (Google only for now).
Any always-on runner. Approved emails go out by themselves when a mailbox is connected; everything else (other channels, scheduled emails) still waits until the person talks to their assistant.
A limit on how many messages one recipient gets (the personal aos has this). There is a limit of 50 emails a day per person.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
This server cannot be deployed
Maintenance
Related MCP Connectors
Give AI agents a real email address, vault, calendar, and task delegation via MCP.
Email for AI agents: send, receive with a safety verdict, reply and approve, as MCP tools.
An MCP connector that lets your AI agents ask you questions, request permission approvals, and send alerts as push notifications to your phone and mac notch, so you can unblock and approve your agents from anywhere.
Connect any AI agent to 1,000+ apps and 27,000+ actions through one remote MCP server (OAuth).
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceAn MCP server that exposes IMAP operations as tools for AI assistants, enabling them to connect to IMAP-compatible email servers. It currently allows users to create email drafts directly in their mail server's drafts folder using natural language.27 npm15MIT
- AlicenseNot gradedqualityDmaintenanceA secure remote MCP server that integrates Microsoft 365 services with AI assistants, enabling email, calendar, Teams, and contact operations via the Microsoft Graph API.2MIT
- AlicenseAqualityDmaintenanceMCP server that gives an AI assistant simultaneous access to multiple Gmail accounts with per-account read-only, draft, or send tiers, two-phase write confirmation, and safeguards for handling untrusted email content.1013 npm1MIT
- FlicenseBqualityBmaintenanceEnables external AI agents to read, send, and manage email over IMAP/SMTP via MCP, including inbox listing, search, drafts, scheduled/batch sending, and operations like reply, archive, and labels.303-