joplin-md-sync 1.5.5__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.
- joplin_md_sync-1.5.5/.version +1 -0
- joplin_md_sync-1.5.5/LICENSE +21 -0
- joplin_md_sync-1.5.5/MANIFEST.in +10 -0
- joplin_md_sync-1.5.5/PKG-INFO +221 -0
- joplin_md_sync-1.5.5/README.md +178 -0
- joplin_md_sync-1.5.5/docs/contracts/AGENT_INTERFACES.md +129 -0
- joplin_md_sync-1.5.5/docs/contracts/CI_RELEASES.md +165 -0
- joplin_md_sync-1.5.5/docs/contracts/CLI.md +112 -0
- joplin_md_sync-1.5.5/docs/contracts/DEPENDENCIES.md +98 -0
- joplin_md_sync-1.5.5/docs/contracts/README.md +49 -0
- joplin_md_sync-1.5.5/docs/contracts/SECURITY.md +99 -0
- joplin_md_sync-1.5.5/docs/contracts/SERVICE.md +123 -0
- joplin_md_sync-1.5.5/docs/contracts/SYNCHRONIZATION.md +126 -0
- joplin_md_sync-1.5.5/docs/contracts/WORKSPACE.md +108 -0
- joplin_md_sync-1.5.5/docs/index.md +282 -0
- joplin_md_sync-1.5.5/docs/maintenance/ARCHITECTURE.md +129 -0
- joplin_md_sync-1.5.5/docs/maintenance/DEPENDENCIES.md +44 -0
- joplin_md_sync-1.5.5/docs/maintenance/DEVELOPMENT.md +65 -0
- joplin_md_sync-1.5.5/docs/maintenance/RELEASES.md +56 -0
- joplin_md_sync-1.5.5/docs/maintenance/SECURITY.md +58 -0
- joplin_md_sync-1.5.5/docs/maintenance/SERVICE.md +59 -0
- joplin_md_sync-1.5.5/docs/maintenance/SYNCHRONIZATION.md +59 -0
- joplin_md_sync-1.5.5/docs/user/AGENT_INTERFACES.md +88 -0
- joplin_md_sync-1.5.5/docs/user/AGENT_WORKFLOWS.md +161 -0
- joplin_md_sync-1.5.5/docs/user/CHATGPT_ACTIONS.md +242 -0
- joplin_md_sync-1.5.5/docs/user/CLI.md +173 -0
- joplin_md_sync-1.5.5/docs/user/CONFLICTS.md +40 -0
- joplin_md_sync-1.5.5/docs/user/GETTING_STARTED.md +174 -0
- joplin_md_sync-1.5.5/docs/user/MCP_API.md +166 -0
- joplin_md_sync-1.5.5/docs/user/OVERVIEW.md +102 -0
- joplin_md_sync-1.5.5/docs/user/SELF_HOSTED.md +122 -0
- joplin_md_sync-1.5.5/docs/user/SERVICE.md +929 -0
- joplin_md_sync-1.5.5/docs/user/WORKSPACE_FORMAT.md +72 -0
- joplin_md_sync-1.5.5/examples/agent-notes-repository/.gitignore +16 -0
- joplin_md_sync-1.5.5/examples/agent-notes-repository/AGENTS.md +222 -0
- joplin_md_sync-1.5.5/examples/agent-notes-repository/README.md +128 -0
- joplin_md_sync-1.5.5/examples/agent-notes-repository/RUNBOOK.md +166 -0
- joplin_md_sync-1.5.5/examples/agent-notes-repository/scripts/install-joplin-md-sync.py +322 -0
- joplin_md_sync-1.5.5/examples/agent-session.md +63 -0
- joplin_md_sync-1.5.5/examples/config.example.json +11 -0
- joplin_md_sync-1.5.5/examples/windows/install-service-task.ps1 +83 -0
- joplin_md_sync-1.5.5/pyproject.toml +89 -0
- joplin_md_sync-1.5.5/scripts/joplin_terminal_service/collect_joplin_debug.sh +260 -0
- joplin_md_sync-1.5.5/scripts/joplin_terminal_service/install_joplin_terminal.py +2868 -0
- joplin_md_sync-1.5.5/scripts/joplin_terminal_service/joplin_terminal_common.py +255 -0
- joplin_md_sync-1.5.5/scripts/joplin_terminal_service/run_joplin_terminal.py +383 -0
- joplin_md_sync-1.5.5/scripts/joplin_terminal_service/systemd/joplin-md-sync.service +28 -0
- joplin_md_sync-1.5.5/scripts/joplin_terminal_service/systemd/joplin-terminal.service +22 -0
- joplin_md_sync-1.5.5/scripts/prepare_chatgpt_action.py +286 -0
- joplin_md_sync-1.5.5/setup.cfg +4 -0
- joplin_md_sync-1.5.5/src/joplin_md_sync/__init__.py +54 -0
- joplin_md_sync-1.5.5/src/joplin_md_sync/__main__.py +6 -0
- joplin_md_sync-1.5.5/src/joplin_md_sync/api.py +624 -0
- joplin_md_sync-1.5.5/src/joplin_md_sync/auth.py +146 -0
- joplin_md_sync-1.5.5/src/joplin_md_sync/canonical.py +81 -0
- joplin_md_sync-1.5.5/src/joplin_md_sync/cli.py +1332 -0
- joplin_md_sync-1.5.5/src/joplin_md_sync/config.py +130 -0
- joplin_md_sync-1.5.5/src/joplin_md_sync/conflicts.py +457 -0
- joplin_md_sync-1.5.5/src/joplin_md_sync/diff.py +185 -0
- joplin_md_sync-1.5.5/src/joplin_md_sync/errors.py +137 -0
- joplin_md_sync-1.5.5/src/joplin_md_sync/gpt_actions.py +577 -0
- joplin_md_sync-1.5.5/src/joplin_md_sync/gpt_openapi.py +244 -0
- joplin_md_sync-1.5.5/src/joplin_md_sync/journal.py +214 -0
- joplin_md_sync-1.5.5/src/joplin_md_sync/json_safety.py +30 -0
- joplin_md_sync-1.5.5/src/joplin_md_sync/locking.py +89 -0
- joplin_md_sync-1.5.5/src/joplin_md_sync/mcp_server.py +493 -0
- joplin_md_sync-1.5.5/src/joplin_md_sync/mcp_service.py +1139 -0
- joplin_md_sync-1.5.5/src/joplin_md_sync/metadata.py +130 -0
- joplin_md_sync-1.5.5/src/joplin_md_sync/models.py +246 -0
- joplin_md_sync-1.5.5/src/joplin_md_sync/paths.py +91 -0
- joplin_md_sync-1.5.5/src/joplin_md_sync/planner.py +760 -0
- joplin_md_sync-1.5.5/src/joplin_md_sync/resources.py +86 -0
- joplin_md_sync-1.5.5/src/joplin_md_sync/state.py +337 -0
- joplin_md_sync-1.5.5/src/joplin_md_sync/sync.py +774 -0
- joplin_md_sync-1.5.5/src/joplin_md_sync/tool_executor.py +113 -0
- joplin_md_sync-1.5.5/src/joplin_md_sync/tool_registry.py +693 -0
- joplin_md_sync-1.5.5/src/joplin_md_sync/tool_schema.py +207 -0
- joplin_md_sync-1.5.5/src/joplin_md_sync/update_check.py +94 -0
- joplin_md_sync-1.5.5/src/joplin_md_sync/workspace.py +338 -0
- joplin_md_sync-1.5.5/src/joplin_md_sync.egg-info/PKG-INFO +221 -0
- joplin_md_sync-1.5.5/src/joplin_md_sync.egg-info/SOURCES.txt +83 -0
- joplin_md_sync-1.5.5/src/joplin_md_sync.egg-info/dependency_links.txt +1 -0
- joplin_md_sync-1.5.5/src/joplin_md_sync.egg-info/entry_points.txt +2 -0
- joplin_md_sync-1.5.5/src/joplin_md_sync.egg-info/requires.txt +22 -0
- joplin_md_sync-1.5.5/src/joplin_md_sync.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
1.5.5
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 joplin-md-sync contributors
|
|
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,10 @@
|
|
|
1
|
+
include .version
|
|
2
|
+
recursive-include docs *.md
|
|
3
|
+
recursive-include examples *.json *.md *.ps1 *.py
|
|
4
|
+
include examples/agent-notes-repository/.gitignore
|
|
5
|
+
include scripts/prepare_chatgpt_action.py
|
|
6
|
+
include scripts/joplin_terminal_service/collect_joplin_debug.sh
|
|
7
|
+
include scripts/joplin_terminal_service/install_joplin_terminal.py
|
|
8
|
+
include scripts/joplin_terminal_service/joplin_terminal_common.py
|
|
9
|
+
include scripts/joplin_terminal_service/run_joplin_terminal.py
|
|
10
|
+
recursive-include scripts/joplin_terminal_service/systemd *.service
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: joplin-md-sync
|
|
3
|
+
Version: 1.5.5
|
|
4
|
+
Summary: Safe two-way synchronization between Joplin notes and a local Markdown workspace, built for autonomous coding agents
|
|
5
|
+
Author: joplin-md-sync contributors
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://joplin-mcp.romancello.net/
|
|
8
|
+
Project-URL: Documentation, https://joplin-mcp.romancello.net/
|
|
9
|
+
Project-URL: Repository, https://github.com/kogeler/joplin-md-sync
|
|
10
|
+
Project-URL: Issues, https://github.com/kogeler/joplin-md-sync/issues
|
|
11
|
+
Project-URL: Changelog, https://github.com/kogeler/joplin-md-sync/blob/main/CHANGELOG.md
|
|
12
|
+
Keywords: joplin,markdown,sync,notes,cli,agents
|
|
13
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
14
|
+
Classifier: Environment :: Console
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
17
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
20
|
+
Classifier: Topic :: Utilities
|
|
21
|
+
Requires-Python: >=3.13
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: bandit[toml]==1.9.4; extra == "dev"
|
|
26
|
+
Requires-Dist: mypy==2.3.1; extra == "dev"
|
|
27
|
+
Requires-Dist: pip-audit==2.10.1; extra == "dev"
|
|
28
|
+
Requires-Dist: ruff==0.16.4; extra == "dev"
|
|
29
|
+
Provides-Extra: test
|
|
30
|
+
Requires-Dist: colorama==0.4.6; extra == "test"
|
|
31
|
+
Requires-Dist: pytest==9.1.1; extra == "test"
|
|
32
|
+
Requires-Dist: pytest-cov==7.1.0; extra == "test"
|
|
33
|
+
Requires-Dist: pytest-xdist==3.8.0; extra == "test"
|
|
34
|
+
Provides-Extra: package
|
|
35
|
+
Requires-Dist: build==1.5.0; extra == "package"
|
|
36
|
+
Requires-Dist: colorama==0.4.6; extra == "package"
|
|
37
|
+
Requires-Dist: pefile==2024.8.26; extra == "package"
|
|
38
|
+
Requires-Dist: pyinstaller==6.22.2; extra == "package"
|
|
39
|
+
Requires-Dist: pywin32-ctypes==0.2.3; extra == "package"
|
|
40
|
+
Provides-Extra: docs
|
|
41
|
+
Requires-Dist: mkdocs-material==9.7.7; extra == "docs"
|
|
42
|
+
Dynamic: license-file
|
|
43
|
+
|
|
44
|
+
# joplin-md-sync
|
|
45
|
+
|
|
46
|
+
Safe two-way synchronization between the local [Joplin](https://joplinapp.org/)
|
|
47
|
+
desktop application and an ordinary directory of Markdown files — built
|
|
48
|
+
primarily for **autonomous coding agents** (deterministic JSON output, stable
|
|
49
|
+
exit codes, explicit conflict handling), and perfectly usable by humans.
|
|
50
|
+
|
|
51
|
+
**Documentation:** [joplin-mcp.romancello.net](https://joplin-mcp.romancello.net/)
|
|
52
|
+
|
|
53
|
+
> **Safety first.** The tool never overwrites divergent edits, never deletes
|
|
54
|
+
> anything without an explicit flag, never uses permanent deletion in Joplin,
|
|
55
|
+
> verifies every write after applying it, and journals every mutating run so
|
|
56
|
+
> interrupted syncs are recoverable. `diff` never mutates anything.
|
|
57
|
+
|
|
58
|
+
The test-backed definitions of these guarantees live in the
|
|
59
|
+
[contract catalog](https://joplin-mcp.romancello.net/contracts/).
|
|
60
|
+
|
|
61
|
+
If you are an agent (or configuring one), start with
|
|
62
|
+
**[AGENTS.md](https://github.com/kogeler/joplin-md-sync/blob/main/AGENTS.md)**.
|
|
63
|
+
|
|
64
|
+
## How it works
|
|
65
|
+
|
|
66
|
+
- Notes are plain `.md` files; each carries a one-line metadata header with
|
|
67
|
+
the Joplin id, title, and tags. Notebooks are directories with a
|
|
68
|
+
`.joplin-folder.json`.
|
|
69
|
+
- Sync state (base snapshots for true three-way comparison) lives in
|
|
70
|
+
`.joplin-sync/state.sqlite3` inside the workspace — never committed to Git.
|
|
71
|
+
- All communication uses the documented local
|
|
72
|
+
[Joplin Data API](https://joplinapp.org/help/api/references/rest_api/)
|
|
73
|
+
(Web Clipper service); Joplin's own database and sync targets are never
|
|
74
|
+
touched.
|
|
75
|
+
|
|
76
|
+
## Installation
|
|
77
|
+
|
|
78
|
+
Source, wheel, and zipapp installations require CPython **>= 3.13** on
|
|
79
|
+
Windows or Linux. Native release executables include Python and have no
|
|
80
|
+
external runtime dependencies.
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
python -m pip install "joplin-md-sync==1.5.5"
|
|
84
|
+
# or: pipx install "joplin-md-sync==1.5.5"
|
|
85
|
+
# or download joplin-md-sync.pyz from a release and: python joplin-md-sync.pyz --help
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Native GitHub Release assets:
|
|
89
|
+
|
|
90
|
+
| Platform | Architecture | Asset |
|
|
91
|
+
| --- | --- | --- |
|
|
92
|
+
| Linux | AMD64 | `joplin-md-sync-linux-amd64` |
|
|
93
|
+
| Linux | ARM64 | `joplin-md-sync-linux-arm64` |
|
|
94
|
+
| Windows | AMD64 | `joplin-md-sync-windows-amd64.exe` |
|
|
95
|
+
|
|
96
|
+
On Linux, mark the downloaded executable as executable before running it:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
chmod +x joplin-md-sync-linux-amd64
|
|
100
|
+
./joplin-md-sync-linux-amd64 version
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
From a checkout, everything is driven by the Makefile:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
make venv # runtime venv/ with the CLI installed (venv/bin/joplin-md-sync)
|
|
107
|
+
make venv-dev # Linux quality tools (ruff, mypy, Bandit, pip-audit)
|
|
108
|
+
make venv-test # cross-platform pytest tools
|
|
109
|
+
make venv-package # cross-platform PyInstaller and build tools
|
|
110
|
+
make check # lint + typecheck + full test suite
|
|
111
|
+
make freeze-check # verify all generated dependency locks are current
|
|
112
|
+
make test-live # opt-in real-Joplin MCP + GPT Actions suites; reads ./token
|
|
113
|
+
make test TEST_WORKERS=8 # override automatic parallel test workers
|
|
114
|
+
make package # wheel, sdist, .pyz, current-platform executable, checksums
|
|
115
|
+
make help # all targets
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
The version's single source is the root `.version` file; runtime
|
|
119
|
+
dependencies are declared in `pyproject.toml` (none by design). Direct tools are
|
|
120
|
+
pinned to their latest stable compatible releases in purpose-specific quality,
|
|
121
|
+
test, package, and docs groups. The four committed `pip-compile` locks include
|
|
122
|
+
SHA-256 hashes and are checked for drift in CI; Windows jobs install only the
|
|
123
|
+
cross-platform test or package lock they need.
|
|
124
|
+
|
|
125
|
+
## Five-minute quick start
|
|
126
|
+
|
|
127
|
+
1. In Joplin: *Tools > Options > Web Clipper* — enable the service, copy the
|
|
128
|
+
authorization token.
|
|
129
|
+
2. ```bash
|
|
130
|
+
export JOPLIN_TOKEN=<your token> # Windows: set JOPLIN_TOKEN=...
|
|
131
|
+
joplin-md-sync init --root ./notes
|
|
132
|
+
joplin-md-sync pull --root ./notes
|
|
133
|
+
```
|
|
134
|
+
The token is the only required configuration: the default endpoint
|
|
135
|
+
`http://127.0.0.1:41184` is built in (override with `JOPLIN_BASE_URL`,
|
|
136
|
+
`JOPLIN_PORT`, `--base-url`, or `--port` when needed).
|
|
137
|
+
3. Edit files under `./notes`, then:
|
|
138
|
+
```bash
|
|
139
|
+
joplin-md-sync diff --root ./notes
|
|
140
|
+
joplin-md-sync push --root ./notes --dry-run
|
|
141
|
+
joplin-md-sync push --root ./notes
|
|
142
|
+
```
|
|
143
|
+
4. If both sides changed the same note, you get exit code 2 and a conflict
|
|
144
|
+
bundle: `joplin-md-sync conflicts list` / `conflicts resolve ID --take-local|--take-remote|--merged-file PATH`.
|
|
145
|
+
|
|
146
|
+
## Agent notes repository template
|
|
147
|
+
|
|
148
|
+
[`examples/agent-notes-repository/`](https://github.com/kogeler/joplin-md-sync/tree/main/examples/agent-notes-repository) is a
|
|
149
|
+
copyable starter repository for users who want an agent to work on their
|
|
150
|
+
Joplin notes as ordinary Markdown files. It includes:
|
|
151
|
+
|
|
152
|
+
- a repository-specific `AGENTS.md` with the guarded pull/edit/diff/dry-run/push
|
|
153
|
+
workflow and an optional MCP setup path;
|
|
154
|
+
- a human runbook that starts with enabling Web Clipper in Joplin Desktop and
|
|
155
|
+
storing its token outside the sync workspace;
|
|
156
|
+
- a `.gitignore` for credentials, the local standalone binary, and all sync
|
|
157
|
+
state; and
|
|
158
|
+
- a standard-library installer that downloads the latest compatible native
|
|
159
|
+
release into `.tools/`, verifies `SHA256SUMS.txt`, and validates the binary
|
|
160
|
+
before replacing an existing installation.
|
|
161
|
+
|
|
162
|
+
Start with the template's [README](https://github.com/kogeler/joplin-md-sync/blob/main/examples/agent-notes-repository/README.md),
|
|
163
|
+
not the development instructions for this source checkout.
|
|
164
|
+
|
|
165
|
+
## MCP and ChatGPT Actions service
|
|
166
|
+
|
|
167
|
+
One foreground `joplin-md-sync` process exposes MCP and authenticated REST
|
|
168
|
+
Actions from the same tool registry on different URIs. It starts while Joplin
|
|
169
|
+
is offline and recovers on later calls without a restart.
|
|
170
|
+
|
|
171
|
+
The Actions token is mandatory when Actions are enabled and is reloaded from a
|
|
172
|
+
protected file. MCP bearer authentication remains optional through a separate
|
|
173
|
+
`--auth-token-file`. The headless installer always installs both APIs as one
|
|
174
|
+
`joplin-md-sync.service`; it generates separate mandatory Actions and MCP
|
|
175
|
+
tokens, stores them in protected files, and reports only their paths after a
|
|
176
|
+
successful install. It never creates a separate Actions service.
|
|
177
|
+
|
|
178
|
+
Use [service installation and operations](https://joplin-mcp.romancello.net/user/SERVICE/) for Linux,
|
|
179
|
+
Windows, credentials, URI isolation, and live tests;
|
|
180
|
+
[MCP API](https://joplin-mcp.romancello.net/user/MCP_API/) for the
|
|
181
|
+
tool contract; and the single [ChatGPT Actions end-to-end
|
|
182
|
+
setup](https://joplin-mcp.romancello.net/user/CHATGPT_ACTIONS/) for endpoint validation, schema export, GPT
|
|
183
|
+
Instructions, editor configuration, and acceptance testing.
|
|
184
|
+
|
|
185
|
+
## Architecture overview
|
|
186
|
+
|
|
187
|
+
```
|
|
188
|
+
cli -> planner (pure three-way classification: base/local/remote)
|
|
189
|
+
-> executor (guard -> apply -> verify -> commit base, journaled)
|
|
190
|
+
api: stdlib urllib client for the Joplin Data API (pagination, retries)
|
|
191
|
+
mcp/actions: two HTTP transports -> shared registry/executor -> Joplin service -> api
|
|
192
|
+
state: SQLite base snapshots, conflicts, tombstones, run journal
|
|
193
|
+
workspace: scanning, atomic writes, backups, quarantine, cross-platform lock
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Details in [Architecture](https://joplin-mcp.romancello.net/maintenance/ARCHITECTURE/).
|
|
197
|
+
|
|
198
|
+
## Supported / not supported (v1)
|
|
199
|
+
|
|
200
|
+
| Supported | Out of scope |
|
|
201
|
+
| --- | --- |
|
|
202
|
+
| notes, notebooks (nested), tags, binary attachments | editing settings, revisions, or encryption state |
|
|
203
|
+
| two-way sync with conflict bundles | Nextcloud/WebDAV or any direct sync target |
|
|
204
|
+
| resource download/upload/edit through MCP | replacing Joplin's own device sync |
|
|
205
|
+
| crash recovery, backups, quarantine | automatic text merging (only explicit `--merged-file`) |
|
|
206
|
+
| Windows + Linux, Python 3.13/3.14; MCP daemon | filesystem watch mode, native mobile CLI, self-update, permanent note/notebook deletion |
|
|
207
|
+
|
|
208
|
+
## Versioning
|
|
209
|
+
|
|
210
|
+
Semantic versioning; Git tags `vX.Y.Z` with GitHub releases carrying the
|
|
211
|
+
wheel, sdist, `.pyz`, native executables, and SHA-256 checksums.
|
|
212
|
+
The same wheel and sdist are published to PyPI through Trusted Publishing.
|
|
213
|
+
`joplin-md-sync update-check --json` compares the installed version against
|
|
214
|
+
the latest stable release
|
|
215
|
+
(exit 8 when outdated). JSON output, exit codes, and the state schema are
|
|
216
|
+
versioned and stable across patch releases. See
|
|
217
|
+
[CHANGELOG.md](https://github.com/kogeler/joplin-md-sync/blob/main/CHANGELOG.md).
|
|
218
|
+
|
|
219
|
+
## License
|
|
220
|
+
|
|
221
|
+
[MIT](https://github.com/kogeler/joplin-md-sync/blob/main/LICENSE).
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# joplin-md-sync
|
|
2
|
+
|
|
3
|
+
Safe two-way synchronization between the local [Joplin](https://joplinapp.org/)
|
|
4
|
+
desktop application and an ordinary directory of Markdown files — built
|
|
5
|
+
primarily for **autonomous coding agents** (deterministic JSON output, stable
|
|
6
|
+
exit codes, explicit conflict handling), and perfectly usable by humans.
|
|
7
|
+
|
|
8
|
+
**Documentation:** [joplin-mcp.romancello.net](https://joplin-mcp.romancello.net/)
|
|
9
|
+
|
|
10
|
+
> **Safety first.** The tool never overwrites divergent edits, never deletes
|
|
11
|
+
> anything without an explicit flag, never uses permanent deletion in Joplin,
|
|
12
|
+
> verifies every write after applying it, and journals every mutating run so
|
|
13
|
+
> interrupted syncs are recoverable. `diff` never mutates anything.
|
|
14
|
+
|
|
15
|
+
The test-backed definitions of these guarantees live in the
|
|
16
|
+
[contract catalog](https://joplin-mcp.romancello.net/contracts/).
|
|
17
|
+
|
|
18
|
+
If you are an agent (or configuring one), start with
|
|
19
|
+
**[AGENTS.md](https://github.com/kogeler/joplin-md-sync/blob/main/AGENTS.md)**.
|
|
20
|
+
|
|
21
|
+
## How it works
|
|
22
|
+
|
|
23
|
+
- Notes are plain `.md` files; each carries a one-line metadata header with
|
|
24
|
+
the Joplin id, title, and tags. Notebooks are directories with a
|
|
25
|
+
`.joplin-folder.json`.
|
|
26
|
+
- Sync state (base snapshots for true three-way comparison) lives in
|
|
27
|
+
`.joplin-sync/state.sqlite3` inside the workspace — never committed to Git.
|
|
28
|
+
- All communication uses the documented local
|
|
29
|
+
[Joplin Data API](https://joplinapp.org/help/api/references/rest_api/)
|
|
30
|
+
(Web Clipper service); Joplin's own database and sync targets are never
|
|
31
|
+
touched.
|
|
32
|
+
|
|
33
|
+
## Installation
|
|
34
|
+
|
|
35
|
+
Source, wheel, and zipapp installations require CPython **>= 3.13** on
|
|
36
|
+
Windows or Linux. Native release executables include Python and have no
|
|
37
|
+
external runtime dependencies.
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
python -m pip install "joplin-md-sync==1.5.5"
|
|
41
|
+
# or: pipx install "joplin-md-sync==1.5.5"
|
|
42
|
+
# or download joplin-md-sync.pyz from a release and: python joplin-md-sync.pyz --help
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Native GitHub Release assets:
|
|
46
|
+
|
|
47
|
+
| Platform | Architecture | Asset |
|
|
48
|
+
| --- | --- | --- |
|
|
49
|
+
| Linux | AMD64 | `joplin-md-sync-linux-amd64` |
|
|
50
|
+
| Linux | ARM64 | `joplin-md-sync-linux-arm64` |
|
|
51
|
+
| Windows | AMD64 | `joplin-md-sync-windows-amd64.exe` |
|
|
52
|
+
|
|
53
|
+
On Linux, mark the downloaded executable as executable before running it:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
chmod +x joplin-md-sync-linux-amd64
|
|
57
|
+
./joplin-md-sync-linux-amd64 version
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
From a checkout, everything is driven by the Makefile:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
make venv # runtime venv/ with the CLI installed (venv/bin/joplin-md-sync)
|
|
64
|
+
make venv-dev # Linux quality tools (ruff, mypy, Bandit, pip-audit)
|
|
65
|
+
make venv-test # cross-platform pytest tools
|
|
66
|
+
make venv-package # cross-platform PyInstaller and build tools
|
|
67
|
+
make check # lint + typecheck + full test suite
|
|
68
|
+
make freeze-check # verify all generated dependency locks are current
|
|
69
|
+
make test-live # opt-in real-Joplin MCP + GPT Actions suites; reads ./token
|
|
70
|
+
make test TEST_WORKERS=8 # override automatic parallel test workers
|
|
71
|
+
make package # wheel, sdist, .pyz, current-platform executable, checksums
|
|
72
|
+
make help # all targets
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
The version's single source is the root `.version` file; runtime
|
|
76
|
+
dependencies are declared in `pyproject.toml` (none by design). Direct tools are
|
|
77
|
+
pinned to their latest stable compatible releases in purpose-specific quality,
|
|
78
|
+
test, package, and docs groups. The four committed `pip-compile` locks include
|
|
79
|
+
SHA-256 hashes and are checked for drift in CI; Windows jobs install only the
|
|
80
|
+
cross-platform test or package lock they need.
|
|
81
|
+
|
|
82
|
+
## Five-minute quick start
|
|
83
|
+
|
|
84
|
+
1. In Joplin: *Tools > Options > Web Clipper* — enable the service, copy the
|
|
85
|
+
authorization token.
|
|
86
|
+
2. ```bash
|
|
87
|
+
export JOPLIN_TOKEN=<your token> # Windows: set JOPLIN_TOKEN=...
|
|
88
|
+
joplin-md-sync init --root ./notes
|
|
89
|
+
joplin-md-sync pull --root ./notes
|
|
90
|
+
```
|
|
91
|
+
The token is the only required configuration: the default endpoint
|
|
92
|
+
`http://127.0.0.1:41184` is built in (override with `JOPLIN_BASE_URL`,
|
|
93
|
+
`JOPLIN_PORT`, `--base-url`, or `--port` when needed).
|
|
94
|
+
3. Edit files under `./notes`, then:
|
|
95
|
+
```bash
|
|
96
|
+
joplin-md-sync diff --root ./notes
|
|
97
|
+
joplin-md-sync push --root ./notes --dry-run
|
|
98
|
+
joplin-md-sync push --root ./notes
|
|
99
|
+
```
|
|
100
|
+
4. If both sides changed the same note, you get exit code 2 and a conflict
|
|
101
|
+
bundle: `joplin-md-sync conflicts list` / `conflicts resolve ID --take-local|--take-remote|--merged-file PATH`.
|
|
102
|
+
|
|
103
|
+
## Agent notes repository template
|
|
104
|
+
|
|
105
|
+
[`examples/agent-notes-repository/`](https://github.com/kogeler/joplin-md-sync/tree/main/examples/agent-notes-repository) is a
|
|
106
|
+
copyable starter repository for users who want an agent to work on their
|
|
107
|
+
Joplin notes as ordinary Markdown files. It includes:
|
|
108
|
+
|
|
109
|
+
- a repository-specific `AGENTS.md` with the guarded pull/edit/diff/dry-run/push
|
|
110
|
+
workflow and an optional MCP setup path;
|
|
111
|
+
- a human runbook that starts with enabling Web Clipper in Joplin Desktop and
|
|
112
|
+
storing its token outside the sync workspace;
|
|
113
|
+
- a `.gitignore` for credentials, the local standalone binary, and all sync
|
|
114
|
+
state; and
|
|
115
|
+
- a standard-library installer that downloads the latest compatible native
|
|
116
|
+
release into `.tools/`, verifies `SHA256SUMS.txt`, and validates the binary
|
|
117
|
+
before replacing an existing installation.
|
|
118
|
+
|
|
119
|
+
Start with the template's [README](https://github.com/kogeler/joplin-md-sync/blob/main/examples/agent-notes-repository/README.md),
|
|
120
|
+
not the development instructions for this source checkout.
|
|
121
|
+
|
|
122
|
+
## MCP and ChatGPT Actions service
|
|
123
|
+
|
|
124
|
+
One foreground `joplin-md-sync` process exposes MCP and authenticated REST
|
|
125
|
+
Actions from the same tool registry on different URIs. It starts while Joplin
|
|
126
|
+
is offline and recovers on later calls without a restart.
|
|
127
|
+
|
|
128
|
+
The Actions token is mandatory when Actions are enabled and is reloaded from a
|
|
129
|
+
protected file. MCP bearer authentication remains optional through a separate
|
|
130
|
+
`--auth-token-file`. The headless installer always installs both APIs as one
|
|
131
|
+
`joplin-md-sync.service`; it generates separate mandatory Actions and MCP
|
|
132
|
+
tokens, stores them in protected files, and reports only their paths after a
|
|
133
|
+
successful install. It never creates a separate Actions service.
|
|
134
|
+
|
|
135
|
+
Use [service installation and operations](https://joplin-mcp.romancello.net/user/SERVICE/) for Linux,
|
|
136
|
+
Windows, credentials, URI isolation, and live tests;
|
|
137
|
+
[MCP API](https://joplin-mcp.romancello.net/user/MCP_API/) for the
|
|
138
|
+
tool contract; and the single [ChatGPT Actions end-to-end
|
|
139
|
+
setup](https://joplin-mcp.romancello.net/user/CHATGPT_ACTIONS/) for endpoint validation, schema export, GPT
|
|
140
|
+
Instructions, editor configuration, and acceptance testing.
|
|
141
|
+
|
|
142
|
+
## Architecture overview
|
|
143
|
+
|
|
144
|
+
```
|
|
145
|
+
cli -> planner (pure three-way classification: base/local/remote)
|
|
146
|
+
-> executor (guard -> apply -> verify -> commit base, journaled)
|
|
147
|
+
api: stdlib urllib client for the Joplin Data API (pagination, retries)
|
|
148
|
+
mcp/actions: two HTTP transports -> shared registry/executor -> Joplin service -> api
|
|
149
|
+
state: SQLite base snapshots, conflicts, tombstones, run journal
|
|
150
|
+
workspace: scanning, atomic writes, backups, quarantine, cross-platform lock
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Details in [Architecture](https://joplin-mcp.romancello.net/maintenance/ARCHITECTURE/).
|
|
154
|
+
|
|
155
|
+
## Supported / not supported (v1)
|
|
156
|
+
|
|
157
|
+
| Supported | Out of scope |
|
|
158
|
+
| --- | --- |
|
|
159
|
+
| notes, notebooks (nested), tags, binary attachments | editing settings, revisions, or encryption state |
|
|
160
|
+
| two-way sync with conflict bundles | Nextcloud/WebDAV or any direct sync target |
|
|
161
|
+
| resource download/upload/edit through MCP | replacing Joplin's own device sync |
|
|
162
|
+
| crash recovery, backups, quarantine | automatic text merging (only explicit `--merged-file`) |
|
|
163
|
+
| Windows + Linux, Python 3.13/3.14; MCP daemon | filesystem watch mode, native mobile CLI, self-update, permanent note/notebook deletion |
|
|
164
|
+
|
|
165
|
+
## Versioning
|
|
166
|
+
|
|
167
|
+
Semantic versioning; Git tags `vX.Y.Z` with GitHub releases carrying the
|
|
168
|
+
wheel, sdist, `.pyz`, native executables, and SHA-256 checksums.
|
|
169
|
+
The same wheel and sdist are published to PyPI through Trusted Publishing.
|
|
170
|
+
`joplin-md-sync update-check --json` compares the installed version against
|
|
171
|
+
the latest stable release
|
|
172
|
+
(exit 8 when outdated). JSON output, exit codes, and the state schema are
|
|
173
|
+
versioned and stable across patch releases. See
|
|
174
|
+
[CHANGELOG.md](https://github.com/kogeler/joplin-md-sync/blob/main/CHANGELOG.md).
|
|
175
|
+
|
|
176
|
+
## License
|
|
177
|
+
|
|
178
|
+
[MIT](https://github.com/kogeler/joplin-md-sync/blob/main/LICENSE).
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
<!-- Copyright (c) 2026 kogeler. SPDX-License-Identifier: MIT. -->
|
|
2
|
+
|
|
3
|
+
# Agent Interface Contract
|
|
4
|
+
|
|
5
|
+
## Assertions
|
|
6
|
+
|
|
7
|
+
### `AIF-001` - One immutable registry owns every agent operation
|
|
8
|
+
|
|
9
|
+
**Contract:** MCP and GPT Actions MUST derive tool names, schemas, effects,
|
|
10
|
+
handlers, and exposure from one ordered immutable registry and invoke handlers
|
|
11
|
+
through one validating executor. HTTP adapters MUST NOT maintain a second
|
|
12
|
+
operation list or bypass schema validation and failure classification.
|
|
13
|
+
|
|
14
|
+
**Evidence:**
|
|
15
|
+
|
|
16
|
+
- [`test_registry_is_ordered_exact_and_deeply_immutable`](../../tests/unit/test_tool_registry.py) - `tests/unit/test_tool_registry.py::test_registry_is_ordered_exact_and_deeply_immutable`
|
|
17
|
+
- [`test_effect_and_route_are_centralized`](../../tests/unit/test_tool_registry.py) - `tests/unit/test_tool_registry.py::test_effect_and_route_are_centralized`
|
|
18
|
+
- [`test_executor_validates_and_preserves_domain_errors`](../../tests/unit/test_tool_registry.py) - `tests/unit/test_tool_registry.py::test_executor_validates_and_preserves_domain_errors`
|
|
19
|
+
- [`test_generated_operations_match_exposed_registry`](../../tests/unit/test_gpt_openapi.py) - `tests/unit/test_gpt_openapi.py::test_generated_operations_match_exposed_registry`
|
|
20
|
+
|
|
21
|
+
### `AIF-002` - MCP follows the advertised Streamable HTTP lifecycle
|
|
22
|
+
|
|
23
|
+
**Contract:** `mcp serve` MUST expose MCP Streamable HTTP on loopback
|
|
24
|
+
`/mcp` by default, negotiate only its supported protocol versions, implement
|
|
25
|
+
initialize/initialized, tools/list, and tools/call, and return both text content
|
|
26
|
+
and `structuredContent`. The server MUST advertise itself through CLI
|
|
27
|
+
capabilities and MUST NOT require a Markdown workspace.
|
|
28
|
+
|
|
29
|
+
**Evidence:**
|
|
30
|
+
|
|
31
|
+
- [`test_lifecycle_and_transport_contract`](../../tests/integration/test_mcp_server.py) - `tests/integration/test_mcp_server.py::McpHttpTest::test_lifecycle_and_transport_contract`
|
|
32
|
+
- [`test_capabilities_advertise_mcp`](../../tests/integration/test_mcp_server.py) - `tests/integration/test_mcp_server.py::McpCliSafetyTest::test_capabilities_advertise_mcp`
|
|
33
|
+
|
|
34
|
+
### `AIF-003` - MCP exposes complete Joplin object workflows
|
|
35
|
+
|
|
36
|
+
**Contract:** MCP MUST support validated note, notebook, tag, and resource
|
|
37
|
+
read/write workflows, relationship traversal, search, trash/restore for notes
|
|
38
|
+
and notebooks, and permanent deletion only where Joplin has no trash API.
|
|
39
|
+
Binary content MUST enter and leave as bounded base64 data, never a server-side
|
|
40
|
+
path. Explicit create operations MUST compare their normalized natural
|
|
41
|
+
identity before mutation, including matching trashed objects, and MUST return a
|
|
42
|
+
non-retryable `*_ALREADY_EXISTS` error with every existing ID and the matching
|
|
43
|
+
update tool rather than create or silently reuse a duplicate. Create preflight
|
|
44
|
+
and mutation MUST be serialized within one bridge process.
|
|
45
|
+
|
|
46
|
+
**Evidence:**
|
|
47
|
+
|
|
48
|
+
- [`test_note_crud_metadata_tags_and_search`](../../tests/integration/test_mcp_server.py) - `tests/integration/test_mcp_server.py::McpHttpTest::test_note_crud_metadata_tags_and_search`
|
|
49
|
+
- [`test_notebook_crud_nesting_notes_trash_and_restore`](../../tests/integration/test_mcp_server.py) - `tests/integration/test_mcp_server.py::McpHttpTest::test_notebook_crud_nesting_notes_trash_and_restore`
|
|
50
|
+
- [`test_tag_crud_listing_and_note_relations`](../../tests/integration/test_mcp_server.py) - `tests/integration/test_mcp_server.py::McpHttpTest::test_tag_crud_listing_and_note_relations`
|
|
51
|
+
- [`test_resource_crud_content_and_note_relations`](../../tests/integration/test_mcp_server.py) - `tests/integration/test_mcp_server.py::McpHttpTest::test_resource_crud_content_and_note_relations`
|
|
52
|
+
- [`test_create_note_from_html_and_with_binary_attachments`](../../tests/integration/test_mcp_server.py) - `tests/integration/test_mcp_server.py::McpHttpTest::test_create_note_from_html_and_with_binary_attachments`
|
|
53
|
+
- [`test_create_tools_reject_existing_object_identities`](../../tests/integration/test_mcp_server.py) - `tests/integration/test_mcp_server.py::McpHttpTest::test_create_tools_reject_existing_object_identities`
|
|
54
|
+
- [`test_notebook_title_resolves_only_one_root_destination`](../../tests/integration/test_mcp_server.py) - `tests/integration/test_mcp_server.py::McpHttpTest::test_notebook_title_resolves_only_one_root_destination`
|
|
55
|
+
- [`test_concurrent_note_create_allows_exactly_one_object`](../../tests/integration/test_mcp_server.py) - `tests/integration/test_mcp_server.py::McpHttpTest::test_concurrent_note_create_allows_exactly_one_object`
|
|
56
|
+
- [`test_create_rejects_matching_trashed_note_and_notebook`](../../tests/integration/test_mcp_server.py) - `tests/integration/test_mcp_server.py::McpHttpTest::test_create_rejects_matching_trashed_note_and_notebook`
|
|
57
|
+
- [`test_existing_note_create_returns_conflict_with_update_target`](../../tests/integration/test_gpt_actions.py) - `tests/integration/test_gpt_actions.py::GptActionsHttpTest::test_existing_note_create_returns_conflict_with_update_target`
|
|
58
|
+
- [`test_ambiguous_notebook_title_returns_candidate_ids`](../../tests/integration/test_gpt_actions.py) - `tests/integration/test_gpt_actions.py::GptActionsHttpTest::test_ambiguous_notebook_title_returns_candidate_ids`
|
|
59
|
+
- [`test_02_explicit_creates_reject_existing_natural_identities`](../../tests_live/test_mcp_live.py) - `tests_live/test_mcp_live.py::LiveMcpTest::test_02_explicit_creates_reject_existing_natural_identities`
|
|
60
|
+
- [`test_02_explicit_creates_reject_existing_natural_identities`](../../tests_live/test_gpt_actions_live.py) - `tests_live/test_gpt_actions_live.py::LiveGptActionsTest::test_02_explicit_creates_reject_existing_natural_identities`
|
|
61
|
+
|
|
62
|
+
### `AIF-004` - Invalid operations fail before side effects
|
|
63
|
+
|
|
64
|
+
**Contract:** Tool definitions and instances MUST pass the supported JSON
|
|
65
|
+
Schema subset before handler execution. Unknown objects, invalid content,
|
|
66
|
+
unsupported combinations, and Joplin failures MUST return structured stable
|
|
67
|
+
domain errors rather than transport crashes or partial writes.
|
|
68
|
+
|
|
69
|
+
**Evidence:**
|
|
70
|
+
|
|
71
|
+
- [`test_schema_definition_and_instance_validation`](../../tests/unit/test_tool_registry.py) - `tests/unit/test_tool_registry.py::test_schema_definition_and_instance_validation`
|
|
72
|
+
- [`test_entity_and_content_validation_errors_are_structured`](../../tests/integration/test_mcp_server.py) - `tests/integration/test_mcp_server.py::McpHttpTest::test_entity_and_content_validation_errors_are_structured`
|
|
73
|
+
- [`test_http_and_schema_validation_precede_side_effects`](../../tests/integration/test_gpt_actions.py) - `tests/integration/test_gpt_actions.py::GptActionsHttpTest::test_http_and_schema_validation_precede_side_effects`
|
|
74
|
+
|
|
75
|
+
### `AIF-005` - Joplin outages do not terminate the bridge
|
|
76
|
+
|
|
77
|
+
**Contract:** Binding the agent listener MUST NOT require Joplin availability.
|
|
78
|
+
Each call MAY perform a bounded availability wait and retry reads. An outage
|
|
79
|
+
MUST return a retryable structured tool error and later calls MUST recover.
|
|
80
|
+
Writes MUST be sent once after preflight; ambiguous outcomes MUST be
|
|
81
|
+
non-retryable and exposed for operator inspection.
|
|
82
|
+
|
|
83
|
+
**Evidence:**
|
|
84
|
+
|
|
85
|
+
- [`test_joplin_outage_is_retryable_tool_error_and_server_recovers`](../../tests/integration/test_mcp_server.py) - `tests/integration/test_mcp_server.py::McpHttpTest::test_joplin_outage_is_retryable_tool_error_and_server_recovers`
|
|
86
|
+
- [`test_executor_classifies_expected_failures`](../../tests/unit/test_tool_registry.py) - `tests/unit/test_tool_registry.py::test_executor_classifies_expected_failures`
|
|
87
|
+
|
|
88
|
+
### `AIF-006` - Actions authentication is independent and fail-closed
|
|
89
|
+
|
|
90
|
+
**Contract:** GPT Actions MUST remain disabled unless explicitly enabled with
|
|
91
|
+
a dedicated protected bearer file. Authentication MUST occur before route
|
|
92
|
+
lookup, files MUST be re-read per request for rotation, malformed or duplicate
|
|
93
|
+
authorization MUST fail closed, and Joplin, MCP, and Actions credentials MUST
|
|
94
|
+
remain independent.
|
|
95
|
+
|
|
96
|
+
**Evidence:**
|
|
97
|
+
|
|
98
|
+
- [`test_authentication_is_uniform_independent_and_rotatable`](../../tests/integration/test_gpt_actions.py) - `tests/integration/test_gpt_actions.py::GptActionsHttpTest::test_authentication_is_uniform_independent_and_rotatable`
|
|
99
|
+
- [`test_malformed_and_duplicate_authorization_fail_closed`](../../tests/integration/test_gpt_actions.py) - `tests/integration/test_gpt_actions.py::GptActionsHttpTest::test_malformed_and_duplicate_authorization_fail_closed`
|
|
100
|
+
- [`test_route_is_fail_closed_and_auth_hides_route_details`](../../tests/integration/test_gpt_actions.py) - `tests/integration/test_gpt_actions.py::GptActionsHttpTest::test_route_is_fail_closed_and_auth_hides_route_details`
|
|
101
|
+
- [`test_mcp_actions_and_joplin_credentials_are_independent`](../../tests/integration/test_gpt_actions.py) - `tests/integration/test_gpt_actions.py::GptActionsHttpTest::test_mcp_actions_and_joplin_credentials_are_independent`
|
|
102
|
+
|
|
103
|
+
### `AIF-007` - OpenAPI is generated, deterministic, and secret-free
|
|
104
|
+
|
|
105
|
+
**Contract:** The Actions OpenAPI 3.1 document MUST be generated from the
|
|
106
|
+
exposed registry for one strict production HTTPS origin. It MUST be
|
|
107
|
+
deterministic and contain no credential, note data, local path, upstream Joplin
|
|
108
|
+
URL, MCP route, or health route. Disabled operations MUST not be exported.
|
|
109
|
+
|
|
110
|
+
**Evidence:**
|
|
111
|
+
|
|
112
|
+
- [`test_contract_is_deterministic_and_secret_free`](../../tests/unit/test_gpt_openapi.py) - `tests/unit/test_gpt_openapi.py::test_contract_is_deterministic_and_secret_free`
|
|
113
|
+
- [`test_production_server_url_is_strict`](../../tests/unit/test_gpt_openapi.py) - `tests/unit/test_gpt_openapi.py::test_production_server_url_is_strict`
|
|
114
|
+
- [`test_cli_exports_contract_and_advertises_optional_transport`](../../tests/unit/test_gpt_openapi.py) - `tests/unit/test_gpt_openapi.py::test_cli_exports_contract_and_advertises_optional_transport`
|
|
115
|
+
|
|
116
|
+
### `AIF-008` - Public transports enforce bounded resource use
|
|
117
|
+
|
|
118
|
+
**Contract:** MCP and Actions MUST bound request size, decoded resource size,
|
|
119
|
+
JSON nesting, active handlers, and stalled connections. Actions MUST also
|
|
120
|
+
bound response size, concurrent execution, and authenticated request rate.
|
|
121
|
+
Oversized successful write results MAY be replaced by an explicit omitted
|
|
122
|
+
result but MUST NOT be reported as a failed write.
|
|
123
|
+
|
|
124
|
+
**Evidence:**
|
|
125
|
+
|
|
126
|
+
- [`test_pre_auth_connection_count_is_bounded`](../../tests/integration/test_mcp_server.py) - `tests/integration/test_mcp_server.py::McpHttpTest::test_pre_auth_connection_count_is_bounded`
|
|
127
|
+
- [`test_request_limit_and_result_limit_semantics`](../../tests/integration/test_gpt_actions.py) - `tests/integration/test_gpt_actions.py::GptActionsHttpTest::test_request_limit_and_result_limit_semantics`
|
|
128
|
+
- [`test_concurrency_capacity_and_authenticated_rate_limit`](../../tests/integration/test_gpt_actions.py) - `tests/integration/test_gpt_actions.py::GptActionsHttpTest::test_concurrency_capacity_and_authenticated_rate_limit`
|
|
129
|
+
- [`test_json_nesting_limit_is_independent_of_parser_recursion_behavior`](../../tests/unit/test_json_safety.py) - `tests/unit/test_json_safety.py::test_json_nesting_limit_is_independent_of_parser_recursion_behavior`
|