knowledge-bundle-mcp 0.1.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.
- knowledge_bundle_mcp-0.1.0/.github/workflows/ci.yml +29 -0
- knowledge_bundle_mcp-0.1.0/.gitignore +13 -0
- knowledge_bundle_mcp-0.1.0/CHANGELOG.md +18 -0
- knowledge_bundle_mcp-0.1.0/LICENSE +21 -0
- knowledge_bundle_mcp-0.1.0/PKG-INFO +136 -0
- knowledge_bundle_mcp-0.1.0/README.md +117 -0
- knowledge_bundle_mcp-0.1.0/pyproject.toml +39 -0
- knowledge_bundle_mcp-0.1.0/scripts/scan_identifiers.py +76 -0
- knowledge_bundle_mcp-0.1.0/server.json +43 -0
- knowledge_bundle_mcp-0.1.0/src/knowledge_mcp/__init__.py +3 -0
- knowledge_bundle_mcp-0.1.0/src/knowledge_mcp/config.py +56 -0
- knowledge_bundle_mcp-0.1.0/src/knowledge_mcp/errors.py +26 -0
- knowledge_bundle_mcp-0.1.0/src/knowledge_mcp/modules/__init__.py +70 -0
- knowledge_bundle_mcp-0.1.0/src/knowledge_mcp/modules/open_notebook.py +468 -0
- knowledge_bundle_mcp-0.1.0/src/knowledge_mcp/scrub.py +47 -0
- knowledge_bundle_mcp-0.1.0/src/knowledge_mcp/server.py +102 -0
- knowledge_bundle_mcp-0.1.0/tests/test_knowledge.py +364 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
name: ci
|
|
2
|
+
on: [push, pull_request]
|
|
3
|
+
permissions:
|
|
4
|
+
contents: read
|
|
5
|
+
jobs:
|
|
6
|
+
test:
|
|
7
|
+
strategy:
|
|
8
|
+
matrix:
|
|
9
|
+
os: [ubuntu-latest, macos-latest]
|
|
10
|
+
python: ["3.11", "3.12", "3.13"]
|
|
11
|
+
runs-on: ${{ matrix.os }}
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
- uses: astral-sh/setup-uv@v5
|
|
15
|
+
- run: uv venv -p ${{ matrix.python }} && uv pip install -e '.[dev]'
|
|
16
|
+
- run: .venv/bin/pytest -q
|
|
17
|
+
scan:
|
|
18
|
+
runs-on: ubuntu-latest
|
|
19
|
+
steps:
|
|
20
|
+
- uses: actions/checkout@v4
|
|
21
|
+
with:
|
|
22
|
+
fetch-depth: 0
|
|
23
|
+
# The gitleaks CLI is free; the GitHub Action needs a license for organization repos.
|
|
24
|
+
- name: gitleaks (full history)
|
|
25
|
+
run: docker run --rm -v "$PWD:/repo" ghcr.io/gitleaks/gitleaks:v8.30.1 git /repo --no-banner --redact
|
|
26
|
+
- name: Identifier scan
|
|
27
|
+
env:
|
|
28
|
+
IDENTIFIER_PATTERNS: ${{ secrets.IDENTIFIER_PATTERNS }}
|
|
29
|
+
run: python3 scripts/scan_identifiers.py .
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
Knowledge follows [semantic versioning](https://semver.org). Modules added after 0.1 ship switched off.
|
|
4
|
+
|
|
5
|
+
## 0.1.0 — unreleased
|
|
6
|
+
|
|
7
|
+
- First release as one bundle, with one module: `open_notebook` (Open Notebook over its REST API).
|
|
8
|
+
- `knowledge_modules` discovery tool: which modules are on, which settings are missing, what each tool does.
|
|
9
|
+
- Tools: list notebooks, search (keyword or vector, optionally one notebook), list a notebook's notes, read a note, read a source, create a note.
|
|
10
|
+
- Paged results with `offset` and `next_offset`; every tool returns structured data with a published output schema.
|
|
11
|
+
- `open_notebook_note_create` previews by default. Saving needs the person's approval: asked through MCP elicitation, or given ahead of time with `KNOWLEDGE_OPEN_NOTEBOOK_ALLOWED_NOTEBOOKS` or `KNOWLEDGE_OPEN_NOTEBOOK_REQUIRE_CONFIRM=false`. The assistant's own `confirm=true` is never enough.
|
|
12
|
+
- Text that looks like a credential is refused before anything is written; the error names the kind, never the value.
|
|
13
|
+
- Optional server password (Bearer), read from settings only.
|
|
14
|
+
- Runs over stdio or streamable HTTP. Starts with no settings and reports what is missing when a tool is called.
|
|
15
|
+
|
|
16
|
+
MIT License
|
|
17
|
+
|
|
18
|
+
Copyright (c) 2026 Seaynic Labs
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Seaynic Labs
|
|
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.
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: knowledge-bundle-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: MCP bundle for people with a self-hosted knowledge base. First module: Open Notebook.
|
|
5
|
+
Project-URL: Repository, https://github.com/seayniclabs/knowledge
|
|
6
|
+
Author: Seaynic Labs
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Keywords: knowledge-base,mcp,model-context-protocol,notes,open-notebook
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Requires-Python: >=3.11
|
|
13
|
+
Requires-Dist: httpx>=0.27
|
|
14
|
+
Requires-Dist: mcp[cli]<2,>=1.27
|
|
15
|
+
Requires-Dist: pydantic>=2.7
|
|
16
|
+
Provides-Extra: dev
|
|
17
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
|
|
20
|
+
# Knowledge
|
|
21
|
+
|
|
22
|
+
<!-- mcp-name: io.github.seayniclabs/knowledge -->
|
|
23
|
+
|
|
24
|
+
Knowledge is an MCP server for people who run their own knowledge base. It lets an AI assistant search, read and write the notes in it, from one install.
|
|
25
|
+
|
|
26
|
+
It is a **bundle**: one server made of modules. You switch on the modules you use. New modules arrive switched off, so an update never changes what your assistant can do until you choose to.
|
|
27
|
+
|
|
28
|
+
**Audience:** people who self-host [Open Notebook](https://github.com/lfnovo/open-notebook) and want their assistant to use it.
|
|
29
|
+
|
|
30
|
+
| Module | What it does | Status |
|
|
31
|
+
|---|---|---|
|
|
32
|
+
| `open_notebook` | Open Notebook over its REST API: list notebooks, search (keyword or meaning), read notes and sources, create a note (you approve each one) | 0.1 |
|
|
33
|
+
|
|
34
|
+
Knowledge works with any MCP client: Claude Desktop, Claude Code, Cursor, VS Code, Sonique, Hermes, OpenClaw and others. It runs on your computer and stores nothing. Your notes stay in your Open Notebook server.
|
|
35
|
+
|
|
36
|
+
## Install
|
|
37
|
+
|
|
38
|
+
Knowledge needs Python 3.11 or newer. With [uv](https://docs.astral.sh/uv/) you don't install anything ahead of time:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
uvx knowledge-bundle-mcp --version
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Or with pip:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pip install knowledge-bundle-mcp
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Settings
|
|
51
|
+
|
|
52
|
+
Set these as environment variables in your MCP client's config, or put them in a config file (see below). Knowledge starts without any of them and tells you what's missing when you call a tool.
|
|
53
|
+
|
|
54
|
+
| Variable | Module | What it is |
|
|
55
|
+
|---|---|---|
|
|
56
|
+
| `KNOWLEDGE_OPEN_NOTEBOOK_URL` | open_notebook | Address of your Open Notebook server, e.g. `https://notebook.example.com`. No path. |
|
|
57
|
+
| `KNOWLEDGE_OPEN_NOTEBOOK_PASSWORD` | open_notebook | Only if your server has a password (`OPEN_NOTEBOOK_PASSWORD` on the server). Leave it unset otherwise. |
|
|
58
|
+
| `KNOWLEDGE_OPEN_NOTEBOOK_REQUIRE_CONFIRM` | open_notebook | `true` by default: you approve each new note. `false` lets the assistant create notes on its own. |
|
|
59
|
+
| `KNOWLEDGE_OPEN_NOTEBOOK_ALLOWED_NOTEBOOKS` | open_notebook | Optional. Comma-separated notebook names the assistant may write to without asking, e.g. `Research,Inbox` |
|
|
60
|
+
| `KNOWLEDGE_<MODULE>_ENABLED` | any | `false` turns a module off, e.g. `KNOWLEDGE_OPEN_NOTEBOOK_ENABLED=false` |
|
|
61
|
+
|
|
62
|
+
**Config file (optional).** Knowledge also reads `~/.config/knowledge/config.toml`, or the file named by `KNOWLEDGE_CONFIG`. Environment variables win over the file.
|
|
63
|
+
|
|
64
|
+
```toml
|
|
65
|
+
[open_notebook]
|
|
66
|
+
url = "https://notebook.example.com"
|
|
67
|
+
password = "change-me"
|
|
68
|
+
require_confirm = true
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Add it to your MCP client
|
|
72
|
+
|
|
73
|
+
Most clients use the same JSON shape. Put it where your client keeps MCP servers:
|
|
74
|
+
|
|
75
|
+
```json
|
|
76
|
+
{
|
|
77
|
+
"mcpServers": {
|
|
78
|
+
"knowledge": {
|
|
79
|
+
"command": "uvx",
|
|
80
|
+
"args": ["knowledge-bundle-mcp"],
|
|
81
|
+
"env": {
|
|
82
|
+
"KNOWLEDGE_OPEN_NOTEBOOK_URL": "https://notebook.example.com"
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
| Client | Where the config goes |
|
|
90
|
+
|---|---|
|
|
91
|
+
| Claude Desktop | `claude_desktop_config.json` (Settings → Developer → Edit Config) |
|
|
92
|
+
| Claude Code | `claude mcp add knowledge -- uvx knowledge-bundle-mcp`, then add the `env` values, or a project `.mcp.json` |
|
|
93
|
+
| Cursor | `~/.cursor/mcp.json`, or `.cursor/mcp.json` in a project |
|
|
94
|
+
| VS Code | `.vscode/mcp.json`, using `"servers"` instead of `"mcpServers"` |
|
|
95
|
+
| Sonique, Hermes, OpenClaw, others | Any client that launches a stdio MCP server: command `uvx`, args `["knowledge-bundle-mcp"]`, plus the env values |
|
|
96
|
+
|
|
97
|
+
**Over HTTP.** For clients that connect to a URL instead of launching a process:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
knowledge-mcp --transport http --port 8765
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
It serves streamable HTTP at `http://127.0.0.1:8765/mcp`. It listens only on your own machine by default. Put authentication in front of it before exposing it anywhere else.
|
|
104
|
+
|
|
105
|
+
## Tools
|
|
106
|
+
|
|
107
|
+
| Tool | What it returns |
|
|
108
|
+
|---|---|
|
|
109
|
+
| `knowledge_modules` | Which modules are on, which settings are missing, and what each tool does. Call this first. |
|
|
110
|
+
| `open_notebook_notebooks` | Your notebooks, most recently updated first, paged |
|
|
111
|
+
| `open_notebook_search` | Matching sources and notes. `mode` is `text` (keywords) or `vector` (meaning; needs an embedding model on the server). Optional `notebook_id`. Paged. |
|
|
112
|
+
| `open_notebook_notes` | The notes in one notebook, titles only, paged |
|
|
113
|
+
| `open_notebook_note_get` | One note's content, cut at `max_chars` |
|
|
114
|
+
| `open_notebook_source_get` | One source: title, topics, extracted text, notebooks |
|
|
115
|
+
| `open_notebook_note_create` | Saves a note after you approve it. Without `confirm=true` it only returns a preview. |
|
|
116
|
+
|
|
117
|
+
Every tool returns structured data with a published schema, so an assistant can work with the results instead of parsing text. Errors always look like `knowledge.<module>.<code>: <message> Fix: <hint>`.
|
|
118
|
+
|
|
119
|
+
## Safety
|
|
120
|
+
|
|
121
|
+
- Knowledge only calls the Open Notebook server you configure. It sends nothing anywhere else and keeps no data of its own.
|
|
122
|
+
- `open_notebook_note_create` changes your notes, so **you** approve each one. When the assistant asks to save, Knowledge asks you directly through your MCP client (MCP elicitation) and shows the notebook, title and full text. The assistant can't answer that question for you.
|
|
123
|
+
- Some MCP clients can't ask you questions yet. In those, Knowledge refuses to write unless you've pre-approved the notebook with `KNOWLEDGE_OPEN_NOTEBOOK_ALLOWED_NOTEBOOKS`, or turned approval off with `KNOWLEDGE_OPEN_NOTEBOOK_REQUIRE_CONFIRM=false`.
|
|
124
|
+
- Before anything is written, text that looks like a credential (API keys, tokens, private keys, passwords in URLs) is refused. The error names the kind of credential found, never the value.
|
|
125
|
+
- Use an `https://` address for your server, and give the password only to Knowledge, not to other tools.
|
|
126
|
+
|
|
127
|
+
## Develop
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
uv venv && uv pip install -e '.[dev]'
|
|
131
|
+
.venv/bin/pytest
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## License
|
|
135
|
+
|
|
136
|
+
MIT. Published by Seaynic Labs.
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# Knowledge
|
|
2
|
+
|
|
3
|
+
<!-- mcp-name: io.github.seayniclabs/knowledge -->
|
|
4
|
+
|
|
5
|
+
Knowledge is an MCP server for people who run their own knowledge base. It lets an AI assistant search, read and write the notes in it, from one install.
|
|
6
|
+
|
|
7
|
+
It is a **bundle**: one server made of modules. You switch on the modules you use. New modules arrive switched off, so an update never changes what your assistant can do until you choose to.
|
|
8
|
+
|
|
9
|
+
**Audience:** people who self-host [Open Notebook](https://github.com/lfnovo/open-notebook) and want their assistant to use it.
|
|
10
|
+
|
|
11
|
+
| Module | What it does | Status |
|
|
12
|
+
|---|---|---|
|
|
13
|
+
| `open_notebook` | Open Notebook over its REST API: list notebooks, search (keyword or meaning), read notes and sources, create a note (you approve each one) | 0.1 |
|
|
14
|
+
|
|
15
|
+
Knowledge works with any MCP client: Claude Desktop, Claude Code, Cursor, VS Code, Sonique, Hermes, OpenClaw and others. It runs on your computer and stores nothing. Your notes stay in your Open Notebook server.
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
Knowledge needs Python 3.11 or newer. With [uv](https://docs.astral.sh/uv/) you don't install anything ahead of time:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
uvx knowledge-bundle-mcp --version
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Or with pip:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pip install knowledge-bundle-mcp
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Settings
|
|
32
|
+
|
|
33
|
+
Set these as environment variables in your MCP client's config, or put them in a config file (see below). Knowledge starts without any of them and tells you what's missing when you call a tool.
|
|
34
|
+
|
|
35
|
+
| Variable | Module | What it is |
|
|
36
|
+
|---|---|---|
|
|
37
|
+
| `KNOWLEDGE_OPEN_NOTEBOOK_URL` | open_notebook | Address of your Open Notebook server, e.g. `https://notebook.example.com`. No path. |
|
|
38
|
+
| `KNOWLEDGE_OPEN_NOTEBOOK_PASSWORD` | open_notebook | Only if your server has a password (`OPEN_NOTEBOOK_PASSWORD` on the server). Leave it unset otherwise. |
|
|
39
|
+
| `KNOWLEDGE_OPEN_NOTEBOOK_REQUIRE_CONFIRM` | open_notebook | `true` by default: you approve each new note. `false` lets the assistant create notes on its own. |
|
|
40
|
+
| `KNOWLEDGE_OPEN_NOTEBOOK_ALLOWED_NOTEBOOKS` | open_notebook | Optional. Comma-separated notebook names the assistant may write to without asking, e.g. `Research,Inbox` |
|
|
41
|
+
| `KNOWLEDGE_<MODULE>_ENABLED` | any | `false` turns a module off, e.g. `KNOWLEDGE_OPEN_NOTEBOOK_ENABLED=false` |
|
|
42
|
+
|
|
43
|
+
**Config file (optional).** Knowledge also reads `~/.config/knowledge/config.toml`, or the file named by `KNOWLEDGE_CONFIG`. Environment variables win over the file.
|
|
44
|
+
|
|
45
|
+
```toml
|
|
46
|
+
[open_notebook]
|
|
47
|
+
url = "https://notebook.example.com"
|
|
48
|
+
password = "change-me"
|
|
49
|
+
require_confirm = true
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Add it to your MCP client
|
|
53
|
+
|
|
54
|
+
Most clients use the same JSON shape. Put it where your client keeps MCP servers:
|
|
55
|
+
|
|
56
|
+
```json
|
|
57
|
+
{
|
|
58
|
+
"mcpServers": {
|
|
59
|
+
"knowledge": {
|
|
60
|
+
"command": "uvx",
|
|
61
|
+
"args": ["knowledge-bundle-mcp"],
|
|
62
|
+
"env": {
|
|
63
|
+
"KNOWLEDGE_OPEN_NOTEBOOK_URL": "https://notebook.example.com"
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
| Client | Where the config goes |
|
|
71
|
+
|---|---|
|
|
72
|
+
| Claude Desktop | `claude_desktop_config.json` (Settings → Developer → Edit Config) |
|
|
73
|
+
| Claude Code | `claude mcp add knowledge -- uvx knowledge-bundle-mcp`, then add the `env` values, or a project `.mcp.json` |
|
|
74
|
+
| Cursor | `~/.cursor/mcp.json`, or `.cursor/mcp.json` in a project |
|
|
75
|
+
| VS Code | `.vscode/mcp.json`, using `"servers"` instead of `"mcpServers"` |
|
|
76
|
+
| Sonique, Hermes, OpenClaw, others | Any client that launches a stdio MCP server: command `uvx`, args `["knowledge-bundle-mcp"]`, plus the env values |
|
|
77
|
+
|
|
78
|
+
**Over HTTP.** For clients that connect to a URL instead of launching a process:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
knowledge-mcp --transport http --port 8765
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
It serves streamable HTTP at `http://127.0.0.1:8765/mcp`. It listens only on your own machine by default. Put authentication in front of it before exposing it anywhere else.
|
|
85
|
+
|
|
86
|
+
## Tools
|
|
87
|
+
|
|
88
|
+
| Tool | What it returns |
|
|
89
|
+
|---|---|
|
|
90
|
+
| `knowledge_modules` | Which modules are on, which settings are missing, and what each tool does. Call this first. |
|
|
91
|
+
| `open_notebook_notebooks` | Your notebooks, most recently updated first, paged |
|
|
92
|
+
| `open_notebook_search` | Matching sources and notes. `mode` is `text` (keywords) or `vector` (meaning; needs an embedding model on the server). Optional `notebook_id`. Paged. |
|
|
93
|
+
| `open_notebook_notes` | The notes in one notebook, titles only, paged |
|
|
94
|
+
| `open_notebook_note_get` | One note's content, cut at `max_chars` |
|
|
95
|
+
| `open_notebook_source_get` | One source: title, topics, extracted text, notebooks |
|
|
96
|
+
| `open_notebook_note_create` | Saves a note after you approve it. Without `confirm=true` it only returns a preview. |
|
|
97
|
+
|
|
98
|
+
Every tool returns structured data with a published schema, so an assistant can work with the results instead of parsing text. Errors always look like `knowledge.<module>.<code>: <message> Fix: <hint>`.
|
|
99
|
+
|
|
100
|
+
## Safety
|
|
101
|
+
|
|
102
|
+
- Knowledge only calls the Open Notebook server you configure. It sends nothing anywhere else and keeps no data of its own.
|
|
103
|
+
- `open_notebook_note_create` changes your notes, so **you** approve each one. When the assistant asks to save, Knowledge asks you directly through your MCP client (MCP elicitation) and shows the notebook, title and full text. The assistant can't answer that question for you.
|
|
104
|
+
- Some MCP clients can't ask you questions yet. In those, Knowledge refuses to write unless you've pre-approved the notebook with `KNOWLEDGE_OPEN_NOTEBOOK_ALLOWED_NOTEBOOKS`, or turned approval off with `KNOWLEDGE_OPEN_NOTEBOOK_REQUIRE_CONFIRM=false`.
|
|
105
|
+
- Before anything is written, text that looks like a credential (API keys, tokens, private keys, passwords in URLs) is refused. The error names the kind of credential found, never the value.
|
|
106
|
+
- Use an `https://` address for your server, and give the password only to Knowledge, not to other tools.
|
|
107
|
+
|
|
108
|
+
## Develop
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
uv venv && uv pip install -e '.[dev]'
|
|
112
|
+
.venv/bin/pytest
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## License
|
|
116
|
+
|
|
117
|
+
MIT. Published by Seaynic Labs.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "knowledge-bundle-mcp"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "MCP bundle for people with a self-hosted knowledge base. First module: Open Notebook."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.11"
|
|
7
|
+
license = "MIT"
|
|
8
|
+
license-files = ["LICENSE"]
|
|
9
|
+
authors = [{ name = "Seaynic Labs" }]
|
|
10
|
+
# mcp is capped below 2: 2.x removed FastMCP, and unpinned installs crash.
|
|
11
|
+
dependencies = [
|
|
12
|
+
"mcp[cli]>=1.27,<2",
|
|
13
|
+
"httpx>=0.27",
|
|
14
|
+
"pydantic>=2.7",
|
|
15
|
+
]
|
|
16
|
+
keywords = ["mcp", "model-context-protocol", "open-notebook", "knowledge-base", "notes"]
|
|
17
|
+
classifiers = [
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Operating System :: OS Independent",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
[project.urls]
|
|
23
|
+
Repository = "https://github.com/seayniclabs/knowledge"
|
|
24
|
+
|
|
25
|
+
[project.optional-dependencies]
|
|
26
|
+
dev = ["pytest>=8"]
|
|
27
|
+
|
|
28
|
+
[project.scripts]
|
|
29
|
+
knowledge-mcp = "knowledge_mcp.server:main"
|
|
30
|
+
|
|
31
|
+
[build-system]
|
|
32
|
+
requires = ["hatchling"]
|
|
33
|
+
build-backend = "hatchling.build"
|
|
34
|
+
|
|
35
|
+
[tool.hatch.build.targets.wheel]
|
|
36
|
+
packages = ["src/knowledge_mcp"]
|
|
37
|
+
|
|
38
|
+
[tool.pytest.ini_options]
|
|
39
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Fail if any tracked file contains a forbidden string.
|
|
3
|
+
|
|
4
|
+
The patterns are not stored in this repository. Pass them in with
|
|
5
|
+
``--patterns FILE`` or the ``IDENTIFIER_PATTERNS`` environment variable
|
|
6
|
+
(one case-insensitive regular expression per line; ``#`` starts a comment).
|
|
7
|
+
A line starting with ``!`` is an allowed exception: a match that also matches
|
|
8
|
+
an exception is ignored.
|
|
9
|
+
|
|
10
|
+
Usage: python scripts/scan_identifiers.py --patterns ~/patterns.txt [path ...]
|
|
11
|
+
Exit status 0 = clean, 1 = hits found, 2 = no patterns given.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import argparse
|
|
17
|
+
import os
|
|
18
|
+
import re
|
|
19
|
+
import subprocess
|
|
20
|
+
import sys
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def load(text: str) -> tuple[list[re.Pattern], list[re.Pattern]]:
|
|
25
|
+
block, allow = [], []
|
|
26
|
+
for line in text.splitlines():
|
|
27
|
+
line = line.split(" #", 1)[0].strip()
|
|
28
|
+
if not line or line.startswith("#"):
|
|
29
|
+
continue
|
|
30
|
+
(allow if line.startswith("!") else block).append(re.compile(line.lstrip("!"), re.I))
|
|
31
|
+
return block, allow
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def tracked_files(root: Path) -> list[Path]:
|
|
35
|
+
try:
|
|
36
|
+
out = subprocess.run(["git", "ls-files", "-co", "--exclude-standard"], cwd=root,
|
|
37
|
+
capture_output=True, text=True, check=True).stdout
|
|
38
|
+
return [root / p for p in out.splitlines()]
|
|
39
|
+
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
40
|
+
skip = {".git", ".venv", "venv", "node_modules", "__pycache__", "dist", "build"}
|
|
41
|
+
return [p for p in root.rglob("*") if p.is_file() and not skip & set(p.parts)]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def main() -> int:
|
|
45
|
+
ap = argparse.ArgumentParser()
|
|
46
|
+
ap.add_argument("--patterns", help="file of patterns (default: $IDENTIFIER_PATTERNS)")
|
|
47
|
+
ap.add_argument("paths", nargs="*", default=["."])
|
|
48
|
+
args = ap.parse_args()
|
|
49
|
+
|
|
50
|
+
text = Path(args.patterns).expanduser().read_text() if args.patterns else os.environ.get("IDENTIFIER_PATTERNS", "")
|
|
51
|
+
block, allow = load(text)
|
|
52
|
+
if not block:
|
|
53
|
+
print("scan_identifiers: no patterns given (use --patterns or IDENTIFIER_PATTERNS)", file=sys.stderr)
|
|
54
|
+
return 2
|
|
55
|
+
|
|
56
|
+
hits = 0
|
|
57
|
+
for root in map(Path, args.paths):
|
|
58
|
+
for f in tracked_files(root) if root.is_dir() else [root]:
|
|
59
|
+
try:
|
|
60
|
+
lines = f.read_text(errors="ignore").splitlines()
|
|
61
|
+
except OSError:
|
|
62
|
+
continue
|
|
63
|
+
for n, line in enumerate(lines, 1):
|
|
64
|
+
for pat in block:
|
|
65
|
+
for m in pat.finditer(line):
|
|
66
|
+
if any(a.search(line) for a in allow):
|
|
67
|
+
continue
|
|
68
|
+
hits += 1
|
|
69
|
+
# Print where, not the line itself: the line may hold the thing we're hiding.
|
|
70
|
+
print(f"{f}:{n}: matches pattern #{block.index(pat) + 1}")
|
|
71
|
+
print(f"scan_identifiers: {hits} hit(s)", file=sys.stderr)
|
|
72
|
+
return 1 if hits else 0
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
if __name__ == "__main__":
|
|
76
|
+
sys.exit(main())
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
|
+
"name": "io.github.seayniclabs/knowledge",
|
|
4
|
+
"description": "Search, read and write your self-hosted Open Notebook knowledge base from an AI assistant.",
|
|
5
|
+
"repository": {
|
|
6
|
+
"url": "https://github.com/seayniclabs/knowledge",
|
|
7
|
+
"source": "github"
|
|
8
|
+
},
|
|
9
|
+
"version": "0.1.0",
|
|
10
|
+
"packages": [
|
|
11
|
+
{
|
|
12
|
+
"registryType": "pypi",
|
|
13
|
+
"identifier": "knowledge-bundle-mcp",
|
|
14
|
+
"version": "0.1.0",
|
|
15
|
+
"transport": {
|
|
16
|
+
"type": "stdio"
|
|
17
|
+
},
|
|
18
|
+
"environmentVariables": [
|
|
19
|
+
{
|
|
20
|
+
"name": "KNOWLEDGE_OPEN_NOTEBOOK_URL",
|
|
21
|
+
"description": "Address of your Open Notebook server, e.g. https://notebook.example.com",
|
|
22
|
+
"isRequired": false
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"name": "KNOWLEDGE_OPEN_NOTEBOOK_PASSWORD",
|
|
26
|
+
"description": "Password for your Open Notebook server, if it has one",
|
|
27
|
+
"isRequired": false,
|
|
28
|
+
"isSecret": true
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"name": "KNOWLEDGE_OPEN_NOTEBOOK_REQUIRE_CONFIRM",
|
|
32
|
+
"description": "true (default): you approve each new note. false: the assistant may write on its own",
|
|
33
|
+
"isRequired": false
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"name": "KNOWLEDGE_OPEN_NOTEBOOK_ALLOWED_NOTEBOOKS",
|
|
37
|
+
"description": "Comma-separated notebook names the assistant may write to without asking",
|
|
38
|
+
"isRequired": false
|
|
39
|
+
}
|
|
40
|
+
]
|
|
41
|
+
}
|
|
42
|
+
]
|
|
43
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""Settings come from environment variables, then an optional TOML file.
|
|
2
|
+
|
|
3
|
+
Environment variables win. The file is ``$KNOWLEDGE_CONFIG`` if set, otherwise
|
|
4
|
+
``$XDG_CONFIG_HOME/knowledge/config.toml`` (``~/.config/knowledge/config.toml``).
|
|
5
|
+
Each module reads its own ``[<module>]`` table; ``KNOWLEDGE_<MODULE>_<KEY>`` overrides
|
|
6
|
+
any key in it. Nothing here assumes a particular MCP client.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import os
|
|
12
|
+
import tomllib
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Mapping
|
|
15
|
+
|
|
16
|
+
TRUE = {"1", "true", "yes", "on"}
|
|
17
|
+
FALSE = {"0", "false", "no", "off"}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def config_path(env: Mapping[str, str]) -> Path:
|
|
21
|
+
if env.get("KNOWLEDGE_CONFIG"):
|
|
22
|
+
return Path(env["KNOWLEDGE_CONFIG"]).expanduser()
|
|
23
|
+
base = env.get("XDG_CONFIG_HOME") or str(Path.home() / ".config")
|
|
24
|
+
return Path(base) / "knowledge" / "config.toml"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class Config:
|
|
28
|
+
def __init__(self, env: Mapping[str, str] | None = None, file_data: dict | None = None):
|
|
29
|
+
self.env = dict(os.environ if env is None else env)
|
|
30
|
+
if file_data is None:
|
|
31
|
+
path = config_path(self.env)
|
|
32
|
+
file_data = tomllib.loads(path.read_text()) if path.is_file() else {}
|
|
33
|
+
self.file = file_data
|
|
34
|
+
|
|
35
|
+
def get(self, module: str, key: str, default: str | None = None) -> str | None:
|
|
36
|
+
value = self.env.get(f"KNOWLEDGE_{module.upper()}_{key.upper()}")
|
|
37
|
+
if value not in (None, ""):
|
|
38
|
+
return value
|
|
39
|
+
value = self.file.get(module, {}).get(key)
|
|
40
|
+
if value not in (None, ""):
|
|
41
|
+
return str(value)
|
|
42
|
+
return default
|
|
43
|
+
|
|
44
|
+
def flag(self, module: str, key: str, default: bool) -> bool:
|
|
45
|
+
value = self.get(module, key)
|
|
46
|
+
if value is None:
|
|
47
|
+
return default
|
|
48
|
+
value = value.strip().lower()
|
|
49
|
+
if value in TRUE:
|
|
50
|
+
return True
|
|
51
|
+
if value in FALSE:
|
|
52
|
+
return False
|
|
53
|
+
return default
|
|
54
|
+
|
|
55
|
+
def enabled(self, module: str, default: bool) -> bool:
|
|
56
|
+
return self.flag(module, "enabled", default)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""One error shape for every module, so scripts can rely on it.
|
|
2
|
+
|
|
3
|
+
Every failure a tool reports reads: ``knowledge.<module>.<code>: <message> Fix: <hint>``.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from mcp.server.fastmcp.exceptions import ToolError
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class KnowledgeError(ToolError):
|
|
10
|
+
def __init__(self, module: str, code: str, message: str, hint: str = ""):
|
|
11
|
+
self.module = module
|
|
12
|
+
self.code = code
|
|
13
|
+
text = f"knowledge.{module}.{code}: {message}"
|
|
14
|
+
if hint:
|
|
15
|
+
text += f" Fix: {hint}"
|
|
16
|
+
super().__init__(text)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def not_configured(module: str, missing: list[str]) -> KnowledgeError:
|
|
20
|
+
return KnowledgeError(
|
|
21
|
+
module,
|
|
22
|
+
"not_configured",
|
|
23
|
+
f"missing setting(s) {', '.join(missing)}.",
|
|
24
|
+
"set them as environment variables or in the [" + module + "] section of the "
|
|
25
|
+
"config file, then restart the server. Call knowledge_modules to see every setting.",
|
|
26
|
+
)
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""Module contract and registry.
|
|
2
|
+
|
|
3
|
+
A module is one integration (Open Notebook, ...). It declares its settings and
|
|
4
|
+
registers its tools on the shared server. Adding a module = adding a file here and
|
|
5
|
+
listing it in ``all_modules``. Modules added after 0.1 must set ``default_enabled = False``
|
|
6
|
+
so an update never turns on something the user did not choose.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from dataclasses import dataclass, field
|
|
12
|
+
from typing import Callable
|
|
13
|
+
|
|
14
|
+
import httpx
|
|
15
|
+
from mcp.server.fastmcp import FastMCP
|
|
16
|
+
|
|
17
|
+
from ..config import Config
|
|
18
|
+
from ..errors import not_configured
|
|
19
|
+
|
|
20
|
+
ClientFactory = Callable[[], httpx.AsyncClient]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def default_client_factory() -> httpx.AsyncClient:
|
|
24
|
+
return httpx.AsyncClient(timeout=60.0)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass(frozen=True)
|
|
28
|
+
class Setting:
|
|
29
|
+
key: str
|
|
30
|
+
help: str
|
|
31
|
+
secret: bool = False
|
|
32
|
+
required: bool = True
|
|
33
|
+
|
|
34
|
+
def env_var(self, module: str) -> str:
|
|
35
|
+
return f"KNOWLEDGE_{module.upper()}_{self.key.upper()}"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass
|
|
39
|
+
class Module:
|
|
40
|
+
name: str
|
|
41
|
+
summary: str
|
|
42
|
+
settings: list[Setting]
|
|
43
|
+
default_enabled: bool = True
|
|
44
|
+
tools: dict[str, str] = field(default_factory=dict) # tool name -> one-line purpose
|
|
45
|
+
|
|
46
|
+
def missing(self, config: Config) -> list[str]:
|
|
47
|
+
return [
|
|
48
|
+
s.env_var(self.name)
|
|
49
|
+
for s in self.settings
|
|
50
|
+
if s.required and not config.get(self.name, s.key)
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
def require(self, config: Config, *keys: str) -> dict[str, str]:
|
|
54
|
+
"""Return the named settings, or raise a not_configured error naming the env vars."""
|
|
55
|
+
values = {k: config.get(self.name, k) for k in keys}
|
|
56
|
+
absent = [
|
|
57
|
+
s.env_var(self.name) for s in self.settings if s.key in keys and not values[s.key]
|
|
58
|
+
]
|
|
59
|
+
if absent:
|
|
60
|
+
raise not_configured(self.name, absent)
|
|
61
|
+
return values # type: ignore[return-value]
|
|
62
|
+
|
|
63
|
+
def register(self, mcp: FastMCP, config: Config, client_factory: ClientFactory) -> None:
|
|
64
|
+
raise NotImplementedError
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def all_modules() -> list[Module]:
|
|
68
|
+
from .open_notebook import OpenNotebookModule
|
|
69
|
+
|
|
70
|
+
return [OpenNotebookModule()]
|