Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion extensions/_template/AGENT_SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,11 @@ const server = new McpServer({
const app = new Hono();

app.all("*", async (c) => {
const provided = c.req.header("x-brain-key") || new URL(c.req.url).searchParams.get("key");
// Accept access key via Authorization: Bearer header, x-brain-key header, or URL query parameter
const authHeader = c.req.header("Authorization");
const provided = (authHeader?.startsWith("Bearer ") && authHeader.split(" ")[1])
|| c.req.header("x-brain-key")
|| new URL(c.req.url).searchParams.get("key");
if (!provided || provided !== MCP_ACCESS_KEY) {
return c.json({ error: "Invalid or missing access key" }, 401);
}
Expand Down
6 changes: 5 additions & 1 deletion integrations/kubernetes-deployment/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -415,7 +415,11 @@ server.registerTool(
const app = new Hono();

app.all("*", async (c) => {
const provided = c.req.header("x-brain-key") || new URL(c.req.url).searchParams.get("key");
// Accept access key via Authorization: Bearer header, x-brain-key header, or URL query parameter
const authHeader = c.req.header("Authorization");
const provided = (authHeader?.startsWith("Bearer ") && authHeader.split(" ")[1])
|| c.req.header("x-brain-key")
|| new URL(c.req.url).searchParams.get("key");
if (!provided || provided !== MCP_ACCESS_KEY) {
return c.json({ error: "Invalid or missing access key" }, 401);
}
Expand Down
22 changes: 11 additions & 11 deletions recipes/fingerprint-dedup-backfill/backfill-fingerprints.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -94,14 +94,14 @@ function buildContentFingerprint(text) {

// ── REST helpers ────────────────────────────────────────────────────────────

async function fetchBatch(cursorId, batchSize) {
async function fetchBatch(cursorCreatedAt, batchSize) {
const url =
`${REST_BASE}/thoughts` +
`?content_fingerprint=is.null` +
`&id=gt.${cursorId}` +
`&select=id,content` +
`&created_at=gt.${encodeURIComponent(cursorCreatedAt)}` +
`&select=id,content,created_at` +
`&limit=${batchSize}` +
`&order=id.asc`;
`&order=created_at.asc`;
const res = await fetch(url, { headers: HEADERS });
if (!res.ok) {
const body = await res.text().catch(() => "");
Expand Down Expand Up @@ -162,7 +162,7 @@ function loadState() {
return JSON.parse(fs.readFileSync(STATE_FILE, "utf8"));
} catch {
return {
cursorId: 0,
cursorCreatedAt: "1970-01-01T00:00:00Z",
totalDone: 0,
totalDuplicates: 0,
totalErrors: 0,
Expand All @@ -183,20 +183,20 @@ async function main() {
const state = loadState();
console.log("=== Backfill content_fingerprint ===");
console.log(
`Resuming from cursor id=${state.cursorId} (${state.totalDone} already done)`
`Resuming from cursor created_at=${state.cursorCreatedAt} (${state.totalDone} already done)`
);
console.log(`Batch size: ${BATCH_SIZE}`);
console.log();

while (true) {
state.batches++;
process.stdout.write(
`Batch ${state.batches}: fetching from id>${state.cursorId}… `
`Batch ${state.batches}: fetching from created_at>${state.cursorCreatedAt}… `
);

let rows;
try {
rows = await fetchBatch(state.cursorId, BATCH_SIZE);
rows = await fetchBatch(state.cursorCreatedAt, BATCH_SIZE);
} catch (err) {
console.error("\n Fetch error:", err.message, "— retrying in 5s…");
await new Promise((r) => setTimeout(r, 5000));
Expand All @@ -221,8 +221,8 @@ async function main() {
state.totalDuplicates += duplicates;
state.totalErrors += errors;

const maxId = rows[rows.length - 1].id;
state.cursorId = typeof maxId === "number" ? maxId : maxId;
const lastCreatedAt = rows[rows.length - 1].created_at;
state.cursorCreatedAt = lastCreatedAt;
saveState(state);

const dupeStr =
Expand All @@ -231,7 +231,7 @@ async function main() {
console.log(
` → ${done} patched${dupeStr}${errStr}. ` +
`Total: ${state.totalDone} patched, ${state.totalDuplicates} duplicates, ${state.totalErrors} errors. ` +
`Cursor: ${state.cursorId}`
`Cursor: ${state.cursorCreatedAt}`
);

await new Promise((r) => setTimeout(r, 150));
Expand Down
22 changes: 11 additions & 11 deletions recipes/fingerprint-dedup-backfill/delete-duplicates.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -93,14 +93,14 @@ function buildFingerprint(text) {

// ── REST helpers ────────────────────────────────────────────────────────────

async function fetchBatch(cursorId, batchSize) {
async function fetchBatch(cursorCreatedAt, batchSize) {
const url =
`${REST_BASE}/thoughts` +
`?content_fingerprint=is.null` +
`&id=gt.${cursorId}` +
`&select=id,content` +
`&created_at=gt.${encodeURIComponent(cursorCreatedAt)}` +
`&select=id,content,created_at` +
`&limit=${batchSize}` +
`&order=id.asc`;
`&order=created_at.asc`;
const res = await fetch(url, { headers: HEADERS });
if (!res.ok) {
const body = await res.text().catch(() => "");
Expand Down Expand Up @@ -173,7 +173,7 @@ function loadState() {
return JSON.parse(fs.readFileSync(STATE_FILE, "utf8"));
} catch {
return {
cursorId: 0,
cursorCreatedAt: "1970-01-01T00:00:00Z",
totalDeleted: 0,
totalPatched: 0,
totalWouldDelete: 0,
Expand Down Expand Up @@ -205,19 +205,19 @@ async function main() {
}

console.log(
`Resuming from cursor id=${state.cursorId} (deleted: ${state.totalDeleted}, patched: ${state.totalPatched})`
`Resuming from cursor created_at=${state.cursorCreatedAt} (deleted: ${state.totalDeleted}, patched: ${state.totalPatched})`
);
console.log(`Batch size: ${BATCH_SIZE}\n`);

while (true) {
state.batches++;
process.stdout.write(
`Batch ${state.batches}: fetching from id>${state.cursorId}… `
`Batch ${state.batches}: fetching from created_at>${state.cursorCreatedAt}… `
);

let rows;
try {
rows = await fetchBatch(state.cursorId, BATCH_SIZE);
rows = await fetchBatch(state.cursorCreatedAt, BATCH_SIZE);
} catch (err) {
console.error("\n Fetch error:", err.message, "— retrying in 5s…");
await new Promise((r) => setTimeout(r, 5000));
Expand Down Expand Up @@ -303,14 +303,14 @@ async function main() {
}

// Advance cursor
const maxId = rows[rows.length - 1].id;
state.cursorId = maxId;
const lastCreatedAt = rows[rows.length - 1].created_at;
state.cursorCreatedAt = lastCreatedAt;
saveState(state);

console.log(
` Totals: deleted=${state.totalDeleted}, patched=${state.totalPatched}, ` +
`would-delete=${state.totalWouldDelete}, errors=${state.totalErrors}. ` +
`Cursor: ${state.cursorId}`
`Cursor: ${state.cursorCreatedAt}`
);

await new Promise((r) => setTimeout(r, 200));
Expand Down
2 changes: 1 addition & 1 deletion recipes/fingerprint-dedup-backfill/metadata.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,5 @@
"difficulty": "beginner",
"estimated_time": "15 minutes",
"created": "2026-03-22",
"updated": "2026-03-22"
"updated": "2026-04-03"
}
7 changes: 5 additions & 2 deletions server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -378,8 +378,11 @@ app.options("*", (c) => {
});

app.all("*", async (c) => {
// Accept access key via header OR URL query parameter
const provided = c.req.header("x-brain-key") || new URL(c.req.url).searchParams.get("key");
// Accept access key via Authorization: Bearer header, x-brain-key header, or URL query parameter
const authHeader = c.req.header("Authorization");
const provided = (authHeader?.startsWith("Bearer ") && authHeader.split(" ")[1])
|| c.req.header("x-brain-key")
|| new URL(c.req.url).searchParams.get("key");
if (!provided || provided !== MCP_ACCESS_KEY) {
return c.json({ error: "Invalid or missing access key" }, 401, corsHeaders);
}
Expand Down
Loading