mcp-kb 0.1.0__py3-none-any.whl → 0.2.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_kb/cli/main.py CHANGED
@@ -8,7 +8,7 @@ import os
8
8
  from pathlib import Path
9
9
  from typing import Iterable, List, Optional
10
10
 
11
- from mcp_kb.config import DOCS_FOLDER_NAME, resolve_knowledge_base_root
11
+ from mcp_kb.config import DATA_FOLDER_NAME, resolve_knowledge_base_root
12
12
  from mcp_kb.cli.args import add_chroma_arguments, build_chroma_listener, parse_bool
13
13
  from mcp_kb.ingest.chroma import ChromaIngestor
14
14
  from mcp_kb.knowledge.bootstrap import install_default_documentation
@@ -79,7 +79,7 @@ def run_server(arguments: Iterable[str] | None = None) -> None:
79
79
  parser = _build_argument_parser()
80
80
  options = parser.parse_args(arguments)
81
81
  root_path = resolve_knowledge_base_root(options.root)
82
- rules = PathRules(root=root_path, protected_folders=(DOCS_FOLDER_NAME,))
82
+ rules = PathRules(root=root_path, protected_folders=(DATA_FOLDER_NAME,))
83
83
  install_default_documentation(root_path)
84
84
  listeners: List[ChromaIngestor] = []
85
85
  try:
mcp_kb/cli/reindex.py CHANGED
@@ -12,7 +12,7 @@ import logging
12
12
  from typing import Iterable, List
13
13
 
14
14
  from mcp_kb.cli.args import add_chroma_arguments, build_chroma_listener
15
- from mcp_kb.config import DOCS_FOLDER_NAME, resolve_knowledge_base_root
15
+ from mcp_kb.config import DATA_FOLDER_NAME, resolve_knowledge_base_root
16
16
  from mcp_kb.knowledge.events import KnowledgeBaseReindexListener
17
17
  from mcp_kb.knowledge.store import KnowledgeBase
18
18
  from mcp_kb.security.path_validation import PathRules
@@ -58,7 +58,7 @@ def run_reindex(arguments: Iterable[str] | None = None) -> int:
58
58
  parser = _build_argument_parser()
59
59
  options = parser.parse_args(arguments)
60
60
  root_path = resolve_knowledge_base_root(options.root)
61
- rules = PathRules(root=root_path, protected_folders=(DOCS_FOLDER_NAME,))
61
+ rules = PathRules(root=root_path, protected_folders=(DATA_FOLDER_NAME,))
62
62
  kb = KnowledgeBase(rules)
63
63
 
64
64
  listeners: List[KnowledgeBaseReindexListener] = []
@@ -71,6 +71,7 @@ def run_reindex(arguments: Iterable[str] | None = None) -> int:
71
71
 
72
72
  total = 0
73
73
  for listener in listeners:
74
+ logger.info("Reindexing via %s", listener.__class__.__name__)
74
75
  count = listener.reindex(kb)
75
76
  logger.info("Reindexed %d documents via %s", count, listener.__class__.__name__)
76
77
  total += count
mcp_kb/config.py CHANGED
@@ -16,7 +16,7 @@ import os
16
16
  DEFAULT_KNOWLEDGE_BASE_DIR = ".knowledgebase"
17
17
  """str: Default relative directory for persisting knowledge base documents."""
18
18
 
19
- DOCS_FOLDER_NAME = ".docs"
19
+ DATA_FOLDER_NAME = ".data"
20
20
  """str: Name of the documentation folder inside the knowledge base tree."""
21
21
 
22
22
  DOC_FILENAME = "KNOWLEDBASE_DOC.md"
@@ -8,7 +8,7 @@ operational practices.
8
8
  ## Structure
9
9
 
10
10
  - All knowledge content lives beneath the `.knowledgebase/` root.
11
- - Documentation resides under `.docs/` and is read-only from the MCP tools.
11
+ - Documentation and other non knowledge resides under `.data/` and is read-only from the MCP tools.
12
12
  - Soft-deleted files are suffixed with `_DELETE_` and ignored by search/overview.
13
13
 
14
14
  ## Recommended Practices
mcp_kb/ingest/chroma.py CHANGED
@@ -6,7 +6,9 @@ from dataclasses import dataclass
6
6
  from pathlib import Path
7
7
  from typing import Any, Dict, List, Mapping, Optional, Set, Tuple, Type, TYPE_CHECKING
8
8
  from langchain_text_splitters import TokenTextSplitter
9
+ from tqdm import tqdm
9
10
 
11
+ from mcp_kb.config import DATA_FOLDER_NAME
10
12
  from mcp_kb.knowledge.events import (
11
13
  FileDeleteEvent,
12
14
  FileUpsertEvent,
@@ -104,7 +106,7 @@ class ChromaConfiguration:
104
106
  if data_directory:
105
107
  resolved_directory = Path(data_directory).expanduser().resolve()
106
108
  elif normalized_type == "persistent":
107
- resolved_directory = (root / "chroma").resolve()
109
+ resolved_directory = (root/DATA_FOLDER_NAME / "chroma").resolve()
108
110
  else:
109
111
  resolved_directory = None
110
112
 
@@ -419,6 +421,7 @@ class ChromaIngestor(KnowledgeBaseListener, KnowledgeBaseReindexListener):
419
421
  d.metadata['chunk_number'] = i
420
422
  d.metadata['startline'] = len(content[:d.metadata['start_index']].splitlines())
421
423
  d.metadata['endline'] = d.metadata['startline'] + len(d.page_content.splitlines())-1
424
+
422
425
 
423
426
  self._collection.add(
424
427
  documents=[d.page_content for d in split_docs],
@@ -451,19 +454,21 @@ class ChromaIngestor(KnowledgeBaseListener, KnowledgeBaseReindexListener):
451
454
 
452
455
  count = 0
453
456
  root = kb.rules.root
454
- for path in kb.iter_active_files(include_docs=False):
455
- try:
456
- content = path.read_text(encoding="utf-8")
457
- except FileNotFoundError: # pragma: no cover - race with external edits
458
- continue
459
-
460
- relative = str(path.relative_to(root))
461
- document_id = f"{self.configuration.id_prefix}{relative}"
462
- metadata = {
463
- "relative_path": relative,
464
- }
465
- self._reindex_document(document_id, content, metadata)
466
- count += 1
457
+ with tqdm(kb.iter_active_files(include_docs=False), desc="Reindexing Chroma",total=kb.total_active_files(include_docs=False)) as pbar:
458
+ for path in pbar:
459
+ pbar.set_description(f"Reindexing Chroma {path.name}")
460
+ try:
461
+ content = path.read_text(encoding="utf-8")
462
+ except FileNotFoundError: # pragma: no cover - race with external edits
463
+ continue
464
+
465
+ relative = str(path.relative_to(root))
466
+ document_id = f"{self.configuration.id_prefix}{relative}"
467
+ metadata = {
468
+ "relative_path": relative,
469
+ }
470
+ self._reindex_document(document_id, content, metadata)
471
+ count += 1
467
472
 
468
473
  return count
469
474
 
@@ -4,7 +4,7 @@ from __future__ import annotations
4
4
  import importlib.resources as resources
5
5
  from pathlib import Path
6
6
 
7
- from mcp_kb.config import DOCS_FOLDER_NAME, DOC_FILENAME
7
+ from mcp_kb.config import DATA_FOLDER_NAME, DOC_FILENAME
8
8
 
9
9
 
10
10
  def install_default_documentation(root: Path) -> Path:
@@ -26,7 +26,7 @@ def install_default_documentation(root: Path) -> Path:
26
26
  Path to the documentation file inside the knowledge base tree.
27
27
  """
28
28
 
29
- docs_dir = root / DOCS_FOLDER_NAME
29
+ docs_dir = root / DATA_FOLDER_NAME
30
30
  doc_path = docs_dir / DOC_FILENAME
31
31
  if doc_path.exists():
32
32
  return doc_path
@@ -11,7 +11,7 @@ from dataclasses import dataclass
11
11
  from pathlib import Path
12
12
  from typing import Dict, Iterable, List, Optional
13
13
 
14
- from mcp_kb.config import DOCS_FOLDER_NAME, DOC_FILENAME
14
+ from mcp_kb.config import DATA_FOLDER_NAME, DOC_FILENAME
15
15
  from mcp_kb.knowledge.events import KnowledgeBaseSearchListener
16
16
  from mcp_kb.knowledge.store import KnowledgeBase
17
17
 
@@ -141,7 +141,7 @@ def read_documentation(kb: KnowledgeBase) -> str:
141
141
  folder.
142
142
  """
143
143
 
144
- doc_path = kb.rules.root / DOCS_FOLDER_NAME / DOC_FILENAME
144
+ doc_path = kb.rules.root / DATA_FOLDER_NAME / DOC_FILENAME
145
145
  if not doc_path.exists():
146
146
  return ""
147
147
  return doc_path.read_text(encoding="utf-8")
mcp_kb/knowledge/store.py CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  This module exposes the ``KnowledgeBase`` class, which orchestrates validated
4
4
  filesystem operations for the MCP server. The class encapsulates logic for
5
- creating, reading, appending, and modifying markdown files while respecting the
5
+ creating, reading, appending, and modifying text files while respecting the
6
6
  security constraints defined in the PRD. Each method returns plain Python data
7
7
  structures so that higher-level layers (e.g., JSON-RPC handlers) can focus on
8
8
  protocol serialization rather than filesystem minutiae.
@@ -14,7 +14,7 @@ from dataclasses import dataclass
14
14
  from pathlib import Path
15
15
  from typing import Iterable, Optional
16
16
 
17
- from mcp_kb.config import DELETE_SENTINEL, DOCS_FOLDER_NAME
17
+ from mcp_kb.config import DELETE_SENTINEL, DATA_FOLDER_NAME
18
18
  from mcp_kb.knowledge.events import FileDeleteEvent, FileUpsertEvent, KnowledgeBaseListener
19
19
  from mcp_kb.security.path_validation import (
20
20
  PathRules,
@@ -79,7 +79,7 @@ class KnowledgeBase:
79
79
  self.listeners = tuple(listeners or ())
80
80
 
81
81
  def create_file(self, relative_path: str, content: str) -> Path:
82
- """Create or overwrite a markdown file at ``relative_path``.
82
+ """Create or overwrite a text file at ``relative_path``.
83
83
 
84
84
  The method validates the path, ensures that the parent directory exists,
85
85
  and writes the provided content as UTF-8 text. Existing files are
@@ -185,9 +185,14 @@ class KnowledgeBase:
185
185
  original_relative = self._relative_path(normalized)
186
186
  self._notify_delete(target, original_relative)
187
187
  return target
188
+
189
+ def total_active_files(self, include_docs: bool = False) -> int:
190
+ """Return the total number of non-deleted UTF-8 text files under the root directory.
191
+ """
192
+ return sum(1 for _ in self.iter_active_files(include_docs=include_docs))
188
193
 
189
194
  def iter_active_files(self, include_docs: bool = False) -> Iterable[Path]:
190
- """Yield non-deleted markdown files under the root directory.
195
+ """Yield non-deleted UTF-8 text files under the root directory.
191
196
 
192
197
  Parameters
193
198
  ----------
@@ -197,13 +202,18 @@ class KnowledgeBase:
197
202
  the search and overview requirements from the PRD.
198
203
  """
199
204
 
200
- for path in self.rules.root.rglob("*.md"):
205
+ from mcp_kb.utils.filesystem import is_text_file
206
+
207
+ for path in self.rules.root.rglob("*"):
208
+ if not path.is_file():
209
+ continue
201
210
  if DELETE_SENTINEL in path.name:
202
211
  continue
203
212
  parts = path.relative_to(self.rules.root).parts
204
- if parts and parts[0] == DOCS_FOLDER_NAME and not include_docs:
213
+ if parts and parts[0] == DATA_FOLDER_NAME and not include_docs:
205
214
  continue
206
- yield path
215
+ if is_text_file(path):
216
+ yield path
207
217
 
208
218
  def _relative_path(self, absolute: Path) -> str:
209
219
  """Return ``absolute`` rewritten relative to the knowledge base root."""
@@ -218,7 +228,7 @@ class KnowledgeBase:
218
228
  absolute:
219
229
  Fully resolved path that was modified on disk.
220
230
  content:
221
- Markdown payload that should be provided to subscribers.
231
+ Text payload that should be provided to subscribers.
222
232
  """
223
233
 
224
234
  if not self.listeners:
@@ -13,7 +13,7 @@ from dataclasses import dataclass
13
13
  from pathlib import Path
14
14
  from typing import Iterable
15
15
 
16
- from mcp_kb.config import DOCS_FOLDER_NAME, DELETE_SENTINEL
16
+ from mcp_kb.config import DATA_FOLDER_NAME, DELETE_SENTINEL
17
17
 
18
18
 
19
19
  class PathValidationError(ValueError):
mcp_kb/server/app.py CHANGED
@@ -89,7 +89,7 @@ def create_fastmcp_app(
89
89
  mcp = FastMCP(
90
90
  "mcp-knowledge-base",
91
91
  instructions=(
92
- "You are connected to a local markdown knowledge base. Use the provided "
92
+ "You are connected to a local text-based knowledge base. Use the provided "
93
93
  "tools to create, inspect, and organize content while respecting the "
94
94
  "soft deletion semantics and the protected documentation folder."
95
95
  ),
@@ -98,7 +98,7 @@ def create_fastmcp_app(
98
98
 
99
99
  @mcp.tool(name="create_file", title="Create File")
100
100
  def create_file(path: str, content: str) -> str:
101
- """Create or overwrite a markdown file at ``path`` with ``content``."""
101
+ """Create or overwrite a text file at ``path`` with ``content``."""
102
102
 
103
103
  try:
104
104
  created = kb.create_file(path, content)
@@ -108,7 +108,7 @@ def create_fastmcp_app(
108
108
 
109
109
  @mcp.tool(name="read_file", title="Read File", structured_output=True)
110
110
  def read_file(path: str, start_line: int | None = None, end_line: int | None = None) -> ReadFileResult:
111
- """Read a markdown file returning metadata about the extracted segment."""
111
+ """Read a text file returning metadata about the extracted segment."""
112
112
 
113
113
  try:
114
114
  segment: FileSegment = kb.read_file(path, start_line=start_line, end_line=end_line)
@@ -81,3 +81,47 @@ def rename(path: Path, target: Path) -> None:
81
81
  """Rename ``path`` to ``target`` using ``Path.rename`` semantics."""
82
82
 
83
83
  path.rename(target)
84
+
85
+
86
+ def is_text_file(path: Path, max_bytes: int = 2048) -> bool:
87
+ """Heuristically determine whether ``path`` contains UTF-8 text.
88
+
89
+ The check is designed to be fast and conservative for use when iterating
90
+ a directory tree. It reads at most ``max_bytes`` from the file in binary
91
+ mode and applies two filters:
92
+
93
+ - Reject files that contain NUL bytes, which are extremely uncommon in
94
+ textual formats and a strong indicator of binary content.
95
+ - Attempt to decode the sampled bytes as UTF-8. If decoding fails, the
96
+ file is treated as binary.
97
+
98
+ Parameters
99
+ ----------
100
+ path:
101
+ Absolute path to the file on disk.
102
+ max_bytes:
103
+ Upper bound on the number of bytes to sample from the head of the
104
+ file. A small sample keeps directory scans fast while remaining
105
+ accurate for typical text formats such as ``.md``, ``.txt``, ``.xml``,
106
+ and source files.
107
+
108
+ Returns
109
+ -------
110
+ bool
111
+ ``True`` if the file appears to be UTF-8 text; ``False`` otherwise.
112
+ """
113
+
114
+ try:
115
+ with path.open("rb") as handle:
116
+ sample = handle.read(max_bytes)
117
+ except (FileNotFoundError, PermissionError): # pragma: no cover - defensive
118
+ return False
119
+
120
+ if b"\x00" in sample:
121
+ return False
122
+
123
+ try:
124
+ sample.decode("utf-8")
125
+ return True
126
+ except UnicodeDecodeError:
127
+ return False
@@ -1,14 +1,14 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mcp-kb
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: MCP server exposing a local markdown knowledge base
5
5
  Author: LLM Maintainer
6
6
  Requires-Python: >=3.11
7
7
  Description-Content-Type: text/markdown
8
- Requires-Dist: chromadb>=1.1.0
9
8
  Requires-Dist: httpx>=0.28.1
10
9
  Requires-Dist: mcp[cli]>=1.15.0
11
10
  Provides-Extra: vector
11
+ Requires-Dist: chromadb>=1.1.0; extra == "vector"
12
12
  Requires-Dist: tiktoken>=0.11.0; extra == "vector"
13
13
  Requires-Dist: langchain-text-splitters>=0.3.11; extra == "vector"
14
14
 
@@ -36,7 +36,7 @@ uv run mcp-kb-server --transport http --host 0.0.0.0 --port 9000
36
36
  ```
37
37
 
38
38
  On first launch the server copies a bundled `KNOWLEDBASE_DOC.md` into the
39
- `.docs/` directory if it is missing so that every deployment starts with a
39
+ `.data/` directory if it is missing so that every deployment starts with a
40
40
  baseline usage guide.
41
41
 
42
42
  ## Optional ChromaDB Mirroring
@@ -0,0 +1,26 @@
1
+ mcp_kb/__init__.py,sha256=Ry7qODhfFQF6u6p2m3bwGWhB0-BdWTQcHDJB7NBYAio,74
2
+ mcp_kb/config.py,sha256=VallCc3_Bjcm2FPElthupvXdbMuXvDkiTkQKj0f4dkQ,2506
3
+ mcp_kb/cli/__init__.py,sha256=dEIRWFycAfPkha1S1Bj_Y6zkvEZv4eF0qtbF9t74r60,67
4
+ mcp_kb/cli/args.py,sha256=0yU5lwjjUkgk91ksocqOdpqO_u5JU6xuCaayiOJ-5pQ,5371
5
+ mcp_kb/cli/main.py,sha256=FMsnWcXmEsXXfETyvPMP2il9jREGFWmR8t23-6QfhMo,3864
6
+ mcp_kb/cli/reindex.py,sha256=UBBN7_u9rcgGXel5FKnnA3yd7a-AQMwHMwQjt7ZFrSs,3033
7
+ mcp_kb/data/KNOWLEDBASE_DOC.md,sha256=bkSpdK1W3F0KR6d3q4V_23fnY8Kw2IBjXPvTTRv06AI,1663
8
+ mcp_kb/data/__init__.py,sha256=UYYuO_n2ikjpwkPSykgleiifYvC0V8_O-atUaRBQUm4,70
9
+ mcp_kb/ingest/__init__.py,sha256=8obrvfa8nLNLYPbi1MHlFUqfoFHgK9YfdryPzAXQ6kU,77
10
+ mcp_kb/ingest/chroma.py,sha256=3Kt7or1Z9ng-wBeXeuOPhrVIfjokwkF25jKHdAeYSH8,22170
11
+ mcp_kb/knowledge/__init__.py,sha256=W_dtRbtnQlrDJ_425vWR8BcoZGJ8gC5-wg1De1E654s,76
12
+ mcp_kb/knowledge/bootstrap.py,sha256=WlbJUXhxglyWjlvwhUdT20oijLNLaZOePQ6nYwfBCxk,1202
13
+ mcp_kb/knowledge/events.py,sha256=A7CfD7U5bxo6mCCIic83yE7VPixAN05ssw_HKRF2zxw,3549
14
+ mcp_kb/knowledge/search.py,sha256=AKsyNipsA8bfRxIJb49tdsU4ICzbHeFrA1Ikvlk1u7w,5901
15
+ mcp_kb/knowledge/store.py,sha256=urZaLrSRjgqxb_hLC6RQjDeNw8Id14ripQH_6HdWb3o,10002
16
+ mcp_kb/security/__init__.py,sha256=lF8_XAjzpwhAFresuskXMo0u9v7KFiTJId88wqOAM4Y,62
17
+ mcp_kb/security/path_validation.py,sha256=21bfKdxjHY-ywDYw0DcGCeXDnvdXDILWVuueUsuuZUM,3617
18
+ mcp_kb/server/__init__.py,sha256=j9TmxW_WLCoibyQvCsDT1MIuUqSL8sRh2h4u0M4eU0c,74
19
+ mcp_kb/server/app.py,sha256=7s10UJWFopJ4CZPqZ4briTKsfjDiRo44ZbLAeWb6Lj8,7064
20
+ mcp_kb/utils/__init__.py,sha256=lKhRsjgnbhye1sSlch1_wsAI3eWKE1M6RVIiNlnsvLI,71
21
+ mcp_kb/utils/filesystem.py,sha256=0M-Waf2vfqhp8UL__2Emfpwpoqxshti3M7XLjXnpjJw,4026
22
+ mcp_kb-0.2.0.dist-info/METADATA,sha256=AjAH4xcztes0PzJkjW3J9qyes0VDXS7jfsJFQMq2s1g,5122
23
+ mcp_kb-0.2.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
24
+ mcp_kb-0.2.0.dist-info/entry_points.txt,sha256=qwJkR3vV7ZeydfS_IYMiDwLv4BdTkrOf4-5neWj25g0,96
25
+ mcp_kb-0.2.0.dist-info/top_level.txt,sha256=IBiz3TNE3FF3TwkbCZpC1kkk6ohTwtBQNSPJNV3-qGA,7
26
+ mcp_kb-0.2.0.dist-info/RECORD,,
@@ -1,26 +0,0 @@
1
- mcp_kb/__init__.py,sha256=Ry7qODhfFQF6u6p2m3bwGWhB0-BdWTQcHDJB7NBYAio,74
2
- mcp_kb/config.py,sha256=snbGzbeUf-YXah_F4h2IzHzL_wII966Pf3LZWLKZ-uU,2506
3
- mcp_kb/cli/__init__.py,sha256=dEIRWFycAfPkha1S1Bj_Y6zkvEZv4eF0qtbF9t74r60,67
4
- mcp_kb/cli/args.py,sha256=0yU5lwjjUkgk91ksocqOdpqO_u5JU6xuCaayiOJ-5pQ,5371
5
- mcp_kb/cli/main.py,sha256=07pxHS4FRfhNO4EXWe_9psF7PV6axrY92U7kpZZHkA0,3864
6
- mcp_kb/cli/reindex.py,sha256=WxGCTX35ngXO2AjCXSP4TAUJDGcbCzEOIVl6NIFwVu8,2963
7
- mcp_kb/data/KNOWLEDBASE_DOC.md,sha256=jrjNdkXNZUIOBZYy9X9ZnRcRWoARJwCKh2pmpiI0oNw,1639
8
- mcp_kb/data/__init__.py,sha256=UYYuO_n2ikjpwkPSykgleiifYvC0V8_O-atUaRBQUm4,70
9
- mcp_kb/ingest/__init__.py,sha256=8obrvfa8nLNLYPbi1MHlFUqfoFHgK9YfdryPzAXQ6kU,77
10
- mcp_kb/ingest/chroma.py,sha256=P3IrPZW3SK3HVayTzyR3KNe_e8aPGiLDfpZswaLmMbE,21849
11
- mcp_kb/knowledge/__init__.py,sha256=W_dtRbtnQlrDJ_425vWR8BcoZGJ8gC5-wg1De1E654s,76
12
- mcp_kb/knowledge/bootstrap.py,sha256=DitA1x7i4ZMb9IZyWyw1oPh78iHz9VkHe03sBF9042U,1202
13
- mcp_kb/knowledge/events.py,sha256=A7CfD7U5bxo6mCCIic83yE7VPixAN05ssw_HKRF2zxw,3549
14
- mcp_kb/knowledge/search.py,sha256=_hfN89GPoJn-Y1TEEfnYawkgOGxOn_3-f7-lzdAqbMA,5901
15
- mcp_kb/knowledge/store.py,sha256=JP8_vGMzm6HYxg2Y2LUB1fnguIWYokuO1yCRh2pLym4,9598
16
- mcp_kb/security/__init__.py,sha256=lF8_XAjzpwhAFresuskXMo0u9v7KFiTJId88wqOAM4Y,62
17
- mcp_kb/security/path_validation.py,sha256=lhyL1WUVCb4ypxmJx8drGmR7y_b7nNohz_JFRWJ25MQ,3617
18
- mcp_kb/server/__init__.py,sha256=j9TmxW_WLCoibyQvCsDT1MIuUqSL8sRh2h4u0M4eU0c,74
19
- mcp_kb/server/app.py,sha256=GnI2z_6C0wwbJd1r-GheQMn1nc4xj71A51_xKLyCUoU,7070
20
- mcp_kb/utils/__init__.py,sha256=lKhRsjgnbhye1sSlch1_wsAI3eWKE1M6RVIiNlnsvLI,71
21
- mcp_kb/utils/filesystem.py,sha256=Uyo_lHXF35qRxNXsyDkCs6NC0pDqwDt-D6dw0xtYZQE,2632
22
- mcp_kb-0.1.0.dist-info/METADATA,sha256=KCfsCIxMSzOzsE7HXtvSmb9d8ouK7ILbab4XNqZjP2Y,5103
23
- mcp_kb-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
24
- mcp_kb-0.1.0.dist-info/entry_points.txt,sha256=qwJkR3vV7ZeydfS_IYMiDwLv4BdTkrOf4-5neWj25g0,96
25
- mcp_kb-0.1.0.dist-info/top_level.txt,sha256=IBiz3TNE3FF3TwkbCZpC1kkk6ohTwtBQNSPJNV3-qGA,7
26
- mcp_kb-0.1.0.dist-info/RECORD,,
File without changes