ojs-mcp 0.2.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.
Files changed (42) hide show
  1. ojs_mcp-0.2.0/.gitignore +36 -0
  2. ojs_mcp-0.2.0/CHANGELOG.md +73 -0
  3. ojs_mcp-0.2.0/LICENSE +21 -0
  4. ojs_mcp-0.2.0/PKG-INFO +202 -0
  5. ojs_mcp-0.2.0/README.md +163 -0
  6. ojs_mcp-0.2.0/demo/README.md +124 -0
  7. ojs_mcp-0.2.0/pyproject.toml +88 -0
  8. ojs_mcp-0.2.0/src/ojs_mcp/__init__.py +3 -0
  9. ojs_mcp-0.2.0/src/ojs_mcp/auth.py +163 -0
  10. ojs_mcp-0.2.0/src/ojs_mcp/catalog.py +233 -0
  11. ojs_mcp-0.2.0/src/ojs_mcp/client.py +330 -0
  12. ojs_mcp-0.2.0/src/ojs_mcp/config.py +121 -0
  13. ojs_mcp-0.2.0/src/ojs_mcp/data/__init__.py +0 -0
  14. ojs_mcp-0.2.0/src/ojs_mcp/data/endpoints.compact.txt +147 -0
  15. ojs_mcp-0.2.0/src/ojs_mcp/dictionaries.py +157 -0
  16. ojs_mcp-0.2.0/src/ojs_mcp/exceptions.py +88 -0
  17. ojs_mcp-0.2.0/src/ojs_mcp/fields.py +349 -0
  18. ojs_mcp-0.2.0/src/ojs_mcp/http_transport.py +345 -0
  19. ojs_mcp-0.2.0/src/ojs_mcp/mcp_errors.py +120 -0
  20. ojs_mcp-0.2.0/src/ojs_mcp/passthrough.py +158 -0
  21. ojs_mcp-0.2.0/src/ojs_mcp/prompts.py +155 -0
  22. ojs_mcp-0.2.0/src/ojs_mcp/resources.py +101 -0
  23. ojs_mcp-0.2.0/src/ojs_mcp/server.py +151 -0
  24. ojs_mcp-0.2.0/src/ojs_mcp/session_login.py +592 -0
  25. ojs_mcp-0.2.0/src/ojs_mcp/tools_read.py +1129 -0
  26. ojs_mcp-0.2.0/src/ojs_mcp/tools_write.py +531 -0
  27. ojs_mcp-0.2.0/tests/conftest.py +0 -0
  28. ojs_mcp-0.2.0/tests/test_auth.py +116 -0
  29. ojs_mcp-0.2.0/tests/test_catalog.py +167 -0
  30. ojs_mcp-0.2.0/tests/test_client.py +243 -0
  31. ojs_mcp-0.2.0/tests/test_config.py +128 -0
  32. ojs_mcp-0.2.0/tests/test_data.py +27 -0
  33. ojs_mcp-0.2.0/tests/test_dictionaries.py +92 -0
  34. ojs_mcp-0.2.0/tests/test_http_transport.py +828 -0
  35. ojs_mcp-0.2.0/tests/test_mcp_errors.py +130 -0
  36. ojs_mcp-0.2.0/tests/test_passthrough.py +159 -0
  37. ojs_mcp-0.2.0/tests/test_prompts_resources.py +178 -0
  38. ojs_mcp-0.2.0/tests/test_server.py +331 -0
  39. ojs_mcp-0.2.0/tests/test_session_auth.py +419 -0
  40. ojs_mcp-0.2.0/tests/test_session_login.py +345 -0
  41. ojs_mcp-0.2.0/tests/test_tools_read.py +888 -0
  42. ojs_mcp-0.2.0/tests/test_tools_write.py +399 -0
@@ -0,0 +1,36 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ *.egg
6
+ build/
7
+ dist/
8
+ .eggs/
9
+
10
+ # venv / uv
11
+ .venv/
12
+ venv/
13
+ .python-version
14
+
15
+ # documentation (MkDocs build)
16
+ site/
17
+
18
+ # tests / tooling
19
+ .pytest_cache/
20
+ .ruff_cache/
21
+ .coverage
22
+ htmlcov/
23
+ .mypy_cache/
24
+
25
+ # IDE / OS
26
+ .idea/
27
+ .vscode/
28
+ .DS_Store
29
+
30
+ # Local credentials (the repository is public; the project is configured
31
+ # exclusively through environment variables — see docs/installation.md).
32
+ # `.env*` (not a bare `.env`), to also catch `.env.local`/`.env.production`.
33
+ .env*
34
+
35
+ # MCPB bundle built locally (see docs/installation.md, option 2).
36
+ *.mcpb
@@ -0,0 +1,73 @@
1
+ # Changelog
2
+
3
+ Format based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
4
+ versioning follows [Semantic Versioning](https://semver.org/).
5
+
6
+ ## [0.2.0] — 2026-09-10
7
+
8
+ ### Added
9
+
10
+ - `demo/` — a throwaway OJS 3.5 stack (Docker: OJS 3.5.0-5 + MariaDB)
11
+ with fictional seed content, plus two MCP clients that launch the
12
+ server over stdio and drive it: `demo/check_api.py` for the sixteen
13
+ read tools and the `ojs_request` escape hatch, `demo/check_writes.py`
14
+ for the five write tools. Every tool has now been exercised against a
15
+ live OJS instance, the write tools by reading back the resulting state
16
+ (announcement created, metadata edited, a submission moved to
17
+ production by two editorial decisions, published, unpublished).
18
+ - A regression test for `python -m ojs_mcp.server`.
19
+
20
+ ### Fixed
21
+
22
+ - `python -m ojs_mcp.server` silently did nothing: the module had no
23
+ `__main__` block, so it defined `main()` and exited. An MCP client
24
+ launched that way saw only the stdio pipe close, and reported a bare
25
+ connection error.
26
+
27
+ ### Documentation
28
+
29
+ - Authentication: a web server that does not forward the `Authorization`
30
+ header to PHP (Apache's default, including in the official
31
+ `pkpofficial/ojs` Docker images) makes OJS refuse every token with a
32
+ 401 that is byte-for-byte identical to an anonymous refusal. Documented
33
+ the symptom, how to tell it apart from a missing journal role, and the
34
+ one-line fix.
35
+ - Status: the README no longer describes the project as unverified
36
+ against a live instance, because every tool now has been. What remains
37
+ unverified is named instead: login/password authentication, network
38
+ mode (`OJS_MCP_TRANSPORT=http`), and OJS 3.6.
39
+
40
+ ## [0.1.0] — 2026-09-09
41
+
42
+ First release.
43
+
44
+ ### Added
45
+
46
+ - An MCP server for the authenticated Open Journal Systems REST API
47
+ (OJS 3.5/3.6) — sixteen read tools (submissions, publications, issues,
48
+ sections, users, reviewers, statistics, DOI, identity, and journal
49
+ catalog) plus the `ojs_request` escape hatch below (seventeen always
50
+ registered in total), and five write tools (editorial decisions,
51
+ publication metadata editing, publish/unpublish, announcements)
52
+ registered only when `OJS_ALLOW_WRITES=1` is explicitly set.
53
+ - The `ojs_request` escape hatch for calling any OJS REST API endpoint
54
+ outside the curated tool list, with path validation and (without
55
+ writes enabled) restriction to read requests.
56
+ - Two authentication strategies: an API token (`OJS_API_TOKEN`) and
57
+ login/password form authentication (`OJS_USERNAME`/`OJS_PASSWORD`)
58
+ with CAPTCHA/ALTCHA and forced-password-change detection, session CSRF
59
+ token management, and automatic retry after session expiry.
60
+ - Multi-instance support: a single binary serves any OJS deployment via
61
+ `OJS_BASE_URL`, with support for multiple journals on one instance
62
+ (the `journal` parameter, the `list_journals` tool).
63
+ - Network mode `OJS_MCP_TRANSPORT=http` (streamable HTTP) to host a
64
+ single process for many users at once, each with their own OJS token
65
+ passed in the request header — the server stores no client
66
+ credentials at all; with `Origin` validation
67
+ (`OJS_MCP_ALLOWED_ORIGINS`) protecting against DNS rebinding.
68
+ - An MCPB bundle (`.mcpb`) for one-click installation in desktop MCP
69
+ clients, and a PyPI release via OIDC.
70
+ - Full documentation (installation, configuration, authentication, tool
71
+ list, multi-tenant hosting) published with MkDocs Material.
72
+
73
+ [0.1.0]: https://github.com/mpasternak/ojs-mcp/releases/tag/v0.1.0
ojs_mcp-0.2.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Michał Pasternak <michal.dtz@gmail.com>
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.
ojs_mcp-0.2.0/PKG-INFO ADDED
@@ -0,0 +1,202 @@
1
+ Metadata-Version: 2.5
2
+ Name: ojs-mcp
3
+ Version: 0.2.0
4
+ Summary: MCP server for the authenticated Open Journal Systems REST API (OJS 3.5/3.6)
5
+ Project-URL: Homepage, https://github.com/mpasternak/ojs-mcp
6
+ Project-URL: Repository, https://github.com/mpasternak/ojs-mcp
7
+ Project-URL: Documentation, https://mpasternak.github.io/ojs-mcp/
8
+ Project-URL: Issues, https://github.com/mpasternak/ojs-mcp/issues
9
+ Project-URL: Changelog, https://github.com/mpasternak/ojs-mcp/blob/main/CHANGELOG.md
10
+ Author-email: Michał Pasternak <michal.dtz@gmail.com>
11
+ License: MIT
12
+ License-File: LICENSE
13
+ Keywords: api,llm,mcp,ojs,open-journal-systems,pkp
14
+ Classifier: Development Status :: 4 - Beta
15
+ Classifier: Environment :: Console
16
+ Classifier: Intended Audience :: Developers
17
+ Classifier: License :: OSI Approved :: MIT License
18
+ Classifier: Operating System :: OS Independent
19
+ Classifier: Programming Language :: Python :: 3
20
+ Classifier: Programming Language :: Python :: 3.10
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Programming Language :: Python :: 3.13
24
+ Classifier: Topic :: Internet :: WWW/HTTP
25
+ Requires-Python: >=3.10
26
+ Requires-Dist: anyio>=4.0
27
+ Requires-Dist: httpx>=0.27
28
+ Requires-Dist: mcp<3,>=2.2
29
+ Requires-Dist: starlette>=0.27
30
+ Requires-Dist: uvicorn>=0.30
31
+ Provides-Extra: dev
32
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
33
+ Requires-Dist: pytest>=8.0; extra == 'dev'
34
+ Requires-Dist: respx>=0.21; extra == 'dev'
35
+ Requires-Dist: ruff>=0.6; extra == 'dev'
36
+ Provides-Extra: docs
37
+ Requires-Dist: mkdocs-material>=9.5; extra == 'docs'
38
+ Description-Content-Type: text/markdown
39
+
40
+ # ojs-mcp
41
+
42
+ [![tests](https://github.com/mpasternak/ojs-mcp/actions/workflows/tests.yml/badge.svg)](https://github.com/mpasternak/ojs-mcp/actions/workflows/tests.yml)
43
+ [![PyPI](https://img.shields.io/pypi/v/ojs-mcp.svg)](https://pypi.org/project/ojs-mcp/)
44
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/mpasternak/ojs-mcp/blob/main/LICENSE)
45
+
46
+ An [MCP](https://modelcontextprotocol.io/) server for the authenticated
47
+ REST API of [Open Journal Systems](https://pkp.sfu.ca/ojs/) (OJS
48
+ 3.5/3.6). Connected to an MCP client (Claude Desktop, Claude Code, and
49
+ others), it gives the model access to a journal's submissions, reviews,
50
+ issues, and editorial statistics — and, with writes explicitly enabled,
51
+ also to making editorial decisions, publishing, and editing metadata.
52
+
53
+ ## Verified against a live OJS 3.5
54
+
55
+ Every tool has been exercised end-to-end against a real OJS 3.5.0-5
56
+ instance — the sixteen read tools, the `ojs_request` escape hatch, and all
57
+ five write tools. The write tools were checked by reading back the state
58
+ OJS actually ended up in, not by trusting the status code: an announcement
59
+ created, publication metadata edited, a submission carried from the
60
+ submission stage to production by two editorial decisions, then published
61
+ and unpublished again.
62
+
63
+ That instance is reproducible. [`demo/`](https://github.com/mpasternak/ojs-mcp/tree/main/demo)
64
+ holds the Docker stack, the fictional seed content, and the two scripts that
65
+ drive this server against it as a real MCP client — `check_api.py` for the
66
+ read tools, `check_writes.py` for the write tools.
67
+
68
+ Three things have **not** been exercised against a live server, and still
69
+ rest on what the PKP source (`pkp-lib`, `pkp/ojs`) says rather than on
70
+ observed behavior: login/password authentication
71
+ (`OJS_USERNAME`/`OJS_PASSWORD`), network mode (`OJS_MCP_TRANSPORT=http`),
72
+ and OJS 3.6 — only 3.5 has been tested. The unit test suite likewise runs
73
+ entirely against stubbed HTTP responses (via `respx`).
74
+
75
+ Writes change real journal data, which is why `OJS_ALLOW_WRITES=1` is off by
76
+ default. Verify each tool's effect on a test journal before pointing it at
77
+ production.
78
+
79
+ ## Quick start
80
+
81
+ ```bash
82
+ OJS_BASE_URL=https://journals.your-university.edu OJS_API_TOKEN=your-token uvx ojs-mcp
83
+ ```
84
+
85
+ No separate install step needed — [`uv`](https://docs.astral.sh/uv/)
86
+ downloads and runs the package on first launch. In practice your MCP
87
+ client calls this command for you, using the configuration format from
88
+ the section below.
89
+
90
+ ## Before you start — this won't work without two things
91
+
92
+ The OJS REST API has **no anonymous read access**. For the server to be
93
+ able to connect at all, both of these must be true on the OJS instance
94
+ side:
95
+
96
+ 1. **`api_key_secret` set in `config.inc.php`** — without it, API tokens
97
+ don't work at all (OJS responds with a 500 error to every request
98
+ that carries a token). This must be done by the OJS server
99
+ administrator; it can't be worked around from the outside.
100
+ 2. **An account with a role in the specific journal** — merely having an
101
+ OJS account is not enough. Almost every API endpoint requires some
102
+ role (manager, editor, reviewer...); an account with no role in the
103
+ given journal gets denied (401) on almost every call.
104
+
105
+ A third condition applies to instances served by Apache — including the
106
+ official `pkpofficial/ojs` Docker images: the web server must forward the
107
+ `Authorization` header to PHP. Apache does not do that on its own, and when
108
+ it doesn't, OJS answers 401 to every request carrying a token, with a
109
+ response indistinguishable from an anonymous one. The one-line fix, and how
110
+ to tell this case apart from a genuine permission problem, are in the
111
+ authentication documentation.
112
+
113
+ Without these conditions the server will start, but every tool that
114
+ reaches into OJS will return an authentication error. Details, including
115
+ the login/password alternative and its limitations, are in
116
+ [docs/authentication.md](https://mpasternak.github.io/ojs-mcp/authentication/).
117
+
118
+ ## Getting a token
119
+
120
+ A logged-in user generates an API token in their own OJS profile: **User
121
+ Profile → API Key** (only available once the instance administrator has
122
+ set `api_key_secret` — see above). The token acts with that account's
123
+ permissions, so its scope is whatever roles that account holds in the
124
+ given journal.
125
+
126
+ ## Example MCP client configuration
127
+
128
+ ```json
129
+ {
130
+ "mcpServers": {
131
+ "ojs": {
132
+ "command": "uvx",
133
+ "args": ["ojs-mcp"],
134
+ "env": {
135
+ "OJS_BASE_URL": "https://journals.your-university.edu",
136
+ "OJS_JOURNAL": "my-journal",
137
+ "OJS_API_TOKEN": "paste-your-ojs-profile-token-here"
138
+ }
139
+ }
140
+ }
141
+ }
142
+ ```
143
+
144
+ `OJS_JOURNAL` is optional — leave it out if the instance serves several
145
+ journals and you'd rather pick one with the `journal` parameter on each
146
+ call.
147
+
148
+ ### Where to actually paste this configuration
149
+
150
+ In Claude Desktop: **Settings → Developer → Edit Config** opens (and, on
151
+ first use, creates) the `claude_desktop_config.json` file:
152
+
153
+ - macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
154
+ - Windows: `%APPDATA%\Claude\claude_desktop_config.json`
155
+
156
+ Paste the snippet above under the `mcpServers` key — if the file already
157
+ has other servers configured, add the `"ojs"` key alongside them, don't
158
+ overwrite the whole file — save and restart Claude Desktop. Other
159
+ desktop MCP clients have their own place for this configuration (e.g.
160
+ Claude Code reads it via `claude mcp add` or from an `.mcp.json` file) —
161
+ check their documentation; the shape of the `env` section above is the
162
+ same across all of them.
163
+
164
+ This step **disappears entirely** when installing from the MCPB bundle
165
+ (see below) — there, the instance address, journal, and token are filled
166
+ in through a form in the client's UI, with no manual JSON editing at
167
+ all. That's a good reason to reach for the bundle instead of `uvx` if
168
+ editing a configuration file by hand isn't your thing.
169
+
170
+ ## Environment variables (summary)
171
+
172
+ | Variable | Required | Description |
173
+ |---|---|---|
174
+ | `OJS_BASE_URL` | yes | The OJS instance address, exactly as it works in the browser. |
175
+ | `OJS_JOURNAL` | no | The journal shortcut — skips the `journal` parameter on every call. |
176
+ | `OJS_API_TOKEN` | no* | The token from a user's profile. Takes precedence over login/password. |
177
+ | `OJS_USERNAME` / `OJS_PASSWORD` | no* | Form-based login — won't work with reCAPTCHA/ALTCHA. |
178
+ | `OJS_ALLOW_WRITES` | no | `1` exposes the tools that modify journal data (hidden by default). |
179
+
180
+ `*` — either `OJS_API_TOKEN` **or** the `OJS_USERNAME`/`OJS_PASSWORD`
181
+ pair is required (in `stdio` mode). The full list, including the
182
+ network-mode variables (`OJS_MCP_TRANSPORT` and others), is in
183
+ [docs/configuration.md](https://mpasternak.github.io/ojs-mcp/configuration/).
184
+
185
+ ## Alternative to `uvx`: the MCPB bundle
186
+
187
+ For desktop MCP clients that support the
188
+ [MCP Bundle (`.mcpb`)](https://github.com/modelcontextprotocol/mcpb)
189
+ format — the installer file is attached to every release under
190
+ [Releases](https://github.com/mpasternak/ojs-mcp/releases). Installation
191
+ happens through the client's UI, configuration through a form instead of
192
+ manual JSON editing; no Python or `uv` installation required — the
193
+ bundle pulls its own dependencies on first run.
194
+
195
+ ## Documentation
196
+
197
+ Full documentation (installation, configuration, authentication, tool
198
+ list, multi-tenant hosting): **https://mpasternak.github.io/ojs-mcp/**
199
+
200
+ ## License
201
+
202
+ MIT. See [LICENSE](https://github.com/mpasternak/ojs-mcp/blob/main/LICENSE).
@@ -0,0 +1,163 @@
1
+ # ojs-mcp
2
+
3
+ [![tests](https://github.com/mpasternak/ojs-mcp/actions/workflows/tests.yml/badge.svg)](https://github.com/mpasternak/ojs-mcp/actions/workflows/tests.yml)
4
+ [![PyPI](https://img.shields.io/pypi/v/ojs-mcp.svg)](https://pypi.org/project/ojs-mcp/)
5
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/mpasternak/ojs-mcp/blob/main/LICENSE)
6
+
7
+ An [MCP](https://modelcontextprotocol.io/) server for the authenticated
8
+ REST API of [Open Journal Systems](https://pkp.sfu.ca/ojs/) (OJS
9
+ 3.5/3.6). Connected to an MCP client (Claude Desktop, Claude Code, and
10
+ others), it gives the model access to a journal's submissions, reviews,
11
+ issues, and editorial statistics — and, with writes explicitly enabled,
12
+ also to making editorial decisions, publishing, and editing metadata.
13
+
14
+ ## Verified against a live OJS 3.5
15
+
16
+ Every tool has been exercised end-to-end against a real OJS 3.5.0-5
17
+ instance — the sixteen read tools, the `ojs_request` escape hatch, and all
18
+ five write tools. The write tools were checked by reading back the state
19
+ OJS actually ended up in, not by trusting the status code: an announcement
20
+ created, publication metadata edited, a submission carried from the
21
+ submission stage to production by two editorial decisions, then published
22
+ and unpublished again.
23
+
24
+ That instance is reproducible. [`demo/`](https://github.com/mpasternak/ojs-mcp/tree/main/demo)
25
+ holds the Docker stack, the fictional seed content, and the two scripts that
26
+ drive this server against it as a real MCP client — `check_api.py` for the
27
+ read tools, `check_writes.py` for the write tools.
28
+
29
+ Three things have **not** been exercised against a live server, and still
30
+ rest on what the PKP source (`pkp-lib`, `pkp/ojs`) says rather than on
31
+ observed behavior: login/password authentication
32
+ (`OJS_USERNAME`/`OJS_PASSWORD`), network mode (`OJS_MCP_TRANSPORT=http`),
33
+ and OJS 3.6 — only 3.5 has been tested. The unit test suite likewise runs
34
+ entirely against stubbed HTTP responses (via `respx`).
35
+
36
+ Writes change real journal data, which is why `OJS_ALLOW_WRITES=1` is off by
37
+ default. Verify each tool's effect on a test journal before pointing it at
38
+ production.
39
+
40
+ ## Quick start
41
+
42
+ ```bash
43
+ OJS_BASE_URL=https://journals.your-university.edu OJS_API_TOKEN=your-token uvx ojs-mcp
44
+ ```
45
+
46
+ No separate install step needed — [`uv`](https://docs.astral.sh/uv/)
47
+ downloads and runs the package on first launch. In practice your MCP
48
+ client calls this command for you, using the configuration format from
49
+ the section below.
50
+
51
+ ## Before you start — this won't work without two things
52
+
53
+ The OJS REST API has **no anonymous read access**. For the server to be
54
+ able to connect at all, both of these must be true on the OJS instance
55
+ side:
56
+
57
+ 1. **`api_key_secret` set in `config.inc.php`** — without it, API tokens
58
+ don't work at all (OJS responds with a 500 error to every request
59
+ that carries a token). This must be done by the OJS server
60
+ administrator; it can't be worked around from the outside.
61
+ 2. **An account with a role in the specific journal** — merely having an
62
+ OJS account is not enough. Almost every API endpoint requires some
63
+ role (manager, editor, reviewer...); an account with no role in the
64
+ given journal gets denied (401) on almost every call.
65
+
66
+ A third condition applies to instances served by Apache — including the
67
+ official `pkpofficial/ojs` Docker images: the web server must forward the
68
+ `Authorization` header to PHP. Apache does not do that on its own, and when
69
+ it doesn't, OJS answers 401 to every request carrying a token, with a
70
+ response indistinguishable from an anonymous one. The one-line fix, and how
71
+ to tell this case apart from a genuine permission problem, are in the
72
+ authentication documentation.
73
+
74
+ Without these conditions the server will start, but every tool that
75
+ reaches into OJS will return an authentication error. Details, including
76
+ the login/password alternative and its limitations, are in
77
+ [docs/authentication.md](https://mpasternak.github.io/ojs-mcp/authentication/).
78
+
79
+ ## Getting a token
80
+
81
+ A logged-in user generates an API token in their own OJS profile: **User
82
+ Profile → API Key** (only available once the instance administrator has
83
+ set `api_key_secret` — see above). The token acts with that account's
84
+ permissions, so its scope is whatever roles that account holds in the
85
+ given journal.
86
+
87
+ ## Example MCP client configuration
88
+
89
+ ```json
90
+ {
91
+ "mcpServers": {
92
+ "ojs": {
93
+ "command": "uvx",
94
+ "args": ["ojs-mcp"],
95
+ "env": {
96
+ "OJS_BASE_URL": "https://journals.your-university.edu",
97
+ "OJS_JOURNAL": "my-journal",
98
+ "OJS_API_TOKEN": "paste-your-ojs-profile-token-here"
99
+ }
100
+ }
101
+ }
102
+ }
103
+ ```
104
+
105
+ `OJS_JOURNAL` is optional — leave it out if the instance serves several
106
+ journals and you'd rather pick one with the `journal` parameter on each
107
+ call.
108
+
109
+ ### Where to actually paste this configuration
110
+
111
+ In Claude Desktop: **Settings → Developer → Edit Config** opens (and, on
112
+ first use, creates) the `claude_desktop_config.json` file:
113
+
114
+ - macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
115
+ - Windows: `%APPDATA%\Claude\claude_desktop_config.json`
116
+
117
+ Paste the snippet above under the `mcpServers` key — if the file already
118
+ has other servers configured, add the `"ojs"` key alongside them, don't
119
+ overwrite the whole file — save and restart Claude Desktop. Other
120
+ desktop MCP clients have their own place for this configuration (e.g.
121
+ Claude Code reads it via `claude mcp add` or from an `.mcp.json` file) —
122
+ check their documentation; the shape of the `env` section above is the
123
+ same across all of them.
124
+
125
+ This step **disappears entirely** when installing from the MCPB bundle
126
+ (see below) — there, the instance address, journal, and token are filled
127
+ in through a form in the client's UI, with no manual JSON editing at
128
+ all. That's a good reason to reach for the bundle instead of `uvx` if
129
+ editing a configuration file by hand isn't your thing.
130
+
131
+ ## Environment variables (summary)
132
+
133
+ | Variable | Required | Description |
134
+ |---|---|---|
135
+ | `OJS_BASE_URL` | yes | The OJS instance address, exactly as it works in the browser. |
136
+ | `OJS_JOURNAL` | no | The journal shortcut — skips the `journal` parameter on every call. |
137
+ | `OJS_API_TOKEN` | no* | The token from a user's profile. Takes precedence over login/password. |
138
+ | `OJS_USERNAME` / `OJS_PASSWORD` | no* | Form-based login — won't work with reCAPTCHA/ALTCHA. |
139
+ | `OJS_ALLOW_WRITES` | no | `1` exposes the tools that modify journal data (hidden by default). |
140
+
141
+ `*` — either `OJS_API_TOKEN` **or** the `OJS_USERNAME`/`OJS_PASSWORD`
142
+ pair is required (in `stdio` mode). The full list, including the
143
+ network-mode variables (`OJS_MCP_TRANSPORT` and others), is in
144
+ [docs/configuration.md](https://mpasternak.github.io/ojs-mcp/configuration/).
145
+
146
+ ## Alternative to `uvx`: the MCPB bundle
147
+
148
+ For desktop MCP clients that support the
149
+ [MCP Bundle (`.mcpb`)](https://github.com/modelcontextprotocol/mcpb)
150
+ format — the installer file is attached to every release under
151
+ [Releases](https://github.com/mpasternak/ojs-mcp/releases). Installation
152
+ happens through the client's UI, configuration through a form instead of
153
+ manual JSON editing; no Python or `uv` installation required — the
154
+ bundle pulls its own dependencies on first run.
155
+
156
+ ## Documentation
157
+
158
+ Full documentation (installation, configuration, authentication, tool
159
+ list, multi-tenant hosting): **https://mpasternak.github.io/ojs-mcp/**
160
+
161
+ ## License
162
+
163
+ MIT. See [LICENSE](https://github.com/mpasternak/ojs-mcp/blob/main/LICENSE).
@@ -0,0 +1,124 @@
1
+ # Demo OJS instance
2
+
3
+ A throwaway OJS 3.5 instance with fictional content, so `ojs-mcp` can be
4
+ exercised against a real server instead of stubbed HTTP responses.
5
+
6
+ Everything in here is disposable: the passwords are in plain sight on
7
+ purpose, all articles and people are invented, and the whole stack is meant
8
+ to be destroyed with `docker compose down -v`.
9
+
10
+ ## Run it
11
+
12
+ ```bash
13
+ cd demo
14
+ ./setup.sh # ~5 minutes on first run, most of it OJS's installer
15
+ ```
16
+
17
+ `setup.sh` is re-runnable — each step checks whether it already happened —
18
+ and prints the connection details and a ready-to-use API token at the end.
19
+
20
+ Then drive the MCP server against it:
21
+
22
+ ```bash
23
+ uv sync --extra dev
24
+ OJS_API_TOKEN=<token from setup.sh> .venv/bin/python demo/check_api.py
25
+ ```
26
+
27
+ `check_api.py` is a real MCP client: it launches `ojs-mcp` over stdio the way
28
+ Claude Desktop would, then calls every read tool and prints one line per call.
29
+
30
+ The write tools have their own script, kept separate because it *changes* the
31
+ journal — it creates an announcement, edits metadata, walks a submission
32
+ through two editorial decisions, publishes it and unpublishes it again,
33
+ reading the state back after each step:
34
+
35
+ ```bash
36
+ OJS_API_TOKEN=<token from setup.sh> .venv/bin/python demo/check_writes.py
37
+ ```
38
+
39
+ It refuses to run against anything but `localhost:8081`. These tools exist to
40
+ modify production journals, and a script that fires all five must not be one
41
+ typo away from doing so.
42
+
43
+ To wipe it all, including the database:
44
+
45
+ ```bash
46
+ docker compose down -v
47
+ ```
48
+
49
+ `setup.sh` copes with that: the database lives in a Docker volume but
50
+ `config.inc.php` lives on the host, so after `down -v` the config still says
51
+ `installed = On` while the schema is gone. The script notices the mismatch
52
+ and regenerates the config instead of handing you an OJS that answers 500 to
53
+ everything.
54
+
55
+ ## What you get
56
+
57
+ | | |
58
+ |---|---|
59
+ | Site | <http://localhost:8081> |
60
+ | Journal | <http://localhost:8081/demojournal> |
61
+ | Admin | `admin` / `ojsdemo1234` |
62
+ | Editor | `editor` / `ojsdemo1234` |
63
+ | Reviewers | `reviewer1`, `reviewer2` / `ojsdemo1234` |
64
+
65
+ Content: one journal, one section, two published issues, six published
66
+ articles with authors, abstracts, keywords and page ranges, two submissions
67
+ still in the editorial workflow (one in external review, one in the
68
+ submission stage), and two review assignments on the one under review — one
69
+ answered with a recommendation, one still pending.
70
+
71
+ ## Files
72
+
73
+ | File | What it is |
74
+ |---|---|
75
+ | `docker-compose.yml` | OJS 3.5.0-5 + MariaDB 11.4, on port 8081 |
76
+ | `config/ojs.config.inc.php` | OJS config; written to by the installer |
77
+ | `config/pkp.conf` | The image's Apache vhost, plus one added line (see below) |
78
+ | `seed/demo-content.xml` | Two issues and six published articles (native XML) |
79
+ | `seed/demo-submissions.xml` | Two unpublished submissions (native XML) |
80
+ | `seed/demo-users.xml` | Editor and two reviewers (users XML) |
81
+ | `seed/assign-reviewers.php` | Review assignments, via OJS's own repositories |
82
+ | `check_api.py` | MCP client that calls every read tool |
83
+ | `check_writes.py` | MCP client that exercises the five write tools |
84
+
85
+ ## Three things that are not obvious
86
+
87
+ **Apache does not pass the `Authorization` header to PHP.** Out of the box,
88
+ the official `pkpofficial/ojs` image answers `401` to every request carrying
89
+ a perfectly valid `Authorization: Bearer <token>` — while the same token
90
+ works when passed as `?apiToken=…`. The response body is byte-for-byte
91
+ identical to the anonymous one, so nothing points at the header. The fix is
92
+ one line in the vhost, which is why `config/pkp.conf` exists:
93
+
94
+ ```apache
95
+ SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1
96
+ ```
97
+
98
+ `ojs-mcp` only ever sends the header, so without this it cannot talk to this
99
+ image at all. Anyone deploying OJS behind Apache with mod_php needs the same
100
+ line before the REST API will accept tokens.
101
+
102
+ **OJS has no CLI installer.** PKP's own automation (`pkp-cli-install` in the
103
+ image) is literally `curl` posting the web installer's form back to the same
104
+ container. `setup.sh` does the same thing from the host, so each field is
105
+ visible and the step can be re-run.
106
+
107
+ **`config.inc.php` is bind-mounted, so `sed -i` inside the container fails.**
108
+ `sed -i` works by renaming, and a bind-mounted file cannot be renamed
109
+ (`Device or resource busy`). The image's entrypoint tries exactly that to set
110
+ `restful_urls`, and silently fails. `setup.sh` therefore edits the file on
111
+ the host before the container ever sees it.
112
+
113
+ ## The API token
114
+
115
+ OJS signs API tokens as a JWT whose payload is a one-element array holding
116
+ the user's `apiKey`, signed with `api_key_secret` from `config.inc.php`
117
+ (`PKP\user\form\APIProfileForm`). `setup.sh` writes the `apiKey` /
118
+ `apiKeyEnabled` settings for the admin account and encodes the JWT with OJS's
119
+ own vendored library — the same thing the **User Profile → API Key** tab does
120
+ in the browser, minus the clicking.
121
+
122
+ Without `api_key_secret`, OJS answers `500` to every request that carries a
123
+ token, which looks like an outage rather than a configuration problem. The
124
+ config here has it set before installation.