Skip to content
allocsysPublic

About

MCP server exposing GitHub, Cloudflare, Notion, Mem0, and web-research tools to AI agents — with built-in delegated multi-step reasoning (Gemini/Exa) and hardened auth for the Claude connector.

Resources

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

🔌 madmcp

An MCP server built around Gemini-powered delegation — hand Claude an open-ended, multi-step investigation instead of chaining 5-10 manual tool calls — plus direct tool access to GitHub, Cloudflare, Notion, Mem0, Context7, Jules, and the web. Reflexive by construction: a connected agent has write access to this very repo, so it can read its own source, diagnose a gap, and patch it through the same connection.

CI Protocol Node Connectors License

typing animation of a madmcp tool-call trace

▶ Watch the live protocol trace


What this is

madmcp's core idea is delegation: instead of an agent making 5-10 manual tool calls to run an investigation itself, it can hand an open-ended, read-only investigation (or a single page + question) to Gemini, which runs its own tool-use loop server-side and returns one synthesized answer. This is split across two tools along a security boundary: delegate_agent covers GitHub, Cloudflare, and Notion (no web access), while delegate_research covers the live web (a precision single-page mode, or a single-shot wide research mode via Exa's /answer endpoint) with no access to those internal systems. Both are described first under Connectors & tools below.

On top of that, the server also gives an AI agent direct tool-level access to real infrastructure — GitHub, Cloudflare, Notion, Mem0, Context7, and arbitrary web pages — so agent workflows can read and write directly instead of relying on manual copy/paste between tabs.

Agent-driven self-modification

DEFAULT_OWNER defaults to your forked repo, and the GitHub connector exposes full read/write/PR tooling (read_file, edit_file, pr_write with create / merge, etc.) — not a read-only subset. That combination means an agent connected to this server has direct write access to this very repo: it can read its own source, diagnose a bug or a gap in the docs, and commit the fix — or open a PR against itself — through the same connection it's already using, with no separate deploy step or out-of-band access required. This isn't a hypothetical: this README, and the portfolio page describing this project, have both been edited exactly this way. Worth being precise about what this is not: the server doesn't modify itself unprompted on some schedule — every change still starts with an agent invoked by a person. What's notable is that there's no separate admin path or special-cased self-access; a connected agent uses the exact same edit_file/pr_write tools on this repo as on any other repo it has a token for.

Live demo

An illustrative six-step walkthrough of a mock incident response — finding a slow endpoint, checking latency metrics, shipping and reviewing a fix, and logging it to memory — touching all five connectors in one flow (including a deeper look at Mem0's semantic dedup and relation-graph tools), with a synced status panel and live call/latency stats alongside the trace. Sample data throughout, not a live call. (If GitHub Pages isn't enabled for this repo yet, open demo.html directly.)

Deploy & connect (quickstart)

Deploy to Render Deploy with Vercel

Need connector tokens first? → Get API keys — one-click links to each provider's key-creation page, or use the env bundler below to collect them all and copy out a single .env block:

Env Bundler GitHub Token Cloudflare Token Notion Integration Mem0 Key Gemini Key Jules Key Exa Key OpenRouter Key Groq Key Upstash Redis Generate Secret

Get tokens, deploy, connect to Claude — in that order.

1. Deploy it — pick a host. Both buttons above deploy this repo as-is, no Dockerfile or CLI needed, but they're not identical:

  • Render reads render.yaml and generates MCP_SHARED_KEY for you automatically — one less thing to get wrong. Trade-off: the free plan spins down after 15 minutes idle, so the first request after a quiet period is slow (~30s cold start). Fine for testing; upgrade to a paid plan if you want Claude's calls to stay consistently fast.

  • Vercel deploys with zero config (this repo's server.js already matches what Vercel expects) and its Fluid compute avoids Render's cold-start spin-down. Trade-off: you have to generate MCP_SHARED_KEY yourself — any long random string, e.g. via a password generator or openssl rand -hex 32 — and paste it into the form; Vercel's button can't auto-fill it the way Render's blueprint does.

  • Manufact Cloud is purpose-built for hosting MCP servers (this one included — the default IP allowlist already carries a comment about Manufact's own deploy-time health check). It doesn't have a one-click button with a public URL scheme the way Render/Vercel do; instead sign in, New Server → Import from GitHub → pick this repo, add your env vars on the Configure Deployment screen (or paste a .env), then Deploy. Node.js is auto-detected from package.json, no Dockerfile needed. Free tier scales to zero like Render's free tier (cold starts); paid plans offer "Prevent Scale to Zero." Manufact's gateway URL pattern is https://<slug>.run.mcp-use.com/mcp — the /mcp/<key> path-auth variant this repo uses does survive that gateway routing (confirmed: it's the exact pattern in use), so the same https://<slug>.run.mcp-use.com/mcp/<your MCP_SHARED_KEY> URL from step 5 below works here too.

Prefer another host, or running it yourself? It's a plain Node/Express app — npm install && npm start, listens on $PORT (default 8080) — so any Node-friendly host (Railway, Fly.io, etc.) or your own server works too, just without any platform's auto-fill.

A note on IP allowlisting across hosts: the allowlist trusts one reverse-proxy hop by default (TRUST_PROXY_HOPS, default 1), which matches Render and most single-CDN-hop platforms. If Claude's calls get unexpectedly 403'd on a different platform, that hop count is the first thing to check — adjust TRUST_PROXY_HOPS, or temporarily set IP_ALLOWLIST_ENABLED=false to confirm that's the cause before tightening it back up.

2. Collect tokens for the connectors you want. Each is independent — skip any you don't need, its tools just fail at call time instead of blocking the rest.

Connector Where to get it
GitHub github.com/settings/tokens → fine-grained PAT, scoped to specific repos
Notion notion.so/my-integrations → create an integration, then share the relevant pages/databases with it
Mem0 app.mem0.ai → API keys
Cloudflare dash.cloudflare.com → My Profile → API Tokens, plus your Account ID from the dashboard sidebar
Jules jules.google.com/settings#api → API key (max 3 per account, no unauthenticated tier)

3. Set two security env vars — don't skip these.

  • MCP_SHARED_KEY — any long random string. Unset = /mcp is open to anyone who has your server's URL, tokens and all.
  • IP_ALLOWLIST_ENABLED — on by default, pre-set to Claude's published connector IP range, so leave it alone if you're only ever connecting from Claude.ai. Set it to false temporarily to test with curl/Postman from your own machine first.

4. Deploy, then verify. GET /health returns {"status":"ok"}, no auth needed. GET / (needs your x-manufact-key header — this endpoint doesn't support the path-key variant) reports which connectors are configured, so you can confirm your tokens landed.

5. Add it to Claude. Settings → Connectors → Add custom connector, using:

https://<your-host>/mcp/<your MCP_SHARED_KEY>

(Path-based, since Claude.ai's connector UI doesn't currently support header-based auth for MCP servers.)

See Configuration below for the full variable reference.

Connectors & tools

⭐ Gemini (delegation) — the flagship feature

The two delegation tools split along a security boundary: delegate_agent has no web access, delegate_research has no access to GitHub/Notion/ Cloudflare. This means a malicious page or search result encountered mid-research can influence at most that run's own answer — it has no internal-system data to exfiltrate, because that loop never has access to any in the first place.

delegate_agent — hand an open-ended, multi-step, read-only investigation (e.g. "why is CI failing on PR #42", "summarize what changed in this repo over the last week") to Gemini (default), GLM, or Groq instead of making 5-10 separate manual tool calls. The model runs its own loop server-side across GitHub, Cloudflare, and Notion (bounded by max_steps, default 6, hard cap 20) and returns one synthesized answer. Falls through an ordered model cascade (GEMINI_MODEL → GEMINI_FALLBACK_MODELS, GLM_MODEL → GLM_FALLBACK_MODELS across every key in OPENROUTER_API_KEYS, or GROQ_MODEL → GROQ_FALLBACK_MODELS across every key in GROQ_API_KEYS) on rate limits, with Redis-backed per-model cooldown so already-limited models are skipped rather than retried. An explicit provider: "gemini" | "glm" | "groq" arg picks which one backs a given call (default: DEFAULT_LLM_PROVIDER, itself defaulting to "gemini") — all three are interchangeable in capability, not just cost/speed; try "glm" or "groq" if Gemini's output has been unreliable for a given task. GLM (via OpenRouter) is currently non-functional on a zero-credit account — see docs/API_KEYS.md — so Groq is the practical free-tier alternative to Gemini for now (request/token-rate-limited rather than credit-balance-gated, no card required). Ignored on a resume (the provider that started the run is always reused, so a checkpointed conversation can't be corrupted by resuming it on a different provider's wire format).

delegate_research — web research, in one of two mutually-exclusive modes selected by which args are passed:

  • Precision mode (url + question): fetch a single URL and get back Gemini's answer to a specific question about its content, without returning the raw page. Use this instead of web_fetch when you need a distilled answer rather than exact wording to copy.
  • Wide mode (task): a single-shot call to Exa's /answer endpoint, which does its own web search + synthesis server-side and returns one answer with sources. No multi-step loop, no effect from max_steps, and nothing to resume via resume_run_id (that Gemini-loop architecture was retired 2026-07-27).

Progress on delegate_agent runs is checkpointed to Redis after every completed step. If the underlying Gemini API call fails partway through (429/503/network blip), the response includes a resume_run_id and everything gathered so far instead of losing the run outright — pass that id back on a follow-up call to continue from the last completed step (checkpoint TTL: 1 hour) rather than re-running, and re-paying for, steps already done. Wide-mode delegate_research is a single Exa call with no steps to checkpoint — a failure just returns an error, and resume_run_id is accepted only for parameter compatibility with delegate_agent.

Both tools can optionally log their task/question, step-by-step tool calls, and final answer to a Notion page under a fixed Gemini root page (log_to_notion, default false).

GitHub

File/repo ops: read_file (accepts optional char_offset/char_limit to page through large files), list_directory, get_file_tree, create_repo_file, edit_file, overwrite_files, rename_file, delete_file

Repo inspection (read-only): repo_inspect — one tool, selected by action: at_commit (file contents at a commit SHA), diff, branch_protection, list_branches, list_commits, get_commit. Consolidated from the former get_file_at_commit, diff_files, get_branch_protection, list_branches, list_commits, and get_commit tools.

Code search: search_code — standalone; requires ref and a repo:owner/name qualifier in query. The rest of the query is matched as one literal string (no OR; filename:/extension: qualifiers are stripped).

Branch creation (mutating): create_branch — standalone; requires owner, repo and the new branch name, with optional from_branch.

Issues: issue_manage (action: get | list | create | update | comment | search), replacing get_issue, list_issues, create_issue, update_issue, add_issue_comment, search_issues. search is cross-repo and ignores owner/repo (scope it with qualifiers in query); comment works on PRs too.

Pull request writes (mutating): pr_write (action: create | update | merge | review | request_reviewers | remove_reviewers | inline_comment), replacing create_pull_request, update_pull_request, merge_pull_request, review_pull_request, request_reviewers, remove_requested_reviewers, add_review_comment. merge is irreversible via this tool.

Pull request reads: pr_read (action: list | get | activity | mergeability), read-only, replacing get_pull_requests, get_pr_activity, get_pr_mergeability. mergeability polls server-side (up to 4 tries) since GitHub computes it async.

Releases & tags: release_manage (action: list | create | list_tags), replacing list_releases, create_release, list_tags

Repo metadata (read-only): repo_metadata (action: list | get | contributors | topics), replacing list_repos, get_repo, list_contributors, get_repo_topics

Repo lifecycle (mutating): repo_lifecycle (action: create | fork | sync_fork | delete | set_topics), replacing create_repo, fork_repo, sync_fork, delete_repo and the topics write path. delete is permanent and requires confirm: true.

CI / Actions: ci_manage (action: list | run_logs | job_logs | trigger | rerun | cancel | checks | status), replacing list_workflow_runs, get_workflow_run_logs, get_job_logs, trigger_workflow, rerun_workflow, cancel_workflow_run, get_check_runs, get_combined_status. trigger, rerun and cancel mutate; the rest are read-only. list, checks and status each carry their own sleep-and-recheck guidance while runs/checks are pending.

Notifications: list_notifications

Codespaces: codespace_manage (action: list | get | machines | create | start | stop | delete), replacing list_codespaces, get_codespace, list_codespace_machines, create_codespace, start_codespace, stop_codespace, delete_codespace. create, start, stop and delete mutate; delete is permanent. Requires the codespace PAT scope on GITHUB_TOKEN — see API_KEYS.md. Runtime requirement: exec_in_codespace (separate, gated by CODE_EXEC_ENABLED) requires the GitHub CLI (gh) to be installed and authenticated on the server running this MCP instance.

Cloudflare

D1: cf_d1_read (action: get | list), cf_d1_manage (action: create), cf_d1_query

KV: cf_kv_read (action: get | list), cf_kv_manage (action: create | update)

R2: cf_r2_read (action: get | list), cf_r2_manage (action: create)

Hyperdrive: cf_hyperdrive_read (action: get | list), cf_hyperdrive_manage (action: update)

Workers: cf_workers_read (action: list | get | code)

Observability: cf_workers_observability (action: query | keys | values | compare)

Delete: cf_delete (resource: d1 | kv | r2 | hyperdrive, id, confirm), replacing cf_d1_database_delete, cf_kv_namespace_delete, cf_r2_bucket_delete, cf_hyperdrive_config_delete. Irreversible; refuses unless confirm: true. For r2, id is the bucket name.

Notion

notion_find, notion_read, notion_create, notion_update, notion_sync_content, notion_index_entries_add_batch — consolidated from a former 12-tool surface (notion_search/notion_list → notion_find; notion_get_page/notion_get_page_history/ notion_get_database/notion_query_database → notion_read; notion_create_page/ notion_create_pages_batch/notion_create_database → notion_create; notion_update_page/ notion_update_pages_batch/notion_update_database → notion_update), each now taking a type/mode arg to select page vs. database behavior. notion_index_entries_add_batch is a separate backfill/repair tool for the entity_id → page_id dedup index, not part of the consolidation.

notion_chart (action: create | update | get | list | delete | create_dashboard | create_from_data) — creates and manages native Notion chart views (column, bar, line, donut, number) through the Views API. Without page_id/dashboard_id a chart becomes a view tab on the database; with page_id (+ optional after_block_id) it is placed inline on that page; with dashboard_id it becomes a widget in that dashboard. Properties are given by name or id. update keeps every existing chart setting you don't change (Notion replaces the whole configuration on PATCH, so the tool re-sends it). dry_run: true returns the exact request body without writing (it still does read-only schema/view lookups). Examples: {action: "create", database_id, name: "Tasks by status", chart_type: "column", x: "Status"}.

Dashboards: {action: "create_dashboard", database_id, name} makes an empty dashboard view, then add charts with {action: "create", database_id, dashboard_id, placement: {type: "new_row" | "existing_row", row_index}, ...} (default placement: a new row at the end; existing_row puts the widget side by side with that row). Notion has no API to move widgets or change the layout after creation. Dashboards need a Business or Enterprise Notion plan.

Ad-hoc data: {action: "create_from_data", parent_page_id, database_title, columns: [{name, type}], rows: [{<column>: value}], chart_type, x, ...} creates a new database under that page, inserts the rows, then adds the chart (name defaults to database_title; page_id/dashboard_id placement also work). Column types: title (exactly one), text, number, select, date (ISO 8601), checkbox. Max 50 rows and 25 columns per call. The data and the chart settings are validated before anything is written; if a later step fails, the error says what was already created (the database is left in place).

Requirements: a paid Notion plan (free workspaces get one chart) and an integration with the capability to create views. Views calls send Notion-Version: 2026-03-11 per request only; the global NOTION_VERSION stays 2022-06-28 because the entity index relies on /databases/{id}/query. Not supported yet: raw "results" mode.

Mem0

mem0_write (action: add | add_batch | update — replaces the former mem0_add, mem0_add_batch and mem0_update), mem0_inspect (action: get | history | relations — replaces the former mem0_get, mem0_get_history and mem0_get_relations), mem0_find (action: list | search — replaces the former mem0_list and mem0_search), mem0_delete (action: one | batch | all — replaces the former mem0_delete, mem0_delete_batch and mem0_delete_all)

Context7

search_library, get_library_docs — resolve a library/framework name to a Context7 ID, then fetch up-to-date, version-specific docs and code examples for it. Works without an API key at low rate limits; CONTEXT7_API_KEY is optional.

Sync

sync_mem0_to_notion — one-way sync from Mem0 into a Notion "Memory Index": creates/updates a Notion page per Mem0 memory, archives pages for superseded or hard-deleted memories, and leaves any manual edits on those pages untouched.

Jules

jules_find (sources | sessions), jules_inspect (session | activities), jules_write (create | message) — hand off a coding task to Google's Jules agent, fire-and-forget: it works in its own sandboxed VM against a connected GitHub repo and (by default, via automation_mode: AUTO_CREATE_PR) opens a PR when done, with plans auto-approved. Distinct from delegate_agent/delegate_designer in this repo: those are synchronous read-only (or frontend-fenced) loops returning one answer per call; a Jules session is asynchronous and can write arbitrary code across a whole repo over several minutes, independent of this server's request lifecycle — create it with jules_write, then poll jules_inspect later. Alpha API (Google's own designation), no unauthenticated tier.

Fetch

web_fetch — fetch a public URL and return text/JSON/stripped HTML

Configuration

All tokens are optional independently — a connector's tools fail at call time (not startup) if its token is missing.

Variable Required for
GITHUB_TOKEN GitHub tools
NOTION_TOKEN Notion tools
MEM0_API_KEY Mem0 tools (MEM0_USER_ID optional, defaults to default)
CLOUDFLARE_API_TOKEN + CLOUDFLARE_ACCOUNT_ID Cloudflare tools
CONTEXT7_API_KEY Context7 tools (optional — works unauthenticated at low rate limits)
JULES_API_KEY Jules tools (jules_*) — required, no unauthenticated tier
GEMINI_API_KEYS Gemini tools (delegate_agent, delegate_research) — comma-separated, multi-key cascade; required (or legacy singular GEMINI_API_KEY), throws if unset
GEMINI_MODEL Primary Gemini model for delegation (default gemini-3.8-flash)
GEMINI_FALLBACK_MODELS Comma-separated fallback model list used on 429s/503s/network errors, and on 404s when a model ID has been retired (default gemini-3.7-flash,gemini-3.6-flash,gemini-3.5-flash,gemini-3.5-flash-lite)
GEMINI_NOTION_ROOT_PAGE_ID Notion page under which Gemini tool outputs are logged (has a working default)
OPENROUTER_API_KEYS Comma-separated OpenRouter API key(s) — required for delegate_agent's provider: "glm" mode, unused otherwise
GLM_MODEL Primary GLM model (via OpenRouter) for provider: "glm" delegation (default z-ai/glm-4.5-air:free)
GLM_FALLBACK_MODELS Comma-separated fallback model list used on 429s, cascaded per OPENROUTER_API_KEYS key (default: none configured)
GLM_REQUEST_TIMEOUT_MS Defensive ceiling on a single GLM/OpenRouter call (default 55000)
GROQ_API_KEYS Comma-separated Groq API key(s) — required for delegate_agent's provider: "groq" mode, unused otherwise; free-tier is request/token-rate-limited, not credit-balance-gated like OpenRouter/GLM
GROQ_MODEL Primary Groq model for provider: "groq" delegation (default openai/gpt-oss-120b, a production model)
GROQ_FALLBACK_MODELS Comma-separated fallback model list used on 429s, cascaded per GROQ_API_KEYS key (default qwen/qwen3.6-27b — stronger on benchmarks but a Groq preview model, kept as fallback rather than primary for availability reasons)
GROQ_REQUEST_TIMEOUT_MS Defensive ceiling on a single Groq call (default 55000)
DEFAULT_LLM_PROVIDER Which provider delegate_agent uses when a call omits provider (default gemini)
UPSTASH_REDIS_REST_URL + UPSTASH_REDIS_REST_TOKEN (or KV_REST_API_URL + KV_REST_API_TOKEN) Optional — persists per-model rate-limit cooldowns and delegate_agent resume checkpoints across invocations; fails open if neither pair is set. Either naming works — the raw Upstash Marketplace integration names them UPSTASH_REDIS_REST_*, Vercel's own "KV" product (also Upstash-backed) names them KV_REST_API_*.
DEFAULT_OWNER Default GitHub owner when omitted from a call (defaults to allocsys)
GITHUB_MIN_REQUEST_INTERVAL_MS Minimum spacing between outgoing GitHub REST requests, to avoid secondary rate limits (default 300)
GITHUB_MAX_RETRIES Max retries on GitHub secondary-rate-limit/429 responses (default 3)
GITHUB_RETRY_BASE_MS Fallback backoff base when GitHub omits Retry-After (default 1500, doubles per retry)
NOTION_INDEX_DATABASE_ID Database used for entity_id → page_id dedup lookups. Set this yourself if you're self-hosting — the default points to a database in the original deployer's own Notion workspace, which your integration won't have access to. Create a database in your workspace, share it with your integration (notion.so/my-integrations), and use its ID (the 32-char segment in its URL).
NOTION_SYNC_PARENT_PAGE_ID Parent page for pages created by sync_mem0_to_notion. Set this yourself if you're self-hosting, same reason as above — the default points to a page in the original deployer's workspace. Share a page in your own workspace with your integration and use its page ID.
MCP_SHARED_KEY Shared-secret auth for /mcp. Unset = endpoint is open to anyone with the URL — set this in any real deployment.
IP_ALLOWLIST_ENABLED Set false to disable the IP allowlist (default: enabled)
ALLOWED_IP_RANGES Comma-separated CIDR ranges allowed to call /mcp (defaults to Anthropic's published connector range)
TRUST_PROXY_HOPS Number of reverse-proxy hops to trust for client-IP detection (default 1, matches Render). Adjust if deploying behind a different proxy chain.

Security notes

  • Requests to /mcp are restricted by IP allowlist and rate-limited (30 requests/min).
  • Scope GITHUB_TOKEN as narrowly as possible (ideally fine-grained, limited to specific repos). This server can create/delete repos and files, merge PRs, and delete Cloudflare resources — treat every configured token as live write access.
  • GET / reports which connectors are configured; GET /health stays open and info-free for uptime checks.
  • Rotate any token if you ever suspect it's been exposed.

License

Licensed under AGPL-3.0 with the Commons Clause. In plain terms:

  • You can use, run, modify, and self-host this server, including for internal business use — for free.
  • If you modify it and let others interact with your version over a network (e.g. host it as a service), you must publish the source of your changes (AGPL-3.0's network-copyleft requirement).
  • You may not sell it — the Commons Clause blocks offering a paid product or service whose value comes substantially from this software's functionality, including paid hosting of it, without a separate agreement with the licensor.

See LICENSE for the full, binding text. This summary is for convenience only and isn't a substitute for reading it (and isn't legal advice).

Running locally

export GITHUB_TOKEN=ghp_yourtokenhere
npm install
npm start

Server listens on PORT (default 8080). MCP endpoint: POST /mcp. Health check: GET /health.

Testing

npm install
npm test

Runs the unit tests (vitest) covering the IP-allowlist/auth helpers (connectors/security.js), the GitHub client's retry/backoff logic (connectors/github/client.js), and the Jules client/tools (connectors/jules/). CI (.github/workflows/ci.yml) runs these same tests on every push and PR, plus a syntax check and a boot/GET /health smoke test, as a merge gate — it does not deploy; Render and Vercel already auto-deploy on push via their own GitHub integrations.

About

MCP server exposing GitHub, Cloudflare, Notion, Mem0, and web-research tools to AI agents — with built-in delegated multi-step reasoning (Gemini/Exa) and hardened auth for the Claude connector.

Resources

Stars

1 star

Watchers

0 watching

Forks

Contributors

Languages