@tpsdev-ai/flair-mcp 0.21.0 → 0.22.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 +1 -0
- package/dist/index.js +36 -0
- package/dist/presence.d.ts +17 -2
- package/dist/presence.js +18 -1
- package/package.json +2 -2
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"),
|
package/dist/presence.d.ts
CHANGED
|
@@ -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.
|
|
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.
|
|
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.
|
|
3
|
+
"version": "0.22.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.
|
|
30
|
+
"@tpsdev-ai/flair-client": "0.22.0",
|
|
31
31
|
"zod": "4.3.6"
|
|
32
32
|
},
|
|
33
33
|
"license": "Apache-2.0",
|