@tpsdev-ai/flair-mcp 0.52.0 → 0.53.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/README.md +3 -0
- package/dist/adapter-surface.d.ts +75 -0
- package/dist/adapter-surface.js +105 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +108 -0
- package/dist/skills.d.ts +49 -0
- package/dist/skills.js +119 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -44,6 +44,9 @@ Once configured, Claude Code (or any MCP client) gets these tools:
|
|
|
44
44
|
| `memory_store` | Save a memory with type (lesson/decision/fact) and durability. Optional `usedMemoryIds` cites memories that informed the write. |
|
|
45
45
|
| `memory_get` | Retrieve a specific memory by ID. |
|
|
46
46
|
| `memory_delete` | Delete a memory. |
|
|
47
|
+
| `skill_store` | Write a skill (trigger + procedure) as a skill-tagged memory. |
|
|
48
|
+
| `skill_search` | Find skills that apply to a task. Returns a catalog, not the procedure. |
|
|
49
|
+
| `skill_get` | Retrieve the full skill by ID (disclosure after `skill_search`). |
|
|
47
50
|
| `bootstrap` | Cold-start context — soul + recent memories in one call. |
|
|
48
51
|
| `soul_set` | Set personality or project context (included in every bootstrap). |
|
|
49
52
|
| `soul_get` | Get a personality or project context entry. |
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* adapter-surface.ts — the stdio adapter's declared tool set (flair#1575).
|
|
3
|
+
*
|
|
4
|
+
* `@tpsdev-ai/flair-mcp` hand-wires each tool via `server.tool(...)` in
|
|
5
|
+
* index.ts. The native `/mcp` handler ships a separate `TOOLS` registry in
|
|
6
|
+
* resources/mcp-tools.ts. Those two surfaces drifted: skill_* landed in
|
|
7
|
+
* TOOLS for 0.52.0 and never reached this package — the surface Claude Code
|
|
8
|
+
* and Cursor actually use.
|
|
9
|
+
*
|
|
10
|
+
* This module is the reviewed chokepoint for that seam:
|
|
11
|
+
*
|
|
12
|
+
* 1. `ADAPTER_TOOL_NAMES` is the adapter's declared tool set.
|
|
13
|
+
* 2. `parseAdapterToolNames` reads the names actually passed to
|
|
14
|
+
* `server.tool(...)` in index.ts, so the declaration cannot outrun
|
|
15
|
+
* registration (or vice versa).
|
|
16
|
+
* 3. `STDIO_ADAPTER_EXEMPTIONS` is the explicit, reviewed list of names
|
|
17
|
+
* that exist on one surface but not the other. A TOOLS name that is
|
|
18
|
+
* neither registered here nor exempted is a CI failure — silent drift
|
|
19
|
+
* of the class that hid skill_*.
|
|
20
|
+
*
|
|
21
|
+
* Deriving the adapter's handlers from TOOLS (so a new registry tool appears
|
|
22
|
+
* here for free) is the durable structural fix; it does not fit this chip
|
|
23
|
+
* because TOOLS is Harper-linked server code and this package talks HTTP via
|
|
24
|
+
* FlairClient. Detection + exemption list ships now; derive is a follow-on.
|
|
25
|
+
*/
|
|
26
|
+
/** Tools registered on the stdio adapter via `server.tool(...)` in index.ts. */
|
|
27
|
+
export declare const ADAPTER_TOOL_NAMES: readonly ["bootstrap", "flair_orgevent", "flair_workspace_set", "memory_delete", "memory_get", "memory_search", "memory_store", "memory_update", "record_usage", "relationship_store", "skill_get", "skill_search", "skill_store", "soul_get", "soul_set"];
|
|
28
|
+
export type AdapterToolName = (typeof ADAPTER_TOOL_NAMES)[number];
|
|
29
|
+
/**
|
|
30
|
+
* Reviewed exemptions at the stdio-adapter ↔ server TOOLS seam (flair#1575).
|
|
31
|
+
*
|
|
32
|
+
* A name here is a deliberate, reviewed difference — not silent drift.
|
|
33
|
+
* Adding or removing a name is the control: CI fails if an exemption is
|
|
34
|
+
* unused (the tool appeared on both sides, or vanished from the side it
|
|
35
|
+
* was excused on) or if a non-exempt name exists on only one side.
|
|
36
|
+
*/
|
|
37
|
+
export declare const STDIO_ADAPTER_EXEMPTIONS: {
|
|
38
|
+
/**
|
|
39
|
+
* Present in resources/mcp-tools.ts `TOOLS`, not wired on the stdio adapter.
|
|
40
|
+
*
|
|
41
|
+
* - attention: native /mcp only (flair#677). mcp-tools.ts's module doc
|
|
42
|
+
* explicitly does not mirror it into this package.
|
|
43
|
+
* - memory_basement / memory_restore: archive verbs (flair#1472) landed
|
|
44
|
+
* on native /mcp; not yet forwarded over FlairClient.
|
|
45
|
+
*/
|
|
46
|
+
readonly registryOnly: readonly ["attention", "memory_basement", "memory_restore"];
|
|
47
|
+
/**
|
|
48
|
+
* Wired on the stdio adapter, absent from `TOOLS`.
|
|
49
|
+
*
|
|
50
|
+
* - relationship_store: the adapter predates the record-types mcp
|
|
51
|
+
* declaration. Relationship has no `RECORD_TYPES.mcp` field, so TOOLS
|
|
52
|
+
* ships zero relationship_* names. The adapter still exposes the triple
|
|
53
|
+
* write (FlairClient.relationship.write).
|
|
54
|
+
*/
|
|
55
|
+
readonly adapterOnly: readonly ["relationship_store"];
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* Collect `server.tool("name", ...)` registrations from adapter source.
|
|
59
|
+
* Plain string scan — the first string literal argument is the tool name.
|
|
60
|
+
* Does not use `new RegExp` built from runtime input (CodeQL js/regex-injection).
|
|
61
|
+
*/
|
|
62
|
+
export declare function parseAdapterToolNames(source: string): string[];
|
|
63
|
+
export interface AdapterRegistryParity {
|
|
64
|
+
/** TOOLS names the adapter neither registers nor exempts. */
|
|
65
|
+
missingFromAdapter: string[];
|
|
66
|
+
/** Adapter names that are neither in TOOLS nor adapter-only exempted. */
|
|
67
|
+
extraOnAdapter: string[];
|
|
68
|
+
/** Exemption entries that no longer describe a real one-sided difference. */
|
|
69
|
+
staleExemptions: string[];
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Compare the stdio adapter's tool set to the server TOOLS registry.
|
|
73
|
+
* Equal after applying the reviewed exemption list — otherwise drift.
|
|
74
|
+
*/
|
|
75
|
+
export declare function adapterRegistryParity(registryNames: readonly string[], adapterNames: readonly string[]): AdapterRegistryParity;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* adapter-surface.ts — the stdio adapter's declared tool set (flair#1575).
|
|
3
|
+
*
|
|
4
|
+
* `@tpsdev-ai/flair-mcp` hand-wires each tool via `server.tool(...)` in
|
|
5
|
+
* index.ts. The native `/mcp` handler ships a separate `TOOLS` registry in
|
|
6
|
+
* resources/mcp-tools.ts. Those two surfaces drifted: skill_* landed in
|
|
7
|
+
* TOOLS for 0.52.0 and never reached this package — the surface Claude Code
|
|
8
|
+
* and Cursor actually use.
|
|
9
|
+
*
|
|
10
|
+
* This module is the reviewed chokepoint for that seam:
|
|
11
|
+
*
|
|
12
|
+
* 1. `ADAPTER_TOOL_NAMES` is the adapter's declared tool set.
|
|
13
|
+
* 2. `parseAdapterToolNames` reads the names actually passed to
|
|
14
|
+
* `server.tool(...)` in index.ts, so the declaration cannot outrun
|
|
15
|
+
* registration (or vice versa).
|
|
16
|
+
* 3. `STDIO_ADAPTER_EXEMPTIONS` is the explicit, reviewed list of names
|
|
17
|
+
* that exist on one surface but not the other. A TOOLS name that is
|
|
18
|
+
* neither registered here nor exempted is a CI failure — silent drift
|
|
19
|
+
* of the class that hid skill_*.
|
|
20
|
+
*
|
|
21
|
+
* Deriving the adapter's handlers from TOOLS (so a new registry tool appears
|
|
22
|
+
* here for free) is the durable structural fix; it does not fit this chip
|
|
23
|
+
* because TOOLS is Harper-linked server code and this package talks HTTP via
|
|
24
|
+
* FlairClient. Detection + exemption list ships now; derive is a follow-on.
|
|
25
|
+
*/
|
|
26
|
+
/** Tools registered on the stdio adapter via `server.tool(...)` in index.ts. */
|
|
27
|
+
export const ADAPTER_TOOL_NAMES = [
|
|
28
|
+
"bootstrap",
|
|
29
|
+
"flair_orgevent",
|
|
30
|
+
"flair_workspace_set",
|
|
31
|
+
"memory_delete",
|
|
32
|
+
"memory_get",
|
|
33
|
+
"memory_search",
|
|
34
|
+
"memory_store",
|
|
35
|
+
"memory_update",
|
|
36
|
+
"record_usage",
|
|
37
|
+
"relationship_store",
|
|
38
|
+
"skill_get",
|
|
39
|
+
"skill_search",
|
|
40
|
+
"skill_store",
|
|
41
|
+
"soul_get",
|
|
42
|
+
"soul_set",
|
|
43
|
+
];
|
|
44
|
+
/**
|
|
45
|
+
* Reviewed exemptions at the stdio-adapter ↔ server TOOLS seam (flair#1575).
|
|
46
|
+
*
|
|
47
|
+
* A name here is a deliberate, reviewed difference — not silent drift.
|
|
48
|
+
* Adding or removing a name is the control: CI fails if an exemption is
|
|
49
|
+
* unused (the tool appeared on both sides, or vanished from the side it
|
|
50
|
+
* was excused on) or if a non-exempt name exists on only one side.
|
|
51
|
+
*/
|
|
52
|
+
export const STDIO_ADAPTER_EXEMPTIONS = {
|
|
53
|
+
/**
|
|
54
|
+
* Present in resources/mcp-tools.ts `TOOLS`, not wired on the stdio adapter.
|
|
55
|
+
*
|
|
56
|
+
* - attention: native /mcp only (flair#677). mcp-tools.ts's module doc
|
|
57
|
+
* explicitly does not mirror it into this package.
|
|
58
|
+
* - memory_basement / memory_restore: archive verbs (flair#1472) landed
|
|
59
|
+
* on native /mcp; not yet forwarded over FlairClient.
|
|
60
|
+
*/
|
|
61
|
+
registryOnly: ["attention", "memory_basement", "memory_restore"],
|
|
62
|
+
/**
|
|
63
|
+
* Wired on the stdio adapter, absent from `TOOLS`.
|
|
64
|
+
*
|
|
65
|
+
* - relationship_store: the adapter predates the record-types mcp
|
|
66
|
+
* declaration. Relationship has no `RECORD_TYPES.mcp` field, so TOOLS
|
|
67
|
+
* ships zero relationship_* names. The adapter still exposes the triple
|
|
68
|
+
* write (FlairClient.relationship.write).
|
|
69
|
+
*/
|
|
70
|
+
adapterOnly: ["relationship_store"],
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* Collect `server.tool("name", ...)` registrations from adapter source.
|
|
74
|
+
* Plain string scan — the first string literal argument is the tool name.
|
|
75
|
+
* Does not use `new RegExp` built from runtime input (CodeQL js/regex-injection).
|
|
76
|
+
*/
|
|
77
|
+
export function parseAdapterToolNames(source) {
|
|
78
|
+
const names = [];
|
|
79
|
+
const re = /server\.tool\(\s*"([a-z][a-z0-9_]*)"/g;
|
|
80
|
+
let match;
|
|
81
|
+
while ((match = re.exec(source)) !== null)
|
|
82
|
+
names.push(match[1]);
|
|
83
|
+
return names;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Compare the stdio adapter's tool set to the server TOOLS registry.
|
|
87
|
+
* Equal after applying the reviewed exemption list — otherwise drift.
|
|
88
|
+
*/
|
|
89
|
+
export function adapterRegistryParity(registryNames, adapterNames) {
|
|
90
|
+
const registry = new Set(registryNames);
|
|
91
|
+
const adapter = new Set(adapterNames);
|
|
92
|
+
const registryOnly = new Set(STDIO_ADAPTER_EXEMPTIONS.registryOnly);
|
|
93
|
+
const adapterOnly = new Set(STDIO_ADAPTER_EXEMPTIONS.adapterOnly);
|
|
94
|
+
const missingFromAdapter = [...registry]
|
|
95
|
+
.filter((name) => !adapter.has(name) && !registryOnly.has(name))
|
|
96
|
+
.sort();
|
|
97
|
+
const extraOnAdapter = [...adapter]
|
|
98
|
+
.filter((name) => !registry.has(name) && !adapterOnly.has(name))
|
|
99
|
+
.sort();
|
|
100
|
+
const staleExemptions = [
|
|
101
|
+
...[...registryOnly].filter((name) => !registry.has(name) || adapter.has(name)),
|
|
102
|
+
...[...adapterOnly].filter((name) => !adapter.has(name) || registry.has(name)),
|
|
103
|
+
].sort();
|
|
104
|
+
return { missingFromAdapter, extraOnAdapter, staleExemptions };
|
|
105
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -15,6 +15,9 @@
|
|
|
15
15
|
* - flair_workspace_set — write own WorkspaceState (Office Space coordination)
|
|
16
16
|
* - flair_orgevent — publish an OrgEvent attributed to self (no forging)
|
|
17
17
|
* - record_usage — report that recalled memories were actually used (flair#1147)
|
|
18
|
+
* - skill_store — write a skill-tagged memory (trigger + procedure)
|
|
19
|
+
* - skill_search — catalog skills that apply to a task (not the procedure)
|
|
20
|
+
* - skill_get — retrieve the full skill by id (disclosure after search)
|
|
18
21
|
*
|
|
19
22
|
* Auto-presence (flair#598): every tool call above triggers a fire-and-forget,
|
|
20
23
|
* rate-limited `POST /Presence` heartbeat for the calling agent (see
|
package/dist/index.js
CHANGED
|
@@ -15,6 +15,9 @@
|
|
|
15
15
|
* - flair_workspace_set — write own WorkspaceState (Office Space coordination)
|
|
16
16
|
* - flair_orgevent — publish an OrgEvent attributed to self (no forging)
|
|
17
17
|
* - record_usage — report that recalled memories were actually used (flair#1147)
|
|
18
|
+
* - skill_store — write a skill-tagged memory (trigger + procedure)
|
|
19
|
+
* - skill_search — catalog skills that apply to a task (not the procedure)
|
|
20
|
+
* - skill_get — retrieve the full skill by id (disclosure after search)
|
|
18
21
|
*
|
|
19
22
|
* Auto-presence (flair#598): every tool call above triggers a fire-and-forget,
|
|
20
23
|
* rate-limited `POST /Presence` heartbeat for the calling agent (see
|
|
@@ -47,6 +50,7 @@ import { deriveActivity, postPresenceSafe, resolveHeartbeatIntervalMs, resolvePr
|
|
|
47
50
|
import { readEnvOrUnset, stripInterpolationLiteralsFromEnv } from "./env-guard.js";
|
|
48
51
|
import { buildRecordUsageBody, citationIds, withCiteNudge, RECORD_USAGE_ID_MERGE_CONTRACT } from "./usage.js";
|
|
49
52
|
import { serverInfo } from "./version.js";
|
|
53
|
+
import { buildSkillSearchBody, buildSkillStoreBody, formatSkillCatalog, isSkillRecord, projectSkillSearchResponse, stripInternalMemoryFields, } from "./skills.js";
|
|
50
54
|
// ─── Error helpers ──────────────────────────────────────────────────────────
|
|
51
55
|
export function classifyError(err, flairUrl) {
|
|
52
56
|
if (err instanceof FlairError) {
|
|
@@ -547,6 +551,110 @@ export async function runMcp() {
|
|
|
547
551
|
return errorResult(err, flair.url);
|
|
548
552
|
}
|
|
549
553
|
});
|
|
554
|
+
// ─── Skills as memory (flair#1575 / #1542 / #1546) ──────────────────────────
|
|
555
|
+
//
|
|
556
|
+
// Native `/mcp` already ships skill_store / skill_search / skill_get in the
|
|
557
|
+
// TOOLS registry. This stdio adapter is the surface Claude Code and Cursor
|
|
558
|
+
// actually use, and it never wired them — 0.52.0's headline was unreachable.
|
|
559
|
+
// Same pattern as the other tools: shape the HTTP call via FlairClient,
|
|
560
|
+
// heartbeat, format the result. Write/recall/scope policy stays server-side.
|
|
561
|
+
server.tool("skill_store", "Write a skill (a reusable capability/procedure) as a skill-tagged memory. " +
|
|
562
|
+
"The `trigger` text is what the skill embeds from (the recall signal — 'when to use this'), " +
|
|
563
|
+
"and `content` is the full procedure. Skills are forced durability=persistent and are " +
|
|
564
|
+
"SkillScan-gated before the embed (a dangerous shell/network payload is rejected).", {
|
|
565
|
+
content: z.string().describe("The full procedure (markdown body of the SKILL.md)"),
|
|
566
|
+
trigger: z.string().optional().describe("The 'when to use' text — the recall signal the skill embeds from"),
|
|
567
|
+
name: z.string().optional().describe("Skill name (SKILL.md frontmatter; stored in metadata)"),
|
|
568
|
+
description: z.string().optional().describe("Skill description (SKILL.md frontmatter; stored in metadata)"),
|
|
569
|
+
tags: z.array(z.string()).optional().describe("Additional tags (the 'skill' tag is added automatically)"),
|
|
570
|
+
}, async ({ content, trigger, name, description, tags }) => {
|
|
571
|
+
heartbeat();
|
|
572
|
+
try {
|
|
573
|
+
const { id, body } = buildSkillStoreBody({
|
|
574
|
+
agentId: flair.agentId,
|
|
575
|
+
content,
|
|
576
|
+
trigger,
|
|
577
|
+
name,
|
|
578
|
+
description,
|
|
579
|
+
tags,
|
|
580
|
+
claimedClient: flair.claimedClient,
|
|
581
|
+
});
|
|
582
|
+
const result = await flair.request("PUT", `/Memory/${id}`, body);
|
|
583
|
+
const writtenId = typeof result?.id === "string" && result.id.length > 0 ? result.id : id;
|
|
584
|
+
const preview = content.length > 120 ? content.slice(0, 120) + "..." : content;
|
|
585
|
+
const lines = [
|
|
586
|
+
`Skill stored (id: ${writtenId})`,
|
|
587
|
+
`Preview: ${preview}`,
|
|
588
|
+
name ? `Name: ${name}` : undefined,
|
|
589
|
+
trigger ? `Trigger: ${trigger}` : undefined,
|
|
590
|
+
].filter((line) => line != null);
|
|
591
|
+
return {
|
|
592
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
593
|
+
structuredContent: { id: writtenId, written: true },
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
catch (err) {
|
|
597
|
+
return errorResult(err, flair.url);
|
|
598
|
+
}
|
|
599
|
+
});
|
|
600
|
+
server.tool("skill_search", "Find skills (reusable capabilities/procedures) that apply to a task. " +
|
|
601
|
+
"Ranks skill-tagged memories by their `trigger` ('when to use') against your task text. " +
|
|
602
|
+
"Returns a lightweight CATALOG — id, name, trigger, description, tags, agentId — NOT the full " +
|
|
603
|
+
"procedure (fetch that with skill_get). Scoped to your own + shared skills; another agent's " +
|
|
604
|
+
"private skill is never returned.", {
|
|
605
|
+
task: z.string().describe("The task/context to match skills against — natural language; ranked against each skill's trigger"),
|
|
606
|
+
limit: z.coerce.number().optional().default(5).describe("Max skills to return (default 5)"),
|
|
607
|
+
}, async ({ task, limit }) => {
|
|
608
|
+
heartbeat();
|
|
609
|
+
try {
|
|
610
|
+
const raw = await flair.request("POST", "/SemanticSearch", buildSkillSearchBody({ task, limit }));
|
|
611
|
+
const projected = projectSkillSearchResponse(raw);
|
|
612
|
+
if (!projected || typeof projected !== "object" || !Array.isArray(projected.results)) {
|
|
613
|
+
return { content: [{ type: "text", text: "No matching skills found." }] };
|
|
614
|
+
}
|
|
615
|
+
const results = projected.results;
|
|
616
|
+
return {
|
|
617
|
+
content: [{ type: "text", text: formatSkillCatalog(results) }],
|
|
618
|
+
structuredContent: { results },
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
catch (err) {
|
|
622
|
+
return errorResult(err, flair.url);
|
|
623
|
+
}
|
|
624
|
+
});
|
|
625
|
+
server.tool("skill_get", "Retrieve a full skill by ID — the complete procedure (`content`) plus trigger and metadata. " +
|
|
626
|
+
"The disclosure step after skill_search's catalog. Read-scoped: you can only get your own or a " +
|
|
627
|
+
"shared skill, never another agent's private skill. A non-skill id returns not-found.", {
|
|
628
|
+
id: z.string().describe("Skill (memory) ID"),
|
|
629
|
+
includeEmbedding: z.coerce.boolean().optional().default(false)
|
|
630
|
+
.describe("Include the raw embedding vector (large, rarely useful). Default false."),
|
|
631
|
+
}, async ({ id, includeEmbedding }) => {
|
|
632
|
+
heartbeat();
|
|
633
|
+
try {
|
|
634
|
+
const mem = await flair.memory.get(id);
|
|
635
|
+
if (!mem || !isSkillRecord(mem)) {
|
|
636
|
+
return { content: [{ type: "text", text: `Skill ${id} not found.` }] };
|
|
637
|
+
}
|
|
638
|
+
const record = includeEmbedding
|
|
639
|
+
? mem
|
|
640
|
+
: stripInternalMemoryFields(mem);
|
|
641
|
+
const trigger = typeof record.trigger === "string" && record.trigger.length > 0
|
|
642
|
+
? record.trigger
|
|
643
|
+
: "";
|
|
644
|
+
const text = [
|
|
645
|
+
record.content,
|
|
646
|
+
"",
|
|
647
|
+
`(id: ${record.id}${trigger ? `, trigger: ${trigger}` : ""}, tags: ${Array.isArray(record.tags) ? record.tags.join(", ") : "skill"}, created: ${record.createdAt ?? ""})`,
|
|
648
|
+
].join("\n");
|
|
649
|
+
return {
|
|
650
|
+
content: [{ type: "text", text }],
|
|
651
|
+
structuredContent: record,
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
catch (err) {
|
|
655
|
+
return errorResult(err, flair.url);
|
|
656
|
+
}
|
|
657
|
+
});
|
|
550
658
|
// ─── Start ───────────────────────────────────────────────────────────────────
|
|
551
659
|
const transport = new StdioServerTransport();
|
|
552
660
|
await server.connect(transport);
|
package/dist/skills.d.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* skills.ts — stdio-adapter shaping for skill_* tools (flair#1575).
|
|
3
|
+
*
|
|
4
|
+
* Native `/mcp` implements skill_store / skill_search / skill_get as thin
|
|
5
|
+
* wrappers over Memory / SemanticSearch (resources/mcp-tools.ts). This
|
|
6
|
+
* package talks HTTP via FlairClient, so the wrappers here only shape
|
|
7
|
+
* the request body and the client-visible result — they re-implement no
|
|
8
|
+
* write, recall, or scoping logic. Server-side SkillScan, forced
|
|
9
|
+
* durability=persistent, and resolveReadScope still run on the daemon.
|
|
10
|
+
*
|
|
11
|
+
* Progressive disclosure matches the native contract: skill_search returns
|
|
12
|
+
* catalog cards (never `content` / embedding); skill_get is the disclosure
|
|
13
|
+
* step for the full procedure.
|
|
14
|
+
*/
|
|
15
|
+
/** The tag that marks a Memory as a skill (resources/skill-write.ts). */
|
|
16
|
+
export declare const SKILL_TAG = "skill";
|
|
17
|
+
export declare function isSkillRecord(record: unknown): boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Lightweight skill CATALOG card — id/name/trigger/description/tags/agentId.
|
|
20
|
+
* `name`/`description` live in the opaque metadata JSON blob; a corrupt or
|
|
21
|
+
* absent blob simply yields no name/desc. `content` and the raw embedding
|
|
22
|
+
* are deliberately absent (skill_search progressive-disclosure contract).
|
|
23
|
+
*/
|
|
24
|
+
export declare function projectSkillCard(r: unknown): Record<string, unknown>;
|
|
25
|
+
export declare function stripInternalMemoryFields<T extends Record<string, unknown>>(value: T): T;
|
|
26
|
+
/** Body for PUT /Memory/:id — matches native skill_store's Memory.post() shape. */
|
|
27
|
+
export declare function buildSkillStoreBody(opts: {
|
|
28
|
+
agentId: string;
|
|
29
|
+
content: string;
|
|
30
|
+
trigger?: string;
|
|
31
|
+
name?: string;
|
|
32
|
+
description?: string;
|
|
33
|
+
tags?: string[];
|
|
34
|
+
claimedClient?: string;
|
|
35
|
+
}): {
|
|
36
|
+
id: string;
|
|
37
|
+
body: Record<string, unknown>;
|
|
38
|
+
};
|
|
39
|
+
/** SemanticSearch body for skill_search — no agentId (scope is the signed identity). */
|
|
40
|
+
export declare function buildSkillSearchBody(opts: {
|
|
41
|
+
task: string;
|
|
42
|
+
limit?: number;
|
|
43
|
+
}): Record<string, unknown>;
|
|
44
|
+
/**
|
|
45
|
+
* Project a SemanticSearch response onto catalog cards. A guard/error payload
|
|
46
|
+
* (no `results` array) is returned untouched so the caller can surface it.
|
|
47
|
+
*/
|
|
48
|
+
export declare function projectSkillSearchResponse(res: unknown): unknown;
|
|
49
|
+
export declare function formatSkillCatalog(results: Array<Record<string, unknown>>): string;
|
package/dist/skills.js
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* skills.ts — stdio-adapter shaping for skill_* tools (flair#1575).
|
|
3
|
+
*
|
|
4
|
+
* Native `/mcp` implements skill_store / skill_search / skill_get as thin
|
|
5
|
+
* wrappers over Memory / SemanticSearch (resources/mcp-tools.ts). This
|
|
6
|
+
* package talks HTTP via FlairClient, so the wrappers here only shape
|
|
7
|
+
* the request body and the client-visible result — they re-implement no
|
|
8
|
+
* write, recall, or scoping logic. Server-side SkillScan, forced
|
|
9
|
+
* durability=persistent, and resolveReadScope still run on the daemon.
|
|
10
|
+
*
|
|
11
|
+
* Progressive disclosure matches the native contract: skill_search returns
|
|
12
|
+
* catalog cards (never `content` / embedding); skill_get is the disclosure
|
|
13
|
+
* step for the full procedure.
|
|
14
|
+
*/
|
|
15
|
+
/** The tag that marks a Memory as a skill (resources/skill-write.ts). */
|
|
16
|
+
export const SKILL_TAG = "skill";
|
|
17
|
+
const INTERNAL_MEMORY_FIELDS = ["embedding", "embeddingModel"];
|
|
18
|
+
export function isSkillRecord(record) {
|
|
19
|
+
const tags = record?.tags;
|
|
20
|
+
return Array.isArray(tags) && tags.includes(SKILL_TAG);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Lightweight skill CATALOG card — id/name/trigger/description/tags/agentId.
|
|
24
|
+
* `name`/`description` live in the opaque metadata JSON blob; a corrupt or
|
|
25
|
+
* absent blob simply yields no name/desc. `content` and the raw embedding
|
|
26
|
+
* are deliberately absent (skill_search progressive-disclosure contract).
|
|
27
|
+
*/
|
|
28
|
+
export function projectSkillCard(r) {
|
|
29
|
+
const row = (r ?? {});
|
|
30
|
+
let name;
|
|
31
|
+
let description;
|
|
32
|
+
if (typeof row.metadata === "string" && row.metadata.length > 0) {
|
|
33
|
+
try {
|
|
34
|
+
const meta = JSON.parse(row.metadata);
|
|
35
|
+
if (meta && typeof meta === "object") {
|
|
36
|
+
if (typeof meta.name === "string")
|
|
37
|
+
name = meta.name;
|
|
38
|
+
if (typeof meta.description === "string")
|
|
39
|
+
description = meta.description;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
/* opaque/corrupt metadata → no name/description on the card */
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
id: row.id,
|
|
48
|
+
name,
|
|
49
|
+
trigger: row.trigger,
|
|
50
|
+
description,
|
|
51
|
+
tags: row.tags,
|
|
52
|
+
agentId: row.agentId,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
export function stripInternalMemoryFields(value) {
|
|
56
|
+
const out = { ...value };
|
|
57
|
+
for (const field of INTERNAL_MEMORY_FIELDS)
|
|
58
|
+
delete out[field];
|
|
59
|
+
return out;
|
|
60
|
+
}
|
|
61
|
+
/** Body for PUT /Memory/:id — matches native skill_store's Memory.post() shape. */
|
|
62
|
+
export function buildSkillStoreBody(opts) {
|
|
63
|
+
const id = `${opts.agentId}-${crypto.randomUUID()}`;
|
|
64
|
+
const body = {
|
|
65
|
+
id,
|
|
66
|
+
agentId: opts.agentId,
|
|
67
|
+
content: opts.content,
|
|
68
|
+
tags: [SKILL_TAG, ...(Array.isArray(opts.tags) ? opts.tags : [])],
|
|
69
|
+
};
|
|
70
|
+
// durability is NOT set — the server forces persistent for skill-tagged
|
|
71
|
+
// writes and rejects an explicit ephemeral/session.
|
|
72
|
+
if (typeof opts.trigger === "string" && opts.trigger.length > 0)
|
|
73
|
+
body.trigger = opts.trigger;
|
|
74
|
+
if (opts.claimedClient)
|
|
75
|
+
body.claimedClient = opts.claimedClient;
|
|
76
|
+
const meta = {};
|
|
77
|
+
if (typeof opts.name === "string" && opts.name.length > 0)
|
|
78
|
+
meta.name = opts.name;
|
|
79
|
+
if (typeof opts.description === "string" && opts.description.length > 0)
|
|
80
|
+
meta.description = opts.description;
|
|
81
|
+
if (Object.keys(meta).length > 0)
|
|
82
|
+
body.metadata = JSON.stringify(meta);
|
|
83
|
+
return { id, body };
|
|
84
|
+
}
|
|
85
|
+
/** SemanticSearch body for skill_search — no agentId (scope is the signed identity). */
|
|
86
|
+
export function buildSkillSearchBody(opts) {
|
|
87
|
+
return {
|
|
88
|
+
q: opts.task,
|
|
89
|
+
tag: SKILL_TAG,
|
|
90
|
+
limit: opts.limit ?? 5,
|
|
91
|
+
includeMetadata: true,
|
|
92
|
+
includeTrigger: true,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Project a SemanticSearch response onto catalog cards. A guard/error payload
|
|
97
|
+
* (no `results` array) is returned untouched so the caller can surface it.
|
|
98
|
+
*/
|
|
99
|
+
export function projectSkillSearchResponse(res) {
|
|
100
|
+
if (!res || typeof res !== "object" || !Array.isArray(res.results)) {
|
|
101
|
+
return res;
|
|
102
|
+
}
|
|
103
|
+
const payload = res;
|
|
104
|
+
return { ...payload, results: payload.results.map(projectSkillCard) };
|
|
105
|
+
}
|
|
106
|
+
export function formatSkillCatalog(results) {
|
|
107
|
+
if (results.length === 0)
|
|
108
|
+
return "No matching skills found.";
|
|
109
|
+
return results
|
|
110
|
+
.map((card, i) => {
|
|
111
|
+
const title = typeof card.name === "string" && card.name.length > 0 ? card.name : "(unnamed skill)";
|
|
112
|
+
const trigger = typeof card.trigger === "string" && card.trigger.length > 0 ? card.trigger : "";
|
|
113
|
+
const desc = typeof card.description === "string" && card.description.length > 0 ? card.description : "";
|
|
114
|
+
const idStr = card.id ? `id:${card.id}` : "";
|
|
115
|
+
const header = [title, trigger, idStr].filter(Boolean).join(" — ");
|
|
116
|
+
return desc ? `${i + 1}. ${header}\n ${desc}` : `${i + 1}. ${header}`;
|
|
117
|
+
})
|
|
118
|
+
.join("\n");
|
|
119
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tpsdev-ai/flair-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.53.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",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@modelcontextprotocol/sdk": "1.27.1",
|
|
31
|
-
"@tpsdev-ai/flair-client": "0.
|
|
31
|
+
"@tpsdev-ai/flair-client": "0.53.0",
|
|
32
32
|
"zod": "4.3.6"
|
|
33
33
|
},
|
|
34
34
|
"license": "Apache-2.0",
|