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 +29 -57
- package/dist/entry-node.js +1 -0
- package/package.json +20 -17
- package/src/cli.ts +122 -0
- package/src/prompts.ts +1 -0
package/README.md
CHANGED
|
@@ -1,57 +1,37 @@
|
|
|
1
1
|
# tana-mcp-codemode
|
|
2
2
|
|
|
3
|
-
Codemode MCP server for [Tana](https://tana.inc).
|
|
3
|
+
Codemode MCP server for [Tana](https://tana.inc). AI writes TypeScript that executes directly against the Tana Local API.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
### Binary (no runtime required)
|
|
5
|
+
**Requires:** Tana Desktop with Local API enabled + an API token (Settings → API).
|
|
8
6
|
|
|
9
|
-
|
|
7
|
+
## Install
|
|
10
8
|
|
|
11
|
-
|
|
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
|
-
|
|
21
|
-
./tana-mcp-codemode-darwin-arm64
|
|
13
|
+
npm install -g tana-mcp-codemode
|
|
22
14
|
```
|
|
23
15
|
|
|
24
|
-
|
|
16
|
+
**From source** (Bun): `git clone` → `bun install` → `bun run src/entry-bun.ts`
|
|
25
17
|
|
|
26
|
-
|
|
27
|
-
npm install -g tana-mcp-codemode
|
|
28
|
-
# or run without installing:
|
|
29
|
-
npx tana-mcp-codemode
|
|
30
|
-
```
|
|
18
|
+
## Setup
|
|
31
19
|
|
|
32
|
-
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
bun
|
|
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
|
-
##
|
|
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": "
|
|
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
|
|
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` |
|
|
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` |
|
|
73
|
-
| `TANA_SEARCH_WORKSPACES` |
|
|
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` |
|
|
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
|
-
|
|
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` |
|
|
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()` |
|
|
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
|
|
117
|
-
bun run
|
|
118
|
-
bun run
|
|
119
|
-
bun run
|
|
120
|
-
bun run
|
|
121
|
-
bun run
|
|
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
|
package/dist/entry-node.js
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
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.
|