feat(mcp): add osw-mcp server exposing a live OSL instance - #133
feat(mcp): add osw-mcp server exposing a live OSL instance#133LukasGold wants to merge 4 commits into
Conversation
Add an in-repo `osw[mcp]` extra and an `osw-mcp` stdio console script that wraps OswExpress and serves it over the Model Context Protocol for clients such as Claude Code. Tools: semantic/SPARQL/full-text search, category schema introspection, entity read + JSON-LD export, create/update/delete, full page-slot access, and file up/download. - Delete is provenance-guarded: a local JSON ledger records pages the server created/modified; deleting anything untracked requires confirm_external_delete=true. - Credentials resolve from env/.env and are validated up front (fail fast, never prompts, so stdio is never corrupted by an input() call). - osw stdout is redirected to stderr so it never leaks onto the JSON-RPC channel. - OSW_MCP_READ_ONLY hides all mutating tools.
b46f046 to
23db777
Compare
Release previewMerging this PR would release v2.1.0 (current: Changelog preview (truncated)## v2.1.0 (2026-08-22)
### Documentation
- Add citation and authorship section to CONTRIBUTING.md
([`a3a8119`](https://github.com/OpenSemanticLab/osw-python/commit/a3a8119cd047cee741812b11e3f1f26c3f91c639))
### Features
- **mcp**: Add osw-mcp server exposing a live OSL instance
([`23db777`](https://github.com/OpenSemanticLab/osw-python/commit/23db777f632a3c0bf17a0ec97aaf4a330623a9cb))
- **mcp**: Authenticate from an osw credential file
([`a7c35a7`](https://github.com/OpenSemanticLab/osw-python/commit/a7c35a7929cdcc497a42724a1aa3a877f5bd47ca))
- **mcp**: Port server to mcp 2.x and isolate the extra
([`e219aae`](https://github.com/OpenSemanticLab/osw-python/commit/e219aae1600811bbde0208258f234f34057efcc7))
- **mcp**: Select between multiple OSL instances at runtime
([`d393a66`](https://github.com/OpenSemanticLab/osw-python/commit/d393a6632c636b3b2206f56ff85def2b1af1a191))
Preview via python-semantic-release and conventional commits. |
|
Credential loading bypasses the
|
Multi-instance credentials: selecting an OSL instance per sessionFollowing on from the credential-file comment above. Pinning one The multi-account file already exists. One correction on the interactive part. Prompting on the CLI cannot work for Elicit the choice, never the secret. Elicited values pass through the MCP Concretely:
Multiple registrations already cover part of this, with no code change: claude mcp add osw-dev --env OSW_MCP_ENV_FILE=... -- uvx ...
claude mcp add osw-prod --env OSW_MCP_ENV_FILE=... --env OSW_MCP_READ_ONLY=true -- uvx ...That has two advantages over in-session switching: the instance is visible in the Supplying a |
- FastMCP replaced by MCPServer, extra now requires mcp>=2 - mcp dropped from the all extra and the dev group: it needs anyio>=4.9, workflow pins anyio<4.7 (#139) - uv conflicts declare mcp exclusive with workflow and with dev - pytest stack moved to its own test group, so an environment with both pytest and mcp exists - src/osw/mcp excluded from ty, mcp tests guarded by importorskip
- OSW_MCP_CRED_FILEPATH configures it, OSL_CRED_FILEPATH is a fallback - an alternative to OSW_USERNAME/OSW_PASSWORD, so the password is not duplicated into a second plaintext file - existence and a matching domain entry are validated at startup - lookups use CredentialFallback.none, so osw never prompts and never blocks the stdio transport
- list_instances and select_instance tools, returning iris only and never any credential value - OSW_DOMAIN becomes optional when a credential file supplies the iris - auto-selects when OSW_DOMAIN is set or the file holds exactly one iri - switching rebuilds the connection and the per-domain provenance ledger - tools resolve the active domain and credentials at call time
|
All three addressed, one commit each: e219aae, a7c35a7, d393a66. mcp>=1.2 resolves to 2.0.0 - e219aaeConfirmed against the published wheel: The anyio clash you predicted is real:
Cost: the module is neither type-checked nor unit-tested in the default Credential loading bypasses the CredentialManager file pattern - a7c35a7
One correction: osw itself does not read Lookups use Multi-instance credentials - d393a66Agreed on the constraint that elicited values pass through the client and into the
Iris may be bare domains or full URLs; Per-instance registration is documented as the alternative in the README, since it Verification
|
Summary
Adds an in-repo MCP server (
osw[mcp]extra,osw-mcpstdio console script) thatwraps
OswExpressand exposes a live OpenSemanticLab instance to MCP clients suchas Claude Code.
Changes
New package
src/osw/mcp/server.py-create_server()/main(),MCPServerover stdioconfig.py- env/.env/credential-file resolution, validation, active-instance stateconnection.py- lazyOswExpressconnection, stdout redirected to stderrledger.py- local JSON provenance ledger of pages the server created/modifiedserialization.py- result trimming and JSON-safe conversiontools/- tool registration, split by domainTools
search.pysearch_entities(semantic/ask),full_text_search,list_instances_of_category,sparql_queryschema.pyget_category_schemaentities.pyget_entity,export_entity_jsonld,create_or_update_entity,delete_entityslots.pylist_page_slots,get_slot,set_slotfiles.pydownload_file,upload_fileinstances.pylist_instances,select_instancestatus.pystatusPackaging (
pyproject.toml)mcpextra:mcp>=2(official SDK) andpython-dotenv>=1.0osw-mcp = "osw.mcp.server:main"alland not in thedevgroup, seeDependency constraint below
testgroupDocs (
README.md) - install,.envand credential-file setup, multi-instanceselection,
.mcp.jsonandclaude mcp addregistration, safe-delete behaviour,editable-checkout caveat.
Design decisions
or modifies. Those delete without extra ceremony; anything untracked requires
confirm_external_delete=true. Prevents an agent from removing wiki content itdid not author. The ledger is keyed per domain and rebuilt when the active
instance changes.
.envor an osw credential fileand validated at startup. The server never calls
input(), since a prompt onstdio would corrupt the JSON-RPC channel; credential-file lookups therefore use
CredentialFallback.none.list_instancesandstatusreturn irisonly. Instance choice is by name; no username or password is ever returned by a
tool, so nothing lands in the agent's context.
file,
select_instanceswitches at runtime and rebuilds the connection.Registering the server once per instance (
osw-dev,osw-prod) still works andkeeps the instance visible in the tool name.
osw's own prints are redirected to stderr for the samereason.
OSW_MCP_READ_ONLY=truehides all mutating tools rather thanfailing them at call time, so the client never sees a tool it cannot use.
Configuration
OSW_DOMAIN,OSW_USERNAME,OSW_PASSWORD,OSW_MCP_CRED_FILEPATH(
OSL_CRED_FILEPATHas fallback),OSW_SPARQL_ENDPOINT,OSW_MCP_ENV_FILE,OSW_MCP_READ_ONLY,OSW_MCP_STATE_DIR,OSW_MCP_MAX_RESULTS,OSW_MCP_MAX_CHARS.OSW_DOMAINis required unless a credential file is configured, in which case theactive instance comes from the file (auto-selected when it holds exactly one iri).
Dependency constraint
mcp>=2requiresanyio>=4.9;osw[workflow]pinsanyio<4.7because prefect2.20.25's
GatherTaskGroupbreaks on newer anyio. The two cannot share anenvironment, so:
[tool.uv] conflictsdeclaresmcpmutually exclusive with theworkflowextra and with the
devgroupmcpis excluded fromalland fromdev, keeping the workflow-compatibleanyio as the default resolution
src/osw/mcpis excluded fromtyand its tests useimportorskip, since theimports are unresolvable in the default dev environment
Tracked in #139, which is the
reminder to re-check whether the anyio pin can be dropped.
Tests
tests/test_mcp_config.py,tests/test_mcp_ledger.py,tests/test_mcp_serialization.py,tests/test_mcp_tools.py,tests/test_mcp_instances.py- offline unit teststests/integration/test_mcp_server.py- against a live instanceThe MCP unit tests need their own environment:
58 passed there; the default suite is 51 passed, 4 skipped, with
tyanddeptryclean. CI does not yet run the mcp environment.
Known caveat
create_or_update_entityandexport_entity_jsonldcallfetch_schema, whichregenerates
src/osw/model/entity.pyinside the installed package. Harmless for anormal
pip install "osw[mcp]"(writes into site-packages), but running the serverfrom an editable checkout will modify the generated model file in the working tree.
The read tools do not trigger it.
Related
The
store_entityparallel-upload fix that previously rode along on this branch wasdropped and is tracked separately in
#132.