Skip to main content
Glama

MCP Object-Authz Lab

lab-integrity CodeQL License: MIT

A small, self-hostable, vulnerable-by-design MCP server for learning how object-level / cross-tenant authorization bugs (BOLA / IDOR — CWE-639, CWE-862) appear in Model Context Protocol tools, and how to hunt them.

It is a multi-tenant note server exposing eighteen MCP tools and one MCP resource across thirteen independent BOLA scenarios. Each scenario is a different variant of the same bug class, toggled by its own environment variable. Run them all at once or isolate one at a time.

Why this lab exists

Most MCP security attention goes to prompt injection and tool-poisoning. Object-level authorization is a quieter, different class, and the usual prompt-injection test suites and scanners do not find it. When an MCP server is multi-tenant, every tool that resolves an object from a client-supplied id must verify the caller is allowed to touch that object. Miss the check on a single tool and you have a cross-tenant read, write, or delete — regardless of how good the prompt-injection defenses are. Catching it takes reading the authorization on each tool, which is exactly the muscle this lab trains.

This isn't theoretical. Asana's MCP connector (Jun 2025) leaked data across tenant boundaries for roughly 1,000 customer organizations — a breakdown in tenant isolation, the same shape of bug as S1-S7 below (Pomerium's writeup). And n8n-mcp — a popular MCP server with 20k+ GitHub stars — shipped CVE-2026-54052 (CVSS 9.6): sequential integer ids on a table missing a tenant-id column let any caller read or delete another tenant's stored API keys. Neither would have been caught by a prompt-injection scanner — both are S1/S3-shaped bugs (this lab's "missing check on one tool" and "list vs. get asymmetry" scenarios), just in production instead of a lab.

Related MCP server: vuln-mcp-server

Try the challenges

Ten hands-on scenarios in challenges/ — no hints until you open solutions/. Each runs locally in under 5 minutes.

Scenario

Pattern

S1

Inconsistent authorization — find the single outlier

S2

Client-supplied scope trusted as authorization

S3

List→get asymmetry — batch skips per-object check

S4

Wildcard/sentinel value bypasses scope filter

S5

Role/token-type bypass — admin-named tool, no role check

S6

Foreign-parent injection — create trusts a caller-supplied org

S7

Unscoped query — tenant key omitted from the filter (the real-world shape)

S8

Resource-URI-as-scope — the resources/read surface, not tools/call

S9

Authz-from-client-round-tripped-value — an editable share grant

S10

Forwarded-header-as-scope — a trusted request header (HTTP transport)

S11

X-Forwarded-For quota bypass — a trusted request header for rate limiting (HTTP transport)

S12

Batch/bulk endpoint BOLA — multi-object endpoint without per-item tenant filter

S13

JWT/token scope confusion — token's scope/aud claim vs actual permissions mismatch

Quickstart (< 5 minutes)

Requirements: Node.js ≥ 20.

npm install
npm test    # 52 tests — auth.js/store.js in isolation, plus docs-consistency
npm run poc # 38-row two-way gate — the tools/resources wired end-to-end over MCP
npm run poc -- --json-output poc-chain-evidence.json --sarif-output poc-chain-evidence.sarif

Expected npm run poc output (38/38 rows, all scenarios + the hardened build):

MCP object-level authorization lab — two-way gate (13 scenarios + hardened build)

  SC   BUILD  ACTION                                         OUTCOME   EXPECT    OK
  S1   vuln   note_get    cross-tenant (Bob→Acme)            DENIED    DENIED    ✓
  S1   vuln   note_delete cross-tenant (Bob→Acme)            DELETED   DELETED   ✓
  S1   fixed  note_delete cross-tenant (Bob→Acme)            DENIED    DENIED    ✓
  S1   fixed  note_delete same-tenant  (Bob→Globex)          DELETED   DELETED   ✓
  S2   vuln   note_search  scope-as-param (Alice→org_globex) LEAKED    LEAKED    ✓
  S2   fixed  note_search  scope-as-param (Alice→org_globex) SCOPED    SCOPED    ✓
  S3   vuln   note_batch_get list→get asymm (Alice+Globex)   LEAKED    LEAKED    ✓
  S3   fixed  note_batch_get list→get asymm (Alice+Globex)   SCOPED    SCOPED    ✓
  S4   vuln   note_export  wildcard org_id='*' (Alice)       LEAKED    LEAKED    ✓
  S4   fixed  note_export  wildcard org_id='*' (Alice)       SCOPED    SCOPED    ✓
  S5   vuln   note_admin_get cross-org as Bob (user)         LEAKED    LEAKED    ✓
  S5   fixed  note_admin_get cross-org as Bob (user)         DENIED    DENIED    ✓
  S5   fixed  note_admin_get cross-org as Dana (real admin)  ALLOWED   ALLOWED   ✓
  S6   vuln   note_create_in_org org_id=org_globex (Alice)   INJECTED  INJECTED  ✓
  S6   fixed  note_create_in_org org_id=org_globex (Alice)   SCOPED    SCOPED    ✓
  S7   vuln   note_get_by_query cross-tenant (Alice→Globex)  LEAKED    LEAKED    ✓
  S7   fixed  note_get_by_query cross-tenant (Alice→Globex)  DENIED    DENIED    ✓
  S8   vuln   resources/read cross-tenant (Alice→Globex)     LEAKED    LEAKED    ✓
  S8   fixed  resources/read cross-tenant (Alice→Globex)     DENIED    DENIED    ✓
  S9   vuln   note_share_redeem tampered grant (Alice→Globex) LEAKED    LEAKED    ✓
  S9   fixed  note_share_redeem tampered grant (Alice→Globex) DENIED    DENIED    ✓
  S9   fixed  note_share_redeem own grant     (Alice→Acme)   ALLOWED   ALLOWED   ✓
  S10  vuln   note_get_scoped X-Org-Id=org_globex (Alice over HTTP) LEAKED    LEAKED    ✓
  S10  fixed  note_get_scoped X-Org-Id=org_globex (Alice over HTTP) SCOPED    SCOPED    ✓
   S11  vuln   note_create_limited quota exhausted (same XFF)  BLOCKED   BLOCKED   ✓
   S11  vuln   note_create_limited quota key is 1.2.3.4       MATCH     MATCH     ✓
   S11  vuln   note_create_limited X-Forwarded-For=5.6.7.8    BYPASS    BYPASS    ✓
   S11  vuln   note_create_limited fresh server XFF=5.6.7.8 (quota key logic) CREATED   CREATED   ✓
   S11  vuln   note_create_limited quota exhausted XFF=5.6.7.8 BLOCKED   BLOCKED   ✓
   S11  fixed  note_create_limited quota exhausted (same XFF)  BLOCKED   BLOCKED   ✓
   S11  fixed  note_create_limited quota key is u_alice       MATCH     MATCH     ✓
   S11  fixed  note_create_limited X-Forwarded-For=5.6.7.8    BLOCKED   BLOCKED   ✓
   S11  fixed  note_create_limited fresh server XFF=5.6.7.8 (quota key logic) CREATED   CREATED   ✓
   S11  fixed  note_create_limited quota exhausted XFF=5.6.7.8 BLOCKED   BLOCKED   ✓
  S12  vuln   note_batch_resolve batch with Globex id (Alice) LEAKED    LEAKED    ✓
  S12  fixed  note_batch_resolve batch with Globex id (Alice) SCOPED    SCOPED    ✓
  S13  vuln   note_get_by_token_scope scope=org_globex (Alice) LEAKED    LEAKED    ✓
  S13  fixed  note_get_by_token_scope scope=org_globex (Alice) SCOPED    SCOPED    ✓
  ALL  fixed  14 cross-tenant routes (Bob→Acme)              BLOCKED   BLOCKED   ✓
  ALL  fixed  legitimate access (Dana admin + Bob own note)  ALLOWED   ALLOWED   ✓

  Two-way gate: PASS (38/38 rows OK)

The PoC is a real MCP client. It spawns the server over stdio (locally — no network, no third party) and runs a two-way gate per scenario: in the vuln build the exploit succeeds; in the fixed build it is blocked and legitimate same-org access still works (no false positive).

--json-output writes the structured evidence contract used by the optional toolkit adapter. --sarif-output writes only observed vulnerability outcomes from the intentionally vulnerable build; fixed controls and expected denials are not emitted as findings.

The final ALL rows apply that same two-way discipline to the whole server at once — every scenario fixed, every cross-tenant route closed, and legitimate access (an admin's cross-org read, a user's own note) still working. Each S1-S10 arm deliberately pins one toggle and leaves the rest at their vuln default, so without these rows the hardened build the section below tells you to run would have no coverage at all.


Scenario S1 — Missing org-scope check (the original)

Tool: note_delete
Class: CWE-639 / CWE-862 — object-level authorization
Toggle: LAB_MODE / LAB_S1

Among the six core note tools, every one that resolves an object by a client-supplied id calls requireOrgAccess() to confirm the note belongs to the caller's org — except note_delete (in vuln mode), so any caller can delete any org's note by knowing or guessing its id.

Challenge: The six core note tools are note_list, note_get, note_create, note_update, note_delete, note_search. Exactly one lets a caller in one org delete another org's note. Which one, and what makes it different?

The server exposes eighteen tools in total; six of them belong to S3-S7 and are vulnerable in their own default state. Run challenges/s1.md's Setup command, which pins them to fixed, or this scenario has more than one answer.

Three tools take a client-supplied id (note_get, note_update, note_delete). Two of them call requireOrgAccess() after resolving the note. One does not.

note_delete. It resolves the note exactly like note_get and note_update, but in LAB_MODE=vuln it never calls requireOrgAccess(session, note). The fix is that one line — the same check its siblings already perform. See the comment block in src/tools.js above note_delete.


Scenario S2 — Scope-as-param

Tool: note_search
Class: CWE-639 — client-supplied scope parameter trusted as authorization
Toggle: LAB_S2

note_search accepts an optional org_id parameter, documented as "admin use." In vuln mode the server uses it unconditionally — any caller can set it to any other org's id and receive that org's notes. The server authenticates who you are correctly, but then trusts the caller's assertion about whose data to return.

Challenge: Alice (org Acme) calls note_search. How does she read Globex's notes without knowing any note ids, using only one tool?

Look at the note_search input schema. There is a parameter that is supposed to be admin-only. What happens if you supply it as an ordinary user?

Alice calls note_search with org_id="org_globex". In LAB_S2=vuln the server substitutes her session's orgId with the supplied value and returns Globex's notes. The fix: in LAB_S2=fixed the org_id parameter is accepted (removing it would be a breaking API change) but silently ignored; session.orgId is always used.


Scenario S3 — List→get asymmetry

Tool: note_batch_get
Class: CWE-862 — inconsistent authorization between list and fetch
Toggle: LAB_S3

note_list safely returns only the caller's org's notes. But note_batch_get accepts an array of explicit ids and resolves each one directly from storage without re-applying the org scope check. An attacker who knows (or guesses) note ids from another org can mix them into the batch and receive them.

Challenge: Alice can call note_list and gets only her own notes. She suspects the ids from other orgs are guessable (they are: n_acme_1, n_globex_1, …). How does she read a Globex note?

note_batch_get accepts a list of ids. What happens when you mix ids from different orgs in the same request?

Alice calls note_batch_get with ids: ["n_acme_1", "n_globex_1"]. In LAB_S3=vuln both are resolved and returned — the batch skips per-object org checks. The fix: in LAB_S3=fixed the resolved notes are filtered to session.orgId before returning, so n_globex_1 is silently dropped.


Scenario S4 — Wildcard/sentinel bypass

Tool: note_export
Class: CWE-639 — sentinel value short-circuits scope filter
Toggle: LAB_S4

note_export exports all notes for a given org. A magic sentinel value ("*" or "all") is documented as "admin only" for a global export. In vuln mode the server honors it from any authenticated caller — letting them dump every note from every tenant in a single call.

Challenge: Alice wants to export data from all three orgs (Acme, Globex, Initech) in one call. She has only a user-level token. How?

Read the note_export tool description. There is a "magic value" mentioned. What happens if a non-admin user passes it?

Alice calls note_export with org_id="*". In LAB_S4=vuln the server recognizes the sentinel and calls store.listAllNotes() — returning all 6 notes from all 3 orgs. The fix: in LAB_S4=fixed the org_id parameter is ignored entirely; the server always exports only session.orgId.


Scenario S5 — Role/token-type bypass

Tool: note_admin_get
Class: CWE-863 — role/token-type bypass
Toggle: LAB_S5

The tool is named and documented as admin-only. In vuln mode nothing actually checks that the caller holds the admin role — any valid token reaches the cross-org lookup. Naming a tool admin_* is documentation, not enforcement.

Challenge: Bob (org Globex, an ordinary user) wants to read Acme's note n_acme_1 using an "admin" tool he was never granted access to. How?

Bob's own token is unprivileged. Does note_admin_get actually verify that before resolving the note?

Bob calls note_admin_get with id="n_acme_1" using bob-token. In LAB_S5=vuln the server resolves and returns the note — it never checked whether Bob's session role is "admin". The fix: LAB_S5=fixed calls requireAdminRole(session) before the lookup; ordinary tokens are denied, while Dana's real admin token (dana-token) still succeeds. See src/auth.js's requireAdminRole() and the comment block above note_admin_get in src/tools.js.


Scenario S6 — Foreign-parent injection

Tool: note_create_in_org
Class: CWE-639 — client-supplied parent/org trusted on create
Toggle: LAB_S6

A cross-team collaboration tool lets a caller create a note "inside" a specified org. In vuln mode the server trusts the caller-supplied org_id with no membership check — any caller can inject a note into an org they do not belong to. Unlike S1-S5 (all reads or a delete), this is a write-side BOLA: it poisons another tenant's data instead of leaking it.

Challenge: Alice (org Acme) wants to plant a note that shows up in Globex's note_list, despite never being a Globex member. How?

note_create_in_org takes an org_id parameter. What org does the note actually end up in if Alice supplies someone else's?

Alice calls note_create_in_org with org_id="org_globex". In LAB_S6=vuln the note is created with orgId: "org_globex" — it will show up the next time Bob calls note_list or note_search, despite Alice never being a Globex member. The fix: LAB_S6=fixed still accepts org_id in the schema (removing it would be a breaking change, same convention as S2/S4) but ignores it; the note is always created inside session.orgId.


Scenario S7 — Unscoped query

Tool: note_get_by_query
Class: CWE-639 — tenant key omitted from a scoped query
Toggle: LAB_S7

S1's outlier is a missing guard call: resolve a note by id, then forget to call requireOrgAccess. Real MCP servers rarely look like that. They bind the tenant into the query — repo.findOneBy({ id, workspaceId }) — so there is no separate guard line to omit. The bug in that world is quieter: the tenant key is simply left out of the filter, and the query matches on id alone.

This is the shape CVE-2026-54052 (n8n, CVSS 9.6) took — a table fetched by a sequential id with the tenant column left out of the WHERE, letting any caller read another tenant's stored secrets. It is also the pattern this lab's own detection rule (mcp-unscoped-query-object-fetch) was written to catch, because a guard-call detector never sees it: there is no guard call to be missing.

Challenge: Alice (org Acme) wants to read a Globex note by its id through note_get_by_query, despite never being a Globex member. Why does it work?

The tool resolves the note through a filtered query. What does the filter contain in vuln mode — and what one key is missing from it?

In LAB_S7=vuln the tool calls store.findNoteBy({ id }) — the filter carries only the caller-supplied id, so the query matches any note with that id regardless of org, and Alice reads Globex's note. The fix (LAB_S7=fixed) binds the tenant key into the same query: store.findNoteBy({ id, orgId: session.orgId }), so a cross-org id resolves to nothing. Note there is no requireOrgAccess call in either build — the authorization is the tenant key in the filter, which is exactly why the S1-style "look for the missing guard" reflex walks past it.


Scenario S8 — Resource-URI-as-scope

Resource: note://{token}/{orgId}/{noteId} Class: CWE-639 — a caller-writable URI segment trusted as scope Toggle: LAB_S8

S1-S7 are all tools (tools/call). This one lives on resources/*, a separate MCP primitive with its own registration API, its own handler signature ((uri, variables) instead of a single args object), and its own client-side call (resources/read). A review that reads "every tool" never sees it.

The resource template turns the tenant into a URI path segment — which the caller writes. In vuln mode the handler trusts that segment as the scope. Identity still comes from the {token} segment, resolved through the same resolveSession() every tool uses; only the scope segment is the planted bug. It is also quieter than a tool-call exploit: many MCP hosts gate tool calls behind an approval prompt but treat a resource read as inert reference material, with lighter or no approval at all.

Challenge: Alice (org Acme) wants to read a Globex note through resources/read, despite never being a Globex member, and without calling a single tool.

The URI template has three variables: token, orgId, noteId. One is who you are. One is what you want. What is the third one actually used for?

In LAB_S8=vuln the handler reads note://alice-token/org_globex/n_globex_1, resolves Alice's session from token, then uses the orgId path segment — not the session's own org — as the scope: store.findNoteBy({ id: noteId, orgId }). The fix (LAB_S8=fixed) ignores that segment and uses session.orgId instead. The URI template still carries {orgId} in both builds — removing it would be a breaking template change — it is simply never trusted as authorization.


Scenario S9 — Authz-from-client-round-tripped-value

Tools: note_share_prepare, note_share_redeem Class: CWE-639 — a value round-tripped through the client trusted as authorization Toggle: LAB_S9

note_share_prepare is correctly authorized: it mints an opaque grant for a note the caller's own session can already access. note_share_redeem decodes that grant and serves whatever note id is inside it — on the assumption that "the grant must have come from an authorized tool." The grant is a plain client-side string between the two calls, with no cryptographic signature; a caller can decode it, edit it, and redeem the edited version.

This is the shape a tool-chaining flow takes in MCP specifically: there is no server-side continuity between two tools/call invocations. Every value that crosses the gap between them travels through the client — and in an agentic pipeline, through the calling model's own context, where it can be edited or mangled without any deliberate tampering at all. "A prior tool already checked this" is a client-side claim, not a server-verified fact.

Challenge: Alice (org Acme) prepares a share grant for her own note. She never authenticates as anyone else. How does she end up reading Globex's note?

Look closely at what note_share_prepare actually returns. Is it opaque, or does it just look opaque?

The grant is base64url-encoded JSON, not a signed token. Alice decodes it, finds {"noteId": "n_acme_1"}, rewrites it to {"noteId": "n_globex_1"}, re-encodes it, and calls note_share_redeem with the tampered grant. In LAB_S9=vuln the tool resolves and returns whatever note the decoded grant names, with no re-check against Alice's session. The fix (LAB_S9=fixed) treats the decoded value as a hint, not an authorization: it calls requireOrgAccess(session, note) on the resolved note before returning it — the same object-level check every other scenario in this lab teaches, applied at the point a round-tripped client value is trusted again.


Scenario S10 — Forwarded-header-as-scope

Tool: note_get_scoped Class: CWE-639 / CWE-290 — a client-supplied request header trusted as scope Toggle: LAB_S10 · Transport: HTTP only (src/http-server.js)

Every other scenario reads its scope from a tool argument or a resource URI. S10 reads it from an HTTP request header. Over the streamable-HTTP transport the SDK hands each tool call the request headers in extra.requestInfo.headers, and note_get_scoped trusts an X-Org-Id header — "set by the gateway" — as the org scope. But any client talking to the server directly sets that header itself, so it is client-controlled input wearing the costume of infrastructure. This is the transport-layer sibling of S2, and the real-world class of trusting X-Forwarded-For for a security decision (the IP-scoping variant is the same bug, same fix). Because stdio carries no request headers, the bug only exists in the HTTP deployment — which is why S10 ships its own src/http-server.js, and why a review that only exercises the stdio server never sees it.

Challenge: You are Alice (alice-token, org Acme). Connect an MCP client to http://127.0.0.1:3010/mcp and read Globex's notes using one extra request header. See challenges/s10.md.


Detection rules — automate the hunt

detection/ ships 46 Semgrep rules — one per code shape above, with Python siblings where the JavaScript spelling cannot parse as Python, plus Go, Rust, Kotlin, Java, Ruby, PHP, C#, and Swift language packs — that flag these patterns in your own MCP server source, not just this lab's. Eleven of them run against Python as well as JavaScript/TypeScript, which matters because the reference MCP SDKs ship in both.

Honestly documented, and re-measured in this release: the earlier claim that the rules "catch 5 of 7 scenarios" against this lab's own source implied the S1/S5 rules miss real-world bugs. They do not. On production-shaped files — no toggle, guard simply absent — both fire exactly as designed; the only code they go quiet on is a handler where the guard is written but gated behind a runtime toggle, i.e. this lab's own scaffolding (see the linked README for the measured decision and the fixtures pinning it). With S12 and S13 added, the current measurement against this lab's own source is 7 of the 13 scenarios flagged (S1, S2, S6, S7, S8, S12, S13). The remaining six stay quiet: S3-S5 and S9-S11 sit behind a runtime toggle (this lab's own scaffolding) or, for S10/S11, a transport-header shape the JS rules match only in the language packs — see detection/README.md for the per-scenario breakdown and the fixtures pinning it. S9 has no dedicated rule: the existing mcp-missing-object-authz-check (S1's rule) already catches its vulnerable shape, since the fix path is a plain $OBJ = store.get...(); ...; return ok($OBJ) span with no guard call in between — see detection/README.md for the measured caveat (it only catches the assignment-carrying spelling, not an inline return ok(store.getNote(...)) with no local variable). Against the official @modelcontextprotocol/sdk the ruleset produces zero findings, measured with a planted canary proving the scan actually reached the tree (semgrep silently skips node_modules, so an unverified 0 is not a result).

Two rules were hardened in this release against false positives found in a 9-repo audit:

  • mcp-authz-scope-from-request-header (+ -py): now requires the header value to reach an authorization decision (store call selecting tenant scope), not merely be read. Two-way canary: FIRE on scope decision, SILENT on logging-only read.

  • mcp-wildcard-sentinel-scope-bypass: now requires the wildcard to be in an authorization bypass context (gates scope widening), not a textual comparison. Two-way canary: FIRE on authz bypass, SILENT on markup/UI filter.

Real-capture fixtures from the audit validate both hardenings: xff-for-logging.js/.py (SILENT), wildcard-in-markup.js (SILENT), and xff-for-ratelimit.js (FIRE for the new S11 rule).

Drop it into your own MCP server's CI as a GitHub Action. Findings upload to your repo's Security tab, so the calling workflow needs security-events: write:

permissions:
  security-events: write   # only needed for the SARIF upload

steps:
  - uses: WRG-11/mcp-objauthz-lab@main
    with:
      path: src/   # your MCP server source

How it is built

File

Role

src/store.js

In-memory multi-tenant seed data: 3 tenant orgs (Acme/Alice, Globex/Bob, Initech/Carol, 2 notes each) + 1 admin org (Platform Ops/Dana, no notes).

src/auth.js

resolveSession(token) → server-trusted { user, org, role }; requireOrgAccess(session, object) — the object-level check; requireAdminRole(session) — the role check.

src/tools.js

Eighteen tools plus one resource (note://{token}/{orgId}/{noteId}). Thirteen planted-bug handlers (one per scenario, S1-S13).

src/server.js

Stdio MCP server. Reads LAB_MODE/LAB_S1..S13 env vars, passes a modes object to registerTools.

src/http-server.js

Streamable-HTTP MCP server (for S10/S11). Same tools/store, transports headers via extra.requestInfo.headers.

poc/exploit.js

MCP client running the 38-row two-way gate: all 13 scenarios in isolation, plus the all-fixed hardened build.

test/

node --test unit tests for auth.js/store.js in isolation (52 tests, no MCP transport involved) plus docs-consistency.test.js.

Identity model (deliberate simplification). Each tool takes a bearer token the server resolves to a fixed user, org, and role. The caller never asserts its own org or role — only presents a token. In a production MCP server this identity would come from the transport / OAuth layer; the lab passes it per call so it stays a single process and the authorization logic is explicit and easy to read.


Environment variables

Each scenario is controlled by an independent env var (all default to "vuln"):

Var

Controls

Vuln behaviour

Fixed behaviour

LAB_MODE / LAB_S1

S1 — note_delete

Cross-tenant delete succeeds

requireOrgAccess() blocks it

LAB_S2

S2 — note_search

org_id param overrides session scope

org_id ignored; session scope always used

LAB_S3

S3 — note_batch_get

All resolved notes returned regardless of org

Notes filtered to session.orgId

LAB_S4

S4 — note_export

org_id="*"/"all" dumps all tenants

org_id ignored; own org only

LAB_S5

S5 — note_admin_get

No role check; any token reaches cross-org lookup

requireAdminRole() blocks non-admins

LAB_S6

S6 — note_create_in_org

org_id param trusted as write target

org_id ignored; note created in session's own org

LAB_S7

S7 — note_get_by_query

tenant key omitted from the query filter; any org's id resolves

orgId bound into the same filter

LAB_S8

S8 — note:// resource

orgId URI path segment trusted as scope

URI segment ignored; session's own org used

LAB_S9

S9 — note_share_redeem

decoded grant's noteId trusted with no session re-check

requireOrgAccess() re-checked against the resolved note

LAB_S10

S10 — note_get_scoped

X-Org-Id header trusted as scope (HTTP)

Header ignored; session scope always used

LAB_S11

S11 — note_create_limited

X-Forwarded-For header as quota key (HTTP)

Quota keyed to session; header ignored

LAB_S12

S12 — note_batch_resolve

Batch returns all resolved notes regardless of org

Each resolved note filtered to session.orgId

LAB_S13

S13 — note_get_by_token_scope

Token's scope/aud claim trusted as scope

Scope claim ignored; session scope always used

Run all scenarios in their fixed state:

# Linux / macOS
LAB_S1=fixed LAB_S2=fixed LAB_S3=fixed LAB_S4=fixed LAB_S5=fixed LAB_S6=fixed LAB_S7=fixed LAB_S8=fixed LAB_S9=fixed LAB_S10=fixed LAB_S11=fixed LAB_S12=fixed LAB_S13=fixed npm start

# Windows PowerShell
$env:LAB_S1='fixed'; $env:LAB_S2='fixed'; $env:LAB_S3='fixed'; $env:LAB_S4='fixed'; $env:LAB_S5='fixed'; $env:LAB_S6='fixed'; $env:LAB_S7='fixed'; $env:LAB_S8='fixed'; $env:LAB_S9='fixed'; $env:LAB_S10='fixed'; $env:LAB_S11='fixed'; $env:LAB_S12='fixed'; $env:LAB_S13='fixed'; npm start

Isolate one scenario (e.g. test only S2):

LAB_S2=vuln LAB_S1=fixed LAB_S3=fixed LAB_S4=fixed LAB_S5=fixed LAB_S6=fixed LAB_S7=fixed LAB_S8=fixed LAB_S9=fixed LAB_S10=fixed LAB_S11=fixed LAB_S12=fixed LAB_S13=fixed npm start

Hunt checklist — object-level authorization in MCP

Use this when auditing a real multi-tenant MCP server (one you own or are authorized to test). The bug class is "the server authenticates who you are but forgets to check whether you may touch this object":

  • Client-supplied scope trusted as authorization (→ S2). A tool takes an org_id / project_id / tenant_id argument and uses it to scope the query instead of checking it against the caller's membership.

  • Membership check decoupled from object resolution. The tool verifies the caller belongs to some org/project, but loads the object by a different id without confirming the object lives under that membership.

  • Inconsistent authorization — the single outlier (→ S1). Most object tools check; one or two do not. Read every tool that resolves an object by id. The forgotten one is usually a less-glamorous verb (delete, archive, export).

  • Reads guarded, mutations not. get/list are scoped but update/delete slipped through — or vice-versa.

  • Wildcard / sentinel short-circuit (→ S4). A special value ('all', '*', empty, 0, null) skips the scope filter entirely.

  • Role / token-type bypass (→ S5). An "admin" or "service" code path skips the per-object check.

  • List → get asymmetry (→ S3). list only returns your org's objects, so ids feel "private" — but get/batch-get accept any id and the ids are guessable or enumerable.

  • Create/update accepting a foreign parent (→ S6). create(parent_id=…) accepts a parent the caller is not a member of, injecting an object into another tenant.

  • Resource URI segment trusted as scope (→ S8). A resources/read handler binds a tenant/scope key straight from a URI template variable instead of the session — easy to miss because a review that only reads tools/* handlers never looks at resources/* at all.

  • A value round-tripped through the client trusted as authorization (→ S9). A tool decodes a token/grant/cursor produced by an earlier tool call and serves the object it names, with no re-check against the current session — "an earlier tool already authorized this" is a client-side claim, not a server fact, and it is not fixed by making the value cryptographically signed if the redeeming tool never re-checks it.

The exploit primitive is always the same: authenticate as tenant B, call the suspect tool with an object or scope that belongs to tenant A, and see whether you get A's data (or mutate it). Confirm a fix the same way the PoC here does — two-way: the cross-tenant call must be blocked and the legitimate same-tenant call must still succeed.


Safety / scope

  • Vulnerable by design. Do not deploy this on a reachable network or use it as a starting point for real code. Run it locally for learning only.

  • Synthetic. All orgs, users, notes, and tokens are made up. There is no real data, no real target, and the PoC never makes a network request — it only spawns the local server process over stdio.

Contributing

The most useful contribution to a detection ruleset is a false positive — a rule that fires on correctly authorized code. A gate wider than the defect it targets gets switched off, and a switched-off rule protects nothing, so those are treated as real defects here. Misses are just as welcome; the rules catch 7 of the 13 scenarios against this lab's own source and detection/README.md says why.

There is an issue template for each. CONTRIBUTING.md has the fixture convention, the exact-count CI gate, and the multi-language rule trap that costs an afternoon if you meet it the hard way.

Participation is covered by the Code of Conduct. Security reports go through SECURITY.md — and please do not report the planted flaws; they are the point.

Citing this

CITATION.cff, or use GitHub's Cite this repository button.

License

MIT.

Available Tools

18 tools
note_admin_getA

Get any note by id, across organizations. Admin/support use only.

ParametersJSON Schema
NameRequiredDescriptionDefault
idYes
tokenYes

TDQS

A3.9/5.0
Behavior3/5

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

No annotations are present, so the description carries the full burden. It discloses the cross-organization admin behavior and access restriction, which are meaningful. However, it does not describe authentication consequences, permission bypass, or error/return behavior.

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 short sentences with no wasted words. The core action, scope, and access restriction are all delivered efficiently and front-loaded.

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

Completeness4/5

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

For a low-complexity tool with two required string parameters and no output schema, the description gives enough context to select and invoke it. It could be more complete by naming a regular alternative and explicitly stating expected returns, but the essential selection criteria are present.

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 compensate. It gives meaning to the 'id' parameter via 'by id', but leaves the 'token' parameter almost entirely implicit. Admin/support-only usage hints at token relevance, but no format, source, or role requirements are stated.

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 has a specific verb and resource: 'Get any note by id, across organizations.' It clearly distinguishes itself from sibling tools like note_get and note_get_scoped by emphasizing cross-organization scope and admin/support-only access.

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

Usage Guidelines4/5

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

'Admin/support use only' provides a clear who-can-use exclusion, and 'across organizations' clarifies when this tool is appropriate. It does not explicitly name an alternative like note_get for normal scoped access, so it stops short of full alternative routing.

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

note_batch_getA

Fetch multiple notes by id in a single call (up to 20 ids).

ParametersJSON Schema
NameRequiredDescriptionDefault
idsYes
tokenYes

TDQS

A3.7/5.0
Behavior3/5

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

With no annotations, the description carries the behavioral burden. It conveys a read-only operation through 'Fetch' and explicitly states the one-call and 20-item limit. However, it does not disclose behavior for missing/invalid IDs, partial failure, authentication via token, or return ordering/shape. Basic transparency exists but is incomplete.

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?

A single sentence that front-loads the action, resource, and key constraint. Every word contributes: 'Fetch multiple notes by id', 'single call', and 'up to 20 ids'. No filler, no redundancy.

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?

The description gives a basic adequate overview for a simple batch get, but it lacks details that an agent would need given there is no output schema and no annotations: return format, order, behavior for missing IDs, and auth expectations. It also fails to distinguish from note_batch_resolve, making full context incomplete.

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

Parameters3/5

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

Schema property descriptions have 0% coverage, so the description must add meaning. It does clarify that 'ids' are note identifiers, which maps directly to the IDs array. However, it says nothing about the 'token' parameter, its role, or any format details beyond the schema. The compensation is only partial.

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 ('Fetch'), names the resource ('notes'), specifies the selection criterion ('by id'), and highlights the batch/single-call behavior with a hard cap (20 ids). This clearly distinguishes it from note_get (single note) and other siblings.

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 usage context is implied: fetch multiple notes by ID in one call. However, it does not explicitly name alternatives like note_get for single fetches or note_batch_resolve, nor does it say when not to use this tool. The sibling note_batch_resolve creates ambiguity that the description does not address.

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

note_batch_resolveC

Fetch multiple notes by id in a single batch call (up to 50 ids). Returns all requested notes.

ParametersJSON Schema
NameRequiredDescriptionDefault
idsYes
tokenYes

TDQS

C2.9/5.0
Behavior2/5

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

No annotations are provided, so the description carries the full burden. It discloses the 50-id cap and that all requested notes are returned, but does not explain what happens with invalid or missing ids, whether results preserve request order, what token scope is required, or how errors are surfaced. For a batched fetch tool this is a noticable gap.

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 one sentence, front-loaded with the core action, and adds the important 50-id bound without padding. It is appropriately concise, though it sacrifices behavioral nuance.

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?

With no annotations, no output schema, and 0% schema coverage, the description is too thin to fully support correct invocation. It leaves out error handling, the meaning of token, ordering behavior, and relationship to note_batch_get, making the tool incomplete for an agent encountering it among many note tools.

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 compensate. It explains that ids are note identifiers and that up to 50 can be provided, but it completely omits the meaning of the required token parameter, leaving the agent without enough information to construct a correct call.

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 clearly states the verb (fetch), the resource (multiple notes by id), and the batching behavior, making the basic purpose easy to grasp. However, it does not distinguish itself from the sibling note_batch_get, which appears to cover the same operation, so it falls short of a 5.

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 'in a single batch call' implies this tool should be used when retrieving multiple notes at once, so usage context is partially conveyed. But it provides no explicit guidance on when to prefer this over note_get or note_batch_get, and no exclusions are stated.

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

note_createC

Create a note in your organization.

ParametersJSON Schema
NameRequiredDescriptionDefault
bodyNo
titleYes
tokenYes

TDQS

C2.6/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 behavioral disclosure burden, but it only restates the mutation. It does not mention auth requirements, what the token represents, side effects, permissions, or failure behavior.

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 efficient sentence with the action and scope front-loaded and no filler. Brevity is good, though it sacrifices useful guidance.

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?

Given the many sibling note creation tools and no annotations or output schema, the description is too thin for confident tool selection and invocation. Token semantics and differentiation from sibling tools are missing.

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

Parameters1/5

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

Schema description coverage is 0% and the description adds no meaning for body, title, or token. The required token parameter is completely unexplained, leaving an agent without enough information to populate parameters confidently.

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 clearly states the action ('Create') and the resource ('a note') with an organizational scope, so the basic purpose is evident. However, it does not distinguish note_create from siblings like note_create_in_org or note_create_limited.

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?

There is no guidance on when to use this tool versus the many sibling creation variants, nor any exclusions or prerequisites. 'In your organization' hints at scope but does not tell an agent when to prefer note_create over alternatives.

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

note_create_in_orgA

Create a note inside a specific organization (cross-team collaboration). The org_id parameter targets the destination org.

ParametersJSON Schema
NameRequiredDescriptionDefault
bodyNo
titleYes
tokenYes
org_idNo

TDQS

A3.5/5.0
Behavior2/5

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

No annotations are present, so the description carries the full burden of behavioral disclosure. It reveals only that the tool creates a note and that org_id targets the destination org; it does not disclose permission requirements, whether org_id is mandatory, side effects, or response/error behavior.

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 tight sentences with the core operation and scope first, followed by the critical parameter clarification. Every sentence earns its place and no filler is present.

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?

For a mutation tool with no annotations and no output schema, this is thin. It omits return value, error behavior, required auth, and how this tool relates to note_create or note_create_limited, leaving an agent without enough context to invoke it confidently beyond simple cases.

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 compensate. It adds meaning only for org_id ('targets the destination org'); token, title, and body are left to inference, and the description does not resolve whether org_id is required even though the schema marks it optional.

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?

States a specific action—create a note—and a precise scope: inside a specific organization, with cross-team collaboration intent. It also clarifies that org_id targets the destination org, distinguishing this from generic note_create siblings even without naming them.

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

Usage Guidelines4/5

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

The description gives a clear context ('inside a specific organization', 'cross-team collaboration') that tells an agent when this tool is appropriate. It does not explicitly name note_create as the alternative or exclude cases where organization targeting is not needed, so it stops one step short of full routing guidance.

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

note_create_limitedA

Create a note with a per-client quota (max 3 notes). The quota is tracked by client identity.

ParametersJSON Schema
NameRequiredDescriptionDefault
bodyNo
titleYes
tokenYes

TDQS

A3.5/5.0
Behavior3/5

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

With no annotations, the description carries the behavioral burden. It usefully discloses the quota limit (max 3) and that tracking is per client identity. However, it does not explain what happens when the quota is exceeded, authorization requirements, or the creation outcome, leaving meaningful behavioral gaps.

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 compact, front-loaded, and each sentence earns its place: the first defines the action and limits, the second clarifies how the limit is tracked. There is no redundant or filler content.

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?

Despite a small parameter set, the lack of annotations, zero schema descriptions, no output schema, and close sibling tools make the description insufficient. An agent cannot confidently determine what 'token' means, how to identify the client, or what error/response to expect when the quota is reached.

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 needed to compensate for the three parameters. It does not mention token, title, or body by name, and 'client identity' is only an oblique reference to what is likely the token. The description adds little beyond the generic act of creating a note.

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 states a specific action and resource: 'Create a note'. It also distinguishes this tool from sibling note_create by naming the per-client quota constraint (max 3 notes), making the tool's specialized role clear.

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 quota and client-identity tracking imply this tool is for creating notes under a client-scoped limit, but there is no explicit when-to-use or when-not-to-use guidance, and no alternative sibling is named. The usage context is only implied, not stated.

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

note_deleteC

Delete a note by id.

ParametersJSON Schema
NameRequiredDescriptionDefault
idYes
tokenYes

TDQS

C2.9/5.0
Behavior2/5

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

No annotations are provided, so the description carries the full burden of behavioral disclosure. It states that a note is deleted but does not mention whether deletion is permanent, whether it requires specific permissions, if it can be undone, or what side effects occur. For a destructive operation, this transparency is insufficient.

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 short sentence with no fluff. It is front-loaded and easy to parse, though it sacrifices informative detail for brevity.

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?

With no annotations, no output schema, and two required parameters, the description is too minimal. It omits critical context about authorization, permanence, failure behavior, and the meaning of the token parameter, leaving the agent under-equipped to invoke the tool correctly.

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%, and the description only clarifies the 'id' parameter indirectly by saying deletion is 'by id.' The 'token' parameter is not explained at all, leaving the agent without guidance on what token is expected or how it is used.

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 ('Delete') with a clear resource ('a note') and identifying key ('by id'). It is immediately distinguishable from sibling tools like note_get, note_update, and note_create.

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?

The description gives no guidance on when to use this tool versus alternatives, such as when to delete versus update, or whether deletion is scoped to certain note types. The user must infer usage solely from the tool name.

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

note_exportB

Export all notes for an organization. Pass org_id='*' for a global export (admin only).

ParametersJSON Schema
NameRequiredDescriptionDefault
tokenYes
org_idNo

TDQS

B3/5.0
Behavior2/5

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

With no annotations provided, the description carries the full burden of behavioral disclosure. It only mentions the admin-only restriction for the wildcard org_id; it does not reveal whether the operation is read-only, returns a downloadable file, triggers a side effect, or produces a large payload, and it offers no information about rate limits or failure modes.

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 short sentences with no fluff: the core purpose is front-loaded, and the admin-only caveat is a natural follow-up. Every word earns its place.

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?

For a tool with no annotations and no output schema, this description is incomplete. The required token parameter is undocumented, the export output format is entirely unspecified, and the behavior when org_id is omitted is not addressed. The only complete context is the wildcard global-export path.

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 compensate for both parameters. It explains org_id's special '*' value and the admin constraint, but it never mentions token—the only required parameter—or even what a normal org_id should look like. This leaves a significant gap for the required parameter.

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 uses a specific verb ('export') and a clear resource ('all notes for an organization'), and it adds the wildcard behavior for global exports. However, it does not explicitly contrast itself with sibling tools like note_list or note_get, so the agent must infer the distinction from the word 'export' alone.

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?

It provides a clear context for org-scoped exports and flags global export as admin-only, which gives some usage boundary. But it does not state when to use this tool over alternatives like note_list or note_search, nor does it explain what happens when org_id is omitted.

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

note_getC

Get one note by id.

ParametersJSON Schema
NameRequiredDescriptionDefault
idYes
tokenYes

TDQS

C2.4/5.0
Behavior2/5

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

No annotations are provided, so the description carries the full burden of behavioral disclosure. It only says 'Get one note by id' and does not mention whether the operation is read-only, what permissions or token scopes are required, whether errors are thrown for missing notes, or what the response contains.

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

Conciseness3/5

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

The description is short and front-loaded with the core action, which is good. However, it is under-specified even as a minimal viable definition; a single terse sentence does not provide enough surrounding structure or context to be genuinely helpful.

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?

For a tool with no annotations, no output schema, and two required parameters that are completely undocumented, the description is not complete enough. It fails to explain token usage, return values, error behavior, or how this tool differs from its many similarly named siblings.

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

Parameters1/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 compensate for the undocumented parameters. It does not mention 'token' at all and only indirectly implies 'id' through 'by id.' The agent receives no added meaning about token purpose, id format, or relationships between the parameters.

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 clearly states the verb ('Get'), the resource ('one note'), and the selection criterion ('by id'). This distinguishes it from tools like note_list and note_batch_get, though it does not explicitly name those alternatives.

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?

The description gives no guidance on when to use this tool versus closely related siblings such as note_get_by_query, note_get_scoped, note_batch_get, or note_search. The intended use is only implicitly 'when you need a single note by its id,' but no exclusions or alternatives are stated.

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

note_get_by_queryC

Fetch a single note by id, resolved through a filtered store query.

ParametersJSON Schema
NameRequiredDescriptionDefault
idYes
tokenYes

TDQS

C2.4/5.0
Behavior2/5

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

No annotations are provided, so the description carries the full burden. It discloses that the operation fetches, implying read-only behavior, but does not explain what 'filtered store query' means, whether token scoping applies, what happens on missing IDs, or any side effects. This is too thin.

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

Conciseness3/5

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

The description is short and front-loaded, but 'resolved through a filtered store query' is vague and potentially confusing. Brevity is good, but here it sacrifices necessary clarity.

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?

For a tool with no annotations, no output schema, two undocumented required parameters, and many similar siblings, this description is incomplete. It lacks parameter explanations, behavior on not-found, and guidance on how this differs from note_get.

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

Parameters1/5

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

Schema description coverage is 0% and the description adds no meaning to the 'id' or 'token' parameters beyond their string types. The token is presumably an auth token, but that is never stated.

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 clearly states the operation ('Fetch a single note by id') and the resource ('note'). It is distinct from list/search tools, but does not differentiate it from the sibling note_get, which likely does the same thing directly.

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?

There is no guidance on when to use this tool versus note_get, note_search, note_get_scoped, or note_get_by_token_scope. The phrase 'resolved through a filtered store query' hints at a different mechanism, but no conditions or alternatives are stated.

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

note_get_by_token_scopeB

Get notes for the organization indicated by the token's scope claim. The token carries the intended scope.

ParametersJSON Schema
NameRequiredDescriptionDefault
scopeNoOptional scope override from token claims (aud/scope)
tokenYes

TDQS

B3.4/5.0
Behavior3/5

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

With no annotations, the description carries the burden of explaining behavior. It does disclose the key behavioral trait: notes are fetched read-only based on the token's scope claim rather than an explicit org parameter. It does not mention token validity, error behavior, pagination, or response shape, but this is minimally adequate for a read-only get operation.

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 brief and front-loaded, stating the primary purpose in the first sentence. The second sentence reinforces the token-based scoping behavior and is not filler, but it is somewhat redundant with the first sentence, preventing a perfect score.

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 two-parameter retrieval tool, the description provides enough information to understand the core call: pass a token and optionally override the scope. However, without an output schema or annotations, missing details such as return shape, error behavior, and how this differs from note_get_scoped leave moderate gaps.

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

Parameters3/5

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

Schema coverage is 50%: the optional scope parameter is documented, while the required token parameter has no schema description. The description adds meaning to token by explaining that it carries the intended scope, but it does not elaborate on the optional scope override semantics or token format, so it only partially compensates for the schema gap.

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 states a clear action ('Get notes') and resource ('organization indicated by the token's scope claim'), so an agent understands what the tool returns and how the target organization is determined. However, it does not differentiate it from closely named siblings like note_get_scoped or note_get_by_query, so it stops short of full clarity.

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 implies the intended use case: retrieve notes when the token's scope claim selects the organization. It does not explicitly say when to use this tool over note_get_scoped, note_get, or note_get_by_query, and it provides no exclusions or alternatives.

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

note_get_scopedB

List the caller's notes. Honors an X-Org-Id routing header set by the API gateway.

ParametersJSON Schema
NameRequiredDescriptionDefault
tokenYes

TDQS

B3.3/5.0
Behavior3/5

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

With no annotations provided, the description carries the burden of behavioral disclosure. It does reveal a meaningful behavior: the tool honors an X-Org-Id routing header set by the API gateway. However, it does not mention return format, pagination, or whether the org header changes which notes are visible.

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 two short sentences with no filler. The primary purpose is front-loaded, and the routing behavior is added concisely without redundancy or repetition of schema information.

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 one-parameter list tool, the description provides the core operation and a useful routing detail. However, with no annotations, no output schema, and many sibling tools, it would benefit from clarifying the token's role and how 'scoped' differs from note_list or note_get_by_token_scope.

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%, and the description never explains the required 'token' parameter. 'List the caller's notes' indirectly implies that token identifies the caller, but the agent is left to infer this rather than being told.

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 clearly states the operation ('List') and the resource ('the caller's notes'), giving an unambiguous purpose and a specific scope. It stops short of explicitly distinguishing itself from similar siblings like note_list or note_get_by_token_scope, so it misses a perfect score.

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 'List the caller's notes' implies the main use case: returning notes owned by the current caller. However, it does not state when to prefer this tool over note_list, note_get_by_token_scope, or note_search, nor does it give any exclusions or alternative routing guidance.

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

note_listC

List the notes in your organization.

ParametersJSON Schema
NameRequiredDescriptionDefault
tokenYes

TDQS

C2.6/5.0
Behavior2/5

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

With no annotations, the description must carry the behavioral disclosure burden, but it only states the scope ('in your organization') and the read-style verb 'List'. It does not mention whether this is read-only, what is returned, pagination/ordering, authorization requirements, or the meaning/role of the token.

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 clear, front-loaded sentence with no filler. It is concise, though not fully adequate for the missing parameter and usage semantics.

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?

Without annotations, output schema, or parameter descriptions, the description alone is too thin for safe invocation. It covers the basic purpose but lacks token semantics, return shape, pagination, and when-to-use context, making it incomplete against the rich sibling toolset.

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

Parameters1/5

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

Schema description coverage is 0%, and the only required parameter, token, is a bare string. The description never mentions token, so the agent cannot know whether it is an auth token, a pagination token, or an organization token. The description must compensate for the missing parameter documentation and does not.

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 uses a specific verb ('List') and resource ('notes in your organization'), making the tool's core purpose clear. It differentiates from note_get/note_create but does not explicitly separate list from the similarly broad note_search or note_get_by_query.

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 about when to use note_list instead of note_search, note_get_by_query, or note_export. There are no exclusions, prerequisites, or alternative conditions, so the agent must infer usage from the tool name and siblings.

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

note_share_prepareB

Prepare an opaque share grant for one of your own notes, redeemable via note_share_redeem.

ParametersJSON Schema
NameRequiredDescriptionDefault
idYes
tokenYes

TDQS

B3.4/5.0
Behavior3/5

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

With no annotations, the description must carry the behavioral burden. It discloses that the grant is opaque and redeemable via a specific sibling, which is useful context. However, it does not mention side effects, authentication, persistence, expiry, or what the caller receives, leaving gaps for a state-changing operation.

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 front-loaded sentence with no filler. It is concise and focused, though the brevity sacrifices paramameter guidance.

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?

Given no annotations, no output schema, and undocumented parameters, the description is not sufficient for an agent to invoke the tool correctly. It lacks parameter semantics, return behavior, and side-effect information.

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

Parameters1/5

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

Schema description coverage is 0%, and the description provides no explanation of the 'token' or 'id' parameters. The agent is left to guess which parameter identifies the note and which serves as an authorization or grant token.

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 ('Prepare') and resource ('opaque share grant') and clearly scopes it to 'one of your own notes.' It also names the redemption tool, which differentiates it from sibling note_share_redeem and other note tools.

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

Usage Guidelines4/5

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

The description clearly implies this tool is for creating a share grant, while note_share_redeem is for redeeming one. It also gives a usage restriction ('one of your own notes'). However, it does not explicitly state when to avoid this tool or compare it to other sharing-related siblings.

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

note_share_redeemA

Redeem an opaque share grant produced by note_share_prepare.

ParametersJSON Schema
NameRequiredDescriptionDefault
grantYes
tokenYes

TDQS

A3.5/5.0
Behavior2/5

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

No annotations are present, so the description carries the full burden of behavioral disclosure. It mentions that the grant is opaque and tied to note_share_prepare, but it does not say whether redemption consumes the grant, mutates access, requires specific auth, or what the response contains.

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?

One tightly written sentence with no filler. The verb and object come first, and every word contributes meaning, including the critical linkage to note_share_prepare.

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?

With no annotations and no output schema, the description leaves important operational context unexplained: what a successful redemption returns, whether the grant is single-use, and what error cases may arise. The link to note_share_prepare helps but does not cover these gaps.

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 compensate. It only echoes the concept of a 'share grant' without clarifying the distinct roles of 'token' and 'grant', their format, or how the two relate.

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 the specific verb 'redeem' with a clear resource, 'an opaque share grant produced by note_share_prepare.' This identifies both the action and the exact provenance of the input, distinguishing it from every sibling tool by naming its paired producer.

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

Usage Guidelines4/5

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

The phrase 'produced by note_share_prepare' gives clear contextual guidance: use this tool when you have a grant from that preparation step. It does not explicitly enumerate exclusions or alternative tools, so it stops short of full 5-level guidance.

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

note_updateA

Update a note's body by id.

ParametersJSON Schema
NameRequiredDescriptionDefault
idYes
bodyYes
tokenYes

TDQS

A3.6/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 states the core mutation but does not mention required authentication, overwrite behavior, error cases for missing or invalid ids, or whether the update is reversible. This leaves important behavioral context unstated.

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, front-loaded sentence with no wasted words. It conveys the operation, target, and identifier efficiently.

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?

For a three-parameter mutation with no annotations and no output schema, this description is too thin. It does not explain return values, error outcomes, authentication prerequisites, or when to prefer this tool over a sibling such as note_create or note_get.

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

Parameters3/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 add parameter meaning. It clarifies that 'id' selects the note and 'body' supplies the new content, but it says nothing about the required 'token' parameter, leaving one of three parameters unexplained.

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 and resource: 'Update a note's body by id.' It clearly identifies the operation, the target entity, and the selection method, and it distinguishes the tool from siblings like note_create, note_get, and note_delete.

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

Usage Guidelines4/5

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

The phrase 'Update a note's body by id' gives clear context: use this tool when an existing note's body needs to be changed. It does not explicitly name alternatives or exclusions, but the operation and target are clear enough to route an agent correctly.

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

whoamiA

Return the session (user + org) your token authenticates as.

ParametersJSON Schema
NameRequiredDescriptionDefault
tokenYes

TDQS

A4/5.0
Behavior3/5

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

No annotations are provided, so the description carries the behavioral-transparency burden. It correctly implies a read-only introspection and specifies what is returned, but it does not disclose behavior for invalid or expired tokens, nor does it explicitly state that no side effects occur.

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 sentence with no filler. The core scope ('session (user + org)') is front-loaded, and every word contributes meaning.

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

Completeness4/5

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

For a one-parameter tool with no output schema, the description covers both input semantics and return content. The only notable omission is error/edge-case behavior for invalid tokens, which is a minor gap for such a simple read-only operation.

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

Parameters3/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 compensate. It clarifies that the 'token' parameter is the caller's authentication token and the basis for the session lookup. However, it does not specify token format or how the token is expected to be supplied, leaving some ambiguity.

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 ('Return') and resource ('session (user + org)') tied to token authentication, making the tool's purpose unambiguous. It is clearly distinct from the sibling note_* tools, which all handle note operations.

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

Usage Guidelines4/5

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

The description gives clear context: use this when you need to know which user and org a token authenticates as. It does not explicitly discuss alternatives or exclusions, but no sibling tool overlaps with this identity-resolution task, so the missing exclusion is low-risk.

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.

  1. 18 tool updatesv3.10.0
    • Addednote_admin_get
    • Addednote_batch_get
    • Addednote_batch_resolve
    • Changednote_create1 field changed
      • removedInput schema / additionalProperties
        Removed value: -false
    • Addednote_create_in_org
    • Addednote_create_limited
    • Changednote_delete1 field changed
      • removedInput schema / additionalProperties
        Removed value: -false
    • Addednote_export
    • Changednote_get1 field changed
      • removedInput schema / additionalProperties
        Removed value: -false
    • Addednote_get_by_query
    • Addednote_get_by_token_scope
    • Addednote_get_scoped
    • Changednote_list1 field changed
      • removedInput schema / additionalProperties
        Removed value: -false
    • Changednote_search2 fields changed
      • removedInput schema / additionalProperties
        Removed value: -false
      • addedInput schema / properties / org_id
        Added value: +{
        +  "type": "string"
        +}
    • Addednote_share_prepare
    • Addednote_share_redeem
    • Changednote_update1 field changed
      • removedInput schema / additionalProperties
        Removed value: -false
    • Changedwhoami1 field changed
      • removedInput schema / additionalProperties
        Removed value: -false
  2. 7 tool updatesv1.0.0
    • First observednote_create
    • First observednote_delete
    • First observednote_get
    • First observednote_list
    • First observednote_search
    • First observednote_update
    • First observedwhoami

TDQS

B3/5.0

Scored across 18 tools

Disambiguation2/5

There are many overlapping read operations: note_get, note_get_by_query, note_get_scoped, note_get_by_token_scope, note_admin_get, and two batch getters. note_batch_resolve and note_batch_get especially are nearly indistinguishable, so an agent would struggle to pick the right tool for a simple fetch.

Naming Consistency4/5

Most tools follow a consistent note_<action> pattern, e.g. note_list, note_get, note_create, note_update, note_delete. Minor deviations like whoami and the inconsistent note_batch_resolve versus note_batch_get keep it from being a perfect 5.

Tool Count3/5

18 tools is above the comfortable 3-15 range and feels heavy for a notes/authz lab. Many read variants inflate the count when fewer, more distinct tools could express the same authorization scenarios.

Completeness4/5

The toolset covers the core note lifecycle: create, get, list, search, update, delete, export, plus authz-specific operations like admin access, org-scoped creation, token-scoped reads, and sharing. Minor gaps like share revocation and scoped update/delete variants exist, but core workflows are not dead-ended.

Maintenance

ActivityActive
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers