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.
- shotlog_mcp-0.1.0/.gitignore +8 -0
- shotlog_mcp-0.1.0/LAUNCH.md +76 -0
- shotlog_mcp-0.1.0/LICENSE +21 -0
- shotlog_mcp-0.1.0/PKG-INFO +93 -0
- shotlog_mcp-0.1.0/README.es.md +42 -0
- shotlog_mcp-0.1.0/README.md +76 -0
- shotlog_mcp-0.1.0/demo/demo.py +31 -0
- shotlog_mcp-0.1.0/demo/demo.tape +21 -0
- shotlog_mcp-0.1.0/demo/window.sh +7 -0
- shotlog_mcp-0.1.0/docs/demo.gif +0 -0
- shotlog_mcp-0.1.0/pyproject.toml +32 -0
- shotlog_mcp-0.1.0/server.json +30 -0
- shotlog_mcp-0.1.0/src/shotlog_mcp/__init__.py +1 -0
- shotlog_mcp-0.1.0/src/shotlog_mcp/server.py +148 -0
- shotlog_mcp-0.1.0/src/shotlog_mcp/store.py +104 -0
- shotlog_mcp-0.1.0/tests/test_store.py +35 -0
- shotlog_mcp-0.1.0/uv.lock +909 -0
|
@@ -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) [](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
|
+

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

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

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