knowns 0.2.1 → 0.3.1
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/CHANGELOG.md +100 -0
- package/CLAUDE.md +197 -75
- package/README.md +3 -3
- package/dist/index.js +31884 -16831
- package/dist/mcp/server.js +1058 -638
- package/dist/ui/assets/index-BUsOx5xS.css +1 -0
- package/dist/ui/assets/index-BqJqFGQL.js +171 -0
- package/dist/ui/index.html +2 -2
- package/package.json +17 -4
- package/dist/ui/assets/index-B6Hb3Tx3.js +0 -152
- package/dist/ui/assets/index-BgbLT-Et.css +0 -1
package/dist/mcp/server.js
CHANGED
|
@@ -10073,10 +10073,10 @@ var require_stringify = __commonJS({
|
|
|
10073
10073
|
data = Object.assign({}, file3.data, data);
|
|
10074
10074
|
const open = opts.delimiters[0];
|
|
10075
10075
|
const close = opts.delimiters[1];
|
|
10076
|
-
const
|
|
10076
|
+
const matter5 = engine.stringify(data, options2).trim();
|
|
10077
10077
|
let buf = "";
|
|
10078
|
-
if (
|
|
10079
|
-
buf = newline(open) + newline(
|
|
10078
|
+
if (matter5 !== "{}") {
|
|
10079
|
+
buf = newline(open) + newline(matter5) + newline(close);
|
|
10080
10080
|
}
|
|
10081
10081
|
if (typeof file3.excerpt === "string" && file3.excerpt !== "") {
|
|
10082
10082
|
if (str2.indexOf(file3.excerpt.trim()) === -1) {
|
|
@@ -10182,19 +10182,19 @@ var require_gray_matter = __commonJS({
|
|
|
10182
10182
|
var toFile = require_to_file();
|
|
10183
10183
|
var parse4 = require_parse();
|
|
10184
10184
|
var utils = require_utils2();
|
|
10185
|
-
function
|
|
10185
|
+
function matter5(input, options2) {
|
|
10186
10186
|
if (input === "") {
|
|
10187
10187
|
return { data: {}, content: input, excerpt: "", orig: input };
|
|
10188
10188
|
}
|
|
10189
10189
|
let file3 = toFile(input);
|
|
10190
|
-
const cached2 =
|
|
10190
|
+
const cached2 = matter5.cache[file3.content];
|
|
10191
10191
|
if (!options2) {
|
|
10192
10192
|
if (cached2) {
|
|
10193
10193
|
file3 = Object.assign({}, cached2);
|
|
10194
10194
|
file3.orig = cached2.orig;
|
|
10195
10195
|
return file3;
|
|
10196
10196
|
}
|
|
10197
|
-
|
|
10197
|
+
matter5.cache[file3.content] = file3;
|
|
10198
10198
|
}
|
|
10199
10199
|
return parseMatter(file3, options2);
|
|
10200
10200
|
}
|
|
@@ -10216,7 +10216,7 @@ var require_gray_matter = __commonJS({
|
|
|
10216
10216
|
}
|
|
10217
10217
|
str2 = str2.slice(openLen);
|
|
10218
10218
|
const len = str2.length;
|
|
10219
|
-
const language =
|
|
10219
|
+
const language = matter5.language(str2, opts);
|
|
10220
10220
|
if (language.name) {
|
|
10221
10221
|
file3.language = language.name;
|
|
10222
10222
|
str2 = str2.slice(language.raw.length);
|
|
@@ -10251,24 +10251,24 @@ var require_gray_matter = __commonJS({
|
|
|
10251
10251
|
}
|
|
10252
10252
|
return file3;
|
|
10253
10253
|
}
|
|
10254
|
-
|
|
10255
|
-
|
|
10256
|
-
if (typeof file3 === "string") file3 =
|
|
10254
|
+
matter5.engines = engines2;
|
|
10255
|
+
matter5.stringify = function(file3, data, options2) {
|
|
10256
|
+
if (typeof file3 === "string") file3 = matter5(file3, options2);
|
|
10257
10257
|
return stringify(file3, data, options2);
|
|
10258
10258
|
};
|
|
10259
|
-
|
|
10259
|
+
matter5.read = function(filepath, options2) {
|
|
10260
10260
|
const str2 = fs.readFileSync(filepath, "utf8");
|
|
10261
|
-
const file3 =
|
|
10261
|
+
const file3 = matter5(str2, options2);
|
|
10262
10262
|
file3.path = filepath;
|
|
10263
10263
|
return file3;
|
|
10264
10264
|
};
|
|
10265
|
-
|
|
10265
|
+
matter5.test = function(str2, options2) {
|
|
10266
10266
|
return utils.startsWith(str2, defaults(options2).delimiters[0]);
|
|
10267
10267
|
};
|
|
10268
|
-
|
|
10268
|
+
matter5.language = function(str2, options2) {
|
|
10269
10269
|
const opts = defaults(options2);
|
|
10270
10270
|
const open = opts.delimiters[0];
|
|
10271
|
-
if (
|
|
10271
|
+
if (matter5.test(str2)) {
|
|
10272
10272
|
str2 = str2.slice(open.length);
|
|
10273
10273
|
}
|
|
10274
10274
|
const language = str2.slice(0, str2.search(/\r?\n/));
|
|
@@ -10277,17 +10277,18 @@ var require_gray_matter = __commonJS({
|
|
|
10277
10277
|
name: language ? language.trim() : ""
|
|
10278
10278
|
};
|
|
10279
10279
|
};
|
|
10280
|
-
|
|
10281
|
-
|
|
10282
|
-
|
|
10280
|
+
matter5.cache = {};
|
|
10281
|
+
matter5.clearCache = function() {
|
|
10282
|
+
matter5.cache = {};
|
|
10283
10283
|
};
|
|
10284
|
-
module2.exports =
|
|
10284
|
+
module2.exports = matter5;
|
|
10285
10285
|
}
|
|
10286
10286
|
});
|
|
10287
10287
|
|
|
10288
10288
|
// src/mcp/server.ts
|
|
10289
|
-
import {
|
|
10290
|
-
import {
|
|
10289
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
10290
|
+
import { readFile as readFile4 } from "node:fs/promises";
|
|
10291
|
+
import { join as join8 } from "node:path";
|
|
10291
10292
|
|
|
10292
10293
|
// node_modules/zod/v3/helpers/util.js
|
|
10293
10294
|
var util;
|
|
@@ -29521,7 +29522,7 @@ var Protocol = class {
|
|
|
29521
29522
|
const capturedTransport = this._transport;
|
|
29522
29523
|
const relatedTaskId = request.params?._meta?.[RELATED_TASK_META_KEY]?.taskId;
|
|
29523
29524
|
if (handler === void 0) {
|
|
29524
|
-
const
|
|
29525
|
+
const errorResponse2 = {
|
|
29525
29526
|
jsonrpc: "2.0",
|
|
29526
29527
|
id: request.id,
|
|
29527
29528
|
error: {
|
|
@@ -29532,11 +29533,11 @@ var Protocol = class {
|
|
|
29532
29533
|
if (relatedTaskId && this._taskMessageQueue) {
|
|
29533
29534
|
this._enqueueTaskMessage(relatedTaskId, {
|
|
29534
29535
|
type: "error",
|
|
29535
|
-
message:
|
|
29536
|
+
message: errorResponse2,
|
|
29536
29537
|
timestamp: Date.now()
|
|
29537
29538
|
}, capturedTransport?.sessionId).catch((error46) => this._onerror(new Error(`Failed to enqueue error response: ${error46}`)));
|
|
29538
29539
|
} else {
|
|
29539
|
-
capturedTransport?.send(
|
|
29540
|
+
capturedTransport?.send(errorResponse2).catch((error46) => this._onerror(new Error(`Failed to send an error response: ${error46}`)));
|
|
29540
29541
|
}
|
|
29541
29542
|
return;
|
|
29542
29543
|
}
|
|
@@ -29601,7 +29602,7 @@ var Protocol = class {
|
|
|
29601
29602
|
if (abortController.signal.aborted) {
|
|
29602
29603
|
return;
|
|
29603
29604
|
}
|
|
29604
|
-
const
|
|
29605
|
+
const errorResponse2 = {
|
|
29605
29606
|
jsonrpc: "2.0",
|
|
29606
29607
|
id: request.id,
|
|
29607
29608
|
error: {
|
|
@@ -29613,11 +29614,11 @@ var Protocol = class {
|
|
|
29613
29614
|
if (relatedTaskId && this._taskMessageQueue) {
|
|
29614
29615
|
await this._enqueueTaskMessage(relatedTaskId, {
|
|
29615
29616
|
type: "error",
|
|
29616
|
-
message:
|
|
29617
|
+
message: errorResponse2,
|
|
29617
29618
|
timestamp: Date.now()
|
|
29618
29619
|
}, capturedTransport?.sessionId);
|
|
29619
29620
|
} else {
|
|
29620
|
-
await capturedTransport?.send(
|
|
29621
|
+
await capturedTransport?.send(errorResponse2);
|
|
29621
29622
|
}
|
|
29622
29623
|
}).catch((error46) => this._onerror(new Error(`Failed to send response: ${error46}`))).finally(() => {
|
|
29623
29624
|
this._requestHandlerAbortControllers.delete(request.id);
|
|
@@ -30874,7 +30875,7 @@ var StdioServerTransport = class {
|
|
|
30874
30875
|
};
|
|
30875
30876
|
|
|
30876
30877
|
// src/storage/file-store.ts
|
|
30877
|
-
import { mkdir as mkdir2, readdir, unlink as unlink2 } from "node:fs/promises";
|
|
30878
|
+
import { mkdir as mkdir2, readdir, rename, unlink as unlink2 } from "node:fs/promises";
|
|
30878
30879
|
import { join as join2 } from "node:path";
|
|
30879
30880
|
|
|
30880
30881
|
// src/models/project.ts
|
|
@@ -31352,6 +31353,8 @@ var FileStore = class {
|
|
|
31352
31353
|
// .knowns/
|
|
31353
31354
|
tasksPath;
|
|
31354
31355
|
// .knowns/tasks/
|
|
31356
|
+
archivePath;
|
|
31357
|
+
// .knowns/archive/
|
|
31355
31358
|
projectPath;
|
|
31356
31359
|
// .knowns/config.json
|
|
31357
31360
|
timeEntriesPath;
|
|
@@ -31361,6 +31364,7 @@ var FileStore = class {
|
|
|
31361
31364
|
this.projectRoot = projectRoot;
|
|
31362
31365
|
this.basePath = join2(projectRoot, ".knowns");
|
|
31363
31366
|
this.tasksPath = join2(this.basePath, "tasks");
|
|
31367
|
+
this.archivePath = join2(this.basePath, "archive");
|
|
31364
31368
|
this.projectPath = join2(this.basePath, "config.json");
|
|
31365
31369
|
this.timeEntriesPath = join2(this.basePath, "time-entries.json");
|
|
31366
31370
|
this.versionStore = new VersionStore(projectRoot);
|
|
@@ -31535,6 +31539,7 @@ var FileStore = class {
|
|
|
31535
31539
|
await this.addSubtask(newParent, id);
|
|
31536
31540
|
}
|
|
31537
31541
|
}
|
|
31542
|
+
const oldFileName = updates.title && updates.title !== task.title ? await this.findTaskFile(id) : null;
|
|
31538
31543
|
const updatedTask = {
|
|
31539
31544
|
...task,
|
|
31540
31545
|
...updates,
|
|
@@ -31543,6 +31548,10 @@ var FileStore = class {
|
|
|
31543
31548
|
updatedAt: /* @__PURE__ */ new Date()
|
|
31544
31549
|
};
|
|
31545
31550
|
await this.versionStore.recordVersion(id, task, updatedTask, author);
|
|
31551
|
+
if (oldFileName) {
|
|
31552
|
+
const oldFilePath = join2(this.tasksPath, oldFileName);
|
|
31553
|
+
await unlink2(oldFilePath);
|
|
31554
|
+
}
|
|
31546
31555
|
await this.saveTask(updatedTask);
|
|
31547
31556
|
if (updates.timeEntries !== void 0) {
|
|
31548
31557
|
const allTimeEntries = await this.loadTimeEntries();
|
|
@@ -31562,6 +31571,66 @@ var FileStore = class {
|
|
|
31562
31571
|
const filePath = join2(this.tasksPath, fileName);
|
|
31563
31572
|
await unlink2(filePath);
|
|
31564
31573
|
}
|
|
31574
|
+
/**
|
|
31575
|
+
* Archive task - move to archive folder
|
|
31576
|
+
*/
|
|
31577
|
+
async archiveTask(id) {
|
|
31578
|
+
const task = await this.getTask(id);
|
|
31579
|
+
if (!task) {
|
|
31580
|
+
throw new Error(`Task ${id} not found`);
|
|
31581
|
+
}
|
|
31582
|
+
const fileName = await this.findTaskFile(id);
|
|
31583
|
+
if (!fileName) {
|
|
31584
|
+
throw new Error(`Task file for ${id} not found`);
|
|
31585
|
+
}
|
|
31586
|
+
await mkdir2(this.archivePath, { recursive: true });
|
|
31587
|
+
const oldPath = join2(this.tasksPath, fileName);
|
|
31588
|
+
const newPath = join2(this.archivePath, fileName);
|
|
31589
|
+
await rename(oldPath, newPath);
|
|
31590
|
+
return task;
|
|
31591
|
+
}
|
|
31592
|
+
/**
|
|
31593
|
+
* Unarchive task - restore from archive folder
|
|
31594
|
+
*/
|
|
31595
|
+
async unarchiveTask(id) {
|
|
31596
|
+
try {
|
|
31597
|
+
const files = await readdir(this.archivePath);
|
|
31598
|
+
const taskFile = files.find((f) => f.startsWith(`task-${id} -`));
|
|
31599
|
+
if (!taskFile) {
|
|
31600
|
+
throw new Error(`Archived task ${id} not found`);
|
|
31601
|
+
}
|
|
31602
|
+
const archiveFilePath = join2(this.archivePath, taskFile);
|
|
31603
|
+
const tasksFilePath = join2(this.tasksPath, taskFile);
|
|
31604
|
+
await rename(archiveFilePath, tasksFilePath);
|
|
31605
|
+
return await this.getTask(id);
|
|
31606
|
+
} catch (error46) {
|
|
31607
|
+
console.error(`Failed to unarchive task ${id}:`, error46);
|
|
31608
|
+
throw error46;
|
|
31609
|
+
}
|
|
31610
|
+
}
|
|
31611
|
+
/**
|
|
31612
|
+
* Batch archive tasks - archive all done tasks older than specified duration
|
|
31613
|
+
* @param olderThanMs - Duration in milliseconds (tasks done before now - olderThanMs will be archived)
|
|
31614
|
+
* @returns Array of archived tasks
|
|
31615
|
+
*/
|
|
31616
|
+
async batchArchiveTasks(olderThanMs) {
|
|
31617
|
+
const allTasks = await this.getAllTasks();
|
|
31618
|
+
const now = Date.now();
|
|
31619
|
+
const cutoffTime = now - olderThanMs;
|
|
31620
|
+
const tasksToArchive = allTasks.filter(
|
|
31621
|
+
(task) => task.status === "done" && new Date(task.updatedAt).getTime() < cutoffTime
|
|
31622
|
+
);
|
|
31623
|
+
const archivedTasks = [];
|
|
31624
|
+
for (const task of tasksToArchive) {
|
|
31625
|
+
try {
|
|
31626
|
+
await this.archiveTask(task.id);
|
|
31627
|
+
archivedTasks.push(task);
|
|
31628
|
+
} catch (error46) {
|
|
31629
|
+
console.error(`Failed to archive task ${task.id}:`, error46);
|
|
31630
|
+
}
|
|
31631
|
+
}
|
|
31632
|
+
return archivedTasks;
|
|
31633
|
+
}
|
|
31565
31634
|
/**
|
|
31566
31635
|
* Save task to file
|
|
31567
31636
|
*/
|
|
@@ -31580,11 +31649,18 @@ var FileStore = class {
|
|
|
31580
31649
|
}
|
|
31581
31650
|
/**
|
|
31582
31651
|
* Find task file by ID
|
|
31652
|
+
* Warns if multiple files with same ID are found (indicates a bug)
|
|
31583
31653
|
*/
|
|
31584
31654
|
async findTaskFile(id) {
|
|
31585
31655
|
try {
|
|
31586
31656
|
const files = await readdir(this.tasksPath);
|
|
31587
|
-
|
|
31657
|
+
const matchingFiles = files.filter((f) => f.startsWith(`task-${id} -`));
|
|
31658
|
+
if (matchingFiles.length > 1) {
|
|
31659
|
+
console.warn(
|
|
31660
|
+
`Warning: Found ${matchingFiles.length} files for task-${id}. This may cause data inconsistency. Files: ${matchingFiles.join(", ")}`
|
|
31661
|
+
);
|
|
31662
|
+
}
|
|
31663
|
+
return matchingFiles[0] || null;
|
|
31588
31664
|
} catch (_error) {
|
|
31589
31665
|
return null;
|
|
31590
31666
|
}
|
|
@@ -31675,9 +31751,95 @@ var FileStore = class {
|
|
|
31675
31751
|
}
|
|
31676
31752
|
};
|
|
31677
31753
|
|
|
31678
|
-
// src/
|
|
31754
|
+
// src/mcp/server.ts
|
|
31755
|
+
var import_gray_matter4 = __toESM(require_gray_matter(), 1);
|
|
31756
|
+
|
|
31757
|
+
// src/utils/notify-server.ts
|
|
31758
|
+
import { existsSync as existsSync2, readFileSync } from "node:fs";
|
|
31759
|
+
import { join as join4 } from "node:path";
|
|
31760
|
+
|
|
31761
|
+
// src/utils/find-project-root.ts
|
|
31679
31762
|
import { existsSync } from "node:fs";
|
|
31680
|
-
import { join as join3 } from "node:path";
|
|
31763
|
+
import { dirname, join as join3 } from "node:path";
|
|
31764
|
+
function findProjectRoot(startPath = process.cwd()) {
|
|
31765
|
+
let currentPath = startPath;
|
|
31766
|
+
for (let i = 0; i < 20; i++) {
|
|
31767
|
+
const knownsPath = join3(currentPath, ".knowns");
|
|
31768
|
+
if (existsSync(knownsPath)) {
|
|
31769
|
+
return currentPath;
|
|
31770
|
+
}
|
|
31771
|
+
const parentPath = dirname(currentPath);
|
|
31772
|
+
if (parentPath === currentPath) {
|
|
31773
|
+
return null;
|
|
31774
|
+
}
|
|
31775
|
+
currentPath = parentPath;
|
|
31776
|
+
}
|
|
31777
|
+
return null;
|
|
31778
|
+
}
|
|
31779
|
+
|
|
31780
|
+
// src/utils/notify-server.ts
|
|
31781
|
+
var DEFAULT_PORT = 6420;
|
|
31782
|
+
function getServerPort() {
|
|
31783
|
+
try {
|
|
31784
|
+
const projectRoot = findProjectRoot();
|
|
31785
|
+
if (!projectRoot) return DEFAULT_PORT;
|
|
31786
|
+
const configPath = join4(projectRoot, ".knowns/config.json");
|
|
31787
|
+
if (!existsSync2(configPath)) return DEFAULT_PORT;
|
|
31788
|
+
const content = readFileSync(configPath, "utf-8");
|
|
31789
|
+
const config2 = JSON.parse(content);
|
|
31790
|
+
const port = config2.settings?.serverPort;
|
|
31791
|
+
return typeof port === "number" ? port : DEFAULT_PORT;
|
|
31792
|
+
} catch {
|
|
31793
|
+
return DEFAULT_PORT;
|
|
31794
|
+
}
|
|
31795
|
+
}
|
|
31796
|
+
async function notifyTaskUpdate(taskId) {
|
|
31797
|
+
try {
|
|
31798
|
+
const port = getServerPort();
|
|
31799
|
+
const response = await fetch(`http://localhost:${port}/api/notify`, {
|
|
31800
|
+
method: "POST",
|
|
31801
|
+
headers: { "Content-Type": "application/json" },
|
|
31802
|
+
body: JSON.stringify({ taskId }),
|
|
31803
|
+
signal: AbortSignal.timeout(1e3)
|
|
31804
|
+
// 1 second timeout
|
|
31805
|
+
});
|
|
31806
|
+
if (response.ok) {
|
|
31807
|
+
}
|
|
31808
|
+
} catch {
|
|
31809
|
+
}
|
|
31810
|
+
}
|
|
31811
|
+
async function notifyDocUpdate(docPath) {
|
|
31812
|
+
try {
|
|
31813
|
+
const port = getServerPort();
|
|
31814
|
+
await fetch(`http://localhost:${port}/api/notify`, {
|
|
31815
|
+
method: "POST",
|
|
31816
|
+
headers: { "Content-Type": "application/json" },
|
|
31817
|
+
body: JSON.stringify({ type: "docs:updated", docPath }),
|
|
31818
|
+
signal: AbortSignal.timeout(1e3)
|
|
31819
|
+
});
|
|
31820
|
+
} catch {
|
|
31821
|
+
}
|
|
31822
|
+
}
|
|
31823
|
+
async function notifyTimeUpdate(active) {
|
|
31824
|
+
try {
|
|
31825
|
+
const port = getServerPort();
|
|
31826
|
+
await fetch(`http://localhost:${port}/api/notify`, {
|
|
31827
|
+
method: "POST",
|
|
31828
|
+
headers: { "Content-Type": "application/json" },
|
|
31829
|
+
body: JSON.stringify({ type: "time:updated", active }),
|
|
31830
|
+
signal: AbortSignal.timeout(1e3)
|
|
31831
|
+
});
|
|
31832
|
+
} catch {
|
|
31833
|
+
}
|
|
31834
|
+
}
|
|
31835
|
+
|
|
31836
|
+
// src/mcp/utils.ts
|
|
31837
|
+
import { readFile as readFile2 } from "node:fs/promises";
|
|
31838
|
+
import { join as join6 } from "node:path";
|
|
31839
|
+
|
|
31840
|
+
// src/utils/doc-links.ts
|
|
31841
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
31842
|
+
import { join as join5 } from "node:path";
|
|
31681
31843
|
function parseMarkdownLinks(text) {
|
|
31682
31844
|
const linkPattern = /\[([^\]]+)\]\(([^)]+)\)/g;
|
|
31683
31845
|
const links = [];
|
|
@@ -31693,7 +31855,7 @@ function parseMarkdownLinks(text) {
|
|
|
31693
31855
|
function resolveDocReferences(content, projectRoot) {
|
|
31694
31856
|
const links = parseMarkdownLinks(content);
|
|
31695
31857
|
const docReferences = [];
|
|
31696
|
-
const docsDir =
|
|
31858
|
+
const docsDir = join5(projectRoot, ".knowns", "docs");
|
|
31697
31859
|
for (const link of links) {
|
|
31698
31860
|
if (link.target.startsWith("http://") || link.target.startsWith("https://")) {
|
|
31699
31861
|
continue;
|
|
@@ -31709,8 +31871,8 @@ function resolveDocReferences(content, projectRoot) {
|
|
|
31709
31871
|
if (!filename.endsWith(".md")) {
|
|
31710
31872
|
filename = `${filename}.md`;
|
|
31711
31873
|
}
|
|
31712
|
-
const resolvedPath =
|
|
31713
|
-
const exists =
|
|
31874
|
+
const resolvedPath = join5(docsDir, filename);
|
|
31875
|
+
const exists = existsSync3(resolvedPath);
|
|
31714
31876
|
docReferences.push({
|
|
31715
31877
|
text: link.text,
|
|
31716
31878
|
filename: link.target,
|
|
@@ -31721,9 +31883,8 @@ function resolveDocReferences(content, projectRoot) {
|
|
|
31721
31883
|
return docReferences;
|
|
31722
31884
|
}
|
|
31723
31885
|
|
|
31724
|
-
// src/mcp/
|
|
31886
|
+
// src/mcp/utils.ts
|
|
31725
31887
|
var import_gray_matter2 = __toESM(require_gray_matter(), 1);
|
|
31726
|
-
var fileStore = new FileStore(process.cwd());
|
|
31727
31888
|
function parseDuration(durationStr) {
|
|
31728
31889
|
let totalSeconds = 0;
|
|
31729
31890
|
const hoursMatch = durationStr.match(/(\d+)h/);
|
|
@@ -31755,7 +31916,7 @@ function formatDuration(seconds) {
|
|
|
31755
31916
|
}
|
|
31756
31917
|
async function fetchLinkedDocs(task) {
|
|
31757
31918
|
const projectRoot = process.cwd();
|
|
31758
|
-
const docsDir =
|
|
31919
|
+
const docsDir = join6(projectRoot, ".knowns", "docs");
|
|
31759
31920
|
const allContent = [task.description || "", task.implementationPlan || "", task.implementationNotes || ""].join("\n");
|
|
31760
31921
|
const docRefs = resolveDocReferences(allContent, projectRoot);
|
|
31761
31922
|
const linkedDocs = [];
|
|
@@ -31763,7 +31924,7 @@ async function fetchLinkedDocs(task) {
|
|
|
31763
31924
|
if (!ref.exists) continue;
|
|
31764
31925
|
try {
|
|
31765
31926
|
const filename = ref.resolvedPath.replace("@.knowns/docs/", "");
|
|
31766
|
-
const filepath =
|
|
31927
|
+
const filepath = join6(docsDir, filename);
|
|
31767
31928
|
const fileContent = await readFile2(filepath, "utf-8");
|
|
31768
31929
|
const { data, content } = (0, import_gray_matter2.default)(fileContent);
|
|
31769
31930
|
linkedDocs.push({
|
|
@@ -31776,6 +31937,28 @@ async function fetchLinkedDocs(task) {
|
|
|
31776
31937
|
}
|
|
31777
31938
|
return linkedDocs;
|
|
31778
31939
|
}
|
|
31940
|
+
function successResponse(data) {
|
|
31941
|
+
return {
|
|
31942
|
+
content: [
|
|
31943
|
+
{
|
|
31944
|
+
type: "text",
|
|
31945
|
+
text: JSON.stringify({ success: true, ...data }, null, 2)
|
|
31946
|
+
}
|
|
31947
|
+
]
|
|
31948
|
+
};
|
|
31949
|
+
}
|
|
31950
|
+
function errorResponse(error46) {
|
|
31951
|
+
return {
|
|
31952
|
+
content: [
|
|
31953
|
+
{
|
|
31954
|
+
type: "text",
|
|
31955
|
+
text: JSON.stringify({ success: false, error: error46 }, null, 2)
|
|
31956
|
+
}
|
|
31957
|
+
]
|
|
31958
|
+
};
|
|
31959
|
+
}
|
|
31960
|
+
|
|
31961
|
+
// src/mcp/handlers/task.ts
|
|
31779
31962
|
var createTaskSchema = external_exports3.object({
|
|
31780
31963
|
title: external_exports3.string(),
|
|
31781
31964
|
description: external_exports3.string().optional(),
|
|
@@ -31806,40 +31989,7 @@ var listTasksSchema = external_exports3.object({
|
|
|
31806
31989
|
var searchTasksSchema = external_exports3.object({
|
|
31807
31990
|
query: external_exports3.string()
|
|
31808
31991
|
});
|
|
31809
|
-
var
|
|
31810
|
-
taskId: external_exports3.string()
|
|
31811
|
-
});
|
|
31812
|
-
var stopTimeSchema = external_exports3.object({
|
|
31813
|
-
taskId: external_exports3.string()
|
|
31814
|
-
});
|
|
31815
|
-
var addTimeSchema = external_exports3.object({
|
|
31816
|
-
taskId: external_exports3.string(),
|
|
31817
|
-
duration: external_exports3.string(),
|
|
31818
|
-
// e.g., "2h", "30m", "1h30m"
|
|
31819
|
-
note: external_exports3.string().optional(),
|
|
31820
|
-
date: external_exports3.string().optional()
|
|
31821
|
-
// ISO date string
|
|
31822
|
-
});
|
|
31823
|
-
var getTimeReportSchema = external_exports3.object({
|
|
31824
|
-
from: external_exports3.string().optional(),
|
|
31825
|
-
// YYYY-MM-DD
|
|
31826
|
-
to: external_exports3.string().optional(),
|
|
31827
|
-
// YYYY-MM-DD
|
|
31828
|
-
groupBy: external_exports3.enum(["task", "label", "status"]).optional()
|
|
31829
|
-
});
|
|
31830
|
-
var server = new Server(
|
|
31831
|
-
{
|
|
31832
|
-
name: "knowns-mcp-server",
|
|
31833
|
-
version: "1.0.0"
|
|
31834
|
-
},
|
|
31835
|
-
{
|
|
31836
|
-
capabilities: {
|
|
31837
|
-
tools: {},
|
|
31838
|
-
resources: {}
|
|
31839
|
-
}
|
|
31840
|
-
}
|
|
31841
|
-
);
|
|
31842
|
-
var tools = [
|
|
31992
|
+
var taskTools = [
|
|
31843
31993
|
{
|
|
31844
31994
|
name: "create_task",
|
|
31845
31995
|
description: "Create a new task with title and optional description, status, priority, labels, and assignee",
|
|
@@ -31932,7 +32082,144 @@ var tools = [
|
|
|
31932
32082
|
},
|
|
31933
32083
|
required: ["query"]
|
|
31934
32084
|
}
|
|
31935
|
-
}
|
|
32085
|
+
}
|
|
32086
|
+
];
|
|
32087
|
+
async function handleCreateTask(args, fileStore2) {
|
|
32088
|
+
const input = createTaskSchema.parse(args);
|
|
32089
|
+
const task = await fileStore2.createTask({
|
|
32090
|
+
title: input.title,
|
|
32091
|
+
description: input.description,
|
|
32092
|
+
status: input.status || "todo",
|
|
32093
|
+
priority: input.priority || "medium",
|
|
32094
|
+
assignee: input.assignee,
|
|
32095
|
+
labels: input.labels || [],
|
|
32096
|
+
parent: input.parent,
|
|
32097
|
+
subtasks: [],
|
|
32098
|
+
acceptanceCriteria: [],
|
|
32099
|
+
timeSpent: 0,
|
|
32100
|
+
timeEntries: []
|
|
32101
|
+
});
|
|
32102
|
+
await notifyTaskUpdate(task.id);
|
|
32103
|
+
return successResponse({
|
|
32104
|
+
task: {
|
|
32105
|
+
id: task.id,
|
|
32106
|
+
title: task.title,
|
|
32107
|
+
status: task.status,
|
|
32108
|
+
priority: task.priority
|
|
32109
|
+
}
|
|
32110
|
+
});
|
|
32111
|
+
}
|
|
32112
|
+
async function handleGetTask(args, fileStore2) {
|
|
32113
|
+
const input = getTaskSchema.parse(args);
|
|
32114
|
+
const task = await fileStore2.getTask(input.taskId);
|
|
32115
|
+
if (!task) {
|
|
32116
|
+
return errorResponse(`Task ${input.taskId} not found`);
|
|
32117
|
+
}
|
|
32118
|
+
const linkedDocs = await fetchLinkedDocs(task);
|
|
32119
|
+
return successResponse({
|
|
32120
|
+
task: {
|
|
32121
|
+
id: task.id,
|
|
32122
|
+
title: task.title,
|
|
32123
|
+
description: task.description,
|
|
32124
|
+
status: task.status,
|
|
32125
|
+
priority: task.priority,
|
|
32126
|
+
assignee: task.assignee,
|
|
32127
|
+
labels: task.labels,
|
|
32128
|
+
acceptanceCriteria: task.acceptanceCriteria,
|
|
32129
|
+
createdAt: task.createdAt,
|
|
32130
|
+
updatedAt: task.updatedAt,
|
|
32131
|
+
linkedDocumentation: linkedDocs
|
|
32132
|
+
}
|
|
32133
|
+
});
|
|
32134
|
+
}
|
|
32135
|
+
async function handleUpdateTask(args, fileStore2) {
|
|
32136
|
+
const input = updateTaskSchema.parse(args);
|
|
32137
|
+
const updates = {};
|
|
32138
|
+
if (input.title) updates.title = input.title;
|
|
32139
|
+
if (input.description) updates.description = input.description;
|
|
32140
|
+
if (input.status) updates.status = input.status;
|
|
32141
|
+
if (input.priority) updates.priority = input.priority;
|
|
32142
|
+
if (input.assignee) updates.assignee = input.assignee;
|
|
32143
|
+
if (input.labels) updates.labels = input.labels;
|
|
32144
|
+
const task = await fileStore2.updateTask(input.taskId, updates);
|
|
32145
|
+
await notifyTaskUpdate(task.id);
|
|
32146
|
+
return successResponse({
|
|
32147
|
+
task: {
|
|
32148
|
+
id: task.id,
|
|
32149
|
+
title: task.title,
|
|
32150
|
+
status: task.status,
|
|
32151
|
+
priority: task.priority
|
|
32152
|
+
}
|
|
32153
|
+
});
|
|
32154
|
+
}
|
|
32155
|
+
async function handleListTasks(args, fileStore2) {
|
|
32156
|
+
const input = listTasksSchema.parse(args);
|
|
32157
|
+
let tasks = await fileStore2.getAllTasks();
|
|
32158
|
+
if (input.status) {
|
|
32159
|
+
tasks = tasks.filter((t) => t.status === input.status);
|
|
32160
|
+
}
|
|
32161
|
+
if (input.priority) {
|
|
32162
|
+
tasks = tasks.filter((t) => t.priority === input.priority);
|
|
32163
|
+
}
|
|
32164
|
+
if (input.assignee) {
|
|
32165
|
+
tasks = tasks.filter((t) => t.assignee === input.assignee);
|
|
32166
|
+
}
|
|
32167
|
+
if (input.label) {
|
|
32168
|
+
tasks = tasks.filter((t) => t.labels.includes(input.label));
|
|
32169
|
+
}
|
|
32170
|
+
return successResponse({
|
|
32171
|
+
count: tasks.length,
|
|
32172
|
+
tasks: tasks.map((t) => ({
|
|
32173
|
+
id: t.id,
|
|
32174
|
+
title: t.title,
|
|
32175
|
+
status: t.status,
|
|
32176
|
+
priority: t.priority,
|
|
32177
|
+
assignee: t.assignee,
|
|
32178
|
+
labels: t.labels
|
|
32179
|
+
}))
|
|
32180
|
+
});
|
|
32181
|
+
}
|
|
32182
|
+
async function handleSearchTasks(args, fileStore2) {
|
|
32183
|
+
const input = searchTasksSchema.parse(args);
|
|
32184
|
+
const tasks = await fileStore2.getAllTasks();
|
|
32185
|
+
const query = input.query.toLowerCase();
|
|
32186
|
+
const results = tasks.filter(
|
|
32187
|
+
(t) => t.title.toLowerCase().includes(query) || t.description?.toLowerCase().includes(query) || t.labels.some((l) => l.toLowerCase().includes(query))
|
|
32188
|
+
);
|
|
32189
|
+
return successResponse({
|
|
32190
|
+
count: results.length,
|
|
32191
|
+
tasks: results.map((t) => ({
|
|
32192
|
+
id: t.id,
|
|
32193
|
+
title: t.title,
|
|
32194
|
+
status: t.status,
|
|
32195
|
+
priority: t.priority
|
|
32196
|
+
}))
|
|
32197
|
+
});
|
|
32198
|
+
}
|
|
32199
|
+
|
|
32200
|
+
// src/mcp/handlers/time.ts
|
|
32201
|
+
var startTimeSchema = external_exports3.object({
|
|
32202
|
+
taskId: external_exports3.string()
|
|
32203
|
+
});
|
|
32204
|
+
var stopTimeSchema = external_exports3.object({
|
|
32205
|
+
taskId: external_exports3.string()
|
|
32206
|
+
});
|
|
32207
|
+
var addTimeSchema = external_exports3.object({
|
|
32208
|
+
taskId: external_exports3.string(),
|
|
32209
|
+
duration: external_exports3.string(),
|
|
32210
|
+
// e.g., "2h", "30m", "1h30m"
|
|
32211
|
+
note: external_exports3.string().optional(),
|
|
32212
|
+
date: external_exports3.string().optional()
|
|
32213
|
+
// ISO date string
|
|
32214
|
+
});
|
|
32215
|
+
var getTimeReportSchema = external_exports3.object({
|
|
32216
|
+
from: external_exports3.string().optional(),
|
|
32217
|
+
// YYYY-MM-DD
|
|
32218
|
+
to: external_exports3.string().optional(),
|
|
32219
|
+
// YYYY-MM-DD
|
|
32220
|
+
groupBy: external_exports3.enum(["task", "label", "status"]).optional()
|
|
32221
|
+
});
|
|
32222
|
+
var timeTools = [
|
|
31936
32223
|
{
|
|
31937
32224
|
name: "start_time",
|
|
31938
32225
|
description: "Start time tracking for a task",
|
|
@@ -31990,7 +32277,173 @@ var tools = [
|
|
|
31990
32277
|
}
|
|
31991
32278
|
}
|
|
31992
32279
|
}
|
|
31993
|
-
}
|
|
32280
|
+
}
|
|
32281
|
+
];
|
|
32282
|
+
async function handleStartTime(args, fileStore2) {
|
|
32283
|
+
const input = startTimeSchema.parse(args);
|
|
32284
|
+
const task = await fileStore2.getTask(input.taskId);
|
|
32285
|
+
if (!task) {
|
|
32286
|
+
return errorResponse(`Task ${input.taskId} not found`);
|
|
32287
|
+
}
|
|
32288
|
+
const activeEntry = task.timeEntries.find((e) => !e.endedAt);
|
|
32289
|
+
if (activeEntry) {
|
|
32290
|
+
return errorResponse("Time tracking already active for this task");
|
|
32291
|
+
}
|
|
32292
|
+
const newEntry = {
|
|
32293
|
+
id: `entry-${Date.now()}`,
|
|
32294
|
+
startedAt: /* @__PURE__ */ new Date(),
|
|
32295
|
+
duration: 0,
|
|
32296
|
+
note: "Started via MCP"
|
|
32297
|
+
};
|
|
32298
|
+
await fileStore2.updateTask(input.taskId, {
|
|
32299
|
+
timeEntries: [...task.timeEntries, newEntry]
|
|
32300
|
+
});
|
|
32301
|
+
await notifyTaskUpdate(input.taskId);
|
|
32302
|
+
await notifyTimeUpdate({
|
|
32303
|
+
taskId: input.taskId,
|
|
32304
|
+
taskTitle: task.title,
|
|
32305
|
+
startedAt: newEntry.startedAt.toISOString(),
|
|
32306
|
+
pausedAt: null,
|
|
32307
|
+
totalPausedMs: 0
|
|
32308
|
+
});
|
|
32309
|
+
return successResponse({
|
|
32310
|
+
message: `Started tracking time for task ${input.taskId}`,
|
|
32311
|
+
startedAt: newEntry.startedAt
|
|
32312
|
+
});
|
|
32313
|
+
}
|
|
32314
|
+
async function handleStopTime(args, fileStore2) {
|
|
32315
|
+
const input = stopTimeSchema.parse(args);
|
|
32316
|
+
const task = await fileStore2.getTask(input.taskId);
|
|
32317
|
+
if (!task) {
|
|
32318
|
+
return errorResponse(`Task ${input.taskId} not found`);
|
|
32319
|
+
}
|
|
32320
|
+
const activeIndex = task.timeEntries.findIndex((e) => !e.endedAt);
|
|
32321
|
+
if (activeIndex === -1) {
|
|
32322
|
+
return errorResponse("No active time tracking for this task");
|
|
32323
|
+
}
|
|
32324
|
+
const endTime = /* @__PURE__ */ new Date();
|
|
32325
|
+
const startTime = task.timeEntries[activeIndex].startedAt;
|
|
32326
|
+
const duration3 = Math.floor((endTime.getTime() - new Date(startTime).getTime()) / 1e3);
|
|
32327
|
+
const updatedEntries = [...task.timeEntries];
|
|
32328
|
+
updatedEntries[activeIndex] = {
|
|
32329
|
+
...updatedEntries[activeIndex],
|
|
32330
|
+
endedAt: endTime,
|
|
32331
|
+
duration: duration3
|
|
32332
|
+
};
|
|
32333
|
+
const newTimeSpent = task.timeSpent + duration3;
|
|
32334
|
+
await fileStore2.updateTask(input.taskId, {
|
|
32335
|
+
timeEntries: updatedEntries,
|
|
32336
|
+
timeSpent: newTimeSpent
|
|
32337
|
+
});
|
|
32338
|
+
await notifyTaskUpdate(input.taskId);
|
|
32339
|
+
await notifyTimeUpdate(null);
|
|
32340
|
+
return successResponse({
|
|
32341
|
+
message: `Stopped tracking time for task ${input.taskId}`,
|
|
32342
|
+
duration: formatDuration(duration3),
|
|
32343
|
+
totalTime: formatDuration(newTimeSpent)
|
|
32344
|
+
});
|
|
32345
|
+
}
|
|
32346
|
+
async function handleAddTime(args, fileStore2) {
|
|
32347
|
+
const input = addTimeSchema.parse(args);
|
|
32348
|
+
const task = await fileStore2.getTask(input.taskId);
|
|
32349
|
+
if (!task) {
|
|
32350
|
+
return errorResponse(`Task ${input.taskId} not found`);
|
|
32351
|
+
}
|
|
32352
|
+
const duration3 = parseDuration(input.duration);
|
|
32353
|
+
const startDate = input.date ? new Date(input.date) : /* @__PURE__ */ new Date();
|
|
32354
|
+
const newEntry = {
|
|
32355
|
+
id: `entry-${Date.now()}`,
|
|
32356
|
+
startedAt: startDate,
|
|
32357
|
+
endedAt: new Date(startDate.getTime() + duration3 * 1e3),
|
|
32358
|
+
duration: duration3,
|
|
32359
|
+
note: input.note || "Added via MCP"
|
|
32360
|
+
};
|
|
32361
|
+
const newTimeSpent = task.timeSpent + duration3;
|
|
32362
|
+
await fileStore2.updateTask(input.taskId, {
|
|
32363
|
+
timeEntries: [...task.timeEntries, newEntry],
|
|
32364
|
+
timeSpent: newTimeSpent
|
|
32365
|
+
});
|
|
32366
|
+
await notifyTaskUpdate(input.taskId);
|
|
32367
|
+
return successResponse({
|
|
32368
|
+
message: `Added ${formatDuration(duration3)} to task ${input.taskId}`,
|
|
32369
|
+
totalTime: formatDuration(newTimeSpent)
|
|
32370
|
+
});
|
|
32371
|
+
}
|
|
32372
|
+
async function handleGetTimeReport(args, fileStore2) {
|
|
32373
|
+
const input = getTimeReportSchema.parse(args);
|
|
32374
|
+
const tasks = await fileStore2.getAllTasks();
|
|
32375
|
+
let fromDate;
|
|
32376
|
+
let toDate;
|
|
32377
|
+
if (input.from) fromDate = new Date(input.from);
|
|
32378
|
+
if (input.to) toDate = new Date(input.to);
|
|
32379
|
+
const taskTimeData = tasks.map((task) => {
|
|
32380
|
+
let totalSeconds = 0;
|
|
32381
|
+
for (const entry of task.timeEntries) {
|
|
32382
|
+
if (entry.endedAt) {
|
|
32383
|
+
const entryDate = new Date(entry.startedAt);
|
|
32384
|
+
if (fromDate && entryDate < fromDate) continue;
|
|
32385
|
+
if (toDate && entryDate > toDate) continue;
|
|
32386
|
+
totalSeconds += entry.duration;
|
|
32387
|
+
}
|
|
32388
|
+
}
|
|
32389
|
+
return {
|
|
32390
|
+
taskId: task.id,
|
|
32391
|
+
title: task.title,
|
|
32392
|
+
status: task.status,
|
|
32393
|
+
labels: task.labels,
|
|
32394
|
+
totalSeconds
|
|
32395
|
+
};
|
|
32396
|
+
}).filter((data) => data.totalSeconds > 0);
|
|
32397
|
+
if (input.groupBy === "label") {
|
|
32398
|
+
const grouped = {};
|
|
32399
|
+
for (const data of taskTimeData) {
|
|
32400
|
+
for (const label of data.labels) {
|
|
32401
|
+
grouped[label] = (grouped[label] || 0) + data.totalSeconds;
|
|
32402
|
+
}
|
|
32403
|
+
if (data.labels.length === 0) {
|
|
32404
|
+
grouped["(no label)"] = (grouped["(no label)"] || 0) + data.totalSeconds;
|
|
32405
|
+
}
|
|
32406
|
+
}
|
|
32407
|
+
return successResponse({
|
|
32408
|
+
groupBy: "label",
|
|
32409
|
+
data: Object.entries(grouped).map(([label, seconds]) => ({
|
|
32410
|
+
label,
|
|
32411
|
+
time: formatDuration(seconds),
|
|
32412
|
+
seconds
|
|
32413
|
+
})),
|
|
32414
|
+
totalSeconds: Object.values(grouped).reduce((a, b) => a + b, 0)
|
|
32415
|
+
});
|
|
32416
|
+
}
|
|
32417
|
+
if (input.groupBy === "status") {
|
|
32418
|
+
const grouped = {};
|
|
32419
|
+
for (const data of taskTimeData) {
|
|
32420
|
+
grouped[data.status] = (grouped[data.status] || 0) + data.totalSeconds;
|
|
32421
|
+
}
|
|
32422
|
+
return successResponse({
|
|
32423
|
+
groupBy: "status",
|
|
32424
|
+
data: Object.entries(grouped).map(([status, seconds]) => ({
|
|
32425
|
+
status,
|
|
32426
|
+
time: formatDuration(seconds),
|
|
32427
|
+
seconds
|
|
32428
|
+
})),
|
|
32429
|
+
totalSeconds: Object.values(grouped).reduce((a, b) => a + b, 0)
|
|
32430
|
+
});
|
|
32431
|
+
}
|
|
32432
|
+
return successResponse({
|
|
32433
|
+
groupBy: "task",
|
|
32434
|
+
data: taskTimeData.map((data) => ({
|
|
32435
|
+
taskId: data.taskId,
|
|
32436
|
+
title: data.title,
|
|
32437
|
+
status: data.status,
|
|
32438
|
+
time: formatDuration(data.totalSeconds),
|
|
32439
|
+
seconds: data.totalSeconds
|
|
32440
|
+
})),
|
|
32441
|
+
totalSeconds: taskTimeData.reduce((sum, data) => sum + data.totalSeconds, 0)
|
|
32442
|
+
});
|
|
32443
|
+
}
|
|
32444
|
+
|
|
32445
|
+
// src/mcp/handlers/board.ts
|
|
32446
|
+
var boardTools = [
|
|
31994
32447
|
{
|
|
31995
32448
|
name: "get_board",
|
|
31996
32449
|
description: "Get current board state with tasks grouped by status",
|
|
@@ -32000,6 +32453,404 @@ var tools = [
|
|
|
32000
32453
|
}
|
|
32001
32454
|
}
|
|
32002
32455
|
];
|
|
32456
|
+
async function handleGetBoard(fileStore2) {
|
|
32457
|
+
const tasks = await fileStore2.getAllTasks();
|
|
32458
|
+
const board = {
|
|
32459
|
+
todo: [],
|
|
32460
|
+
"in-progress": [],
|
|
32461
|
+
"in-review": [],
|
|
32462
|
+
done: [],
|
|
32463
|
+
blocked: []
|
|
32464
|
+
};
|
|
32465
|
+
for (const task of tasks) {
|
|
32466
|
+
if (board[task.status]) {
|
|
32467
|
+
board[task.status].push(task);
|
|
32468
|
+
}
|
|
32469
|
+
}
|
|
32470
|
+
const mapTask = (t) => ({
|
|
32471
|
+
id: t.id,
|
|
32472
|
+
title: t.title,
|
|
32473
|
+
priority: t.priority,
|
|
32474
|
+
assignee: t.assignee,
|
|
32475
|
+
labels: t.labels
|
|
32476
|
+
});
|
|
32477
|
+
return successResponse({
|
|
32478
|
+
board: {
|
|
32479
|
+
todo: board.todo.map(mapTask),
|
|
32480
|
+
"in-progress": board["in-progress"].map(mapTask),
|
|
32481
|
+
"in-review": board["in-review"].map(mapTask),
|
|
32482
|
+
done: board.done.map(mapTask),
|
|
32483
|
+
blocked: board.blocked.map(mapTask)
|
|
32484
|
+
},
|
|
32485
|
+
totalTasks: tasks.length
|
|
32486
|
+
});
|
|
32487
|
+
}
|
|
32488
|
+
|
|
32489
|
+
// src/mcp/handlers/doc.ts
|
|
32490
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
32491
|
+
import { mkdir as mkdir3, readFile as readFile3, readdir as readdir2, writeFile as writeFile2 } from "node:fs/promises";
|
|
32492
|
+
import { join as join7 } from "node:path";
|
|
32493
|
+
var import_gray_matter3 = __toESM(require_gray_matter(), 1);
|
|
32494
|
+
var DOCS_DIR = join7(process.cwd(), ".knowns", "docs");
|
|
32495
|
+
var listDocsSchema = external_exports3.object({
|
|
32496
|
+
tag: external_exports3.string().optional()
|
|
32497
|
+
});
|
|
32498
|
+
var getDocSchema = external_exports3.object({
|
|
32499
|
+
path: external_exports3.string()
|
|
32500
|
+
// Document path (filename or folder/filename)
|
|
32501
|
+
});
|
|
32502
|
+
var createDocSchema = external_exports3.object({
|
|
32503
|
+
title: external_exports3.string(),
|
|
32504
|
+
description: external_exports3.string().optional(),
|
|
32505
|
+
content: external_exports3.string().optional(),
|
|
32506
|
+
tags: external_exports3.array(external_exports3.string()).optional(),
|
|
32507
|
+
folder: external_exports3.string().optional()
|
|
32508
|
+
// Optional folder path
|
|
32509
|
+
});
|
|
32510
|
+
var updateDocSchema = external_exports3.object({
|
|
32511
|
+
path: external_exports3.string(),
|
|
32512
|
+
// Document path
|
|
32513
|
+
title: external_exports3.string().optional(),
|
|
32514
|
+
description: external_exports3.string().optional(),
|
|
32515
|
+
content: external_exports3.string().optional(),
|
|
32516
|
+
tags: external_exports3.array(external_exports3.string()).optional(),
|
|
32517
|
+
appendContent: external_exports3.string().optional()
|
|
32518
|
+
// Append to existing content
|
|
32519
|
+
});
|
|
32520
|
+
var searchDocsSchema = external_exports3.object({
|
|
32521
|
+
query: external_exports3.string(),
|
|
32522
|
+
tag: external_exports3.string().optional()
|
|
32523
|
+
});
|
|
32524
|
+
var docTools = [
|
|
32525
|
+
{
|
|
32526
|
+
name: "list_docs",
|
|
32527
|
+
description: "List all documentation files with optional tag filter",
|
|
32528
|
+
inputSchema: {
|
|
32529
|
+
type: "object",
|
|
32530
|
+
properties: {
|
|
32531
|
+
tag: { type: "string", description: "Filter by tag" }
|
|
32532
|
+
}
|
|
32533
|
+
}
|
|
32534
|
+
},
|
|
32535
|
+
{
|
|
32536
|
+
name: "get_doc",
|
|
32537
|
+
description: "Get a documentation file by path (filename or folder/filename)",
|
|
32538
|
+
inputSchema: {
|
|
32539
|
+
type: "object",
|
|
32540
|
+
properties: {
|
|
32541
|
+
path: {
|
|
32542
|
+
type: "string",
|
|
32543
|
+
description: "Document path (e.g., 'readme', 'guides/setup', 'conventions/naming.md')"
|
|
32544
|
+
}
|
|
32545
|
+
},
|
|
32546
|
+
required: ["path"]
|
|
32547
|
+
}
|
|
32548
|
+
},
|
|
32549
|
+
{
|
|
32550
|
+
name: "create_doc",
|
|
32551
|
+
description: "Create a new documentation file",
|
|
32552
|
+
inputSchema: {
|
|
32553
|
+
type: "object",
|
|
32554
|
+
properties: {
|
|
32555
|
+
title: { type: "string", description: "Document title" },
|
|
32556
|
+
description: { type: "string", description: "Document description" },
|
|
32557
|
+
content: { type: "string", description: "Initial content (markdown)" },
|
|
32558
|
+
tags: {
|
|
32559
|
+
type: "array",
|
|
32560
|
+
items: { type: "string" },
|
|
32561
|
+
description: "Document tags"
|
|
32562
|
+
},
|
|
32563
|
+
folder: {
|
|
32564
|
+
type: "string",
|
|
32565
|
+
description: "Folder path (e.g., 'guides', 'patterns/auth')"
|
|
32566
|
+
}
|
|
32567
|
+
},
|
|
32568
|
+
required: ["title"]
|
|
32569
|
+
}
|
|
32570
|
+
},
|
|
32571
|
+
{
|
|
32572
|
+
name: "update_doc",
|
|
32573
|
+
description: "Update an existing documentation file",
|
|
32574
|
+
inputSchema: {
|
|
32575
|
+
type: "object",
|
|
32576
|
+
properties: {
|
|
32577
|
+
path: {
|
|
32578
|
+
type: "string",
|
|
32579
|
+
description: "Document path (e.g., 'readme', 'guides/setup')"
|
|
32580
|
+
},
|
|
32581
|
+
title: { type: "string", description: "New title" },
|
|
32582
|
+
description: { type: "string", description: "New description" },
|
|
32583
|
+
content: { type: "string", description: "Replace content" },
|
|
32584
|
+
tags: {
|
|
32585
|
+
type: "array",
|
|
32586
|
+
items: { type: "string" },
|
|
32587
|
+
description: "New tags"
|
|
32588
|
+
},
|
|
32589
|
+
appendContent: {
|
|
32590
|
+
type: "string",
|
|
32591
|
+
description: "Append to existing content"
|
|
32592
|
+
}
|
|
32593
|
+
},
|
|
32594
|
+
required: ["path"]
|
|
32595
|
+
}
|
|
32596
|
+
},
|
|
32597
|
+
{
|
|
32598
|
+
name: "search_docs",
|
|
32599
|
+
description: "Search documentation by query string",
|
|
32600
|
+
inputSchema: {
|
|
32601
|
+
type: "object",
|
|
32602
|
+
properties: {
|
|
32603
|
+
query: { type: "string", description: "Search query" },
|
|
32604
|
+
tag: { type: "string", description: "Filter by tag" }
|
|
32605
|
+
},
|
|
32606
|
+
required: ["query"]
|
|
32607
|
+
}
|
|
32608
|
+
}
|
|
32609
|
+
];
|
|
32610
|
+
async function ensureDocsDir() {
|
|
32611
|
+
if (!existsSync4(DOCS_DIR)) {
|
|
32612
|
+
await mkdir3(DOCS_DIR, { recursive: true });
|
|
32613
|
+
}
|
|
32614
|
+
}
|
|
32615
|
+
function titleToFilename(title) {
|
|
32616
|
+
return title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
32617
|
+
}
|
|
32618
|
+
async function getAllMdFiles(dir, basePath = "") {
|
|
32619
|
+
const files = [];
|
|
32620
|
+
if (!existsSync4(dir)) {
|
|
32621
|
+
return files;
|
|
32622
|
+
}
|
|
32623
|
+
const entries = await readdir2(dir, { withFileTypes: true });
|
|
32624
|
+
for (const entry of entries) {
|
|
32625
|
+
const fullPath = join7(dir, entry.name);
|
|
32626
|
+
const relativePath = basePath ? join7(basePath, entry.name) : entry.name;
|
|
32627
|
+
if (entry.isDirectory()) {
|
|
32628
|
+
const subFiles = await getAllMdFiles(fullPath, relativePath);
|
|
32629
|
+
files.push(...subFiles);
|
|
32630
|
+
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
32631
|
+
files.push(relativePath);
|
|
32632
|
+
}
|
|
32633
|
+
}
|
|
32634
|
+
return files;
|
|
32635
|
+
}
|
|
32636
|
+
async function resolveDocPath(name) {
|
|
32637
|
+
await ensureDocsDir();
|
|
32638
|
+
let filename = name.endsWith(".md") ? name : `${name}.md`;
|
|
32639
|
+
let filepath = join7(DOCS_DIR, filename);
|
|
32640
|
+
if (existsSync4(filepath)) {
|
|
32641
|
+
return { filepath, filename };
|
|
32642
|
+
}
|
|
32643
|
+
filename = `${titleToFilename(name)}.md`;
|
|
32644
|
+
filepath = join7(DOCS_DIR, filename);
|
|
32645
|
+
if (existsSync4(filepath)) {
|
|
32646
|
+
return { filepath, filename };
|
|
32647
|
+
}
|
|
32648
|
+
const allFiles = await getAllMdFiles(DOCS_DIR);
|
|
32649
|
+
const searchName = name.toLowerCase().replace(/\.md$/, "");
|
|
32650
|
+
const matchingFile = allFiles.find((file3) => {
|
|
32651
|
+
const fileNameOnly = file3.toLowerCase().replace(/\.md$/, "");
|
|
32652
|
+
const fileBaseName = file3.split("/").pop()?.toLowerCase().replace(/\.md$/, "");
|
|
32653
|
+
return fileNameOnly === searchName || fileBaseName === searchName || file3 === name;
|
|
32654
|
+
});
|
|
32655
|
+
if (matchingFile) {
|
|
32656
|
+
return {
|
|
32657
|
+
filepath: join7(DOCS_DIR, matchingFile),
|
|
32658
|
+
filename: matchingFile
|
|
32659
|
+
};
|
|
32660
|
+
}
|
|
32661
|
+
return null;
|
|
32662
|
+
}
|
|
32663
|
+
async function handleListDocs(args) {
|
|
32664
|
+
const input = listDocsSchema.parse(args);
|
|
32665
|
+
await ensureDocsDir();
|
|
32666
|
+
const mdFiles = await getAllMdFiles(DOCS_DIR);
|
|
32667
|
+
if (mdFiles.length === 0) {
|
|
32668
|
+
return successResponse({
|
|
32669
|
+
count: 0,
|
|
32670
|
+
docs: [],
|
|
32671
|
+
message: "No documentation files found"
|
|
32672
|
+
});
|
|
32673
|
+
}
|
|
32674
|
+
const docs = [];
|
|
32675
|
+
for (const file3 of mdFiles) {
|
|
32676
|
+
const content = await readFile3(join7(DOCS_DIR, file3), "utf-8");
|
|
32677
|
+
const { data } = (0, import_gray_matter3.default)(content);
|
|
32678
|
+
const metadata = data;
|
|
32679
|
+
if (input.tag && !metadata.tags?.includes(input.tag)) {
|
|
32680
|
+
continue;
|
|
32681
|
+
}
|
|
32682
|
+
docs.push({
|
|
32683
|
+
path: file3.replace(/\.md$/, ""),
|
|
32684
|
+
title: metadata.title || file3.replace(/\.md$/, ""),
|
|
32685
|
+
description: metadata.description,
|
|
32686
|
+
tags: metadata.tags,
|
|
32687
|
+
updatedAt: metadata.updatedAt
|
|
32688
|
+
});
|
|
32689
|
+
}
|
|
32690
|
+
return successResponse({
|
|
32691
|
+
count: docs.length,
|
|
32692
|
+
docs
|
|
32693
|
+
});
|
|
32694
|
+
}
|
|
32695
|
+
async function handleGetDoc(args) {
|
|
32696
|
+
const input = getDocSchema.parse(args);
|
|
32697
|
+
const resolved = await resolveDocPath(input.path);
|
|
32698
|
+
if (!resolved) {
|
|
32699
|
+
return errorResponse(`Documentation not found: ${input.path}`);
|
|
32700
|
+
}
|
|
32701
|
+
const fileContent = await readFile3(resolved.filepath, "utf-8");
|
|
32702
|
+
const { data, content } = (0, import_gray_matter3.default)(fileContent);
|
|
32703
|
+
const metadata = data;
|
|
32704
|
+
return successResponse({
|
|
32705
|
+
doc: {
|
|
32706
|
+
path: resolved.filename.replace(/\.md$/, ""),
|
|
32707
|
+
title: metadata.title,
|
|
32708
|
+
description: metadata.description,
|
|
32709
|
+
tags: metadata.tags,
|
|
32710
|
+
createdAt: metadata.createdAt,
|
|
32711
|
+
updatedAt: metadata.updatedAt,
|
|
32712
|
+
content: content.trim()
|
|
32713
|
+
}
|
|
32714
|
+
});
|
|
32715
|
+
}
|
|
32716
|
+
async function handleCreateDoc(args) {
|
|
32717
|
+
const input = createDocSchema.parse(args);
|
|
32718
|
+
await ensureDocsDir();
|
|
32719
|
+
const filename = `${titleToFilename(input.title)}.md`;
|
|
32720
|
+
let targetDir = DOCS_DIR;
|
|
32721
|
+
let relativePath = filename;
|
|
32722
|
+
if (input.folder) {
|
|
32723
|
+
const folderPath = input.folder.replace(/^\/|\/$/g, "");
|
|
32724
|
+
targetDir = join7(DOCS_DIR, folderPath);
|
|
32725
|
+
relativePath = join7(folderPath, filename);
|
|
32726
|
+
if (!existsSync4(targetDir)) {
|
|
32727
|
+
await mkdir3(targetDir, { recursive: true });
|
|
32728
|
+
}
|
|
32729
|
+
}
|
|
32730
|
+
const filepath = join7(targetDir, filename);
|
|
32731
|
+
if (existsSync4(filepath)) {
|
|
32732
|
+
return errorResponse(`Document already exists: ${relativePath}`);
|
|
32733
|
+
}
|
|
32734
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
32735
|
+
const metadata = {
|
|
32736
|
+
title: input.title,
|
|
32737
|
+
createdAt: now,
|
|
32738
|
+
updatedAt: now
|
|
32739
|
+
};
|
|
32740
|
+
if (input.description) {
|
|
32741
|
+
metadata.description = input.description;
|
|
32742
|
+
}
|
|
32743
|
+
if (input.tags) {
|
|
32744
|
+
metadata.tags = input.tags;
|
|
32745
|
+
}
|
|
32746
|
+
const initialContent = input.content || "# Content\n\nWrite your documentation here.";
|
|
32747
|
+
const fileContent = import_gray_matter3.default.stringify(initialContent, metadata);
|
|
32748
|
+
await writeFile2(filepath, fileContent, "utf-8");
|
|
32749
|
+
await notifyDocUpdate(relativePath);
|
|
32750
|
+
return successResponse({
|
|
32751
|
+
message: `Created documentation: ${relativePath}`,
|
|
32752
|
+
doc: {
|
|
32753
|
+
path: relativePath.replace(/\.md$/, ""),
|
|
32754
|
+
title: metadata.title,
|
|
32755
|
+
description: metadata.description,
|
|
32756
|
+
tags: metadata.tags
|
|
32757
|
+
}
|
|
32758
|
+
});
|
|
32759
|
+
}
|
|
32760
|
+
async function handleUpdateDoc(args) {
|
|
32761
|
+
const input = updateDocSchema.parse(args);
|
|
32762
|
+
const resolved = await resolveDocPath(input.path);
|
|
32763
|
+
if (!resolved) {
|
|
32764
|
+
return errorResponse(`Documentation not found: ${input.path}`);
|
|
32765
|
+
}
|
|
32766
|
+
const fileContent = await readFile3(resolved.filepath, "utf-8");
|
|
32767
|
+
const { data, content } = (0, import_gray_matter3.default)(fileContent);
|
|
32768
|
+
const metadata = data;
|
|
32769
|
+
if (input.title) metadata.title = input.title;
|
|
32770
|
+
if (input.description) metadata.description = input.description;
|
|
32771
|
+
if (input.tags) metadata.tags = input.tags;
|
|
32772
|
+
metadata.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
32773
|
+
let updatedContent = content;
|
|
32774
|
+
if (input.content) {
|
|
32775
|
+
updatedContent = input.content;
|
|
32776
|
+
}
|
|
32777
|
+
if (input.appendContent) {
|
|
32778
|
+
updatedContent = `${content.trimEnd()}
|
|
32779
|
+
|
|
32780
|
+
${input.appendContent}`;
|
|
32781
|
+
}
|
|
32782
|
+
const newFileContent = import_gray_matter3.default.stringify(updatedContent, metadata);
|
|
32783
|
+
await writeFile2(resolved.filepath, newFileContent, "utf-8");
|
|
32784
|
+
await notifyDocUpdate(resolved.filename);
|
|
32785
|
+
return successResponse({
|
|
32786
|
+
message: `Updated documentation: ${resolved.filename}`,
|
|
32787
|
+
doc: {
|
|
32788
|
+
path: resolved.filename.replace(/\.md$/, ""),
|
|
32789
|
+
title: metadata.title,
|
|
32790
|
+
description: metadata.description,
|
|
32791
|
+
tags: metadata.tags,
|
|
32792
|
+
updatedAt: metadata.updatedAt
|
|
32793
|
+
}
|
|
32794
|
+
});
|
|
32795
|
+
}
|
|
32796
|
+
async function handleSearchDocs(args) {
|
|
32797
|
+
const input = searchDocsSchema.parse(args);
|
|
32798
|
+
await ensureDocsDir();
|
|
32799
|
+
const mdFiles = await getAllMdFiles(DOCS_DIR);
|
|
32800
|
+
const query = input.query.toLowerCase();
|
|
32801
|
+
const results = [];
|
|
32802
|
+
for (const file3 of mdFiles) {
|
|
32803
|
+
const fileContent = await readFile3(join7(DOCS_DIR, file3), "utf-8");
|
|
32804
|
+
const { data, content } = (0, import_gray_matter3.default)(fileContent);
|
|
32805
|
+
const metadata = data;
|
|
32806
|
+
if (input.tag && !metadata.tags?.includes(input.tag)) {
|
|
32807
|
+
continue;
|
|
32808
|
+
}
|
|
32809
|
+
const titleMatch = metadata.title?.toLowerCase().includes(query);
|
|
32810
|
+
const descMatch = metadata.description?.toLowerCase().includes(query);
|
|
32811
|
+
const tagMatch = metadata.tags?.some((t) => t.toLowerCase().includes(query));
|
|
32812
|
+
const contentMatch = content.toLowerCase().includes(query);
|
|
32813
|
+
if (titleMatch || descMatch || tagMatch || contentMatch) {
|
|
32814
|
+
let matchContext;
|
|
32815
|
+
if (contentMatch) {
|
|
32816
|
+
const contentLower = content.toLowerCase();
|
|
32817
|
+
const matchIndex = contentLower.indexOf(query);
|
|
32818
|
+
if (matchIndex !== -1) {
|
|
32819
|
+
const start = Math.max(0, matchIndex - 50);
|
|
32820
|
+
const end = Math.min(content.length, matchIndex + query.length + 50);
|
|
32821
|
+
matchContext = `...${content.slice(start, end).replace(/\n/g, " ")}...`;
|
|
32822
|
+
}
|
|
32823
|
+
}
|
|
32824
|
+
results.push({
|
|
32825
|
+
path: file3.replace(/\.md$/, ""),
|
|
32826
|
+
title: metadata.title || file3.replace(/\.md$/, ""),
|
|
32827
|
+
description: metadata.description,
|
|
32828
|
+
tags: metadata.tags,
|
|
32829
|
+
matchContext
|
|
32830
|
+
});
|
|
32831
|
+
}
|
|
32832
|
+
}
|
|
32833
|
+
return successResponse({
|
|
32834
|
+
count: results.length,
|
|
32835
|
+
docs: results
|
|
32836
|
+
});
|
|
32837
|
+
}
|
|
32838
|
+
|
|
32839
|
+
// src/mcp/server.ts
|
|
32840
|
+
var fileStore = new FileStore(process.cwd());
|
|
32841
|
+
var server = new Server(
|
|
32842
|
+
{
|
|
32843
|
+
name: "knowns-mcp-server",
|
|
32844
|
+
version: "1.0.0"
|
|
32845
|
+
},
|
|
32846
|
+
{
|
|
32847
|
+
capabilities: {
|
|
32848
|
+
tools: {},
|
|
32849
|
+
resources: {}
|
|
32850
|
+
}
|
|
32851
|
+
}
|
|
32852
|
+
);
|
|
32853
|
+
var tools = [...taskTools, ...timeTools, ...boardTools, ...docTools];
|
|
32003
32854
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
32004
32855
|
return { tools };
|
|
32005
32856
|
});
|
|
@@ -32007,540 +32858,129 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
32007
32858
|
const { name, arguments: args } = request.params;
|
|
32008
32859
|
try {
|
|
32009
32860
|
switch (name) {
|
|
32010
|
-
|
|
32011
|
-
|
|
32012
|
-
|
|
32013
|
-
|
|
32014
|
-
|
|
32015
|
-
|
|
32016
|
-
|
|
32017
|
-
|
|
32018
|
-
|
|
32019
|
-
|
|
32020
|
-
|
|
32021
|
-
|
|
32022
|
-
|
|
32023
|
-
|
|
32024
|
-
|
|
32025
|
-
return
|
|
32026
|
-
|
|
32027
|
-
|
|
32028
|
-
|
|
32029
|
-
|
|
32030
|
-
|
|
32031
|
-
|
|
32032
|
-
|
|
32033
|
-
|
|
32034
|
-
|
|
32035
|
-
|
|
32036
|
-
|
|
32037
|
-
|
|
32038
|
-
|
|
32039
|
-
|
|
32040
|
-
|
|
32041
|
-
|
|
32042
|
-
|
|
32043
|
-
|
|
32044
|
-
};
|
|
32045
|
-
}
|
|
32046
|
-
case "get_task": {
|
|
32047
|
-
const input = getTaskSchema.parse(args);
|
|
32048
|
-
const task = await fileStore.getTask(input.taskId);
|
|
32049
|
-
if (!task) {
|
|
32050
|
-
return {
|
|
32051
|
-
content: [
|
|
32052
|
-
{
|
|
32053
|
-
type: "text",
|
|
32054
|
-
text: JSON.stringify(
|
|
32055
|
-
{
|
|
32056
|
-
success: false,
|
|
32057
|
-
error: `Task ${input.taskId} not found`
|
|
32058
|
-
},
|
|
32059
|
-
null,
|
|
32060
|
-
2
|
|
32061
|
-
)
|
|
32062
|
-
}
|
|
32063
|
-
]
|
|
32064
|
-
};
|
|
32065
|
-
}
|
|
32066
|
-
const linkedDocs = await fetchLinkedDocs(task);
|
|
32067
|
-
return {
|
|
32068
|
-
content: [
|
|
32069
|
-
{
|
|
32070
|
-
type: "text",
|
|
32071
|
-
text: JSON.stringify(
|
|
32072
|
-
{
|
|
32073
|
-
success: true,
|
|
32074
|
-
task: {
|
|
32075
|
-
id: task.id,
|
|
32076
|
-
title: task.title,
|
|
32077
|
-
description: task.description,
|
|
32078
|
-
status: task.status,
|
|
32079
|
-
priority: task.priority,
|
|
32080
|
-
assignee: task.assignee,
|
|
32081
|
-
labels: task.labels,
|
|
32082
|
-
acceptanceCriteria: task.acceptanceCriteria,
|
|
32083
|
-
createdAt: task.createdAt,
|
|
32084
|
-
updatedAt: task.updatedAt,
|
|
32085
|
-
linkedDocumentation: linkedDocs
|
|
32086
|
-
}
|
|
32087
|
-
},
|
|
32088
|
-
null,
|
|
32089
|
-
2
|
|
32090
|
-
)
|
|
32091
|
-
}
|
|
32092
|
-
]
|
|
32093
|
-
};
|
|
32094
|
-
}
|
|
32095
|
-
case "update_task": {
|
|
32096
|
-
const input = updateTaskSchema.parse(args);
|
|
32097
|
-
const updates = {};
|
|
32098
|
-
if (input.title) updates.title = input.title;
|
|
32099
|
-
if (input.description) updates.description = input.description;
|
|
32100
|
-
if (input.status) updates.status = input.status;
|
|
32101
|
-
if (input.priority) updates.priority = input.priority;
|
|
32102
|
-
if (input.assignee) updates.assignee = input.assignee;
|
|
32103
|
-
if (input.labels) updates.labels = input.labels;
|
|
32104
|
-
const task = await fileStore.updateTask(input.taskId, updates);
|
|
32105
|
-
return {
|
|
32106
|
-
content: [
|
|
32107
|
-
{
|
|
32108
|
-
type: "text",
|
|
32109
|
-
text: JSON.stringify(
|
|
32110
|
-
{
|
|
32111
|
-
success: true,
|
|
32112
|
-
task: {
|
|
32113
|
-
id: task.id,
|
|
32114
|
-
title: task.title,
|
|
32115
|
-
status: task.status,
|
|
32116
|
-
priority: task.priority
|
|
32117
|
-
}
|
|
32118
|
-
},
|
|
32119
|
-
null,
|
|
32120
|
-
2
|
|
32121
|
-
)
|
|
32122
|
-
}
|
|
32123
|
-
]
|
|
32124
|
-
};
|
|
32125
|
-
}
|
|
32126
|
-
case "list_tasks": {
|
|
32127
|
-
const input = listTasksSchema.parse(args);
|
|
32128
|
-
let tasks = await fileStore.getAllTasks();
|
|
32129
|
-
if (input.status) {
|
|
32130
|
-
tasks = tasks.filter((t) => t.status === input.status);
|
|
32131
|
-
}
|
|
32132
|
-
if (input.priority) {
|
|
32133
|
-
tasks = tasks.filter((t) => t.priority === input.priority);
|
|
32134
|
-
}
|
|
32135
|
-
if (input.assignee) {
|
|
32136
|
-
tasks = tasks.filter((t) => t.assignee === input.assignee);
|
|
32137
|
-
}
|
|
32138
|
-
if (input.label) {
|
|
32139
|
-
tasks = tasks.filter((t) => t.labels.includes(input.label));
|
|
32140
|
-
}
|
|
32141
|
-
return {
|
|
32142
|
-
content: [
|
|
32143
|
-
{
|
|
32144
|
-
type: "text",
|
|
32145
|
-
text: JSON.stringify(
|
|
32146
|
-
{
|
|
32147
|
-
success: true,
|
|
32148
|
-
count: tasks.length,
|
|
32149
|
-
tasks: tasks.map((t) => ({
|
|
32150
|
-
id: t.id,
|
|
32151
|
-
title: t.title,
|
|
32152
|
-
status: t.status,
|
|
32153
|
-
priority: t.priority,
|
|
32154
|
-
assignee: t.assignee,
|
|
32155
|
-
labels: t.labels
|
|
32156
|
-
}))
|
|
32157
|
-
},
|
|
32158
|
-
null,
|
|
32159
|
-
2
|
|
32160
|
-
)
|
|
32161
|
-
}
|
|
32162
|
-
]
|
|
32163
|
-
};
|
|
32164
|
-
}
|
|
32165
|
-
case "search_tasks": {
|
|
32166
|
-
const input = searchTasksSchema.parse(args);
|
|
32167
|
-
const tasks = await fileStore.getAllTasks();
|
|
32168
|
-
const query = input.query.toLowerCase();
|
|
32169
|
-
const results = tasks.filter(
|
|
32170
|
-
(t) => t.title.toLowerCase().includes(query) || t.description?.toLowerCase().includes(query) || t.labels.some((l) => l.toLowerCase().includes(query))
|
|
32171
|
-
);
|
|
32172
|
-
return {
|
|
32173
|
-
content: [
|
|
32174
|
-
{
|
|
32175
|
-
type: "text",
|
|
32176
|
-
text: JSON.stringify(
|
|
32177
|
-
{
|
|
32178
|
-
success: true,
|
|
32179
|
-
count: results.length,
|
|
32180
|
-
tasks: results.map((t) => ({
|
|
32181
|
-
id: t.id,
|
|
32182
|
-
title: t.title,
|
|
32183
|
-
status: t.status,
|
|
32184
|
-
priority: t.priority
|
|
32185
|
-
}))
|
|
32186
|
-
},
|
|
32187
|
-
null,
|
|
32188
|
-
2
|
|
32189
|
-
)
|
|
32190
|
-
}
|
|
32191
|
-
]
|
|
32192
|
-
};
|
|
32193
|
-
}
|
|
32194
|
-
case "start_time": {
|
|
32195
|
-
const input = startTimeSchema.parse(args);
|
|
32196
|
-
const task = await fileStore.getTask(input.taskId);
|
|
32197
|
-
if (!task) {
|
|
32198
|
-
return {
|
|
32199
|
-
content: [
|
|
32200
|
-
{
|
|
32201
|
-
type: "text",
|
|
32202
|
-
text: JSON.stringify({
|
|
32203
|
-
success: false,
|
|
32204
|
-
error: `Task ${input.taskId} not found`
|
|
32205
|
-
})
|
|
32206
|
-
}
|
|
32207
|
-
]
|
|
32208
|
-
};
|
|
32209
|
-
}
|
|
32210
|
-
const activeEntry = task.timeEntries.find((e) => !e.endedAt);
|
|
32211
|
-
if (activeEntry) {
|
|
32212
|
-
return {
|
|
32213
|
-
content: [
|
|
32214
|
-
{
|
|
32215
|
-
type: "text",
|
|
32216
|
-
text: JSON.stringify({
|
|
32217
|
-
success: false,
|
|
32218
|
-
error: "Time tracking already active for this task"
|
|
32219
|
-
})
|
|
32220
|
-
}
|
|
32221
|
-
]
|
|
32222
|
-
};
|
|
32223
|
-
}
|
|
32224
|
-
const newEntry = {
|
|
32225
|
-
id: `entry-${Date.now()}`,
|
|
32226
|
-
startedAt: /* @__PURE__ */ new Date(),
|
|
32227
|
-
duration: 0,
|
|
32228
|
-
note: "Started via MCP"
|
|
32229
|
-
};
|
|
32230
|
-
await fileStore.updateTask(input.taskId, {
|
|
32231
|
-
timeEntries: [...task.timeEntries, newEntry]
|
|
32232
|
-
});
|
|
32233
|
-
return {
|
|
32234
|
-
content: [
|
|
32235
|
-
{
|
|
32236
|
-
type: "text",
|
|
32237
|
-
text: JSON.stringify({
|
|
32238
|
-
success: true,
|
|
32239
|
-
message: `Started tracking time for task ${input.taskId}`,
|
|
32240
|
-
startedAt: newEntry.startedAt
|
|
32241
|
-
})
|
|
32242
|
-
}
|
|
32243
|
-
]
|
|
32244
|
-
};
|
|
32245
|
-
}
|
|
32246
|
-
case "stop_time": {
|
|
32247
|
-
const input = stopTimeSchema.parse(args);
|
|
32248
|
-
const task = await fileStore.getTask(input.taskId);
|
|
32249
|
-
if (!task) {
|
|
32250
|
-
return {
|
|
32251
|
-
content: [
|
|
32252
|
-
{
|
|
32253
|
-
type: "text",
|
|
32254
|
-
text: JSON.stringify({
|
|
32255
|
-
success: false,
|
|
32256
|
-
error: `Task ${input.taskId} not found`
|
|
32257
|
-
})
|
|
32258
|
-
}
|
|
32259
|
-
]
|
|
32260
|
-
};
|
|
32261
|
-
}
|
|
32262
|
-
const activeIndex = task.timeEntries.findIndex((e) => !e.endedAt);
|
|
32263
|
-
if (activeIndex === -1) {
|
|
32264
|
-
return {
|
|
32265
|
-
content: [
|
|
32266
|
-
{
|
|
32267
|
-
type: "text",
|
|
32268
|
-
text: JSON.stringify({
|
|
32269
|
-
success: false,
|
|
32270
|
-
error: "No active time tracking for this task"
|
|
32271
|
-
})
|
|
32272
|
-
}
|
|
32273
|
-
]
|
|
32274
|
-
};
|
|
32275
|
-
}
|
|
32276
|
-
const endTime = /* @__PURE__ */ new Date();
|
|
32277
|
-
const startTime = task.timeEntries[activeIndex].startedAt;
|
|
32278
|
-
const duration3 = Math.floor((endTime.getTime() - startTime.getTime()) / 1e3);
|
|
32279
|
-
const updatedEntries = [...task.timeEntries];
|
|
32280
|
-
updatedEntries[activeIndex] = {
|
|
32281
|
-
...updatedEntries[activeIndex],
|
|
32282
|
-
endedAt: endTime,
|
|
32283
|
-
duration: duration3
|
|
32284
|
-
};
|
|
32285
|
-
const newTimeSpent = task.timeSpent + duration3;
|
|
32286
|
-
await fileStore.updateTask(input.taskId, {
|
|
32287
|
-
timeEntries: updatedEntries,
|
|
32288
|
-
timeSpent: newTimeSpent
|
|
32289
|
-
});
|
|
32290
|
-
return {
|
|
32291
|
-
content: [
|
|
32292
|
-
{
|
|
32293
|
-
type: "text",
|
|
32294
|
-
text: JSON.stringify({
|
|
32295
|
-
success: true,
|
|
32296
|
-
message: `Stopped tracking time for task ${input.taskId}`,
|
|
32297
|
-
duration: formatDuration(duration3),
|
|
32298
|
-
totalTime: formatDuration(newTimeSpent)
|
|
32299
|
-
})
|
|
32300
|
-
}
|
|
32301
|
-
]
|
|
32302
|
-
};
|
|
32303
|
-
}
|
|
32304
|
-
case "add_time": {
|
|
32305
|
-
const input = addTimeSchema.parse(args);
|
|
32306
|
-
const task = await fileStore.getTask(input.taskId);
|
|
32307
|
-
if (!task) {
|
|
32308
|
-
return {
|
|
32309
|
-
content: [
|
|
32310
|
-
{
|
|
32311
|
-
type: "text",
|
|
32312
|
-
text: JSON.stringify({
|
|
32313
|
-
success: false,
|
|
32314
|
-
error: `Task ${input.taskId} not found`
|
|
32315
|
-
})
|
|
32316
|
-
}
|
|
32317
|
-
]
|
|
32318
|
-
};
|
|
32319
|
-
}
|
|
32320
|
-
const duration3 = parseDuration(input.duration);
|
|
32321
|
-
const startDate = input.date ? new Date(input.date) : /* @__PURE__ */ new Date();
|
|
32322
|
-
const newEntry = {
|
|
32323
|
-
id: `entry-${Date.now()}`,
|
|
32324
|
-
startedAt: startDate,
|
|
32325
|
-
endedAt: new Date(startDate.getTime() + duration3 * 1e3),
|
|
32326
|
-
duration: duration3,
|
|
32327
|
-
note: input.note || "Added via MCP"
|
|
32328
|
-
};
|
|
32329
|
-
const newTimeSpent = task.timeSpent + duration3;
|
|
32330
|
-
await fileStore.updateTask(input.taskId, {
|
|
32331
|
-
timeEntries: [...task.timeEntries, newEntry],
|
|
32332
|
-
timeSpent: newTimeSpent
|
|
32333
|
-
});
|
|
32334
|
-
return {
|
|
32335
|
-
content: [
|
|
32336
|
-
{
|
|
32337
|
-
type: "text",
|
|
32338
|
-
text: JSON.stringify({
|
|
32339
|
-
success: true,
|
|
32340
|
-
message: `Added ${formatDuration(duration3)} to task ${input.taskId}`,
|
|
32341
|
-
totalTime: formatDuration(newTimeSpent)
|
|
32342
|
-
})
|
|
32343
|
-
}
|
|
32344
|
-
]
|
|
32345
|
-
};
|
|
32346
|
-
}
|
|
32347
|
-
case "get_time_report": {
|
|
32348
|
-
const input = getTimeReportSchema.parse(args);
|
|
32349
|
-
const tasks = await fileStore.getAllTasks();
|
|
32350
|
-
let fromDate;
|
|
32351
|
-
let toDate;
|
|
32352
|
-
if (input.from) fromDate = new Date(input.from);
|
|
32353
|
-
if (input.to) toDate = new Date(input.to);
|
|
32354
|
-
const taskTimeData = tasks.map((task) => {
|
|
32355
|
-
let totalSeconds = 0;
|
|
32356
|
-
for (const entry of task.timeEntries) {
|
|
32357
|
-
if (entry.endedAt) {
|
|
32358
|
-
const entryDate = new Date(entry.startedAt);
|
|
32359
|
-
if (fromDate && entryDate < fromDate) continue;
|
|
32360
|
-
if (toDate && entryDate > toDate) continue;
|
|
32361
|
-
totalSeconds += entry.duration;
|
|
32362
|
-
}
|
|
32363
|
-
}
|
|
32364
|
-
return {
|
|
32365
|
-
taskId: task.id,
|
|
32366
|
-
title: task.title,
|
|
32367
|
-
status: task.status,
|
|
32368
|
-
labels: task.labels,
|
|
32369
|
-
totalSeconds
|
|
32370
|
-
};
|
|
32371
|
-
}).filter((data) => data.totalSeconds > 0);
|
|
32372
|
-
if (input.groupBy === "label") {
|
|
32373
|
-
const grouped = {};
|
|
32374
|
-
for (const data of taskTimeData) {
|
|
32375
|
-
for (const label of data.labels) {
|
|
32376
|
-
grouped[label] = (grouped[label] || 0) + data.totalSeconds;
|
|
32377
|
-
}
|
|
32378
|
-
if (data.labels.length === 0) {
|
|
32379
|
-
grouped["(no label)"] = (grouped["(no label)"] || 0) + data.totalSeconds;
|
|
32380
|
-
}
|
|
32381
|
-
}
|
|
32382
|
-
return {
|
|
32383
|
-
content: [
|
|
32384
|
-
{
|
|
32385
|
-
type: "text",
|
|
32386
|
-
text: JSON.stringify(
|
|
32387
|
-
{
|
|
32388
|
-
success: true,
|
|
32389
|
-
groupBy: "label",
|
|
32390
|
-
data: Object.entries(grouped).map(([label, seconds]) => ({
|
|
32391
|
-
label,
|
|
32392
|
-
time: formatDuration(seconds),
|
|
32393
|
-
seconds
|
|
32394
|
-
})),
|
|
32395
|
-
totalSeconds: Object.values(grouped).reduce((a, b) => a + b, 0)
|
|
32396
|
-
},
|
|
32397
|
-
null,
|
|
32398
|
-
2
|
|
32399
|
-
)
|
|
32400
|
-
}
|
|
32401
|
-
]
|
|
32402
|
-
};
|
|
32403
|
-
}
|
|
32404
|
-
if (input.groupBy === "status") {
|
|
32405
|
-
const grouped = {};
|
|
32406
|
-
for (const data of taskTimeData) {
|
|
32407
|
-
grouped[data.status] = (grouped[data.status] || 0) + data.totalSeconds;
|
|
32408
|
-
}
|
|
32409
|
-
return {
|
|
32410
|
-
content: [
|
|
32411
|
-
{
|
|
32412
|
-
type: "text",
|
|
32413
|
-
text: JSON.stringify(
|
|
32414
|
-
{
|
|
32415
|
-
success: true,
|
|
32416
|
-
groupBy: "status",
|
|
32417
|
-
data: Object.entries(grouped).map(([status, seconds]) => ({
|
|
32418
|
-
status,
|
|
32419
|
-
time: formatDuration(seconds),
|
|
32420
|
-
seconds
|
|
32421
|
-
})),
|
|
32422
|
-
totalSeconds: Object.values(grouped).reduce((a, b) => a + b, 0)
|
|
32423
|
-
},
|
|
32424
|
-
null,
|
|
32425
|
-
2
|
|
32426
|
-
)
|
|
32427
|
-
}
|
|
32428
|
-
]
|
|
32429
|
-
};
|
|
32430
|
-
}
|
|
32431
|
-
return {
|
|
32432
|
-
content: [
|
|
32433
|
-
{
|
|
32434
|
-
type: "text",
|
|
32435
|
-
text: JSON.stringify(
|
|
32436
|
-
{
|
|
32437
|
-
success: true,
|
|
32438
|
-
groupBy: "task",
|
|
32439
|
-
data: taskTimeData.map((data) => ({
|
|
32440
|
-
taskId: data.taskId,
|
|
32441
|
-
title: data.title,
|
|
32442
|
-
status: data.status,
|
|
32443
|
-
time: formatDuration(data.totalSeconds),
|
|
32444
|
-
seconds: data.totalSeconds
|
|
32445
|
-
})),
|
|
32446
|
-
totalSeconds: taskTimeData.reduce((sum, data) => sum + data.totalSeconds, 0)
|
|
32447
|
-
},
|
|
32448
|
-
null,
|
|
32449
|
-
2
|
|
32450
|
-
)
|
|
32451
|
-
}
|
|
32452
|
-
]
|
|
32453
|
-
};
|
|
32454
|
-
}
|
|
32455
|
-
case "get_board": {
|
|
32456
|
-
const tasks = await fileStore.getAllTasks();
|
|
32457
|
-
const board = {
|
|
32458
|
-
todo: [],
|
|
32459
|
-
"in-progress": [],
|
|
32460
|
-
"in-review": [],
|
|
32461
|
-
done: [],
|
|
32462
|
-
blocked: []
|
|
32463
|
-
};
|
|
32464
|
-
for (const task of tasks) {
|
|
32465
|
-
if (board[task.status]) {
|
|
32466
|
-
board[task.status].push(task);
|
|
32467
|
-
}
|
|
32468
|
-
}
|
|
32469
|
-
return {
|
|
32470
|
-
content: [
|
|
32471
|
-
{
|
|
32472
|
-
type: "text",
|
|
32473
|
-
text: JSON.stringify(
|
|
32474
|
-
{
|
|
32475
|
-
success: true,
|
|
32476
|
-
board: {
|
|
32477
|
-
todo: board.todo.map((t) => ({
|
|
32478
|
-
id: t.id,
|
|
32479
|
-
title: t.title,
|
|
32480
|
-
priority: t.priority,
|
|
32481
|
-
assignee: t.assignee,
|
|
32482
|
-
labels: t.labels
|
|
32483
|
-
})),
|
|
32484
|
-
"in-progress": board["in-progress"].map((t) => ({
|
|
32485
|
-
id: t.id,
|
|
32486
|
-
title: t.title,
|
|
32487
|
-
priority: t.priority,
|
|
32488
|
-
assignee: t.assignee,
|
|
32489
|
-
labels: t.labels
|
|
32490
|
-
})),
|
|
32491
|
-
"in-review": board["in-review"].map((t) => ({
|
|
32492
|
-
id: t.id,
|
|
32493
|
-
title: t.title,
|
|
32494
|
-
priority: t.priority,
|
|
32495
|
-
assignee: t.assignee,
|
|
32496
|
-
labels: t.labels
|
|
32497
|
-
})),
|
|
32498
|
-
done: board.done.map((t) => ({
|
|
32499
|
-
id: t.id,
|
|
32500
|
-
title: t.title,
|
|
32501
|
-
priority: t.priority,
|
|
32502
|
-
assignee: t.assignee,
|
|
32503
|
-
labels: t.labels
|
|
32504
|
-
})),
|
|
32505
|
-
blocked: board.blocked.map((t) => ({
|
|
32506
|
-
id: t.id,
|
|
32507
|
-
title: t.title,
|
|
32508
|
-
priority: t.priority,
|
|
32509
|
-
assignee: t.assignee,
|
|
32510
|
-
labels: t.labels
|
|
32511
|
-
}))
|
|
32512
|
-
},
|
|
32513
|
-
totalTasks: tasks.length
|
|
32514
|
-
},
|
|
32515
|
-
null,
|
|
32516
|
-
2
|
|
32517
|
-
)
|
|
32518
|
-
}
|
|
32519
|
-
]
|
|
32520
|
-
};
|
|
32521
|
-
}
|
|
32861
|
+
// Task handlers
|
|
32862
|
+
case "create_task":
|
|
32863
|
+
return await handleCreateTask(args, fileStore);
|
|
32864
|
+
case "get_task":
|
|
32865
|
+
return await handleGetTask(args, fileStore);
|
|
32866
|
+
case "update_task":
|
|
32867
|
+
return await handleUpdateTask(args, fileStore);
|
|
32868
|
+
case "list_tasks":
|
|
32869
|
+
return await handleListTasks(args, fileStore);
|
|
32870
|
+
case "search_tasks":
|
|
32871
|
+
return await handleSearchTasks(args, fileStore);
|
|
32872
|
+
// Time handlers
|
|
32873
|
+
case "start_time":
|
|
32874
|
+
return await handleStartTime(args, fileStore);
|
|
32875
|
+
case "stop_time":
|
|
32876
|
+
return await handleStopTime(args, fileStore);
|
|
32877
|
+
case "add_time":
|
|
32878
|
+
return await handleAddTime(args, fileStore);
|
|
32879
|
+
case "get_time_report":
|
|
32880
|
+
return await handleGetTimeReport(args, fileStore);
|
|
32881
|
+
// Board handlers
|
|
32882
|
+
case "get_board":
|
|
32883
|
+
return await handleGetBoard(fileStore);
|
|
32884
|
+
// Doc handlers
|
|
32885
|
+
case "list_docs":
|
|
32886
|
+
return await handleListDocs(args);
|
|
32887
|
+
case "get_doc":
|
|
32888
|
+
return await handleGetDoc(args);
|
|
32889
|
+
case "create_doc":
|
|
32890
|
+
return await handleCreateDoc(args);
|
|
32891
|
+
case "update_doc":
|
|
32892
|
+
return await handleUpdateDoc(args);
|
|
32893
|
+
case "search_docs":
|
|
32894
|
+
return await handleSearchDocs(args);
|
|
32522
32895
|
default:
|
|
32523
|
-
return {
|
|
32524
|
-
content: [
|
|
32525
|
-
{
|
|
32526
|
-
type: "text",
|
|
32527
|
-
text: JSON.stringify({
|
|
32528
|
-
success: false,
|
|
32529
|
-
error: `Unknown tool: ${name}`
|
|
32530
|
-
})
|
|
32531
|
-
}
|
|
32532
|
-
]
|
|
32533
|
-
};
|
|
32896
|
+
return errorResponse(`Unknown tool: ${name}`);
|
|
32534
32897
|
}
|
|
32535
32898
|
} catch (error46) {
|
|
32899
|
+
return errorResponse(error46 instanceof Error ? error46.message : String(error46));
|
|
32900
|
+
}
|
|
32901
|
+
});
|
|
32902
|
+
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
32903
|
+
const tasks = await fileStore.getAllTasks();
|
|
32904
|
+
const docsDir = join8(process.cwd(), ".knowns", "docs");
|
|
32905
|
+
const taskResources = tasks.map((task) => ({
|
|
32906
|
+
uri: `knowns://task/${task.id}`,
|
|
32907
|
+
name: task.title,
|
|
32908
|
+
mimeType: "application/json",
|
|
32909
|
+
description: `Task #${task.id}: ${task.title}`
|
|
32910
|
+
}));
|
|
32911
|
+
const docResources = [];
|
|
32912
|
+
if (existsSync5(docsDir)) {
|
|
32913
|
+
const { readdir: readdir3 } = await import("node:fs/promises");
|
|
32914
|
+
async function getAllMdFiles2(dir, basePath = "") {
|
|
32915
|
+
const files = [];
|
|
32916
|
+
const entries = await readdir3(dir, { withFileTypes: true });
|
|
32917
|
+
for (const entry of entries) {
|
|
32918
|
+
const fullPath = join8(dir, entry.name);
|
|
32919
|
+
const relativePath = basePath ? join8(basePath, entry.name) : entry.name;
|
|
32920
|
+
if (entry.isDirectory()) {
|
|
32921
|
+
const subFiles = await getAllMdFiles2(fullPath, relativePath);
|
|
32922
|
+
files.push(...subFiles);
|
|
32923
|
+
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
32924
|
+
files.push(relativePath);
|
|
32925
|
+
}
|
|
32926
|
+
}
|
|
32927
|
+
return files;
|
|
32928
|
+
}
|
|
32929
|
+
const mdFiles = await getAllMdFiles2(docsDir);
|
|
32930
|
+
for (const file3 of mdFiles) {
|
|
32931
|
+
const filepath = join8(docsDir, file3);
|
|
32932
|
+
const content = await readFile4(filepath, "utf-8");
|
|
32933
|
+
const { data } = (0, import_gray_matter4.default)(content);
|
|
32934
|
+
docResources.push({
|
|
32935
|
+
uri: `knowns://doc/${file3.replace(/\.md$/, "")}`,
|
|
32936
|
+
name: data.title || file3.replace(/\.md$/, ""),
|
|
32937
|
+
mimeType: "text/markdown",
|
|
32938
|
+
description: data.description || `Documentation: ${file3}`
|
|
32939
|
+
});
|
|
32940
|
+
}
|
|
32941
|
+
}
|
|
32942
|
+
return {
|
|
32943
|
+
resources: [...taskResources, ...docResources]
|
|
32944
|
+
};
|
|
32945
|
+
});
|
|
32946
|
+
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
32947
|
+
const uri = request.params.uri;
|
|
32948
|
+
const taskMatch = uri.match(/^knowns:\/\/task\/(.+)$/);
|
|
32949
|
+
if (taskMatch) {
|
|
32950
|
+
const taskId = taskMatch[1];
|
|
32951
|
+
const task = await fileStore.getTask(taskId);
|
|
32952
|
+
if (!task) {
|
|
32953
|
+
throw new Error(`Task ${taskId} not found`);
|
|
32954
|
+
}
|
|
32955
|
+
return {
|
|
32956
|
+
contents: [
|
|
32957
|
+
{
|
|
32958
|
+
uri,
|
|
32959
|
+
mimeType: "application/json",
|
|
32960
|
+
text: JSON.stringify(task, null, 2)
|
|
32961
|
+
}
|
|
32962
|
+
]
|
|
32963
|
+
};
|
|
32964
|
+
}
|
|
32965
|
+
const docMatch = uri.match(/^knowns:\/\/doc\/(.+)$/);
|
|
32966
|
+
if (docMatch) {
|
|
32967
|
+
const docPath = docMatch[1];
|
|
32968
|
+
const docsDir = join8(process.cwd(), ".knowns", "docs");
|
|
32969
|
+
const filepath = join8(docsDir, `${docPath}.md`);
|
|
32970
|
+
if (!existsSync5(filepath)) {
|
|
32971
|
+
throw new Error(`Documentation ${docPath} not found`);
|
|
32972
|
+
}
|
|
32973
|
+
const content = await readFile4(filepath, "utf-8");
|
|
32974
|
+
const { data, content: docContent } = (0, import_gray_matter4.default)(content);
|
|
32536
32975
|
return {
|
|
32537
|
-
|
|
32976
|
+
contents: [
|
|
32538
32977
|
{
|
|
32539
|
-
|
|
32978
|
+
uri,
|
|
32979
|
+
mimeType: "text/markdown",
|
|
32540
32980
|
text: JSON.stringify(
|
|
32541
32981
|
{
|
|
32542
|
-
|
|
32543
|
-
|
|
32982
|
+
metadata: data,
|
|
32983
|
+
content: docContent.trim()
|
|
32544
32984
|
},
|
|
32545
32985
|
null,
|
|
32546
32986
|
2
|
|
@@ -32549,48 +32989,28 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
32549
32989
|
]
|
|
32550
32990
|
};
|
|
32551
32991
|
}
|
|
32992
|
+
throw new Error(`Invalid resource URI: ${uri}`);
|
|
32552
32993
|
});
|
|
32553
|
-
|
|
32554
|
-
|
|
32555
|
-
|
|
32556
|
-
resources: tasks.map((task) => ({
|
|
32557
|
-
uri: `knowns://task/${task.id}`,
|
|
32558
|
-
name: task.title,
|
|
32559
|
-
mimeType: "application/json",
|
|
32560
|
-
description: `Task #${task.id}: ${task.title}`
|
|
32561
|
-
}))
|
|
32562
|
-
};
|
|
32563
|
-
});
|
|
32564
|
-
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
32565
|
-
const uri = request.params.uri;
|
|
32566
|
-
const match = uri.match(/^knowns:\/\/task\/(.+)$/);
|
|
32567
|
-
if (!match) {
|
|
32568
|
-
throw new Error(`Invalid resource URI: ${uri}`);
|
|
32569
|
-
}
|
|
32570
|
-
const taskId = match[1];
|
|
32571
|
-
const task = await fileStore.getTask(taskId);
|
|
32572
|
-
if (!task) {
|
|
32573
|
-
throw new Error(`Task ${taskId} not found`);
|
|
32994
|
+
async function startMcpServer(options2 = {}) {
|
|
32995
|
+
if (options2.verbose) {
|
|
32996
|
+
console.error("Knowns MCP Server starting...");
|
|
32574
32997
|
}
|
|
32575
|
-
return {
|
|
32576
|
-
contents: [
|
|
32577
|
-
{
|
|
32578
|
-
uri,
|
|
32579
|
-
mimeType: "application/json",
|
|
32580
|
-
text: JSON.stringify(task, null, 2)
|
|
32581
|
-
}
|
|
32582
|
-
]
|
|
32583
|
-
};
|
|
32584
|
-
});
|
|
32585
|
-
async function main() {
|
|
32586
32998
|
const transport = new StdioServerTransport();
|
|
32587
32999
|
await server.connect(transport);
|
|
32588
|
-
|
|
33000
|
+
if (options2.verbose) {
|
|
33001
|
+
console.error("Knowns MCP Server running on stdio");
|
|
33002
|
+
}
|
|
32589
33003
|
}
|
|
32590
|
-
|
|
32591
|
-
|
|
32592
|
-
|
|
32593
|
-
|
|
33004
|
+
var isStandaloneServer = process.argv[1]?.includes("mcp/server") || process.argv[1]?.includes("mcp\\server");
|
|
33005
|
+
if (isStandaloneServer) {
|
|
33006
|
+
startMcpServer({ verbose: true }).catch((error46) => {
|
|
33007
|
+
console.error("Fatal error:", error46);
|
|
33008
|
+
process.exit(1);
|
|
33009
|
+
});
|
|
33010
|
+
}
|
|
33011
|
+
export {
|
|
33012
|
+
startMcpServer
|
|
33013
|
+
};
|
|
32594
33014
|
/*! Bundled license information:
|
|
32595
33015
|
|
|
32596
33016
|
is-extendable/index.js:
|