feat: added AGENTS.md
This commit is contained in:
1 parent
4473a6e3d0
commit
1ecf468140
2 files changed
+198
-30
No files matched your search
@@ -0,0 +1,123 @@
|
||||
# St-Pierre — Flashcard MCP Server
|
||||
|
||||
MCP server that stores and retrieves flashcards using SQLite for persistence,
|
||||
Qdrant for vector search, and OpenAI-compatible embeddings (LiteLLM proxy).
|
||||
|
||||
## Tech Stack
|
||||
|
||||
- Go (1.26), flat package `main`
|
||||
- MCP server: `github.com/mark3labs/mcp-go`
|
||||
- SQLite: `modernc.org/sqlite` (pure Go, no CGo)
|
||||
- Migrations: `github.com/pressly/goose/v3` (embedded via `//go:embed migrations/*.sql`)
|
||||
- Vector DB: Qdrant via `github.com/qdrant/go-client`
|
||||
- Embeddings: OpenAI-compatible API (LiteLLM at `https://llm.aviron.ai`)
|
||||
- Logging: `go.uber.org/zap`
|
||||
- Deployed on **enlil** (NixOS server) at `https://st-pierre.home.all-dressed-programming.com/mcp`
|
||||
|
||||
## Repository Structure
|
||||
|
||||
```
|
||||
*.go Flat package main (no cmd/ layout)
|
||||
main.go Entry point, MCP server wiring, auth middleware
|
||||
api.go Tool definitions (store_flashcard, search_flashcard) + vote
|
||||
sqlite.go SQLite store — CRUD, search, vote, migration runner
|
||||
fts5.go FTS5 full-text search queries
|
||||
qdrant.go Qdrant gRPC client — upsert, search, collection management
|
||||
embed.go OpenAI-compatible embeddings client
|
||||
search.go Search engine that combines SQLite + Qdrant results
|
||||
types.go Go types (Flashcard, searchRequest, etc.)
|
||||
migrations/ Goose SQL migrations (embedded)
|
||||
public/ Frontend (served via embed.FS or disk in dev mode)
|
||||
public_fs.go Production: embed public/ (no build tag)
|
||||
public_fs_dev.go Dev mode: serve public/ from disk (build tag: dev)
|
||||
flake.nix Nix build + NixOS module
|
||||
```
|
||||
|
||||
## Build & Run
|
||||
|
||||
```bash
|
||||
# Build
|
||||
nix build
|
||||
|
||||
# Dev (hot-reload frontend)
|
||||
go build -tags dev -o st-pierre .
|
||||
./st-pierre
|
||||
|
||||
# Run directly
|
||||
export OPENAI_API_KEY=sk-... MCP_TOKEN=my-token
|
||||
./st-pierre
|
||||
```
|
||||
|
||||
## Conventions
|
||||
|
||||
### Goose Migrations
|
||||
|
||||
**NEVER modify an applied migration file.** Goose v3 tracks checksums in
|
||||
`goose_db_version`. Modifying an applied migration causes "checksum mismatch".
|
||||
Always add a new migration with a higher version number. If you must recover
|
||||
a corrupted DB: `DELETE FROM goose_db_version WHERE version_id = N;` or
|
||||
`DROP TABLE goose_db_version;` to reset entirely.
|
||||
|
||||
### SQLite
|
||||
|
||||
- `modernc.org/sqlite` — pure Go, no CGo required
|
||||
- Migrations embedded via `//go:embed migrations/*.sql`
|
||||
- `INTEGER PRIMARY KEY` for auto-increment IDs
|
||||
- `SetMaxOpenConns(1)` mandatory (single-writer SQLite)
|
||||
- Append-only design — never UPDATE rows
|
||||
- Default timestamps: `datetime('now')`
|
||||
- Nullable columns use `sql.NullString`
|
||||
- JSON arrays stored as text columns
|
||||
|
||||
### Nix
|
||||
|
||||
After any `go.mod` or `go.sum` change, update `vendorHash` in `flake.nix`
|
||||
with the hash from a failed build attempt. Never commit go.mod/go.sum changes
|
||||
without updating `vendorHash`.
|
||||
|
||||
`lib.mkIf` is incompatible with the `//` merge operator — it can silently
|
||||
drop attributes. Use `lib.optionalAttrs` instead when merging with `//`.
|
||||
|
||||
## Architecture Notes
|
||||
|
||||
### MCP path
|
||||
|
||||
The MCP server is served at `/mcp` via `http.ServeMux`, not at root. CORS
|
||||
wraps the entire mux. The `result/` symlink created by nix builds is in
|
||||
`.gitignore`.
|
||||
|
||||
### Embedding URL normalization
|
||||
|
||||
`EMBEDDING_BASE_URL` strips any trailing `/v1` and always appends
|
||||
`/v1/embeddings`. A bare host like `https://llm.aviron.ai` correctly becomes
|
||||
`https://llm.aviron.ai/v1/embeddings`. The default is `https://api.openai.com`.
|
||||
|
||||
### Dev build tag
|
||||
|
||||
Two files control how `public/` is served:
|
||||
|
||||
- `public_fs.go` (`//go:build !dev`) — embeds `public/` via `embed.FS`
|
||||
- `public_fs_dev.go` (`//go:build dev`) — serves from disk via `http.Dir("public")`
|
||||
|
||||
Build with `go build -tags dev` for frontend hot-reload during development.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### SQLite readonly on NixOS
|
||||
|
||||
`SQLITE_READONLY_CANTINIT (1032)` means the database file is owned by root
|
||||
due to `createHome` + `StateDirectory` interaction. Fix: add `chown` in the
|
||||
systemd unit `preStart`. Also verify the directory is writable with a
|
||||
`SELECT 1` ping in `newSqliteStore`.
|
||||
|
||||
### Embedding API 404
|
||||
|
||||
Empty 404 from embedding API means the URL path is wrong. Verify
|
||||
`EMBEDDING_BASE_URL` is set correctly — the server normalizes the base URL
|
||||
and appends `/v1/embeddings`. For LiteLLM proxy, leave it bare:
|
||||
`EMBEDDING_BASE_URL=https://llm.aviron.ai`.
|
||||
|
||||
### MCP endpoint
|
||||
|
||||
The full endpoint is `https://st-pierre.home.all-dressed-programming.com/mcp`.
|
||||
Auth via `Authorization: Bearer <token>` header. Token set via `MCP_TOKEN` env var.
|
||||
Reference in new issue
Block a user