---
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 |
