Skip to main content
Glama
akshatbatra

Attachpad

by akshatbatra
README.md
# Attachpad

Attachpad is a browser-scoped context control center for MCP clients. Upload files, import public web pages, arrange sources by importance, and let an MCP-compatible client retrieve focused context instead of sending an entire file library every time.

## Built with GPT-5.6 and Codex

Attachpad was fully built with GPT-5.6 and Codex: from the React Router interface and MCP server to SQLite/Drizzle persistence, document normalization, retrieval, and deployment configuration. Codex was used as the implementation partner throughout the project.

## What it does

- Stores files in a local SQLite database using Drizzle ORM.
- Associates every source with a browser-local session ID.
- Accepts drag-and-drop uploads and public URL imports.
- Converts uploaded PDFs and imported HTML pages into agent-readable Markdown.
- Preserves manual source order and a 1–5 priority weight.
- Indexes normalized content locally with SQLite FTS5.
- Exposes focused retrieval tools through a React Router MCP endpoint.
- Makes no external embedding or search API calls.

## Architecture

```mermaid
flowchart LR
    browser["Browser workspace<br/>local session ID"]
    client["MCP client<br/>VS Code, ChatGPT, or another client"]

    subgraph app["Attachpad React Router server"]
        ui["Context control center"]
        filesApi["Files API<br/>uploads, imports, ordering"]
        mcp["MCP endpoint<br/>JSON-RPC over HTTP"]
        importer["Document normalizer<br/>PDF and HTML to Markdown"]
        indexer["Chunking and ranking<br/>priority and manual order"]
    end

    subgraph data["SQLite database"]
        files[("files")]
        chunks[("attachment chunks")]
        fts[("FTS5 search index")]
    end

    browser --> ui
    browser -->|"session-scoped changes"| filesApi
    filesApi --> importer
    filesApi --> files
    filesApi --> indexer
    importer --> files
    indexer --> chunks
    indexer --> fts
    client -->|"MCP URL with session ID"| mcp
    mcp --> fts
    mcp --> files
```
### Request flow

1. The browser creates a UUID and stores it in `localStorage` under `attachpad-session`.
2. Uploads and URL imports send that ID as `x-session-id`.
3. The server stores raw content and normalized Markdown in SQLite.
4. Content is split into chunks and indexed in SQLite FTS5.
5. The MCP client calls a retrieval tool with the same session ID in the MCP URL.
6. Search results combine text relevance, manual order, and priority weight.

## MCP endpoint

The workspace displays a session-specific URL such as:

```text
http://localhost:5173/mcp?session_id=YOUR_SESSION_ID
```

Use that URL as an HTTP MCP server. Keep it private: the URL grants access to that browser session's context.

### Exposed tools

| Tool | Purpose |
| --- | --- |
| `list_attached_files` | Returns metadata only, in manual context order. |
| `search_attached_files` | Searches local SQLite FTS5 chunks using keyword and prefix matching. |
| `read_attached_file` | Reads one file by ID with a character limit. |
| `get_relevant_context` | Searches and assembles ranked chunks within a character budget. |
| `get_attached_files` | Compatibility tool that returns all attached context, or one file by ID. |

Priority semantics are communicated to the MCP client: earlier manual position means higher user preference, and priority `1–5` provides an additional retrieval boost. Relevance still matters.

## Local development

### Requirements

- Node.js 20+
- npm

### Install and run

```bash
npm install
npm run dev
```

Open `http://localhost:5173` in a browser.

### SQLite and Drizzle

The default database is `file:./data/attachpad.db`. The application bootstraps missing tables and indexes when the server first receives a request, including the FTS5 search table.

Optional Drizzle commands:

```bash
npm run db:generate
npm run db:migrate
npm run db:push
```

For the current local bootstrap flow, no database command is required before `npm run dev`.

To use another supported libSQL-compatible URL, set `SQLITE_DATABASE_URL`. PostgreSQL URLs are intentionally ignored because Attachpad uses SQLite/libSQL.

## VS Code setup

Create `.vscode/mcp.json` in the workspace:

```json
{
  "servers": {
    "attachpad": {
      "type": "http",
      "url": "http://localhost:5173/mcp?session_id=YOUR_SESSION_ID"
    }
  }
}
```

Replace `YOUR_SESSION_ID` with the URL copied from the Attachpad MCP settings drawer. Restart the server with `npm run dev`, then use **MCP: List Servers** in VS Code to start or refresh Attachpad.

## Project structure

```text
app/
├── db/
│   ├── db.server.ts       # SQLite connection, bootstrap, indexing, retrieval
│   └── schema.ts          # Drizzle tables
├── lib/
│   ├── document.server.ts # PDF-to-Markdown normalization
│   └── web-import.server.ts # Safe URL fetching and HTML-to-Markdown
├── routes/
│   ├── home.tsx           # Context control center UI
│   ├── api.files.ts       # Upload, import, delete, priority, reorder
│   └── mcp.ts             # MCP JSON-RPC endpoint and tools
└── routes.ts              # React Router route configuration

data/                     # Local SQLite database directory
drizzle/                  # Drizzle SQL migrations
```

## Security notes

- There is no account system or authentication in this setup.
- The session-specific MCP URL functions as a bearer secret.
- URL imports allow public HTTP(S) targets only and block local/private network addresses.
- Imported pages are limited to 5 MB and a small redirect budget.
- Uploaded scanned PDFs are stored, but text extraction may require OCR in a future step.