MCP server·FastAPI · Python

One API to give your AI real actions.

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.

one bearer token 3 platforms 6 tools

request flow

Bearer mm_live_Your appchat · agentMetaMCPresolve · check · runGoogle DocsNotionApify

Resolve the token → check the connection → do the work → return one envelope.

save a chat to Google Docs
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: …"  }'
01How it works

Every request follows the same four steps.

Normal questions never touch MetaMCP — the model just answers. The server is called only when the user asks to save, read, or scrape.

01

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.

02

MetaMCP resolves the user

The token is exchanged with kv_back for { user_id, email }. Nothing downstream ever accepts a user identifier from the request.

03

It checks the connection

kv_back is asked whether that user has that platform connected. Not connected? MetaMCP returns a connect_url — a normal outcome, not an error.

04

It does the real work

Connected? MetaMCP uses the stored OAuth token or API key to save / read / scrape, then returns one consistent envelope your app can relay.

02The MCP tools

Six endpoints under /mcp/tools

GET

Discover the tools

/mcp/tools/manifest

Machine-readable description of every tool, so any LLM client can learn what platforms and actions are available. Public — no token needed.

public · no token
GET

List connections

/mcp/tools/connections

Report 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.

POST

Save a document

/mcp/tools/save_document

Create 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.

GET

List documents

/mcp/tools/list_documents

List the chats this app has saved for the user, newest first, per platform.

GET

Read a document

/mcp/tools/read_document

Read back the plain-text content of one saved document or page by its id.

POST

Scrape the web

/mcp/tools/scrape

Run 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.

03Platforms

Three integrations, one generic contract.

Add another by dropping in a service module — the routes, envelope, connection check, and manifest all work automatically.

Google Docs

OAuth · auto-refreshed

savelistread

Connected by OAuth; the access token is auto-refreshed from the refresh token on every expiry, per request.

Notion

Manual key · ntn_…

savelistread

Stored as a manual integration token, normalized automatically. Needs a parent page — pass parent_page_id or the first accessible page is used.

Apify

Manual key

scrape

Runs website-content-crawler by default, or any actor you name. Results are capped and cleaned for LLM use.

ActionGoogle DocsNotionApify
save_document
list_documents
read_document
scrape
04One envelope

Every response, the same shape.

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.

how to branch
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)
200 OK · application/json
{  "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/…"}
05The token model

Your app holds exactly one credential.

Authorization:Bearermm_live_…

One token is the identity

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.

You hold nothing else

No OAuth, no API keys, no email. MetaMCP owns every provider credential; your app only ever holds the one bearer token.

Revocation is near-instant

Resolved tokens are cached for just 60 seconds, so revoking a token in KeyVault takes effect almost immediately.

Honest status codes

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.

06Integrate a chat app

Wire it up with LLM function-calling.

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 toolMetaMCP call
save_chatPOST /mcp/tools/save_document
list_saved_chatsGET /mcp/tools/list_documents?platform=
read_saved_chatGET /mcp/tools/read_document?platform=&document_id=
scrape_websitePOST /mcp/tools/scrape

Scrapes can take 30–120s — use a generous ≈200s timeout for /scrape.

tools for your LLM (OpenAI)
[  { "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"] } } }]
07OAuth, generically

One set of routes for every provider.

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.

  • GET/mcp_routes/auth/providers
  • GET/mcp_routes/auth/{provider}-login
  • GET/mcp_routes/auth/{provider}-callback
  • DELETE/mcp_routes/auth/{provider}-disconnect

provider catalog

Google is live today. The rest are known OAuth targets — add one template plus credentials to enable it.

googlegithubslacknotionmicrosoftdiscordfacebooktwitterlinkedininstagram

Green = configured & ready. Grey = template pending credentials.

08Run it

Up on :8000 in three commands.

start the server
python -m venv venvvenv/Scripts/pip install -r requirements.txtuvicorn main:app --port 8000
.env
KEYVAULT_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

/mcp/tools
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

FastAPIuvicornhttpxgoogle-auth-oauthlibpydantic-settingsPyJWT

server  mcp.masterymade.com

connect kv.masterymade.com/

kv_back kvbackend.masterymade.com

Give your chat app real actions.

One bearer token, one HTTP API. MetaMCP does the OAuth, the keys, and the plumbing.

GET/mcp/tools/manifest
Open the repo