@tpsdev-ai/flair-mcp 0.55.2 → 0.57.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 +5 -5
- package/dist/adapter-tools.js +2 -1
- package/dist/continuity-capture-hook.d.ts +7 -0
- package/dist/continuity-capture-hook.js +3 -2
- package/dist/record-id-path.d.ts +20 -0
- package/dist/record-id-path.js +28 -0
- package/dist/session-start-hook.d.ts +25 -5
- package/dist/session-start-hook.js +80 -8
- package/dist/tool-descriptors/index.js +2 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@ cat > .mcp.json << 'EOF'
|
|
|
23
23
|
EOF
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
-
`npx -y @tpsdev-ai/flair-mcp` fetches and runs the server on demand — no global install needed. (`flair init
|
|
26
|
+
`npx -y @tpsdev-ai/flair-mcp` fetches and runs the server on demand — no global install needed. (`flair init --agent <id>` attempts to wire detected clients when wiring is enabled; see below.)
|
|
27
27
|
|
|
28
28
|
### Prerequisites
|
|
29
29
|
|
|
@@ -48,7 +48,7 @@ Once configured, Claude Code (or any MCP client) gets these tools:
|
|
|
48
48
|
| `skill_search` | Find skills that apply to a task. Returns a catalog, not the procedure. |
|
|
49
49
|
| `skill_get` | Retrieve the full skill by ID (disclosure after `skill_search`). |
|
|
50
50
|
| `bootstrap` | Cold-start context — soul + recent memories in one call. |
|
|
51
|
-
| `soul_set` |
|
|
51
|
+
| `soul_set` | Soul writes require verified administrator Basic credentials; Ed25519 agent requests are refused. Operators should use the REST API or CLI. |
|
|
52
52
|
| `soul_get` | Get a personality or project context entry. |
|
|
53
53
|
| `record_usage` | Report that recalled memories were actually used (drives `usageCount`). |
|
|
54
54
|
|
|
@@ -68,7 +68,7 @@ Once configured, Claude Code (or any MCP client) gets these tools:
|
|
|
68
68
|
Claude Code ↔ stdio ↔ flair-mcp ↔ HTTP ↔ Flair (Harper)
|
|
69
69
|
```
|
|
70
70
|
|
|
71
|
-
The MCP server is a thin wrapper around `@tpsdev-ai/flair-client`. All memory is stored in
|
|
71
|
+
The MCP server is a thin wrapper around `@tpsdev-ai/flair-client`. All memory is stored in the Flair instance selected by `FLAIR_URL` (defaulting to localhost). Requests are signed with the agent's Ed25519 key whenever one resolves. Only when no key resolves, and both `FLAIR_ADMIN_USER` and `FLAIR_ADMIN_PASSWORD` are set, does the client send admin Basic auth; a key that cannot be parsed is an error, and a rejected signature is not retried with Basic. The MCP client connects to FLAIR_URL; a paired local instance may separately federate eligible memories.
|
|
72
72
|
|
|
73
73
|
## Remote Flair
|
|
74
74
|
|
|
@@ -82,14 +82,14 @@ Point to a remote Flair instance:
|
|
|
82
82
|
"args": ["-y", "@tpsdev-ai/flair-mcp"],
|
|
83
83
|
"env": {
|
|
84
84
|
"FLAIR_AGENT_ID": "my-project",
|
|
85
|
-
"FLAIR_URL": "
|
|
85
|
+
"FLAIR_URL": "https://your-server:19926"
|
|
86
86
|
}
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
90
|
```
|
|
91
91
|
|
|
92
|
-
Copy your key from the server: `scp server:~/.flair/keys/my-project.key ~/.flair/keys/`
|
|
92
|
+
The client REFUSES to send admin Basic credentials over plain HTTP to a non-loopback host (the credentials would travel in a request header): use an HTTPS `FLAIR_URL`, or an Ed25519 key, for a remote instance. Copy your key from the server: `scp server:~/.flair/keys/my-project.key ~/.flair/keys/`
|
|
93
93
|
|
|
94
94
|
## License
|
|
95
95
|
|
package/dist/adapter-tools.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* descriptor with no handler (or a handler with no descriptor) fails at
|
|
7
7
|
* registration, so the surfaces cannot drift by omission.
|
|
8
8
|
*/
|
|
9
|
+
import { encodeRecordId } from "./record-id-path.js";
|
|
9
10
|
import { STDIO_TOOL_DESCRIPTORS, toStdioMcpToolDef, } from "./tool-descriptors/index.js";
|
|
10
11
|
import { buildCatchupRequest, summarizeCatchup } from "./catchup.js";
|
|
11
12
|
import { classifyError } from "./errors.js";
|
|
@@ -265,7 +266,7 @@ const skill_store = async ({ content, trigger, name, description, tags }, { flai
|
|
|
265
266
|
tags,
|
|
266
267
|
claimedClient: flair.claimedClient,
|
|
267
268
|
});
|
|
268
|
-
const result = await flair.request("PUT", `/Memory/${id}`, body);
|
|
269
|
+
const result = await flair.request("PUT", `/Memory/${encodeRecordId(id)}`, body);
|
|
269
270
|
const writtenId = typeof result?.id === "string" && result.id.length > 0 ? result.id : id;
|
|
270
271
|
const preview = content.length > 120 ? content.slice(0, 120) + "..." : content;
|
|
271
272
|
const lines = [
|
|
@@ -41,6 +41,13 @@ export interface CaptureDeps {
|
|
|
41
41
|
sessionDir?: string;
|
|
42
42
|
env?: Record<string, string | undefined>;
|
|
43
43
|
now?: () => Date;
|
|
44
|
+
/** Test seam (flair#1970): the row whose `id` becomes the `/Memory/<id>` path
|
|
45
|
+
* segment. Its id is URL-safe by construction, so a test drives the REAL
|
|
46
|
+
* request path with a dot-segment id through this override and checks that
|
|
47
|
+
* no request is sent when the id is refused. Production never sets it. */
|
|
48
|
+
buildRow?: (agentId: string, state: unknown, plan: unknown, now: Date) => {
|
|
49
|
+
id: string;
|
|
50
|
+
};
|
|
44
51
|
/** Debug-level warn sink (default: one stderr line). Never stdout. */
|
|
45
52
|
warn?: (message: string) => void;
|
|
46
53
|
}
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
* FLAIR_HOOK_PROBE (probe mode: exit immediately, no stdin read, no writes)
|
|
35
35
|
*/
|
|
36
36
|
import { isProbeMode, readEnvOrUnset, stripInterpolationLiteralsFromEnv } from "./env-guard.js";
|
|
37
|
+
import { memoryPutPath } from "./record-id-path.js";
|
|
37
38
|
import { buildJournalRow, bumpSeq, isSafeFileId, planCapture, resolveContinuityTimeoutMs, resolveSessionDir, } from "./continuity.js";
|
|
38
39
|
/** Read all of stdin. Resolves on EOF, with a short fallback for manual runs
|
|
39
40
|
* where nothing is piped (so it never hangs). */
|
|
@@ -121,11 +122,11 @@ export async function runCapture(rawInput, deps = {}) {
|
|
|
121
122
|
const state = bumpSeq(sessionDir, agentId, harnessSessionId, now());
|
|
122
123
|
if (!state)
|
|
123
124
|
return { wrote: false, reason: "no-state" };
|
|
124
|
-
const row = buildJournalRow(agentId, state, plan, now());
|
|
125
|
+
const row = (deps.buildRow ?? buildJournalRow)(agentId, state, plan, now());
|
|
125
126
|
const makeClient = deps.makeClient ?? defaultClientFactory;
|
|
126
127
|
try {
|
|
127
128
|
const client = await makeClient(agentId);
|
|
128
|
-
await withTimeout(Promise.resolve(client.request("PUT",
|
|
129
|
+
await withTimeout(Promise.resolve(client.request("PUT", memoryPutPath(row.id), row)), resolveContinuityTimeoutMs(env));
|
|
129
130
|
return { wrote: true, reason: "written" };
|
|
130
131
|
}
|
|
131
132
|
catch (err) {
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* record-id-path.ts — the one rule for a record id used as the single dynamic
|
|
3
|
+
* path segment of a `/Memory/<id>` request built inside flair-mcp (flair#1970).
|
|
4
|
+
*
|
|
5
|
+
* Self-contained on purpose: this module must load and typecheck WITHOUT
|
|
6
|
+
* @tpsdev-ai/flair-client's built dist present (the root `bun test` and strict
|
|
7
|
+
* test-suite typecheck lanes run before the client is built), so it does not
|
|
8
|
+
* import the client's own `encodeRecordId` — it states the same rule here.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Percent-encode an id so it reaches the server as ONE path segment that
|
|
12
|
+
* decodes back to the id, addressing exactly that record. REFUSES an id that is
|
|
13
|
+
* exactly `.` or `..`: percent-encoding leaves those unchanged and URL
|
|
14
|
+
* normalization collapses `/Memory/.` to `/Memory/` and `/Memory/..` to `/`, so
|
|
15
|
+
* the sent path would not be the id (nor the signed path). Such an id cannot
|
|
16
|
+
* address its record, so it is an error, not a request.
|
|
17
|
+
*/
|
|
18
|
+
export declare function encodeRecordId(id: string): string;
|
|
19
|
+
/** The PUT path for a row: its id as ONE percent-encoded path segment. */
|
|
20
|
+
export declare function memoryPutPath(id: string): string;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* record-id-path.ts — the one rule for a record id used as the single dynamic
|
|
3
|
+
* path segment of a `/Memory/<id>` request built inside flair-mcp (flair#1970).
|
|
4
|
+
*
|
|
5
|
+
* Self-contained on purpose: this module must load and typecheck WITHOUT
|
|
6
|
+
* @tpsdev-ai/flair-client's built dist present (the root `bun test` and strict
|
|
7
|
+
* test-suite typecheck lanes run before the client is built), so it does not
|
|
8
|
+
* import the client's own `encodeRecordId` — it states the same rule here.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Percent-encode an id so it reaches the server as ONE path segment that
|
|
12
|
+
* decodes back to the id, addressing exactly that record. REFUSES an id that is
|
|
13
|
+
* exactly `.` or `..`: percent-encoding leaves those unchanged and URL
|
|
14
|
+
* normalization collapses `/Memory/.` to `/Memory/` and `/Memory/..` to `/`, so
|
|
15
|
+
* the sent path would not be the id (nor the signed path). Such an id cannot
|
|
16
|
+
* address its record, so it is an error, not a request.
|
|
17
|
+
*/
|
|
18
|
+
export function encodeRecordId(id) {
|
|
19
|
+
if (id === "." || id === "..") {
|
|
20
|
+
throw new Error(`record id ${JSON.stringify(id)} is a URL path dot-segment ("." or ".."); ` +
|
|
21
|
+
`it cannot be addressed as one path segment of /Memory/<id>. Use a different id.`);
|
|
22
|
+
}
|
|
23
|
+
return encodeURIComponent(id);
|
|
24
|
+
}
|
|
25
|
+
/** The PUT path for a row: its id as ONE percent-encoded path segment. */
|
|
26
|
+
export function memoryPutPath(id) {
|
|
27
|
+
return `/Memory/${encodeRecordId(id)}`;
|
|
28
|
+
}
|
|
@@ -17,11 +17,14 @@
|
|
|
17
17
|
*
|
|
18
18
|
* NO-OP-ON-ANY-FAILURE GUARANTEE
|
|
19
19
|
* ------------------------------
|
|
20
|
-
* This hook can never block or break Claude Code startup. Every failure mode
|
|
21
|
-
* missing FLAIR_AGENT_ID, malformed stdin, Flair unreachable, auth error, a
|
|
22
|
-
* hung daemon, an unexpected throw
|
|
23
|
-
*
|
|
24
|
-
* a
|
|
20
|
+
* This hook can never block or break Claude Code startup. Every failure mode
|
|
21
|
+
* (missing FLAIR_AGENT_ID, malformed stdin, Flair unreachable, auth error, a
|
|
22
|
+
* hung daemon, an unexpected throw) exits 0. Malformed stdin is treated as
|
|
23
|
+
* empty input and can still yield bootstrap context. A failed bootstrap yields
|
|
24
|
+
* a continuity resume hint only when the separate lookup finds eligible prior
|
|
25
|
+
* entries; otherwise stdout is `{}`. The hook attempts one stderr diagnostic
|
|
26
|
+
* when bootstrap fails (flair#1943). The Codex command retains stderr and the
|
|
27
|
+
* Claude Code command discards it; delivery depends on stderr being writable.
|
|
25
28
|
*
|
|
26
29
|
* A hard timeout (FLAIR_HOOK_TIMEOUT_MS, default 8s) wraps the bootstrap call
|
|
27
30
|
* so a stalled Flair daemon can't hang session startup; on timeout we no-op.
|
|
@@ -96,6 +99,23 @@ interface BootstrapClient extends Partial<PresencePoster> {
|
|
|
96
99
|
* sharing it; re-exported here unchanged so existing importers keep working.
|
|
97
100
|
*/
|
|
98
101
|
export { isProbeMode };
|
|
102
|
+
/** The hook's OWN bootstrap-timer rejection (flair#1943). A dedicated class so
|
|
103
|
+
* the classifier recognises its own timeout by IDENTITY, never by reading a
|
|
104
|
+
* message. */
|
|
105
|
+
export declare class BootstrapTimeoutError extends Error {
|
|
106
|
+
constructor();
|
|
107
|
+
}
|
|
108
|
+
export type BootstrapFailureKind = "auth" | "timeout" | "unreachable" | `http-${number}`;
|
|
109
|
+
/**
|
|
110
|
+
* flair#1943 — classify a bootstrap failure for the one stderr line. Reads a
|
|
111
|
+
* numeric HTTP status FIRST (`status`, what FlairError carries, then
|
|
112
|
+
* `status_code`, then `statusCode`); when a status exists the message is never
|
|
113
|
+
* consulted. With no status, the ONLY timeout is the hook's own bootstrap
|
|
114
|
+
* timer (a BootstrapTimeoutError) or an error whose name is exactly
|
|
115
|
+
* `TimeoutError`; everything else is `unreachable`. No kind is ever decided
|
|
116
|
+
* from message text. Never reads or includes credentials.
|
|
117
|
+
*/
|
|
118
|
+
export declare function classifyBootstrapFailure(err: unknown): BootstrapFailureKind;
|
|
99
119
|
/**
|
|
100
120
|
* Core hook logic, with injectable dependencies so it can be unit-tested
|
|
101
121
|
* without a live Flair daemon. Returns the exact string to print to stdout.
|
|
@@ -17,11 +17,14 @@
|
|
|
17
17
|
*
|
|
18
18
|
* NO-OP-ON-ANY-FAILURE GUARANTEE
|
|
19
19
|
* ------------------------------
|
|
20
|
-
* This hook can never block or break Claude Code startup. Every failure mode
|
|
21
|
-
* missing FLAIR_AGENT_ID, malformed stdin, Flair unreachable, auth error, a
|
|
22
|
-
* hung daemon, an unexpected throw
|
|
23
|
-
*
|
|
24
|
-
* a
|
|
20
|
+
* This hook can never block or break Claude Code startup. Every failure mode
|
|
21
|
+
* (missing FLAIR_AGENT_ID, malformed stdin, Flair unreachable, auth error, a
|
|
22
|
+
* hung daemon, an unexpected throw) exits 0. Malformed stdin is treated as
|
|
23
|
+
* empty input and can still yield bootstrap context. A failed bootstrap yields
|
|
24
|
+
* a continuity resume hint only when the separate lookup finds eligible prior
|
|
25
|
+
* entries; otherwise stdout is `{}`. The hook attempts one stderr diagnostic
|
|
26
|
+
* when bootstrap fails (flair#1943). The Codex command retains stderr and the
|
|
27
|
+
* Claude Code command discards it; delivery depends on stderr being writable.
|
|
25
28
|
*
|
|
26
29
|
* A hard timeout (FLAIR_HOOK_TIMEOUT_MS, default 8s) wraps the bootstrap call
|
|
27
30
|
* so a stalled Flair daemon can't hang session startup; on timeout we no-op.
|
|
@@ -120,10 +123,19 @@ function readStdin() {
|
|
|
120
123
|
setTimeout(() => resolve(data), 200).unref?.();
|
|
121
124
|
});
|
|
122
125
|
}
|
|
123
|
-
/**
|
|
126
|
+
/** The hook's OWN bootstrap-timer rejection (flair#1943). A dedicated class so
|
|
127
|
+
* the classifier recognises its own timeout by IDENTITY, never by reading a
|
|
128
|
+
* message. */
|
|
129
|
+
export class BootstrapTimeoutError extends Error {
|
|
130
|
+
constructor() {
|
|
131
|
+
super("bootstrap timeout");
|
|
132
|
+
this.name = "BootstrapTimeoutError";
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/** Race a promise against a timeout. Rejects with a BootstrapTimeoutError if exceeded. */
|
|
124
136
|
function withTimeout(promise, ms) {
|
|
125
137
|
return new Promise((resolve, reject) => {
|
|
126
|
-
const timer = setTimeout(() => reject(new
|
|
138
|
+
const timer = setTimeout(() => reject(new BootstrapTimeoutError()), ms);
|
|
127
139
|
timer.unref?.();
|
|
128
140
|
promise.then((value) => {
|
|
129
141
|
clearTimeout(timer);
|
|
@@ -134,6 +146,60 @@ function withTimeout(promise, ms) {
|
|
|
134
146
|
});
|
|
135
147
|
});
|
|
136
148
|
}
|
|
149
|
+
/**
|
|
150
|
+
* flair#1943 — classify a bootstrap failure for the one stderr line. Reads a
|
|
151
|
+
* numeric HTTP status FIRST (`status`, what FlairError carries, then
|
|
152
|
+
* `status_code`, then `statusCode`); when a status exists the message is never
|
|
153
|
+
* consulted. With no status, the ONLY timeout is the hook's own bootstrap
|
|
154
|
+
* timer (a BootstrapTimeoutError) or an error whose name is exactly
|
|
155
|
+
* `TimeoutError`; everything else is `unreachable`. No kind is ever decided
|
|
156
|
+
* from message text. Never reads or includes credentials.
|
|
157
|
+
*/
|
|
158
|
+
export function classifyBootstrapFailure(err) {
|
|
159
|
+
const e = err;
|
|
160
|
+
const status = numericStatus(e);
|
|
161
|
+
if (status !== undefined)
|
|
162
|
+
return status === 401 || status === 403 ? "auth" : `http-${status}`;
|
|
163
|
+
if (err instanceof BootstrapTimeoutError)
|
|
164
|
+
return "timeout";
|
|
165
|
+
if (typeof e?.name === "string" && e.name === "TimeoutError")
|
|
166
|
+
return "timeout";
|
|
167
|
+
return "unreachable";
|
|
168
|
+
}
|
|
169
|
+
/** The first NUMERIC HTTP status the error carries, checked `status` →
|
|
170
|
+
* `status_code` → `statusCode` (flair#1943). */
|
|
171
|
+
function numericStatus(e) {
|
|
172
|
+
for (const key of ["status", "status_code", "statusCode"]) {
|
|
173
|
+
const v = e?.[key];
|
|
174
|
+
if (typeof v === "number" && Number.isFinite(v))
|
|
175
|
+
return v;
|
|
176
|
+
}
|
|
177
|
+
return undefined;
|
|
178
|
+
}
|
|
179
|
+
// flair#1943: one no-op 'error' listener per process, so repeated failed
|
|
180
|
+
// runs in one process never add listeners (and never trigger Node's
|
|
181
|
+
// max-listeners warning on stderr).
|
|
182
|
+
let stderrErrorAbsorbed = false;
|
|
183
|
+
/** The stderr diagnostic for a failed bootstrap. NAMES the actor, the state and
|
|
184
|
+
* the remedy; never contains a key, token, password or Authorization value. */
|
|
185
|
+
function reportBootstrapFailure(err) {
|
|
186
|
+
const kind = classifyBootstrapFailure(err);
|
|
187
|
+
const line = `flair session-start: bootstrap failed (${kind}); this session starts without bootstrap context. Next: run \`flair doctor\`, and check FLAIR_URL and this agent's key.\n`;
|
|
188
|
+
try {
|
|
189
|
+
// Best-effort (flair#1943): a failed stderr write (a closed pipe → EPIPE)
|
|
190
|
+
// must not change stdout or the exit code. The write may throw
|
|
191
|
+
// SYNCHRONOUSLY or surface later as an 'error' event on the stream; absorb
|
|
192
|
+
// both, so the hook still prints its payload and exits 0.
|
|
193
|
+
if (!stderrErrorAbsorbed) {
|
|
194
|
+
process.stderr.on("error", () => { });
|
|
195
|
+
stderrErrorAbsorbed = true;
|
|
196
|
+
}
|
|
197
|
+
process.stderr.write(line);
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
// ignore — the diagnostic is best-effort
|
|
201
|
+
}
|
|
202
|
+
}
|
|
137
203
|
/** Build the SessionStart hook output JSON from a context string. */
|
|
138
204
|
function hookOutput(context) {
|
|
139
205
|
return JSON.stringify({
|
|
@@ -207,8 +273,14 @@ export async function runHook(rawInput, makeClient = defaultClientFactory) {
|
|
|
207
273
|
})), resolveTimeoutMs());
|
|
208
274
|
context = res && res.context ? String(res.context) : "";
|
|
209
275
|
}
|
|
210
|
-
catch {
|
|
276
|
+
catch (err) {
|
|
211
277
|
context = ""; // flair unreachable / auth error / timeout → no bootstrap context
|
|
278
|
+
// flair#1943: keeping stderr open cannot reveal an error never written to
|
|
279
|
+
// it. Write ONE line to STDERR (never stdout — that is the hook payload),
|
|
280
|
+
// so a real failure stays visible instead of being swallowed. stdout and
|
|
281
|
+
// the exit code are unchanged (the no-op payload), so a failure never
|
|
282
|
+
// blocks the session.
|
|
283
|
+
reportBootstrapFailure(err);
|
|
212
284
|
}
|
|
213
285
|
const resumeHint = await resumeHintDone;
|
|
214
286
|
await presenceDone;
|
|
@@ -51,7 +51,7 @@ export function descriptorNames(descriptors) {
|
|
|
51
51
|
export const TOOL_DESCRIPTORS = [
|
|
52
52
|
{
|
|
53
53
|
"name": "memory_search",
|
|
54
|
-
"description": "Search memories by meaning. Understands temporal queries like 'what happened today'. Scoped to your agent's own
|
|
54
|
+
"description": "Search memories by meaning. Understands temporal queries like 'what happened today'. Scoped to your agent's own and other agents' non-private memories.",
|
|
55
55
|
"inputSchema": {
|
|
56
56
|
"type": "object",
|
|
57
57
|
"properties": {
|
|
@@ -80,7 +80,7 @@ export const TOOL_DESCRIPTORS = [
|
|
|
80
80
|
"query"
|
|
81
81
|
]
|
|
82
82
|
},
|
|
83
|
-
"outputShape": "{ results: MemoryRecord[] } — semantic hits scoped to the caller's own
|
|
83
|
+
"outputShape": "{ results: MemoryRecord[] } — semantic hits scoped to the caller's own and other agents' non-private memories; each hit carries content, never the raw embedding.",
|
|
84
84
|
"annotations": {
|
|
85
85
|
"readOnlyHint": true
|
|
86
86
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tpsdev-ai/flair-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.57.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",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"@modelcontextprotocol/sdk": "1.27.1",
|
|
32
|
-
"@tpsdev-ai/flair-client": "0.
|
|
32
|
+
"@tpsdev-ai/flair-client": "0.57.0",
|
|
33
33
|
"zod": "4.3.6"
|
|
34
34
|
},
|
|
35
35
|
"license": "Apache-2.0",
|