sqlite-kb-mcp 0.1.0__py3-none-any.whl
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.
mcp_knowledge_base/db.py
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
"""Data layer: SQLite + FTS5. Knows nothing about MCP."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import sqlite3
|
|
7
|
+
from datetime import datetime, timezone
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _default_db_path() -> Path:
|
|
12
|
+
"""User-writable DB path. Override with the KB_DB_PATH env var.
|
|
13
|
+
|
|
14
|
+
Never stored inside the installed package (site-packages may be read-only).
|
|
15
|
+
"""
|
|
16
|
+
override = os.environ.get("KB_DB_PATH")
|
|
17
|
+
if override:
|
|
18
|
+
return Path(override)
|
|
19
|
+
if os.name == "nt":
|
|
20
|
+
base = Path(os.environ.get("LOCALAPPDATA", Path.home() / "AppData" / "Local"))
|
|
21
|
+
else:
|
|
22
|
+
base = Path(os.environ.get("XDG_DATA_HOME", Path.home() / ".local" / "share"))
|
|
23
|
+
data_dir = base / "sqlite-kb-mcp"
|
|
24
|
+
data_dir.mkdir(parents=True, exist_ok=True)
|
|
25
|
+
return data_dir / "knowledge.db"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
DB_PATH = _default_db_path()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _now() -> str:
|
|
32
|
+
"""Current UTC time as ISO-8601 text."""
|
|
33
|
+
return datetime.now(timezone.utc).isoformat()
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def get_connection(db_path: Path | str = DB_PATH) -> sqlite3.Connection:
|
|
37
|
+
"""Open a connection with rows accessible by column name."""
|
|
38
|
+
conn = sqlite3.connect(db_path)
|
|
39
|
+
conn.row_factory = sqlite3.Row # rows behave like dicts
|
|
40
|
+
conn.execute("PRAGMA foreign_keys = ON")
|
|
41
|
+
return conn
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def init_db(conn: sqlite3.Connection) -> None:
|
|
45
|
+
"""Create the documents table and its FTS5 search index."""
|
|
46
|
+
conn.executescript(
|
|
47
|
+
"""
|
|
48
|
+
CREATE TABLE IF NOT EXISTS documents (
|
|
49
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
50
|
+
title TEXT NOT NULL,
|
|
51
|
+
content TEXT NOT NULL,
|
|
52
|
+
tags TEXT NOT NULL DEFAULT '',
|
|
53
|
+
created_at TEXT NOT NULL,
|
|
54
|
+
updated_at TEXT NOT NULL
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
-- FTS5 index mirrors the searchable columns of `documents`.
|
|
58
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS documents_fts USING fts5(
|
|
59
|
+
title, content, tags,
|
|
60
|
+
content='documents',
|
|
61
|
+
content_rowid='id'
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
-- Triggers keep the FTS index in sync with the table.
|
|
65
|
+
CREATE TRIGGER IF NOT EXISTS documents_ai AFTER INSERT ON documents BEGIN
|
|
66
|
+
INSERT INTO documents_fts(rowid, title, content, tags)
|
|
67
|
+
VALUES (new.id, new.title, new.content, new.tags);
|
|
68
|
+
END;
|
|
69
|
+
|
|
70
|
+
CREATE TRIGGER IF NOT EXISTS documents_ad AFTER DELETE ON documents BEGIN
|
|
71
|
+
INSERT INTO documents_fts(documents_fts, rowid, title, content, tags)
|
|
72
|
+
VALUES ('delete', old.id, old.title, old.content, old.tags);
|
|
73
|
+
END;
|
|
74
|
+
|
|
75
|
+
CREATE TRIGGER IF NOT EXISTS documents_au AFTER UPDATE ON documents BEGIN
|
|
76
|
+
INSERT INTO documents_fts(documents_fts, rowid, title, content, tags)
|
|
77
|
+
VALUES ('delete', old.id, old.title, old.content, old.tags);
|
|
78
|
+
INSERT INTO documents_fts(rowid, title, content, tags)
|
|
79
|
+
VALUES (new.id, new.title, new.content, new.tags);
|
|
80
|
+
END;
|
|
81
|
+
"""
|
|
82
|
+
)
|
|
83
|
+
conn.commit()
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def add_document(
|
|
87
|
+
conn: sqlite3.Connection, title: str, content: str, tags: str = ""
|
|
88
|
+
) -> int:
|
|
89
|
+
"""Insert a document and return its new id."""
|
|
90
|
+
now = _now()
|
|
91
|
+
cur = conn.execute(
|
|
92
|
+
"INSERT INTO documents (title, content, tags, created_at, updated_at) "
|
|
93
|
+
"VALUES (?, ?, ?, ?, ?)",
|
|
94
|
+
(title, content, tags, now, now),
|
|
95
|
+
)
|
|
96
|
+
conn.commit()
|
|
97
|
+
return int(cur.lastrowid)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def get_document(conn: sqlite3.Connection, doc_id: int) -> dict | None:
|
|
101
|
+
"""Fetch one document by id, or None if missing."""
|
|
102
|
+
row = conn.execute(
|
|
103
|
+
"SELECT * FROM documents WHERE id = ?", (doc_id,)
|
|
104
|
+
).fetchone()
|
|
105
|
+
return dict(row) if row else None
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def list_documents(conn: sqlite3.Connection) -> list[dict]:
|
|
109
|
+
"""Return all documents, newest first."""
|
|
110
|
+
rows = conn.execute(
|
|
111
|
+
"SELECT * FROM documents ORDER BY created_at DESC, id DESC"
|
|
112
|
+
).fetchall()
|
|
113
|
+
return [dict(r) for r in rows]
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def search_documents(
|
|
117
|
+
conn: sqlite3.Connection, query: str, limit: int = 10
|
|
118
|
+
) -> list[dict]:
|
|
119
|
+
"""Full-text search, ranked by relevance (BM25)."""
|
|
120
|
+
rows = conn.execute(
|
|
121
|
+
"""
|
|
122
|
+
SELECT d.*, bm25(documents_fts) AS score
|
|
123
|
+
FROM documents_fts
|
|
124
|
+
JOIN documents d ON d.id = documents_fts.rowid
|
|
125
|
+
WHERE documents_fts MATCH ?
|
|
126
|
+
ORDER BY score
|
|
127
|
+
LIMIT ?
|
|
128
|
+
""",
|
|
129
|
+
(query, limit),
|
|
130
|
+
).fetchall()
|
|
131
|
+
return [dict(r) for r in rows]
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def update_document(
|
|
135
|
+
conn: sqlite3.Connection,
|
|
136
|
+
doc_id: int,
|
|
137
|
+
title: str | None = None,
|
|
138
|
+
content: str | None = None,
|
|
139
|
+
tags: str | None = None,
|
|
140
|
+
) -> bool:
|
|
141
|
+
"""Update provided fields of a document. Returns True if it existed."""
|
|
142
|
+
existing = get_document(conn, doc_id)
|
|
143
|
+
if existing is None:
|
|
144
|
+
return False
|
|
145
|
+
conn.execute(
|
|
146
|
+
"UPDATE documents SET title = ?, content = ?, tags = ?, updated_at = ? "
|
|
147
|
+
"WHERE id = ?",
|
|
148
|
+
(
|
|
149
|
+
title if title is not None else existing["title"],
|
|
150
|
+
content if content is not None else existing["content"],
|
|
151
|
+
tags if tags is not None else existing["tags"],
|
|
152
|
+
_now(),
|
|
153
|
+
doc_id,
|
|
154
|
+
),
|
|
155
|
+
)
|
|
156
|
+
conn.commit()
|
|
157
|
+
return True
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def delete_document(conn: sqlite3.Connection, doc_id: int) -> bool:
|
|
161
|
+
"""Delete a document. Returns True if a row was removed."""
|
|
162
|
+
cur = conn.execute("DELETE FROM documents WHERE id = ?", (doc_id,))
|
|
163
|
+
conn.commit()
|
|
164
|
+
return cur.rowcount > 0
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"""MCP server: thin tool layer over the db.py knowledge base."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import Annotated, Optional
|
|
5
|
+
|
|
6
|
+
from mcp.server.fastmcp import FastMCP
|
|
7
|
+
from pydantic import Field
|
|
8
|
+
|
|
9
|
+
from mcp_knowledge_base import db
|
|
10
|
+
|
|
11
|
+
mcp = FastMCP("knowledge-base")
|
|
12
|
+
|
|
13
|
+
# create tables once on load (idempotent)
|
|
14
|
+
with db.get_connection() as _conn:
|
|
15
|
+
db.init_db(_conn)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@mcp.tool()
|
|
19
|
+
def add_document(
|
|
20
|
+
title: Annotated[str, Field(min_length=1, description="Document title")],
|
|
21
|
+
content: Annotated[str, Field(min_length=1, description="Document body")],
|
|
22
|
+
tags: Annotated[str, Field(description="Comma-separated tags")] = "",
|
|
23
|
+
) -> dict:
|
|
24
|
+
"""Add a document to the knowledge base. Returns the new document."""
|
|
25
|
+
with db.get_connection() as conn:
|
|
26
|
+
doc_id = db.add_document(conn, title, content, tags)
|
|
27
|
+
return db.get_document(conn, doc_id)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@mcp.tool()
|
|
31
|
+
def search_documents(
|
|
32
|
+
query: Annotated[str, Field(min_length=1, description="Full-text search query")],
|
|
33
|
+
limit: Annotated[int, Field(ge=1, le=50, description="Max results")] = 10,
|
|
34
|
+
) -> list[dict] | str:
|
|
35
|
+
"""Full-text search the knowledge base, ranked by relevance."""
|
|
36
|
+
with db.get_connection() as conn:
|
|
37
|
+
results = db.search_documents(conn, query, limit)
|
|
38
|
+
if not results:
|
|
39
|
+
return f"No documents matched '{query}'."
|
|
40
|
+
return results
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@mcp.tool()
|
|
44
|
+
def get_document(
|
|
45
|
+
doc_id: Annotated[int, Field(ge=1, description="Document id")],
|
|
46
|
+
) -> dict | str:
|
|
47
|
+
"""Fetch a single document by its id."""
|
|
48
|
+
with db.get_connection() as conn:
|
|
49
|
+
doc = db.get_document(conn, doc_id)
|
|
50
|
+
return doc if doc else f"No document found with id {doc_id}."
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@mcp.tool()
|
|
54
|
+
def list_documents() -> list[dict] | str:
|
|
55
|
+
"""List all documents, newest first."""
|
|
56
|
+
with db.get_connection() as conn:
|
|
57
|
+
docs = db.list_documents(conn)
|
|
58
|
+
return docs if docs else "The knowledge base is empty."
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@mcp.tool()
|
|
62
|
+
def update_document(
|
|
63
|
+
doc_id: Annotated[int, Field(ge=1, description="Document id to update")],
|
|
64
|
+
title: Annotated[str | None, Field(min_length=1)] = None,
|
|
65
|
+
content: Annotated[str | None, Field(min_length=1)] = None,
|
|
66
|
+
tags: str | None = None,
|
|
67
|
+
) -> dict | str:
|
|
68
|
+
"""Update a document's fields. Only provided fields change."""
|
|
69
|
+
with db.get_connection() as conn:
|
|
70
|
+
ok = db.update_document(conn, doc_id, title, content, tags)
|
|
71
|
+
if not ok:
|
|
72
|
+
return f"No document found with id {doc_id}."
|
|
73
|
+
return db.get_document(conn, doc_id)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@mcp.tool()
|
|
77
|
+
def delete_document(
|
|
78
|
+
doc_id: Annotated[int, Field(ge=1, description="Document id to delete")],
|
|
79
|
+
) -> str:
|
|
80
|
+
"""Delete a document by its id."""
|
|
81
|
+
with db.get_connection() as conn:
|
|
82
|
+
ok = db.delete_document(conn, doc_id)
|
|
83
|
+
return f"Deleted document {doc_id}." if ok else f"No document found with id {doc_id}."
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
# --- Resources: read-only data addressed by URI (app/user-controlled) ---
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@mcp.resource("kb://documents")
|
|
90
|
+
def all_documents() -> str:
|
|
91
|
+
"""All documents in the knowledge base, as JSON."""
|
|
92
|
+
with db.get_connection() as conn:
|
|
93
|
+
docs = db.list_documents(conn)
|
|
94
|
+
return json.dumps(docs, indent=2)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@mcp.resource("kb://documents/{doc_id}")
|
|
98
|
+
def one_document(doc_id: str) -> str:
|
|
99
|
+
"""A single document by id, as JSON. (URI params arrive as strings.)"""
|
|
100
|
+
with db.get_connection() as conn:
|
|
101
|
+
doc = db.get_document(conn, int(doc_id))
|
|
102
|
+
if doc is None:
|
|
103
|
+
return json.dumps({"error": f"No document found with id {doc_id}"})
|
|
104
|
+
return json.dumps(doc, indent=2)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
# --- Prompts: reusable instruction templates (user-controlled) ---
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
@mcp.prompt()
|
|
111
|
+
def summarize_search(
|
|
112
|
+
query: Annotated[str, Field(min_length=1, description="Topic to search for")],
|
|
113
|
+
limit: Annotated[int, Field(ge=1, le=20, description="How many results")] = 5,
|
|
114
|
+
) -> str:
|
|
115
|
+
"""Search the KB and ask the model to summarize the results (data embedded)."""
|
|
116
|
+
with db.get_connection() as conn:
|
|
117
|
+
results = db.search_documents(conn, query, limit)
|
|
118
|
+
if not results:
|
|
119
|
+
return f"No documents matched '{query}'. Tell the user nothing was found."
|
|
120
|
+
body = json.dumps(results, indent=2)
|
|
121
|
+
return (
|
|
122
|
+
f"Summarize these knowledge base results for '{query}'. "
|
|
123
|
+
f"Group related points and note any gaps.\n\n{body}"
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@mcp.prompt()
|
|
128
|
+
def find_related(
|
|
129
|
+
topic: Annotated[str, Field(min_length=1, description="Subject to explore")],
|
|
130
|
+
) -> str:
|
|
131
|
+
"""Instruct the model to find related documents using the search tool."""
|
|
132
|
+
return (
|
|
133
|
+
f"Use the search_documents tool to find documents related to '{topic}'. "
|
|
134
|
+
"Then explain how they connect and suggest what to read first."
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def main() -> None:
|
|
139
|
+
"""Console entry point. Runs the server over stdio."""
|
|
140
|
+
mcp.run()
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
if __name__ == "__main__":
|
|
144
|
+
main()
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sqlite-kb-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A Model Context Protocol server for a SQLite-backed knowledge base with full-text search.
|
|
5
|
+
Keywords: mcp,model-context-protocol,sqlite,fts5,knowledge-base,llm
|
|
6
|
+
Author: Hammad
|
|
7
|
+
Author-email: Hammad <mhd-Hammad@users.noreply.github.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
16
|
+
Requires-Dist: mcp[cli]>=1.0,<2
|
|
17
|
+
Requires-Python: >=3.10
|
|
18
|
+
Project-URL: Homepage, https://github.com/mhd-Hammad/mcp-knowledge-base
|
|
19
|
+
Project-URL: Repository, https://github.com/mhd-Hammad/mcp-knowledge-base
|
|
20
|
+
Project-URL: Issues, https://github.com/mhd-Hammad/mcp-knowledge-base/issues
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
# mcp-knowledge-base
|
|
24
|
+
|
|
25
|
+
A [Model Context Protocol](https://modelcontextprotocol.io) (MCP) server that gives an
|
|
26
|
+
AI assistant a searchable, persistent knowledge base. Documents are stored in SQLite and
|
|
27
|
+
searched with FTS5 full-text search ranked by relevance.
|
|
28
|
+
|
|
29
|
+
It is a **self-contained** MCP server: it owns its own data and logic rather than wrapping
|
|
30
|
+
an external API. The SQLite database *is* the application; MCP is the interface an AI host
|
|
31
|
+
(Kiro, Claude Desktop, any MCP client) talks to it through.
|
|
32
|
+
|
|
33
|
+
## Features
|
|
34
|
+
|
|
35
|
+
- **Tools** — add, search, get, list, update, and delete documents.
|
|
36
|
+
- **Resources** — read documents by URI (`kb://documents`, `kb://documents/{id}`).
|
|
37
|
+
- **Prompts** — reusable templates (`summarize_search`, `find_related`).
|
|
38
|
+
- **Full-text search** — SQLite FTS5 with BM25 relevance ranking, not naive `LIKE`.
|
|
39
|
+
- **Input validation** — Pydantic field constraints surfaced in the generated tool schemas.
|
|
40
|
+
|
|
41
|
+
## Installation
|
|
42
|
+
|
|
43
|
+
Once published, add the server to your MCP host's config. No clone, no paths:
|
|
44
|
+
|
|
45
|
+
```json
|
|
46
|
+
{
|
|
47
|
+
"mcpServers": {
|
|
48
|
+
"knowledge-base": {
|
|
49
|
+
"command": "uvx",
|
|
50
|
+
"args": ["sqlite-kb-mcp"]
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
- **Kiro** — put this in `.kiro/settings/mcp.json` (workspace) or `~/.kiro/settings/mcp.json` (user).
|
|
57
|
+
- **Claude Desktop** — put this in `claude_desktop_config.json`.
|
|
58
|
+
|
|
59
|
+
`uvx` downloads and runs the published package on demand, so users never touch the source.
|
|
60
|
+
|
|
61
|
+
> `autoApprove` is intentionally omitted here: auto-approving tool calls is a personal trust
|
|
62
|
+
> decision. Add it in your own config if you want read-only tools to run without a prompt.
|
|
63
|
+
|
|
64
|
+
## What it exposes
|
|
65
|
+
|
|
66
|
+
### Tools
|
|
67
|
+
|
|
68
|
+
| Tool | Description |
|
|
69
|
+
|------|-------------|
|
|
70
|
+
| `add_document(title, content, tags="")` | Add a document. |
|
|
71
|
+
| `search_documents(query, limit=10)` | Full-text search, ranked by relevance. |
|
|
72
|
+
| `get_document(doc_id)` | Fetch one document by id. |
|
|
73
|
+
| `list_documents()` | List all documents, newest first. |
|
|
74
|
+
| `update_document(doc_id, ...)` | Update provided fields. |
|
|
75
|
+
| `delete_document(doc_id)` | Delete a document. |
|
|
76
|
+
|
|
77
|
+
### Resources
|
|
78
|
+
|
|
79
|
+
| URI | Description |
|
|
80
|
+
|-----|-------------|
|
|
81
|
+
| `kb://documents` | All documents, as JSON. |
|
|
82
|
+
| `kb://documents/{doc_id}` | One document, as JSON. |
|
|
83
|
+
|
|
84
|
+
### Prompts
|
|
85
|
+
|
|
86
|
+
| Prompt | Description |
|
|
87
|
+
|--------|-------------|
|
|
88
|
+
| `summarize_search(query, limit=5)` | Searches the KB and asks the model to summarize (data embedded). |
|
|
89
|
+
| `find_related(topic)` | Instructs the model to find related documents using the search tool. |
|
|
90
|
+
|
|
91
|
+
## Architecture
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
MCP host ──stdio──► server.py ──► db.py ──► knowledge.db
|
|
95
|
+
(Kiro/Claude) (MCP layer) (logic) (SQLite + FTS5)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
- **`server.py`** — the MCP interface. Each tool/resource/prompt is a thin wrapper that
|
|
99
|
+
validates input and delegates to `db.py`. No business logic lives here.
|
|
100
|
+
- **`db.py`** — pure data-access logic. Knows nothing about MCP, so it is independently
|
|
101
|
+
testable. Opens a connection per call to avoid SQLite threading issues.
|
|
102
|
+
- **SQLite + FTS5** — a `documents` table plus a `documents_fts` external-content virtual
|
|
103
|
+
table. Triggers keep the search index in sync automatically, so CRUD touches only the
|
|
104
|
+
main table.
|
|
105
|
+
|
|
106
|
+
This separation of concerns means the whole data layer is tested without any MCP machinery.
|
|
107
|
+
|
|
108
|
+
## Development
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
# clone, then:
|
|
112
|
+
uv sync # install deps (incl. dev group)
|
|
113
|
+
uv run pytest # run the test suite
|
|
114
|
+
uv run mcp dev src/mcp_knowledge_base/server.py # open the MCP Inspector
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
To run the server directly over stdio:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
uv run sqlite-kb-mcp
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Notes and future work
|
|
124
|
+
|
|
125
|
+
- **Transport** — currently stdio (local subprocess). Streamable HTTP would make it
|
|
126
|
+
deployable as a remote service; FastMCP supports it with a small change.
|
|
127
|
+
- **Search** — FTS5 matches whole tokens (`index` != `indexes`). Prefix queries (`index*`)
|
|
128
|
+
work today; a Porter stemmer could be enabled for automatic stem matching.
|
|
129
|
+
- **Not implemented (by design)** — client-side primitives (sampling, elicitation, roots)
|
|
130
|
+
and OAuth authorization, which are relevant mainly to remote/HTTP deployments.
|
|
131
|
+
|
|
132
|
+
## License
|
|
133
|
+
|
|
134
|
+
MIT
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
mcp_knowledge_base/__init__.py,sha256=P9YXMrSkRUr76uLBoZZhV1MM7o-QdZOFF9togKRu-0s,116
|
|
2
|
+
mcp_knowledge_base/db.py,sha256=Sl9_cm-UUt0sS_xO_USvUKCg-rXzDfQj9LfR1M7NnNQ,5327
|
|
3
|
+
mcp_knowledge_base/server.py,sha256=kva3CFmTvlUIlqkkuajrIn2Gj6BzfZ1864J97bNF-ak,4738
|
|
4
|
+
sqlite_kb_mcp-0.1.0.dist-info/WHEEL,sha256=si2p8vW03RnhyBnhWl4tj-ObJa78SpE-c9HsEtblGR0,80
|
|
5
|
+
sqlite_kb_mcp-0.1.0.dist-info/entry_points.txt,sha256=r9o1fIoHPCsVgNF3SE7rC1dQeV5bT-PvR11o70y32Gk,66
|
|
6
|
+
sqlite_kb_mcp-0.1.0.dist-info/METADATA,sha256=_2xW5C_crYqZxt4WU-FijjwJ0rec9dsziqdsFwlubsM,5201
|
|
7
|
+
sqlite_kb_mcp-0.1.0.dist-info/RECORD,,
|