@cognistore/mcp-server 2.2.2 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +214 -32
  2. package/package.json +3 -2
package/dist/index.js CHANGED
@@ -15,13 +15,13 @@ import * as sqliteVec from "sqlite-vec";
15
15
 
16
16
  // ../../packages/shared/dist/types/knowledge.js
17
17
  var KnowledgeType;
18
- (function(KnowledgeType2) {
19
- KnowledgeType2["DECISION"] = "decision";
20
- KnowledgeType2["PATTERN"] = "pattern";
21
- KnowledgeType2["FIX"] = "fix";
22
- KnowledgeType2["CONSTRAINT"] = "constraint";
23
- KnowledgeType2["GOTCHA"] = "gotcha";
24
- KnowledgeType2["SYSTEM"] = "system";
18
+ (function(KnowledgeType3) {
19
+ KnowledgeType3["DECISION"] = "decision";
20
+ KnowledgeType3["PATTERN"] = "pattern";
21
+ KnowledgeType3["FIX"] = "fix";
22
+ KnowledgeType3["CONSTRAINT"] = "constraint";
23
+ KnowledgeType3["GOTCHA"] = "gotcha";
24
+ KnowledgeType3["SYSTEM"] = "system";
25
25
  })(KnowledgeType || (KnowledgeType = {}));
26
26
  var KnowledgeStatus;
27
27
  (function(KnowledgeStatus2) {
@@ -74,7 +74,8 @@ var createKnowledgeSchema = z.object({
74
74
  confidenceScore: z.number().min(0).max(1).optional().default(1),
75
75
  expiresAt: z.date().nullable().optional().default(null),
76
76
  relatedIds: z.array(z.string().uuid()).nullable().optional().default(null),
77
- agentId: z.string().nullable().optional().default(null)
77
+ agentId: z.string().max(64).nullable().optional().default(null),
78
+ platform: z.string().max(64).nullable().optional().default(null)
78
79
  });
79
80
  var updateKnowledgeSchema = z.object({
80
81
  title: z.string().min(1).optional(),
@@ -86,7 +87,8 @@ var updateKnowledgeSchema = z.object({
86
87
  confidenceScore: z.number().min(0).max(1).optional(),
87
88
  expiresAt: z.date().nullable().optional(),
88
89
  relatedIds: z.array(z.string().uuid()).nullable().optional(),
89
- agentId: z.string().nullable().optional()
90
+ agentId: z.string().max(64).nullable().optional(),
91
+ platform: z.string().max(64).nullable().optional()
90
92
  });
91
93
  var searchOptionsSchema = z.object({
92
94
  tags: z.array(z.string()).optional(),
@@ -105,6 +107,8 @@ var createPlanSchema = z.object({
105
107
  source: z.string().min(1, "Source is required"),
106
108
  status: knowledgeStatusSchema.optional().default(KnowledgeStatus.DRAFT),
107
109
  planFilePath: z.string().min(1).nullable().optional(),
110
+ agentId: z.string().max(64).nullable().optional(),
111
+ platform: z.string().max(64).nullable().optional(),
108
112
  tasks: z.array(z.object({
109
113
  description: z.string().min(1),
110
114
  priority: z.enum(["low", "medium", "high"]).optional()
@@ -117,7 +121,9 @@ var updatePlanSchema = z.object({
117
121
  scope: scopeSchema.optional(),
118
122
  status: knowledgeStatusSchema.optional(),
119
123
  source: z.string().min(1).optional(),
120
- planFilePath: z.string().min(1).nullable().optional()
124
+ planFilePath: z.string().min(1).nullable().optional(),
125
+ agentId: z.string().max(64).nullable().optional(),
126
+ platform: z.string().max(64).nullable().optional()
121
127
  });
122
128
  var createPlanTaskSchema = z.object({
123
129
  planId: z.string().min(1),
@@ -140,6 +146,25 @@ var mergeTagsBatchSchema = z.object({
140
146
  to: z.string().min(1, "to is required")
141
147
  })).min(1, "at least one merge is required").max(50, "at most 50 merges per batch")
142
148
  });
149
+ var MAX_IMPORT_ITEMS = 5e4;
150
+ var importKnowledgeEntrySchema = z.object({
151
+ title: z.string().max(2e3).optional(),
152
+ content: z.string().min(1).max(5e5),
153
+ tags: z.array(z.string().max(200)).max(200).optional().default([]),
154
+ type: knowledgeTypeSchema,
155
+ scope: z.string().min(1).max(300),
156
+ source: z.string().max(300).optional(),
157
+ confidenceScore: z.number().min(0).max(1).optional(),
158
+ expiresAt: z.string().max(64).nullable().optional(),
159
+ relatedIds: z.array(z.string().max(64)).nullable().optional(),
160
+ agentId: z.string().max(200).nullable().optional(),
161
+ platform: z.string().max(200).nullable().optional()
162
+ });
163
+ var importSchema = z.object({
164
+ include: z.array(z.string().max(50)).max(10).optional().default([]),
165
+ knowledge: z.array(importKnowledgeEntrySchema).max(MAX_IMPORT_ITEMS).optional(),
166
+ plans: z.array(z.record(z.unknown())).max(MAX_IMPORT_ITEMS).optional()
167
+ });
143
168
 
144
169
  // ../../packages/core/dist/db/schema/index.js
145
170
  var schema_exports = {};
@@ -174,11 +199,13 @@ var knowledgeEntries = sqliteTable("knowledge_entries", {
174
199
  confidenceScore: real("confidence_score").notNull().default(1),
175
200
  relatedIds: text("related_ids", { mode: "json" }).$type(),
176
201
  agentId: text("agent_id"),
202
+ platform: text("platform"),
177
203
  createdAt: text("created_at").notNull().$defaultFn(() => (/* @__PURE__ */ new Date()).toISOString()),
178
204
  updatedAt: text("updated_at").notNull().$defaultFn(() => (/* @__PURE__ */ new Date()).toISOString())
179
205
  }, (table) => [
180
206
  index("idx_type").on(table.type),
181
- index("idx_scope").on(table.scope)
207
+ index("idx_scope").on(table.scope),
208
+ index("idx_knowledge_platform").on(table.platform)
182
209
  ]);
183
210
 
184
211
  // ../../packages/core/dist/db/schema/sqlite-vec.js
@@ -419,6 +446,16 @@ ALTER TABLE plans ADD COLUMN plan_file_path TEXT;
419
446
  `,
420
447
  "2.1.0": `
421
448
  CREATE VIRTUAL TABLE IF NOT EXISTS knowledge_fts USING fts5(id UNINDEXED, title, content, tags);
449
+ `,
450
+ // v2.3.0: provenance tracking — platform (auto) + agent_id (caller) on entries and plans.
451
+ // knowledge_entries.agent_id already exists since 0.8.0; only platform is new there.
452
+ "2.3.0": `
453
+ ALTER TABLE knowledge_entries ADD COLUMN platform TEXT;
454
+ ALTER TABLE plans ADD COLUMN agent_id TEXT;
455
+ ALTER TABLE plans ADD COLUMN platform TEXT;
456
+ CREATE INDEX IF NOT EXISTS idx_knowledge_platform ON knowledge_entries(platform);
457
+ CREATE INDEX IF NOT EXISTS idx_plans_platform ON plans(platform);
458
+ CREATE INDEX IF NOT EXISTS idx_plans_agent ON plans(agent_id);
422
459
  `
423
460
  };
424
461
  function runMigrations(sqlite, migrationsDir) {
@@ -563,7 +600,7 @@ function createPlansEmbeddingsTable(sqlite, dimensions = DEFAULT_EMBEDDING_DIMEN
563
600
 
564
601
  // ../../packages/core/dist/repositories/knowledge.repository.js
565
602
  import { eq, ne, sql, and, or, isNull } from "drizzle-orm";
566
- var OPERATIONS_RETENTION_DAYS = 30;
603
+ var OPERATIONS_RETENTION_DAYS = 800;
567
604
  var KnowledgeRepository = class _KnowledgeRepository {
568
605
  db;
569
606
  sqlite;
@@ -585,8 +622,8 @@ var KnowledgeRepository = class _KnowledgeRepository {
585
622
  const now = (/* @__PURE__ */ new Date()).toISOString();
586
623
  const insertTxn = this.sqlite.transaction(() => {
587
624
  this.sqlite.prepare(`INSERT INTO knowledge_entries
588
- (id, title, content, tags, type, scope, source, version, expires_at, confidence_score, related_ids, agent_id, created_at, updated_at)
589
- VALUES (?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?)`).run(id, input.title, input.content, JSON.stringify(input.tags ?? []), input.type, input.scope, input.source, input.expiresAt ? input.expiresAt.toISOString() : null, input.confidenceScore ?? 1, input.relatedIds ? JSON.stringify(input.relatedIds) : null, input.agentId ?? null, now, now);
625
+ (id, title, content, tags, type, scope, source, version, expires_at, confidence_score, related_ids, agent_id, platform, created_at, updated_at)
626
+ VALUES (?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?, ?)`).run(id, input.title, input.content, JSON.stringify(input.tags ?? []), input.type, input.scope, input.source, input.expiresAt ? input.expiresAt.toISOString() : null, input.confidenceScore ?? 1, input.relatedIds ? JSON.stringify(input.relatedIds) : null, input.agentId ?? null, input.platform ?? null, now, now);
590
627
  insertEmbedding(this.sqlite, id, input.embedding);
591
628
  try {
592
629
  insertFts(this.sqlite, { id, title: input.title, content: input.content, tags: ftsTags(input.tags) });
@@ -694,6 +731,12 @@ var KnowledgeRepository = class _KnowledgeRepository {
694
731
  conditions.push(sql`${knowledgeEntries.type} = ${filters.type}`);
695
732
  if (filters?.scope)
696
733
  conditions.push(sql`${knowledgeEntries.scope} = ${filters.scope}`);
734
+ if (filters?.agent) {
735
+ conditions.push(filters.agent === "unspecified" ? isNull(knowledgeEntries.agentId) : sql`${knowledgeEntries.agentId} = ${filters.agent}`);
736
+ }
737
+ if (filters?.platform) {
738
+ conditions.push(filters.platform === "unknown" ? or(isNull(knowledgeEntries.platform), sql`${knowledgeEntries.platform} = 'unknown'`) : sql`${knowledgeEntries.platform} = ${filters.platform}`);
739
+ }
697
740
  if (filters?.tags && filters.tags.length > 0) {
698
741
  const tagConditions = filters.tags.map((tag) => sql`EXISTS (SELECT 1 FROM json_each(${knowledgeEntries.tags}) WHERE value = ${tag})`);
699
742
  conditions.push(or(...tagConditions));
@@ -944,6 +987,39 @@ var KnowledgeRepository = class _KnowledgeRepository {
944
987
  }).from(knowledgeEntries).where(and(...conditions)).groupBy(knowledgeEntries.scope);
945
988
  return results.map((r) => ({ scope: r.scope, count: Number(r.count) }));
946
989
  }
990
+ /**
991
+ * Count entries by the agent that created them. agent_id is caller-provided and
992
+ * NULL when not passed; COALESCE collapses NULL into a single "unspecified"
993
+ * bucket (the chart label the frontend round-trips back to IS NULL on filter).
994
+ */
995
+ async countByAgent(opts = {}) {
996
+ const { from, to } = opts;
997
+ const conditions = [ne(knowledgeEntries.type, "system")];
998
+ if (from && to) {
999
+ conditions.push(sql`${knowledgeEntries.createdAt} >= ${from}`);
1000
+ conditions.push(sql`${knowledgeEntries.createdAt} < ${to}`);
1001
+ }
1002
+ const agentLabel = sql`COALESCE(${knowledgeEntries.agentId}, 'unspecified')`;
1003
+ const results = await this.db.select({ agent: agentLabel, count: sql`count(*)` }).from(knowledgeEntries).where(and(...conditions)).groupBy(agentLabel);
1004
+ return results.map((r) => ({ agent: r.agent, count: Number(r.count) }));
1005
+ }
1006
+ /**
1007
+ * Count entries by the platform that created them. platform is NULL for
1008
+ * pre-2.3.0 / dashboard-created rows and the literal "unknown" for MCP rows
1009
+ * whose client couldn't be resolved; COALESCE merges both into one "unknown"
1010
+ * bucket so they don't render as two slices.
1011
+ */
1012
+ async countByPlatform(opts = {}) {
1013
+ const { from, to } = opts;
1014
+ const conditions = [ne(knowledgeEntries.type, "system")];
1015
+ if (from && to) {
1016
+ conditions.push(sql`${knowledgeEntries.createdAt} >= ${from}`);
1017
+ conditions.push(sql`${knowledgeEntries.createdAt} < ${to}`);
1018
+ }
1019
+ const platformLabel = sql`COALESCE(${knowledgeEntries.platform}, 'unknown')`;
1020
+ const results = await this.db.select({ platform: platformLabel, count: sql`count(*)` }).from(knowledgeEntries).where(and(...conditions)).groupBy(platformLabel);
1021
+ return results.map((r) => ({ platform: r.platform, count: Number(r.count) }));
1022
+ }
947
1023
  async listAll() {
948
1024
  return this.db.select().from(knowledgeEntries).where(ne(knowledgeEntries.type, "system")).orderBy(sql`${knowledgeEntries.createdAt} DESC`);
949
1025
  }
@@ -958,7 +1034,7 @@ var KnowledgeRepository = class _KnowledgeRepository {
958
1034
  createPlan(input) {
959
1035
  const id = crypto.randomUUID();
960
1036
  const now = (/* @__PURE__ */ new Date()).toISOString();
961
- this.sqlite.prepare("INSERT INTO plans (id, title, content, tags, scope, status, source, plan_file_path, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)").run(id, input.title, input.content, JSON.stringify(input.tags), input.scope, input.status ?? "draft", input.source, input.planFilePath ?? null, now, now);
1037
+ this.sqlite.prepare("INSERT INTO plans (id, title, content, tags, scope, status, source, plan_file_path, agent_id, platform, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)").run(id, input.title, input.content, JSON.stringify(input.tags), input.scope, input.status ?? "draft", input.source, input.planFilePath ?? null, input.agentId ?? null, input.platform ?? null, now, now);
962
1038
  try {
963
1039
  insertPlanEmbedding(this.sqlite, id, input.embedding);
964
1040
  } catch {
@@ -1230,6 +1306,28 @@ var KnowledgeRepository = class _KnowledgeRepository {
1230
1306
  }
1231
1307
  return Object.entries(map).map(([date, counts]) => ({ date, ...counts }));
1232
1308
  }
1309
+ /** Plans created per day, zero-filled for every day in the window (mirrors getOperationsByDay). */
1310
+ getPlansByDay(days = 15) {
1311
+ const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1e3).toISOString();
1312
+ const rows = this.sqlite.prepare(`
1313
+ SELECT date(created_at) as date, COUNT(*) as count
1314
+ FROM plans
1315
+ WHERE created_at >= ?
1316
+ GROUP BY date(created_at)
1317
+ ORDER BY date(created_at)
1318
+ `).all(cutoff);
1319
+ const map = {};
1320
+ const now = /* @__PURE__ */ new Date();
1321
+ for (let i = days - 1; i >= 0; i--) {
1322
+ const d = new Date(now);
1323
+ d.setDate(d.getDate() - i);
1324
+ map[d.toISOString().split("T")[0]] = 0;
1325
+ }
1326
+ for (const row of rows) {
1327
+ map[row.date] = row.count;
1328
+ }
1329
+ return Object.entries(map).map(([date, count]) => ({ date, count }));
1330
+ }
1233
1331
  cleanupOldOperations() {
1234
1332
  const cutoff = new Date(Date.now() - OPERATIONS_RETENTION_DAYS * 24 * 60 * 60 * 1e3).toISOString();
1235
1333
  return this.sqlite.prepare("DELETE FROM operations_log WHERE created_at < ?").run(cutoff).changes;
@@ -1346,7 +1444,13 @@ var KnowledgeService = class {
1346
1444
  });
1347
1445
  if (similar.length > 0) {
1348
1446
  const existing = similar[0].entry;
1349
- const updated = await this.repository.update(existing.id, { ...rest, embedding });
1447
+ const { platform, agentId, ...keep } = rest;
1448
+ const prov = {};
1449
+ if (platform != null)
1450
+ prov.platform = platform;
1451
+ if (agentId != null)
1452
+ prov.agentId = agentId;
1453
+ const updated = await this.repository.update(existing.id, { ...keep, ...prov, embedding });
1350
1454
  this.logOp("write");
1351
1455
  return { ...this.toKnowledgeEntry(updated), deduplicated: true };
1352
1456
  }
@@ -1731,6 +1835,12 @@ var KnowledgeService = class {
1731
1835
  async countByScope(opts = {}) {
1732
1836
  return this.repository.countByScope(opts);
1733
1837
  }
1838
+ async countByAgent(opts = {}) {
1839
+ return this.repository.countByAgent(opts);
1840
+ }
1841
+ async countByPlatform(opts = {}) {
1842
+ return this.repository.countByPlatform(opts);
1843
+ }
1734
1844
  // ─── Plans (separate entity) ────────────────────────────────
1735
1845
  async createPlan(input) {
1736
1846
  const { tasks, skipDedup, ...planInput } = input;
@@ -1978,6 +2088,9 @@ var KnowledgeService = class {
1978
2088
  getOperationsByDay(days = 15) {
1979
2089
  return this.repository.getOperationsByDay(days);
1980
2090
  }
2091
+ getPlansByDay(days = 15) {
2092
+ return this.repository.getPlansByDay(days);
2093
+ }
1981
2094
  cleanupOldOperations() {
1982
2095
  return this.repository.cleanupOldOperations();
1983
2096
  }
@@ -1999,7 +2112,8 @@ var KnowledgeService = class {
1999
2112
  expiresAt: row.expiresAt ? new Date(row.expiresAt) : null,
2000
2113
  confidenceScore: row.confidenceScore,
2001
2114
  relatedIds: row.relatedIds ? Array.isArray(row.relatedIds) ? row.relatedIds : JSON.parse(row.relatedIds) : null,
2002
- agentId: row.agentId,
2115
+ agentId: row.agentId ?? row.agent_id ?? null,
2116
+ platform: row.platform ?? null,
2003
2117
  createdAt: new Date(row.createdAt ?? row.created_at),
2004
2118
  updatedAt: new Date(row.updatedAt ?? row.updated_at)
2005
2119
  };
@@ -2014,6 +2128,8 @@ var KnowledgeService = class {
2014
2128
  status: row.status,
2015
2129
  source: row.source ?? "",
2016
2130
  planFilePath: row.plan_file_path ?? row.planFilePath ?? null,
2131
+ agentId: row.agent_id ?? row.agentId ?? null,
2132
+ platform: row.platform ?? null,
2017
2133
  createdAt: new Date(row.created_at ?? row.createdAt),
2018
2134
  updatedAt: new Date(row.updated_at ?? row.updatedAt)
2019
2135
  };
@@ -2538,12 +2654,16 @@ var OllamaEmbeddingClient = class {
2538
2654
  dimensions;
2539
2655
  maxRetries;
2540
2656
  maxInputChars;
2657
+ requestTimeoutMs;
2658
+ healthTimeoutMs;
2541
2659
  constructor(config) {
2542
2660
  this.host = config?.host ?? (process.env.OLLAMA_HOST ?? DEFAULT_OLLAMA_HOST);
2543
2661
  this.model = config?.model ?? (process.env.OLLAMA_MODEL ?? DEFAULT_EMBEDDING_MODEL);
2544
2662
  this.dimensions = config?.dimensions ?? (Number(process.env.EMBEDDING_DIMENSIONS) || DEFAULT_EMBEDDING_DIMENSIONS);
2545
2663
  this.maxRetries = config?.maxRetries ?? 3;
2546
2664
  this.maxInputChars = config?.maxInputChars ?? 2e3;
2665
+ this.requestTimeoutMs = config?.requestTimeoutMs ?? 3e4;
2666
+ this.healthTimeoutMs = config?.healthTimeoutMs ?? 1e4;
2547
2667
  }
2548
2668
  truncateText(text2, maxChars) {
2549
2669
  if (text2.length <= maxChars)
@@ -2590,7 +2710,7 @@ var OllamaEmbeddingClient = class {
2590
2710
  }
2591
2711
  async isHealthy() {
2592
2712
  try {
2593
- const response = await fetch(`${this.host}/api/tags`);
2713
+ const response = await fetch(`${this.host}/api/tags`, { signal: AbortSignal.timeout(this.healthTimeoutMs) });
2594
2714
  return response.ok;
2595
2715
  } catch {
2596
2716
  return false;
@@ -2598,7 +2718,7 @@ var OllamaEmbeddingClient = class {
2598
2718
  }
2599
2719
  async isModelAvailable() {
2600
2720
  try {
2601
- const response = await fetch(`${this.host}/api/tags`);
2721
+ const response = await fetch(`${this.host}/api/tags`, { signal: AbortSignal.timeout(this.healthTimeoutMs) });
2602
2722
  if (!response.ok)
2603
2723
  return false;
2604
2724
  const data = await response.json();
@@ -2608,20 +2728,43 @@ var OllamaEmbeddingClient = class {
2608
2728
  }
2609
2729
  }
2610
2730
  async pullModel() {
2611
- const response = await fetch(`${this.host}/api/pull`, {
2612
- method: "POST",
2613
- headers: { "Content-Type": "application/json" },
2614
- body: JSON.stringify({ name: this.model })
2615
- });
2731
+ const controller = new AbortController();
2732
+ const connectTimer = setTimeout(() => controller.abort(), 3e4);
2733
+ let response;
2734
+ try {
2735
+ response = await fetch(`${this.host}/api/pull`, {
2736
+ method: "POST",
2737
+ headers: { "Content-Type": "application/json" },
2738
+ body: JSON.stringify({ name: this.model }),
2739
+ signal: controller.signal
2740
+ });
2741
+ } finally {
2742
+ clearTimeout(connectTimer);
2743
+ }
2616
2744
  if (!response.ok) {
2617
2745
  throw new Error(`Failed to pull model ${this.model}: ${response.statusText}`);
2618
2746
  }
2619
2747
  const reader = response.body?.getReader();
2620
2748
  if (reader) {
2621
- while (true) {
2622
- const { done } = await reader.read();
2623
- if (done)
2624
- break;
2749
+ const IDLE_MS = 12e4;
2750
+ try {
2751
+ while (true) {
2752
+ let timer;
2753
+ const idle = new Promise((_, reject) => {
2754
+ timer = setTimeout(() => reject(new Error(`Model pull stalled (no data for ${IDLE_MS / 1e3}s)`)), IDLE_MS);
2755
+ });
2756
+ try {
2757
+ const { done } = await Promise.race([reader.read(), idle]);
2758
+ if (done)
2759
+ break;
2760
+ } finally {
2761
+ clearTimeout(timer);
2762
+ }
2763
+ }
2764
+ } catch (err) {
2765
+ await reader.cancel().catch(() => {
2766
+ });
2767
+ throw err;
2625
2768
  }
2626
2769
  }
2627
2770
  }
@@ -2653,7 +2796,7 @@ var OllamaEmbeddingClient = class {
2653
2796
  let lastError;
2654
2797
  for (let attempt = 0; attempt < this.maxRetries; attempt++) {
2655
2798
  try {
2656
- return await fetch(url, init);
2799
+ return await fetch(url, { ...init, signal: init.signal ?? AbortSignal.timeout(this.requestTimeoutMs) });
2657
2800
  } catch (error) {
2658
2801
  lastError = error instanceof Error ? error : new Error(String(error));
2659
2802
  if (attempt < this.maxRetries - 1) {
@@ -3533,6 +3676,22 @@ var KnowledgeSDK = class {
3533
3676
  throw this.wrapError(error, "Failed to count by scope");
3534
3677
  }
3535
3678
  }
3679
+ async countByAgent(opts = {}) {
3680
+ this.ensureInitialized();
3681
+ try {
3682
+ return await this.service.countByAgent(opts);
3683
+ } catch (error) {
3684
+ throw this.wrapError(error, "Failed to count by agent");
3685
+ }
3686
+ }
3687
+ async countByPlatform(opts = {}) {
3688
+ this.ensureInitialized();
3689
+ try {
3690
+ return await this.service.countByPlatform(opts);
3691
+ } catch (error) {
3692
+ throw this.wrapError(error, "Failed to count by platform");
3693
+ }
3694
+ }
3536
3695
  async getStats() {
3537
3696
  this.ensureInitialized();
3538
3697
  try {
@@ -3688,6 +3847,10 @@ var KnowledgeSDK = class {
3688
3847
  this.ensureInitialized();
3689
3848
  return this.service.getOperationsByDay(days);
3690
3849
  }
3850
+ getPlansByDay(days = 15) {
3851
+ this.ensureInitialized();
3852
+ return this.service.getPlansByDay(days);
3853
+ }
3691
3854
  cleanupOldOperations() {
3692
3855
  if (!this.initialized || !this.service)
3693
3856
  return 0;
@@ -3834,6 +3997,21 @@ function createServer(sdk) {
3834
3997
  version: "1.0.0"
3835
3998
  });
3836
3999
  let lastSearchResultIds = [];
4000
+ const PLATFORM_ALLOWLIST = /* @__PURE__ */ new Set(["claude-code", "copilot", "opencode"]);
4001
+ function normalizeProvenance(s) {
4002
+ if (typeof s !== "string") return void 0;
4003
+ const cleaned = Array.from(s).filter((c) => {
4004
+ const n = c.charCodeAt(0);
4005
+ return n >= 32 && n !== 127;
4006
+ }).join("").trim().slice(0, 64);
4007
+ return cleaned.length > 0 ? cleaned : void 0;
4008
+ }
4009
+ function resolvePlatform() {
4010
+ const raw = normalizeProvenance(process.env.COGNISTORE_PLATFORM) ?? normalizeProvenance(server.server.getClientVersion()?.name);
4011
+ if (!raw) return "unknown";
4012
+ const mapped = raw.toLowerCase() === "claude" ? "claude-code" : raw.toLowerCase();
4013
+ return PLATFORM_ALLOWLIST.has(mapped) ? mapped : "unknown";
4014
+ }
3837
4015
  const knowledgeEntrySchema = z3.object({
3838
4016
  title: z3.string().describe("Short descriptive title"),
3839
4017
  content: z3.string().describe("The knowledge content text"),
@@ -3842,7 +4020,7 @@ function createServer(sdk) {
3842
4020
  scope: z3.string().describe('Scope: "global" or "workspace:<project-name>"'),
3843
4021
  source: z3.string().describe("Source of the knowledge"),
3844
4022
  confidenceScore: z3.number().min(0).max(1).optional().describe("Confidence score 0-1"),
3845
- agentId: z3.string().optional().describe("ID of the agent that created this"),
4023
+ agentId: z3.string().optional().describe("Your agent/role name (e.g. 'documentation', 'code-reviewer') so entries can be summarized per agent. Pass it whenever you are a named/custom agent."),
3846
4024
  planId: z3.string().optional().describe("Plan ID to auto-link this knowledge as output. ALWAYS pass this if you have an active plan.")
3847
4025
  });
3848
4026
  async function createEntry(params) {
@@ -3854,7 +4032,8 @@ function createServer(sdk) {
3854
4032
  scope: params.scope,
3855
4033
  source: params.source,
3856
4034
  confidenceScore: params.confidenceScore,
3857
- agentId: params.agentId
4035
+ agentId: normalizeProvenance(params.agentId),
4036
+ platform: resolvePlatform()
3858
4037
  });
3859
4038
  let linked = false;
3860
4039
  let linkWarning = "";
@@ -4057,6 +4236,7 @@ function createServer(sdk) {
4057
4236
  scope: z3.string().describe('Scope: "global" or "workspace:<project-name>"'),
4058
4237
  source: z3.string().describe("Source/context of the plan"),
4059
4238
  planFilePath: z3.string().optional().describe("ABSOLUTE path to the local plan file you wrote (e.g. a plan-mode file like /home/user/.claude/plans/<name>.md). REQUIRED whenever you persisted the plan to a file \u2014 always link it so the CogniStore plan points back to the on-disk file."),
4239
+ agentId: z3.string().optional().describe("Your agent/role name (e.g. 'documentation') so plans can be summarized per agent. Pass it whenever you are a named/custom agent."),
4060
4240
  relatedKnowledgeIds: z3.array(z3.string()).optional().describe("IDs of knowledge entries consulted during planning (auto-linked as input)"),
4061
4241
  tasks: z3.array(z3.object({
4062
4242
  description: z3.string(),
@@ -4076,6 +4256,8 @@ function createServer(sdk) {
4076
4256
  scope: params.scope,
4077
4257
  source: params.source,
4078
4258
  planFilePath: params.planFilePath,
4259
+ agentId: normalizeProvenance(params.agentId),
4260
+ platform: resolvePlatform(),
4079
4261
  relatedKnowledgeIds: inputIds.size > 0 ? [...inputIds] : void 0,
4080
4262
  tasks: params.tasks
4081
4263
  });
@@ -4232,7 +4414,7 @@ function createServer(sdk) {
4232
4414
  },
4233
4415
  WRITE,
4234
4416
  async (params) => {
4235
- const result = sdk.updatePlan(params.planId, { status: "archived" });
4417
+ const result = sdk.updatePlan(params.planId, { status: KnowledgeStatus.ARCHIVED });
4236
4418
  if (!result) return { content: [{ type: "text", text: JSON.stringify({ error: "not_found", type: "plan", id: params.planId }) }] };
4237
4419
  return { content: [{ type: "text", text: JSON.stringify({ archived: true, id: result.id, status: result.status }, null, 2) }] };
4238
4420
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cognistore/mcp-server",
3
- "version": "2.2.2",
3
+ "version": "2.3.0",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "MCP server for CogniStore — integrates with Claude Code and GitHub Copilot",
@@ -35,6 +35,7 @@
35
35
  "build:deps": "pnpm --filter @cognistore/shared build && pnpm --filter @cognistore/core build && pnpm --filter @cognistore/embeddings build && pnpm --filter @cognistore/sdk build",
36
36
  "dev": "tsx src/index.ts",
37
37
  "clean": "rm -rf dist",
38
- "test": "if find . -name '*.test.ts' -o -name '*.spec.ts' | grep -q .; then npx playwright test; else echo 'No tests found, skipping'; fi"
38
+ "test": "if find . -name '*.test.ts' -o -name '*.spec.ts' | grep -q .; then npx playwright test; else echo 'No tests found, skipping'; fi",
39
+ "type-check": "tsc --noEmit"
39
40
  }
40
41
  }