@remnic/plugin-claude-code 9.48.1 → 9.49.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/.claude-plugin/plugin.json +29 -2
- package/mcp-server-stdio/server.js +380 -0
- package/package.json +2 -1
|
@@ -1,10 +1,37 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "remnic",
|
|
3
3
|
"description": "Universal memory for AI agents — automatic recall, observation, and cross-agent knowledge sharing",
|
|
4
|
-
"version": "9.
|
|
4
|
+
"version": "9.49.1",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Joshua Warren"
|
|
7
7
|
},
|
|
8
8
|
"homepage": "https://github.com/joshuaswarren/remnic",
|
|
9
|
-
"repository": "https://github.com/joshuaswarren/remnic"
|
|
9
|
+
"repository": "https://github.com/joshuaswarren/remnic",
|
|
10
|
+
"userConfig": {
|
|
11
|
+
"remnic_daemon_token": {
|
|
12
|
+
"type": "string",
|
|
13
|
+
"title": "Remnic Daemon Bearer Token",
|
|
14
|
+
"description": "Bearer token the MCP server sends on every request. Mint via `remnic token generate claude-code` (or `remnic connectors install claude-code` on first install).",
|
|
15
|
+
"sensitive": true
|
|
16
|
+
},
|
|
17
|
+
"remnic_daemon_url": {
|
|
18
|
+
"type": "string",
|
|
19
|
+
"title": "Remnic Daemon MCP URL",
|
|
20
|
+
"description": "Full URL of the Remnic daemon's MCP endpoint. Default http://localhost:4318/mcp. Use http:// loopback or https:// — plaintext http to non-loopback is rejected because the bearer token travels with every request.",
|
|
21
|
+
"default": "http://localhost:4318/mcp"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"mcpServers": {
|
|
25
|
+
"remnic": {
|
|
26
|
+
"type": "stdio",
|
|
27
|
+
"command": "node",
|
|
28
|
+
"args": [
|
|
29
|
+
"${CLAUDE_PLUGIN_ROOT}/mcp-server-stdio/server.js"
|
|
30
|
+
],
|
|
31
|
+
"env": {
|
|
32
|
+
"REMNIC_PLUGIN_DAEMON_URL": "${user_config.remnic_daemon_url}",
|
|
33
|
+
"REMNIC_PLUGIN_DAEMON_TOKEN": "${user_config.remnic_daemon_token}"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
10
37
|
}
|
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// @remnic/plugin-claude-code — stdio↔HTTP MCP proxy
|
|
3
|
+
//
|
|
4
|
+
// Bridges the Remnic HTTP MCP endpoint (`<user_config.remnic_daemon_url>`)
|
|
5
|
+
// to a stdio MCP server Claude Code can speak without forking per request.
|
|
6
|
+
//
|
|
7
|
+
// Auth: reads `REMNIC_PLUGIN_DAEMON_TOKEN` (a daemon bearer token the user
|
|
8
|
+
// provided at plugin install time via Claude Code's `userConfig` flow) and
|
|
9
|
+
// sends it as the `Authorization: Bearer …` header on every HTTP request.
|
|
10
|
+
//
|
|
11
|
+
// URL: reads `REMNIC_PLUGIN_DAEMON_URL`, defaulting to `http://localhost:4318/mcp`
|
|
12
|
+
// (matches the package's documented local daemon default). http:// is gated to
|
|
13
|
+
// loopback hostnames (localhost, an IPv4 in 127.0.0.0/8, or ::1) so the bearer
|
|
14
|
+
// token never travels cleartext to a non-loopback target; https:// is
|
|
15
|
+
// unrestricted (the bearer is encrypted in transport).
|
|
16
|
+
//
|
|
17
|
+
// Transport: line-delimited JSON-RPC over stdin (with chunk-boundary safe
|
|
18
|
+
// accumulation), JSON-RPC over stdout. The Remnic daemon's `/mcp` endpoint
|
|
19
|
+
// streams SSE in some modes; for tool-call sizing this proxy reads a single
|
|
20
|
+
// buffered response per request and tolerates empty 202 bodies used by
|
|
21
|
+
// notifications/initialized.
|
|
22
|
+
//
|
|
23
|
+
// Exit codes:
|
|
24
|
+
// 0 graceful shutdown
|
|
25
|
+
// 2 fatal config error (missing required env, non-loopback http, etc.)
|
|
26
|
+
|
|
27
|
+
import http from "node:http";
|
|
28
|
+
import https from "node:https";
|
|
29
|
+
import { URL } from "node:url";
|
|
30
|
+
|
|
31
|
+
const URL_DEFAULT = "http://localhost:4318/mcp";
|
|
32
|
+
const REQUEST_TIMEOUT_MS = 30_000;
|
|
33
|
+
const EXIT_DRAIN_TIMEOUT_MS = 10_000;
|
|
34
|
+
const MAX_BODY_BYTES = 8 * 1024 * 1024; // 8 MiB; matches MCP tool-result upper bound
|
|
35
|
+
|
|
36
|
+
function readEnv(name, fallback) {
|
|
37
|
+
const v = process.env[name];
|
|
38
|
+
if (v === undefined || v === "") return fallback;
|
|
39
|
+
return v;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function fatal(msg) {
|
|
43
|
+
process.stderr.write(`remnic-mcp-proxy: ${msg}\n`);
|
|
44
|
+
process.exit(2);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function sanitizeMessage(msg) {
|
|
48
|
+
// Strip paths and control characters from upstream Node errors so they do
|
|
49
|
+
// not leak absolute filesystem paths or stack snippets into a JSON-RPC
|
|
50
|
+
// error envelope the client sees.
|
|
51
|
+
if (typeof msg !== "string") return "internal error";
|
|
52
|
+
let s = msg.replace(/[\p{Cc}]/gu, " ");
|
|
53
|
+
// Replace absolute paths with a generic token (defense-in-depth).
|
|
54
|
+
s = s.replace(/\s(?:file:|\/)[^\s'"]+/g, " <path> ");
|
|
55
|
+
if (s.length > 200) s = `${s.slice(0, 200)}…`;
|
|
56
|
+
return s;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function isLoopbackHostname(hostname) {
|
|
60
|
+
if (!hostname) return false;
|
|
61
|
+
const h = hostname.toLowerCase();
|
|
62
|
+
if (h === "localhost") return true;
|
|
63
|
+
// Reject bracketed literal IPv6 like "[::1]" — strip brackets first.
|
|
64
|
+
const bare = h.startsWith("[") && h.endsWith("]") ? h.slice(1, -1) : h;
|
|
65
|
+
if (bare === "::1") return true;
|
|
66
|
+
// IPv4 dotted: check 127.0.0.0/8 range.
|
|
67
|
+
if (/^127(?:\.\d{1,3}){3}$/.test(bare)) return true;
|
|
68
|
+
// Defensive: anything that resolves through DNS that happens to be a
|
|
69
|
+
// loopback name (rare) is allowed by the URL constructor only if literally
|
|
70
|
+
// "localhost" so users must opt-in to LAN/external IPs explicitly.
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const daemonUrl = readEnv("REMNIC_PLUGIN_DAEMON_URL", URL_DEFAULT);
|
|
75
|
+
const token = readEnv("REMNIC_PLUGIN_DAEMON_TOKEN", "");
|
|
76
|
+
if (!token) {
|
|
77
|
+
fatal(
|
|
78
|
+
"REMNIC_PLUGIN_DAEMON_TOKEN is not set; the plugin install flow must supply the bearer token via Claude Code userConfig."
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
let parsed;
|
|
82
|
+
try {
|
|
83
|
+
parsed = new URL(daemonUrl);
|
|
84
|
+
} catch {
|
|
85
|
+
fatal(`REMNIC_PLUGIN_DAEMON_URL is not a parseable URL: ${daemonUrl}`);
|
|
86
|
+
}
|
|
87
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
88
|
+
fatal(
|
|
89
|
+
`unsupported URL protocol in REMNIC_PLUGIN_DAEMON_URL: ${parsed.protocol} (expected http: or https:; refuse to send bearer token over plaintext to a non-loopback target)`
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
if (parsed.protocol === "http:" && !isLoopbackHostname(parsed.hostname)) {
|
|
93
|
+
fatal(
|
|
94
|
+
`REMNIC_PLUGIN_DAEMON_URL uses plain http:// to a non-loopback host (${parsed.hostname}); refuse to send bearer token cleartext. Use https:// or http://localhost / http://127.0.0.x.`
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
const transport = parsed.protocol === "https:" ? https : http;
|
|
98
|
+
|
|
99
|
+
// State: an in-flight count for graceful drain on stdin EOF.
|
|
100
|
+
let inFlight = 0;
|
|
101
|
+
let stdinEnded = false;
|
|
102
|
+
let exiting = false;
|
|
103
|
+
|
|
104
|
+
let stdinTail = ""; // partial line carried across stdin data events
|
|
105
|
+
const lineQueue = [];
|
|
106
|
+
process.stdin.setEncoding("utf8");
|
|
107
|
+
process.stdin.on("data", (chunk) => {
|
|
108
|
+
// Concatenate with any prior tail (a JSON-RPC record split across chunks),
|
|
109
|
+
// split on \n, and carry the final fragment forward.
|
|
110
|
+
const buf = `${stdinTail}${chunk}`;
|
|
111
|
+
const parts = buf.split("\n");
|
|
112
|
+
stdinTail = parts.pop();
|
|
113
|
+
for (const p of parts) {
|
|
114
|
+
if (p.length === 0) continue;
|
|
115
|
+
lineQueue.push(p);
|
|
116
|
+
}
|
|
117
|
+
drain();
|
|
118
|
+
});
|
|
119
|
+
process.stdin.on("end", () => {
|
|
120
|
+
stdinEnded = true;
|
|
121
|
+
// Flush any trailing partial line; the boundary contract is that one
|
|
122
|
+
// newline-terminated line per JSON-RPC record.
|
|
123
|
+
if (stdinTail.length > 0) {
|
|
124
|
+
lineQueue.push(stdinTail);
|
|
125
|
+
stdinTail = "";
|
|
126
|
+
drain();
|
|
127
|
+
}
|
|
128
|
+
// Drain in-flight forwards before exiting.
|
|
129
|
+
tryExit();
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
process.stdin.on("error", (err) => {
|
|
133
|
+
fatal(`stdin error: ${sanitizeMessage(err.message)}`);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
process.stdout.on("error", (err) => {
|
|
137
|
+
process.stderr.write(`remnic-mcp-proxy: stdout error: ${sanitizeMessage(err.message)}\n`);
|
|
138
|
+
process.exit(1);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
function writeRaw(payload) {
|
|
142
|
+
try {
|
|
143
|
+
process.stdout.write(`${payload}\n`);
|
|
144
|
+
} catch (err) {
|
|
145
|
+
process.stderr.write(`remnic-mcp-proxy: stdout write failed: ${sanitizeMessage(err.message)}\n`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function writeError(id, code, message, requestId) {
|
|
150
|
+
const safeMsg = sanitizeMessage(message);
|
|
151
|
+
const payload = {
|
|
152
|
+
jsonrpc: "2.0",
|
|
153
|
+
id: id ?? null,
|
|
154
|
+
error: { code, message: safeMsg },
|
|
155
|
+
};
|
|
156
|
+
if (requestId !== undefined && requestId !== null) payload.id = requestId;
|
|
157
|
+
writeRaw(JSON.stringify(payload));
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function writeResult(id, result) {
|
|
161
|
+
writeRaw(JSON.stringify({ jsonrpc: "2.0", id, result }));
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function writeNotificationAck() {
|
|
165
|
+
// Per JSON-RPC 2.0, a notification has no `id` and the receiver MUST NOT reply.
|
|
166
|
+
// The proxy acknowledges indirectly by emitting nothing for notifications.
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function drain() {
|
|
170
|
+
while (lineQueue.length > 0) {
|
|
171
|
+
const line = lineQueue.shift();
|
|
172
|
+
handleLine(line);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function handleLine(line) {
|
|
177
|
+
let msg;
|
|
178
|
+
try {
|
|
179
|
+
msg = JSON.parse(line);
|
|
180
|
+
} catch (err) {
|
|
181
|
+
writeError(null, -32700, `parse error: ${sanitizeMessage(err.message)}`);
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
// Validate JSON-RPC shape: must be a non-null object with a string method.
|
|
185
|
+
if (msg === null || typeof msg !== "object" || Array.isArray(msg)) {
|
|
186
|
+
writeError(null, -32600, "invalid request: not a JSON-RPC object");
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
if (typeof msg.method !== "string" || msg.method.length === 0) {
|
|
190
|
+
writeError(msg.id ?? null, -32600, "invalid request: missing or empty method");
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
// Distinguish notification (no `id`) from request (has `id`).
|
|
194
|
+
const isNotification = !Object.prototype.hasOwnProperty.call(msg, "id");
|
|
195
|
+
inFlight += 1;
|
|
196
|
+
forward(msg, isNotification).finally(() => {
|
|
197
|
+
inFlight -= 1;
|
|
198
|
+
tryExit();
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function forward(msg, isNotification) {
|
|
203
|
+
// For the SDK's "initialized" notification, the daemon may return 202 with
|
|
204
|
+
// an empty body and no Content-Type. Treat that as a successful notification
|
|
205
|
+
// ack: emit no reply and resolve without surfacing a parse error. For
|
|
206
|
+
// requests, missing/empty body is also a parse error from the daemon.
|
|
207
|
+
const body = JSON.stringify({
|
|
208
|
+
jsonrpc: msg.jsonrpc ?? "2.0",
|
|
209
|
+
...(isNotification ? {} : { id: msg.id ?? null }),
|
|
210
|
+
method: msg.method,
|
|
211
|
+
params: msg.params ?? {},
|
|
212
|
+
});
|
|
213
|
+
return new Promise((resolve) => {
|
|
214
|
+
const req = transport.request(
|
|
215
|
+
{
|
|
216
|
+
method: "POST",
|
|
217
|
+
hostname: parsed.hostname,
|
|
218
|
+
port: parsed.port || (parsed.protocol === "https:" ? 443 : 80),
|
|
219
|
+
path: `${parsed.pathname || ""}${parsed.search || ""}` || "/mcp",
|
|
220
|
+
headers: {
|
|
221
|
+
"Content-Type": "application/json",
|
|
222
|
+
Accept: "application/json, text/event-stream",
|
|
223
|
+
"X-Engram-Client-Id": "claude-code",
|
|
224
|
+
Authorization: `Bearer ${token}`,
|
|
225
|
+
"Content-Length": Buffer.byteLength(body, "utf8"),
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
(res) => {
|
|
229
|
+
let chunks = "";
|
|
230
|
+
let received = 0;
|
|
231
|
+
res.setEncoding("utf8");
|
|
232
|
+
res.on("data", (c) => {
|
|
233
|
+
received += Buffer.byteLength(c, "utf8");
|
|
234
|
+
if (received > MAX_BODY_BYTES) {
|
|
235
|
+
req.destroy(new Error(`response exceeded MAX_BODY_BYTES (${MAX_BODY_BYTES})`));
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
chunks += c;
|
|
239
|
+
});
|
|
240
|
+
res.on("end", () => {
|
|
241
|
+
try {
|
|
242
|
+
if (isNotification) {
|
|
243
|
+
// No reply expected. Either 2xx success or non-2xx-with-no-body
|
|
244
|
+
// both resolve silently; a 2xx body (some daemons return
|
|
245
|
+
// 202+JSON for non-streaming notifications) is also silently
|
|
246
|
+
// discarded because notifications never reply.
|
|
247
|
+
if (res.statusCode < 200 || res.statusCode >= 300) {
|
|
248
|
+
process.stderr.write(
|
|
249
|
+
`remnic-mcp-proxy: notification ${msg.method} got HTTP ${res.statusCode}: ${sanitizeMessage(chunks).slice(0, 200)}\n`
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
writeNotificationAck();
|
|
253
|
+
resolve();
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
if (res.statusCode < 200 || res.statusCode >= 300) {
|
|
257
|
+
writeError(
|
|
258
|
+
msg.id ?? null,
|
|
259
|
+
-32001,
|
|
260
|
+
`daemon responded ${res.statusCode}: ${sanitizeMessage(chunks).slice(0, 200)}`,
|
|
261
|
+
msg.id ?? null
|
|
262
|
+
);
|
|
263
|
+
resolve();
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
const trimmed = chunks.trim();
|
|
267
|
+
// Empty 202 / 204 bodies on a request are a contract violation
|
|
268
|
+
// (the daemon should always JSON-RPC). Surface as a parse error
|
|
269
|
+
// so MCP clients see the real failure mode.
|
|
270
|
+
if (trimmed.length === 0) {
|
|
271
|
+
writeError(
|
|
272
|
+
msg.id ?? null,
|
|
273
|
+
-32002,
|
|
274
|
+
`daemon returned empty body with HTTP ${res.statusCode}`,
|
|
275
|
+
msg.id ?? null
|
|
276
|
+
);
|
|
277
|
+
resolve();
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
// The Remnic /mcp endpoint may emit SSE in streaming mode —
|
|
281
|
+
// strip `data: ` prefixes if present.
|
|
282
|
+
const cleaned = trimmed
|
|
283
|
+
.split("\n")
|
|
284
|
+
.filter((l) => l && !l.startsWith("event:") && !l.startsWith(":"))
|
|
285
|
+
.map((l) => (l.startsWith("data: ") ? l.slice(6) : l))
|
|
286
|
+
.join("\n")
|
|
287
|
+
.trim();
|
|
288
|
+
let parsedResp;
|
|
289
|
+
try {
|
|
290
|
+
parsedResp = JSON.parse(cleaned);
|
|
291
|
+
} catch (err) {
|
|
292
|
+
writeError(
|
|
293
|
+
msg.id ?? null,
|
|
294
|
+
-32002,
|
|
295
|
+
`daemon returned non-JSON: ${sanitizeMessage(err.message)} (preview: ${cleaned.slice(0, 200)})`,
|
|
296
|
+
msg.id ?? null
|
|
297
|
+
);
|
|
298
|
+
resolve();
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
if (parsedResp && "error" in parsedResp) {
|
|
302
|
+
writeRaw(JSON.stringify(parsedResp));
|
|
303
|
+
resolve();
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
writeResult(msg.id ?? null, parsedResp.result ?? parsedResp);
|
|
307
|
+
resolve();
|
|
308
|
+
} catch (err) {
|
|
309
|
+
// The outer guard catches anything that slipped through the inner
|
|
310
|
+
// parses. For requests, emit a JSON-RPC error envelope so the MCP
|
|
311
|
+
// client sees a real failure rather than a silent stall. For
|
|
312
|
+
// notifications (which carry no id and to which the receiver MUST
|
|
313
|
+
// NOT reply per JSON-RPC 2.0), log to stderr only.
|
|
314
|
+
if (isNotification) {
|
|
315
|
+
process.stderr.write(
|
|
316
|
+
`remnic-mcp-proxy: notification ${msg.method} handler error: ${sanitizeMessage(err.message)}\n`
|
|
317
|
+
);
|
|
318
|
+
} else {
|
|
319
|
+
writeError(
|
|
320
|
+
msg.id ?? null,
|
|
321
|
+
-32603,
|
|
322
|
+
`internal proxy error: ${sanitizeMessage(err.message)}`,
|
|
323
|
+
msg.id ?? null
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
resolve();
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
res.on("error", (err) => {
|
|
330
|
+
if (isNotification) {
|
|
331
|
+
process.stderr.write(
|
|
332
|
+
`remnic-mcp-proxy: notification ${msg.method} response stream error: ${sanitizeMessage(err.message)}\n`
|
|
333
|
+
);
|
|
334
|
+
} else {
|
|
335
|
+
writeError(
|
|
336
|
+
msg.id ?? null,
|
|
337
|
+
-32003,
|
|
338
|
+
`response stream error: ${sanitizeMessage(err.message)}`,
|
|
339
|
+
msg.id ?? null
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
resolve();
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
);
|
|
346
|
+
req.setTimeout(REQUEST_TIMEOUT_MS, () => {
|
|
347
|
+
req.destroy(new Error(`request timed out after ${REQUEST_TIMEOUT_MS}ms`));
|
|
348
|
+
});
|
|
349
|
+
req.on("error", (err) => {
|
|
350
|
+
if (isNotification) {
|
|
351
|
+
process.stderr.write(
|
|
352
|
+
`remnic-mcp-proxy: notification ${msg.method} transport error: ${sanitizeMessage(err.message)}\n`
|
|
353
|
+
);
|
|
354
|
+
} else {
|
|
355
|
+
writeError(msg.id ?? null, -32003, `transport error: ${sanitizeMessage(err.message)}`, msg.id ?? null);
|
|
356
|
+
}
|
|
357
|
+
resolve();
|
|
358
|
+
});
|
|
359
|
+
req.write(body);
|
|
360
|
+
req.end();
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function tryExit() {
|
|
365
|
+
if (exiting) return;
|
|
366
|
+
if (!stdinEnded) return;
|
|
367
|
+
if (inFlight > 0) {
|
|
368
|
+
// Wait for forwards to drain up to EXIT_DRAIN_TIMEOUT_MS, then exit anyway.
|
|
369
|
+
// Guard against an unbounded wait via a one-shot timer.
|
|
370
|
+
setTimeout(() => {
|
|
371
|
+
exiting = true;
|
|
372
|
+
process.exit(0);
|
|
373
|
+
}, EXIT_DRAIN_TIMEOUT_MS).unref();
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
exiting = true;
|
|
377
|
+
process.exit(0);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
drain();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remnic/plugin-claude-code",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.49.1",
|
|
4
4
|
"description": "Remnic memory plugin for Claude Code — hooks, skills, MCP integration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
"hooks",
|
|
26
26
|
"skills",
|
|
27
27
|
"agents",
|
|
28
|
+
"mcp-server-stdio",
|
|
28
29
|
".mcp.json",
|
|
29
30
|
"settings.json"
|
|
30
31
|
]
|