@batalabs/virlow-mcp-core 3.18.0 → 3.68.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.
Files changed (73) hide show
  1. package/LICENSE +8 -18
  2. package/README.md +1 -1
  3. package/dist/api.d.ts +32 -0
  4. package/dist/api.d.ts.map +1 -1
  5. package/dist/api.js +38 -5
  6. package/dist/api.js.map +1 -1
  7. package/dist/attrs-extract.d.ts +48 -0
  8. package/dist/attrs-extract.d.ts.map +1 -0
  9. package/dist/attrs-extract.js +153 -0
  10. package/dist/attrs-extract.js.map +1 -0
  11. package/dist/attrs.d.ts +445 -0
  12. package/dist/attrs.d.ts.map +1 -0
  13. package/dist/attrs.js +604 -0
  14. package/dist/attrs.js.map +1 -0
  15. package/dist/cjs/api.d.ts +32 -0
  16. package/dist/cjs/api.js +38 -5
  17. package/dist/cjs/attrs-extract.d.ts +47 -0
  18. package/dist/cjs/attrs-extract.js +159 -0
  19. package/dist/cjs/attrs.d.ts +444 -0
  20. package/dist/cjs/attrs.js +635 -0
  21. package/dist/cjs/index.d.ts +5 -1
  22. package/dist/cjs/index.js +22 -1
  23. package/dist/cjs/memories.d.ts +79 -1
  24. package/dist/cjs/memories.js +249 -15
  25. package/dist/cjs/memory-graph.d.ts +105 -0
  26. package/dist/cjs/memory-graph.js +270 -0
  27. package/dist/cjs/memory-note.d.ts +57 -0
  28. package/dist/cjs/memory-note.js +174 -10
  29. package/dist/cjs/memory-secrets.d.ts +19 -0
  30. package/dist/cjs/memory-secrets.js +108 -0
  31. package/dist/cjs/notes.d.ts +208 -2
  32. package/dist/cjs/notes.js +721 -12
  33. package/dist/http/index.d.ts +4 -0
  34. package/dist/http/index.d.ts.map +1 -0
  35. package/dist/http/index.js +3 -0
  36. package/dist/http/index.js.map +1 -0
  37. package/dist/http/local-origin.d.ts +61 -0
  38. package/dist/http/local-origin.d.ts.map +1 -0
  39. package/dist/http/local-origin.js +92 -0
  40. package/dist/http/local-origin.js.map +1 -0
  41. package/dist/http/server.d.ts +39 -0
  42. package/dist/http/server.d.ts.map +1 -0
  43. package/dist/http/server.js +131 -0
  44. package/dist/http/server.js.map +1 -0
  45. package/dist/index.d.ts +5 -1
  46. package/dist/index.d.ts.map +1 -1
  47. package/dist/index.js +2 -0
  48. package/dist/index.js.map +1 -1
  49. package/dist/memories.d.ts +79 -1
  50. package/dist/memories.d.ts.map +1 -1
  51. package/dist/memories.js +249 -15
  52. package/dist/memories.js.map +1 -1
  53. package/dist/memory-graph.d.ts +106 -0
  54. package/dist/memory-graph.d.ts.map +1 -0
  55. package/dist/memory-graph.js +258 -0
  56. package/dist/memory-graph.js.map +1 -0
  57. package/dist/memory-note.d.ts +57 -0
  58. package/dist/memory-note.d.ts.map +1 -1
  59. package/dist/memory-note.js +170 -10
  60. package/dist/memory-note.js.map +1 -1
  61. package/dist/memory-secrets.d.ts +20 -0
  62. package/dist/memory-secrets.d.ts.map +1 -0
  63. package/dist/memory-secrets.js +103 -0
  64. package/dist/memory-secrets.js.map +1 -0
  65. package/dist/notes.d.ts +208 -2
  66. package/dist/notes.d.ts.map +1 -1
  67. package/dist/notes.js +722 -13
  68. package/dist/notes.js.map +1 -1
  69. package/dist/tools.d.ts +8 -2
  70. package/dist/tools.d.ts.map +1 -1
  71. package/dist/tools.js +465 -10
  72. package/dist/tools.js.map +1 -1
  73. package/package.json +21 -5
@@ -0,0 +1,4 @@
1
+ export { bearerFrom, defaultTokenPath, loadOrCreateToken, startHttpServer, tokenMatches, } from './server.js';
2
+ export type { HttpServerOptions, RunningHttpServer } from './server.js';
3
+ export { SECURITY_HEADERS, isAllowedOrigin, isLoopbackHost, rejectReason } from './local-origin.js';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/http/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EACV,gBAAgB,EAChB,iBAAiB,EACjB,eAAe,EACf,YAAY,GACb,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACxE,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC"}
@@ -0,0 +1,3 @@
1
+ export { bearerFrom, defaultTokenPath, loadOrCreateToken, startHttpServer, tokenMatches, } from './server.js';
2
+ export { SECURITY_HEADERS, isAllowedOrigin, isLoopbackHost, rejectReason } from './local-origin.js';
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/http/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EACV,gBAAgB,EAChB,iBAAiB,EACjB,eAAe,EACf,YAAY,GACb,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * DNS-rebinding defence for the two HTTP listeners this process runs.
3
+ *
4
+ * Both are bound to loopback and both sit in front of something worth taking:
5
+ * the unlock form accepts the master password, and the --http transport serves
6
+ * decrypted notes. Loopback is not the boundary people assume it is. A page in
7
+ * the user's browser can be made to send requests to 127.0.0.1 — the attacker
8
+ * points a hostname they control at their own server, the browser caches that,
9
+ * then the DNS record flips to 127.0.0.1 and the browser treats
10
+ * http://evil.example:PORT as same-origin with the attacker's page. Same-origin
11
+ * means no CORS, so the response is readable.
12
+ *
13
+ * The tell is the Host header: a rebound request carries the attacker's
14
+ * hostname, never `127.0.0.1` or `localhost`, because that is what the browser
15
+ * was asked to fetch. Requiring a loopback Host closes it, and the MCP
16
+ * specification asks local Streamable HTTP servers to do exactly this.
17
+ *
18
+ * Enforced only when the server is actually bound to loopback. `--http --host`
19
+ * is a deliberate act by an operator who was warned, and the documented way to
20
+ * reach it from elsewhere is a tunnel — which legitimately rewrites Host to its
21
+ * own name. Enforcing there would break the supported setup while protecting a
22
+ * listener the operator already chose to expose.
23
+ */
24
+ /**
25
+ * True when a Host header addresses this machine by a loopback name.
26
+ * A missing Host fails: HTTP/1.1 requires one, and a request without it is not
27
+ * something a browser or a well-formed client sends.
28
+ */
29
+ export declare function isLoopbackHost(hostHeader: string | undefined): boolean;
30
+ /**
31
+ * True when an Origin header is absent or loopback.
32
+ *
33
+ * Absent is the normal case and must pass: an MCP client, curl, or any
34
+ * non-browser caller sends no Origin at all. Present-and-not-loopback is a
35
+ * browser telling us a page on another site initiated this, which no legitimate
36
+ * flow here does. `null` (a sandboxed iframe, a redirected cross-origin POST)
37
+ * is not loopback and is refused with everything else.
38
+ */
39
+ export declare function isAllowedOrigin(originHeader: string | undefined): boolean;
40
+ /**
41
+ * Both checks, for a server that knows whether it is on loopback.
42
+ * Returns null when the request is acceptable, or a short reason when it is
43
+ * not — the reason is for a comment, never for the response body, which stays
44
+ * uninformative on purpose.
45
+ */
46
+ export declare function rejectReason(headers: {
47
+ host?: string;
48
+ origin?: string;
49
+ }, enforce: boolean): string | null;
50
+ /**
51
+ * Headers for every response from either local listener.
52
+ *
53
+ * The unlock page is a credential form, so it must not be framable and must not
54
+ * be cached: its URL carries the one-time nonce, and a cached copy of the page
55
+ * or its POST response outlives the server that issued it. `no-referrer` keeps
56
+ * that nonce out of a Referer header. The CSP allows the inline <style> the
57
+ * form uses and nothing else — no script of any kind, so a gap in the error
58
+ * escaping cannot become execution.
59
+ */
60
+ export declare const SECURITY_HEADERS: Readonly<Record<string, string>>;
61
+ //# sourceMappingURL=local-origin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local-origin.d.ts","sourceRoot":"","sources":["../../src/http/local-origin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAUH;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAGtE;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,YAAY,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAQzE;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAC1B,OAAO,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,EAC3C,OAAO,EAAE,OAAO,GACf,MAAM,GAAG,IAAI,CAKf;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,gBAAgB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAO5D,CAAC"}
@@ -0,0 +1,92 @@
1
+ /**
2
+ * DNS-rebinding defence for the two HTTP listeners this process runs.
3
+ *
4
+ * Both are bound to loopback and both sit in front of something worth taking:
5
+ * the unlock form accepts the master password, and the --http transport serves
6
+ * decrypted notes. Loopback is not the boundary people assume it is. A page in
7
+ * the user's browser can be made to send requests to 127.0.0.1 — the attacker
8
+ * points a hostname they control at their own server, the browser caches that,
9
+ * then the DNS record flips to 127.0.0.1 and the browser treats
10
+ * http://evil.example:PORT as same-origin with the attacker's page. Same-origin
11
+ * means no CORS, so the response is readable.
12
+ *
13
+ * The tell is the Host header: a rebound request carries the attacker's
14
+ * hostname, never `127.0.0.1` or `localhost`, because that is what the browser
15
+ * was asked to fetch. Requiring a loopback Host closes it, and the MCP
16
+ * specification asks local Streamable HTTP servers to do exactly this.
17
+ *
18
+ * Enforced only when the server is actually bound to loopback. `--http --host`
19
+ * is a deliberate act by an operator who was warned, and the documented way to
20
+ * reach it from elsewhere is a tunnel — which legitimately rewrites Host to its
21
+ * own name. Enforcing there would break the supported setup while protecting a
22
+ * listener the operator already chose to expose.
23
+ */
24
+ /** Host values that mean "this machine", with any port stripped. */
25
+ const LOOPBACK_HOSTS = new Set(['127.0.0.1', 'localhost', '[::1]', '::1']);
26
+ /** Strip a trailing `:port`, leaving an IPv6 literal's own colons alone. */
27
+ function withoutPort(host) {
28
+ return host.replace(/:\d+$/, '').toLowerCase();
29
+ }
30
+ /**
31
+ * True when a Host header addresses this machine by a loopback name.
32
+ * A missing Host fails: HTTP/1.1 requires one, and a request without it is not
33
+ * something a browser or a well-formed client sends.
34
+ */
35
+ export function isLoopbackHost(hostHeader) {
36
+ if (hostHeader === undefined || hostHeader === '')
37
+ return false;
38
+ return LOOPBACK_HOSTS.has(withoutPort(hostHeader.trim()));
39
+ }
40
+ /**
41
+ * True when an Origin header is absent or loopback.
42
+ *
43
+ * Absent is the normal case and must pass: an MCP client, curl, or any
44
+ * non-browser caller sends no Origin at all. Present-and-not-loopback is a
45
+ * browser telling us a page on another site initiated this, which no legitimate
46
+ * flow here does. `null` (a sandboxed iframe, a redirected cross-origin POST)
47
+ * is not loopback and is refused with everything else.
48
+ */
49
+ export function isAllowedOrigin(originHeader) {
50
+ if (originHeader === undefined || originHeader === '')
51
+ return true;
52
+ try {
53
+ return LOOPBACK_HOSTS.has(new URL(originHeader).hostname.toLowerCase());
54
+ }
55
+ catch {
56
+ // Unparseable, including the literal "null".
57
+ return false;
58
+ }
59
+ }
60
+ /**
61
+ * Both checks, for a server that knows whether it is on loopback.
62
+ * Returns null when the request is acceptable, or a short reason when it is
63
+ * not — the reason is for a comment, never for the response body, which stays
64
+ * uninformative on purpose.
65
+ */
66
+ export function rejectReason(headers, enforce) {
67
+ if (!enforce)
68
+ return null;
69
+ if (!isLoopbackHost(headers.host))
70
+ return 'non-loopback Host';
71
+ if (!isAllowedOrigin(headers.origin))
72
+ return 'cross-site Origin';
73
+ return null;
74
+ }
75
+ /**
76
+ * Headers for every response from either local listener.
77
+ *
78
+ * The unlock page is a credential form, so it must not be framable and must not
79
+ * be cached: its URL carries the one-time nonce, and a cached copy of the page
80
+ * or its POST response outlives the server that issued it. `no-referrer` keeps
81
+ * that nonce out of a Referer header. The CSP allows the inline <style> the
82
+ * form uses and nothing else — no script of any kind, so a gap in the error
83
+ * escaping cannot become execution.
84
+ */
85
+ export const SECURITY_HEADERS = Object.freeze({
86
+ 'content-security-policy': "default-src 'none'; style-src 'unsafe-inline'; form-action 'self'; frame-ancestors 'none'; base-uri 'none'",
87
+ 'x-frame-options': 'DENY',
88
+ 'x-content-type-options': 'nosniff',
89
+ 'referrer-policy': 'no-referrer',
90
+ 'cache-control': 'no-store',
91
+ });
92
+ //# sourceMappingURL=local-origin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local-origin.js","sourceRoot":"","sources":["../../src/http/local-origin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,oEAAoE;AACpE,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC,CAAC,WAAW,EAAE,WAAW,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC;AAE3E,4EAA4E;AAC5E,SAAS,WAAW,CAAC,IAAY;IAC/B,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;AACjD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,UAA8B;IAC3D,IAAI,UAAU,KAAK,SAAS,IAAI,UAAU,KAAK,EAAE;QAAE,OAAO,KAAK,CAAC;IAChE,OAAO,cAAc,CAAC,GAAG,CAAC,WAAW,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;AAC5D,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,eAAe,CAAC,YAAgC;IAC9D,IAAI,YAAY,KAAK,SAAS,IAAI,YAAY,KAAK,EAAE;QAAE,OAAO,IAAI,CAAC;IACnE,IAAI,CAAC;QACH,OAAO,cAAc,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,YAAY,CAAC,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAC;IAC1E,CAAC;IAAC,MAAM,CAAC;QACP,6CAA6C;QAC7C,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAC1B,OAA2C,EAC3C,OAAgB;IAEhB,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IAC1B,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC,IAAI,CAAC;QAAE,OAAO,mBAAmB,CAAC;IAC9D,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC,MAAM,CAAC;QAAE,OAAO,mBAAmB,CAAC;IACjE,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAqC,MAAM,CAAC,MAAM,CAAC;IAC9E,yBAAyB,EACvB,4GAA4G;IAC9G,iBAAiB,EAAE,MAAM;IACzB,wBAAwB,EAAE,SAAS;IACnC,iBAAiB,EAAE,aAAa;IAChC,eAAe,EAAE,UAAU;CAC5B,CAAC,CAAC"}
@@ -0,0 +1,39 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ /** Where the bearer token lives when the caller does not supply one. */
3
+ export declare function defaultTokenPath(homeDir: string): string;
4
+ /**
5
+ * Read the bearer token, generating and persisting one on first run.
6
+ *
7
+ * The token is the only thing standing between a reachable port and the
8
+ * user's decrypted notes, so it is 32 random bytes and the file is 0600.
9
+ */
10
+ export declare function loadOrCreateToken(tokenPath: string): Promise<string>;
11
+ /** Constant-time compare that does not leak the expected length. */
12
+ export declare function tokenMatches(provided: string, expected: string): boolean;
13
+ export declare function bearerFrom(header: string | undefined): string | null;
14
+ export interface HttpServerOptions {
15
+ /** Builds a fresh McpServer bound to the process's shared services. The SDK
16
+ * binds one server to one transport, so each request gets its own pair while
17
+ * the vault behind them stays the same. */
18
+ makeServer: () => McpServer;
19
+ /**
20
+ * Decide who a bearer token belongs to. Return null to refuse. The CLI has
21
+ * one token; the desktop app has one per connected AI client and uses the
22
+ * returned id to record last use. Compare in constant time (tokenMatches).
23
+ */
24
+ authorize: (bearer: string) => {
25
+ clientId: string;
26
+ } | null;
27
+ port?: number;
28
+ /** Defaults to loopback. Binding elsewhere exposes decrypted notes to the
29
+ * network and must be a deliberate act by the operator. */
30
+ host?: string;
31
+ path?: string;
32
+ }
33
+ export interface RunningHttpServer {
34
+ port: number;
35
+ host: string;
36
+ close(): Promise<void>;
37
+ }
38
+ export declare function startHttpServer(opts: HttpServerOptions): Promise<RunningHttpServer>;
39
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/http/server.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAGzE,wEAAwE;AACxE,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAExD;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAW1E;AAED,oEAAoE;AACpE,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CASxE;AAED,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAIpE;AAED,MAAM,WAAW,iBAAiB;IAChC;;+CAE2C;IAC3C,UAAU,EAAE,MAAM,SAAS,CAAC;IAC5B;;;;OAIG;IACH,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAC3D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;+DAC2D;IAC3D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AA4BD,wBAAsB,eAAe,CACnC,IAAI,EAAE,iBAAiB,GACtB,OAAO,CAAC,iBAAiB,CAAC,CAwE5B"}
@@ -0,0 +1,131 @@
1
+ import { createServer } from 'node:http';
2
+ import { mkdir, readFile, writeFile } from 'node:fs/promises';
3
+ import { randomBytes, timingSafeEqual } from 'node:crypto';
4
+ import path from 'node:path';
5
+ import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
6
+ import { SECURITY_HEADERS, rejectReason } from './local-origin.js';
7
+ /** Where the bearer token lives when the caller does not supply one. */
8
+ export function defaultTokenPath(homeDir) {
9
+ return path.join(homeDir, '.virlow-mcp', 'http-token');
10
+ }
11
+ /**
12
+ * Read the bearer token, generating and persisting one on first run.
13
+ *
14
+ * The token is the only thing standing between a reachable port and the
15
+ * user's decrypted notes, so it is 32 random bytes and the file is 0600.
16
+ */
17
+ export async function loadOrCreateToken(tokenPath) {
18
+ try {
19
+ const existing = (await readFile(tokenPath, 'utf8')).trim();
20
+ if (existing.length > 0)
21
+ return existing;
22
+ }
23
+ catch {
24
+ // Missing or unreadable — fall through and mint a new one.
25
+ }
26
+ const token = randomBytes(32).toString('base64url');
27
+ await mkdir(path.dirname(tokenPath), { recursive: true, mode: 0o700 });
28
+ await writeFile(tokenPath, `${token}\n`, { mode: 0o600 });
29
+ return token;
30
+ }
31
+ /** Constant-time compare that does not leak the expected length. */
32
+ export function tokenMatches(provided, expected) {
33
+ const a = Buffer.from(provided);
34
+ const b = Buffer.from(expected);
35
+ if (a.length !== b.length) {
36
+ // Still burn a comparison so a length mismatch is not measurably faster.
37
+ timingSafeEqual(b, b);
38
+ return false;
39
+ }
40
+ return timingSafeEqual(a, b);
41
+ }
42
+ export function bearerFrom(header) {
43
+ if (!header)
44
+ return null;
45
+ const match = /^Bearer[ ]+(.+)$/i.exec(header.trim());
46
+ return match ? match[1].trim() : null;
47
+ }
48
+ /**
49
+ * Serve the MCP server over Streamable HTTP so a client that cannot spawn a
50
+ * child process — a cloud session, another machine — can reach this vault.
51
+ *
52
+ * The vault still lives in THIS process on the user's own machine: `unlock`
53
+ * opens its browser form here, and the master key never crosses the wire.
54
+ * What does cross the wire is decrypted note and memory text, which is why the
55
+ * listener is loopback-only unless the operator says otherwise and why every
56
+ * request must carry the bearer token.
57
+ */
58
+ async function handleWithFreshTransport(server, req, res) {
59
+ const transport = new StreamableHTTPServerTransport({
60
+ sessionIdGenerator: undefined,
61
+ });
62
+ res.on('close', () => {
63
+ void transport.close();
64
+ void server.close();
65
+ });
66
+ await server.connect(transport);
67
+ await transport.handleRequest(req, res);
68
+ }
69
+ export async function startHttpServer(opts) {
70
+ const host = opts.host ?? '127.0.0.1';
71
+ const mcpPath = opts.path ?? '/mcp';
72
+ // Only a loopback bind gets the Host check: a tunnel in front of a --host
73
+ // listener rewrites Host to its own name, and that is the documented way to
74
+ // reach this from another machine. See local-origin.ts.
75
+ const enforceLocal = host === '127.0.0.1' || host === '::1' || host === 'localhost';
76
+ const httpServer = createServer((req, res) => {
77
+ const url = new URL(req.url ?? '/', `http://${host}`);
78
+ // Before anything else, including /health: a rebound page must not even be
79
+ // able to confirm something is listening on this port.
80
+ if (rejectReason(req.headers, enforceLocal) !== null) {
81
+ res.writeHead(403, { ...SECURITY_HEADERS, 'content-type': 'application/json' });
82
+ res.end(JSON.stringify({ error: 'forbidden' }));
83
+ return;
84
+ }
85
+ if (url.pathname === '/health') {
86
+ res.writeHead(200, { ...SECURITY_HEADERS, 'content-type': 'application/json' });
87
+ res.end(JSON.stringify({ ok: true }));
88
+ return;
89
+ }
90
+ if (url.pathname !== mcpPath) {
91
+ res.writeHead(404, SECURITY_HEADERS).end();
92
+ return;
93
+ }
94
+ const provided = bearerFrom(req.headers.authorization);
95
+ if (provided === null || opts.authorize(provided) === null) {
96
+ // No detail about why: a caller without the token learns nothing beyond
97
+ // "something is listening here".
98
+ res.writeHead(401, {
99
+ ...SECURITY_HEADERS,
100
+ 'content-type': 'application/json',
101
+ 'www-authenticate': 'Bearer',
102
+ });
103
+ res.end(JSON.stringify({ error: 'unauthorized' }));
104
+ return;
105
+ }
106
+ // A transport instance serves exactly one exchange: a stateful one answers
107
+ // every later `initialize` with "Server already initialized", and a reused
108
+ // stateless one 500s on its second request. So each request gets its own
109
+ // server+transport pair, both discarded when the response closes. The
110
+ // vault they share is the process's, so a reconnecting client — a cloud
111
+ // session that dropped — still finds it unlocked.
112
+ void handleWithFreshTransport(opts.makeServer(), req, res);
113
+ });
114
+ await new Promise((resolve, reject) => {
115
+ httpServer.once('error', reject);
116
+ httpServer.listen(opts.port ?? 0, host, () => {
117
+ httpServer.removeListener('error', reject);
118
+ resolve();
119
+ });
120
+ });
121
+ const address = httpServer.address();
122
+ const port = typeof address === 'object' && address !== null ? address.port : 0;
123
+ return {
124
+ port,
125
+ host,
126
+ close: () => new Promise((resolve) => {
127
+ httpServer.close(() => resolve());
128
+ }),
129
+ };
130
+ }
131
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/http/server.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { ApiError, VirlowApi } from './api.js';
2
- export type { ApiFolder, ApiNote, AuthTokens, LoginResult, NoteWrite } from './api.js';
2
+ export type { ApiFolder, ApiNote, AuthTokens, LoginResult, NoteWrite, RefreshBody } from './api.js';
3
3
  export { LockedError, Vault } from './vault.js';
4
4
  export type { UnlockedSession } from './vault.js';
5
5
  export { NotesService, SEARCH_SCAN_CAP } from './notes.js';
@@ -14,6 +14,10 @@ export type { Exposure } from './exposure.js';
14
14
  export { EmbeddingCache } from './embedding-cache.js';
15
15
  export type { EmbeddingCacheLike } from './embedding-cache.js';
16
16
  export { MEMORIES_DISABLED_MESSAGE, memoriesAllowed } from './memories-enabled.js';
17
+ export { ATTRS_MAX_BYTES, TAGS_PROP, decodeAttrs, decodeSchema, defineProp, emptyAttrs, emptySchema, encodeAttrs, encodeSchema, newPropId, removeProp, reorderProps, sortedProps, tagsFromAttrs, updateProp, withTags, } from './attrs.js';
18
+ export type { FolderSchemaV1, NoteAttrsV1, PropDefinition, PropOption, PropType, PropValue, TagsValue } from './attrs.js';
19
+ export { buildTitleMatcher, extractLinkIds, htmlToText } from './attrs-extract.js';
20
+ export type { TitleEntry, TitleMatcher } from './attrs-extract.js';
17
21
  /**
18
22
  * The MCP tool surface is NOT exported here. It lives at the `/tools` subpath,
19
23
  * which is ESM-only, because it depends on @modelcontextprotocol/sdk — an
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAC/C,YAAY,EAAE,SAAS,EAAE,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACvF,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAChD,YAAY,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC3D,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAC7D,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC7E,OAAO,EACL,gBAAgB,EAChB,SAAS,EACT,eAAe,EACf,mBAAmB,EACnB,cAAc,GACf,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACrE,OAAO,EACL,kBAAkB,EAClB,eAAe,EACf,iBAAiB,GAClB,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAChE,YAAY,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,YAAY,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC/D,OAAO,EAAE,yBAAyB,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAEnF;;;;;;;;;;GAUG"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAC/C,YAAY,EAAE,SAAS,EAAE,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACpG,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAChD,YAAY,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC3D,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAC7D,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC7E,OAAO,EACL,gBAAgB,EAChB,SAAS,EACT,eAAe,EACf,mBAAmB,EACnB,cAAc,GACf,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACrE,OAAO,EACL,kBAAkB,EAClB,eAAe,EACf,iBAAiB,GAClB,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAChE,YAAY,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,YAAY,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC/D,OAAO,EAAE,yBAAyB,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACnF,OAAO,EACL,eAAe,EACf,SAAS,EACT,WAAW,EACX,YAAY,EACZ,UAAU,EACV,UAAU,EACV,WAAW,EACX,WAAW,EACX,YAAY,EACZ,SAAS,EACT,UAAU,EACV,YAAY,EACZ,WAAW,EACX,aAAa,EACb,UAAU,EACV,QAAQ,GACT,MAAM,YAAY,CAAC;AACpB,YAAY,EAAE,cAAc,EAAE,WAAW,EAAE,cAAc,EAAE,UAAU,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC1H,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACnF,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEnE;;;;;;;;;;GAUG"}
package/dist/index.js CHANGED
@@ -7,6 +7,8 @@ export { MEMORIES_ROOT_NAME, ensureNamespace, resolveMemoryTree, } from './memor
7
7
  export { computeExposure, filteredNotice } from './exposure.js';
8
8
  export { EmbeddingCache } from './embedding-cache.js';
9
9
  export { MEMORIES_DISABLED_MESSAGE, memoriesAllowed } from './memories-enabled.js';
10
+ export { ATTRS_MAX_BYTES, TAGS_PROP, decodeAttrs, decodeSchema, defineProp, emptyAttrs, emptySchema, encodeAttrs, encodeSchema, newPropId, removeProp, reorderProps, sortedProps, tagsFromAttrs, updateProp, withTags, } from './attrs.js';
11
+ export { buildTitleMatcher, extractLinkIds, htmlToText } from './attrs-extract.js';
10
12
  /**
11
13
  * The MCP tool surface is NOT exported here. It lives at the `/tools` subpath,
12
14
  * which is ESM-only, because it depends on @modelcontextprotocol/sdk — an
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAE/C,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEhD,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC3D,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAE7D,OAAO,EACL,gBAAgB,EAChB,SAAS,EACT,eAAe,EACf,mBAAmB,EACnB,cAAc,GACf,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACL,kBAAkB,EAClB,eAAe,EACf,iBAAiB,GAClB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAEhE,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtD,OAAO,EAAE,yBAAyB,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAEnF;;;;;;;;;;GAUG"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAE/C,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEhD,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC3D,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAE7D,OAAO,EACL,gBAAgB,EAChB,SAAS,EACT,eAAe,EACf,mBAAmB,EACnB,cAAc,GACf,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACL,kBAAkB,EAClB,eAAe,EACf,iBAAiB,GAClB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAEhE,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtD,OAAO,EAAE,yBAAyB,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACnF,OAAO,EACL,eAAe,EACf,SAAS,EACT,WAAW,EACX,YAAY,EACZ,UAAU,EACV,UAAU,EACV,WAAW,EACX,WAAW,EACX,YAAY,EACZ,SAAS,EACT,UAAU,EACV,YAAY,EACZ,WAAW,EACX,aAAa,EACb,UAAU,EACV,QAAQ,GACT,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAGnF;;;;;;;;;;GAUG"}
@@ -2,6 +2,8 @@ import { type Embedder } from '@batalabs/virlow-memory';
2
2
  import { type VirlowApi } from './api.js';
3
3
  import type { EmbeddingCacheLike } from './embedding-cache.js';
4
4
  import { type MemoryMeta } from './memory-note.js';
5
+ import { type MemoryGraph } from './memory-graph.js';
6
+ import type { MemoryRelation } from './memory-note.js';
5
7
  import type { Vault } from './vault.js';
6
8
  /**
7
9
  * Page size for the sync walk over each memory folder.
@@ -33,6 +35,13 @@ export interface SimilarMemory {
33
35
  text: string;
34
36
  similarity: number;
35
37
  }
38
+ /** A fact the new memory ended: it said something single-valued (who leads
39
+ * a project, where someone works) that the new one replaces. */
40
+ export interface ClosedFact {
41
+ id: string;
42
+ label: string;
43
+ relation: MemoryRelation;
44
+ }
36
45
  export type SyncResult = {
37
46
  added: number;
38
47
  updated: number;
@@ -66,10 +75,79 @@ export declare class MemoryStore {
66
75
  action: 'stored' | 'updated';
67
76
  id: string;
68
77
  similar: SimilarMemory[];
78
+ closed: ClosedFact[];
69
79
  }>;
70
- search(query: string, namespace?: string, limit?: number): Promise<Array<Omit<CachedMemory, 'embedding' | 'folderId' | 'updatedAt'> & {
80
+ /**
81
+ * Ends the facts a new memory replaces. Each superseded relation gets a
82
+ * `to` date (the new fact's start); a memory whose relations have all
83
+ * ended gets `valid_to` and `superseded_by`. Nothing is deleted, so "who
84
+ * led Atlas before Sam?" stays answerable.
85
+ */
86
+ private closeSuperseded;
87
+ /** The graph over what is cached now. Cheap: rebuilt from the notes on
88
+ * demand, never stored. */
89
+ graph(namespace?: string): MemoryGraph;
90
+ /** New names spelled the way existing memories already spell them, and
91
+ * predicates in snake_case, so one entity does not split in two. */
92
+ private canonicalize;
93
+ /**
94
+ * Hybrid recall: similarity, then the graph (memories about an entity the
95
+ * query names, and one relation out from it) and recency. Current facts
96
+ * only, unless `includeHistory`, or as they stood on `asOf`.
97
+ */
98
+ search(query: string, namespace?: string, limit?: number, opts?: {
99
+ asOf?: string;
100
+ includeHistory?: boolean;
101
+ }): Promise<Array<Omit<CachedMemory, 'embedding' | 'folderId' | 'updatedAt'> & {
71
102
  similarity: number;
72
103
  }>>;
104
+ /** Everything known about one entity: its current and past relations and
105
+ * the facts behind them. Null when no memory names it. */
106
+ recallEntity(name: string, namespace?: string): Promise<{
107
+ name: string;
108
+ current: CachedMemory[];
109
+ past: CachedMemory[];
110
+ relations: {
111
+ memoryId: string;
112
+ s: string;
113
+ p: string;
114
+ o: string;
115
+ from?: string;
116
+ to?: string;
117
+ }[];
118
+ } | null>;
119
+ /** Adds entities and relations to an existing memory: how a memory saved
120
+ * before the graph existed gets linked. Also ends what it replaces. */
121
+ link(id: string, add: {
122
+ about?: string[];
123
+ relations?: MemoryRelation[];
124
+ }): Promise<ClosedFact[]>;
125
+ /** One entity spelled two ways becomes one: every memory naming `from`
126
+ * is rewritten to name `into`. Returns how many memories changed. */
127
+ mergeEntities(from: string, into: string): Promise<number>;
128
+ /**
129
+ * What to load at the start of a session: the entities this namespace
130
+ * talks about most, and its most recent current facts, within `limit`
131
+ * facts. Global memories are included alongside a project's.
132
+ */
133
+ context(namespace?: string, limit?: number): Promise<{
134
+ entities: {
135
+ name: string;
136
+ facts: number;
137
+ }[];
138
+ facts: CachedMemory[];
139
+ }>;
140
+ /** Findings for a tidy pass: unlinked memories, likely duplicate names and
141
+ * single-valued facts holding two values at once. */
142
+ health(namespace?: string): Promise<{
143
+ unlinked: string[];
144
+ possibleDuplicates: [string, string][];
145
+ contradictions: {
146
+ subject: string;
147
+ predicate: string;
148
+ objects: string[];
149
+ }[];
150
+ }>;
73
151
  /** Requires a prior sync() in the same unlock to have populated the cache. */
74
152
  list(namespace?: string): CachedMemory[];
75
153
  update(id: string, changes: {
@@ -1 +1 @@
1
- {"version":3,"file":"memories.d.ts","sourceRoot":"","sources":["../src/memories.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"memories.d.ts","sourceRoot":"","sources":["../src/memories.ts"],"names":[],"mappings":"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"}