filamental-mcp 0.2.1 → 0.2.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.
- package/README.md +13 -6
- package/dist/index.js +110 -45
- package/dist/server.js +9 -3
- package/package.json +1 -1
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
|
|
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
|
-
|
|
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
|
|
|
@@ -122,10 +120,19 @@ This server opens that SQLite index read-write. Read tools query it directly. Wr
|
|
|
122
120
|
|
|
123
121
|
---
|
|
124
122
|
|
|
123
|
+
## Compatibility
|
|
124
|
+
|
|
125
|
+
| filamental-mcp | Filamental app | DB schema |
|
|
126
|
+
|---|---|---|
|
|
127
|
+
| 0.2.x | 0.2.x | v5 |
|
|
128
|
+
|
|
129
|
+
If the versions are mismatched the server exits immediately with a clear message telling you which side to update.
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
125
133
|
## Known limitations
|
|
126
134
|
|
|
127
135
|
- The pre-built binary (`better-sqlite3`) is Windows x64 only. Other platforms require building from source.
|
|
128
|
-
- The server must be restarted if you change the active vault in Filamental.
|
|
129
136
|
- Auto-config via Filamental Settings has been tested on Windows. macOS path resolution is included but untested.
|
|
130
137
|
|
|
131
138
|
---
|
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:
|
|
4
|
-
|
|
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++;
|
|
17
|
+
i++;
|
|
17
18
|
}
|
|
18
19
|
}
|
|
19
20
|
return result;
|
|
20
21
|
}
|
|
21
|
-
// ──
|
|
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,105 @@ 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
|
-
|
|
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
|
+
// ── Schema compatibility ──────────────────────────────────────────────────────
|
|
62
|
+
// Must match SCHEMA_VERSION in src-tauri/src/index.rs
|
|
63
|
+
const SUPPORTED_SCHEMA_VERSION = 5;
|
|
64
|
+
function schemaVersionHint(detected) {
|
|
65
|
+
if (detected > SUPPORTED_SCHEMA_VERSION) {
|
|
66
|
+
return (`Version mismatch: your Filamental app uses schema v${detected} but this MCP only supports v${SUPPORTED_SCHEMA_VERSION}. ` +
|
|
67
|
+
`To fix:\n 1. Open a terminal\n 2. Run: npm update -g filamental-mcp\n 3. Restart Claude Desktop`);
|
|
68
|
+
}
|
|
69
|
+
return (`Version mismatch: this MCP expects schema v${SUPPORTED_SCHEMA_VERSION} but detected v${detected}. ` +
|
|
70
|
+
`To fix:\n 1. Download the latest Filamental app at https://filamental.app\n 2. Install it\n 3. Restart Claude Desktop`);
|
|
71
|
+
}
|
|
72
|
+
let activeState = null;
|
|
73
|
+
function openVault(vaultPath, explicitDb) {
|
|
74
|
+
const dbPath = resolveDbPath(vaultPath, explicitDb);
|
|
75
|
+
if (!existsSync(dbPath)) {
|
|
76
|
+
throw new Error(`Filamental database not found at ${dbPath}.\n` +
|
|
77
|
+
`Open this vault in Filamental at least once so the SQLite index is initialised.`);
|
|
78
|
+
}
|
|
79
|
+
const db = new Database(dbPath);
|
|
80
|
+
const row = db.prepare('PRAGMA user_version').get();
|
|
81
|
+
const schemaVersion = row.user_version;
|
|
82
|
+
let schemaHint = null;
|
|
83
|
+
if (schemaVersion !== SUPPORTED_SCHEMA_VERSION) {
|
|
84
|
+
schemaHint = schemaVersionHint(schemaVersion);
|
|
85
|
+
// Log to stderr only — user never sees this in normal operation
|
|
86
|
+
console.error(`[filamental-mcp] warning: ${schemaHint}`);
|
|
87
|
+
}
|
|
88
|
+
return { vaultPath, db, schemaHint };
|
|
89
|
+
}
|
|
59
90
|
// ── Main ──────────────────────────────────────────────────────────────────────
|
|
60
91
|
async function main() {
|
|
61
92
|
const args = parseArgs(process.argv.slice(2));
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
93
|
+
const explicitVault = args['vault'] ? resolve(args['vault']) : undefined;
|
|
94
|
+
const explicitDb = args['db'] ? resolve(args['db']) : undefined;
|
|
95
|
+
// Resolve the initial vault path: explicit arg → active-vault file
|
|
96
|
+
let initialVaultPath;
|
|
97
|
+
if (explicitVault) {
|
|
98
|
+
if (!existsSync(explicitVault)) {
|
|
99
|
+
console.error(`Error: vault path does not exist: ${explicitVault}`);
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
102
|
+
initialVaultPath = explicitVault;
|
|
70
103
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
104
|
+
else {
|
|
105
|
+
const active = readActiveVault();
|
|
106
|
+
if (!active) {
|
|
107
|
+
console.error('Error: no vault path supplied and no active-vault file found.');
|
|
108
|
+
console.error('');
|
|
109
|
+
console.error('Either:');
|
|
110
|
+
console.error(' 1. Open a vault in Filamental (it writes the active-vault file automatically), or');
|
|
111
|
+
console.error(' 2. Pass --vault <path> explicitly.');
|
|
112
|
+
process.exit(1);
|
|
113
|
+
}
|
|
114
|
+
if (!existsSync(active)) {
|
|
115
|
+
console.error(`Error: active vault path does not exist: ${active}`);
|
|
116
|
+
process.exit(1);
|
|
117
|
+
}
|
|
118
|
+
initialVaultPath = active;
|
|
75
119
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
console.error(
|
|
82
|
-
console.error('so that the SQLite index is initialised.');
|
|
120
|
+
// Open the initial DB connection
|
|
121
|
+
try {
|
|
122
|
+
activeState = openVault(initialVaultPath, explicitDb);
|
|
123
|
+
}
|
|
124
|
+
catch (err) {
|
|
125
|
+
console.error(`Error: ${err}`);
|
|
83
126
|
process.exit(1);
|
|
84
127
|
}
|
|
85
|
-
|
|
86
|
-
|
|
128
|
+
// Watch the active-vault file for changes (user switched worlds in Filamental).
|
|
129
|
+
// Only active when --vault was NOT explicitly supplied — explicit arg is pinned.
|
|
130
|
+
if (!explicitVault) {
|
|
131
|
+
const activeVaultFile = join(appConfigDir(), 'active-vault');
|
|
132
|
+
watchFile(activeVaultFile, { interval: 2000 }, () => {
|
|
133
|
+
const newPath = readActiveVault();
|
|
134
|
+
if (!newPath || newPath === activeState?.vaultPath)
|
|
135
|
+
return;
|
|
136
|
+
if (!existsSync(newPath))
|
|
137
|
+
return;
|
|
138
|
+
try {
|
|
139
|
+
const next = openVault(newPath);
|
|
140
|
+
activeState?.db.close();
|
|
141
|
+
activeState = next;
|
|
142
|
+
// Notify via stderr so Claude Desktop can surface the switch if desired
|
|
143
|
+
console.error(`[filamental-mcp] vault switched → ${newPath}`);
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
// Keep the old connection alive if the new vault's DB isn't ready yet
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
// Create the MCP server — it reads activeState on each tool call via the getter
|
|
151
|
+
const server = createServer(() => activeState.db, () => activeState.vaultPath, () => activeState.schemaHint);
|
|
87
152
|
const transport = new StdioServerTransport();
|
|
88
153
|
await server.connect(transport);
|
|
89
154
|
}
|
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(
|
|
855
|
-
const server = new Server({ name: 'filamental', version: '0.2.
|
|
854
|
+
export function createServer(getDb, getVaultPath, getSchemaHint) {
|
|
855
|
+
const server = new Server({ name: 'filamental', version: '0.2.2' }, { 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) {
|
|
@@ -906,7 +909,10 @@ export function createServer(db, vaultPath) {
|
|
|
906
909
|
catch (err) {
|
|
907
910
|
if (err instanceof McpError)
|
|
908
911
|
throw err;
|
|
909
|
-
|
|
912
|
+
// Append version hint only if there is one — keeps the message clean when versions match
|
|
913
|
+
const hint = getSchemaHint();
|
|
914
|
+
const base = String(err);
|
|
915
|
+
throw new McpError(ErrorCode.InternalError, hint ? `${base}\n\n${hint}` : base);
|
|
910
916
|
}
|
|
911
917
|
});
|
|
912
918
|
return server;
|