feat: add docker setup

This commit is contained in:
2026-03-01 23:03:54 +01:00
parent 3314443fb7
commit e9a087fc69
6 changed files with 80 additions and 7 deletions

6
.dockerignore Normal file
View File

@@ -0,0 +1,6 @@
*
!src/
!migrations/
!build.rs
!Cargo.toml
!Cargo.lock

21
Cargo.lock generated
View File

@@ -376,6 +376,16 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "errno"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
"windows-sys 0.60.2",
]
[[package]] [[package]]
name = "etcetera" name = "etcetera"
version = "0.8.0" version = "0.8.0"
@@ -1193,6 +1203,16 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "signal-hook-registry"
version = "1.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
dependencies = [
"errno",
"libc",
]
[[package]] [[package]]
name = "signature" name = "signature"
version = "2.2.0" version = "2.2.0"
@@ -1561,6 +1581,7 @@ dependencies = [
"libc", "libc",
"mio", "mio",
"pin-project-lite", "pin-project-lite",
"signal-hook-registry",
"socket2", "socket2",
"tokio-macros", "tokio-macros",
"windows-sys 0.61.2", "windows-sys 0.61.2",

View File

@@ -19,7 +19,7 @@ dirs = "6"
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
serde_json = "1" serde_json = "1"
sqlx = { version = "0.8", features = ["runtime-tokio", "sqlite", "chrono", "migrate"] } sqlx = { version = "0.8", features = ["runtime-tokio", "sqlite", "chrono", "migrate"] }
tokio = { version = "1", features = ["rt", "macros"] } tokio = { version = "1", features = ["rt", "macros", "io-std", "io-util", "signal"] }
[build-dependencies] [build-dependencies]
anyhow = "1" anyhow = "1"

9
Dockerfile Normal file
View File

@@ -0,0 +1,9 @@
FROM rust:1.85 AS build
WORKDIR /src
COPY . .
ENV SQLX_OFFLINE=true
RUN cargo build --release --bin todo-mcp
FROM gcr.io/distroless/cc-debian12
COPY --from=build /src/target/release/todo-mcp /
ENTRYPOINT ["/todo-mcp"]

View File

@@ -62,10 +62,18 @@ todo-mcp tags
todo-mcp purge todo-mcp purge
``` ```
## Docker
```sh
docker build -t local/todo-mcp .
docker run --rm -v todo-mcp-data:/data -e TODO_DB=/data/todos.db local/todo-mcp --help
```
Data is persisted in the `todo-mcp-data` volume. See [Usage](#usage) for more commands.
## Claude Setup ## Claude Setup
todo-mcp ships with an MCP server that lets Claude manage your todos. Add this ### Native
to your Claude MCP config:
```json ```json
{ {
@@ -78,5 +86,25 @@ to your Claude MCP config:
} }
``` ```
### Docker
Build the image first (see [Docker](#docker)), then add to your Claude MCP config:
```json
{
"mcpServers": {
"todo": {
"command": "docker",
"args": [
"run", "-i", "--rm",
"-v", "todo-mcp-data:/data",
"-e", "TODO_DB=/data/todos.db",
"local/todo-mcp", "mcp-serve"
]
}
}
}
```
This gives Claude access to all todo operations — adding, listing, completing, This gives Claude access to all todo operations — adding, listing, completing,
editing, removing, and purging. editing, removing, and purging.

View File

@@ -1,8 +1,9 @@
use std::io::{self, BufRead, Write}; use std::io::{self, Write};
use anyhow::Result; use anyhow::Result;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::{json, Value}; use serde_json::{json, Value};
use tokio::io::{AsyncBufReadExt, BufReader};
use crate::db::{self, ListFilters, Pool}; use crate::db::{self, ListFilters, Pool};
use crate::model::Priority; use crate::model::Priority;
@@ -233,11 +234,19 @@ async fn dispatch(pool: &Pool, name: &str, args: &Value) -> Value {
} }
pub async fn serve(pool: &Pool) -> Result<()> { pub async fn serve(pool: &Pool) -> Result<()> {
let stdin = io::stdin().lock(); let stdin = BufReader::new(tokio::io::stdin());
let mut stdout = io::stdout().lock(); let mut stdout = io::stdout().lock();
let mut lines = stdin.lines();
loop {
let line: String = tokio::select! {
line = lines.next_line() => match line? {
Some(line) => line,
None => break,
},
_ = tokio::signal::ctrl_c() => break,
};
for line in stdin.lines() {
let line = line?;
if line.is_empty() { if line.is_empty() {
continue; continue;
} }