Your app sends a token
Every request carries Authorization: Bearer mm_live_… plus a platform and data. The token is the identity — your app never sends an email or user id.
MetaMCPis a Model-Context-Protocol server. Your chat app saves conversations to Google Docs & Notion, reads them back, and scrapes the web with Apify — through one HTTP API and a single Bearer token. It handles every OAuth flow and API key for you.
request flow
Resolve the token → check the connection → do the work → return one envelope.
curl -X POST https://mcp.masterymade.com/mcp/tools/save_document \ -H "Authorization: Bearer mm_live_…" \ -H "Content-Type: application/json" \ -d '{ "platform": "google", "title": "My chat", "content": "User: what is python?\nAssistant: …" }'Normal questions never touch MetaMCP — the model just answers. The server is called only when the user asks to save, read, or scrape.
Every request carries Authorization: Bearer mm_live_… plus a platform and data. The token is the identity — your app never sends an email or user id.
The token is exchanged with kv_back for { user_id, email }. Nothing downstream ever accepts a user identifier from the request.
kv_back is asked whether that user has that platform connected. Not connected? MetaMCP returns a connect_url — a normal outcome, not an error.
Connected? MetaMCP uses the stored OAuth token or API key to save / read / scrape, then returns one consistent envelope your app can relay.
/mcp/tools/mcp/tools/manifestMachine-readable description of every tool, so any LLM client can learn what platforms and actions are available. Public — no token needed.
public · no token/mcp/tools/connectionsReport which platforms this user has connected — Google, Notion, Apify — each with a connected flag. Never errors; a not-connected user just gets a connect link.
/mcp/tools/save_documentCreate a document/page and write the chat text into it — a Google Doc or a Notion page. Returns the document id and a shareable url.
/mcp/tools/list_documentsList the chats this app has saved for the user, newest first, per platform.
/mcp/tools/read_documentRead back the plain-text content of one saved document or page by its id.
/mcp/tools/scrapeRun an Apify actor with the user's key to extract clean page text. Pass a URL, or any Apify actor id with custom input for advanced crawls.
Add another by dropping in a service module — the routes, envelope, connection check, and manifest all work automatically.
OAuth · auto-refreshed
Connected by OAuth; the access token is auto-refreshed from the refresh token on every expiry, per request.
Manual key · ntn_…
Stored as a manual integration token, normalized automatically. Needs a parent page — pass parent_page_id or the first accessible page is used.
Manual key
Runs website-content-crawler by default, or any actor you name. Results are capped and cleaned for LLM use.
| Action | Google Docs | Notion | Apify |
|---|---|---|---|
| save_document | |||
| list_documents | |||
| read_document | |||
| scrape |
Each tool returns HTTP 200 with a consistent JSON body. Branch on the fields — never on the status code. “Not connected” is a normal outcome carrying a connect_url, not an error.
if body.success: # use body.data render(body.data)elif body.connected == False: # ask the user to connect show(body.message, body.connect_url)else: # a real provider / input error show(body.message){ "success": true, "connected": true, "action": "save_document", "platform": "google", "data": { "document_id": "1bzCTzH194-MXn7s5uRgf…", "title": "My chat", "url": "https://docs.google.com/document/d/1bzCTzH…/edit" }, "message": "Saved to Google Docs: https://docs.google.com/…"}Authorization: Bearer mm_live_… resolves to a user via kv_back. Your app proves who it is — it can never act as a user it doesn't own.
No OAuth, no API keys, no email. MetaMCP owns every provider credential; your app only ever holds the one bearer token.
Resolved tokens are cached for just 60 seconds, so revoking a token in KeyVault takes effect almost immediately.
401 means the token is missing or revoked. 503 means kv_back is unreachable — retry, it isn't your fault. Everything else is HTTP 200.
Give the model tool definitions and let it decide when to call them. Map each tool call to a MetaMCPendpoint — and inject the user's token server-side. The model never sees it.
| LLM tool | MetaMCP call |
|---|---|
| save_chat | POST /mcp/tools/save_document |
| list_saved_chats | GET /mcp/tools/list_documents?platform= |
| read_saved_chat | GET /mcp/tools/read_document?platform=&document_id= |
| scrape_website | POST /mcp/tools/scrape |
Scrapes can take 30–120s — use a generous ≈200s timeout for /scrape.
[ { "type": "function", "function": { "name": "save_chat", "description": "Save the current conversation to Google Docs or Notion.", "parameters": { "type": "object", "properties": { "platform": { "type": "string", "enum": ["google", "notion"] }, "title": { "type": "string" }, "content": { "type": "string" } }, "required": ["platform", "title", "content"] } } }, { "type": "function", "function": { "name": "scrape_website", "description": "Extract the content of a web page using Apify.", "parameters": { "type": "object", "properties": { "url": { "type": "string" }, "max_pages": { "type": "integer" } }, "required": ["url"] } } }]The provider comes from the URL path; its config from core/providers.py. A JWT state token guards the callback, the code is exchanged for a token, and the result is stored in KeyVault.
/mcp_routes/auth/providersconfigured + catalog/mcp_routes/auth/{provider}-loginbuild authorize url/mcp_routes/auth/{provider}-callbackcode → token → store/mcp_routes/auth/{provider}-disconnectrevoke + removeprovider catalog
Google is live today. The rest are known OAuth targets — add one template plus credentials to enable it.
Green = configured & ready. Grey = template pending credentials.
:8000 in three commands.python -m venv venvvenv/Scripts/pip install -r requirements.txtuvicorn main:app --port 8000KEYVAULT_URL="https://kvbackend.masterymade.com"SERVICE_API_KEY="<x-service-key for kv_back /internal/keys>"JWT_SECRET_KEY="<signs the OAuth state token>"FRONTEND_URL="https://kv.masterymade.com"OAUTH_REDIRECT_BASE_URL="https://mcp.masterymade.com"GOOGLE_CLIENT_ID="…"GOOGLE_CLIENT_SECRET="…"quick reference
GET /mcp/tools/manifest (public)GET /mcp/tools/connectionsPOST /mcp/tools/save_document {platform, title, content, parent_page_id?}GET /mcp/tools/list_documents ?platform=GET /mcp/tools/read_document ?platform=&document_id=POST /mcp/tools/scrape {url, max_pages?, actor?, input?}stack
server mcp.masterymade.com
connect kv.masterymade.com/
kv_back kvbackend.masterymade.com
One bearer token, one HTTP API. MetaMCP does the OAuth, the keys, and the plumbing.
/mcp/tools/manifest