filamental-mcp 0.2.0 → 0.2.2

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.
package/README.md CHANGED
@@ -23,7 +23,7 @@ The easiest way to connect is through the app:
23
23
  2. Click **Connect to Claude Desktop**
24
24
  3. Restart Claude Desktop
25
25
 
26
- Filamental resolves all paths automatically and writes the correct entry to `claude_desktop_config.json`.
26
+ Filamental resolves all paths automatically. The MCP follows whichever vault you have open — no restart needed when you switch worlds.
27
27
 
28
28
  ---
29
29
 
@@ -47,16 +47,14 @@ Then add to your `claude_desktop_config.json`:
47
47
  "command": "node",
48
48
  "args": [
49
49
  "--no-warnings",
50
- "/absolute/path/to/node_modules/filamental-mcp/dist/index.js",
51
- "--vault",
52
- "/absolute/path/to/your/vault"
50
+ "/absolute/path/to/node_modules/filamental-mcp/dist/index.js"
53
51
  ]
54
52
  }
55
53
  }
56
54
  }
57
55
  ```
58
56
 
59
- > Vault path must be absolute. Claude Desktop launches the server from a varying working directory, so relative paths are not reliable.
57
+ No `--vault` argument needed. The server reads the active vault from Filamental automatically and reconnects when you switch worlds. To pin to a specific vault (e.g. for testing), pass `--vault <absolute-path>` explicitly.
60
58
 
61
59
  ### Claude Code
62
60
 
package/dist/index.js CHANGED
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  // Filamental MCP Server — index.ts
3
- // Entry point: parse CLI args, resolve vault DB path, open DB, connect transport.
4
- import { existsSync } from 'fs';
3
+ // Entry point: resolve vault path (from --vault arg or active-vault file),
4
+ // open DB, connect transport. Re-connects automatically when vault changes.
5
+ import { existsSync, readFileSync, watchFile } from 'fs';
5
6
  import { homedir } from 'os';
6
7
  import { join, resolve } from 'path';
7
8
  import Database from 'better-sqlite3';
@@ -13,12 +14,32 @@ function parseArgs(argv) {
13
14
  for (let i = 0; i < argv.length - 1; i++) {
14
15
  if (argv[i].startsWith('--')) {
15
16
  result[argv[i].slice(2)] = argv[i + 1];
16
- i++; // consume the value
17
+ i++;
17
18
  }
18
19
  }
19
20
  return result;
20
21
  }
21
- // ── Vault hash (mirrors Rust vault_hash fn — FNV-1a 32-bit) ──────────────────
22
+ // ── App config dir (mirrors Tauri's app_config_dir for "com.filamental.app") ──
23
+ function appConfigDir() {
24
+ if (process.platform === 'win32') {
25
+ return join(process.env['APPDATA'] ?? homedir(), 'com.filamental.app');
26
+ }
27
+ else if (process.platform === 'darwin') {
28
+ return join(homedir(), 'Library', 'Application Support', 'com.filamental.app');
29
+ }
30
+ else {
31
+ return join(process.env['XDG_CONFIG_HOME'] ?? join(homedir(), '.config'), 'com.filamental.app');
32
+ }
33
+ }
34
+ // ── Read the active vault path written by Filamental on every vault open ──────
35
+ function readActiveVault() {
36
+ const filePath = join(appConfigDir(), 'active-vault');
37
+ if (!existsSync(filePath))
38
+ return null;
39
+ const content = readFileSync(filePath, 'utf-8').trim();
40
+ return content.length > 0 ? content : null;
41
+ }
42
+ // ── Vault hash (FNV-1a 32-bit — mirrors Rust vault_hash fn) ──────────────────
22
43
  function vaultHash(vaultPath) {
23
44
  let hash = 2_166_136_261;
24
45
  const bytes = Buffer.from(vaultPath, 'utf-8');
@@ -29,61 +50,86 @@ function vaultHash(vaultPath) {
29
50
  return `vault-${hash.toString(16).padStart(8, '0')}`;
30
51
  }
31
52
  // ── DB path resolution ────────────────────────────────────────────────────────
32
- //
33
- // Filamental stores its SQLite index in the OS app-config directory, NOT inside
34
- // the vault folder (so cloud-synced vaults don't re-index on every machine).
35
- // Path: <app_config_dir>/vaults/<vault-hash>/filamental.db
36
- //
37
- // If --db is supplied it overrides everything (useful for testing).
38
53
  function resolveDbPath(vaultPath, explicitDb) {
39
54
  if (explicitDb)
40
55
  return resolve(explicitDb);
41
- // Tauri's app_config_dir for "com.filamental.app"
42
- let appConfigDir;
43
- if (process.platform === 'win32') {
44
- appConfigDir = join(process.env['APPDATA'] ?? homedir(), 'com.filamental.app');
45
- }
46
- else if (process.platform === 'darwin') {
47
- appConfigDir = join(homedir(), 'Library', 'Application Support', 'com.filamental.app');
48
- }
49
- else {
50
- // Linux / XDG
51
- appConfigDir = join(process.env['XDG_CONFIG_HOME'] ?? join(homedir(), '.config'), 'com.filamental.app');
52
- }
53
- const appDbPath = join(appConfigDir, 'vaults', vaultHash(vaultPath), 'filamental.db');
56
+ const appDbPath = join(appConfigDir(), 'vaults', vaultHash(vaultPath), 'filamental.db');
54
57
  if (existsSync(appDbPath))
55
58
  return appDbPath;
56
- // Fallback: vault-local path (dev builds or future embedded mode)
57
59
  return join(vaultPath, '.filamental', 'filamental.db');
58
60
  }
61
+ let activeState = null;
62
+ function openVault(vaultPath, explicitDb) {
63
+ const dbPath = resolveDbPath(vaultPath, explicitDb);
64
+ if (!existsSync(dbPath)) {
65
+ throw new Error(`Filamental database not found at ${dbPath}.\n` +
66
+ `Open this vault in Filamental at least once so the SQLite index is initialised.`);
67
+ }
68
+ const db = new Database(dbPath);
69
+ return { vaultPath, db };
70
+ }
59
71
  // ── Main ──────────────────────────────────────────────────────────────────────
60
72
  async function main() {
61
73
  const args = parseArgs(process.argv.slice(2));
62
- const rawVaultArg = args['vault'];
63
- if (!rawVaultArg) {
64
- console.error('Error: --vault <path> is required');
65
- console.error('');
66
- console.error('Usage:');
67
- console.error(' node --no-warnings dist/index.js --vault /path/to/your/vault');
68
- console.error(' node --no-warnings dist/index.js --vault /path/to/vault --db /explicit/db/path');
69
- process.exit(1);
74
+ const explicitVault = args['vault'] ? resolve(args['vault']) : undefined;
75
+ const explicitDb = args['db'] ? resolve(args['db']) : undefined;
76
+ // Resolve the initial vault path: explicit arg → active-vault file
77
+ let initialVaultPath;
78
+ if (explicitVault) {
79
+ if (!existsSync(explicitVault)) {
80
+ console.error(`Error: vault path does not exist: ${explicitVault}`);
81
+ process.exit(1);
82
+ }
83
+ initialVaultPath = explicitVault;
70
84
  }
71
- const vaultPath = resolve(rawVaultArg);
72
- if (!existsSync(vaultPath)) {
73
- console.error(`Error: vault path does not exist: ${vaultPath}`);
74
- process.exit(1);
85
+ else {
86
+ const active = readActiveVault();
87
+ if (!active) {
88
+ console.error('Error: no vault path supplied and no active-vault file found.');
89
+ console.error('');
90
+ console.error('Either:');
91
+ console.error(' 1. Open a vault in Filamental (it writes the active-vault file automatically), or');
92
+ console.error(' 2. Pass --vault <path> explicitly.');
93
+ process.exit(1);
94
+ }
95
+ if (!existsSync(active)) {
96
+ console.error(`Error: active vault path does not exist: ${active}`);
97
+ process.exit(1);
98
+ }
99
+ initialVaultPath = active;
75
100
  }
76
- const dbPath = resolveDbPath(vaultPath, args['db']);
77
- if (!existsSync(dbPath)) {
78
- console.error(`Error: Filamental database not found at:`);
79
- console.error(` ${dbPath}`);
80
- console.error('');
81
- console.error('Make sure this vault has been opened in Filamental at least once');
82
- console.error('so that the SQLite index is initialised.');
101
+ // Open the initial DB connection
102
+ try {
103
+ activeState = openVault(initialVaultPath, explicitDb);
104
+ }
105
+ catch (err) {
106
+ console.error(`Error: ${err}`);
83
107
  process.exit(1);
84
108
  }
85
- const db = new Database(dbPath);
86
- const server = createServer(db, vaultPath);
109
+ // Watch the active-vault file for changes (user switched worlds in Filamental).
110
+ // Only active when --vault was NOT explicitly supplied — explicit arg is pinned.
111
+ if (!explicitVault) {
112
+ const activeVaultFile = join(appConfigDir(), 'active-vault');
113
+ watchFile(activeVaultFile, { interval: 2000 }, () => {
114
+ const newPath = readActiveVault();
115
+ if (!newPath || newPath === activeState?.vaultPath)
116
+ return;
117
+ if (!existsSync(newPath))
118
+ return;
119
+ try {
120
+ const next = openVault(newPath);
121
+ activeState?.db.close();
122
+ activeState = next;
123
+ // Notify via stderr so Claude Desktop can surface the switch if desired
124
+ console.error(`[filamental-mcp] vault switched → ${newPath}`);
125
+ }
126
+ catch {
127
+ // Keep the old connection alive if the new vault's DB isn't ready yet
128
+ }
129
+ });
130
+ }
131
+ // Create the MCP server — it reads activeState on each tool call via the getter
132
+ const server = createServer(() => activeState.db, () => activeState.vaultPath);
87
133
  const transport = new StdioServerTransport();
88
134
  await server.connect(transport);
89
135
  }
package/dist/server.js CHANGED
@@ -851,12 +851,15 @@ function toolDeleteEdge(db, args) {
851
851
  return { deleted: true, edge_id: edgeId };
852
852
  }
853
853
  // ── Server factory ────────────────────────────────────────────────────────────
854
- export function createServer(db, vaultPath) {
855
- const server = new Server({ name: 'filamental', version: '0.2.0' }, { capabilities: { tools: {} } });
854
+ export function createServer(getDb, getVaultPath) {
855
+ const server = new Server({ name: 'filamental', version: '0.2.1' }, { capabilities: { tools: {} } });
856
856
  server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
857
857
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
858
858
  const { name, arguments: args = {} } = request.params;
859
859
  const a = args;
860
+ // Resolve db and vaultPath fresh on every call so vault switches are transparent
861
+ const db = getDb();
862
+ const vaultPath = getVaultPath();
860
863
  try {
861
864
  let result;
862
865
  switch (name) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "filamental-mcp",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "MCP server exposing your Filamental vault to AI assistants (Claude, Claude Code, etc.)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -22,7 +22,8 @@
22
22
  "license": "MIT",
23
23
  "repository": {
24
24
  "type": "git",
25
- "url": "git+https://github.com/Scottnine/filamental.git"
25
+ "url": "git+https://github.com/Scottnine/filamental.git",
26
+ "directory": "mcp"
26
27
  },
27
28
  "engines": {
28
29
  "node": ">=22.0.0"
@@ -36,7 +37,7 @@
36
37
  "dev": "tsx src/index.ts"
37
38
  },
38
39
  "dependencies": {
39
- "@modelcontextprotocol/sdk": "latest",
40
+ "@modelcontextprotocol/sdk": "^1.29.0",
40
41
  "better-sqlite3": "^12.10.0",
41
42
  "js-yaml": "^4.2.0"
42
43
  },