Skip to content
Open
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
135 changes: 135 additions & 0 deletions integrations/delete-thought-mcp/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
# Delete Thought MCP

> Standalone MCP Edge Function that adds a `delete_thought` tool — hard-deletes a thought by UUID with a pre-flight fetch and a clear confirmation response.

## What It Does

The core Open Brain MCP server exposes capture/search/list/stats tools but has no delete path. As a result, thoughts accumulate forever — there is no way for an AI client to remove a test entry, a duplicate, or something captured in error without dropping into the Supabase SQL editor.

This integration deploys a second Edge Function that exposes exactly one tool, `delete_thought(id)`. It is a hard delete (the row is removed), with a pre-flight existence check so the caller sees a distinct "not found" outcome rather than a silent success.

**Recovery:** this is a hard delete, not a soft delete. Recovery depends on your Supabase project's database backups (daily backups are available on paid tiers; Point-in-Time Recovery on higher tiers). If you need recoverable deletes, install the companion `schemas/thought-audit` schema and extend this function to write an audit row with the prior content before the delete — see the "Audit hook" section below.

## Prerequisites

- Working Open Brain setup ([guide](../../docs/01-getting-started.md))
- Supabase CLI installed

## Credential Tracker

Copy this block into a text editor and fill it in as you go.

```text
DELETE THOUGHT MCP -- CREDENTIAL TRACKER
--------------------------------------

FROM YOUR OPEN BRAIN SETUP
Project URL: ____________
Service role key: ____________
MCP access key: ____________

GENERATED DURING SETUP
Delete Thought URL: https://<project>.supabase.co/functions/v1/delete-thought-mcp
Custom connector name: Open Brain — Delete

--------------------------------------
```

## Steps

### 1. Create the Edge Function

From the root of your local Open Brain repo:

**1. Create the function folder:**

```bash
supabase functions new delete-thought-mcp
```

**2. Copy the integration code:**

```bash
curl -o supabase/functions/delete-thought-mcp/index.ts \
https://raw.githubusercontent.com/NateBJones-Projects/OB1/main/integrations/delete-thought-mcp/index.ts
curl -o supabase/functions/delete-thought-mcp/deno.json \
https://raw.githubusercontent.com/NateBJones-Projects/OB1/main/integrations/delete-thought-mcp/deno.json
```

### 2. Set environment variables

```bash
supabase secrets set MCP_ACCESS_KEY="your-mcp-access-key"
```

`SUPABASE_URL` and `SUPABASE_SERVICE_ROLE_KEY` are injected automatically by the Supabase platform.

### 3. Deploy

```bash
supabase functions deploy delete-thought-mcp --no-verify-jwt
```

### 4. Register the connector

In Claude Desktop: **Settings → Connectors → Add custom connector**, paste:

```
https://<project>.supabase.co/functions/v1/delete-thought-mcp?key=<MCP_ACCESS_KEY>
```

Use a distinct connector name (e.g. `Open Brain — Delete`) so the tool is easy to spot in your tool list.

### 5. Verify

Ask Claude: `Call the delete_thought tool with id = "<some-uuid>".`

Run through this short verification sequence:

1. Capture a throwaway thought and copy its id from the response.
2. Call `delete_thought` with that id — you should see `Deleted thought <id> (prior content length: N chars).`
3. Call `delete_thought` with the same id again — you should see `Thought not found: <id>` with `isError: true`.
4. Confirm in the Supabase Table Editor that the row is gone (reload the Table Editor if it still appears cached).

## Expected Outcome

- A new Edge Function at `https://<project>.supabase.co/functions/v1/delete-thought-mcp`.
- A custom connector in your AI client that exposes exactly one tool, `delete_thought`.
- Invoking the tool with a valid UUID removes that row from the `thoughts` table and returns a confirmation.
- Invoking with a non-existent UUID returns a clear `Thought not found: <id>` error.

The [MCP Tool Audit & Optimization Guide](../../docs/05-tool-audit.md) explains how to manage your tool surface area as you add this and other custom connectors.

## Audit Hook (optional)

If you also install `schemas/thought-audit`, extend this function to write an audit row before the delete so the prior `content`, `metadata`, and `created_at` are preserved in `thought_audit` for recovery or historical audit queries. A minimal sketch:

```ts
// Before the delete call:
await supabase.from("thought_audit").insert({
thought_id: id,
action: "delete",
diff: {
previous_content: existing.content,
previous_metadata: existing.metadata ?? null,
},
actor_context: { origin: "mcp:delete_thought" },
});
```

Left out of the base integration to keep its dependencies to a single table.

## Troubleshooting

**Issue: Tool call returns `401 Invalid or missing access key`**
Solution: Confirm the `?key=` in your custom connector URL matches the `MCP_ACCESS_KEY` secret set on the Edge Function. If you rotate the key, re-deploy and update the connector URL.

**Issue: `delete_thought error: permission denied for table thoughts`**
Solution: Ensure your service role has DELETE permission on `public.thoughts`. The getting-started guide grants this in Step 2.5 — re-run `grant select, insert, update, delete on table public.thoughts to service_role;` in the SQL editor if it was missed.

**Issue: Tool succeeds but the row is still visible in the Table Editor**
Solution: The Table Editor caches results. Reload the page, or run `select id from thoughts where id = '<uuid>'` directly in the SQL Editor to confirm the row is gone.

## Attribution

Adapted from a multi-participant capture design used across live Claude / ChatGPT / Codex sessions. Released as a standalone integration so any Open Brain user can opt in without modifying the core server.
5 changes: 5 additions & 0 deletions integrations/delete-thought-mcp/deno.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"imports": {
"@supabase/supabase-js": "npm:@supabase/supabase-js@2.47.10"
}
}
155 changes: 155 additions & 0 deletions integrations/delete-thought-mcp/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
/**
* delete-thought-mcp — Standalone MCP Edge Function that adds a single tool:
* delete_thought(id)
*
* The core open-brain MCP server does not expose a delete path. This
* integration adds one without modifying the core server — deploy alongside
* your main MCP connector and register as a separate custom connector.
*
* Behavior:
* - Pre-flight fetch to confirm the thought exists (so the caller gets a
* clear "not found" instead of a silent success).
* - Hard delete — the row is gone once this returns. Recovery depends on
* your database backup strategy (see README).
*
* Auth: x-brain-key header OR ?key=... URL query parameter.
*
* Env vars:
* SUPABASE_URL
* SUPABASE_SERVICE_ROLE_KEY
* MCP_ACCESS_KEY
*
* Extension hook:
* If you install the thought_audit schema (see `schemas/thought-audit`)
* you can extend this function to write an audit row before the delete
* so the prior content is preserved for recovery. Left out of the base
* integration to keep dependencies minimal.
*/

import "jsr:@supabase/functions-js/edge-runtime.d.ts";

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPTransport } from "@hono/mcp";
import { Hono } from "hono";
import { z } from "zod";
import { createClient } from "@supabase/supabase-js";

const SUPABASE_URL = Deno.env.get("SUPABASE_URL")!;
const SUPABASE_SERVICE_ROLE_KEY = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!;
const MCP_ACCESS_KEY = Deno.env.get("MCP_ACCESS_KEY")!;

const supabase = createClient(SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY);

// --- MCP Server Setup ---

const server = new McpServer({
name: "open-brain-delete-thought",
version: "1.0.0",
});

server.registerTool(
"delete_thought",
{
title: "Delete Thought",
description:
"Permanently delete a thought by UUID. The row is hard-deleted — recovery depends on your database backups. Returns a confirmation including the prior content length so the caller can log what was removed.",
inputSchema: {
id: z.string().uuid().describe("UUID of the thought to delete"),
},
},
async ({ id }) => {
try {
// Pre-flight fetch so "not found" is a clear, distinct outcome.
const { data: existing, error: fetchError } = await supabase
.from("thoughts")
.select("id, content")
.eq("id", id)
.single();

if (fetchError || !existing) {
return {
content: [
{
type: "text" as const,
text: `Thought not found: ${id}`,
},
],
isError: true,
};
}

const { error } = await supabase.from("thoughts").delete().eq("id", id);

if (error) {
return {
content: [
{
type: "text" as const,
text: `delete_thought error: ${error.message}`,
},
],
isError: true,
};
}

const priorLength =
typeof existing.content === "string" ? existing.content.length : 0;

return {
content: [
{
type: "text" as const,
text: `Deleted thought ${id} (prior content length: ${priorLength} chars).`,
},
],
};
} catch (err: unknown) {
return {
content: [
{ type: "text" as const, text: `Error: ${(err as Error).message}` },
],
isError: true,
};
}
},
);

// --- Hono app with auth + CORS ---

const corsHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers":
"authorization, x-client-info, apikey, content-type, x-brain-key, accept, mcp-session-id",
"Access-Control-Allow-Methods": "GET, POST, OPTIONS, DELETE",
};

const app = new Hono();

app.options("*", (c) => c.text("ok", 200, corsHeaders));

app.all("*", async (c) => {
const provided =
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);
}

if (!c.req.header("accept")?.includes("text/event-stream")) {
const headers = new Headers(c.req.raw.headers);
headers.set("Accept", "application/json, text/event-stream");
const patched = new Request(c.req.raw.url, {
method: c.req.raw.method,
headers,
body: c.req.raw.body,
// @ts-ignore -- duplex required for streaming body in Deno
duplex: "half",
});
Object.defineProperty(c.req, "raw", { value: patched, writable: true });
}

const transport = new StreamableHTTPTransport();
await server.connect(transport);
return transport.handleRequest(c);
});

Deno.serve(app.fetch);
20 changes: 20 additions & 0 deletions integrations/delete-thought-mcp/metadata.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"name": "Delete Thought MCP",
"description": "Standalone MCP Edge Function that adds a delete_thought tool — hard-deletes a thought by UUID with a pre-flight fetch and a clear confirmation response.",
"category": "integrations",
"author": {
"name": "Scott Hutchinson",
"github": "txcfi-scott"
},
"version": "1.0.0",
"requires": {
"open_brain": true,
"services": ["Supabase"],
"tools": ["Supabase CLI", "Deno"]
},
"tags": ["mcp", "delete", "cleanup", "edge-function"],
"difficulty": "beginner",
"estimated_time": "10 minutes",
"created": "2026-04-23",
"updated": "2026-04-23"
}
Loading