tana-mcp-codemode 0.1.0

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.
@@ -0,0 +1,202 @@
1
+ /**
2
+ * Script History - SQLite Storage
3
+ *
4
+ * Persists executed scripts for debugging and replay.
5
+ * Uses bun:sqlite (built-in, no external dependency).
6
+ * Fire-and-forget saves to avoid blocking execution.
7
+ *
8
+ * Configure database location via TANA_HISTORY_PATH env var.
9
+ */
10
+
11
+ import { Database } from "bun:sqlite";
12
+ import { homedir } from "os";
13
+ import { join, dirname } from "path";
14
+ import { mkdirSync, existsSync } from "fs";
15
+ import type { ScriptRun } from "../types";
16
+
17
+ let db: Database | null = null;
18
+
19
+ function getDbPath(): string {
20
+ // Allow custom path via env var
21
+ if (process.env.TANA_HISTORY_PATH) {
22
+ const customPath = process.env.TANA_HISTORY_PATH;
23
+ const dir = dirname(customPath);
24
+ if (!existsSync(dir)) {
25
+ mkdirSync(dir, { recursive: true });
26
+ }
27
+ return customPath;
28
+ }
29
+
30
+ // Default platform-specific paths
31
+ const platform = process.platform;
32
+ let baseDir: string;
33
+
34
+ if (platform === "darwin") {
35
+ baseDir = join(homedir(), "Library", "Application Support", "tana-mcp");
36
+ } else if (platform === "win32") {
37
+ baseDir = join(process.env.APPDATA || homedir(), "tana-mcp");
38
+ } else {
39
+ baseDir = join(homedir(), ".local", "share", "tana-mcp");
40
+ }
41
+
42
+ if (!existsSync(baseDir)) {
43
+ mkdirSync(baseDir, { recursive: true });
44
+ }
45
+
46
+ return join(baseDir, "history.db");
47
+ }
48
+
49
+ function migrateDb(database: Database): void {
50
+ // Get existing columns
51
+ const columns = database
52
+ .prepare("PRAGMA table_info(script_runs)")
53
+ .all() as { name: string }[];
54
+ const columnNames = new Set(columns.map((c) => c.name));
55
+
56
+ // Add missing columns (SQLite doesn't support multiple ADD COLUMN in one statement)
57
+ const newColumns = [
58
+ { name: "input", type: "TEXT" },
59
+ { name: "api_calls", type: "TEXT" },
60
+ { name: "node_ids_affected", type: "TEXT" },
61
+ { name: "workspace_id", type: "TEXT" },
62
+ ];
63
+
64
+ for (const col of newColumns) {
65
+ if (!columnNames.has(col.name)) {
66
+ database.run(`ALTER TABLE script_runs ADD COLUMN ${col.name} ${col.type}`);
67
+ }
68
+ }
69
+ }
70
+
71
+ export function initDb(): Database {
72
+ if (db) return db;
73
+
74
+ const dbPath = getDbPath();
75
+ db = new Database(dbPath, { create: true });
76
+
77
+ db.run(`
78
+ CREATE TABLE IF NOT EXISTS script_runs (
79
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
80
+ timestamp INTEGER NOT NULL,
81
+ script TEXT NOT NULL,
82
+ success INTEGER NOT NULL,
83
+ output TEXT NOT NULL,
84
+ error TEXT,
85
+ duration_ms INTEGER NOT NULL,
86
+ session_id TEXT,
87
+ input TEXT,
88
+ api_calls TEXT,
89
+ node_ids_affected TEXT,
90
+ workspace_id TEXT
91
+ )
92
+ `);
93
+
94
+ // Migrate existing databases
95
+ migrateDb(db);
96
+
97
+ db.run(`
98
+ CREATE INDEX IF NOT EXISTS idx_script_runs_timestamp
99
+ ON script_runs(timestamp DESC)
100
+ `);
101
+
102
+ db.run(`
103
+ CREATE INDEX IF NOT EXISTS idx_script_runs_session
104
+ ON script_runs(session_id)
105
+ `);
106
+
107
+ db.run(`
108
+ CREATE INDEX IF NOT EXISTS idx_script_runs_workspace
109
+ ON script_runs(workspace_id)
110
+ `);
111
+
112
+ return db;
113
+ }
114
+
115
+ export interface SaveScriptRunOptions {
116
+ script: string;
117
+ success: boolean;
118
+ output: string;
119
+ error: string | null;
120
+ durationMs: number;
121
+ sessionId: string | null;
122
+ input?: string | null;
123
+ apiCalls?: string[] | null;
124
+ nodeIdsAffected?: string[] | null;
125
+ workspaceId?: string | null;
126
+ }
127
+
128
+ export function saveScriptRun(options: SaveScriptRunOptions): void {
129
+ // Fire-and-forget: don't await, don't block
130
+ try {
131
+ const database = initDb();
132
+ const stmt = database.prepare(`
133
+ INSERT INTO script_runs (
134
+ timestamp, script, success, output, error, duration_ms, session_id,
135
+ input, api_calls, node_ids_affected, workspace_id
136
+ )
137
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
138
+ `);
139
+ stmt.run(
140
+ Date.now(),
141
+ options.script,
142
+ options.success ? 1 : 0,
143
+ options.output,
144
+ options.error,
145
+ options.durationMs,
146
+ options.sessionId,
147
+ options.input ?? null,
148
+ options.apiCalls ? JSON.stringify(options.apiCalls) : null,
149
+ options.nodeIdsAffected ? JSON.stringify(options.nodeIdsAffected) : null,
150
+ options.workspaceId ?? null
151
+ );
152
+ } catch (e) {
153
+ // Silently ignore history save errors - don't disrupt the main flow
154
+ console.error("Failed to save script run to history:", e);
155
+ }
156
+ }
157
+
158
+ export function getRecentRuns(limit = 50, sessionId?: string): ScriptRun[] {
159
+ const database = initDb();
160
+
161
+ const baseQuery = `
162
+ SELECT
163
+ id, timestamp, script, success, output, error,
164
+ duration_ms as durationMs, session_id as sessionId,
165
+ input, api_calls as apiCalls, node_ids_affected as nodeIdsAffected,
166
+ workspace_id as workspaceId
167
+ FROM script_runs
168
+ `;
169
+
170
+ if (sessionId) {
171
+ const stmt = database.prepare(`
172
+ ${baseQuery}
173
+ WHERE session_id = ?
174
+ ORDER BY timestamp DESC
175
+ LIMIT ?
176
+ `);
177
+ return stmt.all(sessionId, limit) as ScriptRun[];
178
+ }
179
+
180
+ const stmt = database.prepare(`
181
+ ${baseQuery}
182
+ ORDER BY timestamp DESC
183
+ LIMIT ?
184
+ `);
185
+ return stmt.all(limit) as ScriptRun[];
186
+ }
187
+
188
+ export function cleanupOldRuns(daysOld = 30): number {
189
+ const database = initDb();
190
+ const cutoff = Date.now() - daysOld * 24 * 60 * 60 * 1000;
191
+
192
+ const stmt = database.prepare(`
193
+ DELETE FROM script_runs WHERE timestamp < ?
194
+ `);
195
+ const result = stmt.run(cutoff);
196
+ return result.changes;
197
+ }
198
+
199
+ /** Get the current database path (for debugging) */
200
+ export function getDbLocation(): string {
201
+ return getDbPath();
202
+ }
package/src/types.ts ADDED
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Internal Types (not from Tana API)
3
+ *
4
+ * These are types specific to the MCP server implementation,
5
+ * not derived from the Tana Local API OpenAPI spec.
6
+ *
7
+ * For API types, see ./api-types.ts (generated from api-1.json)
8
+ */
9
+
10
+ /** Result of sandbox code execution */
11
+ export interface SandboxResult {
12
+ success: boolean;
13
+ output: string;
14
+ error?: string;
15
+ durationMs: number;
16
+ }
17
+
18
+ /** Record of a script execution in history */
19
+ export interface ScriptRun {
20
+ id: number;
21
+ timestamp: number;
22
+ script: string;
23
+ success: boolean;
24
+ output: string;
25
+ error: string | null;
26
+ durationMs: number;
27
+ sessionId: string | null;
28
+ input: string | null;
29
+ apiCalls: string | null; // JSON array of method names called
30
+ nodeIdsAffected: string | null; // JSON array of node IDs
31
+ workspaceId: string | null;
32
+ }