@tpsdev-ai/flair-mcp 0.21.0 → 0.22.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/dist/index.d.ts CHANGED
@@ -8,6 +8,7 @@
8
8
  * - memory_update — update an existing memory by ID (dedup-bypassed)
9
9
  * - memory_get — retrieve a specific memory by ID
10
10
  * - memory_delete — delete a memory
11
+ * - relationship_store — assert a subject/predicate/object relationship triple
11
12
  * - bootstrap — cold-start context (soul + recent memories)
12
13
  * - soul_set — set a personality/context entry
13
14
  * - soul_get — get a personality/context entry
package/dist/index.js CHANGED
@@ -8,6 +8,7 @@
8
8
  * - memory_update — update an existing memory by ID (dedup-bypassed)
9
9
  * - memory_get — retrieve a specific memory by ID
10
10
  * - memory_delete — delete a memory
11
+ * - relationship_store — assert a subject/predicate/object relationship triple
11
12
  * - bootstrap — cold-start context (soul + recent memories)
12
13
  * - soul_set — set a personality/context entry
13
14
  * - soul_get — get a personality/context entry
@@ -334,6 +335,41 @@ export async function runMcp() {
334
335
  return errorResult(err, flair.url);
335
336
  }
336
337
  });
338
+ server.tool("relationship_store", "Record that <subject> <predicate> <object> — an explicit entity-to-entity relationship triple " +
339
+ "(e.g. 'nathan manages flair', 'flint reviews cli'), distinct from a free-text memory. " +
340
+ "ASSERT/UPSERT semantics: writing the SAME triple again (same subject/predicate/object) updates the " +
341
+ "existing row in place (confidence/validTo/source refresh) rather than creating a duplicate — safe to " +
342
+ "re-assert. Predicate is free text (no fixed enum) but prefer a small, consistent vocabulary so the graph " +
343
+ "stays queryable: manages, works_on, reviews, depends_on, replaces, owns, reports_to, advises. " +
344
+ "TO CONTRADICT a prior relationship: (a) re-asserting the identical triple just updates it — fine. " +
345
+ "(b) changing validTo on the SAME subject/predicate/object overwrites the old validTo (the graph tracks " +
346
+ "current state, not full history). (c) changing the PREDICATE (e.g. 'nathan manages flair' -> 'nathan " +
347
+ "advises flair') creates a SEPARATE relationship — it does NOT automatically close the old one. Close it " +
348
+ "yourself first: re-assert the OLD triple with a validTo set to now (or call relationship's delete), THEN " +
349
+ "store the new one.", {
350
+ subject: z.string().describe("Source entity — a person, project, or service (e.g. 'nathan')"),
351
+ predicate: z.string().describe("Relationship type, free text. Recommended vocabulary: manages, works_on, reviews, depends_on, " +
352
+ "replaces, owns, reports_to, advises — consistency helps recall, but any short verb phrase works."),
353
+ object: z.string().describe("Target entity — a person, project, or service (e.g. 'flair')"),
354
+ confidence: z.coerce.number().optional().describe("0.0-1.0, how certain (default 1.0 = explicitly stated)"),
355
+ validFrom: z.string().optional().describe("ISO timestamp this relationship became true (default: now)"),
356
+ validTo: z.string().optional().describe("ISO timestamp this relationship ended. Leave unset for an active relationship; set it (via a re-assert " +
357
+ "of this SAME subject/predicate/object) to close out a relationship you're contradicting with a new predicate."),
358
+ source: z.string().optional().describe("Where this was learned from (a memory ID, conversation, etc.)"),
359
+ }, async ({ subject, predicate, object, confidence, validFrom, validTo, source }) => {
360
+ heartbeat(); // auto-presence (flair#598) — fire-and-forget, rate-limited
361
+ try {
362
+ const result = await flair.relationship.write({ subject, predicate, object, confidence, validFrom, validTo, source });
363
+ const confStr = confidence !== undefined ? ` (confidence: ${confidence})` : "";
364
+ return {
365
+ content: [{ type: "text", text: `Relationship recorded: ${subject} → ${predicate} → ${object}${confStr} (id: ${result.id})` }],
366
+ structuredContent: { id: result.id, subject, predicate, object, written: true },
367
+ };
368
+ }
369
+ catch (err) {
370
+ return errorResult(err, flair.url);
371
+ }
372
+ });
337
373
  server.tool("bootstrap", "Get session context: soul + memories + predicted context. Run at session start. Pass subjects for predictive loading.", {
338
374
  maxTokens: z.coerce.number().optional().default(4000).describe("Max tokens in output"),
339
375
  currentTask: z.string().optional().describe("Current task description — enables semantic search for relevant memories"),
@@ -19,16 +19,23 @@
19
19
  * of those decisions without sharing any actual state.
20
20
  */
21
21
  /** Mirrors VALID_ACTIVITIES in resources/Presence.ts — keep in sync. */
22
- export type PresenceActivity = "coding" | "reviewing" | "planning" | "idle";
22
+ export type PresenceActivity = "coding" | "reviewing" | "planning" | "debugging" | "idle";
23
23
  /**
24
24
  * Derive a presence `activity` from the SAME context signals the `bootstrap`
25
25
  * tool and the SessionStart hook already receive (channel/surface) — no new
26
- * inputs, no separate classifier call. Two narrow, name-based overrides for
26
+ * inputs, no separate classifier call. Three narrow, name-based overrides for
27
27
  * the surfaces that are unambiguous on their own; everything else (including
28
28
  * no surface at all, e.g. the session-start hook) falls through to "coding",
29
29
  * since an MCP tool call is, definitionally, an agent doing something — never
30
30
  * "idle" (idle/offline are purely functions of elapsed time since the last
31
31
  * heartbeat; see derivePresenceStatus() in resources/Presence.ts).
32
+ *
33
+ * "debug"/"investigat"/"incident" is checked FIRST, ahead of "review" and
34
+ * "plan" — an active incident investigation (flair#613: the flagship
35
+ * collision-detection use case) is the most unambiguous and highest-signal
36
+ * surface name an agent can report, and previously had no matching bucket at
37
+ * all, silently falling into "coding" (or "reviewing" if the surface also
38
+ * happened to contain that substring — e.g. "incident-review").
32
39
  */
33
40
  export declare function deriveActivity(ctx?: {
34
41
  surface?: string;
@@ -82,6 +89,14 @@ export declare function resolvePresenceTimeoutMs(): number;
82
89
  * omitted (not sent as null/empty) when absent, so callers that don't know a
83
90
  * task can still heartbeat without explicitly clearing one that was set
84
91
  * earlier — see postPresenceSafe()'s doc comment for why that matters.
92
+ *
93
+ * Natural-presence: this body always carries `activity`, and the server stamps
94
+ * `activityUpdatedAt` (resources/Presence.ts, buildPresenceRecord) on any beat
95
+ * that asserts activity/task — so a working agent keeps activity fresh, and one
96
+ * that stops lets it decay to last-known automatically (no manual clear). A
97
+ * future liveness-only beat (this body without `activity`) would refresh
98
+ * lastHeartbeatAt without re-stamping activity — the server treats the two
99
+ * independently.
85
100
  */
86
101
  export declare function buildPresenceBody(activity: PresenceActivity, currentTask?: string): Record<string, unknown>;
87
102
  /** Minimal surface a presence write needs from a Flair client — matches
package/dist/presence.js CHANGED
@@ -21,15 +21,24 @@
21
21
  /**
22
22
  * Derive a presence `activity` from the SAME context signals the `bootstrap`
23
23
  * tool and the SessionStart hook already receive (channel/surface) — no new
24
- * inputs, no separate classifier call. Two narrow, name-based overrides for
24
+ * inputs, no separate classifier call. Three narrow, name-based overrides for
25
25
  * the surfaces that are unambiguous on their own; everything else (including
26
26
  * no surface at all, e.g. the session-start hook) falls through to "coding",
27
27
  * since an MCP tool call is, definitionally, an agent doing something — never
28
28
  * "idle" (idle/offline are purely functions of elapsed time since the last
29
29
  * heartbeat; see derivePresenceStatus() in resources/Presence.ts).
30
+ *
31
+ * "debug"/"investigat"/"incident" is checked FIRST, ahead of "review" and
32
+ * "plan" — an active incident investigation (flair#613: the flagship
33
+ * collision-detection use case) is the most unambiguous and highest-signal
34
+ * surface name an agent can report, and previously had no matching bucket at
35
+ * all, silently falling into "coding" (or "reviewing" if the surface also
36
+ * happened to contain that substring — e.g. "incident-review").
30
37
  */
31
38
  export function deriveActivity(ctx = {}) {
32
39
  const surface = (ctx.surface ?? "").toLowerCase();
40
+ if (surface.includes("debug") || surface.includes("investigat") || surface.includes("incident"))
41
+ return "debugging";
33
42
  if (surface.includes("review"))
34
43
  return "reviewing";
35
44
  if (surface.includes("plan") || surface.includes("spec") || surface.includes("design"))
@@ -123,6 +132,14 @@ function withTimeout(promise, ms) {
123
132
  * omitted (not sent as null/empty) when absent, so callers that don't know a
124
133
  * task can still heartbeat without explicitly clearing one that was set
125
134
  * earlier — see postPresenceSafe()'s doc comment for why that matters.
135
+ *
136
+ * Natural-presence: this body always carries `activity`, and the server stamps
137
+ * `activityUpdatedAt` (resources/Presence.ts, buildPresenceRecord) on any beat
138
+ * that asserts activity/task — so a working agent keeps activity fresh, and one
139
+ * that stops lets it decay to last-known automatically (no manual clear). A
140
+ * future liveness-only beat (this body without `activity`) would refresh
141
+ * lastHeartbeatAt without re-stamping activity — the server treats the two
142
+ * independently.
126
143
  */
127
144
  export function buildPresenceBody(activity, currentTask) {
128
145
  const body = { activity };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tpsdev-ai/flair-mcp",
3
- "version": "0.21.0",
3
+ "version": "0.22.1",
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.21.0",
30
+ "@tpsdev-ai/flair-client": "0.22.1",
31
31
  "zod": "4.3.6"
32
32
  },
33
33
  "license": "Apache-2.0",