mindpm 1.1.2 → 1.2.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 CHANGED
@@ -1,168 +1,168 @@
1
- # mindpm
2
-
3
- **Persistent project memory for LLMs.** Never re-explain your project again.
4
-
5
- mindpm is an MCP (Model Context Protocol) server that gives LLMs a SQLite-backed brain for your projects. It tracks tasks, decisions, architecture notes, and session context — so every new conversation picks up exactly where you left off.
6
-
7
- ## The Problem
8
-
9
- Every new LLM chat starts from zero:
10
-
11
- - *"Let me remind you about my project..."*
12
- - *"Last time we decided to use Redis for..."*
13
- - *"Where did we leave off?"*
14
-
15
- ## The Solution
16
-
17
- mindpm persists your project state in a local SQLite database. The LLM reads and writes to it via MCP tools. No chat history needed. No memory features needed.
18
-
19
- ```
20
- You: "What should I work on next?"
21
- LLM: [queries mindpm] "Last session you finished the auth refactor.
22
- You have 3 high-priority tasks: rate limiting, API docs, and
23
- the webhook retry bug. Rate limiting is unblocked — start there."
24
- ```
25
-
26
- ## What It Tracks
27
-
28
- - **Tasks** — status, priority, blockers, sub-tasks
29
- - **Decisions** — what was decided, why, what alternatives were rejected
30
- - **Notes** — architecture, bugs, ideas, research
31
- - **Context** — key-value pairs (tech stack, conventions, config)
32
- - **Sessions** — what was done, what's next
33
-
34
- ## Setup
35
-
36
- ### Install
37
-
38
- ```bash
39
- npm install -g mindpm
40
- ```
41
-
42
- Or run from source:
43
-
44
- ```bash
45
- git clone https://github.com/umitkavala/mindpm.git
46
- cd mindpm
47
- npm install
48
- npm run build
49
- ```
50
-
51
- ### Configure with Claude Code
52
-
53
- Add to your MCP config (`~/.claude/claude_desktop_config.json` or similar):
54
-
55
- ```json
56
- {
57
- "mcpServers": {
58
- "mindpm": {
59
- "command": "mindpm",
60
- "env": {
61
- "MINDPM_DB_PATH": "~/.mindpm/memory.db"
62
- }
63
- }
64
- }
65
- }
66
- ```
67
-
68
- If running from source, use the built file directly:
69
-
70
- ```json
71
- {
72
- "mcpServers": {
73
- "mindpm": {
74
- "command": "node",
75
- "args": ["/path/to/mindpm/dist/index.js"],
76
- "env": {
77
- "MINDPM_DB_PATH": "~/.mindpm/memory.db"
78
- }
79
- }
80
- }
81
- }
82
- ```
83
-
84
- ### Start Using
85
-
86
- That's it. The LLM now has access to mindpm tools. Just start talking about your projects.
87
-
88
- ## MCP Tools
89
-
90
- ### Projects
91
- | Tool | Description |
92
- |------|-------------|
93
- | `create_project` | Create a new project |
94
- | `list_projects` | List all projects |
95
- | `get_project_status` | Full project overview |
96
-
97
- ### Tasks
98
- | Tool | Description |
99
- |------|-------------|
100
- | `create_task` | Add a task |
101
- | `update_task` | Update status, priority, etc. |
102
- | `list_tasks` | List with filters |
103
- | `get_task` | Full task detail with sub-tasks and notes |
104
- | `get_next_tasks` | Smart: highest priority, unblocked |
105
-
106
- ### Decisions
107
- | Tool | Description |
108
- |------|-------------|
109
- | `log_decision` | Record a decision with reasoning |
110
- | `list_decisions` | Browse decision history |
111
-
112
- ### Notes & Context
113
- | Tool | Description |
114
- |------|-------------|
115
- | `add_note` | Add a note (architecture, bug, idea, etc.) |
116
- | `search_notes` | Full-text search |
117
- | `set_context` | Store key-value context |
118
- | `get_context` | Retrieve context |
119
-
120
- ### Sessions
121
- | Tool | Description |
122
- |------|-------------|
123
- | `start_session` | Get full project context + last session's next steps |
124
- | `end_session` | Record summary + what to do next time |
125
-
126
- ### Query
127
- | Tool | Description |
128
- |------|-------------|
129
- | `query` | Read-only SQL against the database |
130
- | `get_project_summary` | Tasks by status, blockers, recent activity |
131
- | `get_blockers` | All blocked tasks with what's blocking them |
132
- | `search` | Full-text search across everything |
133
-
134
- ## How It Works
135
-
136
- ```
137
- ┌─────────────┐ MCP ┌─────────┐ SQLite ┌──────────┐
138
- │ Claude Code │ ◄──────────► │ mindpm │ ◄────────────► │ memory.db│
139
- │ / Desktop │ tools │ server │ read/write │ │
140
- └─────────────┘ └─────────┘ └──────────┘
141
- ```
142
-
143
- 1. You start a conversation and mention your project
144
- 2. The LLM calls `start_session` → gets full context
145
- 3. During the conversation, it creates tasks, logs decisions, adds notes
146
- 4. When you're done, it calls `end_session` → saves what's next
147
- 5. Next conversation: instant context, zero re-explanation
148
-
149
- ## Storage
150
-
151
- Default: `~/.mindpm/memory.db`
152
-
153
- Override with `MINDPM_DB_PATH` or `PROJECT_MEMORY_DB_PATH` environment variable.
154
-
155
- Database and tables are created automatically on first run.
156
-
157
- ## Development
158
-
159
- ```bash
160
- npm install
161
- npm run build # Build with tsup
162
- npm run typecheck # Type-check without emitting
163
- npm run dev # Build in watch mode
164
- ```
165
-
166
- ## License
167
-
168
- MIT
1
+ # mindpm
2
+
3
+ **Persistent project memory for LLMs.** Never re-explain your project again.
4
+
5
+ mindpm is an MCP (Model Context Protocol) server that gives LLMs a SQLite-backed brain for your projects. It tracks tasks, decisions, architecture notes, and session context — so every new conversation picks up exactly where you left off.
6
+
7
+ ## The Problem
8
+
9
+ Every new LLM chat starts from zero:
10
+
11
+ - *"Let me remind you about my project..."*
12
+ - *"Last time we decided to use Redis for..."*
13
+ - *"Where did we leave off?"*
14
+
15
+ ## The Solution
16
+
17
+ mindpm persists your project state in a local SQLite database. The LLM reads and writes to it via MCP tools. No chat history needed. No memory features needed.
18
+
19
+ ```
20
+ You: "What should I work on next?"
21
+ LLM: [queries mindpm] "Last session you finished the auth refactor.
22
+ You have 3 high-priority tasks: rate limiting, API docs, and
23
+ the webhook retry bug. Rate limiting is unblocked — start there."
24
+ ```
25
+
26
+ ## What It Tracks
27
+
28
+ - **Tasks** — status, priority, blockers, sub-tasks
29
+ - **Decisions** — what was decided, why, what alternatives were rejected
30
+ - **Notes** — architecture, bugs, ideas, research
31
+ - **Context** — key-value pairs (tech stack, conventions, config)
32
+ - **Sessions** — what was done, what's next
33
+
34
+ ## Setup
35
+
36
+ ### Install
37
+
38
+ ```bash
39
+ npm install -g mindpm
40
+ ```
41
+
42
+ Or run from source:
43
+
44
+ ```bash
45
+ git clone https://github.com/umitkavala/mindpm.git
46
+ cd mindpm
47
+ npm install
48
+ npm run build
49
+ ```
50
+
51
+ ### Configure with Claude Code
52
+
53
+ Add to your MCP config (`~/.claude/claude_desktop_config.json` or similar):
54
+
55
+ ```json
56
+ {
57
+ "mcpServers": {
58
+ "mindpm": {
59
+ "command": "mindpm",
60
+ "env": {
61
+ "MINDPM_DB_PATH": "~/.mindpm/memory.db"
62
+ }
63
+ }
64
+ }
65
+ }
66
+ ```
67
+
68
+ If running from source, use the built file directly:
69
+
70
+ ```json
71
+ {
72
+ "mcpServers": {
73
+ "mindpm": {
74
+ "command": "node",
75
+ "args": ["/path/to/mindpm/dist/index.js"],
76
+ "env": {
77
+ "MINDPM_DB_PATH": "~/.mindpm/memory.db"
78
+ }
79
+ }
80
+ }
81
+ }
82
+ ```
83
+
84
+ ### Start Using
85
+
86
+ That's it. The LLM now has access to mindpm tools. Just start talking about your projects.
87
+
88
+ ## MCP Tools
89
+
90
+ ### Projects
91
+ | Tool | Description |
92
+ |------|-------------|
93
+ | `create_project` | Create a new project |
94
+ | `list_projects` | List all projects |
95
+ | `get_project_status` | Full project overview |
96
+
97
+ ### Tasks
98
+ | Tool | Description |
99
+ |------|-------------|
100
+ | `create_task` | Add a task |
101
+ | `update_task` | Update status, priority, etc. |
102
+ | `list_tasks` | List with filters |
103
+ | `get_task` | Full task detail with sub-tasks and notes |
104
+ | `get_next_tasks` | Smart: highest priority, unblocked |
105
+
106
+ ### Decisions
107
+ | Tool | Description |
108
+ |------|-------------|
109
+ | `log_decision` | Record a decision with reasoning |
110
+ | `list_decisions` | Browse decision history |
111
+
112
+ ### Notes & Context
113
+ | Tool | Description |
114
+ |------|-------------|
115
+ | `add_note` | Add a note (architecture, bug, idea, etc.) |
116
+ | `search_notes` | Full-text search |
117
+ | `set_context` | Store key-value context |
118
+ | `get_context` | Retrieve context |
119
+
120
+ ### Sessions
121
+ | Tool | Description |
122
+ |------|-------------|
123
+ | `start_session` | Get full project context + last session's next steps |
124
+ | `end_session` | Record summary + what to do next time |
125
+
126
+ ### Query
127
+ | Tool | Description |
128
+ |------|-------------|
129
+ | `query` | Read-only SQL against the database |
130
+ | `get_project_summary` | Tasks by status, blockers, recent activity |
131
+ | `get_blockers` | All blocked tasks with what's blocking them |
132
+ | `search` | Full-text search across everything |
133
+
134
+ ## How It Works
135
+
136
+ ```
137
+ ┌─────────────┐ MCP ┌─────────┐ SQLite ┌──────────┐
138
+ │ Claude Code │ ◄──────────► │ mindpm │ ◄────────────► │ memory.db│
139
+ │ / Desktop │ tools │ server │ read/write │ │
140
+ └─────────────┘ └─────────┘ └──────────┘
141
+ ```
142
+
143
+ 1. You start a conversation and mention your project
144
+ 2. The LLM calls `start_session` → gets full context
145
+ 3. During the conversation, it creates tasks, logs decisions, adds notes
146
+ 4. When you're done, it calls `end_session` → saves what's next
147
+ 5. Next conversation: instant context, zero re-explanation
148
+
149
+ ## Storage
150
+
151
+ Default: `~/.mindpm/memory.db`
152
+
153
+ Override with `MINDPM_DB_PATH` or `PROJECT_MEMORY_DB_PATH` environment variable.
154
+
155
+ Database and tables are created automatically on first run.
156
+
157
+ ## Development
158
+
159
+ ```bash
160
+ npm install
161
+ npm run build # Build with tsup
162
+ npm run typecheck # Type-check without emitting
163
+ npm run dev # Build in watch mode
164
+ ```
165
+
166
+ ## License
167
+
168
+ MIT
package/dist/index.js CHANGED
@@ -1002,11 +1002,328 @@ function registerQueryTools(server2) {
1002
1002
  );
1003
1003
  }
1004
1004
 
1005
+ // src/server/http.ts
1006
+ import { createServer } from "http";
1007
+ import { readFile } from "fs/promises";
1008
+ import { join, extname } from "path";
1009
+ import { fileURLToPath } from "url";
1010
+ import { dirname as dirname2 } from "path";
1011
+
1012
+ // src/server/routes.ts
1013
+ var listProjects = async (_req, res) => {
1014
+ const db2 = getDb();
1015
+ const url = new URL(_req.url || "/", "http://localhost");
1016
+ const status = url.searchParams.get("status");
1017
+ let rows;
1018
+ if (status) {
1019
+ rows = db2.prepare("SELECT * FROM projects WHERE status = ? ORDER BY updated_at DESC").all(status);
1020
+ } else {
1021
+ rows = db2.prepare("SELECT * FROM projects ORDER BY updated_at DESC").all();
1022
+ }
1023
+ sendJson(res, 200, rows);
1024
+ };
1025
+ var getProject = async (_req, res, params) => {
1026
+ const db2 = getDb();
1027
+ const project = db2.prepare("SELECT * FROM projects WHERE id = ?").get(params.id);
1028
+ if (!project) {
1029
+ sendJson(res, 404, { error: "Project not found" });
1030
+ return;
1031
+ }
1032
+ const taskCounts = db2.prepare("SELECT status, COUNT(*) as count FROM tasks WHERE project_id = ? GROUP BY status").all(params.id);
1033
+ sendJson(res, 200, { ...project, task_counts: taskCounts });
1034
+ };
1035
+ var updateProject = async (req, res, params) => {
1036
+ const db2 = getDb();
1037
+ const body = await parseBody(req);
1038
+ const existing = db2.prepare("SELECT * FROM projects WHERE id = ?").get(params.id);
1039
+ if (!existing) {
1040
+ sendJson(res, 404, { error: "Project not found" });
1041
+ return;
1042
+ }
1043
+ const updates = [];
1044
+ const sqlParams = [];
1045
+ if (body.name !== void 0) {
1046
+ updates.push("name = ?");
1047
+ sqlParams.push(body.name);
1048
+ }
1049
+ if (body.description !== void 0) {
1050
+ updates.push("description = ?");
1051
+ sqlParams.push(body.description);
1052
+ }
1053
+ if (body.status !== void 0) {
1054
+ updates.push("status = ?");
1055
+ sqlParams.push(body.status);
1056
+ }
1057
+ if (updates.length === 0) {
1058
+ sendJson(res, 400, { error: "No updates provided" });
1059
+ return;
1060
+ }
1061
+ sqlParams.push(params.id);
1062
+ try {
1063
+ db2.prepare(`UPDATE projects SET ${updates.join(", ")} WHERE id = ?`).run(...sqlParams);
1064
+ } catch (e) {
1065
+ if (e.message?.includes("UNIQUE constraint failed")) {
1066
+ sendJson(res, 409, { error: "A project with that name already exists" });
1067
+ return;
1068
+ }
1069
+ throw e;
1070
+ }
1071
+ const updated = db2.prepare("SELECT * FROM projects WHERE id = ?").get(params.id);
1072
+ sendJson(res, 200, updated);
1073
+ };
1074
+ var listTasks = async (req, res, params) => {
1075
+ const db2 = getDb();
1076
+ const url = new URL(req.url || "/", "http://localhost");
1077
+ const includeDone = url.searchParams.get("include_done") === "true";
1078
+ let sql = "SELECT * FROM tasks WHERE project_id = ?";
1079
+ if (!includeDone) {
1080
+ sql += " AND status NOT IN ('done', 'cancelled')";
1081
+ }
1082
+ sql += " ORDER BY CASE priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 WHEN 'low' THEN 3 END, created_at DESC";
1083
+ const rows = db2.prepare(sql).all(params.pid);
1084
+ sendJson(res, 200, rows);
1085
+ };
1086
+ var createTask = async (req, res, params) => {
1087
+ const db2 = getDb();
1088
+ const body = await parseBody(req);
1089
+ if (!body.title || typeof body.title !== "string") {
1090
+ sendJson(res, 400, { error: "title is required" });
1091
+ return;
1092
+ }
1093
+ const project = db2.prepare("SELECT id FROM projects WHERE id = ?").get(params.pid);
1094
+ if (!project) {
1095
+ sendJson(res, 404, { error: "Project not found" });
1096
+ return;
1097
+ }
1098
+ const id = generateId();
1099
+ const priority = body.priority || "medium";
1100
+ const tags = Array.isArray(body.tags) ? JSON.stringify(body.tags) : null;
1101
+ db2.prepare(
1102
+ "INSERT INTO tasks (id, project_id, title, description, priority, tags, parent_task_id) VALUES (?, ?, ?, ?, ?, ?, ?)"
1103
+ ).run(
1104
+ id,
1105
+ params.pid,
1106
+ body.title,
1107
+ body.description ?? null,
1108
+ priority,
1109
+ tags,
1110
+ body.parent_task_id ?? null
1111
+ );
1112
+ const task = db2.prepare("SELECT * FROM tasks WHERE id = ?").get(id);
1113
+ sendJson(res, 201, task);
1114
+ };
1115
+ var updateTask = async (req, res, params) => {
1116
+ const db2 = getDb();
1117
+ const body = await parseBody(req);
1118
+ const existing = db2.prepare("SELECT * FROM tasks WHERE id = ?").get(params.id);
1119
+ if (!existing) {
1120
+ sendJson(res, 404, { error: "Task not found" });
1121
+ return;
1122
+ }
1123
+ const updates = [];
1124
+ const sqlParams = [];
1125
+ if (body.title !== void 0) {
1126
+ updates.push("title = ?");
1127
+ sqlParams.push(body.title);
1128
+ }
1129
+ if (body.description !== void 0) {
1130
+ updates.push("description = ?");
1131
+ sqlParams.push(body.description);
1132
+ }
1133
+ if (body.status !== void 0) {
1134
+ updates.push("status = ?");
1135
+ sqlParams.push(body.status);
1136
+ if (body.status === "done") {
1137
+ updates.push("completed_at = CURRENT_TIMESTAMP");
1138
+ } else {
1139
+ updates.push("completed_at = NULL");
1140
+ }
1141
+ }
1142
+ if (body.priority !== void 0) {
1143
+ updates.push("priority = ?");
1144
+ sqlParams.push(body.priority);
1145
+ }
1146
+ if (body.tags !== void 0) {
1147
+ updates.push("tags = ?");
1148
+ sqlParams.push(Array.isArray(body.tags) ? JSON.stringify(body.tags) : null);
1149
+ }
1150
+ if (body.blocked_by !== void 0) {
1151
+ updates.push("blocked_by = ?");
1152
+ sqlParams.push(Array.isArray(body.blocked_by) ? JSON.stringify(body.blocked_by) : null);
1153
+ if (Array.isArray(body.blocked_by) && body.blocked_by.length > 0 && body.status === void 0) {
1154
+ updates.push("status = 'blocked'");
1155
+ }
1156
+ }
1157
+ if (updates.length === 0) {
1158
+ sendJson(res, 400, { error: "No updates provided" });
1159
+ return;
1160
+ }
1161
+ sqlParams.push(params.id);
1162
+ db2.prepare(`UPDATE tasks SET ${updates.join(", ")} WHERE id = ?`).run(...sqlParams);
1163
+ const updated = db2.prepare("SELECT * FROM tasks WHERE id = ?").get(params.id);
1164
+ sendJson(res, 200, updated);
1165
+ };
1166
+ var deleteTask = async (_req, res, params) => {
1167
+ const db2 = getDb();
1168
+ const existing = db2.prepare("SELECT * FROM tasks WHERE id = ?").get(params.id);
1169
+ if (!existing) {
1170
+ sendJson(res, 404, { error: "Task not found" });
1171
+ return;
1172
+ }
1173
+ const deleteTransaction = db2.transaction((taskId) => {
1174
+ const subtasks = db2.prepare("SELECT id FROM tasks WHERE parent_task_id = ?").all(taskId);
1175
+ for (const sub of subtasks) {
1176
+ db2.prepare("DELETE FROM notes WHERE task_id = ?").run(sub.id);
1177
+ db2.prepare("DELETE FROM tasks WHERE id = ?").run(sub.id);
1178
+ }
1179
+ db2.prepare("DELETE FROM notes WHERE task_id = ?").run(taskId);
1180
+ db2.prepare("DELETE FROM tasks WHERE id = ?").run(taskId);
1181
+ });
1182
+ deleteTransaction(params.id);
1183
+ sendJson(res, 200, { message: "Task deleted" });
1184
+ };
1185
+ var routes = [
1186
+ { method: "GET", pattern: "/api/projects", handler: listProjects },
1187
+ { method: "GET", pattern: "/api/projects/:id", handler: getProject },
1188
+ { method: "PATCH", pattern: "/api/projects/:id", handler: updateProject },
1189
+ { method: "GET", pattern: "/api/projects/:pid/tasks", handler: listTasks },
1190
+ { method: "POST", pattern: "/api/projects/:pid/tasks", handler: createTask },
1191
+ { method: "PATCH", pattern: "/api/tasks/:id", handler: updateTask },
1192
+ { method: "DELETE", pattern: "/api/tasks/:id", handler: deleteTask }
1193
+ ];
1194
+ async function handleApiRequest(req, res) {
1195
+ const url = new URL(req.url || "/", "http://localhost");
1196
+ const method = req.method || "GET";
1197
+ for (const route of routes) {
1198
+ if (route.method !== method) continue;
1199
+ const params = matchRoute(route.pattern, url.pathname);
1200
+ if (params) {
1201
+ await route.handler(req, res, params);
1202
+ return;
1203
+ }
1204
+ }
1205
+ sendJson(res, 404, { error: "Not found" });
1206
+ }
1207
+
1208
+ // src/server/http.ts
1209
+ var __filename = fileURLToPath(import.meta.url);
1210
+ var __dirname = dirname2(__filename);
1211
+ function resolveStaticDir() {
1212
+ const scriptDir = dirname2(process.argv[1] || __filename);
1213
+ return join(scriptDir, "ui");
1214
+ }
1215
+ var MIME_TYPES = {
1216
+ ".html": "text/html; charset=utf-8",
1217
+ ".js": "application/javascript; charset=utf-8",
1218
+ ".css": "text/css; charset=utf-8",
1219
+ ".json": "application/json; charset=utf-8",
1220
+ ".svg": "image/svg+xml",
1221
+ ".png": "image/png",
1222
+ ".jpg": "image/jpeg",
1223
+ ".ico": "image/x-icon",
1224
+ ".woff": "font/woff",
1225
+ ".woff2": "font/woff2",
1226
+ ".ttf": "font/ttf"
1227
+ };
1228
+ function parseBody(req) {
1229
+ return new Promise((resolve2, reject) => {
1230
+ const chunks = [];
1231
+ req.on("data", (chunk) => chunks.push(chunk));
1232
+ req.on("end", () => {
1233
+ if (chunks.length === 0) {
1234
+ resolve2({});
1235
+ return;
1236
+ }
1237
+ try {
1238
+ resolve2(JSON.parse(Buffer.concat(chunks).toString()));
1239
+ } catch {
1240
+ resolve2({});
1241
+ }
1242
+ });
1243
+ req.on("error", reject);
1244
+ });
1245
+ }
1246
+ function matchRoute(pattern, pathname) {
1247
+ const patternParts = pattern.split("/").filter(Boolean);
1248
+ const pathParts = pathname.split("/").filter(Boolean);
1249
+ if (patternParts.length !== pathParts.length) return null;
1250
+ const params = {};
1251
+ for (let i = 0; i < patternParts.length; i++) {
1252
+ if (patternParts[i].startsWith(":")) {
1253
+ params[patternParts[i].slice(1)] = decodeURIComponent(pathParts[i]);
1254
+ } else if (patternParts[i] !== pathParts[i]) {
1255
+ return null;
1256
+ }
1257
+ }
1258
+ return params;
1259
+ }
1260
+ function sendJson(res, status, data) {
1261
+ res.writeHead(status, { "Content-Type": "application/json; charset=utf-8" });
1262
+ res.end(JSON.stringify(data));
1263
+ }
1264
+ async function serveStatic(req, res) {
1265
+ const staticDir = resolveStaticDir();
1266
+ const url = new URL(req.url || "/", "http://localhost");
1267
+ let filePath = join(staticDir, url.pathname === "/" ? "index.html" : url.pathname);
1268
+ try {
1269
+ const content = await readFile(filePath);
1270
+ const ext = extname(filePath);
1271
+ const contentType = MIME_TYPES[ext] || "application/octet-stream";
1272
+ res.writeHead(200, { "Content-Type": contentType });
1273
+ res.end(content);
1274
+ } catch {
1275
+ try {
1276
+ const indexPath = join(staticDir, "index.html");
1277
+ const content = await readFile(indexPath);
1278
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
1279
+ res.end(content);
1280
+ } catch {
1281
+ res.writeHead(503, { "Content-Type": "text/html; charset=utf-8" });
1282
+ res.end(
1283
+ "<html><body><h1>mindpm UI not built</h1><p>Run <code>npm run build:ui</code> to build the Kanban UI.</p></body></html>"
1284
+ );
1285
+ }
1286
+ }
1287
+ }
1288
+ function startHttpServer(port) {
1289
+ const server2 = createServer(async (req, res) => {
1290
+ try {
1291
+ if (req.url?.startsWith("/api/")) {
1292
+ await handleApiRequest(req, res);
1293
+ } else {
1294
+ await serveStatic(req, res);
1295
+ }
1296
+ } catch (err) {
1297
+ process.stderr.write(`[mindpm] HTTP error: ${err}
1298
+ `);
1299
+ if (!res.headersSent) {
1300
+ sendJson(res, 500, { error: "Internal server error" });
1301
+ }
1302
+ }
1303
+ });
1304
+ server2.on("error", (err) => {
1305
+ if (err.code === "EADDRINUSE") {
1306
+ process.stderr.write(
1307
+ `[mindpm] Warning: Port ${port} is in use. Kanban UI not available. MCP server continues.
1308
+ `
1309
+ );
1310
+ } else {
1311
+ process.stderr.write(`[mindpm] HTTP server error: ${err.message}
1312
+ `);
1313
+ }
1314
+ });
1315
+ server2.listen(port, () => {
1316
+ process.stderr.write(`[mindpm] Kanban UI available at http://localhost:${port}
1317
+ `);
1318
+ });
1319
+ return server2;
1320
+ }
1321
+
1005
1322
  // src/index.ts
1006
1323
  var server = new McpServer(
1007
1324
  {
1008
1325
  name: "mindpm",
1009
- version: "1.1.2"
1326
+ version: "1.2.0"
1010
1327
  },
1011
1328
  {
1012
1329
  capabilities: {
@@ -1020,21 +1337,27 @@ registerDecisionTools(server);
1020
1337
  registerNoteTools(server);
1021
1338
  registerSessionTools(server);
1022
1339
  registerQueryTools(server);
1340
+ var httpServer;
1023
1341
  async function main() {
1024
1342
  ensureDbDirectory();
1343
+ const port = parseInt(process.env.MINDPM_PORT || "3131", 10);
1344
+ httpServer = startHttpServer(port);
1025
1345
  const transport = new StdioServerTransport();
1026
1346
  await server.connect(transport);
1027
1347
  }
1028
1348
  main().catch((error) => {
1029
1349
  console.error("Fatal error:", error);
1350
+ httpServer?.close();
1030
1351
  closeDb();
1031
1352
  process.exit(1);
1032
1353
  });
1033
1354
  process.on("SIGINT", () => {
1355
+ httpServer?.close();
1034
1356
  closeDb();
1035
1357
  process.exit(0);
1036
1358
  });
1037
1359
  process.on("SIGTERM", () => {
1360
+ httpServer?.close();
1038
1361
  closeDb();
1039
1362
  process.exit(0);
1040
1363
  });