Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
23db777
feat(mcp): add osw-mcp server exposing a live OSL instance
LukasGold Jul 16, 2026
e219aae
feat(mcp): port server to mcp 2.x and isolate the extra
LukasGold Aug 22, 2026
a7c35a7
feat(mcp): authenticate from an osw credential file
LukasGold Aug 22, 2026
d393a66
feat(mcp): select between multiple OSL instances at runtime
LukasGold Aug 22, 2026
ec54bcd
refactor: extract SDK-free osw.service core from osw.mcp
LukasGold Aug 25, 2026
431f33f
refactor(service): lift search tools into osw.service.ops
LukasGold Aug 25, 2026
1959876
refactor(service): lift remaining tool bodies into osw.service.ops
LukasGold Aug 25, 2026
c1cede3
feat(cli): add typer CLI assembled from the operation registry
LukasGold Aug 25, 2026
4ed8c7c
feat: remove filesystem paths from the MCP surface
LukasGold Aug 25, 2026
cb7d807
feat(mcp): pin one instance per server, wire annotations and meta
LukasGold Aug 25, 2026
b6fc0c0
refactor(mcp): drop tool closures, fold registration into server
LukasGold Aug 26, 2026
c3ee9de
build: type-check src/osw/mcp instead of excluding it
LukasGold Aug 26, 2026
cf21923
docs: document the osw CLI and unify the config reference
LukasGold Aug 26, 2026
085db4a
fix(config): find .env from the CWD, report config sources
LukasGold Aug 26, 2026
1070646
docs: move CLI and MCP sections out of the README
LukasGold Aug 27, 2026
a1ef49a
docs: default MCP examples to a credential file in env
LukasGold Aug 27, 2026
cd17247
feat(mcp): require an explicitly configured OSW_DOMAIN
LukasGold Aug 27, 2026
3d91d92
docs: restructure the CLI and MCP guide
LukasGold Aug 28, 2026
3a41c48
docs: simplify the CLI and MCP setup section
LukasGold Aug 28, 2026
84b69e9
added local folders to .gitignore
LukasGold Aug 28, 2026
951a66e
docs: correct why the CLI may infer an instance
LukasGold Aug 28, 2026
c1ef65e
Merge origin/main into feat/mcp-server
LukasGold Aug 28, 2026
8f81fec
refactor: de-isolate the mcp extra from the dev environment
LukasGold Aug 28, 2026
be75e47
docs: drop remaining references to the separate MCP environment
LukasGold Aug 28, 2026
2051c26
refactor(service): validate Settings with pydantic
LukasGold Aug 28, 2026
4a933af
Merge fix/in-memory-file-upload into feat/mcp-server
LukasGold Aug 28, 2026
4b7dd7e
test: stop test_init_from_env_vars leaking OSW_CRED_FILEPATH
LukasGold Aug 28, 2026
f4ef72c
test: do not assume the first ask-query hit carries jsondata
LukasGold Aug 28, 2026
56711ed
fix(service): validate read_only via pydantic instead of truthy set
LukasGold Aug 31, 2026
422d872
fix(deps): make python-dotenv a base dependency
LukasGold Sep 4, 2026
97b950a
feat(cli): find accounts.pwd.yaml in the working directory
LukasGold Sep 4, 2026
7d77397
feat(cli): print one configuration line by default
LukasGold Sep 4, 2026
568205b
docs(mcp): add a quick install section for claude mcp add
LukasGold Sep 4, 2026
e57eb1f
docs(cli): make the search help explain OSW-ID titles
LukasGold Sep 4, 2026
c7306a9
feat(search): add a page-content search
LukasGold Sep 4, 2026
38a49ea
docs(cli): rename `search text` to `search titles`
LukasGold Sep 4, 2026
4e70ab9
docs(mcp): describe the credential lookup without the rationale
LukasGold Sep 4, 2026
2b66474
feat(mcp): report config sources only when OSW_VERBOSE is set
LukasGold Sep 4, 2026
a3f5eec
test(mcp): cover main() on a successful start, quiet and verbose
LukasGold Sep 4, 2026
c0304eb
Merge origin/main into feat/mcp-server
LukasGold Sep 7, 2026
5c26a76
fix(service): flag truncation at the limit that reached the wiki
LukasGold Sep 7, 2026
bca9d1f
docs(service): say what search_entities actually counts
LukasGold Sep 7, 2026
3a9bb89
Merge remote-tracking branch 'origin/main' into feat/mcp-server
LukasGold Sep 17, 2026
d53f5c8
docs: reorganize CLI and MCP pages into a Tools section
LukasGold Sep 18, 2026
22cbc55
feat(cli): add instances status, fix the username status reports
LukasGold Sep 18, 2026
d5db23f
fix(service): print the running adapter's name in shared messages
LukasGold Sep 21, 2026
27ddac4
refactor(cli): rename osw search instances to osw search entities
LukasGold Sep 21, 2026
fe2882b
Merge branch 'main' into feat/mcp-server
LukasGold Sep 21, 2026
1c14ade
test(logging): reset the osw logger in its fixture
LukasGold Sep 21, 2026
827e963
refactor(service): log shared diagnostics instead of printing them
LukasGold Sep 21, 2026
34629db
build: declare click as a direct dependency
LukasGold Sep 21, 2026
2db7c1e
docs(service): correct the reason the config report flushes
LukasGold Sep 21, 2026
996be9c
test(cli): normalize typer's rich output before asserting
LukasGold Sep 21, 2026
3a0d704
fix(cli): write stdout and stderr as UTF-8, not the locale encoding
LukasGold Sep 21, 2026
6cf4a1f
feat(cli): suppress osw's import notice on the console scripts
LukasGold Sep 21, 2026
11bde33
test(cli): cover the osw-mcp script and the shim's level choice
LukasGold Sep 21, 2026
2925855
fix(config): validate domain and state_dir (#195)
LukasGold Sep 21, 2026
5a61980
Merge remote-tracking branch 'origin/main' into feat/mcp-server-pr-re…
LukasGold Sep 21, 2026
55aab85
test: decode subprocess output as UTF-8, not the locale codepage
LukasGold Sep 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -69,5 +69,8 @@ playground
/osw_files/
*/accounts.pwd.yaml
/accounts.pwd.yaml
.ign
.claude

# Local folders
.ign/
.claude/
graphify-out/
15 changes: 14 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ pip install osw
```

Optional extras (`osw[wikitext]`, `osw[DB]`, `osw[S3]`, `osw[dataimport]`,
`osw[UI]`, `osw[all]`) are described in the
`osw[UI]`, `osw[mcp]`, `osw[all]`) are described in the
[Get Started guide](https://opensemanticlab.github.io/osw-python/get-started/).

## Quickstart
Expand All @@ -39,6 +39,19 @@ More runnable scripts live in [examples/](examples/), and the
[Basics tutorial](docs/tutorials/basics.ipynb) walks through the
OpenSemanticLab data model.

## Tools

Installing `osw` also installs an `osw` command line client, and the
`osw[mcp]` extra adds an MCP server that exposes a live instance to agent
clients such as Claude Code:

```bash
osw search ask '[[Category:Item]]' --limit 5
```

Commands, tools and their configuration are described in the
[Tools guide](https://opensemanticlab.github.io/osw-python/tools/).

## Logging

osw reports what it is doing on the `osw` logger at INFO by default. Levels,
Expand Down
1 change: 1 addition & 0 deletions docs/get-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
| `osw[S3]` | Interact with S3 stores per S3FileController |
| `osw[dataimport]` | Additional tools to import data |
| `osw[UI]` | To use a helper UI to work with entity slots |
| `osw[mcp]` | [MCP server](tools/mcp.md) for agent clients |
| `osw[all]` | All of the above |

Install multiple extras with `pip install osw[opt1,opt2]`.
Expand Down
74 changes: 74 additions & 0 deletions docs/tools/cli.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# CLI

## Quick start

Both adapters need an instance and credentials. The quickest start is a
gitignored `.env` file in your project root:

```dotenv
OSW_DOMAIN=wiki-dev.open-semantic-lab.org
OSW_USERNAME=your-user
OSW_PASSWORD=your-password
```

The CLI searches upward from the working directory for it, so `osw status`
now reports the instance, the username (whether it comes from `OSW_USERNAME`
or a credential file), and connection state. The MCP server takes its
settings from the `env` block of its registration instead, see
[Registering a server](mcp.md#registering-a-server). Every variable is listed
under [Configuration](configuration.md).

## Command line

```bash
osw status
osw search ask '[[Category:Item]]' --limit 5
osw entity get 'Item:OSW1234...' --json | jq .
osw file cat 'File:Example.csv' # inline text
osw file download 'File:Example.csv' --target-dir ./tmp # to disk
```

Commands are grouped by subject:

| Group | Commands |
| --- | --- |
| `entity` | `get`, `put`, `export`, `delete` |
| `file` | `info`, `cat`, `write`, `download`, `upload` |
| `search` | `ask`, `titles`, `content`, `entities`, `sparql` |
| `slot` | `list`, `get`, `set` |
| `schema` | `get` |
| `instances` | `list`, `status` |
| `ledger` | `path` |
| top level | `status` |

`osw search entities` finds pages in the wiki that are instances of a
category, while `osw instances` is about the OSL servers this process can
connect to.

`osw instances list` lists the iris the process can connect to: the
env-configured domain plus every entry of a configured credential file.

`osw instances status` reports the same instances in more detail. For each
one it prints the iri, whether it is the active one, the username that would
be used, and whether a connection succeeded. It never prints passwords. The
instances are contacted one after another and a single attempt has no
timeout, so an unreachable instance delays the command until its connection
attempt gives up.

Global options apply to every command. They belong to the `osw` command
itself, so they come before the subcommand, the same way `git` and `docker`
options do: `osw --instance <iri> status`, not `osw status --instance <iri>`.
Typing them after the subcommand now produces an error that names the correct
form.

- `--instance IRI` picks the instance. Optional: it is only required when
`OSW_DOMAIN` is not set and the configured credential file holds more than
one iri.
- `--json` / `-j` writes machine-readable JSON to stdout and keeps osw's own
progress output on stderr, so it pipes cleanly into `jq`.
- `--read-only` refuses write operations.
- `--verbose` / `-v` shows full tracebacks instead of a one-line message, and
adds the env-file line to the source report described under
[Where settings come from](configuration.md#where-settings-come-from).

Failures exit non-zero with a short message on stderr and no traceback.
125 changes: 125 additions & 0 deletions docs/tools/configuration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
# Configuration

Both adapters share the settings below.

## Where settings come from

Settings are read from the process environment. A `.env` file fills that
environment; a real environment variable wins over the same name in a file.

**Env file**

| `OSW_ENV_FILE` | CLI | MCP server |
| --- | --- | --- |
| set | loads that file, searches nowhere | loads that file, searches nowhere |
| unset | searches upward from the working directory | searches nowhere |

**Credential file.** The first step that produces a file wins:

1. `OSW_CRED_FILEPATH` or an alias, set in the environment or the env file.
The run fails if this file has no entry for `OSW_DOMAIN`. That check is
skipped when `OSW_USERNAME` and `OSW_PASSWORD` are both set.
2. CLI only: `accounts.pwd.yaml` in the working directory. Parent directories
are not searched. This step is skipped when `OSW_USERNAME` or
`OSW_PASSWORD` is set. If the file has no entry for `OSW_DOMAIN` it is
ignored and the run continues.
3. No credential file.

**Source report.** Both adapters write to stderr before connecting. The first
line is labelled `credential file` when a file was found:

- `<path> (from the OSW_CRED_FILEPATH environment variable)`
- `<path> (from OSW_CRED_FILEPATH in the env file)`
- `<path> (accounts.pwd.yaml found in the working directory)`
- `<path> (accounts.pwd.yaml found in the working directory, ignored: no entry for domain '<domain>')`

and `credentials` when none was:

- `OSW_USERNAME/OSW_PASSWORD (from the environment)`
- `OSW_USERNAME/OSW_PASSWORD (from the env file)`
- `not configured (set OSW_CRED_FILEPATH, or OSW_USERNAME/OSW_PASSWORD)`

The second line is labelled `env file`. Which lines appear depends on the
adapter:

- **CLI**: the first line only. `--verbose`, or a command that fails, adds
the second.
- **MCP server**: neither, since its sources are fixed in the server entry.
`OSW_VERBOSE=true` prints both, and a failed start prints both regardless.

A verbose run of the CLI prints:

```text
[osw] credential file: /home/me/project/accounts.pwd.yaml (accounts.pwd.yaml found in the working directory)
[osw] env file : /home/me/project/.env (found from the working directory upward)
```

The prefix names the adapter that printed the line: `[osw]` for the CLI,
`[osw-mcp]` for the MCP server. This holds for every message the two share,
not only these two lines.

## Where messages go

The source report above is printed directly, because the adapter's own verbose
flag decides whether it appears, not the log level.

Every other message the adapters produce goes to the `osw` logger, together
with the records of the library itself. A failed connection check and an
unreadable provenance ledger are reported that way. `OSW_LOG_LEVEL` sets how
much of it appears, and an application that configures logging itself takes the
records over. See [Logging](../get-started.md#logging).

Both kinds of message are written to stderr, never to stdout. The MCP server
speaks JSON-RPC over stdout, and the CLI writes its `--json` output there, so
stdout has to stay free.

## Credentials

Keep credentials in a gitignored file. They are read once per process, into that
process only, and never written back to disk. Set either `OSW_USERNAME` and
`OSW_PASSWORD`, or `OSW_CRED_FILEPATH`.

A credential file uses the YAML format osw's `CredentialManager` reads, keyed
by iri (default file name: `accounts.pwd.yaml`):

```yaml
wiki-dev.open-semantic-lab.org:
username: your-user
password: your-password
```

A credential file may hold several iris. The CLI selects one automatically if it
is the only one, and otherwise requires `osw --instance <iri>`. The MCP server
never selects one, see [One server per instance](mcp.md).

## Variable reference

The canonical variable names are `OSW_*`. Older `OSW_MCP_*` and `OSL_*` names
stay accepted so existing deployments keep working, and the first name that is
set wins:

| Canonical | Also accepted | Meaning |
| --- | --- | --- |
| `OSW_DOMAIN` | `OSL_DOMAIN` | Instance to connect to. A bare host (`wiki.example.org`) or a full URL (`https://wiki.example.org/w/`); the host is taken from either, and a value no host can be read from (`https://`, `/w/index.php`) is rejected at startup |
| `OSW_USERNAME` | `OSL_USERNAME` | Login user |
| `OSW_PASSWORD` | `OSL_PASSWORD` | Login password |
| `OSW_CRED_FILEPATH` | `OSW_MCP_CRED_FILEPATH`, `OSL_CRED_FILEPATH` | YAML credential file, keyed by iri (falls back to `accounts.pwd.yaml` in the working directory, CLI only) |
| `OSW_ENV_FILE` | `OSW_MCP_ENV_FILE` | `.env` file to load |
| `OSW_READ_ONLY` | `OSW_MCP_READ_ONLY` | `true` refuses every write |
| `OSW_SPARQL_ENDPOINT` | | Endpoint for `sparql` queries |
| `OSW_STATE_DIR` | `OSW_MCP_STATE_DIR` | Where the provenance ledger is kept. Must be an absolute path; a leading `~` is expanded |
| `OSW_MAX_RESULTS` | `OSW_MCP_MAX_RESULTS` | Default result cap (100) |
| `OSW_MAX_CHARS` | `OSW_MCP_MAX_CHARS` | Result size cap in characters (100000) |
| `OSW_VERBOSE` | `OSW_MCP_VERBOSE` | `true` prints the configuration source report |

## Windows paths in a `.env` file

Quote them with single quotes, or leave them unquoted. A double-quoted value is
escape-decoded, so `\a` in a path silently becomes a BEL byte that renders as
nothing:

```dotenv
OSW_CRED_FILEPATH='C:\Users\me\accounts.pwd.yaml' # ok
OSW_CRED_FILEPATH=C:\Users\me\accounts.pwd.yaml # ok
OSW_CRED_FILEPATH="C:\Users\me\accounts.pwd.yaml" # broken: \a is eaten
```
41 changes: 41 additions & 0 deletions docs/tools/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Tools

Besides the Python API, osw ships two adapters that talk to a live instance:
the `osw` command line client, and an MCP server for agent clients such as
Claude Code. Both run the same operations from one shared, SDK-free core
(`osw.service`), so a command and its matching tool behave identically. They
differ in exactly one way: only the CLI accepts filesystem paths.

## Setup

Install one of the two; the second includes the first:

```bash
uv tool install osw # the `osw` command
uv tool install "osw[mcp]" # the same, plus the `osw-mcp` server
```

<details markdown="1">
<summary>Other ways to install</summary>

```bash
pip install "osw[mcp]" # into the active environment
uv add "osw[mcp]" # as a dependency of the current uv project
uvx --from "osw[mcp]" osw-mcp # run the server without installing it
```

`uvx` is what the registration examples further down use, so the server needs
no install of its own.

</details>

`osw[mcp]` is also part of `osw[all]`. The other extras are listed in the
[Get Started guide](../get-started.md#optional-extras).

## In this section

| Page | Contents |
| ---- | -------- |
| [CLI](cli.md) | The `.env` quick start, the full command reference, and the global flags |
| [MCP server](mcp.md) | The tool surface, the no-filesystem-access and one-server-per-instance rules, and how to register the server with a client such as Claude Code |
| [Configuration](configuration.md) | What both adapters share: where credentials and settings come from, and the full environment-variable reference |
Loading
Loading