brainllm 12.4.0 → 12.4.2
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 +2 -2
- package/dist/index.js +161 -54
- package/manifest.json +1 -1
- package/package.json +1 -1
- package/server.json +2 -2
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ A persistent, graph-structured second brain for Claude and any MCP client — bu
|
|
|
10
10
|
|
|
11
11
|
[**brainllm site**](https://miisodev.github.io/BrainLLM/) · [How it works](https://miisodev.github.io/BrainLLM/how-it-works.html) · [Use cases](https://miisodev.github.io/BrainLLM/use-cases.html) · [Docs](https://miisodev.github.io/BrainLLM/docs.html)
|
|
12
12
|
|
|
13
|
-
[](https://github.com/miisodev/BrainLLM/releases)
|
|
14
14
|
[](https://github.com/miisodev/BrainLLM/actions/workflows/ci.yml)
|
|
15
15
|
[](./LICENSE)
|
|
16
16
|
[](https://bun.sh)
|
|
@@ -233,7 +233,7 @@ The complete operational reference is [`skills/brainllm/SKILL.md`](./skills/brai
|
|
|
233
233
|
| **stdio** | Local — Claude Desktop / Claude Code spawns BrainLLM as a child process | `PORT` unset (default) |
|
|
234
234
|
| **HTTP connector** | Remote — clients reach BrainLLM over the network | `PORT` set (Railway injects it) |
|
|
235
235
|
|
|
236
|
-
The HTTP connector serves a streamable-HTTP MCP endpoint at `/mcp` (one session per `mcp-session-id`, DELETE terminates, CORS-enabled with `mcp-session-id` exposed for browser clients), a **legacy SSE endpoint** at `/sse` (with `POST /messages`) for clients that predate streamable HTTP — older Cursor builds, Continue, and Python-SDK clients — and `GET /health`. The server root serves a plain page
|
|
236
|
+
The HTTP connector serves a streamable-HTTP MCP endpoint at `/mcp` (one session per `mcp-session-id`, DELETE terminates, CORS-enabled with `mcp-session-id` exposed for browser clients), a **legacy SSE endpoint** at `/sse` (with `POST /messages`) for clients that predate streamable HTTP — older Cursor builds, Continue, and Python-SDK clients — and `GET /health`. The server root serves a plain page for ordinary browser `GET` requests; MCP-shaped requests sent to `/` are also accepted as a compatibility alias, because some hosted Claude connector versions normalize the post-token MCP path to the origin root. The server itself speaks plain HTTP — TLS is expected to terminate in front of it (Railway's edge does this automatically for you; on a bare VPS or your own Docker host, put it behind a reverse proxy such as Caddy, nginx, or a Cloudflare Tunnel). **HTTP mode now fails closed unless `MCP_AUTH_TOKEN` or `BRAINLLM_OWNER_PASSWORD` is configured**; `BRAINLLM_ALLOW_UNAUTHENTICATED_HTTP=true` is an explicit escape hatch for an isolated trusted network only. CORS defaults to `*`, so a reachable deployment must have a real authentication gate. Idle sessions are evicted after 1 hour; MCP request bodies are capped at 50 MiB by both the server and SDK transport. Both transports sit behind the same authentication gate.
|
|
237
237
|
|
|
238
238
|
### Docker / Railway
|
|
239
239
|
|
package/dist/index.js
CHANGED
|
@@ -27788,6 +27788,7 @@ import { existsSync as existsSync2, readFileSync as readFileSync2, statSync, wri
|
|
|
27788
27788
|
import { lookup } from "dns/promises";
|
|
27789
27789
|
import { isIP } from "net";
|
|
27790
27790
|
import { request as httpsRequest } from "https";
|
|
27791
|
+
var CIMD_USER_AGENT = "BrainLLM-CIMD/1.0 (+https://github.com/miisodev/BrainLLM)";
|
|
27791
27792
|
function isNonPublicAddress(address) {
|
|
27792
27793
|
const value = address.trim().toLowerCase().replace(/^\[|\]$/g, "").split("%")[0];
|
|
27793
27794
|
const version = isIP(value);
|
|
@@ -27937,7 +27938,13 @@ async function fetchPinned(target, maxBytes, timeoutMs) {
|
|
|
27937
27938
|
const hostname = target.url.hostname.replace(/^\[|\]$/g, "");
|
|
27938
27939
|
const request = httpsRequest(target.url, {
|
|
27939
27940
|
method: "GET",
|
|
27940
|
-
headers: {
|
|
27941
|
+
headers: {
|
|
27942
|
+
Accept: "application/json",
|
|
27943
|
+
"Accept-Encoding": "identity",
|
|
27944
|
+
"User-Agent": CIMD_USER_AGENT,
|
|
27945
|
+
Host: target.url.host,
|
|
27946
|
+
Connection: "close"
|
|
27947
|
+
},
|
|
27941
27948
|
agent: false,
|
|
27942
27949
|
servername: isIP(hostname) ? undefined : hostname,
|
|
27943
27950
|
lookup: (_hostname, options, callback) => {
|
|
@@ -27989,6 +27996,7 @@ var SCOPE = "brain";
|
|
|
27989
27996
|
var AUTH_CODE_TTL_MS = 10 * 60 * 1000;
|
|
27990
27997
|
var ACCESS_TOKEN_TTL_S = 60 * 60;
|
|
27991
27998
|
var REFRESH_TOKEN_TTL_MS = 90 * 24 * 60 * 60 * 1000;
|
|
27999
|
+
var CONSENT_TRANSACTION_TTL_S = 5 * 60;
|
|
27992
28000
|
var CIMD_FETCH_TIMEOUT_MS = 5000;
|
|
27993
28001
|
var MAX_OAUTH_BODY_BYTES = 64 * 1024;
|
|
27994
28002
|
var MAX_CIMD_DOCUMENT_BYTES = 64 * 1024;
|
|
@@ -28044,13 +28052,16 @@ var bodyLimitResponse = (format = "json") => {
|
|
|
28044
28052
|
};
|
|
28045
28053
|
var b64url = (buf) => Buffer.from(buf).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
28046
28054
|
var b64urlDecode = (s) => Buffer.from(s.replace(/-/g, "+").replace(/_/g, "/"), "base64");
|
|
28047
|
-
function
|
|
28055
|
+
function signSignedClaims(claims, secret) {
|
|
28048
28056
|
const header = b64url(JSON.stringify({ alg: "HS256", typ: "JWT" }));
|
|
28049
28057
|
const payload = b64url(JSON.stringify(claims));
|
|
28050
28058
|
const sig = b64url(createHmac("sha256", secret).update(`${header}.${payload}`).digest());
|
|
28051
28059
|
return `${header}.${payload}.${sig}`;
|
|
28052
28060
|
}
|
|
28053
|
-
function
|
|
28061
|
+
function signJwt(claims, secret) {
|
|
28062
|
+
return signSignedClaims(claims, secret);
|
|
28063
|
+
}
|
|
28064
|
+
function verifySignedClaims(token, secret) {
|
|
28054
28065
|
const parts = token.split(".");
|
|
28055
28066
|
if (parts.length !== 3)
|
|
28056
28067
|
return null;
|
|
@@ -28069,6 +28080,51 @@ function verifyJwt(token, secret) {
|
|
|
28069
28080
|
return null;
|
|
28070
28081
|
}
|
|
28071
28082
|
}
|
|
28083
|
+
function verifyJwt(token, secret) {
|
|
28084
|
+
const claims = verifySignedClaims(token, secret);
|
|
28085
|
+
if (!claims || typeof claims.iss !== "string" || typeof claims.aud !== "string" || typeof claims.sub !== "string" || typeof claims.scope !== "string" || typeof claims.iat !== "number")
|
|
28086
|
+
return null;
|
|
28087
|
+
return claims;
|
|
28088
|
+
}
|
|
28089
|
+
function issueConsentTransaction(request, base) {
|
|
28090
|
+
const now = Math.floor(Date.now() / 1000);
|
|
28091
|
+
const transaction = {
|
|
28092
|
+
kind: "authorization-consent",
|
|
28093
|
+
iss: base,
|
|
28094
|
+
iat: now,
|
|
28095
|
+
exp: now + CONSENT_TRANSACTION_TTL_S,
|
|
28096
|
+
...request
|
|
28097
|
+
};
|
|
28098
|
+
return signSignedClaims(transaction, signingSecret());
|
|
28099
|
+
}
|
|
28100
|
+
function verifyConsentTransaction(ticket, base) {
|
|
28101
|
+
const claims = verifySignedClaims(ticket, signingSecret());
|
|
28102
|
+
if (!claims || claims.kind !== "authorization-consent" || claims.iss !== base || typeof claims.clientId !== "string" || claims.clientName !== undefined && typeof claims.clientName !== "string" || typeof claims.redirectUri !== "string" || typeof claims.responseType !== "string" || typeof claims.codeChallenge !== "string" || typeof claims.challengeMethod !== "string" || typeof claims.state !== "string" || typeof claims.resource !== "string" || typeof claims.scope !== "string")
|
|
28103
|
+
return null;
|
|
28104
|
+
let redirect;
|
|
28105
|
+
try {
|
|
28106
|
+
redirect = new URL(claims.redirectUri);
|
|
28107
|
+
} catch {
|
|
28108
|
+
return null;
|
|
28109
|
+
}
|
|
28110
|
+
if (redirect.protocol !== "http:" && redirect.protocol !== "https:")
|
|
28111
|
+
return null;
|
|
28112
|
+
if (claims.responseType !== "code" || claims.challengeMethod !== "S256" || !claims.codeChallenge)
|
|
28113
|
+
return null;
|
|
28114
|
+
if (claims.resource.replace(/\/+$/, "") !== resourceUri(base).replace(/\/+$/, ""))
|
|
28115
|
+
return null;
|
|
28116
|
+
return {
|
|
28117
|
+
clientId: claims.clientId,
|
|
28118
|
+
...typeof claims.clientName === "string" ? { clientName: claims.clientName } : {},
|
|
28119
|
+
redirectUri: claims.redirectUri,
|
|
28120
|
+
responseType: claims.responseType,
|
|
28121
|
+
codeChallenge: claims.codeChallenge,
|
|
28122
|
+
challengeMethod: claims.challengeMethod,
|
|
28123
|
+
state: claims.state,
|
|
28124
|
+
resource: claims.resource,
|
|
28125
|
+
scope: claims.scope
|
|
28126
|
+
};
|
|
28127
|
+
}
|
|
28072
28128
|
var MAX_REGISTERED_CLIENTS = 200;
|
|
28073
28129
|
function storePath() {
|
|
28074
28130
|
return configFilePath().replace(/\.json$/, "") + ".oauth.json";
|
|
@@ -28196,6 +28252,12 @@ function wwwAuthenticate(base, error) {
|
|
|
28196
28252
|
];
|
|
28197
28253
|
return parts.join(", ");
|
|
28198
28254
|
}
|
|
28255
|
+
var PRE_REGISTERED_CLIENTS = {
|
|
28256
|
+
"https://claude.ai/oauth/client-metadata": {
|
|
28257
|
+
client_id: "https://claude.ai/oauth/client-metadata",
|
|
28258
|
+
redirect_uris: ["https://claude.ai/api/mcp/auth_callback"]
|
|
28259
|
+
}
|
|
28260
|
+
};
|
|
28199
28261
|
function isLoopback(u) {
|
|
28200
28262
|
return (u.protocol === "http:" || u.protocol === "https:") && (u.hostname === "127.0.0.1" || u.hostname === "::1" || u.hostname === "localhost");
|
|
28201
28263
|
}
|
|
@@ -28270,6 +28332,10 @@ async function resolveClient(clientId) {
|
|
|
28270
28332
|
}
|
|
28271
28333
|
};
|
|
28272
28334
|
}
|
|
28335
|
+
const preRegistered = PRE_REGISTERED_CLIENTS[clientId];
|
|
28336
|
+
if (preRegistered) {
|
|
28337
|
+
return { client: { ...preRegistered, redirect_uris: [...preRegistered.redirect_uris] } };
|
|
28338
|
+
}
|
|
28273
28339
|
let url;
|
|
28274
28340
|
try {
|
|
28275
28341
|
url = new URL(clientId);
|
|
@@ -28451,6 +28517,26 @@ function landingPage(base, oauthOn, sseOn) {
|
|
|
28451
28517
|
<p class="foot">Health: <span class="host">/health</span> \xB7 Everything else is MCP.</p>
|
|
28452
28518
|
</div>`);
|
|
28453
28519
|
}
|
|
28520
|
+
var PAGE_SECURITY_DIRECTIVES = "default-src 'none'; frame-ancestors 'none'; base-uri 'none'; img-src 'self' data:";
|
|
28521
|
+
function pageContentSecurityPolicy(redirectUri) {
|
|
28522
|
+
let formAction = "'self'";
|
|
28523
|
+
if (redirectUri) {
|
|
28524
|
+
const origin = new URL(redirectUri).origin;
|
|
28525
|
+
if (origin !== "null")
|
|
28526
|
+
formAction += ` ${origin}`;
|
|
28527
|
+
}
|
|
28528
|
+
return `${PAGE_SECURITY_DIRECTIVES}; form-action ${formAction}; style-src 'unsafe-inline'`;
|
|
28529
|
+
}
|
|
28530
|
+
function consentResponse(transaction, redirectUri, clientHost, verifiedHost, error, status = 200) {
|
|
28531
|
+
return new Response(consentPage({ transaction }, clientHost, error, verifiedHost), {
|
|
28532
|
+
status,
|
|
28533
|
+
headers: {
|
|
28534
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
28535
|
+
"Cache-Control": "no-store",
|
|
28536
|
+
"Content-Security-Policy": pageContentSecurityPolicy(redirectUri)
|
|
28537
|
+
}
|
|
28538
|
+
});
|
|
28539
|
+
}
|
|
28454
28540
|
function consentPage(params, clientHost, error, verifiedHost = true) {
|
|
28455
28541
|
const hidden = Object.entries(params).map(([k, v]) => `<input type="hidden" name="${esc2(k)}" value="${esc2(v)}">`).join("");
|
|
28456
28542
|
const foot = verifiedHost ? "Only the host above is shown, and it is the one DNS and TLS vouch for." : "The name above is self-reported by the application \u2014 your password is what authorizes access.";
|
|
@@ -28481,7 +28567,13 @@ var htmlError = (message, status = 400) => new Response(shell("Authorization err
|
|
|
28481
28567
|
<h1>Authorization error</h1>
|
|
28482
28568
|
<div class="err">${esc2(message)}</div>
|
|
28483
28569
|
<p class="foot">Nothing was authorized. Close this window and start again from your client.</p>
|
|
28484
|
-
</div>`), {
|
|
28570
|
+
</div>`), {
|
|
28571
|
+
status,
|
|
28572
|
+
headers: {
|
|
28573
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
28574
|
+
"Content-Security-Policy": pageContentSecurityPolicy()
|
|
28575
|
+
}
|
|
28576
|
+
});
|
|
28485
28577
|
async function handleAuthorize(req, base) {
|
|
28486
28578
|
const url = new URL(req.url);
|
|
28487
28579
|
let form = null;
|
|
@@ -28494,63 +28586,73 @@ async function handleAuthorize(req, base) {
|
|
|
28494
28586
|
return htmlError("The authorization form could not be read.", 400);
|
|
28495
28587
|
}
|
|
28496
28588
|
}
|
|
28497
|
-
|
|
28498
|
-
|
|
28499
|
-
|
|
28500
|
-
|
|
28501
|
-
|
|
28502
|
-
|
|
28503
|
-
|
|
28504
|
-
|
|
28505
|
-
|
|
28506
|
-
|
|
28507
|
-
|
|
28508
|
-
|
|
28509
|
-
|
|
28510
|
-
|
|
28511
|
-
|
|
28512
|
-
|
|
28589
|
+
let request;
|
|
28590
|
+
let transaction;
|
|
28591
|
+
if (form) {
|
|
28592
|
+
transaction = form.get("transaction") ?? "";
|
|
28593
|
+
const verified = verifyConsentTransaction(transaction, base);
|
|
28594
|
+
if (!verified) {
|
|
28595
|
+
return htmlError("This authorization request expired or was altered. Start again from your client.");
|
|
28596
|
+
}
|
|
28597
|
+
request = verified;
|
|
28598
|
+
} else {
|
|
28599
|
+
const get = (k) => url.searchParams.get(k) ?? "";
|
|
28600
|
+
const clientId = get("client_id");
|
|
28601
|
+
const redirectUri = get("redirect_uri");
|
|
28602
|
+
const responseType = get("response_type");
|
|
28603
|
+
const codeChallenge = get("code_challenge");
|
|
28604
|
+
const challengeMethod = get("code_challenge_method");
|
|
28605
|
+
const state = get("state");
|
|
28606
|
+
const resource = get("resource");
|
|
28607
|
+
const scope = get("scope") || SCOPE;
|
|
28608
|
+
if (!clientId || !redirectUri)
|
|
28609
|
+
return htmlError("Missing client_id or redirect_uri.");
|
|
28610
|
+
const resolved = await resolveClient(clientId);
|
|
28611
|
+
if ("error" in resolved)
|
|
28612
|
+
return htmlError(resolved.error);
|
|
28613
|
+
if (!redirectUriAllowed(redirectUri, resolved.client.redirect_uris)) {
|
|
28614
|
+
return htmlError("redirect_uri is not listed in the client's metadata document.");
|
|
28615
|
+
}
|
|
28616
|
+
const fail = (error, description) => redirectWith(redirectUri, { error, error_description: description, iss: base, ...state ? { state } : {} });
|
|
28617
|
+
if (responseType !== "code")
|
|
28618
|
+
return fail("unsupported_response_type", "Only response_type=code is supported.");
|
|
28619
|
+
if (!codeChallenge || challengeMethod !== "S256") {
|
|
28620
|
+
return fail("invalid_request", "PKCE with code_challenge_method=S256 is required.");
|
|
28621
|
+
}
|
|
28622
|
+
if (resource && resource.replace(/\/+$/, "") !== resourceUri(base).replace(/\/+$/, "")) {
|
|
28623
|
+
return fail("invalid_target", `This server only issues tokens for ${resourceUri(base)}.`);
|
|
28624
|
+
}
|
|
28625
|
+
request = {
|
|
28626
|
+
clientId,
|
|
28627
|
+
...resolved.client.client_name ? { clientName: resolved.client.client_name } : {},
|
|
28628
|
+
redirectUri,
|
|
28629
|
+
responseType,
|
|
28630
|
+
codeChallenge,
|
|
28631
|
+
challengeMethod,
|
|
28632
|
+
state,
|
|
28633
|
+
resource: resource || resourceUri(base),
|
|
28634
|
+
scope
|
|
28635
|
+
};
|
|
28636
|
+
transaction = issueConsentTransaction(request, base);
|
|
28513
28637
|
}
|
|
28638
|
+
const { clientId, redirectUri, codeChallenge, state, resource, scope } = request;
|
|
28514
28639
|
const fail = (error, description) => redirectWith(redirectUri, { error, error_description: description, iss: base, ...state ? { state } : {} });
|
|
28515
|
-
if (responseType !== "code")
|
|
28516
|
-
return fail("unsupported_response_type", "Only response_type=code is supported.");
|
|
28517
|
-
if (!codeChallenge || challengeMethod !== "S256") {
|
|
28518
|
-
return fail("invalid_request", "PKCE with code_challenge_method=S256 is required.");
|
|
28519
|
-
}
|
|
28520
|
-
if (resource && resource.replace(/\/+$/, "") !== resourceUri(base).replace(/\/+$/, "")) {
|
|
28521
|
-
return fail("invalid_target", `This server only issues tokens for ${resourceUri(base)}.`);
|
|
28522
|
-
}
|
|
28523
28640
|
let clientHost;
|
|
28524
28641
|
let verifiedHost = true;
|
|
28525
28642
|
try {
|
|
28526
28643
|
clientHost = new URL(clientId).host;
|
|
28527
28644
|
} catch {
|
|
28528
28645
|
verifiedHost = false;
|
|
28529
|
-
clientHost =
|
|
28646
|
+
clientHost = request.clientName ?? "a dynamically registered client";
|
|
28530
28647
|
}
|
|
28531
|
-
const carried = {
|
|
28532
|
-
client_id: clientId,
|
|
28533
|
-
redirect_uri: redirectUri,
|
|
28534
|
-
response_type: responseType,
|
|
28535
|
-
code_challenge: codeChallenge,
|
|
28536
|
-
code_challenge_method: challengeMethod,
|
|
28537
|
-
scope,
|
|
28538
|
-
...state ? { state } : {},
|
|
28539
|
-
...resource ? { resource } : {}
|
|
28540
|
-
};
|
|
28541
28648
|
if (!form) {
|
|
28542
|
-
return
|
|
28543
|
-
headers: { "Content-Type": "text/html; charset=utf-8", "Cache-Control": "no-store" }
|
|
28544
|
-
});
|
|
28649
|
+
return consentResponse(transaction, redirectUri, clientHost, verifiedHost);
|
|
28545
28650
|
}
|
|
28546
|
-
if (get("decision") !== "approve") {
|
|
28651
|
+
if (form.get("decision") !== "approve") {
|
|
28547
28652
|
return fail("access_denied", "The owner denied the request.");
|
|
28548
28653
|
}
|
|
28549
|
-
if (!ownerPasswordMatches(get("password"))) {
|
|
28550
|
-
return
|
|
28551
|
-
status: 401,
|
|
28552
|
-
headers: { "Content-Type": "text/html; charset=utf-8", "Cache-Control": "no-store" }
|
|
28553
|
-
});
|
|
28654
|
+
if (!ownerPasswordMatches(form.get("password") ?? "")) {
|
|
28655
|
+
return consentResponse(transaction, redirectUri, clientHost, verifiedHost, "Incorrect password.", 401);
|
|
28554
28656
|
}
|
|
28555
28657
|
const store = loadStore();
|
|
28556
28658
|
const code = randomBytes(32).toString("base64url");
|
|
@@ -28559,7 +28661,7 @@ async function handleAuthorize(req, base) {
|
|
|
28559
28661
|
redirectUri,
|
|
28560
28662
|
codeChallenge,
|
|
28561
28663
|
scope,
|
|
28562
|
-
resource
|
|
28664
|
+
resource,
|
|
28563
28665
|
expiresAt: Date.now() + AUTH_CODE_TTL_MS
|
|
28564
28666
|
};
|
|
28565
28667
|
saveStore();
|
|
@@ -28572,7 +28674,7 @@ var tokenError = (error, description, status = 400) => new Response(JSON.stringi
|
|
|
28572
28674
|
function issueTokens(base, clientId, resource, scope) {
|
|
28573
28675
|
const store = loadStore();
|
|
28574
28676
|
const now = Math.floor(Date.now() / 1000);
|
|
28575
|
-
const accessToken = signJwt({ iss: base, aud: resource, sub: "owner", scope, iat: now, exp: now + ACCESS_TOKEN_TTL_S },
|
|
28677
|
+
const accessToken = signJwt({ iss: base, aud: resource, sub: "owner", scope, iat: now, exp: now + ACCESS_TOKEN_TTL_S }, signingSecret());
|
|
28576
28678
|
const refreshToken = randomBytes(32).toString("base64url");
|
|
28577
28679
|
store.refresh[refreshToken] = { clientId, resource, scope, expiresAt: Date.now() + REFRESH_TOKEN_TTL_MS };
|
|
28578
28680
|
saveStore();
|
|
@@ -28722,7 +28824,7 @@ function createServer(origin = null) {
|
|
|
28722
28824
|
const s = new McpServer({
|
|
28723
28825
|
name: "BrainLLM",
|
|
28724
28826
|
title: "BrainLLM",
|
|
28725
|
-
version: "12.4.
|
|
28827
|
+
version: "12.4.2",
|
|
28726
28828
|
icons: brandingIcons(origin)
|
|
28727
28829
|
});
|
|
28728
28830
|
registerTools(s, trilium, brainRef);
|
|
@@ -28761,8 +28863,11 @@ if (port) {
|
|
|
28761
28863
|
"Strict-Transport-Security": "max-age=31536000; includeSubDomains"
|
|
28762
28864
|
};
|
|
28763
28865
|
const withCors = (res) => {
|
|
28764
|
-
for (const [k, v] of Object.entries({ ...CORS_HEADERS, ...SECURITY_HEADERS }))
|
|
28866
|
+
for (const [k, v] of Object.entries({ ...CORS_HEADERS, ...SECURITY_HEADERS })) {
|
|
28867
|
+
if (k === "Content-Security-Policy" && res.headers.has(k))
|
|
28868
|
+
continue;
|
|
28765
28869
|
res.headers.set(k, v);
|
|
28870
|
+
}
|
|
28766
28871
|
return res;
|
|
28767
28872
|
};
|
|
28768
28873
|
const rateLimiters = {
|
|
@@ -28872,12 +28977,14 @@ if (port) {
|
|
|
28872
28977
|
return withCors(await handleRegister(req));
|
|
28873
28978
|
}
|
|
28874
28979
|
}
|
|
28875
|
-
|
|
28980
|
+
const rootMcpRequest = url.pathname === "/" && (req.method !== "GET" || req.headers.has("mcp-session-id") || req.headers.has("mcp-protocol-version") || (req.headers.get("accept") ?? "").includes("application/json") || (req.headers.get("accept") ?? "").includes("text/event-stream"));
|
|
28981
|
+
if (url.pathname === "/" && !rootMcpRequest) {
|
|
28876
28982
|
return withCors(new Response(landingPage(base, oauthOn, true), {
|
|
28877
28983
|
headers: { "Content-Type": "text/html; charset=utf-8", "Cache-Control": "no-store" }
|
|
28878
28984
|
}));
|
|
28879
28985
|
}
|
|
28880
|
-
|
|
28986
|
+
const isMcpEndpoint = url.pathname === "/mcp" || rootMcpRequest;
|
|
28987
|
+
if (!isMcpEndpoint && url.pathname !== "/sse" && url.pathname !== "/messages") {
|
|
28881
28988
|
return withCors(new Response("Not Found", { status: 404 }));
|
|
28882
28989
|
}
|
|
28883
28990
|
const transportLimit = rateLimited(rateLimiters.transport, requestKey);
|
package/manifest.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"manifest_version": "0.3",
|
|
3
3
|
"name": "brainllm",
|
|
4
4
|
"display_name": "BrainLLM",
|
|
5
|
-
"version": "12.4.
|
|
5
|
+
"version": "12.4.2",
|
|
6
6
|
"description": "Turns TriliumNext Notes into a persistent, graph-structured second brain for Claude and other LLM clients.",
|
|
7
7
|
"long_description": "BrainLLM is an MCP server that organises memory into five purpose-built areas (Master, LLM, Memory, Knowledge, Insights). The model supplies content while the server owns placement, format, lifecycle, dates and backups. Includes a full raw ETAPI surface for advanced use. Requires a running TriliumNext instance and the Bun runtime.",
|
|
8
8
|
"author": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brainllm",
|
|
3
|
-
"version": "12.4.
|
|
3
|
+
"version": "12.4.2",
|
|
4
4
|
"mcpName": "io.github.miisodev/brainllm",
|
|
5
5
|
"description": "MCP server that turns TriliumNext Notes into a persistent, graph-structured second brain for Claude and other LLM clients.",
|
|
6
6
|
"author": "Kevin Miiso Novo <miisodev@gmail.com>",
|
package/server.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
3
|
"name": "io.github.miisodev/brainllm",
|
|
4
4
|
"description": "Persistent graph-structured memory for LLMs in TriliumNext Notes — readable and editable by humans.",
|
|
5
|
-
"version": "12.4.
|
|
5
|
+
"version": "12.4.2",
|
|
6
6
|
"websiteUrl": "https://github.com/miisodev/BrainLLM#readme",
|
|
7
7
|
"repository": {
|
|
8
8
|
"url": "https://github.com/miisodev/BrainLLM",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
{
|
|
13
13
|
"registryType": "npm",
|
|
14
14
|
"identifier": "brainllm",
|
|
15
|
-
"version": "12.4.
|
|
15
|
+
"version": "12.4.2",
|
|
16
16
|
"transport": {
|
|
17
17
|
"type": "stdio"
|
|
18
18
|
},
|