Skip to main content
Glama

mcp-multisource

An MCP server that federates GitHub, Notion and Linear behind one access-controlled tool surface, with per-source failure isolation.

It is built around a claim that is easy to state and annoying to implement: a model should be able to ask one question that spans three systems, get an answer when only two of them are up, and never be able to write to a system it was not scoped to.

                        ┌──────────────────────────┐
   MCP client ────────▶ │  AccessControlMiddleware │  role + resource scoping
   (Claude Desktop)     └────────────┬─────────────┘
                                     │
                        ┌────────────▼─────────────┐
                        │      tool surface        │  16 tools, each declaring
                        └──┬──────────┬──────────┬─┘  the capability it needs
                           │          │          │
                    ┌──────▼───┐ ┌────▼─────┐ ┌──▼───────┐
                    │  GitHub  │ │  Notion  │ │  Linear  │  one adapter each:
                    │ adapter  │ │ adapter  │ │ adapter  │  own auth, own rate
                    └────┬─────┘ └────┬─────┘ └────┬─────┘  dialect, own breaker
                         │            │            │
                      REST         REST+ver     GraphQL

What it does

Sixteen tools. Five read GitHub, three read/write Notion, four read/write Linear, one joins all three, three are metadata.

The tool that justifies the architecture is release_readiness:

release_readiness(repo="acme/service", spec_query="checkout rewrite", team_key="ENG")

It fans out concurrently to open PRs and issues on GitHub, matching spec pages in Notion, and tracked work in Linear — then returns a single object with a degraded list naming any section it could not fill and why. A model calling it during a Notion outage still gets the GitHub and Linear halves plus an explicit note that the spec section is missing, instead of an error.

Tool

Source

Capability

github_list_repos

GitHub

read

github_list_issues

GitHub

read

github_list_pull_requests

GitHub

read

github_search_code

GitHub

read

github_comment_on_issue

GitHub

write

notion_search

Notion

read

notion_get_page_text

Notion

read

notion_create_page

Notion

write

linear_list_teams

Linear

read

linear_list_issues

Linear

read

linear_create_issue

Linear

write

linear_comment_on_issue

Linear

write

release_readiness

all three

read

system_health

read

access_policy

read

source_diagnostics

admin


Related MCP server: MCP-GET

Why these three sources

The brief for this project asks for a structured source, an unstructured knowledge source, and an action-capable source. That is not an arbitrary taxonomy — it is the minimum set needed to answer the question an engineering org actually asks, which is some variant of "is this thing done, and if not, what is left?"

Answering it requires three different kinds of lookup:

  • GitHub — structured ground truth. What the code actually says. PRs, issues, branches. Machine-shaped, queryable, and not subject to opinion.

  • Notion — unstructured intent. What the thing was supposed to do. Specs, runbooks, decision docs, postmortems. Prose written by humans for humans; there is no schema to query, so it has to be searched and flattened.

  • Linear — action. Where the gap between the first two becomes somebody's problem. This is the source the server can write to, which is why it is also the one with the tightest access control.

Each one is a genuinely different integration problem, which is the point. They do not share an auth scheme, a rate-limit dialect, an error convention, or even a protocol — Linear is GraphQL, the others are REST, and Notion's REST is version-pinned by header rather than by URL. Wrapping three REST APIs from the same vendor would demonstrate nothing. Every meaningful difference between these three forced a design decision that is documented below.

The use case they compose into: an engineering assistant. "Read the checkout rewrite spec, tell me which parts have no PR yet, and file Linear issues for the gaps." That single request touches unstructured knowledge, structured state, and a write — one from each column.


Authentication

Three sources, three schemes. None of them interchangeable, and two of them have a trap.

Source

Header

Notes

GitHub

Authorization: Bearer <fine-grained PAT>

Also requires X-GitHub-Api-Version. Fine-grained tokens are per-repo — a token scoped to one repo returns 404, not 403, for the others, which reads like a typo.

Notion

Authorization: Bearer <integration secret> + Notion-Version: 2022-06-28

The version header is mandatory. Notion pins behaviour to a date string rather than negotiating; omit it and every call 400s.

Linear

Authorization: <personal API key>

No Bearer prefix. Adding one — the reflex, since the other two want it — fails with an unhelpful error. This is the single most common Linear integration mistake.

The Notion sharing trap. An integration token that authenticates perfectly still sees an empty workspace until a human explicitly shares each page with it (page → ⋯ → Connections → <integration>). Notion reports this as 404 object_not_found, which is indistinguishable from a bad page id unless you know. The adapter intercepts that specific code and rewrites the message to name the real cause — see sources/notion.py. One line of code, an hour of debugging saved, and a good example of why per-source adapters beat a generic HTTP wrapper.

Credentials live in .env (gitignored), are read once in config.py, and are never logged. source_diagnostics reports whether each credential is present and what scheme it uses — never a value.


Access control

Most MCP examples expose every tool to every caller. That is fine for a demo and untenable the moment the server points at a company's real Linear workspace. This server gates on two axes.

Capabilities. Three roles, set by MCP_ROLE:

Role

read

write

admin

viewer

contributor

admin

An unrecognised role fails closed to viewer rather than erroring or defaulting open.

Resource scoping. Capability is not enough — a contributor allowed to write should still not be able to write anywhere. ALLOWED_REPOS, ALLOWED_LINEAR_TEAMS and ALLOWED_NOTION_PAGES scope a deployment to specific resources. Empty means unscoped. Repo matching is case-insensitive (GitHub is); Notion page ids are compared with dashes stripped, because Notion hands out the same id in both forms.

Enforced in two places, on purpose. AccessControlMiddleware sits on the SDK's ServerMiddleware hook, which runs before validation or handler dispatch on every inbound request:

  • On tools/list it filters the advertised tools. This is prompt hygiene — the model never burns a turn on a tool it cannot call.

  • On tools/call it authorizes independently. This is the actual boundary. A list filter is not security: a client can call any tool name it likes, including one it was never shown. tests/test_access.py asserts exactly this case.

Requirements are declarative. Each tool carries a ToolAccess in its MCP meta, so a new tool cannot silently ship without a policy — there is no "remembered to add the check" failure mode:

register(linear_create_issue, ToolAccess(WRITE, "linear", "team_key", "linear_team"), ...)

A denial comes back as a structured isError tool result carrying retryable: false, not a protocol exception. The model reads why it was denied and stops, instead of seeing a transport failure and retrying.


What happens when a source is unavailable

The design rule: a failing source degrades its own tools and nothing else. Breakers, rate budgets and clients are per adapter. There is no global state that one sick source can poison.

Error taxonomy

Every failure — an HTTP status, an httpx exception, a GraphQL errors array — is normalised into one typed error before it leaves the adapter (errors.py). The tool layer never branches on status codes.

Error

Retryable

Trips breaker

Raised for

auth_failed

401, or 403 with quota remaining

rate_limited

429, or GitHub's 403-with-remaining: 0

unavailable

5xx, DNS failure, connection reset, TLS error

timeout

exceeded HTTP_TIMEOUT_SECONDS

not_found

404 / Notion object_not_found

bad_request

other 4xx, Linear GraphQL errors

circuit_open

local refusal; the source is not called

Non-retryable errors deliberately do not trip the breaker. A bad token or a typo'd repo name is not evidence that GitHub is down. Counting them would open the circuit on a typo and take out a healthy source.

Retry and backoff

Retryable errors are retried up to MAX_RETRIES with exponential backoff and full jitter — uniform over [0, min(8s, 0.5 · 2ⁿ)). Full jitter rather than fixed backoff because several tools hitting the same rate-limited source would otherwise collide again on every retry wave. When the server sends Retry-After, that number wins over our guess. Both delta-seconds and HTTP-date forms are parsed.

Circuit breaker

Per source, three states:

closed ──(N consecutive retryable failures)──▶ open
  ▲                                             │
  │                                    (cooldown elapses)
  │                                             ▼
  └────────(probe succeeds)──────────────── half_open ──(probe fails)──▶ open

half_open admits exactly one probe; concurrent callers are refused locally rather than all stampeding a recovering source. While open, calls fail in microseconds without touching the network — which is the point: one dead source must not consume the latency budget of a request that also touches two healthy ones.

Per-source failure behaviour

Scenario

What the server does

What the model sees

GitHub primary rate limit (403, remaining: 0)

reads reset timestamp, sleeps that long, retries

rate_limited + exact retry_after_seconds

GitHub search rate limit (30/min, separate pool)

same, but only search tools are affected — core quota is untouched

rate_limited, other GitHub tools keep working

GitHub 404 from a fine-grained token's blind spot

no retry

not_found

Notion 429 (~3 req/s)

honours Retry-After

rate_limited + retry_after_seconds

Notion page not shared with the integration

no retry

not_found with a message naming the sharing fix

Notion 5xx

retried with jittered backoff, breaker counts it

unavailable

Linear GraphQL errors array on HTTP 200

mapped to auth_failed / rate_limited / bad_request by extensions.code

the correct typed error, not a false success

Linear API key sent with a Bearer prefix

no retry

auth_failed

Any source down repeatedly

breaker opens; calls refused locally for BREAKER_RESET_SECONDS

circuit_open + seconds until the next probe

Any source not configured at all

its tools are hidden from tools/list entirely

tool does not exist; system_health says not_configured

Unexpected exception inside a tool

caught in _guard

internal_error; the stdio transport stays up

That last row matters more than it looks. An unhandled exception in an MCP tool can take down the transport, which kills every other source too. _guard wraps every source-touching tool for exactly that reason.

Observability

system_health probes all sources concurrently and returns, per source: reachable, circuit state, consecutive failures, success/failure/locally-rejected counts, rate budget, and last error kind. Where a source publishes no quota headers — Notion — it reports null rather than inventing a number.

{
  "healthy": ["github", "linear"],
  "degraded": ["notion"],
  "not_configured": [],
  "server_usable": true
}

Setup

git clone <this repo> && cd mcp-multisource
make setup
cp .env.example .env      # then fill it in
make test                 # offline: access control, breaker, degradation
make e2e                  # protocol: real stdio, fake creds, no secrets needed
make verify               # live: hits all three real APIs

Getting credentials:

  • GitHub — Settings → Developer settings → Personal access tokens → Fine-grained. Grant Contents: read, Issues: read+write, Metadata: read, Pull requests: read.

  • Notionnotion.so/my-integrations → new internal integration → copy the secret. Then share at least one page with it, or it sees nothing.

  • Linear — Settings → API → Personal API keys.

Register with an MCP client

{
  "mcpServers": {
    "multisource": {
      "command": "/absolute/path/to/mcp-multisource/.venv/bin/python",
      "args": ["-m", "mcp_multisource"],
      "cwd": "/absolute/path/to/mcp-multisource"
    }
  }
}

.env is read relative to the project root, so cwd matters.


Verification

Three suites, testing different things.

make test — offline, no secrets. 39 tests using httpx.MockTransport, so real adapter code runs against synthetic responses with no network. Covers the capability matrix, scoping, the list-filter-versus-boundary distinction, every row of the error taxonomy above, breaker state transitions, retry semantics, and cross-source isolation.

make e2e — protocol, no secrets. Everything else tests Python objects. This launches the server over real stdio and drives it with a real MCP client, using deliberately invalid credentials — so it checks the handshake, the wire format, and degradation in one pass. Observed output:

Role-based tool visibility
  PASS  viewer sees no write tools  [11 tools]
  PASS  viewer sees no admin tools
  PASS  admin sees every tool  [16 tools]
  PASS  capabilities differ

Enforcement is independent of the list filter
  PASS  hidden tool call is refused  [access_denied]
  PASS  denial is marked non-retryable

Partial configuration
  PASS  unconfigured sources' tools are absent  [9 tools]
  PASS  health names them as not_configured

Degradation with invalid credentials
  PASS  a failing tool returns a structured result, not an exception  [auth_failed]
  PASS  the failure names its source
  PASS  cross-source tool still returns
  PASS  and reports what it could not reach  [open_issues, open_pull_requests, tracked_work]
  PASS  marking itself incomplete

That fourth block is the brief's question answered concretely: with GitHub returning 401 and the other two sources unconfigured, release_readiness still returns ok: true with a populated degraded list rather than raising.

make verify — live. Authenticates against all three real APIs, runs a real read per source, optionally (--write) creates real records, then deliberately breaks things: invalid credentials per source asserting the error taxonomy, and a repeated failure run asserting the breaker opens. Exits nonzero on failure, so it works in CI.


Layout

src/mcp_multisource/
  server.py        tool surface; every source call wrapped in _guard
  middleware.py    AccessControlMiddleware — the enforcement seam
  access.py        capabilities, roles, resource scoping
  resilience.py    circuit breaker, jittered backoff, retry loop
  errors.py        the typed error taxonomy
  registry.py      builds adapters from settings; concurrent health probes
  config.py        the only module that reads os.environ
  sources/
    base.py        shared HTTP: timeouts, retry, breaker, status→error mapping
    github.py      Bearer PAT; x-ratelimit-*; 403-means-rate-limit quirk
    notion.py      Bearer + version header; sharing trap; no quota headers
    linear.py      bare key; GraphQL; 200-with-errors dialect
scripts/verify_sources.py   live verification + fault injection
scripts/e2e_check.py        protocol smoke test over real stdio
tests/                      offline suite

Known limits

  • No pagination beyond the first page on any list tool. Fine for the queries these tools serve; would need cursors for exhaustive sync.

  • Notion block flattening covers the common spec/runbook shapes, not arbitrary nesting or database properties.

  • Roles come from an environment variable, i.e. one role per server process. A multi-tenant deployment would take identity from the transport (the SDK exposes TokenVerifier / auth for this) rather than from config; the policy layer is already shaped for it — only the source of role changes.

  • Health is probed on demand, not on a background schedule.

Available Tools

3 tools
access_policyA

Show this session's role, capabilities and resource scopes.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full disclosure burden. It clearly signals a read-only, non-mutating operation via 'Show' and scopes the result to the current session. It does not discuss error conditions or side effects, but for a zero-parameter introspection tool this is adequate.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single focused sentence with no filler. It front-loads the action ('Show') and the important scoping information ('this session's role, capabilities and resource scopes') immediately.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With zero parameters, an output schema, and a simple introspection purpose, the description provides all essential selection and invocation information. Nothing material is missing for an agent to understand when and how to call this tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool has no parameters, so there is no parameter semantics burden. The schema is trivially complete, and the description cannot add parameter-level meaning. The baseline score of 4 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description uses a specific verb 'Show' and identifies exactly what is exposed: this session's role, capabilities, and resource scopes. This clearly distinguishes the tool from sibling tools like release_readiness and system_health, which focus on different concerns.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description makes the usage context easy to infer: an agent needing session access details would choose this tool. However, it does not explicitly state when to use this tool versus alternatives, nor does it mention any exclusions or prerequisites.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

release_readinessB

Cross-source snapshot: open GitHub PRs/issues for a repo, matching Notion specs, and Linear tracked work. Returns partial results with a degraded list when a source is unavailable.

ParametersJSON Schema
NameRequiredDescriptionDefault
repoYes
team_keyNo
spec_queryNo

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

B3.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full behavioral burden; it discloses the notable degraded/partial-result behavior and the `degraded` list, which is valuable. It does not state read-only status or permissions, but the aggregation context and failure-mode disclosure provide a solid behavioral profile.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences with no filler; the core aggregation purpose is front-loaded and the degraded behavior is clearly stated. Very efficient for the information conveyed.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The output schema covers return shape, so the description can focus on invocation context, but the ambiguous 'matching Notion specs' phrasing and lack of any guidance on optional parameters leave real gaps. The degraded-source caveat is good, yet an agent still does not know how to supply or shape `spec_query` or `team_key`.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must explain the parameters, but it only loosely references Notion specs and Linear work without mapping to `spec_query` or `team_key`. `repo` is implied by 'for a repo', but optional params and their string formats remain unexplained.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description identifies a specific aggregation task—open GitHub PRs/issues, Notion specs, and Linear tracked work for a repo—so an agent can infer the tool's role. It lacks a direct verb and does not contrast with the unrelated sibling tools, but the resource and actions are sufficiently concrete.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The phrase 'Cross-source snapshot' implies this is the go-to tool for combined release-readiness data, and the degraded-note signals when partial results should be expected. No explicit when-to-use/when-not-to-use guidance or alternative routing is provided, though the siblings appear unrelated.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

system_healthC

Per-source health: reachable, circuit state, rate budget, last error.

ParametersJSON Schema
NameRequiredDescriptionDefault
probeNo

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

C2.8/5.0
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden of behavioral disclosure. It adds useful per-source scope and lists the health dimensions, but it does not say whether the call is read-only, whether probe triggers active network checks or side effects, or how circuit state and rate budget should be interpreted.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single compact fragment with no filler; the colon list is easy to scan and front-loads the core idea. It is arguably too sparse for full guidance, but the brevity itself is well executed.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple one-optional-parameter tool with an output schema, the core purpose is stated, but the probe parameter and safety/behavioral context are missing. It is minimally viable but leaves gaps an agent must guess around.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The description never mentions the probe parameter, and schema description coverage is 0%, so the description adds no semantics beyond the schema's type and default. The parameter name is mildly self-explanatory, but the definition fails to state what different probe values actually do.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The phrase 'Per-source health' identifies the resource as source-level health status and enumerates what it covers: reachability, circuit state, rate budget, and last error. It lacks an explicit verb like 'get' or 'check,' so it is clear but not maximally precise and does not explicitly distinguish itself from release_readiness or access_policy.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

No guidance is given on when to call this tool versus its siblings release_readiness or access_policy. There are no exclusion criteria, prerequisites, or context cues, so the agent must infer when this tool is the right choice.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections. Dates show when Glama detected each change.

  1. 3 tool updatesv0.1.0
    • First observedaccess_policy
    • First observedrelease_readiness
    • First observedsystem_health

TDQS

A3.7/5.0
Disambiguation5/5

Each tool addresses a completely distinct concern: release_readiness provides aggregated cross-source data, system_health reports on source connectivity/status, and access_policy describes permissions. There is no overlap or ambiguity between their purposes.

Naming Consistency5/5

All tool names follow the same snake_case noun_noun pattern (release_readiness, system_health, access_policy), which is predictable and readable. No mixed conventions or vague verbs appear.

Tool Count5/5

Three tools is a well-scoped count for a focused multisource status server. Each tool earns its place and the set feels neither bloated nor too thin.

Completeness4/5

The server covers the core lifecycle of reading cross-source release readiness, monitoring source health, and checking access. A minor gap is the lack of a tool to query a specific source in detail or perform any mutating action, but agents can work around this with release_readiness and system_health.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

Related MCP Servers

  • F
    license
    B
    quality
    D
    maintenance
    Enables AI assistants to interact with GitHub repositories, Confluence documentation, and Databricks Unity Catalog through comprehensive tools for code exploration, documentation retrieval, and data schema management.
    19
    -
  • A
    license
    Not graded
    quality
    C
    maintenance
    A unified Model Context Protocol server that provides a consistent interface for AI assistants to interact with productivity tools like Linear, GitHub, Slack, and Notion. It enables users to search, retrieve, and manage tasks and data across multiple workplace services from a single endpoint.
    16
    MIT
  • F
    license
    Not graded
    quality
    C
    maintenance
    Provides read-only access to Confluence, GitHub, Jira, Figma, Outlook, Teams, and browser automation for AI clients, enabling safe discovery, retrieval, and summarization of company knowledge without modifying source systems.
    -
  • A
    license
    A
    quality
    A
    maintenance
    Search, document and execute authenticated API calls across all your apps (Gmail, Slack, Stripe, Notion, GitHub, and more) through 4 universal tools whose context footprint stays constant no matter how many connections you add. Hosted remote server with OAuth at https://mcp.withone.ai/mcp, or run locally via npx @withone/mcp.
    4
    393
    10
    MIT

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/arpitpaliwal007/mcp-multisource'

If you have feedback or need assistance with the MCP directory API, please join our Discord server