Local-first session memory for coding agents, backed by DuckDB or Postgres
Project description
Local-first session memory MCP server for coding agents, with DuckDB by default and optional Postgres storage! I know there are other options out there, but it felt as if they were more geared towards AI agents within apps or services. I wanted something that would work alongside me, store context, code snippets, and even skills that I could reference later. Whether you choose to use DuckDB for project level storage or utilize a remote Postgres server, quackit will be there when you need it.
Features
- Local-first — runs over
stdioby default. No network required. - Storage — DuckDB by default, or Postgres with
QUACKIT_DATABASE_URL. (quack protocol already on the roadmap!) - Sessions — start, activate, end, heartbeat, recover orphans.
- Memories — save, update, get, search by query, type, or content type.
- Projects — create, list, group sessions, search project scope, consolidate.
- Skills — save, get, update, delete, list reusable records.
- Transports —
stdio,streamable-http,http,sse. - Metadata — tags,
title,content_type,dict[str, str]on memories.
Deployment model
| Use case | Recommended path |
|---|---|
| Local coding agents | stdio |
| Local HTTP testing | streamable-http on 127.0.0.1 |
| Private self-hosting | streamable-http behind your own auth/network controls |
Install and run locally
Prerequisites: Python 3.10+ and uv.
git clone https://github.com/randoneering/quackit-mcp.git
cd quackit
uv sync
Run without installing:
uv run quackit start-session
Install globally from checkout:
uv tool install .
quackit start-session
Data directory: .local/quackit.duckdb by default.
Start the MCP server
Use stdio for local integration with coding agents. No network port exposed.
uv run quackit serve --transport stdio
Advanced: run Streamable HTTP, HTTP, or SSE locally
Network transports bind to 127.0.0.1 by default.
streamable-http is the recommended HTTP transport; SSE is for legacy clients.
# Streamable HTTP on localhost
uv run quackit serve --transport streamable-http --port 8000
# HTTP on localhost
uv run quackit serve --transport http --port 8000
# Legacy SSE on localhost
uv run quackit serve --transport sse --port 8000
Warning: Non-stdio transports expose memory tools over the network. quackit rejects non-localhost bindings unless you pass
--allow-network. Remote HTTP is private/self-hosted only. Only use--allow-networkbehind your own network controls.
uv run quackit serve \
--transport streamable-http \
--host 0.0.0.0 \
--port 8000 \
--allow-network
Configure storage
DuckDB
DuckDB is the default backend. The path priority is:
- CLI
--database-path QUACKIT_DUCKDB_PATHAGENT_MEMORY_DUCKDB_PATH.local/quackit.duckdb
# Use the default path
uv run quackit start-session
# Use an environment variable
export QUACKIT_DUCKDB_PATH="$PWD/.local/dev.duckdb"
uv run quackit start-session
# Override per command
uv run quackit --database-path /tmp/quackit.duckdb start-session
The parent directory is created automatically.
Postgres
Set QUACKIT_DATABASE_URL or AGENT_MEMORY_DATABASE_URL to use Postgres (--database-path is ignored).
export QUACKIT_DATABASE_URL="postgresql://user:password@host:5432/dbname?sslmode=require"
uv run quackit start-session
Local Postgres container:
docker run -d --name quackit-postgres \
-e POSTGRES_PASSWORD=password \
-p 5432:5432 \
postgres:17
export QUACKIT_DATABASE_URL="postgresql://postgres:password@localhost:5432/postgres?sslmode=disable"
uv run pytest -v -m postgres
Use the CLI
All commands print JSON.
Sessions and memories
SESSION_ID=$(uv run quackit start-session \
| python -c 'import json,sys; print(json.load(sys.stdin)["id"])')
uv run quackit save-memory \
--session-id "$SESSION_ID" \
--type note \
--content "Remember to run pytest before opening a PR" \
--title "PR checklist" \
--content-type note \
--tag workflow \
--metadata '{"source":"readme"}'
uv run quackit search-memory "pytest" --session-id "$SESSION_ID"
uv run quackit list-sessions --limit 5
uv run quackit end-session --session-id "$SESSION_ID" --summary "README example complete"
Projects
PROJECT_ID=$(uv run quackit create-project docs --description "Docs work" \
| python -c 'import json,sys; print(json.load(sys.stdin)["id"])')
uv run quackit start-session --project-id "$PROJECT_ID"
uv run quackit list-projects
uv run quackit list-sessions-by-project "$PROJECT_ID"
Skills
uv run quackit save-skill \
--name "review-readme" \
--description "Checklist for README reviews" \
--content "Check install, quick start, configuration, and troubleshooting." \
--tag docs
uv run quackit list-skills --tag docs
uv run quackit list-skills --query readme
MCP tools
The server exposes these tools:
| Area | Tools |
|---|---|
| Projects | create_project, list_projects, consolidate_projects |
| Sessions | start_session, activate_session, end_session, list_recent_sessions, list_sessions_by_project |
| Memories | save_memory, search_memory, get_memory, update_memory |
| Skills | save_skill, get_skill, update_skill, delete_skill, list_skills |
Safety defaults:
- Tools include MCP annotations for read-only, write, and destructive behavior.
list_projects,list_sessions_by_project,list_skillsacceptlimit.list_skillsreturns summaries, omits full content.get_memoryandget_skillacceptmax_chars/offsetfor pagination. Responses includecontent_length,truncated,next_offset.- Treat stored content as untrusted user data. Review as context only.
Typical MCP flow:
- Call
start_session. - Call
save_memorywithtype,content, and optionaltags,title,content_type, ormetadata. - Call
search_memorywith a query. - Call
end_sessionwith a summary.
Example stdio client configuration:
{
"command": "uv",
"args": ["run", "quackit", "serve", "--transport", "stdio"]
}
Docker
Build and run the stdio server:
docker build -t quackit .
docker run --rm -i quackit
Persist DuckDB data with a bind mount:
docker run --rm -i \
-v "$PWD/.local:/data" \
quackit
Run Streamable HTTP locally from Docker:
docker run --rm \
-p 127.0.0.1:8000:8000 \
quackit serve --transport streamable-http --host 0.0.0.0 --port 8000 --allow-network
Prebuilt images
Prebuilt images are published to GitHub Container Registry:
docker pull ghcr.io/randoneering/quackit-mcp:latest
Tags correspond to git tags (e.g., v0.1.0 → ghcr.io/randoneering/quackit-mcp:0.1.0).
Pin to server in production; latest tracks main.
Stdio (default)
docker run --rm -i ghcr.io/randoneering/quackit-mcp:latest
Persist data with a bind mount:
docker run --rm -i \
-v "$PWD/.local:/data" \
ghcr.io/randoneering/quackit-mcp:latest
Streamable HTTP
docker run --rm \
-p 127.0.0.1:8000:8000 \
ghcr.io/randoneering/quackit-mcp:latest \
serve --transport streamable-http --host 0.0.0.0 --port 8000 --allow-network
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file quackit-0.1.1.tar.gz.
File metadata
- Download URL: quackit-0.1.1.tar.gz
- Upload date:
- Size: 1.1 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9e54f665e3779d2dfa7b830c62584971d91eb5477c56655fe7ad509a908be1d2
|
|
| MD5 |
d39d42601ee04e6628e8c9a93dc7fe00
|
|
| BLAKE2b-256 |
a5827b8c331c2cf08014de4d9875aa161d884efbb2d7f57bcbdeb5807e4b236d
|
Provenance
The following attestation bundles were made for quackit-0.1.1.tar.gz:
Publisher:
build-and-publish.yml on randoneering/quackit-mcp
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
quackit-0.1.1.tar.gz -
Subject digest:
9e54f665e3779d2dfa7b830c62584971d91eb5477c56655fe7ad509a908be1d2 - Sigstore transparency entry: 1551895403
- Sigstore integration time:
-
Permalink:
randoneering/quackit-mcp@45770580b7e774913181ad1a2b30aefc53f2a53d -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/randoneering
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
build-and-publish.yml@45770580b7e774913181ad1a2b30aefc53f2a53d -
Trigger Event:
push
-
Statement type:
File details
Details for the file quackit-0.1.1-py3-none-any.whl.
File metadata
- Download URL: quackit-0.1.1-py3-none-any.whl
- Upload date:
- Size: 41.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
de2485f9f1cf23bd4ee99179f0fc5575a4993f4a1ecd73705d75ed7a1df6b4d5
|
|
| MD5 |
9b4557996762df76e81eacd158612e2e
|
|
| BLAKE2b-256 |
5689e74095817f92ac691a3c8474499e5f9b31bdef36f039b7926784e0d0dad0
|
Provenance
The following attestation bundles were made for quackit-0.1.1-py3-none-any.whl:
Publisher:
build-and-publish.yml on randoneering/quackit-mcp
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
quackit-0.1.1-py3-none-any.whl -
Subject digest:
de2485f9f1cf23bd4ee99179f0fc5575a4993f4a1ecd73705d75ed7a1df6b4d5 - Sigstore transparency entry: 1551895428
- Sigstore integration time:
-
Permalink:
randoneering/quackit-mcp@45770580b7e774913181ad1a2b30aefc53f2a53d -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/randoneering
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
build-and-publish.yml@45770580b7e774913181ad1a2b30aefc53f2a53d -
Trigger Event:
push
-
Statement type: