tana-mcp-codemode 0.3.0 → 0.3.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 CHANGED
@@ -1,57 +1,37 @@
1
1
  # tana-mcp-codemode
2
2
 
3
- Codemode MCP server for [Tana](https://tana.inc). Instead of structured API calls, your AI writes TypeScript that executes directly against the Tana Local API.
3
+ Codemode MCP server for [Tana](https://tana.inc). AI writes TypeScript that executes directly against the Tana Local API.
4
4
 
5
- ## Install
6
-
7
- ### Binary (no runtime required)
5
+ **Requires:** Tana Desktop with Local API enabled + an API token (Settings → API).
8
6
 
9
- Download from [GitHub Releases](https://github.com/fabiogaliano/tana-mcp-codemode/releases) for your platform:
7
+ ## Install
10
8
 
11
- | Platform | File |
12
- |----------|------|
13
- | macOS ARM | `tana-mcp-codemode-darwin-arm64` |
14
- | macOS Intel | `tana-mcp-codemode-darwin-x64` |
15
- | Linux x64 | `tana-mcp-codemode-linux-x64` |
16
- | Linux ARM | `tana-mcp-codemode-linux-arm64` |
17
- | Windows | `tana-mcp-codemode-windows-x64.exe` |
9
+ **Binary** download from [GitHub Releases](https://github.com/fabiogaliano/tana-mcp-codemode/releases), `chmod +x`, run.
18
10
 
11
+ **npm** (Node.js ≥ 20):
19
12
  ```bash
20
- chmod +x tana-mcp-codemode-darwin-arm64
21
- ./tana-mcp-codemode-darwin-arm64
13
+ npm install -g tana-mcp-codemode
22
14
  ```
23
15
 
24
- ### npm (requires Node.js 20)
16
+ **From source** (Bun): `git clone` → `bun install` → `bun run src/entry-bun.ts`
25
17
 
26
- ```bash
27
- npm install -g tana-mcp-codemode
28
- # or run without installing:
29
- npx tana-mcp-codemode
30
- ```
18
+ ## Setup
31
19
 
32
- ### From source (requires Bun)
20
+ Run the interactive setup script — detects your installation, prompts for credentials, and configures Claude Code and/or Claude Desktop automatically:
33
21
 
34
22
  ```bash
35
- git clone https://github.com/fabiogaliano/tana-mcp-codemode
36
- cd tana-mcp-codemode
37
- bun install
38
- bun run src/entry-bun.ts
23
+ bun run setup
24
+ # or for team-shared config:
25
+ bun run setup --scope project
39
26
  ```
40
27
 
41
- ## Prerequisites
42
-
43
- - [Tana Desktop](https://tana.inc) with **Local API enabled** (Settings → Local API → Enable)
44
- - An API token from Tana (Settings → API → Generate Token)
45
-
46
- ## MCP Configuration
47
-
48
- Add to your MCP client config (e.g. `claude_desktop_config.json`):
28
+ ## Manual MCP Configuration
49
29
 
50
30
  ```json
51
31
  {
52
32
  "mcpServers": {
53
33
  "tana": {
54
- "command": "/path/to/tana-mcp-codemode-darwin-arm64",
34
+ "command": "tana-mcp-codemode",
55
35
  "env": {
56
36
  "TANA_API_TOKEN": "your-token-here",
57
37
  "MAIN_TANA_WORKSPACE": "My Workspace"
@@ -61,39 +41,33 @@ Add to your MCP client config (e.g. `claude_desktop_config.json`):
61
41
  }
62
42
  ```
63
43
 
64
- For npm install, use `"command": "tana-mcp-codemode"`. For source, use `"command": "bun"` with `"args": ["run", "/path/to/src/entry-bun.ts"]`.
44
+ For binary: set `command` to the binary path. For source: `"command": "bun", "args": ["run", "/path/to/src/entry-bun.ts"]`.
65
45
 
66
46
  ## Environment Variables
67
47
 
68
48
  | Variable | Default | Description |
69
49
  |----------|---------|-------------|
70
- | `TANA_API_TOKEN` | (required) | Bearer token from Tana Desktop |
50
+ | `TANA_API_TOKEN` | required | Bearer token from Tana Desktop |
71
51
  | `TANA_API_URL` | `http://127.0.0.1:8262` | Tana Local API URL |
72
- | `MAIN_TANA_WORKSPACE` | (none) | Default workspace name or ID |
73
- | `TANA_SEARCH_WORKSPACES` | (none) | Comma-separated workspaces for search scope |
52
+ | `MAIN_TANA_WORKSPACE` | | Default workspace name or ID |
53
+ | `TANA_SEARCH_WORKSPACES` | | Comma-separated workspaces for search scope |
74
54
  | `TANA_TIMEOUT` | `10000` | Request timeout in ms |
75
- | `TANA_HISTORY_PATH` | (platform default) | Custom SQLite history path |
55
+ | `TANA_HISTORY_PATH` | platform default | Custom SQLite history path |
76
56
 
77
57
  ## Usage Examples
78
58
 
79
- Search for nodes:
80
-
81
59
  ```typescript
60
+ // Search nodes
82
61
  const results = await tana.nodes.search("project ideas", {
83
62
  workspaceIds: [tana.workspace.id]
84
63
  });
85
- console.log(results.map(n => n.name));
86
- ```
87
-
88
- Import content with tags and fields:
89
64
 
90
- ```typescript
65
+ // Import with tags and fields
91
66
  await tana.import(tana.workspace.id, `
92
67
  %%tana%%
93
68
  - Meeting notes #meeting
94
69
  - Date:: [[2024-01-15]]
95
70
  - Attendees:: Alice, Bob
96
- - Summary:: Discussed Q1 roadmap
97
71
  `);
98
72
  ```
99
73
 
@@ -101,26 +75,24 @@ await tana.import(tana.workspace.id, `
101
75
 
102
76
  | Namespace | Methods |
103
77
  |-----------|---------|
104
- | `tana.workspace` | Pre-resolved default workspace |
78
+ | `tana.workspace` | pre-resolved default workspace |
105
79
  | `tana.workspaces` | `list()` |
106
80
  | `tana.nodes` | `search()`, `read()`, `getChildren()`, `edit()`, `move()`, `trash()`, `check()`, `uncheck()`, `open()` |
107
81
  | `tana.tags` | `listAll()`, `getSchema()`, `create()`, `modify()`, `addField()`, `setCheckbox()` |
108
82
  | `tana.fields` | `setOption()`, `setContent()`, `getFieldOptions()` |
109
83
  | `tana.calendar` | `getOrCreate()` |
110
- | `tana.import()` | Import Tana Paste content |
84
+ | `tana.import()` | import Tana Paste content |
111
85
  | `tana.health()` | API health check |
112
86
 
113
87
  ## Development
114
88
 
115
89
  ```bash
116
- bun install # Install deps
117
- bun run start # Start MCP server
118
- bun run dev # Dev mode with watch
119
- bun run test # Run tests
120
- bun run typecheck # Type check
121
- bun run build # Build Node.js dist (tsup)
122
- bun run build:binary # Build self-contained binary
123
- bun run debug # Debug UI at localhost:3333
90
+ bun run start # MCP server
91
+ bun run dev # watch mode
92
+ bun run test # tests
93
+ bun run build # Node.js dist (tsup)
94
+ bun run build:binary # self-contained binary
95
+ bun run debug # debug UI at :3333
124
96
  ```
125
97
 
126
98
  ## License
@@ -1215,6 +1215,7 @@ console.log() output becomes LLM context. Keep it compact:
1215
1215
 
1216
1216
  ## API Notes
1217
1217
 
1218
+ - **WARNING ON BULK UPDATES**: NEVER use tana.nodes.trash() followed by tana.import() to bulk-edit existing nodes. This destroys user data. Always update existing nodes individually using tana.fields.setContent or tana.nodes.edit.
1218
1219
  - search: no offset/pagination \u2014 use narrower queries, not repeated calls
1219
1220
  - getChildren: only endpoint with pagination (limit + offset)
1220
1221
  - Timeout is 10s. On timeout, try a different approach, not the same call.
package/package.json CHANGED
@@ -1,12 +1,29 @@
1
1
  {
2
2
  "name": "tana-mcp-codemode",
3
- "version": "0.3.0",
3
+ "version": "0.3.3",
4
4
  "description": "Codemode MCP server for Tana — AI writes TypeScript that executes against Tana Local API",
5
5
  "type": "module",
6
6
  "main": "dist/entry-node.js",
7
7
  "bin": {
8
8
  "tana-mcp-codemode": "dist/entry-node.js"
9
9
  },
10
+ "scripts": {
11
+ "setup": "bun run scripts/setup.ts",
12
+ "generate": "bun run scripts/generate.ts",
13
+ "list-endpoints": "bun run scripts/list-endpoints.ts",
14
+ "check-api": "bun run scripts/check-api-version.ts",
15
+ "start": "bun run --env-file=.env src/entry-bun.ts",
16
+ "dev": "bun run --env-file=.env --watch src/entry-bun.ts",
17
+ "debug": "bun run --env-file=.env src/debug-server.ts",
18
+ "build": "tsup",
19
+ "build:binary": "bun build --compile src/entry-bun.ts --outfile dist/tana-mcp-codemode",
20
+ "test": "bun test",
21
+ "typecheck": "bunx tsc --noEmit",
22
+ "prepublishOnly": "bun run typecheck && bun run build",
23
+ "release:patch": "npm version patch && git push && git push origin $(git describe --tags --abbrev=0)",
24
+ "release:minor": "npm version minor && git push && git push origin $(git describe --tags --abbrev=0)",
25
+ "release:major": "npm version major && git push && git push origin $(git describe --tags --abbrev=0)"
26
+ },
10
27
  "files": [
11
28
  "dist/",
12
29
  "src/",
@@ -42,19 +59,5 @@
42
59
  "tsup": "^8",
43
60
  "typescript": "^5.9.3"
44
61
  },
45
- "license": "MIT",
46
- "scripts": {
47
- "generate": "bun run scripts/generate.ts",
48
- "list-endpoints": "bun run scripts/list-endpoints.ts",
49
- "start": "bun run --env-file=.env src/entry-bun.ts",
50
- "dev": "bun run --env-file=.env --watch src/entry-bun.ts",
51
- "debug": "bun run --env-file=.env src/debug-server.ts",
52
- "build": "tsup",
53
- "build:binary": "bun build --compile src/entry-bun.ts --outfile dist/tana-mcp-codemode",
54
- "test": "bun test",
55
- "typecheck": "bunx tsc --noEmit",
56
- "release:patch": "npm version patch && npm publish",
57
- "release:minor": "npm version minor && npm publish",
58
- "release:major": "npm version major && npm publish"
59
- }
60
- }
62
+ "license": "MIT"
63
+ }
package/src/cli.ts ADDED
@@ -0,0 +1,122 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * CLI entry point — pipe TypeScript to stdin or pass a file path as argument.
4
+ *
5
+ * Usage:
6
+ * echo 'console.log(await tana.workspaces.list())' | bun run src/cli.ts
7
+ * bun run src/cli.ts script.ts
8
+ */
9
+
10
+ import { initCompat } from "./compat";
11
+ import { bunCompat } from "./compat/bun";
12
+ import { createClient } from "./api/client";
13
+ import { createTanaAPI } from "./api/tana";
14
+ import { executeSandbox } from "./sandbox/executor";
15
+ import { initDb, cleanupOldRuns } from "./storage/history";
16
+ import type { Workspace } from "./api/types";
17
+ import type { TanaClient } from "./api/client";
18
+
19
+ initCompat(bunCompat);
20
+
21
+ async function resolveWorkspace(client: TanaClient): Promise<Workspace | null> {
22
+ const envValue = process.env.MAIN_TANA_WORKSPACE?.trim();
23
+ if (!envValue) return null;
24
+
25
+ let workspaces: Workspace[];
26
+ try {
27
+ workspaces = await client.get<Workspace[]>("/workspaces");
28
+ } catch {
29
+ return null;
30
+ }
31
+
32
+ const byId = workspaces.find((w) => w.id === envValue);
33
+ if (byId) return byId;
34
+
35
+ const lowerEnv = envValue.toLowerCase();
36
+ return workspaces.find((w) => w.name.toLowerCase() === lowerEnv) ?? null;
37
+ }
38
+
39
+ async function resolveSearchWorkspaces(client: TanaClient): Promise<string[]> {
40
+ const envValue = process.env.TANA_SEARCH_WORKSPACES?.trim();
41
+ if (!envValue) return [];
42
+
43
+ const values = envValue.split(",").map((v) => v.trim()).filter(Boolean);
44
+ if (values.length === 0) return [];
45
+
46
+ let workspaces: Workspace[];
47
+ try {
48
+ workspaces = await client.get<Workspace[]>("/workspaces");
49
+ } catch {
50
+ return [];
51
+ }
52
+
53
+ const resolvedIds: string[] = [];
54
+ for (const value of values) {
55
+ const byId = workspaces.find((w) => w.id === value);
56
+ if (byId) { resolvedIds.push(byId.id); continue; }
57
+ const byName = workspaces.find((w) => w.name.toLowerCase() === value.toLowerCase());
58
+ if (byName) resolvedIds.push(byName.id);
59
+ }
60
+ return resolvedIds;
61
+ }
62
+
63
+ async function readCode(): Promise<string> {
64
+ const filePath = process.argv[2];
65
+
66
+ if (filePath) {
67
+ const file = Bun.file(filePath);
68
+ const exists = await file.exists();
69
+ if (!exists) {
70
+ console.error(`Error: file not found: ${filePath}`);
71
+ process.exit(1);
72
+ }
73
+ return file.text();
74
+ }
75
+
76
+ // Read from stdin
77
+ const chunks: Buffer[] = [];
78
+ for await (const chunk of process.stdin) {
79
+ chunks.push(chunk as Buffer);
80
+ }
81
+ const code = Buffer.concat(chunks).toString("utf8").trim();
82
+
83
+ if (!code) {
84
+ console.error("Usage: bun run src/cli.ts [file.ts]");
85
+ console.error(" echo '<code>' | bun run src/cli.ts");
86
+ process.exit(1);
87
+ }
88
+
89
+ return code;
90
+ }
91
+
92
+ async function main() {
93
+ initDb();
94
+ const cleaned = cleanupOldRuns(30);
95
+ if (cleaned > 0) {
96
+ process.stderr.write(`Cleaned up ${cleaned} old script runs\n`);
97
+ }
98
+
99
+ const code = await readCode();
100
+ const client = createClient();
101
+ const workspace = await resolveWorkspace(client);
102
+ const searchWorkspaceIds = await resolveSearchWorkspaces(client);
103
+ const tana = createTanaAPI(client, workspace, searchWorkspaceIds);
104
+
105
+ const result = await executeSandbox(code, tana);
106
+
107
+ if (result.output) {
108
+ process.stdout.write(result.output + "\n");
109
+ }
110
+
111
+ if (result.error) {
112
+ process.stderr.write(`Error: ${result.error}\n`);
113
+ }
114
+
115
+ process.stderr.write(`[${result.durationMs}ms]\n`);
116
+ process.exit(result.success ? 0 : 1);
117
+ }
118
+
119
+ main().catch((err) => {
120
+ console.error("Fatal:", err instanceof Error ? err.message : err);
121
+ process.exit(1);
122
+ });
package/src/prompts.ts CHANGED
@@ -74,6 +74,7 @@ console.log() output becomes LLM context. Keep it compact:
74
74
 
75
75
  ## API Notes
76
76
 
77
+ - **WARNING ON BULK UPDATES**: NEVER use tana.nodes.trash() followed by tana.import() to bulk-edit existing nodes. This destroys user data. Always update existing nodes individually using tana.fields.setContent or tana.nodes.edit.
77
78
  - search: no offset/pagination — use narrower queries, not repeated calls
78
79
  - getChildren: only endpoint with pagination (limit + offset)
79
80
  - Timeout is 10s. On timeout, try a different approach, not the same call.