mcp-tool-acl.mjs
sha256:65ccb454656ea5acdea0a10e559b78bcde1eb6ff753ecc2911bc99d1c3d7cadd
feat(calendar): enforce agent context tiers in retrieval AP…
Human
minor
⚠ breaking
2 days ago
| 1 | /** |
| 2 | * Issue #1 Phase D2 — role-based tool access control for hosted MCP. |
| 3 | * Filters available tools based on user role (viewer, editor, admin, evaluator). |
| 4 | * Hosted prompts (Track B1–B3): default **viewer**; **`write-from-capture`** is **editor** (implies persisting notes). |
| 5 | */ |
| 6 | |
| 7 | const READ_TOOLS = new Set([ |
| 8 | 'search', |
| 9 | 'get_note', |
| 10 | 'get_note_outline', |
| 11 | 'get_document_tree', |
| 12 | 'get_metadata_facets', |
| 13 | 'get_section_source', |
| 14 | 'list_notes', |
| 15 | 'relate', |
| 16 | 'backlinks', |
| 17 | 'extract_tasks', |
| 18 | 'cluster', |
| 19 | 'tag_suggest', |
| 20 | 'summarize', |
| 21 | 'enrich', |
| 22 | ]); |
| 23 | |
| 24 | const WRITE_TOOLS = new Set([ |
| 25 | ...READ_TOOLS, |
| 26 | 'write', |
| 27 | 'hub_create_proposal', |
| 28 | 'capture', |
| 29 | 'transcribe', |
| 30 | 'vault_sync', |
| 31 | ]); |
| 32 | |
| 33 | const ADMIN_TOOLS = new Set([ |
| 34 | ...WRITE_TOOLS, |
| 35 | 'index', |
| 36 | 'export', |
| 37 | 'import', |
| 38 | 'import_url', |
| 39 | ]); |
| 40 | |
| 41 | const ROLE_TOOL_MAP = { |
| 42 | viewer: READ_TOOLS, |
| 43 | editor: WRITE_TOOLS, |
| 44 | /** Same tool surface as editor (incl. hub_create_proposal); bridge hosted-context may report role evaluator. */ |
| 45 | evaluator: WRITE_TOOLS, |
| 46 | admin: ADMIN_TOOLS, |
| 47 | }; |
| 48 | |
| 49 | /** Hosted MCP prompt IDs (Track B1 + B2 + B3 memory trio); each maps to canister / bridge routes like tools — no local vault files. */ |
| 50 | const HOSTED_PROMPT_IDS = new Set([ |
| 51 | 'daily-brief', |
| 52 | 'search-and-synthesize', |
| 53 | 'project-summary', |
| 54 | 'temporal-summary', |
| 55 | 'content-plan', |
| 56 | 'meeting-notes', |
| 57 | 'knowledge-gap', |
| 58 | 'causal-chain', |
| 59 | 'extract-entities', |
| 60 | 'write-from-capture', |
| 61 | 'memory-context', |
| 62 | 'memory-informed-search', |
| 63 | 'resume-session', |
| 64 | ]); |
| 65 | |
| 66 | /** Minimum role per prompt (`write-from-capture` implies vault write → editor). */ |
| 67 | const PROMPT_MIN_ROLE = /** @type {Record<string, 'viewer' | 'editor' | 'admin' | 'evaluator'>} */ ({ |
| 68 | 'daily-brief': 'viewer', |
| 69 | 'search-and-synthesize': 'viewer', |
| 70 | 'project-summary': 'viewer', |
| 71 | 'temporal-summary': 'viewer', |
| 72 | 'content-plan': 'viewer', |
| 73 | 'meeting-notes': 'viewer', |
| 74 | 'knowledge-gap': 'viewer', |
| 75 | 'causal-chain': 'viewer', |
| 76 | 'extract-entities': 'viewer', |
| 77 | 'write-from-capture': 'editor', |
| 78 | 'memory-context': 'viewer', |
| 79 | 'memory-informed-search': 'viewer', |
| 80 | 'resume-session': 'viewer', |
| 81 | }); |
| 82 | |
| 83 | /** evaluator ≥ editor for prompts; admin remains highest for future admin-only prompts. */ |
| 84 | const ROLE_RANK = { viewer: 0, editor: 1, evaluator: 2, admin: 3 }; |
| 85 | |
| 86 | /** |
| 87 | * Get the set of allowed tool names for a given role. |
| 88 | * @param {'viewer' | 'editor' | 'admin' | 'evaluator'} role |
| 89 | * @returns {Set<string>} |
| 90 | */ |
| 91 | export function allowedToolsForRole(role) { |
| 92 | return ROLE_TOOL_MAP[role] || READ_TOOLS; |
| 93 | } |
| 94 | |
| 95 | /** |
| 96 | * Check whether a specific tool is allowed for the given role. |
| 97 | * @param {string} toolName |
| 98 | * @param {'viewer' | 'editor' | 'admin' | 'evaluator'} role |
| 99 | * @returns {boolean} |
| 100 | */ |
| 101 | export function isToolAllowed(toolName, role) { |
| 102 | const allowed = allowedToolsForRole(role); |
| 103 | return allowed.has(toolName); |
| 104 | } |
| 105 | |
| 106 | /** |
| 107 | * Filter a list of tool definitions to only those allowed for the role. |
| 108 | * @param {{ name: string }[]} tools |
| 109 | * @param {'viewer' | 'editor' | 'admin' | 'evaluator'} role |
| 110 | * @returns {{ name: string }[]} |
| 111 | */ |
| 112 | export function filterToolsByRole(tools, role) { |
| 113 | const allowed = allowedToolsForRole(role); |
| 114 | return tools.filter((t) => allowed.has(t.name)); |
| 115 | } |
| 116 | |
| 117 | /** |
| 118 | * Prompt names exposed for this role (subset of {@link HOSTED_PROMPT_IDS} when min role not met). |
| 119 | * @param {'viewer' | 'editor' | 'admin' | 'evaluator'} role |
| 120 | * @returns {Set<string>} |
| 121 | */ |
| 122 | export function allowedPromptsForRole(role) { |
| 123 | const rank = ROLE_RANK[role] ?? 0; |
| 124 | const out = new Set(); |
| 125 | for (const name of HOSTED_PROMPT_IDS) { |
| 126 | const min = PROMPT_MIN_ROLE[name] ?? 'viewer'; |
| 127 | if (rank >= ROLE_RANK[min]) out.add(name); |
| 128 | } |
| 129 | return out; |
| 130 | } |
| 131 | |
| 132 | /** |
| 133 | * @param {string} promptName |
| 134 | * @param {'viewer' | 'editor' | 'admin' | 'evaluator'} role |
| 135 | */ |
| 136 | export function isPromptAllowed(promptName, role) { |
| 137 | if (!HOSTED_PROMPT_IDS.has(promptName)) return false; |
| 138 | const min = PROMPT_MIN_ROLE[promptName] ?? 'viewer'; |
| 139 | return (ROLE_RANK[role] ?? 0) >= ROLE_RANK[min]; |
| 140 | } |
File History
2 commits
sha256:65ccb454656ea5acdea0a10e559b78bcde1eb6ff753ecc2911bc99d1c3d7cadd
feat(calendar): enforce agent context tiers in retrieval AP…
Human
minor
⚠
2 days ago
sha256:9103f98c89257ed2b01c237cea895dabb3e85ea337dccb1161c175e4422355b6
docs: accept Calendar Events v0 spec with Phase 0 security …
Human
2 days ago