Skip to content
Open
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
81 changes: 56 additions & 25 deletions integrations/enhanced-mcp/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,12 +149,16 @@ server.registerTool(
}

if (mode === "text") {
// `p_filter` is a metadata *containment* test in search_thoughts_text
// (`t.metadata @> coalesce(p_filter, '{}')`), not a bag of control
// flags. Adding exclude_restricted/start_date/end_date here asks for
// thoughts whose metadata literally contains those keys, which no
// thought does — so every text query matched zero rows. Only genuine
// metadata filters may go into the payload; sensitivity and date
// bounds are applied below, as the semantic branch already does.
const filter: Record<string, unknown> = {
...(metadataFilter as Record<string, unknown>),
};
filter.exclude_restricted = true;
if (startDate) filter.start_date = startDate;
if (endDate) filter.end_date = endDate;

const { data, error } = await supabase.rpc("search_thoughts_text", {
p_query: query,
Expand All @@ -167,7 +171,10 @@ server.registerTool(
throw new Error(`search_thoughts_text failed: ${error.message}`);
}

const rows = (data ?? []) as ThoughtRow[];
const rows = ((data ?? []) as ThoughtRow[])
.filter((row) => row.sensitivity_tier !== "restricted")
.filter((row) => !startDate || row.created_at >= startDate)
.filter((row) => !endDate || row.created_at <= endDate);
const totalCount =
rows.length > 0
? Number(
Expand Down Expand Up @@ -212,15 +219,14 @@ server.registerTool(
// over-fetch slack to avoid silently returning zero results on active
// brains with old date windows. See `known limitations` in the README.
const dateFilterActive = !!(startDate || endDate);
// Forward filters into the RPC payload — ignored by older RPC versions
// but used by versions that support them, at which point the
// post-filter becomes a no-op.
// Only genuine metadata filters belong in the RPC payload. The getting-
// started `match_thoughts` applies `filter = '{}' OR metadata @> filter`,
// so unknown keys are NOT ignored as previously assumed here — they make
// the containment test fail and the query returns zero rows. Sensitivity
// and date bounds are post-filtered below instead.
const semanticFilter: Record<string, unknown> = {
...(metadataFilter as Record<string, unknown>),
exclude_restricted: true,
};
if (startDate) semanticFilter.start_date = startDate;
if (endDate) semanticFilter.end_date = endDate;

// Over-fetch when date filter is active so client-side post-filter
// has headroom. 3x the requested limit is a reasonable compromise
Expand Down Expand Up @@ -387,17 +393,16 @@ server.registerTool(
description:
"Fetch a thought by ID with its full metadata and provenance.",
inputSchema: z.object({
id: z.number().int().min(1).describe("Thought ID"),
// The core Open Brain schema defines `thoughts.id` as
// `uuid default gen_random_uuid()`, so an integer declaration made this
// tool impossible to call on a standard install. The lookup below is a
// plain `.eq()`, so a uuid string works unchanged.
id: z.string().uuid().describe("Thought ID (uuid)"),
}),
},
async (params) => {
try {
const id = asInteger(
(params as Record<string, unknown>).id,
0,
1,
Number.MAX_SAFE_INTEGER,
);
const id = String((params as Record<string, unknown>).id ?? "").trim();

if (!id) {
return toolFailure("id is required");
Expand Down Expand Up @@ -687,17 +692,39 @@ server.registerTool(
throw new Error(`upsert_thought failed: ${error.message}`);
}

const result = data as UpsertThoughtResult | null;
if (!result?.thought_id) {
// The `upsert_thought` shipped in schemas/enhanced-thoughts returns
// `{id, fingerprint}` and never reads `p_payload.embedding` — the
// embedding column is not in its INSERT list. Reading only `thought_id`
// therefore threw *after* the row had already been inserted, leaving a
// thought with a NULL embedding that semantic search can never return.
// Accept either key, then persist the embedding in a follow-up update,
// which is the same two-step the stock core server performs.
const result = data as (UpsertThoughtResult & { id?: string }) | null;
const thoughtId = result?.thought_id ?? result?.id;
if (!thoughtId) {
throw new Error("upsert_thought returned no result");
}

if (safeEmbedding(prepared.embedding)) {
const { error: embError } = await supabase
.from("thoughts")
.update({ embedding: prepared.embedding })
.eq("id", thoughtId);
if (embError) {
return toolFailure(
`Thought ${thoughtId} saved, but its embedding failed to store (${embError.message}). ` +
`It will not appear in semantic search until re-embedded.`,
);
}
}

return toolSuccess(
`${result.action === "inserted" ? "Captured new" : "Updated"} thought #${result.thought_id} as ${prepared.type}.`,
`${result?.action === "inserted" ? "Captured new" : "Saved"} thought #${thoughtId} as ${prepared.type}.`,
{
thought_id: result.thought_id,
action: result.action,
content_fingerprint: result.content_fingerprint,
thought_id: thoughtId,
action: result?.action,
content_fingerprint: result?.content_fingerprint ??
(result as Record<string, unknown> | null)?.fingerprint,
type: prepared.type,
sensitivity_tier: prepared.sensitivity_tier,
metadata: prepared.metadata,
Expand Down Expand Up @@ -804,15 +831,19 @@ server.registerTool(
const { data, error } = await supabase.rpc("search_thoughts_text", {
p_query: query,
p_limit: limit,
p_filter: { exclude_restricted: true },
// Was `{ exclude_restricted: true }`, which the RPC treats as a
// metadata containment test and so matched nothing. Restricted rows
// are dropped client-side below instead.
p_filter: {},
p_offset: offset,
});

if (error) {
throw new Error(`search_thoughts_text failed: ${error.message}`);
}

const rows = (data ?? []) as ThoughtRow[];
const rows = ((data ?? []) as ThoughtRow[])
.filter((row) => row.sensitivity_tier !== "restricted");

if (rows.length === 0) {
return toolSuccess("No matches found.", { results: [] });
Expand Down
Loading