shotlog-mcp 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,8 @@
1
+ __pycache__/
2
+ *.pyc
3
+ .venv/
4
+ dist/
5
+ .pytest_cache/
6
+ *.png
7
+ manifest*.json
8
+ report.md
@@ -0,0 +1,76 @@
1
+ # Lanzamiento de shotlog-mcp (notas privadas, no se suben al repo)
2
+
3
+ Orden: GitHub → PyPI → MCP Registry → Glama + awesome-mcp-servers → LinkedIn.
4
+
5
+ ## 1. GitHub
6
+
7
+ ```bash
8
+ cd ~/shotlog-mcp && gh repo create shotlog-mcp --public --source . --push \
9
+ --description "MCP server: let your AI assistant capture macOS windows into a numbered, captioned evidence log for lab reports, QA and docs."
10
+ gh repo edit --add-topic mcp,mcp-server,model-context-protocol,screenshot,macos,claude,python
11
+ ```
12
+
13
+ ## 2. PyPI (lo haces tú)
14
+
15
+ 1. Crea una cuenta en https://pypi.org/account/register/ y verifica el correo.
16
+ 2. Activa la 2FA (es obligatoria para publicar): *Account settings → Two factor authentication*.
17
+ 3. Crea un token: *Account settings → API tokens → Add API token*. En el alcance elige **"Entire account"**, porque el proyecto todavía no existe. Copia el token `pypi-…`; solo se muestra una vez.
18
+ 4. En **tu propia terminal** (no con `!` en Claude, para que el token no quede en la conversación):
19
+ ```bash
20
+ cd ~/shotlog-mcp
21
+ rm -rf dist && uv build
22
+ uv publish --token pypi-TU_TOKEN
23
+ ```
24
+ 5. Comprueba que se publicó:
25
+ - https://pypi.org/project/shotlog-mcp/ debe mostrar el README.
26
+ - `claude mcp add shotlog -- uvx shotlog-mcp` y luego `/mcp` en Claude Code: debe aparecer *connected*.
27
+ 6. Seguridad: borra el token "Entire account" y crea uno limitado al proyecto `shotlog-mcp` para las próximas versiones.
28
+
29
+ Para cada versión nueva, sube el número en `pyproject.toml`, en `src/shotlog_mcp/__init__.py` y en las 2 entradas de `server.json`. Después ejecuta `uv build && uv publish` y crea un tag `git tag v0.1.1 && git push --tags`.
30
+
31
+ ## 3. MCP Registry oficial (solo cuando ya esté en PyPI)
32
+
33
+ ```bash
34
+ cd ~/shotlog-mcp
35
+ mcp-publisher login github # te muestra un código; lo pegas en github.com/login/device
36
+ mcp-publisher publish
37
+ ```
38
+ Para verificar: `curl "https://registry.modelcontextprotocol.io/v0/servers?search=shotlog"`
39
+
40
+ ## 4. Glama + awesome-mcp-servers
41
+
42
+ 1. Entra en https://glama.ai/mcp/servers con GitHub y usa *Add server* con la URL del repo. Casi todas las entradas de la lista llevan su insignia de Glama.
43
+ 2. Agrega un PR a https://github.com/punkpeye/awesome-mcp-servers en la sección **🖥️ OS Automation**, en orden alfabético:
44
+
45
+ ```markdown
46
+ - [Marlon-Trujillo-Jaramillo/shotlog-mcp](https://github.com/Marlon-Trujillo-Jaramillo/shotlog-mcp) [![Marlon-Trujillo-Jaramillo/shotlog-mcp MCP server](https://glama.ai/mcp/servers/Marlon-Trujillo-Jaramillo/shotlog-mcp/badges/score.svg)](https://glama.ai/mcp/servers/Marlon-Trujillo-Jaramillo/shotlog-mcp) 🐍 🏠 🍎 - Captures specific macOS windows into a numbered, captioned screenshot log (activity, command, result), pulls screenshots from other machines over SCP and exports a Markdown report for lab reports, QA evidence and docs. `uvx shotlog-mcp`
47
+ ```
48
+
49
+ Título del PR: `Add shotlog-mcp (OS Automation)`
50
+
51
+ ## 5. Post para LinkedIn (español)
52
+
53
+ > En mi laboratorio de Sistemas Operativos cada paso necesitaba una captura de pantalla como evidencia, y trabajaba a la vez en un Mac y en un portátil con Windows. Tomarlas, renombrarlas, ordenarlas y describirlas a mano me costaba más que el laboratorio mismo.
54
+ >
55
+ > Así que construí **shotlog-mcp**, un servidor MCP de código abierto que le permite a un asistente de IA (Claude, por ejemplo):
56
+ > 📸 capturar una ventana concreta de macOS, no toda la pantalla
57
+ > 🔢 numerarla y guardarle una ficha: actividad, comando y resultado
58
+ > 💻 traer por SSH las capturas del otro equipo, en la misma serie
59
+ > 📄 generar el informe en Markdown/PDF con todo en orden
60
+ >
61
+ > Ahora solo le digo: "ejecuta ssh -V, toma la captura y regístrala como paso 3".
62
+ >
63
+ > Sirve igual para evidencias de QA, tutoriales y reportes de bugs. Es MIT y se instala con una línea:
64
+ > `claude mcp add shotlog -- uvx shotlog-mcp`
65
+ >
66
+ > 🔗 github.com/Marlon-Trujillo-Jaramillo/shotlog-mcp
67
+ >
68
+ > Se aceptan feedback, estrellas ⭐ y PRs (el siguiente paso es el soporte para Windows y Linux).
69
+ >
70
+ > #MCP #IA #Python #OpenSource #macOS #ClaudeAI
71
+
72
+ Adjunta `docs/demo.gif`. En LinkedIn, un GIF o video consigue mucho más alcance que un enlace solo.
73
+
74
+ ### Versión corta para X / Reddit (r/ClaudeAI, r/mcp)
75
+
76
+ > I built shotlog-mcp: an MCP server that lets your AI assistant screenshot a specific macOS window, number it, caption it (activity/command/result) and export a report. It was born from an OS lab where every step needed evidence. MIT, `uvx shotlog-mcp`. github.com/Marlon-Trujillo-Jaramillo/shotlog-mcp
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Marlon Trujillo Jaramillo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,93 @@
1
+ Metadata-Version: 2.5
2
+ Name: shotlog-mcp
3
+ Version: 0.1.0
4
+ Summary: MCP server that captures macOS windows into a numbered, captioned evidence log for lab reports, QA and docs.
5
+ Project-URL: Repository, https://github.com/Marlon-Trujillo-Jaramillo/shotlog-mcp
6
+ Author: Marlon Trujillo Jaramillo
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Keywords: claude,documentation,macos,mcp,model-context-protocol,qa,screenshot
10
+ Classifier: Operating System :: MacOS
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Topic :: Multimedia :: Graphics :: Capture :: Screen Capture
13
+ Requires-Python: >=3.11
14
+ Requires-Dist: mcp<2,>=1.2
15
+ Requires-Dist: pyobjc-framework-quartz; sys_platform == 'darwin'
16
+ Description-Content-Type: text/markdown
17
+
18
+ # shotlog-mcp
19
+
20
+ **Let your AI assistant take the screenshots for your report.**
21
+
22
+ An [MCP](https://modelcontextprotocol.io) server for macOS. It captures a specific window, saves it with a sequential number and a caption (activity, command, result), and puts everything together into a Markdown report you can turn into a PDF.
23
+
24
+ It was built for a university OS lab where every step had to be backed by a screenshot, on a Mac and a Windows laptop at the same time. It works the same way for QA evidence, tutorials, runbooks and bug reports.
25
+
26
+ [Leer en español](README.es.md)
27
+
28
+ ![shotlog demo: the assistant captures a Terminal window as step 1 and exports the report](https://raw.githubusercontent.com/Marlon-Trujillo-Jaramillo/shotlog-mcp/main/docs/demo.gif)
29
+
30
+ <!-- mcp-name: io.github.Marlon-Trujillo-Jaramillo/shotlog-mcp -->
31
+
32
+ ## What it does
33
+
34
+ | Tool | Description |
35
+ |---|---|
36
+ | `list_windows` | Lists the visible windows (app, title, size). |
37
+ | `save_screenshot` | Captures **one** window, picked by app and/or title (front-most by default), and saves it as `001_name.png` together with its caption. |
38
+ | `update_caption` | Fills in or fixes the caption afterwards. |
39
+ | `list_screenshots` | Shows the log in order. |
40
+ | `pull_remote_screenshots` | Fetches new screenshots from another machine (e.g. Windows' `Pictures\Screenshots`) over SCP and numbers them in the same sequence. |
41
+ | `export_report` | Writes `report.md`: every screenshot with its caption, in order. |
42
+
43
+ Then just ask: *"Run `ssh -V` in the terminal, take a screenshot of it and caption it as step 3."*
44
+
45
+ ## Install
46
+
47
+ Requires macOS, Python 3.11+ and [uv](https://docs.astral.sh/uv/).
48
+
49
+ **Claude Code**
50
+
51
+ ```bash
52
+ claude mcp add shotlog -- uvx shotlog-mcp
53
+ ```
54
+
55
+ **Claude Desktop / other MCP clients**
56
+
57
+ ```json
58
+ {
59
+ "mcpServers": {
60
+ "shotlog": { "command": "uvx", "args": ["shotlog-mcp"] }
61
+ }
62
+ }
63
+ ```
64
+
65
+ The first capture needs the **Screen Recording** permission for the app that runs the server (Terminal, iTerm, VS Code, Claude…): *System Settings → Privacy & Security → Screen Recording*.
66
+
67
+ ## Configuration
68
+
69
+ | Variable | Default | |
70
+ |---|---|---|
71
+ | `SHOTLOG_DIR` | `~/shotlog` | Where screenshots, `manifest.json` and `report.md` are saved. |
72
+
73
+ Use one folder per project: `claude mcp add shotlog -e SHOTLOG_DIR=~/lab3/shots -- uvx shotlog-mcp`
74
+
75
+ ### Remote screenshots (optional)
76
+
77
+ To use `pull_remote_screenshots`, the other machine needs an SSH server (on Windows: *Settings → Optional features → OpenSSH Server*) and key-based login from your Mac, so that no password is asked for.
78
+
79
+ ## Report to PDF
80
+
81
+ ```bash
82
+ cd ~/shotlog && pandoc report.md -o report.pdf
83
+ ```
84
+
85
+ ## Development
86
+
87
+ ```bash
88
+ uv run --group dev pytest
89
+ ```
90
+
91
+ ## License
92
+
93
+ MIT
@@ -0,0 +1,42 @@
1
+ # shotlog-mcp
2
+
3
+ **Que tu asistente de IA tome las capturas de tu informe.**
4
+
5
+ Es un servidor [MCP](https://modelcontextprotocol.io) para macOS. Captura una ventana concreta, la guarda con un número consecutivo y una ficha (actividad, comando, resultado), y junta todo en un informe Markdown que puedes convertir a PDF.
6
+
7
+ Nació en un laboratorio de Sistemas Operativos donde cada paso tenía que respaldarse con una captura, trabajando a la vez en un Mac y en un portátil Windows. También sirve para evidencias de QA, tutoriales, runbooks y reportes de bugs.
8
+
9
+ [Read in English](README.md)
10
+
11
+ ![demo](docs/demo.gif)
12
+
13
+ ## Herramientas
14
+
15
+ | Herramienta | Qué hace |
16
+ |---|---|
17
+ | `list_windows` | Lista las ventanas visibles. |
18
+ | `save_screenshot` | Captura **una** ventana, elegida por app o título (si no indicas nada, la que está al frente), y la guarda como `001_nombre.png` con su ficha. |
19
+ | `update_caption` | Completa o corrige la ficha después. |
20
+ | `list_screenshots` | Muestra el registro en orden. |
21
+ | `pull_remote_screenshots` | Trae por SCP las capturas nuevas de otro equipo (por ejemplo `Imágenes\Capturas` en Windows) y las numera en la misma serie. |
22
+ | `export_report` | Genera `report.md` con todas las capturas y sus fichas, en orden. |
23
+
24
+ Luego solo pides: *"Ejecuta `ssh -V` en la terminal, toma una captura y ponle como ficha el paso 3."*
25
+
26
+ ## Instalación
27
+
28
+ Necesitas macOS, Python 3.11+ y [uv](https://docs.astral.sh/uv/).
29
+
30
+ ```bash
31
+ claude mcp add shotlog -- uvx shotlog-mcp
32
+ ```
33
+
34
+ Para la primera captura hay que dar el permiso de **Grabación de pantalla** a la app que ejecuta el servidor: *Ajustes del Sistema → Privacidad y seguridad → Grabación de pantalla*.
35
+
36
+ La carpeta de destino se cambia con `SHOTLOG_DIR` (por defecto es `~/shotlog`).
37
+
38
+ Para pasar el informe a PDF: `pandoc report.md -o report.pdf`
39
+
40
+ ## Licencia
41
+
42
+ MIT
@@ -0,0 +1,76 @@
1
+ # shotlog-mcp
2
+
3
+ **Let your AI assistant take the screenshots for your report.**
4
+
5
+ An [MCP](https://modelcontextprotocol.io) server for macOS. It captures a specific window, saves it with a sequential number and a caption (activity, command, result), and puts everything together into a Markdown report you can turn into a PDF.
6
+
7
+ It was built for a university OS lab where every step had to be backed by a screenshot, on a Mac and a Windows laptop at the same time. It works the same way for QA evidence, tutorials, runbooks and bug reports.
8
+
9
+ [Leer en español](README.es.md)
10
+
11
+ ![shotlog demo: the assistant captures a Terminal window as step 1 and exports the report](https://raw.githubusercontent.com/Marlon-Trujillo-Jaramillo/shotlog-mcp/main/docs/demo.gif)
12
+
13
+ <!-- mcp-name: io.github.Marlon-Trujillo-Jaramillo/shotlog-mcp -->
14
+
15
+ ## What it does
16
+
17
+ | Tool | Description |
18
+ |---|---|
19
+ | `list_windows` | Lists the visible windows (app, title, size). |
20
+ | `save_screenshot` | Captures **one** window, picked by app and/or title (front-most by default), and saves it as `001_name.png` together with its caption. |
21
+ | `update_caption` | Fills in or fixes the caption afterwards. |
22
+ | `list_screenshots` | Shows the log in order. |
23
+ | `pull_remote_screenshots` | Fetches new screenshots from another machine (e.g. Windows' `Pictures\Screenshots`) over SCP and numbers them in the same sequence. |
24
+ | `export_report` | Writes `report.md`: every screenshot with its caption, in order. |
25
+
26
+ Then just ask: *"Run `ssh -V` in the terminal, take a screenshot of it and caption it as step 3."*
27
+
28
+ ## Install
29
+
30
+ Requires macOS, Python 3.11+ and [uv](https://docs.astral.sh/uv/).
31
+
32
+ **Claude Code**
33
+
34
+ ```bash
35
+ claude mcp add shotlog -- uvx shotlog-mcp
36
+ ```
37
+
38
+ **Claude Desktop / other MCP clients**
39
+
40
+ ```json
41
+ {
42
+ "mcpServers": {
43
+ "shotlog": { "command": "uvx", "args": ["shotlog-mcp"] }
44
+ }
45
+ }
46
+ ```
47
+
48
+ The first capture needs the **Screen Recording** permission for the app that runs the server (Terminal, iTerm, VS Code, Claude…): *System Settings → Privacy & Security → Screen Recording*.
49
+
50
+ ## Configuration
51
+
52
+ | Variable | Default | |
53
+ |---|---|---|
54
+ | `SHOTLOG_DIR` | `~/shotlog` | Where screenshots, `manifest.json` and `report.md` are saved. |
55
+
56
+ Use one folder per project: `claude mcp add shotlog -e SHOTLOG_DIR=~/lab3/shots -- uvx shotlog-mcp`
57
+
58
+ ### Remote screenshots (optional)
59
+
60
+ To use `pull_remote_screenshots`, the other machine needs an SSH server (on Windows: *Settings → Optional features → OpenSSH Server*) and key-based login from your Mac, so that no password is asked for.
61
+
62
+ ## Report to PDF
63
+
64
+ ```bash
65
+ cd ~/shotlog && pandoc report.md -o report.pdf
66
+ ```
67
+
68
+ ## Development
69
+
70
+ ```bash
71
+ uv run --group dev pytest
72
+ ```
73
+
74
+ ## License
75
+
76
+ MIT
@@ -0,0 +1,31 @@
1
+ """Drives the real shotlog server over MCP, the way an AI client would. Used to record the README GIF."""
2
+ import asyncio, json, os, sys, time
3
+ from mcp import ClientSession, StdioServerParameters
4
+ from mcp.client.stdio import stdio_client
5
+
6
+ C, D, R = "\033[36m", "\033[2m", "\033[0m"
7
+
8
+ def say(text):
9
+ print(f"\n{C}> {text}{R}"); time.sleep(0.8)
10
+
11
+ def show(result, keys=None):
12
+ data = json.loads(result.content[0].text)
13
+ if keys:
14
+ data = {k: data[k] for k in keys if k in data}
15
+ print(D + json.dumps(data, ensure_ascii=False, indent=2) + R); time.sleep(1.6)
16
+
17
+ async def main():
18
+ params = StdioServerParameters(command="uv", args=["run", "-q", "shotlog-mcp"],
19
+ env={**os.environ}, cwd=sys.argv[1])
20
+ async with stdio_client(params, errlog=open(os.devnull, "w")) as (r, w):
21
+ async with ClientSession(r, w) as s:
22
+ await s.initialize()
23
+ say('save_screenshot(name="step1_ssh_version", app="Terminal", title_contains="shotlog-demo", ...)')
24
+ show(await s.call_tool("save_screenshot", {
25
+ "name": "step1_ssh_version", "app": "Terminal", "title_contains": "shotlog-demo",
26
+ "activity": "Step 1 - Check the SSH client", "command": "ssh -V",
27
+ "result": "OpenSSH client is installed"}), ["file", "source", "activity", "command", "result"])
28
+ say("export_report(title=\"OS Lab - SSH\")")
29
+ show(await s.call_tool("export_report", {"title": "OS Lab - SSH"}))
30
+
31
+ asyncio.run(main())
@@ -0,0 +1,21 @@
1
+ Output docs/demo-terminal.gif
2
+ # Recreate the README GIF: run demo/window.sh in a separate Terminal window, then `vhs demo/demo.tape`.
3
+ Set Shell zsh
4
+ Set FontSize 15
5
+ Set Width 1100
6
+ Set Height 560
7
+ Set Theme "Catppuccin Mocha"
8
+ Set TypingSpeed 35ms
9
+ Hide
10
+ Type "setopt interactivecomments; export SHOTLOG_DIR=~/os-lab PS1='$ ' && cd ~/shotlog-mcp && clear"
11
+ Enter
12
+ Show
13
+ Type "# What your AI assistant does when you ask: 'screenshot the terminal as step 1'"
14
+ Enter
15
+ Sleep 600ms
16
+ Type "uv run -q python demo/demo.py ."
17
+ Enter
18
+ Sleep 12s
19
+ Type "ls $SHOTLOG_DIR"
20
+ Enter
21
+ Sleep 3s
@@ -0,0 +1,7 @@
1
+ #!/bin/zsh
2
+ printf '\e]0;shotlog-demo\a'
3
+ sleep 2; clear
4
+ echo '$ ssh -V'; ssh -V 2>&1
5
+ echo; echo '$ uname -sm'; uname -sm
6
+ echo; echo '$ which sshd scp'; which sshd scp
7
+ exec sleep 600
Binary file
@@ -0,0 +1,32 @@
1
+ [project]
2
+ name = "shotlog-mcp"
3
+ version = "0.1.0"
4
+ description = "MCP server that captures macOS windows into a numbered, captioned evidence log for lab reports, QA and docs."
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ license-files = ["LICENSE"]
8
+ authors = [{ name = "Marlon Trujillo Jaramillo" }]
9
+ requires-python = ">=3.11"
10
+ keywords = ["mcp", "model-context-protocol", "screenshot", "macos", "claude", "documentation", "qa"]
11
+ classifiers = [
12
+ "Operating System :: MacOS",
13
+ "Programming Language :: Python :: 3",
14
+ "Topic :: Multimedia :: Graphics :: Capture :: Screen Capture",
15
+ ]
16
+ dependencies = [
17
+ "mcp>=1.2,<2",
18
+ "pyobjc-framework-Quartz; sys_platform == 'darwin'",
19
+ ]
20
+
21
+ [project.scripts]
22
+ shotlog-mcp = "shotlog_mcp.server:main"
23
+
24
+ [project.urls]
25
+ Repository = "https://github.com/Marlon-Trujillo-Jaramillo/shotlog-mcp"
26
+
27
+ [dependency-groups]
28
+ dev = ["pytest>=8"]
29
+
30
+ [build-system]
31
+ requires = ["hatchling>=1.26"]
32
+ build-backend = "hatchling.build"
@@ -0,0 +1,30 @@
1
+ {
2
+ "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
3
+ "name": "io.github.Marlon-Trujillo-Jaramillo/shotlog-mcp",
4
+ "title": "shotlog",
5
+ "description": "Capture macOS windows into a numbered, captioned screenshot log and export it as a report.",
6
+ "repository": {
7
+ "url": "https://github.com/Marlon-Trujillo-Jaramillo/shotlog-mcp",
8
+ "source": "github"
9
+ },
10
+ "version": "0.1.0",
11
+ "packages": [
12
+ {
13
+ "registryType": "pypi",
14
+ "identifier": "shotlog-mcp",
15
+ "version": "0.1.0",
16
+ "transport": {
17
+ "type": "stdio"
18
+ },
19
+ "environmentVariables": [
20
+ {
21
+ "name": "SHOTLOG_DIR",
22
+ "description": "Folder for screenshots, manifest.json and report.md (default: ~/shotlog)",
23
+ "isRequired": false,
24
+ "format": "string",
25
+ "isSecret": false
26
+ }
27
+ ]
28
+ }
29
+ ]
30
+ }
@@ -0,0 +1 @@
1
+ __version__ = "0.1.0"
@@ -0,0 +1,148 @@
1
+ """shotlog MCP server: capture macOS windows into a numbered, captioned evidence log."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import subprocess
6
+ import sys
7
+ from pathlib import Path
8
+
9
+ from mcp.server.fastmcp import FastMCP
10
+
11
+ from .store import Store
12
+
13
+ mcp = FastMCP("shotlog")
14
+ store = Store()
15
+
16
+
17
+ def _windows() -> list[dict]:
18
+ import Quartz # macOS only; imported lazily so the store stays portable
19
+
20
+ opts = Quartz.kCGWindowListOptionOnScreenOnly | Quartz.kCGWindowListExcludeDesktopElements
21
+ out = []
22
+ for w in Quartz.CGWindowListCopyWindowInfo(opts, Quartz.kCGNullWindowID) or []:
23
+ if w.get("kCGWindowLayer", 1) != 0:
24
+ continue
25
+ b = w.get("kCGWindowBounds", {})
26
+ if b.get("Width", 0) < 200 or b.get("Height", 0) < 150:
27
+ continue
28
+ out.append({
29
+ "id": int(w["kCGWindowNumber"]),
30
+ "app": w.get("kCGWindowOwnerName", ""),
31
+ "title": w.get("kCGWindowName", "") or "",
32
+ "size": f"{int(b['Width'])}x{int(b['Height'])}",
33
+ })
34
+ return out # front-most first
35
+
36
+
37
+ @mcp.tool()
38
+ def list_windows() -> list[dict]:
39
+ """List visible windows (id, app, title, size). The first one is the front-most."""
40
+ return _windows()
41
+
42
+
43
+ @mcp.tool()
44
+ def save_screenshot(
45
+ name: str,
46
+ app: str = "",
47
+ title_contains: str = "",
48
+ activity: str = "",
49
+ command: str = "",
50
+ result: str = "",
51
+ ) -> dict:
52
+ """Capture ONE macOS window and save it numbered in the shotlog folder.
53
+
54
+ - name: short slug, e.g. "step1_install".
55
+ - app / title_contains: pick the window (e.g. app="Terminal"). Empty = front-most window.
56
+ - activity / command / result: caption stored in the manifest and used by export_report.
57
+ """
58
+ wins = _windows()
59
+ if app:
60
+ wins = [w for w in wins if app.lower() in w["app"].lower()]
61
+ if title_contains:
62
+ wins = [w for w in wins if title_contains.lower() in w["title"].lower()]
63
+ if not wins:
64
+ return {"error": "Window not found. Call list_windows to see what is available."}
65
+ win = wins[0]
66
+ path = store.next_path(name)
67
+ r = subprocess.run(
68
+ ["screencapture", "-x", "-o", f"-l{win['id']}", str(path)],
69
+ capture_output=True, text=True,
70
+ )
71
+ if r.returncode != 0 or not path.exists() or path.stat().st_size == 0:
72
+ return {
73
+ "error": "screencapture failed. Grant 'Screen Recording' permission to your terminal/"
74
+ "MCP host in System Settings > Privacy & Security.",
75
+ "detail": r.stderr.strip(),
76
+ }
77
+ entry = store.register(path, f"mac:{win['app']} — {win['title']}", activity, command, result)
78
+ return {"path": str(path), **entry}
79
+
80
+
81
+ @mcp.tool()
82
+ def pull_remote_screenshots(
83
+ host: str,
84
+ user: str,
85
+ remote_dir: str = "Pictures/Screenshots",
86
+ prefix: str = "remote",
87
+ ) -> dict:
88
+ """Fetch new PNG screenshots from another machine over SCP (e.g. a Windows laptop where
89
+ Win+PrtScn saves to Pictures\\Screenshots) and add them to the log.
90
+ Requires an SSH server on that machine and key-based auth (no password prompt)."""
91
+ tmp = store.base / "_remote_tmp"
92
+ tmp.mkdir(exist_ok=True)
93
+ r = subprocess.run(
94
+ ["scp", "-p", "-o", "BatchMode=yes", "-o", "ConnectTimeout=8",
95
+ f"{user}@{host}:{remote_dir}/*.png", str(tmp)],
96
+ capture_output=True, text=True,
97
+ )
98
+ if r.returncode != 0:
99
+ for f in tmp.glob("*.png"):
100
+ f.unlink()
101
+ if "No such file" in r.stderr or "no matches" in r.stderr.lower():
102
+ return {"new": [], "total": 0, "note": f"No PNG files in {remote_dir}."}
103
+ return {"error": "scp failed", "detail": r.stderr.strip()}
104
+
105
+ known = {(e.get("source"), e.get("original"), e.get("original_size"))
106
+ for e in store.load()}
107
+ source = f"remote:{host}"
108
+ new = []
109
+ for f in sorted(tmp.glob("*.png"), key=lambda p: p.stat().st_mtime):
110
+ size = str(f.stat().st_size)
111
+ if (source, f.name, size) in known:
112
+ f.unlink()
113
+ continue
114
+ dest = store.next_path(f"{prefix}_{f.stem}")
115
+ f.rename(dest)
116
+ store.register(dest, source, original=f.name, original_size=size)
117
+ new.append(dest.name)
118
+ return {"new": new, "total": len(new)}
119
+
120
+
121
+ @mcp.tool()
122
+ def update_caption(file: str, activity: str = "", command: str = "", result: str = "") -> dict:
123
+ """Fill in or fix the caption (activity, command, result) of a saved screenshot."""
124
+ entry = store.update(file, activity=activity, command=command, result=result)
125
+ return entry or {"error": f"{file} is not in the manifest"}
126
+
127
+
128
+ @mcp.tool()
129
+ def list_screenshots() -> list[dict]:
130
+ """List every logged screenshot with its caption, in order."""
131
+ return store.load()
132
+
133
+
134
+ @mcp.tool()
135
+ def export_report(title: str = "Screenshots") -> dict:
136
+ """Write report.md in the shotlog folder: every screenshot with its caption, in order.
137
+ Convert it to PDF/DOCX with any tool (e.g. pandoc)."""
138
+ return {"path": str(store.export_markdown(title))}
139
+
140
+
141
+ def main() -> None:
142
+ if sys.platform != "darwin":
143
+ sys.exit("shotlog-mcp captures windows with macOS APIs and only runs on macOS.")
144
+ mcp.run()
145
+
146
+
147
+ if __name__ == "__main__":
148
+ main()