dspace-mcp
dspace-mcp
English · Русский
MCP server that gives an AI agent a stable, typed interface to operate
DSpace institutional repositories: building SAF import packages, running
bin/dspace CLI operations, batch metadata editing, author/COAR/ORCID
normalization, scientometric harvesting by affiliation, open-access PDF
download (Unpaywall included), LLM-assisted metadata enrichment, and
repository admin/diagnostics.
Grew out of ad-hoc tooling used to administer open.mgimo.ru (DSpace 6.3), but is not tied to that institution: every host/credential/handle-prefix/ affiliation-string/journal-identity/type-map value is config- or argument-driven (see "Adding another institution" below), and a version-capability layer covers DSpace 5.x-8.x generally.
Keywords
MCP · Model Context Protocol · DSpace · institutional repository · AI agent · LLM · Simple Archive Format (SAF) · metadata · scientometrics · open access · ORCID · COAR · Unpaywall · OpenAlex · Python
Ключевые слова: MCP · Model Context Protocol · DSpace · институциональный репозиторий · ИИ-агент · LLM · Simple Archive Format (SAF) · метаданные · наукометрия · открытый доступ · ORCID · COAR · Unpaywall · OpenAlex · Python
How it connects
Two execution paths:
Local, no server needed: SAF package building, OJS XML conversion, scientometric API harvesting, OA PDF download, LLM enrichment, dedup/ batching. These run entirely on the machine hosting this MCP server.
Remote, over SSH: anything that touches the actual DSpace installation (
bin/dspaceCLI, Postgres, the assetstore) shells out to the configured host viassh/scp. This is mandatory for classic DSpace (<7) instances, where the REST API is frequently broken/disabled andimportmust run as root. A REST-based path can be added later for instances where DSpace 7/8's REST API is known to work, but every tool here currently talks to the DSpace CLI/DB, not REST.
Guarded writes
Reads, local builds, and external API calls always run.
State-changing DSpace operations (
saf_import,metadata_import,item_update_*,filter_media,cleanup) default to confirm=False: they report the exact command(s) they would run and their predicted effect, and execute nothing. Passconfirm=Trueto actually run them.Direct database/assetstore mutations (
collection_create,set_collection_logo,set_intranet_access, and the normalization tools when writing) additionally requireallow_db_write=True, which triggers an automaticdb_backup()before anything is touched.
This was verified: with confirm=False, saf_import/metadata_import/
filter_media return instantly with zero network I/O (tested against an
unroutable host) and report the exact remote commands they would run.
Install
Requires Python >=3.10 and uv.
git clone https://github.com/or-sirina/dspace-mcp.git
cd dspace-mcp
uv venv .venv
uv pip install -e . --python .venv/bin/pythonRegister with an MCP client (e.g. in .claude.json, matching how other
stdio servers are configured on this machine):
{
"mcpServers": {
"dspace-mcp": {
"type": "stdio",
"command": "uv",
"args": ["--directory", "/path/to/dspace-mcp", "run", "dspace-mcp"]
}
}
}See docs/INSTALLATION.md for ready-to-paste registration snippets for Claude Desktop / Claude Code, Cursor, VS Code (Copilot, Cline, Roo Code), Continue.dev, opencode, DeepSeek Harness, and any generic stdio client.
Configure
Copy config.example.toml to ~/.config/dspace-mcp/config.toml (or point
DSPACE_MCP_CONFIG elsewhere) and fill in one [profile.<name>] block per
DSpace instance. Never write secrets directly into the file -- any
string value of the form "env:VAR_NAME" is resolved from the process
environment at load time:
[profile.mgimo]
dspace_version = "6.3"
admin_eperson = "admin@example.org"
handle_prefix = "123456789"
[profile.mgimo.ssh]
host = "203.0.113.10"
user = "deploy"
identity_file = "~/.ssh/id_dspace" # prefer a key over a password
[profile.mgimo.db]
password = "env:DSPACE_DB_PASSWORD"
[profile.mgimo.api]
unpaywall_email = "you@example.org"
llm_api_key = "env:DEEPSEEK_API_KEY"Most tools take an optional profile argument selecting which
[profile.*] block to use; if only one is configured it's picked
automatically, otherwise set DSPACE_MCP_PROFILE or pass profile
explicitly on each call.
Security notes carried over from the tooling this replaces:
Prefer
ssh.identity_fileoverssh.password/sudo_password. Password auth still works for parity with older setups, but the password is briefly visible topson the remote host duringsudo -S.Grant NOPASSWD sudo for the
dspaceCLI/chown commands this server needs if you want the password path avoided entirely --sudo_run()always triessudo -nfirst and only falls back to piping a password if one is configured.If you're migrating off scripts that had an API key or DB password hardcoded in source, rotate that credential -- it's already compromised once it's been committed in plaintext.
Adding another institution
The server is multi-institution by design -- MGIMO is the deployment it was first built against, not a hardcoded target. Nothing in the tool logic references MGIMO, a specific handle prefix, or a specific host; every institution-specific value (SSH host/creds, DB creds, handle prefix, assetstore layout, COAR type map, IP ranges, LLM provider/key, affiliation string) is a config field or a tool argument.
To onboard a second (or twentieth) institution:
Add a new
[profile.<short-name>]block toconfig.toml-- see[profile.example_university]inconfig.example.tomlfor a complete template (DSpace 7,dspace-angular, an OpenAI-compatible LLM instead of DeepSeek, all different from the MGIMO example next to it).Run
dspace_ping(profile="<short-name>")first to confirm SSH/DB/Tomcat/ Solr reachability before trying anything else.Run
list_communities/list_collections/resolve_field_idto confirm the canned SQL indspace/cli.py/dspace/admin.py(written against the classic uuid-keyed schema) matches this instance; if it doesn't, useraw_sqlto find the right table/column names for that instance instead of assuming MGIMO's.For
harvest_by_affiliation, pass that institution's affiliation string (e.g."University of Example") and afilename_prefix(e.g."uex") so its harvested PDF filenames don't collide with another institution's if you ever run both against a shared working directory.For
saf_from_ojs_xml, pass that institution's ownjournal_name_en/journal_name_ru/ISSNs -- nothing about the OJS converter assumes a particular journal or language pair.For
assign_coar_types, the defaulttype_mapreflects one institution's historicaldc.typevalues (a mix of English and Russian strings, since that's what MGIMO's existing records used) -- pass your owntype_mapreflecting whateverdc.typevalues this instance actually has;list_communities/raw_sqlcan show you the current distribution first.
Nothing else needs to change -- the same 33 tools, the same guarded-write behavior, and the same version-capability layer apply to every profile.
Tool reference
Introspection -- dspace_ping, list_communities, list_collections,
resolve_field_id, raw_sql, server_health.
SAF building (local) -- saf_build_from_csv, saf_generate_manifest,
saf_validate, saf_repair, saf_from_ojs_xml.
Metadata / import -- metadata_export, metadata_import, saf_import,
item_update_add_bitstreams, item_update_delete_bitstreams,
filter_media, index_discovery, cleanup.
Normalization -- normalize_authors, assign_coar_types,
harvest_orcid, apply_orcid_matches.
Admin -- db_backup, collection_create, set_collection_logo,
set_intranet_access.
Scientometrics / OA -- harvest_by_affiliation (OpenAlex, CrossRef,
CORE, Semantic Scholar), deduplicate, batch_missing, download_oa_pdfs
(Unpaywall-backed), extract_pdf_text, enrich_metadata_llm.
Every tool has a docstring visible to the calling agent describing its exact behavior, arguments, and any caveats -- read those first; this list is just an index.
Use cases
Ingest a whole discipline by affiliation (scientometric pipeline).
harvest_by_affiliation -> deduplicate (against existing metadata
exports) -> download_oa_pdfs (Unpaywall) -> enrich_metadata_llm ->
saf_build_from_csv -> saf_import. Turns an institution name into a batch
of validated, deduplicated, PDF-backed items ready to import.
Import a journal issue from OJS/PKP. saf_from_ojs_xml converts an
"Articles and Issues XML" export into a batch CSV plus extracted PDFs;
saf_build_from_csv packages it as SAF; saf_import loads it. Bilingual
metadata and transliterated filenames are handled automatically.
Batch metadata cleanup. metadata_export a collection to CSV, edit it in
a spreadsheet, then metadata_import it back. The import is a dry run by
default (confirm=False), so you can preview the exact command and effect
before executing.
Author / type / ORCID normalization. normalize_authors canonicalizes
ё/е, case, and punctuation variants; assign_coar_types remaps dc.type to
bilingual values + COAR URIs; harvest_orcid + apply_orcid_matches link
authors to ORCIDs via Crossref.
Create and brand a collection. collection_create (structure-builder
first, SQL fallback) -> set_collection_logo -> set_intranet_access for
IP-restricted collections.
Diagnose an unhealthy repository. dspace_ping / server_health
pinpoint SSH/CLI/Postgres/Tomcat/Solr failures; db_backup,
index_discovery, filter_media, and cleanup cover routine maintenance.
Add or remove files on existing items. item_update_add_bitstreams /
item_update_delete_bitstreams change bitstreams without touching metadata
(follow deletion with cleanup).
Version coverage
src/dspace_mcp/versions/base.py encodes what differs across DSpace
5.x-8.x: whether import needs root, whether REST is generally usable,
how collections/logos are created, and known per-version CLI deltas
(all sourced from the LYRASIS DSpace documentation, cross-checked against
hands-on operation of a 6.3 instance). collection_create tries
structure-builder (present since 5.x/6.x) before falling back to direct
SQL; if a canned SQL query in list_communities/list_collections
doesn't match your instance's schema, use raw_sql to introspect it and
report/fix the mismatch.
Known gaps / follow-ups
No REST API backend yet -- everything goes through SSH + the
dspaceCLI + Postgres. A REST adapter behind the same tool signatures is a natural Phase 4 for DSpace 7/8 instances where REST is reachable and preferred over SSH.SQL in
dspace/cli.pyanddspace/admin.pytargets the classic uuid-keyed schema (hands-on verified against DSpace 6.3); table/column names were not independently re-verified against a live 7/8 instance.raw_sqlis the escape hatch when a canned query doesn't match.Keyed scientometric sources (Scopus, Web of Science, Dimensions) aren't wired up --
scholar/harvest.pyis structured so adding one is a newfetch_<source>function plus a branch inharvest_by_affiliation.collection_create'sstructure-builderXML currently passes a bare<collection>element; verify the resulting placement/handle before relying on it in bulk, since the documented format nests collections under a<community>block for whole-subtree declarations.
Verification performed
saf_build_from_csvon a sample CSV -> correctdublin_core.xml,metadata_local.xml,contents,collections, copied bitstreams;saf_validateconfirms no issues;saf_repairfixes an intentionally corruptedcontentsfile.saf_from_ojs_xmlon a synthetic OJS issue XML -> correct bilingual title/author/abstract extraction, HTML stripping, citation building, transliterated filename, and base64 PDF extraction verified byte-for-byte.saf_import/metadata_import/filter_mediawithconfirm=Falsereturn in <2ms against an unroutable host (192.0.2.1, RFC 5737 TEST-NET) with the correct command preview -- proves zero network I/O happens before confirm.dedup/batch_missingcorrectly drop within-batch and existing-corpus duplicates and chunk output CSVs.harvest_by_affiliation(OpenAlex) executed as a live call through the actual MCPcall_toolprotocol path (not just the underlying Python function) and returned real MGIMO-affiliated papers with correctly mapped fields, including a genuine open-access URL.The installed console entrypoint (
dspace-mcp) starts cleanly under the stdio transport with no traceback.Not verified in this pass (no reachable DSpace host from the build environment):
dspace_ping/saf_import(confirm=True)/collection_createagainst a live instance. Rundspace_pingfirst against your real profile before trusting the SSH-dependent tools.An automated
pytestsuite (tests/) covers the local-only paths — SAF building/validation/repair, OJS conversion, dedup/normalization helpers, the guarded-write gates, and version resolution (Python 3.10-3.13).
Development
uv pip install -e ".[dev]" --python .venv/bin/python
.venv/bin/python -m pytestThe test suite is local-only and needs no DSpace host. SSH/CLI/Postgres tools
are exercised manually against a live profile (dspace_ping first). See
CONTRIBUTING.md and SECURITY.md.
Acknowledgments
This project was developed with the assistance of AI coding agents: Claude Code (Claude Opus, Claude Sonnet) and DeepSeek Harness (DeepSeek V4 Pro, DeepSeek V4 Flash).
License
MIT © 2026 V. V. Vinogradov.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/or-sirina/dspace-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server