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.
- package/README.md +344 -0
- package/package.json +54 -0
- package/src/api/client.ts +243 -0
- package/src/api/tana.ts +332 -0
- package/src/api/types.ts +207 -0
- package/src/generated/api.d.ts +1425 -0
- package/src/index.ts +90 -0
- package/src/prompts.ts +81 -0
- package/src/sandbox/executor.ts +270 -0
- package/src/sandbox/stdin.ts +69 -0
- package/src/sandbox/workflow.ts +197 -0
- package/src/storage/history.ts +202 -0
- package/src/types.ts +32 -0
|
@@ -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
|
+
}
|