Inspiration
Every AI agent today faces the same problem: OAuth is hard, and every agent reinvents the wheel. When we connected Claude to Gmail, GitHub, and Google Calendar, we realized the real challenge isn't getting tokens — it's controlling what the AI can do with them. A misconfigured scope means your AI assistant could send emails to your boss, delete repos, or book meetings you never intended. We asked: what if the most secure permission is the one the AI never knows about?
What it does
VaultMCP is a universal MCP server that connects AI agents to external services (Gmail, GitHub, Google Calendar) through Auth0 Token Vault — with a twist. Instead of blocking unauthorized actions with error messages, VaultMCP omits tools entirely from the AI's discovery layer. If you revoke gmail.send, the AI doesn't see a "send_email" tool that's disabled — it doesn't see it at all. We call this authorization by omission.
The dashboard gives users real-time control:
- Toggle scopes on/off and the MCP server updates mid-conversation — no restart needed
- Set time-based access (30m, 1h, 4h) with live countdown badges — scopes auto-revoke when the timer expires
- See the AI's perspective — a split panel showing exactly what tools are visible vs hidden
- Audit every action — full trail of tool invocations with timing and status
How we built it
- MCP Server: TypeScript with the @modelcontextprotocol/sdk, using the low-level Server class for dynamic tools/list responses. The server reads a shared scopes.json file, watches it with fs.watchFile, and sends tools/list_changed notifications when scopes change. A 30-second expiry checker auto-removes timed scopes.
- Token Flow: Auth0 Management API fetches user identities with stored OAuth tokens. For Google services, we use the refresh token to get fresh access tokens. For GitHub, the stored access token works directly.
- Dashboard: Next.js 15 + React 19 + Tailwind CSS. Scope toggles POST to an API route that writes scopes.json — the MCP server detects the change within 500ms and notifies the AI client.
- Architecture: The entire real-time sync runs through a single shared JSON file — no WebSocket server, no database, no message queue. Simple, fast, reliable.
Challenges we ran into
- Token Vault federated exchange didn't work on our tenant — we pivoted to the Management API approach (read user identities → extract tokens → call provider APIs directly). This actually gave us more control.
- Making scope changes truly real-time was the biggest challenge. The standard McpServer class registers tools statically at startup. We had to drop down to the raw Server class and implement dynamic tools/list and tools/call handlers that re-read scopes on every request.
- Time-based auto-revocation required careful coordination: the dashboard writes expiresAt timestamps, the MCP server's expiry checker cleans them up, and the file watcher triggers tools/list_changed — all without race conditions.
Accomplishments we're proud of
- Authorization by omission — a genuinely different approach to AI safety. The AI can't even attempt unauthorized actions because unauthorized tools don't exist in its world.
- Zero-restart real-time control — toggle a scope on the dashboard and the AI loses (or gains) a tool mid-conversation, within seconds.
- Time-based access — grant gmail.send for 30 minutes and forget about it. The scope auto-revokes, no human in the loop. Forgotten permissions can't haunt you.
- 13 tools across 3 services working end-to-end through a single unified MCP server.
What we learned
- Omission is stronger than denial — telling an AI "you can't use this tool" still gives it information. Not showing the tool at all is a fundamentally more secure pattern.
- Scopes can be time-limited — this solves the "I granted it once and forgot" problem that plagues every OAuth integration.
- Simple architectures win — a shared JSON file + file watcher replaced what could have been a complex pub/sub system.
- Auth0 Token Vault + Account Linking is powerful — two OAuth connections (Google + GitHub) covering three services (Gmail, Calendar, GitHub), all within the free tier.
What's next for VaultMCP
- More services — Slack, Notion, Linear, Jira — any OAuth provider that Auth0 supports
- Approval workflows — require human confirmation for write operations before execution
- Per-recipient restrictions — allow gmail.send but only to specific email addresses
- Multi-user support — each user gets their own scope configuration and audit trail
- Publish as an npm package — npx vaultmcp to add authorized OAuth tools to any MCP client
Blog post
The Problem Nobody Talks About
Every tutorial on connecting AI agents to external APIs follows the same pattern: get an OAuth token, call the API, return the result. Simple enough — until you
realize your AI assistant now has the keys to your entire digital life.
I connected Claude to my Gmail, GitHub, and Google Calendar. It worked beautifully. Then I asked it to "help me clean up my inbox" and watched it draft a reply to my manager. I never asked it to write emails. It just... could.
That's the problem. Access isn't the hard part. Control is.
The Insight: What If the AI Simply Didn't Know?
Traditional authorization works by blocking: the AI tries to call send_email, gets a 403 Forbidden, and tells you "I don't have permission to do that." But here's the thing — the AI still knows the tool exists. It can still suggest using it. It can still try.
What if instead of telling the AI "you can't use this," we just... didn't show it the tool at all?
This is authorization by omission. If you revoke the gmail.send scope, the send_email tool doesn't appear as "disabled" — it vanishes completely from the AI's world. The AI can't attempt, suggest, or even hallucinate about a tool it doesn't know exists.
I built VaultMCP to prove this concept works.
What VaultMCP Does
VaultMCP is a universal MCP (Model Context Protocol) server that sits between AI clients like Claude and external services. It uses Auth0 Token Vault to manage OAuth tokens for Gmail, GitHub, and Google Calendar — 13 tools across 3 services, all controlled from a single dashboard.
But the magic isn't in the token management. It's in the tool discovery layer.
When Claude connects to VaultMCP, it calls tools/list to see what's available. VaultMCP reads the user's current scopes and only returns tools the user has explicitly authorized. No scope for gmail.send? The send_email tool doesn't exist in the response. Claude never sees it. Claude never tries it.
User grants: gmail.readonly, repo, calendar.readonly Claude sees: search_emails, read_email, list_labels, list_repos, list_issues, read_issue, create_comment, create_issue, list_events, get_event Claude doesn't see: send_email, create_draft, create_event
10 tools visible. 3 tools hidden. The AI operates in a world where unauthorized actions are literally impossible — not because they're blocked, but because they don't exist.
Real-Time Control, Not Restart-and-Pray
Static configuration isn't enough. If I grant gmail.send for one task and forget to revoke it, I've created a permanent security hole. VaultMCP solves this with real-time scope management.
The architecture is deliberately simple:
- A shared scopes.json file sits at the monorepo root
- The dashboard writes to it when you toggle a scope
- The MCP server watches it with fs.watchFile (500ms polling)
- On change, the server sends a tools/list_changed MCP notification
- Claude re-fetches the tool list — mid-conversation, no restart
Toggle off gmail.send on the dashboard. Switch to Claude. Ask it to send an email. It doesn't say "permission denied" — it says "I don't have an email sending tool." Because it doesn't.
The entire real-time sync runs through a single JSON file. No WebSocket server. No database. No message queue. Simple, fast, and reliable.
Time-Based Access: Set It and Forget It (Safely)
The most dangerous permission is the one you granted three months ago and forgot about. VaultMCP introduces time-based scope access to solve this.
Each scope can carry an optional expiresAt timestamp. On the dashboard, every active scope shows timer preset buttons — 30m, 1h, 4h. Click one and a live countdown badge appears. When the timer hits zero, the MCP server's expiry checker (running every 30 seconds) automatically removes the scope from scopes.json, triggering tools/list_changed.
Grant gmail.send for 30 minutes to handle a specific task. Walk away. The scope auto-revokes — no human in the loop. Forgotten permissions can't haunt you.
{ "gmail": [ { "scope": "gmail.readonly" }, { "scope": "gmail.send", "expiresAt": 1775494411520 } ] }
The Architecture
┌─────────────────────────────────────────────────────┐ │ Claude Desktop / Claude Code (MCP Client) │ │ ← only sees tools matching current scopes │ └──────────────────────┬──────────────────────────────┘ │ tools/list, tools/call │ tools/list_changed ↑ ┌──────────────────────▼──────────────────────────────┐ │ VaultMCP (MCP Server) │ │ ┌─────────────────────────────────────────────┐ │ │ │ Dynamic tools/list — re-reads scopes.json │ │ │ │ on every request │ │ │ ├─────────────────────────────────────────────┤ │ │ │ Expiry Checker (30s interval) │ │ │ │ Auto-removes expired scopes │ │ │ ├─────────────────────────────────────────────┤ │ │ │ Auth0 Management API → Token Exchange │ │ │ └─────────────────────────────────────────────┘ │ └──────────────────────┬──────────────────────────────┘ ┌────────────┼────────────┐ ▼ ▼ ▼ Gmail API GitHub API Calendar API
scopes.json ← Dashboard (Next.js on localhost:3000)
The MCP server uses the low-level Server class from @modelcontextprotocol/sdk — not the higher-level McpServer — because the standard class registers tools statically at startup. We needed dynamic tools/list handlers that re-read scopes on every request.
Token management flows through Auth0's Management API. The server fetches the user's linked identities (which contain stored OAuth tokens from Token Vault), then calls provider APIs directly — Google's token endpoint for fresh access tokens, GitHub's API with the stored token.
The Security Model: Defense in Depth
Authorization by omission is the headline, but VaultMCP layers seven security mechanisms: ┌──────────────────────┬────────────────────────────────────────────────────────────────────────────────┐ │ Layer │ What it does │ ├──────────────────────┼────────────────────────────────────────────────────────────────────────────────┤ │ Token Vault │ Auth0 stores and manages OAuth tokens — the MCP server never persists them │ ├──────────────────────┼────────────────────────────────────────────────────────────────────────────────┤ │ Scope filtering │ Tools only appear in tools/list if the user has matching scopes │ ├──────────────────────┼────────────────────────────────────────────────────────────────────────────────┤ │ Call-time recheck │ Even if a tool appeared in tools/list, scopes are verified again at tools/call │ ├──────────────────────┼────────────────────────────────────────────────────────────────────────────────┤ │ Real-time revocation │ Dashboard toggles take effect within seconds via file watch + notification │ ├──────────────────────┼────────────────────────────────────────────────────────────────────────────────┤ │ Time-based access │ Scopes auto-revoke after a user-defined duration │ ├──────────────────────┼────────────────────────────────────────────────────────────────────────────────┤ │ Audit trail │ Every tool invocation logged with timestamp, duration, and status │ ├──────────────────────┼────────────────────────────────────────────────────────────────────────────────┤ │ Omission proof │ The AI's discovery response itself proves unauthorized tools are invisible │ └──────────────────────┴────────────────────────────────────────────────────────────────────────────────┘ What I Learned
Omission is stronger than denial. Telling an AI "you can't do this" still leaks information. The AI knows the capability exists and may try to work around the restriction. Not showing the tool at all is a fundamentally different — and stronger — security posture.
Simple architectures win. A shared JSON file replaced what could have been a WebSocket pub/sub system. fs.watchFile replaced a change notification service. The entire real-time sync is about 50 lines of code.
Time-limited access changes the game. The ability to say "grant this permission for 30 minutes" is something every OAuth system should have but almost none do. It turns "I'll revoke it later" (which never happens) into "it'll revoke itself."
Auth0 Token Vault is underrated. Two OAuth connections (Google + GitHub) covering three services (Gmail, Calendar, GitHub), account linking, stored refresh tokens — all on the free tier.
Try It Yourself
VaultMCP is open source. It works with any MCP client — Claude Desktop, Claude Code, or anything that speaks the Model Context Protocol.
The core idea is portable: don't block unauthorized AI actions — make them impossible by removing them from the AI's world. Whether you use Auth0 or another identity provider, the authorization-by-omission pattern works anywhere you control the tool discovery layer.
VaultMCP was built for the "Authorized to Act: Auth0 for AI Agents" hackathon. The source code, setup instructions, and architecture documentation are available on GitHub.
Built With
- auth0-management-api
- auth0-token-vault
- github
- google-calendar-api
- google-gmail-api
- model-context-protocol-(mcp)
- next.js-15
- node.js
- npm-workspaces
- react-19
- tailwind-css
- typescript
- zod
Log in or sign up for Devpost to join the conversation.