Knowledge Base MCP - Permissions

2 min read

kb-mcp has no access of its own. It authenticates the caller against Zitadel, then passes that identity to the Unique API on every call. The Unique API enforces it. A search, content_tree, content_metadata, or read_file result therefore only ever contains knowledge-base content the calling user could already open in the web app.

This is why kb-mcp requests no knowledge-base scopes: there are none to request. It holds no service-wide credential that could read another user's content.

Zitadel Scopes

kb-mcp requests three scopes, all identity-only.

Scope

Purpose

openid

Standard OIDC subject claim

profile

Basic profile claims

urn:zitadel:iam:user:resourceowner

Resolves the user's owning organisation, which becomes the company id

mcp:* scopes are advertised on the OAuth metadata endpoint but never required. This is standard OAuth authorization-server metadata behavior (RFC 8414). Zitadel decides which of them to grant, and a scope it withholds is rejected at the middleware rather than silently honoured.

How a Call Is Scoped

Identity is resolved per call from the OIDC session, never from a tool argument. An MCP client cannot ask kb-mcp to act as somebody else.

On top of that identity, four filters combine with AND before the query reaches the Unique API, each of them optional:

embedded_749689e4f399cda9c3cb68399101bfd6.png

Because the combination is an AND, an LLM can't widen its own reach by supplying a permissive filter, and it can't reach outside the user's own permissions in the first place: those are enforced upstream by the Unique API, not by these filters.

content_metadata is the one exception: it takes folder_ids/folder_paths but no caller metadata_filter, only the admin one. It exists to discover what a filter could say, not to apply one.

An empty result is ambiguous

A search returning nothing may mean the user has no matching content, or that an admin metadata_filter excluded it. content_tree and search return a hint suggesting the caller drop its own metadata_filter and retry, which distinguishes the two cases.

Per-User Isolation

content_tree and content_metadata share one cache, keyed on (company_id, user_id, folder scope). Two users never share a cache entry, and the cache is per pod. It is not a shared store.

  • Architecture - components and the authentication flow

  • Flows - connection and tool-call sequences

  • Tools - what each tool accepts and returns

  • Configuration - environment variable reference

Last updated