Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
24da4b1
feat: add MCP server with search and photo tools
SmilyOrg Jun 13, 2026
443bb0b
fix: clean up MCP server code
SmilyOrg Jun 13, 2026
c862430
add /health endpoint to MCP server
SmilyOrg Jun 13, 2026
5c55021
tools: add mcp-test.sh harness for testing MCP tools
SmilyOrg Jun 13, 2026
8dc4e16
fix: default no-args shows help, fix BIN path resolution
SmilyOrg Jun 13, 2026
6268095
refactor: replace mcp-test.sh with unified agent-test.sh harness
SmilyOrg Jun 13, 2026
16443c5
fix: use $'...' quoting for color variables so ANSI escapes are inter…
SmilyOrg Jun 13, 2026
5e94265
format: unify output formatting across all log and print functions
SmilyOrg Jun 13, 2026
d5db22f
docs: note stderr/stdout separation in agent-test.sh output
SmilyOrg Jun 13, 2026
5d60286
fix(mcp): resolve errors, location bug, crop validation, and dead code
SmilyOrg Jun 13, 2026
bb67df9
Add get_photo_metadata tool; remove metadata from get_photo
SmilyOrg Jun 13, 2026
07dd15f
fix: discard Dependencies from GetInfos instead of treating as error
SmilyOrg Jun 13, 2026
107b458
fix(photo): populate Content for get_photo_metadata so MCP clients ca…
SmilyOrg Jun 16, 2026
443f47b
Merge branch 'main' into mcp
SmilyOrg Jun 16, 2026
fe9f191
refactor(mcp): derive base URL from request Host header with listener…
SmilyOrg Jun 20, 2026
f03b99c
Fix aspect ratio for previews
SmilyOrg Jun 21, 2026
e3fe039
Merge remote-tracking branch 'origin/main' into mcp
SmilyOrg Jun 21, 2026
9e47cab
docs(local-dev): clarify -- separator and verbose flag forms
SmilyOrg Jun 21, 2026
c52d8af
refactor: consolidate dev docs into local-dev skill, fix agent.sh ver…
SmilyOrg Jun 21, 2026
f3acbb2
refactor(local-dev): replace task agent with direct ./tools/agent.sh …
SmilyOrg Jun 21, 2026
0ef3136
local-dev: consolidate verbose flag and smoke test docs to reduce con…
SmilyOrg Jun 21, 2026
189ac33
docs: add MCP server overview documentation
SmilyOrg Jun 21, 2026
d0471ac
Optimize MCP tool response payloads by removing low-signal fields
SmilyOrg Jun 21, 2026
b22d521
Add preview_url to photo metadata — ~400px dynamically-resized image …
SmilyOrg Jun 21, 2026
b09c45b
Refine MCP tool descriptions — tighten noise, add sort options and se…
SmilyOrg Jun 21, 2026
a315216
thread configurable apiPrefix into MCP file URL construction
SmilyOrg Jun 21, 2026
5a45a6d
Fix MCP tool descriptions to prevent raw HTML output
SmilyOrg Jun 21, 2026
f2e275d
improve(mcp): add photo verification guidance to tool descriptions
SmilyOrg Jun 21, 2026
60e9b10
refactor(mcp): remove unused collection parameter from photo handlers
SmilyOrg Jun 21, 2026
dcb8684
fix: regenerate openapi code
SmilyOrg Jun 21, 2026
fa9b9af
docs: add PR #190 fix checklist from Copilot review comments
SmilyOrg Jun 21, 2026
c37b3d3
fix: export PHOTOFIELD_ADDRESS and PHOTOFIELD_DATA_DIR in server_start
SmilyOrg Jun 21, 2026
f986fb3
fix: clamp preview dimensions to 4096px max (DoS prevention)
SmilyOrg Jun 21, 2026
715685c
fix: remove top-level defer recover that suppressed panic stack traces
SmilyOrg Jun 21, 2026
2415ec0
docs: add health check endpoint documentation
SmilyOrg Jun 21, 2026
d8df0fa
refactor: consolidate New() to return a single Server instance
SmilyOrg Jun 21, 2026
60efb68
fix: correct JSON schema array length for sort type
SmilyOrg Jun 21, 2026
9455c52
fix: return error for unknown collection in events and search handlers
SmilyOrg Jun 21, 2026
3a74f58
fix: add nil checks in encodePhoto (crop params) and gatherPhotoMetad…
SmilyOrg Jun 21, 2026
bd72043
fix: face preview URL, wrap gatherPhotoMetadata and encodePhoto in de…
SmilyOrg Jun 21, 2026
a99f5e6
fix: clamp dimensions preserving aspect ratio, move health docs to lo…
SmilyOrg Jun 21, 2026
376c791
fix(mcp): move temp files to DATA_DIR and fix agent.sh bugs
SmilyOrg Aug 5, 2026
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
313 changes: 313 additions & 0 deletions .agents/skills/local-dev/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,313 @@
---
name: local-dev
description: >-
Run, test, and debug the photofield server locally. Use when building,
running, or testing the server, making API calls, calling MCP tools, or
inspecting runtime state. Covers server lifecycle via `./tools/agent.sh server`,
generic HTTP calls via `./tools/agent.sh api`, MCP tool calls, database inspection,
error debugging, and common fixes.
---

# Local Development — Running and Testing the Server

This skill covers building, running, testing, and debugging the photofield
server on your local machine.

All tool invocation, server management, and API calls go through
`./tools/agent.sh` — a unified harness that handles the server lifecycle,
generic HTTP calls, and MCP tool invocation with session management, SSE
parsing, and named-arg parsing.

## Verbose Flag

The harness accepts `--verbose`, `-v`, and `-V`. Any of these can be placed
directly after `./tools/agent.sh`:

```bash
./tools/agent.sh --verbose mcp call get_photo --file_id 1
./tools/agent.sh -v mcp call get_photo --file_id 1
./tools/agent.sh -V mcp call get_photo --file_id 1
```

Or avoid the flag entirely via environment variable:
```bash
AGT_VERBOSE=1 ./tools/agent.sh mcp call get_photo --file_id 1
```

## 1. Build

```bash
go build -o photofield .
```

Kill any old instance before rebuilding:

```bash
./tools/agent.sh server kill
```

## 2. Configuration

The server reads `data/configuration.yaml`. Without it, collections point to
empty directories.

**Minimal setup:**

```bash
mkdir -p data
cat > data/configuration.yaml <<EOF
collections:
- name: test
dirs:
- docs/assets
EOF
```

## 3. Server Lifecycle

Use `./tools/agent.sh server <command>` to manage the server process:

```bash
# Start (auto-detects if already running)
./tools/agent.sh server start

# Stop gracefully (uses PID file)
./tools/agent.sh server stop

# Restart
./tools/agent.sh server restart

# Check status (shows PID and port listeners)
./tools/agent.sh server status

# Aggressive kill (PID file + all port listeners including exiftool)
./tools/agent.sh server kill
```

**How it works:** `server start` launches the binary with `nohup` and writes a
PID file to `data/agent.pid`. It then polls the server endpoint until
ready (up to 30s). `server stop` reads the PID file and sends SIGTERM.
`server kill` sends SIGKILL to the PID and anything else listening on the port.

**Important:** The server does **not** auto-scan. After starting, run a scan:

```bash
./photofield -scan test
# or from another directory:
AGT_BIN=/path/to/photofield ./tools/agent.sh server start && ./photofield -scan test
```

The server listens on port `8080` by default (override with `AGT_PORT`).

**Note:** With `AGT_START=true` (the default), `mcp call`, `mcp quick`, and
`mcp shell` will auto-start the server if it is not already running. The
`server start`, `server stop`, `server restart`, `server status`, and
`server kill` commands manage the process regardless of `AGT_START`.

### Stale PID cleanup

If the server crashes without a clean shutdown, the PID file may become stale.
The harness auto-detects this: `server start` will clean up a dead PID file
and launch a new instance. `server status` also removes stale entries.

## 4. MCP Tool Calls

Use `./tools/agent.sh mcp` to call MCP tools. The harness handles the session
handshake (initialize + initialized notification), session ID extraction from
response headers, SSE response parsing, and named-arg to JSON conversion.

### Tool commands

```bash
# Call a tool with JSON args
./tools/agent.sh mcp call list_collections '{}'

# Call with named args (auto-detects --key val pairs)
./tools/agent.sh mcp call search_photos --query 'beach' --collection_id 'test' --limit 3

# Verbose mode — shows full raw JSON response
./tools/agent.sh --verbose mcp call get_photo --file_id 1 --w 200

# Smoke test (calls list_collections by default)
./tools/agent.sh mcp quick

# Smoke test with a specific tool
./tools/agent.sh mcp quick get_photo --file_id 1

# Interactive REPL
./tools/agent.sh mcp shell
```

### Argument modes

| Mode | Syntax |
|------|--------|
| JSON | `./tools/agent.sh mcp call <tool> '<json>'` |
| Named | `./tools/agent.sh mcp call <tool> --key val` |

Named args are auto-converted to JSON: numbers stay numeric, `true`/`false`
become booleans, `null` stays null, everything else is quoted as strings.
`agent.sh` auto-detects the named-arg mode when the first token starts with
`--`, so no explicit `-- --` boundary is ever needed.

### Output

Non-verbose mode shows a clean summary:
```
✓ list_collections — 22 items
✓ events — 19 events
✓ get_photo
```

Errors show with a red ✗ and the error message. Set `--verbose` (see Verbose Flag above) for full raw JSON on every call.

**Output streams:** The `log_*` helpers (`ℹ`, `▶`) go to stderr. Tool result
summaries (`✓`, `✗`) and raw JSON output go to stdout. This lets you pipe tool
results: `./tools/agent.sh mcp call list_collections '{}' | jq '.collections'`.

### From another directory

```bash
AGT_BIN=/path/to/photofield ./tools/agent.sh mcp call list_collections '{}'
AGT_URL=http://remote-host:9000/mcp ./tools/agent.sh mcp call list_collections '{}'
```

## 5. Generic API Calls

Use `./tools/agent.sh api` for arbitrary HTTP calls to any server endpoint. This is
useful for testing non-MCP routes, debugging, or calling endpoints that don't
have a dedicated tool.

```bash
# GET request (health check)
./tools/agent.sh api GET http://localhost:8080/health

# POST with JSON body
./tools/agent.sh api POST http://localhost:8080/api/collections \
'{"name":"my-collection","dirs":["/path/to/photos"]}'

# POST with named args (auto-constructs JSON body)
./tools/agent.sh api POST http://localhost:8080/api/collections \
--name my-collection --dirs /path/to/photos

# PUT / DELETE
./tools/agent.sh api DELETE http://localhost:8080/api/collections/test
```

The output shows the HTTP status code, pretty-printed JSON when possible, and
the response body (truncated if over 500 chars). Note: `AGT_VERBOSE=1` does
not currently change the truncation behavior for API calls.

### Health Check

The server exposes a health check endpoint at `/health`:

```bash
./tools/agent.sh api GET http://localhost:8080/health
```

Returns `{"status": "ok"}` when healthy.

## 6. Test the Server

### Tool tests

```bash
# Basic call
./tools/agent.sh mcp call get_photo --file_id 1

# Metadata-only call
./tools/agent.sh mcp call get_photo_metadata --file_id 1

# Error handling
./tools/agent.sh mcp call get_photo --file_id 999999

# Verbose debugging
./tools/agent.sh --verbose mcp call search_photos --query 'test' --collection_id 'test'
```

### API tests

```bash
# Check health
./tools/agent.sh api GET http://localhost:8080/health

# List collections via API (alternative to mcp call)
./tools/agent.sh api GET http://localhost:8080/api/collections
```

## 7. Inspect Errors and Crashes

The harness captures the server's **entire stdout and stderr** to
`data/agent.log` via `nohup`. Panics and errors appear in this log:

```bash
tail -100 data/agent.log
```

### Session warnings

If the server does not return a `Mcp-Session-Id` header, the harness continues
without one (some MCP servers don't require it). You will see a benign info
message: `No session ID (server may not require one)`.

| Symptom | Cause | Fix |
|---------|-------|-----|
| `cannot create context from nil parent` | Nil context passed to `WithTimeout` | Add `if ctx == nil { ctx = context.Background() }` in handler |
| `file not found: N` | Photo ID doesn't exist | Scan collection or check DB |
| Empty response data | Rendering panic | Check server log |
| Schema says all fields required | SDK infers from Go struct pointers | Use explicit `InputSchema` in tool registration |
| Server not responding | Old binary running | `./tools/agent.sh server kill` then rebuild |
| No photos found | Config points to empty dirs | Create `data/configuration.yaml` |

## 8. Inspect Runtime State

### Database

```bash
sqlite3 data/photofield.cache.db "SELECT id, width, height FROM infos ORDER BY id;"
sqlite3 data/photofield.cache.db ".tables"
```

### Via the harness

```bash
# List collections
./tools/agent.sh mcp call list_collections '{}'

# Check events for a collection
./tools/agent.sh mcp call events --collection_id 'test'

# Search photos
./tools/agent.sh mcp call search_photos --query 'faces' --collection_id 'test' --limit 5
```

## Environment Variables

| Variable | Default | Purpose |
|----------|---------|---------|
| `AGT_PORT` | `8080` | Server port |
| `AGT_BIN` | `./photofield` | Path to binary |
| `AGT_DATA_DIR` | `./data` | Data directory |
| `AGT_START` | `true` | Auto-start server if not running |
| `AGT_URL` | (derived) | Full MCP endpoint URL |
| `AGT_API_BASE` | `http://localhost:$PORT` | Base URL for API calls |
| `AGT_VERBOSE` | `0` | Verbose output |

## Quick Reference

| Command | Purpose |
|---------|---------|
| `go build -o photofield .` | Build the server |
| `./photofield -scan <name>` | Scan a collection |
| `./tools/agent.sh server start` | Start the server |
| `./tools/agent.sh server stop` | Stop the server |
| `./tools/agent.sh server restart` | Restart the server |
| `./tools/agent.sh server status` | Show PID/port status |
| `./tools/agent.sh server kill` | Kill server processes |
| `./tools/agent.sh mcp call <tool> <args>` | Call an MCP tool |
| `./tools/agent.sh mcp quick [tool]` | Smoke test |
| `./tools/agent.sh mcp shell` | Interactive REPL |
| `./tools/agent.sh api <method> <url> [body]` | Generic HTTP call |
| `./tools/agent.sh --verbose <cmd>` | Verbose output (see Verbose Flag above for `-v`/`-V`/env var) |
| `sqlite3 data/photofield.cache.db ...` | Inspect the database |
9 changes: 9 additions & 0 deletions .mcp.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"mcpServers": {
"photofield": {
"url": "http://localhost:8080/mcp",
"transport": "http",
"directTools": true
}
}
}
1 change: 1 addition & 0 deletions Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ tasks:
- "echo ' task docs: Run the docs frontend in development mode'"
- "echo ' task e2e: Run end-to-end tests in watch mode'"
- "echo ' task release:local: Build, package, and create a local Docker image'"

silent: true

commit:analyze:
Expand Down
Loading
Loading