( cwd: string, config: QodexConfig, )
| 21 | } |
| 22 | |
| 23 | export async function makeServerToolContext( |
| 24 | cwd: string, |
| 25 | config: QodexConfig, |
| 26 | ): Promise<ServerToolContext> { |
| 27 | const { getJournal } = await import('../../filesystem/transaction.js'); |
| 28 | const { PermissionEngine } = await import('../../security/permissions.js'); |
| 29 | const path = await import('path'); |
| 30 | |
| 31 | const sessionId = `mcp-server-${Date.now().toString(36)}`; |
| 32 | const journal = getJournal(); |
| 33 | const transaction = await journal.begin(sessionId); |
| 34 | const permissions = new PermissionEngine(config); |
| 35 | |
| 36 | // Rule-based auto-approval for the headless server. A server can't prompt a |
| 37 | // human, so instead of blanket-declining we consult config.mcpServer.autoApprove: |
| 38 | // approve if `all`, or if the operation touches an allowed path prefix, else "no". |
| 39 | const aa = (config as any).mcpServer?.autoApprove ?? {}; |
| 40 | const approvePaths: string[] = Array.isArray(aa.paths) ? aa.paths : []; |
| 41 | const approveAll = aa.all === true; |
| 42 | |
| 43 | const askUser = async (prompt: string): Promise<string> => { |
| 44 | if (approveAll) return 'yes'; |
| 45 | // If the prompt names a path under an approved prefix, allow it. |
| 46 | if (approvePaths.length > 0) { |
| 47 | const lower = prompt.toLowerCase(); |
| 48 | for (const p of approvePaths) { |
| 49 | const norm = p.replace(/\\/g, '/').toLowerCase(); |
| 50 | // Match either a relative prefix mention or an absolute path under cwd/prefix. |
| 51 | if (lower.includes(norm) || lower.includes(path.join(cwd, p).toLowerCase().replace(/\\/g, '/'))) { |
| 52 | return 'yes'; |
| 53 | } |
| 54 | } |
| 55 | } |
| 56 | return 'no'; // default-safe: decline anything not explicitly whitelisted |
| 57 | }; |
| 58 | |
| 59 | return { |
| 60 | cwd, |
| 61 | sessionId, |
| 62 | transaction, |
| 63 | permissions, |
| 64 | askUser, |
| 65 | emit: () => { /* no UI sink in server mode */ }, |
| 66 | currentTurn: 0, |
| 67 | _cleanup: async () => { |
| 68 | try { await transaction.commit?.(); } catch { /* best-effort */ } |
| 69 | }, |
| 70 | }; |
| 71 | } |
no test coverage detected