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.
- ojs_mcp-0.2.0/.gitignore +36 -0
- ojs_mcp-0.2.0/CHANGELOG.md +73 -0
- ojs_mcp-0.2.0/LICENSE +21 -0
- ojs_mcp-0.2.0/PKG-INFO +202 -0
- ojs_mcp-0.2.0/README.md +163 -0
- ojs_mcp-0.2.0/demo/README.md +124 -0
- ojs_mcp-0.2.0/pyproject.toml +88 -0
- ojs_mcp-0.2.0/src/ojs_mcp/__init__.py +3 -0
- ojs_mcp-0.2.0/src/ojs_mcp/auth.py +163 -0
- ojs_mcp-0.2.0/src/ojs_mcp/catalog.py +233 -0
- ojs_mcp-0.2.0/src/ojs_mcp/client.py +330 -0
- ojs_mcp-0.2.0/src/ojs_mcp/config.py +121 -0
- ojs_mcp-0.2.0/src/ojs_mcp/data/__init__.py +0 -0
- ojs_mcp-0.2.0/src/ojs_mcp/data/endpoints.compact.txt +147 -0
- ojs_mcp-0.2.0/src/ojs_mcp/dictionaries.py +157 -0
- ojs_mcp-0.2.0/src/ojs_mcp/exceptions.py +88 -0
- ojs_mcp-0.2.0/src/ojs_mcp/fields.py +349 -0
- ojs_mcp-0.2.0/src/ojs_mcp/http_transport.py +345 -0
- ojs_mcp-0.2.0/src/ojs_mcp/mcp_errors.py +120 -0
- ojs_mcp-0.2.0/src/ojs_mcp/passthrough.py +158 -0
- ojs_mcp-0.2.0/src/ojs_mcp/prompts.py +155 -0
- ojs_mcp-0.2.0/src/ojs_mcp/resources.py +101 -0
- ojs_mcp-0.2.0/src/ojs_mcp/server.py +151 -0
- ojs_mcp-0.2.0/src/ojs_mcp/session_login.py +592 -0
- ojs_mcp-0.2.0/src/ojs_mcp/tools_read.py +1129 -0
- ojs_mcp-0.2.0/src/ojs_mcp/tools_write.py +531 -0
- ojs_mcp-0.2.0/tests/conftest.py +0 -0
- ojs_mcp-0.2.0/tests/test_auth.py +116 -0
- ojs_mcp-0.2.0/tests/test_catalog.py +167 -0
- ojs_mcp-0.2.0/tests/test_client.py +243 -0
- ojs_mcp-0.2.0/tests/test_config.py +128 -0
- ojs_mcp-0.2.0/tests/test_data.py +27 -0
- ojs_mcp-0.2.0/tests/test_dictionaries.py +92 -0
- ojs_mcp-0.2.0/tests/test_http_transport.py +828 -0
- ojs_mcp-0.2.0/tests/test_mcp_errors.py +130 -0
- ojs_mcp-0.2.0/tests/test_passthrough.py +159 -0
- ojs_mcp-0.2.0/tests/test_prompts_resources.py +178 -0
- ojs_mcp-0.2.0/tests/test_server.py +331 -0
- ojs_mcp-0.2.0/tests/test_session_auth.py +419 -0
- ojs_mcp-0.2.0/tests/test_session_login.py +345 -0
- ojs_mcp-0.2.0/tests/test_tools_read.py +888 -0
- ojs_mcp-0.2.0/tests/test_tools_write.py +399 -0
ojs_mcp-0.2.0/.gitignore
ADDED
|
@@ -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
|
+
[](https://github.com/mpasternak/ojs-mcp/actions/workflows/tests.yml)
|
|
43
|
+
[](https://pypi.org/project/ojs-mcp/)
|
|
44
|
+
[](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).
|
ojs_mcp-0.2.0/README.md
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# ojs-mcp
|
|
2
|
+
|
|
3
|
+
[](https://github.com/mpasternak/ojs-mcp/actions/workflows/tests.yml)
|
|
4
|
+
[](https://pypi.org/project/ojs-mcp/)
|
|
5
|
+
[](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.
|