@tpsdev-ai/flair-mcp 0.16.1 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -5,6 +5,7 @@
5
5
  * Tools:
6
6
  * - memory_search — semantic search across memories
7
7
  * - memory_store — save a memory with type + durability
8
+ * - memory_update — update an existing memory by ID (dedup-bypassed)
8
9
  * - memory_get — retrieve a specific memory by ID
9
10
  * - memory_delete — delete a memory
10
11
  * - bootstrap — cold-start context (soul + recent memories)
package/dist/index.js CHANGED
@@ -5,6 +5,7 @@
5
5
  * Tools:
6
6
  * - memory_search — semantic search across memories
7
7
  * - memory_store — save a memory with type + durability
8
+ * - memory_update — update an existing memory by ID (dedup-bypassed)
8
9
  * - memory_get — retrieve a specific memory by ID
9
10
  * - memory_delete — delete a memory
10
11
  * - bootstrap — cold-start context (soul + recent memories)
@@ -184,40 +185,56 @@ export async function runMcp() {
184
185
  dedup: true,
185
186
  dedupThreshold: 0.95,
186
187
  });
187
- // Dedup path: existing memory matched, NEW content was NOT written.
188
- // Emit both prose AND structuredContent so callers can react
189
- // programmatically even when LLMs compress prose imprecisely
190
- // (see flair#449 work agent missed dedup signal and lost data).
191
- if (result.deduped) {
192
- const sc = {
193
- deduplicated: true,
194
- mergedWith: result.id,
195
- existingContent: result.content,
196
- written: false,
197
- };
198
- return {
199
- content: [{
200
- type: "text",
201
- text: `⚠️ DEDUPLICATED — new content was NOT written. ` +
202
- `Matched existing memory id=${result.id}: ${result.content?.slice(0, 200)}\n\n` +
203
- `If the new content is genuinely distinct, retry with different phrasing ` +
204
- `or call memory_store with the same id to update.`,
205
- }],
206
- structuredContent: sc,
207
- };
208
- }
188
+ // The server's conservative dedup gate NEVER suppresses a write
189
+ // (memory-integrity fix, flair#526) `result.deduplicated` is a
190
+ // collision SIGNAL, not a "was this dropped" flag. The new content at
191
+ // `result.id` is ALWAYS written; when flagged, `result.matchedId` names
192
+ // the similar existing memory (see result.matchConfidence for the
193
+ // cosine/lexical scores). Emit both prose AND structuredContent so
194
+ // callers can react programmatically even when LLMs compress prose
195
+ // imprecisely. (Historical note: this tool used to treat a dedup hit as
196
+ // "new content was NOT written" — that WAS the flair#449/#526 silent
197
+ // data-loss bug. The gate is server-side now and never suppresses.)
198
+ const deduplicated = result.deduplicated === true;
199
+ const matchedId = result.matchedId;
209
200
  const preview = content.length > 120 ? content.slice(0, 120) + "..." : content;
210
201
  const tagStr = tags && tags.length > 0 ? tags.join(", ") : "none";
211
- const text = [
202
+ const lines = [
212
203
  `Memory stored (id: ${result.id})`,
213
204
  `Preview: ${preview}`,
214
205
  `Size: ${content.length} chars`,
215
206
  `Tags: ${tagStr}`,
216
207
  `Type: ${type}, Durability: ${durability}`,
217
- ].join("\n");
208
+ ];
209
+ if (deduplicated && matchedId) {
210
+ lines.push("", `Note: similar to existing memory id=${matchedId} — both are kept. ` +
211
+ `If this was meant to UPDATE that memory rather than add a new one, use memory_update instead.`);
212
+ }
213
+ return {
214
+ content: [{ type: "text", text: lines.join("\n") }],
215
+ structuredContent: { deduplicated, id: result.id, written: true, ...(deduplicated ? { matchedId } : {}) },
216
+ };
217
+ }
218
+ catch (err) {
219
+ return errorResult(err, flair.url);
220
+ }
221
+ });
222
+ server.tool("memory_update", "Update an existing memory by ID. Dedup-bypassed — this is an intentional overwrite/version, not an ambiguous new write. " +
223
+ "Default: overwrites the same id in place. Pass preserveHistory=true to instead write a new version linked via " +
224
+ "`supersedes`, closing the old one's validity window (requires owning the memory, or a write grant if it's another agent's).", {
225
+ id: z.string().describe("ID of the memory to update"),
226
+ content: z.string().describe("New content"),
227
+ preserveHistory: z.coerce.boolean().optional().default(false)
228
+ .describe("Write a new supersedes-linked version instead of overwriting in place (default false)"),
229
+ }, async ({ id, content, preserveHistory }) => {
230
+ try {
231
+ const result = await flair.memory.update(id, content, { preserveHistory });
232
+ const text = preserveHistory
233
+ ? `Memory updated: new version stored (id: ${result.id}), supersedes ${id}.`
234
+ : `Memory updated (id: ${id}).`;
218
235
  return {
219
236
  content: [{ type: "text", text }],
220
- structuredContent: { deduplicated: false, id: result.id, written: true },
237
+ structuredContent: { id: result.id, supersedes: preserveHistory ? id : undefined, written: true },
221
238
  };
222
239
  }
223
240
  catch (err) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tpsdev-ai/flair-mcp",
3
- "version": "0.16.1",
3
+ "version": "0.18.0",
4
4
  "description": "MCP server for Flair — persistent memory for Claude Code, Cursor, and any MCP client.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -27,7 +27,7 @@
27
27
  },
28
28
  "dependencies": {
29
29
  "@modelcontextprotocol/sdk": "1.27.1",
30
- "@tpsdev-ai/flair-client": "0.16.1",
30
+ "@tpsdev-ai/flair-client": "0.18.0",
31
31
  "zod": "4.3.6"
32
32
  },
33
33
  "license": "Apache-2.0",