@thenavidm/apple-photos-mcp-cli 1.0.1 → 1.0.3

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.
Files changed (41) hide show
  1. package/README.md +67 -17
  2. package/SKILL.md +16 -0
  3. package/lib/bridge.d.ts +43 -0
  4. package/lib/bridge.js +91 -0
  5. package/lib/bridge.js.map +1 -0
  6. package/lib/cli.d.ts +92 -0
  7. package/lib/cli.js +634 -0
  8. package/lib/cli.js.map +1 -0
  9. package/lib/config.d.ts +19 -0
  10. package/lib/config.js +58 -0
  11. package/lib/config.js.map +1 -0
  12. package/lib/errors.d.ts +15 -0
  13. package/lib/errors.js +22 -0
  14. package/lib/errors.js.map +1 -0
  15. package/lib/index.d.ts +12 -0
  16. package/lib/index.js +117 -0
  17. package/lib/index.js.map +1 -0
  18. package/lib/safety.d.ts +27 -0
  19. package/lib/safety.js +70 -0
  20. package/lib/safety.js.map +1 -0
  21. package/lib/server.d.ts +10 -0
  22. package/lib/server.js +38 -0
  23. package/lib/server.js.map +1 -0
  24. package/lib/tools/index.d.ts +55 -0
  25. package/lib/tools/index.js +154 -0
  26. package/lib/tools/index.js.map +1 -0
  27. package/lib/tools/kit.d.ts +48 -0
  28. package/lib/tools/kit.js +64 -0
  29. package/lib/tools/kit.js.map +1 -0
  30. package/package.json +7 -7
  31. package/pyproject.toml +2 -2
  32. package/src/apple_photos_mcp/__pycache__/__init__.cpython-313.pyc +0 -0
  33. package/src/apple_photos_mcp/__pycache__/__main__.cpython-313.pyc +0 -0
  34. package/src/apple_photos_mcp/__pycache__/config.cpython-313.pyc +0 -0
  35. package/src/apple_photos_mcp/__pycache__/doctor.cpython-313.pyc +0 -0
  36. package/src/apple_photos_mcp/__pycache__/library.cpython-313.pyc +0 -0
  37. package/src/apple_photos_mcp/__pycache__/previews.cpython-313.pyc +0 -0
  38. package/src/apple_photos_mcp/__pycache__/safety.cpython-313.pyc +0 -0
  39. package/src/apple_photos_mcp/__pycache__/search.cpython-313.pyc +0 -0
  40. package/src/apple_photos_mcp/__pycache__/server.cpython-313.pyc +0 -0
  41. package/src/apple_photos_mcp/__pycache__/writes.cpython-313.pyc +0 -0
package/README.md CHANGED
@@ -1,14 +1,17 @@
1
1
  <img src="https://cdn.navid.media/connectors/apple-photos-icon.png" alt="Apple Photos" width="88">
2
2
 
3
- # Apple Photos MCP
3
+ # Apple Photos MCP + CLI
4
4
 
5
- [![Stars](https://img.shields.io/github/stars/navidmoazzez/apple-photos-mcp?style=flat&logo=github&label=Stars)](https://github.com/navidmoazzez/apple-photos-mcp)
5
+ [![npm](https://img.shields.io/npm/v/@thenavidm/apple-photos-mcp-cli?color=orange&label=npm)](https://www.npmjs.com/package/@thenavidm/apple-photos-mcp-cli)
6
+ [![Stars](https://img.shields.io/github/stars/navidmoazzez/apple-photos-mcp-cli?style=flat&logo=github&label=Stars)](https://github.com/navidmoazzez/apple-photos-mcp-cli)
6
7
  [![License](https://img.shields.io/badge/License-MIT-blue)](./LICENSE)
7
8
  [![YouTube](https://img.shields.io/badge/YouTube-@thenavidm-red?logo=youtube&logoColor=white)](https://youtube.com/@thenavidm?sub_confirmation=1)
8
9
  [![X](https://img.shields.io/badge/X-@thenavidm-black?logo=x)](https://x.com/thenavidm)
9
10
  [![LinkedIn](https://img.shields.io/badge/LinkedIn-thenavidm-0A66C2?logo=linkedin&logoColor=white)](https://linkedin.com/in/thenavidm)
10
11
 
11
- Give any AI agent real access to your own Apple Photos library, so it can find, see, organize and export your photos instead of guessing.
12
+ Apple Photos MCP server and CLI for Claude Code and AI agents. 13 tools to search, look at, organise and export your own library, entirely on your Mac.
13
+
14
+ Give any AI agent real access to your own Apple Photos library, so it can find, see, organise and export your photos instead of guessing.
12
15
 
13
16
  Built and maintained by [Navid Moazzez](https://navid.me?utm_source=github&utm_medium=readme&utm_campaign=apple-photos-mcp).
14
17
 
@@ -16,12 +19,54 @@ Built and maintained by [Navid Moazzez](https://navid.me?utm_source=github&utm_m
16
19
 
17
20
  Everything runs on your Mac. There is no backend.
18
21
 
22
+ ## Two ways to use it
23
+
24
+ ### Command line
25
+
26
+ `apple-photos-cli` in your terminal, for scripting, cron, pipes, or a quick
27
+ question without opening anything:
28
+
29
+ ```bash
30
+ apple-photos-cli # every command, one line each
31
+ apple-photos-cli library-stats --json # real totals, one call
32
+ apple-photos-cli search-photos "sunset" --limit 5 --screenshots exclude
33
+ apple-photos-cli photo-info --refs IMG_2073.MOV
34
+ apple-photos-cli export-originals --refs <uuid> --directory ./out
35
+ apple-photos-cli <command> --help # what any command takes
36
+ ```
37
+
38
+ `--json` gives JSON, `--compact` puts it on one line, `--select total,videos`
39
+ keeps only the fields you name, and errors are JSON on stderr whichever you
40
+ pick. `--confirm` is the shell spelling of the confirmation archiving requires.
41
+
42
+ ### MCP server, for AI agents
43
+
44
+ `apple-photos-mcp` is what Claude Code, Claude Desktop, Cursor and the rest
45
+ launch. You never run it by hand:
46
+
47
+ ```bash
48
+ claude mcp add apple-photos -- npx -y @thenavidm/apple-photos-mcp-cli@latest
49
+ ```
50
+
51
+ Then just ask: _"which receipts do I have from Vietnam?"_
52
+
53
+ ### Which one
54
+
55
+ | What you are doing | Use |
56
+ |---|---|
57
+ | Inside a conversation with an agent | MCP |
58
+ | Piping, scripting, cron, CI | CLI |
59
+ | A one-off question in a terminal | CLI |
60
+
61
+ They are the same program reading the same tool definitions, so anything one
62
+ can do, the other can. A test asserts they cannot drift.
63
+
19
64
  ## Contents
20
65
 
21
66
  | | Section | |
22
67
  |---|---|---|
23
68
  | 1 | [What you can ask it](#1-what-you-can-ask-it-) | Real prompts, not features |
24
- | 2 | [Quick install](#2-quick-install-) | The package only |
69
+ | 2 | [Quick install](#2-quick-install-) | One command, no credential |
25
70
  | 3 | [Setup](#3-setup-) | One permission, once |
26
71
  | 4 | [Connect your client](#4-connect-your-client-) | Every client, copy and paste |
27
72
  | 5 | [Check it worked](#5-check-it-worked-) | `doctor` |
@@ -49,21 +94,26 @@ The first one is the point. Your library already knows what is in every photo, b
49
94
 
50
95
  ## 2. Quick install ⚡
51
96
 
52
- macOS, and Python 3.11 or newer.
53
-
54
- > **Not on PyPI yet.** Install it from GitHub until it is published. The command below works today.
97
+ macOS, and Node 20 or newer.
55
98
 
56
99
  ```bash
57
- uvx --from git+https://github.com/navidmoazzez/apple-photos-mcp apple-photos-mcp --version
100
+ npx -y @thenavidm/apple-photos-mcp-cli@latest --version
58
101
  ```
59
102
 
60
- If you do not have [uv](https://docs.astral.sh/uv/), which is the Python equivalent of `npx`:
103
+ That is the whole install. `npx` fetches it on demand, so there is nothing to
104
+ update later.
105
+
106
+ The engine underneath is Python, because `osxphotos` and `photoscript` are the
107
+ only libraries that can read a Photos library and both are Python-only. You do
108
+ not install them: [uv](https://docs.astral.sh/uv/) fetches them on first run and
109
+ caches them. If you do not have uv:
61
110
 
62
111
  ```bash
63
112
  curl -LsSf https://astral.sh/uv/install.sh | sh
64
113
  ```
65
114
 
66
- That completes the install. No account, no API key, no credential.
115
+ No account, no API key, no credential. macOS will ask for permission the first
116
+ time something reads the library.
67
117
 
68
118
  ## 3. Setup 🔑
69
119
 
@@ -107,7 +157,7 @@ Set up apple-photos-mcp for me.
107
157
 
108
158
  ```bash
109
159
  claude mcp add apple-photos -s user \
110
- -- uvx --from git+https://github.com/navidmoazzez/apple-photos-mcp apple-photos-mcp
160
+ -- npx -y @thenavidm/apple-photos-mcp-cli@latest
111
161
  ```
112
162
 
113
163
  `-s user` makes it available in every project rather than just the current one.
@@ -138,8 +188,8 @@ Apple Photos only exists on macOS, so there is no Windows or Linux path here.
138
188
  {
139
189
  "mcpServers": {
140
190
  "apple-photos": {
141
- "command": "uvx",
142
- "args": ["--from", "git+https://github.com/navidmoazzez/apple-photos-mcp", "apple-photos-mcp"]
191
+ "command": "npx",
192
+ "args": ["-y", "@thenavidm/apple-photos-mcp-cli@latest"]
143
193
  }
144
194
  }
145
195
  }
@@ -178,7 +228,7 @@ For one project instead of globally, use `.cursor/mcp.json` in that project.
178
228
  "apple-photos": {
179
229
  "type": "stdio",
180
230
  "command": "uvx",
181
- "args": ["--from", "git+https://github.com/navidmoazzez/apple-photos-mcp", "apple-photos-mcp"]
231
+ "args": ["--from", "git+https://github.com/navidmoazzez/apple-photos-mcp-cli", "apple-photos-mcp"]
182
232
  }
183
233
  }
184
234
  }
@@ -193,7 +243,7 @@ Reload the window: Cmd+Shift+P, then **Developer: Reload Window**.
193
243
  ```toml
194
244
  [mcp_servers.apple-photos]
195
245
  command = "uvx"
196
- args = ["--from", "git+https://github.com/navidmoazzez/apple-photos-mcp", "apple-photos-mcp"]
246
+ args = ["--from", "git+https://github.com/navidmoazzez/apple-photos-mcp-cli", "apple-photos-mcp"]
197
247
  ```
198
248
 
199
249
  ### Gemini CLI
@@ -215,7 +265,7 @@ That relay is not included here. It is a separate deployment with its own hostin
215
265
  ## 5. Check it worked 🩺
216
266
 
217
267
  ```bash
218
- uvx --from git+https://github.com/navidmoazzez/apple-photos-mcp apple-photos-mcp doctor
268
+ npx -y @thenavidm/apple-photos-mcp-cli@latest doctor
219
269
  ```
220
270
 
221
271
  Or just ask your agent: **"run doctor on apple photos"**.
@@ -448,7 +498,7 @@ Delete the server from your client's config, or run `claude mcp remove apple-pho
448
498
 
449
499
  ## Questions
450
500
 
451
- Run into a problem or have a question? [Open an issue](https://github.com/navidmoazzez/apple-photos-mcp/issues) and I will help.
501
+ Run into a problem or have a question? [Open an issue](https://github.com/navidmoazzez/apple-photos-mcp-cli/issues) and I will help.
452
502
 
453
503
  ## About the author
454
504
 
package/SKILL.md CHANGED
@@ -16,6 +16,22 @@ description: >
16
16
  The library is read directly from disk on this Mac. Nothing is uploaded, and
17
17
  nothing here can permanently delete a photo.
18
18
 
19
+
20
+ ## Two surfaces, same tools
21
+
22
+ The MCP server is for work inside a conversation. The CLI is for scripting,
23
+ piping and one-off questions, and costs no context until it is called.
24
+
25
+ ```bash
26
+ apple-photos-cli library-stats --json
27
+ apple-photos-cli search-photos "sunset" --limit 5 --screenshots exclude
28
+ apple-photos-cli photo-info --refs IMG_2073.MOV
29
+ apple-photos-cli <command> --help
30
+ ```
31
+
32
+ The command is the tool name with dashes. `--select total,videos` trims the
33
+ output, which matters on a library this size.
34
+
19
35
  ## The one thing that matters
20
36
 
21
37
  Search returns candidates. It does not return answers.
@@ -0,0 +1,43 @@
1
+ /**
2
+ * The bridge to the Python engine.
3
+ *
4
+ * Apple Photos is the one case the house standard carves out for Python: the
5
+ * only libraries that can read a Photos library are `osxphotos` and
6
+ * `photoscript`, both Python-only, both macOS-only, both pulling in pyobjc.
7
+ * Reimplementing them in TypeScript would mean reimplementing Apple's private
8
+ * SQLite schema, which is not a thing to own.
9
+ *
10
+ * So the engine stays Python and this layer wraps it, the way
11
+ * google-workspace-mcp wraps the `gws` binary. What TypeScript buys is the part
12
+ * Python cost us: `npx` with no toolchain to install first, a CLI generated
13
+ * from the same tool array as the MCP server, and a static `ALL_TOOLS` that the
14
+ * HQ connector can import so the hosted surface cannot drift from this one.
15
+ *
16
+ * Calls are proxied to the Python MCP server over stdio using the official
17
+ * client, rather than a bespoke JSON protocol. That means the Python needs no
18
+ * changes at all, and there is exactly one implementation of every tool.
19
+ */
20
+ import type { Config } from "./config.js";
21
+ export type BridgeResult = {
22
+ text: string;
23
+ isError: boolean;
24
+ };
25
+ /**
26
+ * One long-lived connection to the Python server.
27
+ *
28
+ * Spawning per call would pay `uv`'s dependency resolution and a Photos library
29
+ * scan every time, which is seconds each. The process is started on first use
30
+ * and reused.
31
+ */
32
+ export declare class PythonBridge {
33
+ private readonly config;
34
+ private client?;
35
+ private starting?;
36
+ constructor(config: Config);
37
+ private connect;
38
+ /** Call one Python tool and return its text payload. */
39
+ call(tool: string, args: Record<string, unknown>): Promise<BridgeResult>;
40
+ /** What the Python server says it offers. Used by `doctor` to prove the two agree. */
41
+ listTools(): Promise<string[]>;
42
+ close(): Promise<void>;
43
+ }
package/lib/bridge.js ADDED
@@ -0,0 +1,91 @@
1
+ /**
2
+ * The bridge to the Python engine.
3
+ *
4
+ * Apple Photos is the one case the house standard carves out for Python: the
5
+ * only libraries that can read a Photos library are `osxphotos` and
6
+ * `photoscript`, both Python-only, both macOS-only, both pulling in pyobjc.
7
+ * Reimplementing them in TypeScript would mean reimplementing Apple's private
8
+ * SQLite schema, which is not a thing to own.
9
+ *
10
+ * So the engine stays Python and this layer wraps it, the way
11
+ * google-workspace-mcp wraps the `gws` binary. What TypeScript buys is the part
12
+ * Python cost us: `npx` with no toolchain to install first, a CLI generated
13
+ * from the same tool array as the MCP server, and a static `ALL_TOOLS` that the
14
+ * HQ connector can import so the hosted surface cannot drift from this one.
15
+ *
16
+ * Calls are proxied to the Python MCP server over stdio using the official
17
+ * client, rather than a bespoke JSON protocol. That means the Python needs no
18
+ * changes at all, and there is exactly one implementation of every tool.
19
+ */
20
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
21
+ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
22
+ import { BridgeError } from "./errors.js";
23
+ /**
24
+ * One long-lived connection to the Python server.
25
+ *
26
+ * Spawning per call would pay `uv`'s dependency resolution and a Photos library
27
+ * scan every time, which is seconds each. The process is started on first use
28
+ * and reused.
29
+ */
30
+ export class PythonBridge {
31
+ config;
32
+ client;
33
+ starting;
34
+ constructor(config) {
35
+ this.config = config;
36
+ }
37
+ async connect() {
38
+ if (this.client)
39
+ return this.client;
40
+ if (this.starting)
41
+ return this.starting;
42
+ this.starting = (async () => {
43
+ const client = new Client({ name: "apple-photos-cli", version: "1.0.0" }, { capabilities: {} });
44
+ // `uv run` resolves osxphotos and photoscript on first use and caches
45
+ // them, so the reader needs neither a virtualenv nor a pip install.
46
+ const transport = new StdioClientTransport({
47
+ command: this.config.pythonCommand,
48
+ args: this.config.pythonArgs,
49
+ env: { ...process.env, ...this.config.pythonEnv },
50
+ stderr: "pipe",
51
+ });
52
+ try {
53
+ await client.connect(transport);
54
+ }
55
+ catch (error) {
56
+ throw new BridgeError(`Could not start the Photos engine with \`${this.config.pythonCommand} ${this.config.pythonArgs.join(" ")}\`. ` +
57
+ `Install uv (https://docs.astral.sh/uv/) or set APPLE_PHOTOS_PYTHON to a Python that has osxphotos and photoscript. ` +
58
+ `Underlying error: ${error.message}`);
59
+ }
60
+ this.client = client;
61
+ return client;
62
+ })();
63
+ try {
64
+ return await this.starting;
65
+ }
66
+ finally {
67
+ this.starting = undefined;
68
+ }
69
+ }
70
+ /** Call one Python tool and return its text payload. */
71
+ async call(tool, args) {
72
+ const client = await this.connect();
73
+ const result = (await client.callTool({ name: tool, arguments: args }));
74
+ const text = (result.content ?? [])
75
+ .filter((part) => part.type === "text" && typeof part.text === "string")
76
+ .map((part) => part.text)
77
+ .join("\n");
78
+ return { text, isError: result.isError === true };
79
+ }
80
+ /** What the Python server says it offers. Used by `doctor` to prove the two agree. */
81
+ async listTools() {
82
+ const client = await this.connect();
83
+ const { tools } = await client.listTools();
84
+ return tools.map((tool) => tool.name);
85
+ }
86
+ async close() {
87
+ await this.client?.close().catch(() => undefined);
88
+ this.client = undefined;
89
+ }
90
+ }
91
+ //# sourceMappingURL=bridge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bridge.js","sourceRoot":"","sources":["../src-ts/bridge.ts"],"names":[],"mappings":"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"}
package/lib/cli.d.ts ADDED
@@ -0,0 +1,92 @@
1
+ /**
2
+ * The CLI adapter.
3
+ *
4
+ * `register()` in tools/kit.ts turns a `ToolSpec` into an MCP tool. This turns
5
+ * the same spec into a shell command, from the same `ALL_TOOLS` array, through
6
+ * the same handler and the same `WriteGuard`. Nothing is described twice, so a
7
+ * tool added tomorrow is a command tomorrow and the two surfaces cannot drift.
8
+ *
9
+ * The command IS the tool name. `submit_imagine` runs as `submit-imagine`, and
10
+ * the underscore form works too. Inventing a prettier command tree would mean a
11
+ * hand-written mapping, which is exactly the drift this avoids, and it would
12
+ * force anyone reading the SKILL.md to learn two vocabularies for one action.
13
+ *
14
+ * Zod is the only schema: every flag, its placeholder, its help text and its
15
+ * validation come from the shape the MCP tool already declares.
16
+ */
17
+ import { type ZodRawShape } from "zod";
18
+ import { type AnyToolSpec } from "./tools/kit.js";
19
+ /** How a value reaches the parser, once the Zod wrappers are peeled off. */
20
+ type FlagKind = "string" | "number" | "boolean" | "enum" | "json";
21
+ type Flag = {
22
+ key: string;
23
+ flag: string;
24
+ kind: FlagKind;
25
+ required: boolean;
26
+ repeatable: boolean;
27
+ choices?: string[];
28
+ help: string;
29
+ };
30
+ export declare function flagsFor(shape: ZodRawShape): Flag[];
31
+ /**
32
+ * Midjourney's own parameter spellings, accepted as flags.
33
+ *
34
+ * The schema names things in full because a tool description is read by a model
35
+ * that has never seen a Midjourney prompt. A person at a terminal has, and they
36
+ * will type `--ar 16:9`, because that is what the parameter is called
37
+ * everywhere Midjourney documents it. Refusing the name the whole ecosystem
38
+ * uses, to protect a naming convention nobody outside this repo can see, would
39
+ * be the wrong trade.
40
+ */
41
+ export declare const FLAG_ALIASES: Record<string, string>;
42
+ /**
43
+ * Parse argv against a tool's flags.
44
+ *
45
+ * Zod does the real validation afterwards, so this only has to get values into
46
+ * the right JavaScript types and catch the mistakes Zod would otherwise report
47
+ * in terms of a schema the person at the terminal never sees.
48
+ */
49
+ export declare function parseArgs(argv: string[], flags: Flag[]): Record<string, unknown>;
50
+ /**
51
+ * Keep only the named fields.
52
+ *
53
+ * These endpoints are verbose: one explore page is tens of kilobytes of JSON,
54
+ * most of it layout hints. An agent piping that into its own context pays for
55
+ * every byte. `--select id,status,images` cuts it to what was asked for.
56
+ * Dotted paths descend, and arrays are traversed element-wise.
57
+ */
58
+ export declare function selectFields(data: unknown, paths: string[]): unknown;
59
+ /** Errors are JSON on stderr, always, so a caller parses one shape. */
60
+ export declare function emitError(error: unknown): void;
61
+ /**
62
+ * The name this was invoked as, so examples are copy-pasteable.
63
+ *
64
+ * The package puts two binaries on the same file. Printing `apple-photos-mcp` at
65
+ * someone who typed `apple-photos-cli` hands them a command that works but is not
66
+ * the one in their fingers.
67
+ */
68
+ export declare function binName(): string;
69
+ /**
70
+ * Words people use mapped onto words the tools use.
71
+ *
72
+ * Without this, "make a picture" matches nothing, because every tool says
73
+ * "generate" and "image". A lookup that only works when you already know the
74
+ * vocabulary is not a lookup.
75
+ */
76
+ export declare const SYNONYMS: Record<string, string[]>;
77
+ /**
78
+ * Resolve a capability described in words to the command that does it.
79
+ *
80
+ * An agent that knows what it wants but not what this tool calls it would
81
+ * otherwise read the full tool list to find out, which is the cost the CLI
82
+ * exists to avoid. Scoring is deliberately dumb: name and title carry the most
83
+ * weight, because a tool called `download_job` should win "download my images"
84
+ * without any cleverness.
85
+ */
86
+ export declare function whichCommand(query: string): {
87
+ tool: AnyToolSpec;
88
+ score: number;
89
+ }[];
90
+ export declare function isCliCommand(argv: string[]): boolean;
91
+ export declare function runCli(argv: string[]): Promise<number>;
92
+ export {};