mcp-tool-acl.mjs
140 lines 3.8 KB
Raw
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