Repository navigation
MCP server: scoped tokens (read-only / read-write), and optionally OIDC-based RBAC #1657
Replies: 1 comment
|
Thanks for the kind words and thanks for the detailed write-up, the risk you describe with an unattended agent is real and the split you propose was a good basis. The next release adds POZNOTE_MCP_SCOPE with three levels. read exposes only the tools that change nothing (get, list, search, backlinks, revisions, share status, system info, settings). write adds the changes that can be undone: notes, tasks, reminders, folders, moves, attachments, moving a note to the trash and restoring from the trash or a revision. admin, the default, adds everything that cannot be undone or reaches the whole instance: emptying the trash, deleting from it, workspaces, backups, Git sync, app settings and public sharing. I moved one thing compared with your split: delete_note is in write, because it only moves the note to the trash and restore_note brings it back. Permanent deletion stays in admin. Public sharing is in admin too, since it is what takes data out of the instance. A tool above the configured level is not registered at all, so it is missing from tools/list and a call to it is refused, whatever the client is configured to do. An unknown value stops the server instead of falling back to admin. Every tool also carries the standard readOnlyHint and destructiveHint annotations now, for clients that filter or confirm on their side. With the shared token, a second MCP container with POZNOTE_MCP_SCOPE=read, its own port and its own POZNOTE_MCP_AUTH_TOKEN gives your morning-brief agent a read-only server. About user_id crossing profiles: that is true with the shared POZNOTE_MCP_AUTH_TOKEN, but Poznote already has per-caller authentication (POZNOTE_MCP_AUTH_PROVIDER, userinfo or introspection against your OIDC provider). Each client then presents its own token, which is mapped to its own profile, and a user_id naming another profile is refused. On top of that, the level can now come from the caller's token, which covers your point 3 on a single server. POZNOTE_MCP_SCOPE_MAP maps claim values to levels, for example poznote.read=read,poznote.write=write,poznote.admin=admin, read from the scope claim by default or from any list claim such as groups or roles with POZNOTE_MCP_SCOPE_CLAIM. A caller gets the highest level its token maps to, never above POZNOTE_MCP_SCOPE, and a caller whose token maps to no level is refused rather than given the server's level. It is not a full OAuth 2.1 resource server with discovery metadata, but it gives per-token RBAC from your identity provider without a separate permission system in Poznote. Both are documented in the "Tool scope" section of docs/MCP-SERVER.md. |
Uh oh!
There was an error while loading. Please reload this page.
Hi, and thanks for Poznote and its MCP server.
I connect two MCP clients to the same instance: an interactive assistant I supervise, and an autonomous agent that runs on a schedule and only needs to read my tasks for a morning brief. Today I cannot give them different permissions. Every client gets all tools, including destructive ones (
delete_note,empty_trash,restore_backup,update_app_setting), and the optionalPOZNOTE_MCP_AUTH_TOKENis a single shared secret with no scope. Since tools also accept auser_id, any client can reach any profile.For an unattended agent this is a real risk, because note content can contain text that misleads a weaker model into calling a write or delete tool. Client-side tool filtering helps, but a server-side limit is enforced no matter how the client is configured.
Proposal
POZNOTE_MCP_READ_ONLY=trueoption that registers only the read tools. Running two MCP containers with separate tokens would already cover my case.user_idcannot cross profiles:read: non-mutating tools only (get, list, search, backlinks, snapshots, share status, system info).write: read, plus reversible note-level changes (create and update notes, tasks, reminders, folders, moves, attachments, restoring from trash or snapshots).admin: everything, including permanent deletions (empty_trash,delete_trash_note), workspace management, backups, Git sync, app settings, public sharing, and any cross-profileuser_iduse.The exact split is of course open for discussion (for example whether sharing deserves its own scope). I realize 3 is a bigger change, so I would be happy with 1 or 2 alone. Thanks!
All reactions