@cognistore/mcp-server 1.0.9 → 1.0.14
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/dist/index.js +374 -55
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -44,8 +44,8 @@ var TaskPriority;
|
|
|
44
44
|
})(TaskPriority || (TaskPriority = {}));
|
|
45
45
|
|
|
46
46
|
// ../../packages/shared/dist/constants/defaults.js
|
|
47
|
-
var DEFAULT_EMBEDDING_MODEL = "
|
|
48
|
-
var DEFAULT_EMBEDDING_DIMENSIONS =
|
|
47
|
+
var DEFAULT_EMBEDDING_MODEL = "nomic-embed-text";
|
|
48
|
+
var DEFAULT_EMBEDDING_DIMENSIONS = 256;
|
|
49
49
|
var DEFAULT_SIMILARITY_THRESHOLD = 0.3;
|
|
50
50
|
var DEFAULT_SEARCH_LIMIT = 10;
|
|
51
51
|
var DEFAULT_OLLAMA_HOST = "http://localhost:11434";
|
|
@@ -58,12 +58,13 @@ var knowledgeTypeSchema = z.nativeEnum(KnowledgeType);
|
|
|
58
58
|
var knowledgeStatusSchema = z.nativeEnum(KnowledgeStatus);
|
|
59
59
|
var taskStatusSchema = z.nativeEnum(TaskStatus);
|
|
60
60
|
var taskPrioritySchema = z.nativeEnum(TaskPriority);
|
|
61
|
+
var scopeSchema = z.string().regex(/^(global|workspace:[a-zA-Z0-9._-]+)$/, 'Scope must be "global" or "workspace:<project-name>" (alphanumeric, dots, hyphens, underscores)');
|
|
61
62
|
var createKnowledgeSchema = z.object({
|
|
62
63
|
title: z.string().min(1, "Title is required"),
|
|
63
64
|
content: z.string().min(1, "Content is required"),
|
|
64
65
|
tags: z.array(z.string().min(1)).min(1, "At least one tag is required"),
|
|
65
66
|
type: knowledgeTypeSchema,
|
|
66
|
-
scope:
|
|
67
|
+
scope: scopeSchema,
|
|
67
68
|
source: z.string().min(1, "Source is required"),
|
|
68
69
|
confidenceScore: z.number().min(0).max(1).optional().default(1),
|
|
69
70
|
expiresAt: z.date().nullable().optional().default(null),
|
|
@@ -75,7 +76,7 @@ var updateKnowledgeSchema = z.object({
|
|
|
75
76
|
content: z.string().min(1).optional(),
|
|
76
77
|
tags: z.array(z.string().min(1)).min(1).optional(),
|
|
77
78
|
type: knowledgeTypeSchema.optional(),
|
|
78
|
-
scope:
|
|
79
|
+
scope: scopeSchema.optional(),
|
|
79
80
|
source: z.string().min(1).optional(),
|
|
80
81
|
confidenceScore: z.number().min(0).max(1).optional(),
|
|
81
82
|
expiresAt: z.date().nullable().optional(),
|
|
@@ -85,7 +86,7 @@ var updateKnowledgeSchema = z.object({
|
|
|
85
86
|
var searchOptionsSchema = z.object({
|
|
86
87
|
tags: z.array(z.string()).optional(),
|
|
87
88
|
type: knowledgeTypeSchema.optional(),
|
|
88
|
-
scope:
|
|
89
|
+
scope: scopeSchema.optional(),
|
|
89
90
|
limit: z.number().int().min(1).max(100).optional().default(DEFAULT_SEARCH_LIMIT),
|
|
90
91
|
threshold: z.number().min(0).max(1).optional().default(DEFAULT_SIMILARITY_THRESHOLD)
|
|
91
92
|
});
|
|
@@ -93,15 +94,19 @@ var createPlanSchema = z.object({
|
|
|
93
94
|
title: z.string().min(1, "Title is required"),
|
|
94
95
|
content: z.string().min(1, "Content is required"),
|
|
95
96
|
tags: z.array(z.string().min(1)).min(1, "At least one tag is required"),
|
|
96
|
-
scope:
|
|
97
|
+
scope: scopeSchema,
|
|
97
98
|
source: z.string().min(1, "Source is required"),
|
|
98
|
-
status: knowledgeStatusSchema.optional().default(KnowledgeStatus.DRAFT)
|
|
99
|
+
status: knowledgeStatusSchema.optional().default(KnowledgeStatus.DRAFT),
|
|
100
|
+
tasks: z.array(z.object({
|
|
101
|
+
description: z.string().min(1),
|
|
102
|
+
priority: z.enum(["low", "medium", "high"]).optional()
|
|
103
|
+
})).optional()
|
|
99
104
|
});
|
|
100
105
|
var updatePlanSchema = z.object({
|
|
101
106
|
title: z.string().min(1).optional(),
|
|
102
107
|
content: z.string().min(1).optional(),
|
|
103
108
|
tags: z.array(z.string().min(1)).min(1).optional(),
|
|
104
|
-
scope:
|
|
109
|
+
scope: scopeSchema.optional(),
|
|
105
110
|
status: knowledgeStatusSchema.optional(),
|
|
106
111
|
source: z.string().min(1).optional()
|
|
107
112
|
});
|
|
@@ -126,11 +131,15 @@ var schema_exports = {};
|
|
|
126
131
|
__export(schema_exports, {
|
|
127
132
|
createEmbeddingsTable: () => createEmbeddingsTable,
|
|
128
133
|
deleteEmbedding: () => deleteEmbedding,
|
|
134
|
+
deletePlanEmbedding: () => deletePlanEmbedding,
|
|
129
135
|
insertEmbedding: () => insertEmbedding,
|
|
136
|
+
insertPlanEmbedding: () => insertPlanEmbedding,
|
|
130
137
|
knowledgeEntries: () => knowledgeEntries,
|
|
131
138
|
knowledgeTypeEnum: () => knowledgeTypeEnum,
|
|
132
139
|
searchKnn: () => searchKnn,
|
|
133
|
-
|
|
140
|
+
searchPlansKnn: () => searchPlansKnn,
|
|
141
|
+
updateEmbedding: () => updateEmbedding,
|
|
142
|
+
updatePlanEmbedding: () => updatePlanEmbedding
|
|
134
143
|
});
|
|
135
144
|
|
|
136
145
|
// ../../packages/core/dist/db/schema/knowledge.js
|
|
@@ -158,7 +167,7 @@ var knowledgeEntries = sqliteTable("knowledge_entries", {
|
|
|
158
167
|
|
|
159
168
|
// ../../packages/core/dist/db/schema/sqlite-vec.js
|
|
160
169
|
var VIRTUAL_TABLE_NAME = "knowledge_embeddings";
|
|
161
|
-
function createEmbeddingsTable(sqlite, dimensions =
|
|
170
|
+
function createEmbeddingsTable(sqlite, dimensions = DEFAULT_EMBEDDING_DIMENSIONS) {
|
|
162
171
|
sqlite.exec(`
|
|
163
172
|
CREATE VIRTUAL TABLE IF NOT EXISTS ${VIRTUAL_TABLE_NAME} USING vec0(
|
|
164
173
|
id TEXT PRIMARY KEY,
|
|
@@ -187,6 +196,25 @@ function searchKnn(sqlite, queryEmbedding, k) {
|
|
|
187
196
|
`);
|
|
188
197
|
return stmt.all(Buffer.from(new Float32Array(queryEmbedding).buffer), k);
|
|
189
198
|
}
|
|
199
|
+
var PLANS_TABLE_NAME = "plans_embeddings";
|
|
200
|
+
function insertPlanEmbedding(sqlite, id, embedding) {
|
|
201
|
+
sqlite.prepare(`INSERT INTO ${PLANS_TABLE_NAME}(id, embedding) VALUES (?, ?)`).run(id, Buffer.from(new Float32Array(embedding).buffer));
|
|
202
|
+
}
|
|
203
|
+
function updatePlanEmbedding(sqlite, id, embedding) {
|
|
204
|
+
sqlite.prepare(`UPDATE ${PLANS_TABLE_NAME} SET embedding = ? WHERE id = ?`).run(Buffer.from(new Float32Array(embedding).buffer), id);
|
|
205
|
+
}
|
|
206
|
+
function deletePlanEmbedding(sqlite, id) {
|
|
207
|
+
sqlite.prepare(`DELETE FROM ${PLANS_TABLE_NAME} WHERE id = ?`).run(id);
|
|
208
|
+
}
|
|
209
|
+
function searchPlansKnn(sqlite, queryEmbedding, k) {
|
|
210
|
+
const stmt = sqlite.prepare(`
|
|
211
|
+
SELECT id, distance
|
|
212
|
+
FROM ${PLANS_TABLE_NAME}
|
|
213
|
+
WHERE embedding MATCH ?
|
|
214
|
+
AND k = ?
|
|
215
|
+
`);
|
|
216
|
+
return stmt.all(Buffer.from(new Float32Array(queryEmbedding).buffer), k);
|
|
217
|
+
}
|
|
190
218
|
|
|
191
219
|
// ../../packages/core/dist/db/migrate.js
|
|
192
220
|
import { readdirSync, readFileSync, existsSync } from "fs";
|
|
@@ -275,7 +303,7 @@ function runMigrations(sqlite, migrationsDir) {
|
|
|
275
303
|
if (tableExists) {
|
|
276
304
|
sqlite.prepare("INSERT INTO schema_version (version, applied_at) VALUES (?, ?)").run("0.8.0", (/* @__PURE__ */ new Date()).toISOString());
|
|
277
305
|
applied.add("0.8.0");
|
|
278
|
-
console.
|
|
306
|
+
console.error("Migration: bootstrapped existing DB as v0.8.0");
|
|
279
307
|
}
|
|
280
308
|
}
|
|
281
309
|
let migrations;
|
|
@@ -287,7 +315,7 @@ function runMigrations(sqlite, migrationsDir) {
|
|
|
287
315
|
}));
|
|
288
316
|
} else {
|
|
289
317
|
migrations = Object.entries(EMBEDDED_MIGRATIONS).map(([version, sql2]) => ({ version, sql: sql2 })).sort((a, b) => compareSemver(a.version, b.version));
|
|
290
|
-
console.
|
|
318
|
+
console.error("Migration: using embedded migrations (bundled mode)");
|
|
291
319
|
}
|
|
292
320
|
for (const { version, sql: sql2 } of migrations) {
|
|
293
321
|
if (applied.has(version))
|
|
@@ -306,7 +334,7 @@ function runMigrations(sqlite, migrationsDir) {
|
|
|
306
334
|
}
|
|
307
335
|
}
|
|
308
336
|
sqlite.prepare("INSERT INTO schema_version (version, applied_at) VALUES (?, ?)").run(version, (/* @__PURE__ */ new Date()).toISOString());
|
|
309
|
-
console.
|
|
337
|
+
console.error(`Migration: ${version} applied`);
|
|
310
338
|
}
|
|
311
339
|
}
|
|
312
340
|
function runSeeds(sqlite, seedsDir, isFreshInstall) {
|
|
@@ -319,7 +347,7 @@ function runSeeds(sqlite, seedsDir, isFreshInstall) {
|
|
|
319
347
|
const sqlPath = resolve(seedsDir, file);
|
|
320
348
|
const sqlContent = readFileSync(sqlPath, "utf-8");
|
|
321
349
|
sqlite.exec(sqlContent);
|
|
322
|
-
console.
|
|
350
|
+
console.error(`Seed: ${file} applied`);
|
|
323
351
|
}
|
|
324
352
|
}
|
|
325
353
|
function compareSemver(a, b) {
|
|
@@ -359,17 +387,18 @@ function createDbClient(dbPath) {
|
|
|
359
387
|
const seedsDir = resolve2(__dirname, "seeds");
|
|
360
388
|
runMigrations(sqlite, migrationsDir);
|
|
361
389
|
runSeeds(sqlite, seedsDir, isFreshInstall);
|
|
362
|
-
|
|
363
|
-
|
|
390
|
+
const dims = Number(process.env.EMBEDDING_DIMENSIONS) || DEFAULT_EMBEDDING_DIMENSIONS;
|
|
391
|
+
createEmbeddingsTable(sqlite, dims);
|
|
392
|
+
createPlansEmbeddingsTable(sqlite, dims);
|
|
364
393
|
const db = drizzle(sqlite, { schema: schema_exports });
|
|
365
394
|
return { db, sqlite };
|
|
366
395
|
}
|
|
367
|
-
function createPlansEmbeddingsTable(sqlite) {
|
|
396
|
+
function createPlansEmbeddingsTable(sqlite, dimensions = DEFAULT_EMBEDDING_DIMENSIONS) {
|
|
368
397
|
try {
|
|
369
398
|
sqlite.exec(`
|
|
370
399
|
CREATE VIRTUAL TABLE IF NOT EXISTS plans_embeddings USING vec0(
|
|
371
400
|
id TEXT PRIMARY KEY,
|
|
372
|
-
embedding float[
|
|
401
|
+
embedding float[${dimensions}] distance_metric=cosine
|
|
373
402
|
);
|
|
374
403
|
`);
|
|
375
404
|
} catch {
|
|
@@ -525,7 +554,7 @@ var KnowledgeRepository = class {
|
|
|
525
554
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
526
555
|
this.sqlite.prepare("INSERT INTO plans (id, title, content, tags, scope, status, source, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)").run(id, input.title, input.content, JSON.stringify(input.tags), input.scope, input.status ?? "draft", input.source, now, now);
|
|
527
556
|
try {
|
|
528
|
-
this.sqlite
|
|
557
|
+
insertPlanEmbedding(this.sqlite, id, input.embedding);
|
|
529
558
|
} catch {
|
|
530
559
|
}
|
|
531
560
|
return this.getPlanById(id);
|
|
@@ -554,7 +583,7 @@ var KnowledgeRepository = class {
|
|
|
554
583
|
deletePlan(id) {
|
|
555
584
|
const result = this.sqlite.prepare("DELETE FROM plans WHERE id = ?").run(id);
|
|
556
585
|
try {
|
|
557
|
-
this.sqlite
|
|
586
|
+
deletePlanEmbedding(this.sqlite, id);
|
|
558
587
|
} catch {
|
|
559
588
|
}
|
|
560
589
|
return result.changes > 0;
|
|
@@ -562,11 +591,62 @@ var KnowledgeRepository = class {
|
|
|
562
591
|
listAllPlans() {
|
|
563
592
|
return this.sqlite.prepare("SELECT * FROM plans ORDER BY created_at DESC").all();
|
|
564
593
|
}
|
|
565
|
-
listPlans(limit = 20, status) {
|
|
594
|
+
listPlans(limit = 20, status, scope) {
|
|
595
|
+
const conditions = [];
|
|
596
|
+
const params = [];
|
|
566
597
|
if (status) {
|
|
567
|
-
|
|
598
|
+
conditions.push("status = ?");
|
|
599
|
+
params.push(status);
|
|
600
|
+
}
|
|
601
|
+
if (scope) {
|
|
602
|
+
conditions.push("scope = ?");
|
|
603
|
+
params.push(scope);
|
|
568
604
|
}
|
|
569
|
-
|
|
605
|
+
const where = conditions.length ? "WHERE " + conditions.join(" AND ") : "";
|
|
606
|
+
params.push(limit);
|
|
607
|
+
return this.sqlite.prepare(`SELECT * FROM plans ${where} ORDER BY created_at DESC LIMIT ?`).all(...params);
|
|
608
|
+
}
|
|
609
|
+
findSimilarActivePlans(embedding, scope, threshold = 0.5) {
|
|
610
|
+
try {
|
|
611
|
+
const candidates = this.sqlite.prepare("SELECT id FROM plans WHERE scope = ? AND status IN ('draft', 'active')").all(scope);
|
|
612
|
+
if (!candidates.length)
|
|
613
|
+
return [];
|
|
614
|
+
const ids = candidates.map((c) => c.id);
|
|
615
|
+
const placeholders = ids.map(() => "?").join(",");
|
|
616
|
+
const rows = this.sqlite.prepare(`SELECT id, embedding FROM plans_embeddings WHERE id IN (${placeholders})`).all(...ids);
|
|
617
|
+
if (!rows.length)
|
|
618
|
+
return [];
|
|
619
|
+
const queryVec = new Float32Array(embedding);
|
|
620
|
+
return rows.map((row) => {
|
|
621
|
+
const vec = new Float32Array(row.embedding.buffer, row.embedding.byteOffset, row.embedding.byteLength / 4);
|
|
622
|
+
const sim = cosineSimilarity(queryVec, vec);
|
|
623
|
+
return { id: row.id, similarity: sim };
|
|
624
|
+
}).filter((r) => r.similarity >= threshold).sort((a, b) => b.similarity - a.similarity).map((r) => ({
|
|
625
|
+
plan: this.sqlite.prepare("SELECT * FROM plans WHERE id = ?").get(r.id),
|
|
626
|
+
similarity: r.similarity
|
|
627
|
+
}));
|
|
628
|
+
} catch {
|
|
629
|
+
return [];
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
archiveStaleDrafts(maxAgeHours = 24) {
|
|
633
|
+
const cutoff = new Date(Date.now() - maxAgeHours * 60 * 60 * 1e3).toISOString();
|
|
634
|
+
return this.sqlite.prepare("UPDATE plans SET status = 'archived', updated_at = ? WHERE status = 'draft' AND updated_at < ?").run((/* @__PURE__ */ new Date()).toISOString(), cutoff).changes;
|
|
635
|
+
}
|
|
636
|
+
deletePlanTasks(planId) {
|
|
637
|
+
return this.sqlite.prepare("DELETE FROM plan_tasks WHERE plan_id = ?").run(planId).changes;
|
|
638
|
+
}
|
|
639
|
+
updatePlanEmbeddingById(id, embedding) {
|
|
640
|
+
try {
|
|
641
|
+
updatePlanEmbedding(this.sqlite, id, embedding);
|
|
642
|
+
} catch {
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
insertEmbeddingById(id, embedding) {
|
|
646
|
+
insertEmbedding(this.sqlite, id, embedding);
|
|
647
|
+
}
|
|
648
|
+
insertPlanEmbeddingById(id, embedding) {
|
|
649
|
+
insertPlanEmbedding(this.sqlite, id, embedding);
|
|
570
650
|
}
|
|
571
651
|
// ─── Plan Relations ─────────────────────────────────────────
|
|
572
652
|
addPlanRelation(planId, knowledgeId, relationType) {
|
|
@@ -659,6 +739,17 @@ var KnowledgeRepository = class {
|
|
|
659
739
|
logOperation(operation) {
|
|
660
740
|
this.sqlite.prepare("INSERT INTO operations_log (operation, created_at) VALUES (?, ?)").run(operation, (/* @__PURE__ */ new Date()).toISOString());
|
|
661
741
|
}
|
|
742
|
+
logOperationBatch(operation, count) {
|
|
743
|
+
if (count <= 0)
|
|
744
|
+
return;
|
|
745
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
746
|
+
const stmt = this.sqlite.prepare("INSERT INTO operations_log (operation, created_at) VALUES (?, ?)");
|
|
747
|
+
const insertMany = this.sqlite.transaction((n) => {
|
|
748
|
+
for (let i = 0; i < n; i++)
|
|
749
|
+
stmt.run(operation, now);
|
|
750
|
+
});
|
|
751
|
+
insertMany(count);
|
|
752
|
+
}
|
|
662
753
|
getOperationCounts() {
|
|
663
754
|
const now = /* @__PURE__ */ new Date();
|
|
664
755
|
const oneHourAgo = new Date(now.getTime() - 60 * 60 * 1e3).toISOString();
|
|
@@ -708,33 +799,85 @@ var KnowledgeRepository = class {
|
|
|
708
799
|
const cutoff = new Date(Date.now() - 7 * 24 * 60 * 60 * 1e3).toISOString();
|
|
709
800
|
return this.sqlite.prepare("DELETE FROM operations_log WHERE created_at < ?").run(cutoff).changes;
|
|
710
801
|
}
|
|
802
|
+
/**
|
|
803
|
+
* Delete embeddings for completed/archived plans older than maxAgeDays.
|
|
804
|
+
* These plans are never searched semantically (only draft/active are).
|
|
805
|
+
*/
|
|
806
|
+
cleanupCompletedPlanEmbeddings(maxAgeDays = 30) {
|
|
807
|
+
const cutoff = new Date(Date.now() - maxAgeDays * 24 * 60 * 60 * 1e3).toISOString();
|
|
808
|
+
const oldPlans = this.sqlite.prepare(`SELECT id FROM plans WHERE status IN ('completed', 'archived') AND updated_at < ?`).all(cutoff);
|
|
809
|
+
if (oldPlans.length === 0)
|
|
810
|
+
return 0;
|
|
811
|
+
let removed = 0;
|
|
812
|
+
const deleteStmt = this.sqlite.prepare("DELETE FROM plans_embeddings WHERE id = ?");
|
|
813
|
+
for (const plan of oldPlans) {
|
|
814
|
+
const result = deleteStmt.run(plan.id);
|
|
815
|
+
removed += result.changes;
|
|
816
|
+
}
|
|
817
|
+
return removed;
|
|
818
|
+
}
|
|
711
819
|
};
|
|
820
|
+
function cosineSimilarity(a, b) {
|
|
821
|
+
let dot = 0, magA = 0, magB = 0;
|
|
822
|
+
for (let i = 0; i < a.length; i++) {
|
|
823
|
+
dot += a[i] * b[i];
|
|
824
|
+
magA += a[i] * a[i];
|
|
825
|
+
magB += b[i] * b[i];
|
|
826
|
+
}
|
|
827
|
+
const denom = Math.sqrt(magA) * Math.sqrt(magB);
|
|
828
|
+
return denom === 0 ? 0 : dot / denom;
|
|
829
|
+
}
|
|
712
830
|
|
|
713
831
|
// ../../packages/core/dist/services/knowledge.service.js
|
|
714
832
|
var KnowledgeService = class {
|
|
715
833
|
repository;
|
|
716
834
|
embeddingProvider;
|
|
835
|
+
lastArchiveRunMs = 0;
|
|
717
836
|
constructor(repository, embeddingProvider) {
|
|
718
837
|
this.repository = repository;
|
|
719
838
|
this.embeddingProvider = embeddingProvider;
|
|
720
839
|
}
|
|
721
|
-
logOp(op) {
|
|
840
|
+
logOp(op, count = 1) {
|
|
722
841
|
try {
|
|
723
|
-
|
|
842
|
+
if (count <= 1)
|
|
843
|
+
this.repository.logOperation(op);
|
|
844
|
+
else
|
|
845
|
+
this.repository.logOperationBatch(op, count);
|
|
724
846
|
} catch {
|
|
725
847
|
}
|
|
726
848
|
}
|
|
849
|
+
buildEmbeddingText(title, content, tags) {
|
|
850
|
+
return `${title} ${content} ${tags.join(" ")}`;
|
|
851
|
+
}
|
|
727
852
|
async add(input) {
|
|
728
|
-
const
|
|
729
|
-
const
|
|
730
|
-
const
|
|
853
|
+
const { skipDedup, ...rest } = input;
|
|
854
|
+
const embeddingText = this.buildEmbeddingText(rest.title, rest.content, rest.tags);
|
|
855
|
+
const embedding = await this.embeddingProvider.embed(embeddingText);
|
|
856
|
+
if (!skipDedup) {
|
|
857
|
+
try {
|
|
858
|
+
const similar = await this.repository.searchBySimilarity(embedding, {
|
|
859
|
+
scope: rest.scope,
|
|
860
|
+
type: rest.type,
|
|
861
|
+
limit: 1,
|
|
862
|
+
threshold: 0.85
|
|
863
|
+
});
|
|
864
|
+
if (similar.length > 0) {
|
|
865
|
+
const existing = similar[0].entry;
|
|
866
|
+
const updated = await this.repository.update(existing.id, { ...rest, embedding });
|
|
867
|
+
this.logOp("write");
|
|
868
|
+
return { ...this.toKnowledgeEntry(updated), deduplicated: true };
|
|
869
|
+
}
|
|
870
|
+
} catch {
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
const entry = await this.repository.create({ ...rest, embedding });
|
|
731
874
|
this.logOp("write");
|
|
732
875
|
return this.toKnowledgeEntry(entry);
|
|
733
876
|
}
|
|
734
877
|
async search(query, options) {
|
|
735
878
|
const queryEmbedding = await this.embeddingProvider.embed(query);
|
|
736
879
|
const results = await this.repository.searchBySimilarity(queryEmbedding, options);
|
|
737
|
-
this.logOp("read");
|
|
880
|
+
this.logOp("read", results.length);
|
|
738
881
|
return results.map((r) => ({
|
|
739
882
|
entry: this.toKnowledgeEntry(r.entry),
|
|
740
883
|
similarity: r.similarity
|
|
@@ -746,8 +889,12 @@ var KnowledgeService = class {
|
|
|
746
889
|
}
|
|
747
890
|
async update(id, updates) {
|
|
748
891
|
let embedding;
|
|
749
|
-
if (updates.
|
|
750
|
-
|
|
892
|
+
if (updates.content || updates.title || updates.tags) {
|
|
893
|
+
const current = await this.repository.findById(id);
|
|
894
|
+
const title = updates.title || current?.title || "";
|
|
895
|
+
const content = updates.content || current?.content || "";
|
|
896
|
+
const tags = updates.tags || (current?.tags ?? []);
|
|
897
|
+
embedding = await this.embeddingProvider.embed(this.buildEmbeddingText(title, content, tags));
|
|
751
898
|
}
|
|
752
899
|
const entry = await this.repository.update(id, { ...updates, embedding });
|
|
753
900
|
if (entry)
|
|
@@ -764,6 +911,43 @@ var KnowledgeService = class {
|
|
|
764
911
|
const entries = await this.repository.listAll();
|
|
765
912
|
return entries.map((e) => this.toKnowledgeEntry(e));
|
|
766
913
|
}
|
|
914
|
+
/**
|
|
915
|
+
* Re-embed all knowledge entries and plans with the current embedding provider.
|
|
916
|
+
* Used when switching embedding models (e.g. all-minilm 384d → nomic-embed-text 768d).
|
|
917
|
+
* Assumes vec tables have already been dropped and recreated with new dimensions.
|
|
918
|
+
*/
|
|
919
|
+
async reembedAll() {
|
|
920
|
+
let count = 0;
|
|
921
|
+
const entries = await this.repository.listAll();
|
|
922
|
+
for (const entry of entries) {
|
|
923
|
+
try {
|
|
924
|
+
const tags = Array.isArray(entry.tags) ? entry.tags : JSON.parse(entry.tags ?? "[]");
|
|
925
|
+
const text2 = this.buildEmbeddingText(entry.title, entry.content, tags);
|
|
926
|
+
const embedding = await this.embeddingProvider.embed(text2);
|
|
927
|
+
try {
|
|
928
|
+
this.repository.insertEmbeddingById(entry.id, embedding);
|
|
929
|
+
} catch {
|
|
930
|
+
}
|
|
931
|
+
count++;
|
|
932
|
+
} catch (e) {
|
|
933
|
+
console.warn(`[CogniStore] Re-embed failed for entry ${entry.id}:`, e);
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
const plans = this.repository.listAllPlans();
|
|
937
|
+
for (const plan of plans) {
|
|
938
|
+
try {
|
|
939
|
+
const embedding = await this.embeddingProvider.embed(`${plan.title} ${plan.content}`);
|
|
940
|
+
try {
|
|
941
|
+
this.repository.insertPlanEmbeddingById(plan.id, embedding);
|
|
942
|
+
} catch {
|
|
943
|
+
}
|
|
944
|
+
count++;
|
|
945
|
+
} catch (e) {
|
|
946
|
+
console.warn(`[CogniStore] Re-embed failed for plan ${plan.id}:`, e);
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
return count;
|
|
950
|
+
}
|
|
767
951
|
async listScopes() {
|
|
768
952
|
return this.repository.listScopes();
|
|
769
953
|
}
|
|
@@ -847,8 +1031,50 @@ var KnowledgeService = class {
|
|
|
847
1031
|
}
|
|
848
1032
|
// ─── Plans (separate entity) ────────────────────────────────
|
|
849
1033
|
async createPlan(input) {
|
|
850
|
-
const { tasks, ...planInput } = input;
|
|
851
|
-
const embedding = await this.embeddingProvider.embed(input.
|
|
1034
|
+
const { tasks, skipDedup, ...planInput } = input;
|
|
1035
|
+
const embedding = await this.embeddingProvider.embed(`${input.title} ${input.content}`);
|
|
1036
|
+
const now = Date.now();
|
|
1037
|
+
if (now - this.lastArchiveRunMs > 36e5) {
|
|
1038
|
+
try {
|
|
1039
|
+
this.repository.archiveStaleDrafts(24);
|
|
1040
|
+
this.lastArchiveRunMs = now;
|
|
1041
|
+
} catch {
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
const similarPlans = skipDedup ? [] : this.repository.findSimilarActivePlans(embedding, input.scope, 0.5);
|
|
1045
|
+
if (similarPlans.length > 0) {
|
|
1046
|
+
const { plan: existingRow } = similarPlans[0];
|
|
1047
|
+
const isActive = existingRow.status === "active";
|
|
1048
|
+
if (isActive) {
|
|
1049
|
+
if (tasks && tasks.length > 0) {
|
|
1050
|
+
for (const task of tasks) {
|
|
1051
|
+
this.repository.createPlanTask({ planId: existingRow.id, description: task.description, priority: task.priority });
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
const plan2 = this.toPlan(existingRow);
|
|
1055
|
+
return { ...plan2, deduplicated: true, deduplicatedAction: "tasks_added_to_active_plan" };
|
|
1056
|
+
} else {
|
|
1057
|
+
this.repository.updatePlan(existingRow.id, {
|
|
1058
|
+
title: input.title,
|
|
1059
|
+
content: input.content,
|
|
1060
|
+
tags: input.tags,
|
|
1061
|
+
source: input.source
|
|
1062
|
+
});
|
|
1063
|
+
if (tasks && tasks.length > 0) {
|
|
1064
|
+
this.repository.deletePlanTasks(existingRow.id);
|
|
1065
|
+
for (let i = 0; i < tasks.length; i++) {
|
|
1066
|
+
this.repository.createPlanTask({ planId: existingRow.id, description: tasks[i].description, priority: tasks[i].priority, position: i });
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
try {
|
|
1070
|
+
this.repository.updatePlanEmbeddingById(existingRow.id, embedding);
|
|
1071
|
+
} catch {
|
|
1072
|
+
}
|
|
1073
|
+
const updated = this.repository.getPlanById(existingRow.id);
|
|
1074
|
+
const plan2 = this.toPlan(updated);
|
|
1075
|
+
return { ...plan2, deduplicated: true, deduplicatedAction: "draft_plan_updated" };
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
852
1078
|
const row = this.repository.createPlan({ ...planInput, embedding });
|
|
853
1079
|
const plan = this.toPlan(row);
|
|
854
1080
|
if (tasks && tasks.length > 0) {
|
|
@@ -888,10 +1114,13 @@ var KnowledgeService = class {
|
|
|
888
1114
|
const rows = this.repository.listAllPlans();
|
|
889
1115
|
return rows.map((r) => this.toPlan(r));
|
|
890
1116
|
}
|
|
891
|
-
listPlans(limit = 20, status) {
|
|
892
|
-
const rows = this.repository.listPlans(limit, status);
|
|
1117
|
+
listPlans(limit = 20, status, scope) {
|
|
1118
|
+
const rows = this.repository.listPlans(limit, status, scope);
|
|
893
1119
|
return rows.map((r) => this.toPlan(r));
|
|
894
1120
|
}
|
|
1121
|
+
archiveStaleDrafts(maxAgeHours = 24) {
|
|
1122
|
+
return this.repository.archiveStaleDrafts(maxAgeHours);
|
|
1123
|
+
}
|
|
895
1124
|
async importPlans(plans) {
|
|
896
1125
|
let imported = 0;
|
|
897
1126
|
let skipped = 0;
|
|
@@ -1007,6 +1236,9 @@ var KnowledgeService = class {
|
|
|
1007
1236
|
cleanupOldOperations() {
|
|
1008
1237
|
return this.repository.cleanupOldOperations();
|
|
1009
1238
|
}
|
|
1239
|
+
cleanupCompletedPlanEmbeddings(maxAgeDays = 30) {
|
|
1240
|
+
return this.repository.cleanupCompletedPlanEmbeddings(maxAgeDays);
|
|
1241
|
+
}
|
|
1010
1242
|
// ─── Converters ─────────────────────────────────────────────
|
|
1011
1243
|
toKnowledgeEntry(row) {
|
|
1012
1244
|
return {
|
|
@@ -1061,16 +1293,25 @@ var OllamaEmbeddingClient = class {
|
|
|
1061
1293
|
model;
|
|
1062
1294
|
dimensions;
|
|
1063
1295
|
maxRetries;
|
|
1296
|
+
maxInputChars;
|
|
1064
1297
|
constructor(config) {
|
|
1065
1298
|
this.host = config?.host ?? (process.env.OLLAMA_HOST ?? DEFAULT_OLLAMA_HOST);
|
|
1066
1299
|
this.model = config?.model ?? (process.env.OLLAMA_MODEL ?? DEFAULT_EMBEDDING_MODEL);
|
|
1067
1300
|
this.dimensions = config?.dimensions ?? (Number(process.env.EMBEDDING_DIMENSIONS) || DEFAULT_EMBEDDING_DIMENSIONS);
|
|
1068
1301
|
this.maxRetries = config?.maxRetries ?? 3;
|
|
1302
|
+
this.maxInputChars = config?.maxInputChars ?? 2e3;
|
|
1303
|
+
}
|
|
1304
|
+
truncateText(text2, maxChars) {
|
|
1305
|
+
if (text2.length <= maxChars)
|
|
1306
|
+
return text2;
|
|
1307
|
+
const truncated = text2.slice(0, maxChars);
|
|
1308
|
+
const lastSpace = truncated.lastIndexOf(" ");
|
|
1309
|
+
return lastSpace > maxChars * 0.5 ? truncated.slice(0, lastSpace) : truncated;
|
|
1069
1310
|
}
|
|
1070
1311
|
async embed(text2) {
|
|
1071
1312
|
const body = {
|
|
1072
1313
|
model: this.model,
|
|
1073
|
-
prompt: text2
|
|
1314
|
+
prompt: this.truncateText(text2, this.maxInputChars)
|
|
1074
1315
|
};
|
|
1075
1316
|
const response = await this.fetchWithRetry(`${this.host}/api/embeddings`, {
|
|
1076
1317
|
method: "POST",
|
|
@@ -1085,8 +1326,11 @@ var OllamaEmbeddingClient = class {
|
|
|
1085
1326
|
if (!data.embedding || !Array.isArray(data.embedding)) {
|
|
1086
1327
|
throw new Error("Invalid embedding response from Ollama");
|
|
1087
1328
|
}
|
|
1088
|
-
if (data.embedding.length
|
|
1089
|
-
throw new Error(`Embedding dimension mismatch: expected ${this.dimensions}, got ${data.embedding.length}. Check that OLLAMA_MODEL and EMBEDDING_DIMENSIONS are compatible.`);
|
|
1329
|
+
if (data.embedding.length < this.dimensions) {
|
|
1330
|
+
throw new Error(`Embedding dimension mismatch: expected at least ${this.dimensions}, got ${data.embedding.length}. Check that OLLAMA_MODEL and EMBEDDING_DIMENSIONS are compatible.`);
|
|
1331
|
+
}
|
|
1332
|
+
if (data.embedding.length > this.dimensions) {
|
|
1333
|
+
return this.truncateAndNormalize(data.embedding, this.dimensions);
|
|
1090
1334
|
}
|
|
1091
1335
|
return data.embedding;
|
|
1092
1336
|
}
|
|
@@ -1149,6 +1393,17 @@ var OllamaEmbeddingClient = class {
|
|
|
1149
1393
|
dimensions: this.dimensions
|
|
1150
1394
|
};
|
|
1151
1395
|
}
|
|
1396
|
+
/**
|
|
1397
|
+
* Matryoshka truncation: slice to first targetDims dimensions, then L2-normalize.
|
|
1398
|
+
* L2 normalization is critical for cosine similarity quality after truncation.
|
|
1399
|
+
*/
|
|
1400
|
+
truncateAndNormalize(embedding, targetDims) {
|
|
1401
|
+
const truncated = embedding.slice(0, targetDims);
|
|
1402
|
+
const norm = Math.sqrt(truncated.reduce((sum, v) => sum + v * v, 0));
|
|
1403
|
+
if (norm === 0)
|
|
1404
|
+
return truncated;
|
|
1405
|
+
return truncated.map((v) => v / norm);
|
|
1406
|
+
}
|
|
1152
1407
|
async fetchWithRetry(url, init) {
|
|
1153
1408
|
let lastError;
|
|
1154
1409
|
for (let attempt = 0; attempt < this.maxRetries; attempt++) {
|
|
@@ -1263,6 +1518,7 @@ var KnowledgeSDK = class {
|
|
|
1263
1518
|
} catch (error) {
|
|
1264
1519
|
throw new EmbeddingError(`Failed to ensure embedding model: ${error instanceof Error ? error.message : String(error)}`);
|
|
1265
1520
|
}
|
|
1521
|
+
await this.detectAndMigrateDimensions();
|
|
1266
1522
|
const repository = new KnowledgeRepository(this.db, this.sqlite);
|
|
1267
1523
|
this.service = new KnowledgeService(repository, this.ollamaClient);
|
|
1268
1524
|
this.initialized = true;
|
|
@@ -1418,13 +1674,13 @@ var KnowledgeSDK = class {
|
|
|
1418
1674
|
// ─── Plans (separate entity) ─────────────────────────────────
|
|
1419
1675
|
async createPlan(input) {
|
|
1420
1676
|
this.ensureInitialized();
|
|
1421
|
-
const { relatedKnowledgeIds,
|
|
1677
|
+
const { relatedKnowledgeIds, ...rest } = input;
|
|
1422
1678
|
const parsed = createPlanSchema.safeParse(rest);
|
|
1423
1679
|
if (!parsed.success) {
|
|
1424
1680
|
throw new ValidationError(`Invalid plan input: ${parsed.error.message}`);
|
|
1425
1681
|
}
|
|
1426
1682
|
try {
|
|
1427
|
-
const plan = await this.service.createPlan(
|
|
1683
|
+
const plan = await this.service.createPlan(parsed.data);
|
|
1428
1684
|
if (relatedKnowledgeIds) {
|
|
1429
1685
|
for (const kid of relatedKnowledgeIds) {
|
|
1430
1686
|
try {
|
|
@@ -1450,9 +1706,9 @@ var KnowledgeSDK = class {
|
|
|
1450
1706
|
this.ensureInitialized();
|
|
1451
1707
|
return this.service.deletePlan(id);
|
|
1452
1708
|
}
|
|
1453
|
-
listPlans(limit = 20, status) {
|
|
1709
|
+
listPlans(limit = 20, status, scope) {
|
|
1454
1710
|
this.ensureInitialized();
|
|
1455
|
-
return this.service.listPlans(limit, status);
|
|
1711
|
+
return this.service.listPlans(limit, status, scope);
|
|
1456
1712
|
}
|
|
1457
1713
|
async addPlanRelation(planId, knowledgeId, relationType) {
|
|
1458
1714
|
this.ensureInitialized();
|
|
@@ -1501,6 +1757,14 @@ var KnowledgeSDK = class {
|
|
|
1501
1757
|
this.ensureInitialized();
|
|
1502
1758
|
return this.service.getPlanTaskStats();
|
|
1503
1759
|
}
|
|
1760
|
+
/**
|
|
1761
|
+
* Re-embed all knowledge entries and plans with the current embedding model.
|
|
1762
|
+
* Used during upgrade when switching embedding models (dimensions change).
|
|
1763
|
+
*/
|
|
1764
|
+
async reembedAll() {
|
|
1765
|
+
this.ensureInitialized();
|
|
1766
|
+
return this.service.reembedAll();
|
|
1767
|
+
}
|
|
1504
1768
|
// ─── Operations ─────────────────────────────────────────────
|
|
1505
1769
|
getOperationCounts() {
|
|
1506
1770
|
this.ensureInitialized();
|
|
@@ -1515,6 +1779,11 @@ var KnowledgeSDK = class {
|
|
|
1515
1779
|
return 0;
|
|
1516
1780
|
return this.service.cleanupOldOperations();
|
|
1517
1781
|
}
|
|
1782
|
+
cleanupCompletedPlanEmbeddings(maxAgeDays = 30) {
|
|
1783
|
+
if (!this.initialized || !this.service)
|
|
1784
|
+
return 0;
|
|
1785
|
+
return this.service.cleanupCompletedPlanEmbeddings(maxAgeDays);
|
|
1786
|
+
}
|
|
1518
1787
|
async healthCheck() {
|
|
1519
1788
|
const ollamaHealth = await checkOllamaHealth(this.ollamaClient);
|
|
1520
1789
|
let dbConnected = false;
|
|
@@ -1563,6 +1832,14 @@ var KnowledgeSDK = class {
|
|
|
1563
1832
|
throw this.wrapError(error, "Failed to cleanup database");
|
|
1564
1833
|
}
|
|
1565
1834
|
}
|
|
1835
|
+
/**
|
|
1836
|
+
* Run a passive WAL checkpoint to keep the WAL file small.
|
|
1837
|
+
* PASSIVE mode does not block readers or writers.
|
|
1838
|
+
*/
|
|
1839
|
+
walCheckpoint() {
|
|
1840
|
+
this.ensureInitialized();
|
|
1841
|
+
this.sqlite.pragma("wal_checkpoint(PASSIVE)");
|
|
1842
|
+
}
|
|
1566
1843
|
ensureInitialized() {
|
|
1567
1844
|
if (!this.initialized || !this.service) {
|
|
1568
1845
|
throw new ConnectionError("SDK not initialized. Call initialize() first.");
|
|
@@ -1579,6 +1856,38 @@ var KnowledgeSDK = class {
|
|
|
1579
1856
|
this.db = null;
|
|
1580
1857
|
this.service = null;
|
|
1581
1858
|
}
|
|
1859
|
+
/**
|
|
1860
|
+
* Detect if stored embeddings have different dimensions than config.
|
|
1861
|
+
* If mismatch found (e.g., 768→256 Matryoshka migration), drop vec tables,
|
|
1862
|
+
* recreate at new dimensions, and re-embed all entries.
|
|
1863
|
+
*/
|
|
1864
|
+
async detectAndMigrateDimensions() {
|
|
1865
|
+
const targetDims = this.config.ollama.dimensions;
|
|
1866
|
+
try {
|
|
1867
|
+
const sampleRow = this.sqlite.prepare("SELECT embedding FROM knowledge_embeddings LIMIT 1").get();
|
|
1868
|
+
if (!sampleRow)
|
|
1869
|
+
return;
|
|
1870
|
+
const currentDims = sampleRow.embedding.byteLength / 4;
|
|
1871
|
+
if (currentDims === targetDims)
|
|
1872
|
+
return;
|
|
1873
|
+
console.error(`[CogniStore] Embedding dimension mismatch: DB has ${currentDims}d, config wants ${targetDims}d. Re-embedding all entries...`);
|
|
1874
|
+
this.sqlite.exec("DROP TABLE IF EXISTS knowledge_embeddings");
|
|
1875
|
+
this.sqlite.exec("DROP TABLE IF EXISTS plans_embeddings");
|
|
1876
|
+
createEmbeddingsTable(this.sqlite, targetDims);
|
|
1877
|
+
this.sqlite.exec(`
|
|
1878
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS plans_embeddings USING vec0(
|
|
1879
|
+
id TEXT PRIMARY KEY,
|
|
1880
|
+
embedding float[${targetDims}] distance_metric=cosine
|
|
1881
|
+
)
|
|
1882
|
+
`);
|
|
1883
|
+
const repository = new KnowledgeRepository(this.db, this.sqlite);
|
|
1884
|
+
const tempService = new KnowledgeService(repository, this.ollamaClient);
|
|
1885
|
+
const count = await tempService.reembedAll();
|
|
1886
|
+
console.error(`[CogniStore] Re-embedded ${count} entries at ${targetDims} dimensions`);
|
|
1887
|
+
} catch (error) {
|
|
1888
|
+
console.error(`[CogniStore] Dimension migration failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1582
1891
|
wrapError(error, context) {
|
|
1583
1892
|
if (error instanceof Error && error.name.endsWith("Error")) {
|
|
1584
1893
|
return error;
|
|
@@ -1685,19 +1994,26 @@ function createServer(sdk) {
|
|
|
1685
1994
|
});
|
|
1686
1995
|
lastSearchResultIds = results.map((r) => r.entry.id);
|
|
1687
1996
|
const response = { results };
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1997
|
+
if (params.scope) {
|
|
1998
|
+
try {
|
|
1999
|
+
const activePlans = sdk.listPlans(1, "active", params.scope);
|
|
2000
|
+
const draftPlans = sdk.listPlans(1, "draft", params.scope);
|
|
2001
|
+
const currentPlan = activePlans[0] || draftPlans[0];
|
|
2002
|
+
if (currentPlan) {
|
|
2003
|
+
const tasks = sdk.listPlanTasks(currentPlan.id);
|
|
2004
|
+
const completedTasks = tasks.filter((t) => t.status === "completed").length;
|
|
2005
|
+
response.activePlan = {
|
|
2006
|
+
id: currentPlan.id,
|
|
2007
|
+
title: currentPlan.title,
|
|
2008
|
+
status: currentPlan.status,
|
|
2009
|
+
scope: currentPlan.scope,
|
|
2010
|
+
taskCount: tasks.length,
|
|
2011
|
+
completedTasks,
|
|
2012
|
+
hint: `You have an active plan (${completedTasks}/${tasks.length} tasks done). Use updatePlan(planId, ...) to modify it or updatePlanTask() to track progress. Do NOT call createPlan() \u2014 it will auto-deduplicate into this plan.`
|
|
2013
|
+
};
|
|
2014
|
+
}
|
|
2015
|
+
} catch {
|
|
1699
2016
|
}
|
|
1700
|
-
} catch {
|
|
1701
2017
|
}
|
|
1702
2018
|
return { content: [{ type: "text", text: JSON.stringify(response, null, 2) }] };
|
|
1703
2019
|
}
|
|
@@ -1795,9 +2111,12 @@ function createServer(sdk) {
|
|
|
1795
2111
|
tasks: params.tasks
|
|
1796
2112
|
});
|
|
1797
2113
|
lastSearchResultIds = [];
|
|
2114
|
+
const deduplicated = result.deduplicated === true;
|
|
2115
|
+
const deduplicatedAction = result.deduplicatedAction;
|
|
2116
|
+
const reminder = deduplicated ? `Existing plan "${result.title}" was reused (${deduplicatedAction === "tasks_added_to_active_plan" ? "new tasks added to active plan" : "draft plan updated"}). Plan ID: "${result.id}". Pass this planId to addKnowledge calls.` : `Your plan ID is "${result.id}". Pass planId: "${result.id}" to every addKnowledge call for output linking. Plan auto-activates when you start the first task.`;
|
|
1798
2117
|
const response = {
|
|
1799
2118
|
...result,
|
|
1800
|
-
reminder
|
|
2119
|
+
reminder
|
|
1801
2120
|
};
|
|
1802
2121
|
return { content: [{ type: "text", text: JSON.stringify(response, null, 2) }] };
|
|
1803
2122
|
}
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cognistore/mcp-server",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.14",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "MCP server for CogniStore — integrates with Claude Code and GitHub Copilot",
|
|
7
7
|
"license": "BUSL-1.1",
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
10
|
-
"url": "https://github.com/Sithion/cognistore",
|
|
10
|
+
"url": "git+https://github.com/Sithion/cognistore.git",
|
|
11
11
|
"directory": "apps/mcp-server"
|
|
12
12
|
},
|
|
13
13
|
"main": "./dist/index.js",
|