faf-mcp 2.1.2 โ†’ 2.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -40,6 +40,7 @@ const os = __importStar(require("os"));
40
40
  const pathModule = __importStar(require("path"));
41
41
  const fuzzy_detector_1 = require("../utils/fuzzy-detector");
42
42
  const faf_file_finder_js_1 = require("../utils/faf-file-finder.js");
43
+ const safe_path_1 = require("../utils/safe-path");
43
44
  const version_1 = require("../version");
44
45
  const path_resolver_1 = require("../utils/path-resolver");
45
46
  // v2.1.1: single-source scoring โ€” same path the championship handler uses.
@@ -66,12 +67,10 @@ class FafToolHandler {
66
67
  */
67
68
  getProjectPath(explicitPath) {
68
69
  if (explicitPath) {
69
- // Expand tilde
70
- const expandedPath = explicitPath.startsWith('~')
71
- ? pathModule.join(os.homedir(), explicitPath.slice(1))
72
- : explicitPath;
73
- // Resolve to absolute path
74
- const resolvedPath = pathModule.resolve(expandedPath);
70
+ // Confine the caller-supplied path. A passed *file* must be a .faf/.fafm
71
+ // context file; absolute/`..` escapes to secrets are refused. Throws
72
+ // PathConfinementError, caught centrally in callTool() (CWE-22/73/200).
73
+ const resolvedPath = (0, safe_path_1.confinePath)(explicitPath);
75
74
  // If it's a file path, get the directory
76
75
  const projectDir = fs.existsSync(resolvedPath) && fs.statSync(resolvedPath).isFile()
77
76
  ? pathModule.dirname(resolvedPath)
@@ -281,7 +280,7 @@ class FafToolHandler {
281
280
  },
282
281
  {
283
282
  name: 'faf_read',
284
- description: 'Read content from any file on the local filesystem',
283
+ description: 'Read a file within the project root (cwd / FAF_ALLOWED_ROOTS). Paths that escape the project are refused.',
285
284
  annotations: {
286
285
  title: 'Read .faf File',
287
286
  readOnlyHint: true,
@@ -301,7 +300,7 @@ class FafToolHandler {
301
300
  },
302
301
  {
303
302
  name: 'faf_write',
304
- description: 'Write content to any file on the local filesystem',
303
+ description: 'Write a file within the project root (cwd / FAF_ALLOWED_ROOTS). Paths that escape the project are refused.',
305
304
  annotations: {
306
305
  title: 'Write .faf File',
307
306
  readOnlyHint: false,
@@ -358,20 +357,9 @@ class FafToolHandler {
358
357
  additionalProperties: false
359
358
  }
360
359
  },
361
- {
362
- name: 'faf_chat',
363
- description: '๐Ÿ—ฃ๏ธ Natural language project.faf generation - Ask 6W questions (Who/What/Why/Where/When/How) to build complete human context ๐Ÿงกโšก๏ธ',
364
- annotations: {
365
- title: 'Chat about FAF',
366
- readOnlyHint: true,
367
- openWorldHint: false
368
- },
369
- inputSchema: {
370
- type: 'object',
371
- properties: {},
372
- additionalProperties: false
373
- }
374
- },
360
+ // faf_chat โ€” DEPRECATED, un-advertised. The host IS the chat โ€” a chat-shim
361
+ // tool is redundant. Dispatch keeps a deprecation stub (below). Fleet sweep โ€”
362
+ // mirrors grok-faf-mcp + claude-faf-mcp's retire.
375
363
  {
376
364
  name: 'faf_friday',
377
365
  description: '๐ŸŽ‰ Friday Features - Chrome Extension detection, fuzzy matching & more! ๐Ÿงกโšก๏ธ',
@@ -705,81 +693,91 @@ class FafToolHandler {
705
693
  if (!name || typeof name !== 'string') {
706
694
  throw new Error('Tool name must be a non-empty string');
707
695
  }
708
- switch (name) {
709
- case 'faf_status':
710
- return await this.handleFafStatus(args);
711
- case 'faf_score':
712
- return await this.handleFafScore(args);
713
- case 'faf_init':
714
- return await this.handleFafInit(args);
715
- case 'faf_trust':
716
- return await this.handleFafTrust(args);
717
- case 'faf_sync':
718
- return await this.handleFafSync(args);
719
- case 'faf_enhance':
720
- return await this.handleFafEnhance(args);
721
- case 'faf_bi_sync':
722
- return await this.handleFafBiSync(args);
723
- case 'faf_clear':
724
- return await this.handleFafClear(args);
725
- case 'faf_debug':
726
- return await this.handleFafDebug(args);
727
- case 'faf_about':
728
- return await this.handleFafAbout(args);
729
- case 'faf_what':
730
- return await this.handleFafWhat(args);
731
- case 'faf_read': {
732
- // Handle faf_read specially to set context when reading project.faf files
733
- const readResult = await fileHandler_1.fileHandlers.faf_read(args);
734
- // If reading a project.faf file, set the session context
735
- if (args?.path && (args.path.includes('project.faf') || args.path.endsWith('.faf'))) {
736
- this.getProjectPath(args.path);
696
+ try {
697
+ switch (name) {
698
+ case 'faf_status':
699
+ return await this.handleFafStatus(args);
700
+ case 'faf_score':
701
+ return await this.handleFafScore(args);
702
+ case 'faf_init':
703
+ return await this.handleFafInit(args);
704
+ case 'faf_trust':
705
+ return await this.handleFafTrust(args);
706
+ case 'faf_sync':
707
+ return await this.handleFafSync(args);
708
+ case 'faf_enhance':
709
+ return await this.handleFafEnhance(args);
710
+ case 'faf_bi_sync':
711
+ return await this.handleFafBiSync(args);
712
+ case 'faf_clear':
713
+ return await this.handleFafClear(args);
714
+ case 'faf_debug':
715
+ return await this.handleFafDebug(args);
716
+ case 'faf_about':
717
+ return await this.handleFafAbout(args);
718
+ case 'faf_what':
719
+ return await this.handleFafWhat(args);
720
+ case 'faf_read': {
721
+ // Handle faf_read specially to set context when reading project.faf files
722
+ const readResult = await fileHandler_1.fileHandlers.faf_read(args);
723
+ // If reading a project.faf file, set the session context
724
+ if (args?.path && (args.path.includes('project.faf') || args.path.endsWith('.faf'))) {
725
+ this.getProjectPath(args.path);
726
+ }
727
+ return readResult;
737
728
  }
738
- return readResult;
739
- }
740
- case 'faf_chat':
741
- return await this.handleFafChat(args);
742
- case 'faf_friday':
743
- return await this.handleFafFriday(args);
744
- case 'faf_write':
745
- return await fileHandler_1.fileHandlers.faf_write(args);
746
- case 'faf_list':
747
- return await this.handleFafList(args);
748
- case 'faf_guide':
749
- return await this.handleFafGuide(args);
750
- case 'faf_readme':
751
- return await this.handleFafReadme(args);
752
- case 'faf_human_add':
753
- return await this.handleFafHumanAdd(args);
754
- case 'faf_check':
755
- return await this.handleFafCheck(args);
756
- case 'faf_context':
757
- return await this.handleFafContext(args);
758
- case 'faf_go':
759
- return await this.handleFafGo(args);
760
- case 'faf_auto':
761
- return await this.handleFafAuto(args);
762
- case 'faf_dna':
763
- return await this.handleFafDna(args);
764
- case 'faf_formats':
765
- return await this.handleFafFormats(args);
766
- case 'faf_quick':
767
- return await this.handleFafQuick(args);
768
- case 'faf_doctor':
769
- return await this.handleFafDoctor(args);
770
- // v4.5.0 Interop tools
771
- case 'faf_agents':
772
- return await this.handleFafAgents(args);
773
- case 'faf_cursor':
774
- return await this.handleFafCursor(args);
775
- case 'faf_gemini':
776
- return await this.handleFafGemini(args);
777
- case 'faf_conductor':
778
- return await this.handleFafConductor(args);
779
- case 'faf_git':
780
- return await this.handleFafGit(args);
781
- default:
782
- throw new Error(`Unknown tool: ${name}`);
729
+ case 'faf_chat':
730
+ return await this.handleFafChat(args);
731
+ case 'faf_friday':
732
+ return await this.handleFafFriday(args);
733
+ case 'faf_write':
734
+ return await fileHandler_1.fileHandlers.faf_write(args);
735
+ case 'faf_list':
736
+ return await this.handleFafList(args);
737
+ case 'faf_guide':
738
+ return await this.handleFafGuide(args);
739
+ case 'faf_readme':
740
+ return await this.handleFafReadme(args);
741
+ case 'faf_human_add':
742
+ return await this.handleFafHumanAdd(args);
743
+ case 'faf_check':
744
+ return await this.handleFafCheck(args);
745
+ case 'faf_context':
746
+ return await this.handleFafContext(args);
747
+ case 'faf_go':
748
+ return await this.handleFafGo(args);
749
+ case 'faf_auto':
750
+ return await this.handleFafAuto(args);
751
+ case 'faf_dna':
752
+ return await this.handleFafDna(args);
753
+ case 'faf_formats':
754
+ return await this.handleFafFormats(args);
755
+ case 'faf_quick':
756
+ return await this.handleFafQuick(args);
757
+ case 'faf_doctor':
758
+ return await this.handleFafDoctor(args);
759
+ // v4.5.0 Interop tools
760
+ case 'faf_agents':
761
+ return await this.handleFafAgents(args);
762
+ case 'faf_cursor':
763
+ return await this.handleFafCursor(args);
764
+ case 'faf_gemini':
765
+ return await this.handleFafGemini(args);
766
+ case 'faf_conductor':
767
+ return await this.handleFafConductor(args);
768
+ case 'faf_git':
769
+ return await this.handleFafGit(args);
770
+ default:
771
+ throw new Error(`Unknown tool: ${name}`);
772
+ }
773
+ }
774
+ catch (err) {
775
+ // Central catch for path-confinement violations from getProjectPath()
776
+ // (CWE-22/73/200). Anything else propagates unchanged.
777
+ if (err instanceof safe_path_1.PathConfinementError) {
778
+ return { content: [{ type: 'text', text: `PATH DENIED\n\n${err.message}` }], isError: true };
779
+ }
780
+ throw err;
783
781
  }
784
782
  }
785
783
  async handleFafStatus(args) {
@@ -1307,37 +1305,17 @@ ${debugInfo.permissions.fafError ? ` FAF Error: ${debugInfo.permissions.fafErr
1307
1305
  }
1308
1306
  }
1309
1307
  async handleFafChat(_args) {
1310
- try {
1311
- const result = await this.engineAdapter.callEngine('chat');
1312
- if (!result.success) {
1313
- return {
1314
- content: [{
1315
- type: 'text',
1316
- text: `Error running faf chat: ${result.error || 'Unknown error'}`
1317
- }],
1318
- isError: true
1319
- };
1320
- }
1321
- // Format the response text
1322
- const responseText = typeof result.data === 'string'
1323
- ? result.data
1324
- : result.data?.output || JSON.stringify(result.data, null, 2);
1325
- return {
1326
- content: [{
1327
- type: 'text',
1328
- text: responseText
1329
- }]
1330
- };
1331
- }
1332
- catch (error) {
1333
- return {
1334
- content: [{
1335
- type: 'text',
1336
- text: `Error running faf chat: ${error instanceof Error ? error.message : String(error)}`
1337
- }],
1338
- isError: true
1339
- };
1340
- }
1308
+ // DEPRECATED: the host IS the chat โ€” a chat-shim MCP tool is redundant.
1309
+ // Un-advertised in listTools; this stub stays so anyone still wired gets a
1310
+ // clear signal, not a crash. The old body shelled `faf chat` via the engine
1311
+ // subprocess (a command faf-cli no longer ships) โ€” removing it ends that dead shell.
1312
+ return {
1313
+ content: [{
1314
+ type: 'text',
1315
+ text: 'faf_chat is retired โ€” the host is your chat, just talk here. ' +
1316
+ 'For FAF: faf_init / faf_score / faf_sync, or "ask questions" to build context.',
1317
+ }],
1318
+ };
1341
1319
  }
1342
1320
  async handleFafFriday(args) {
1343
1321
  const { test } = args || {};