timeverse-code-reader 0.1.1__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.
@@ -0,0 +1 @@
1
+ """timeverse_code_reader - TimeVerse 文件读取 MCP 工具."""
@@ -0,0 +1,105 @@
1
+ """
2
+ FileCache - 文件内容缓存
3
+
4
+ 功能简述:
5
+ 基于 mtime 校验 + LRU 淘汰策略的文件内容缓存。
6
+ 跨多次 tool_call 复用,避免重复读取同一文件。
7
+ 单文件超过 10MB 不缓存,单次对话典型命中 10-30 次。
8
+
9
+ 主要方法:
10
+ - get: 获取缓存内容,mtime 变更时自动失效
11
+ - set: 写入缓存,超过 MAX_FILE_SIZE 时跳过
12
+ - invalidate: 主动失效(预留)
13
+ - clear: 清空缓存
14
+ """
15
+
16
+ from collections import OrderedDict
17
+ from pathlib import Path
18
+
19
+
20
+ class FileCache:
21
+ """
22
+ 文件内容缓存 - mtime 校验 + LRU 淘汰
23
+
24
+ MAX_ENTRIES: 最多缓存 100 个文件(~30MB)
25
+ MAX_FILE_SIZE: 单文件超过 10MB 不缓存
26
+ """
27
+
28
+ MAX_ENTRIES = 100
29
+ MAX_FILE_SIZE = 10 * 1024 * 1024 # 10MB
30
+
31
+ def __init__(self):
32
+ self._cache: OrderedDict[str, tuple[float, str]] = OrderedDict()
33
+
34
+ def get(self, path: Path) -> str | None:
35
+ """
36
+ 获取缓存内容
37
+
38
+ 读取前检查文件的 mtime,如果文件已变更则自动失效。
39
+ 缓存命中时将条目移到队尾(LRU 标记)。
40
+
41
+ Args:
42
+ path: 文件路径
43
+
44
+ Returns:
45
+ 缓存的文件内容,未命中或已失效返回 None
46
+ """
47
+ key = str(path)
48
+ cached = self._cache.get(key)
49
+ if cached is None:
50
+ return None
51
+
52
+ try:
53
+ current_mtime = path.stat().st_mtime
54
+ except OSError:
55
+ self._cache.pop(key, None)
56
+ return None
57
+
58
+ if cached[0] != current_mtime:
59
+ # 文件已修改,缓存失效
60
+ del self._cache[key]
61
+ return None
62
+
63
+ # 命中:移到末尾(LRU 标记为最近使用)
64
+ self._cache.move_to_end(key)
65
+ return cached[1]
66
+
67
+ def set(self, path: Path, content: str):
68
+ """
69
+ 写入缓存
70
+
71
+ 大文件(超过 MAX_FILE_SIZE)不缓存,避免挤占内存。
72
+
73
+ Args:
74
+ path: 文件路径
75
+ content: 文件内容
76
+ """
77
+ try:
78
+ stat_result = path.stat()
79
+ if stat_result.st_size > self.MAX_FILE_SIZE:
80
+ return
81
+ mtime = stat_result.st_mtime
82
+ except OSError:
83
+ return
84
+
85
+ key = str(path)
86
+
87
+ # 超过上限:淘汰最久未访问的(队首)
88
+ if len(self._cache) >= self.MAX_ENTRIES:
89
+ self._cache.popitem(last=False)
90
+
91
+ self._cache[key] = (mtime, content)
92
+ self._cache.move_to_end(key)
93
+
94
+ def invalidate(self, path: Path):
95
+ """主动失效缓存条目(预留,外部通知文件变更时使用)"""
96
+ self._cache.pop(str(path), None)
97
+
98
+ def clear(self):
99
+ """清空所有缓存"""
100
+ self._cache.clear()
101
+
102
+ @property
103
+ def size(self) -> int:
104
+ """当前缓存条目数"""
105
+ return len(self._cache)
@@ -0,0 +1,231 @@
1
+ """
2
+ CodeReaderService - 文件读取核心逻辑
3
+
4
+ 功能简述:
5
+ 提供文件读取、目录列取和元数据获取的底层实现。
6
+ 与 server.py 分离,便于独立测试。
7
+
8
+ 主要方法清单:
9
+ - read_file: 读取文件内容(支持行范围)
10
+ - list_directory: 列出目录内容
11
+ - get_file_info: 获取文件元数据
12
+ """
13
+
14
+ # ==================== 导入依赖 ====================
15
+
16
+ import logging
17
+ import mimetypes
18
+ import os
19
+ from datetime import datetime, timezone
20
+ from pathlib import Path
21
+
22
+ from timeverse_code_reader.cache import FileCache
23
+ from timeverse_code_reader.language_map import detect_language
24
+
25
+ logger = logging.getLogger(__name__)
26
+
27
+ # ==================== 工作区根目录 ====================
28
+
29
+ # 优先使用 PROJECT_ROOT 环境变量,兜底当前工作目录
30
+ PROJECT_ROOT = os.environ.get("PROJECT_ROOT", os.getcwd())
31
+
32
+ # ==================== 全局缓存实例 ====================
33
+
34
+ # MCP 子进程长驻,缓存跨多次 tool_call 复用
35
+ _file_cache = FileCache()
36
+
37
+
38
+ def _resolve_path(file_path: str) -> Path:
39
+ """
40
+ 将文件路径解析为绝对路径
41
+
42
+ Args:
43
+ file_path: 用户传入的文件路径
44
+
45
+ Returns:
46
+ 解析后的绝对路径 Path 对象
47
+ """
48
+ p = Path(file_path)
49
+ if not p.is_absolute():
50
+ p = Path(PROJECT_ROOT) / p
51
+ return p.resolve()
52
+
53
+
54
+ # ==================== 核心服务 ====================
55
+
56
+
57
+ class CodeReaderService:
58
+ """文件读取服务"""
59
+
60
+ @staticmethod
61
+ def read_file(
62
+ file_path: str,
63
+ start_line: int | None = None,
64
+ end_line: int | None = None,
65
+ ) -> str:
66
+ """
67
+ 读取文件内容
68
+
69
+ 支持按行范围读取,不设自动截断阈值。
70
+ 大文件由 LLM 上下文窗口自然截断。
71
+
72
+ Args:
73
+ file_path: 文件路径
74
+ start_line: 起始行号(从 1 开始)
75
+ end_line: 结束行号
76
+
77
+ Returns:
78
+ 文件内容的纯文本字符串
79
+
80
+ Raises:
81
+ FileNotFoundError: 文件不存在
82
+ ValueError: 行号参数不合法
83
+ """
84
+ path = _resolve_path(file_path)
85
+
86
+ if not path.exists():
87
+ raise FileNotFoundError(f"文件不存在: {path}")
88
+ if not path.is_file():
89
+ raise ValueError(f"路径不是文件: {path}")
90
+
91
+ # 全量读取:尝试走缓存
92
+ if start_line is None and end_line is None:
93
+ cached = _file_cache.get(path)
94
+ if cached is not None:
95
+ return cached
96
+
97
+ content = path.read_text(encoding="utf-8", errors="replace")
98
+
99
+ # 全量读取:写入缓存(行范围读取不缓存,因为内容已被截取)
100
+ if start_line is None and end_line is None:
101
+ _file_cache.set(path, content)
102
+
103
+ if start_line is not None or end_line is not None:
104
+ lines = content.splitlines(keepends=True)
105
+ total_lines = len(lines)
106
+
107
+ if start_line is None:
108
+ start_line = 1
109
+ if end_line is None:
110
+ end_line = total_lines
111
+
112
+ if start_line < 1:
113
+ raise ValueError(f"start_line 必须 >= 1,当前值: {start_line}")
114
+ if end_line > total_lines:
115
+ raise ValueError(f"end_line ({end_line}) 超出文件总行数 ({total_lines})")
116
+ if start_line > end_line:
117
+ raise ValueError(f"start_line ({start_line}) 不能大于 end_line ({end_line})")
118
+
119
+ content = "".join(lines[start_line - 1 : end_line])
120
+
121
+ return content
122
+
123
+ @staticmethod
124
+ def list_directory(dir_path: str) -> list[dict]:
125
+ """
126
+ 列出目录内容(不递归)
127
+
128
+ Args:
129
+ dir_path: 目录路径
130
+
131
+ Returns:
132
+ 目录条目列表,每个条目包含 name, type, size
133
+
134
+ Raises:
135
+ FileNotFoundError: 目录不存在
136
+ NotADirectoryError: 路径不是目录
137
+ """
138
+ path = _resolve_path(dir_path)
139
+
140
+ if not path.exists():
141
+ raise FileNotFoundError(f"目录不存在: {path}")
142
+ if not path.is_dir():
143
+ raise NotADirectoryError(f"路径不是目录: {path}")
144
+
145
+ entries = []
146
+ for entry in sorted(path.iterdir()):
147
+ try:
148
+ stat_info = entry.stat()
149
+ entries.append(
150
+ {
151
+ "name": entry.name,
152
+ "type": "dir" if entry.is_dir() else "file",
153
+ "size": stat_info.st_size if entry.is_file() else None,
154
+ }
155
+ )
156
+ except (PermissionError, OSError) as e:
157
+ logger.warning(f"跳过无法访问的条目 {entry}: {e}")
158
+ continue
159
+
160
+ return entries
161
+
162
+ @staticmethod
163
+ def get_file_info(path_str: str) -> dict:
164
+ """
165
+ 获取文件或目录的元数据
166
+
167
+ 返回文件大小、最后修改时间、编程语言类型、行数等信息。
168
+
169
+ Args:
170
+ path_str: 文件或目录路径
171
+
172
+ Returns:
173
+ 元数据字典
174
+ """
175
+ path = _resolve_path(path_str)
176
+
177
+ if not path.exists():
178
+ return {
179
+ "exists": False,
180
+ "is_file": False,
181
+ "is_dir": False,
182
+ "error": f"路径不存在: {path_str}",
183
+ }
184
+
185
+ stat_info = path.stat()
186
+ is_file = path.is_file()
187
+ is_dir = path.is_dir()
188
+
189
+ modified_ts = stat_info.st_mtime
190
+ modified_at = datetime.fromtimestamp(modified_ts, tz=timezone.utc).isoformat()
191
+
192
+ result: dict = {
193
+ "exists": True,
194
+ "is_file": is_file,
195
+ "is_dir": is_dir,
196
+ "size_bytes": stat_info.st_size if is_file else None,
197
+ "size_display": CodeReaderService._format_size(stat_info.st_size) if is_file else None,
198
+ "modified_at": modified_at,
199
+ }
200
+
201
+ if is_file:
202
+ language = detect_language(path.name)
203
+ if language:
204
+ result["language"] = language
205
+
206
+ mime_type, _ = mimetypes.guess_type(path.name)
207
+ if mime_type:
208
+ result["mime_type"] = mime_type
209
+
210
+ result["encoding"] = "utf-8"
211
+
212
+ try:
213
+ content = path.read_bytes()
214
+ if b"\x00" not in content[:1024]:
215
+ result["line_count"] = content.count(b"\n")
216
+ else:
217
+ result["is_binary"] = True
218
+ except Exception:
219
+ pass
220
+
221
+ return result
222
+
223
+ @staticmethod
224
+ def _format_size(size_bytes: int) -> str:
225
+ """格式化文件大小显示"""
226
+ size = float(size_bytes)
227
+ for unit in ["B", "KB", "MB", "GB"]:
228
+ if size < 1024:
229
+ return f"{size:.1f} {unit}"
230
+ size /= 1024
231
+ return f"{size:.1f} TB"
@@ -0,0 +1,186 @@
1
+ """
2
+ 语言检测映射 - 文件扩展名到编程语言的映射
3
+
4
+ 功能简述:
5
+ 提供 40+ 种常见编程语言的文件扩展名到标准化语言名称的映射。
6
+ 与 timeverse-code-editor 的语言策略保持一致。
7
+
8
+ 主要方法:
9
+ - detect_language: 根据文件名检测编程语言类型
10
+ """
11
+
12
+ import os
13
+
14
+ # ==================== 语言扩展名映射 ====================
15
+
16
+ _LANGUAGE_MAP: dict[str, str] = {
17
+ # Python
18
+ ".py": "Python",
19
+ ".pyi": "Python",
20
+ ".pyx": "Python",
21
+ ".pyw": "Python",
22
+ # TypeScript
23
+ ".ts": "TypeScript",
24
+ ".tsx": "TypeScript",
25
+ ".mts": "TypeScript",
26
+ ".cts": "TypeScript",
27
+ # JavaScript
28
+ ".js": "JavaScript",
29
+ ".jsx": "JavaScript",
30
+ ".mjs": "JavaScript",
31
+ ".cjs": "JavaScript",
32
+ # Go
33
+ ".go": "Go",
34
+ # Rust
35
+ ".rs": "Rust",
36
+ ".rlib": "Rust",
37
+ # Java
38
+ ".java": "Java",
39
+ ".class": "Java",
40
+ # Kotlin
41
+ ".kt": "Kotlin",
42
+ ".kts": "Kotlin",
43
+ # C & C++
44
+ ".c": "C",
45
+ ".h": "C",
46
+ ".cpp": "C++",
47
+ ".hpp": "C++",
48
+ ".cc": "C++",
49
+ ".cxx": "C++",
50
+ ".hxx": "C++",
51
+ # C#
52
+ ".cs": "C#",
53
+ # Swift
54
+ ".swift": "Swift",
55
+ # Ruby
56
+ ".rb": "Ruby",
57
+ ".erb": "Ruby",
58
+ ".gemspec": "Ruby",
59
+ # PHP
60
+ ".php": "PHP",
61
+ ".phtml": "PHP",
62
+ # Shell
63
+ ".sh": "Shell",
64
+ ".bash": "Shell",
65
+ ".zsh": "Shell",
66
+ ".zshrc": "Shell",
67
+ ".profile": "Shell",
68
+ ".bashrc": "Shell",
69
+ # HTML
70
+ ".html": "HTML",
71
+ ".htm": "HTML",
72
+ ".xhtml": "HTML",
73
+ # CSS
74
+ ".css": "CSS",
75
+ ".scss": "SCSS",
76
+ ".sass": "Sass",
77
+ ".less": "Less",
78
+ # SQL
79
+ ".sql": "SQL",
80
+ # YAML / JSON
81
+ ".yaml": "YAML",
82
+ ".yml": "YAML",
83
+ ".json": "JSON",
84
+ ".jsonc": "JSON with Comments",
85
+ # TOML
86
+ ".toml": "TOML",
87
+ # Markdown
88
+ ".md": "Markdown",
89
+ ".mdx": "MDX",
90
+ # Lua
91
+ ".lua": "Lua",
92
+ # Dart
93
+ ".dart": "Dart",
94
+ # Scala
95
+ ".scala": "Scala",
96
+ # Elixir
97
+ ".ex": "Elixir",
98
+ ".exs": "Elixir",
99
+ # Haskell
100
+ ".hs": "Haskell",
101
+ ".lhs": "Haskell",
102
+ # R
103
+ ".r": "R",
104
+ ".R": "R",
105
+ ".rmd": "R Markdown",
106
+ # Perl
107
+ ".pl": "Perl",
108
+ ".pm": "Perl",
109
+ # Zig
110
+ ".zig": "Zig",
111
+ # GraphQL
112
+ ".graphql": "GraphQL",
113
+ ".gql": "GraphQL",
114
+ # Protocol Buffers
115
+ ".proto": "Protocol Buffers",
116
+ # Docker
117
+ "Dockerfile": "Dockerfile",
118
+ "Containerfile": "Dockerfile",
119
+ # Makefile
120
+ "Makefile": "Makefile",
121
+ "makefile": "Makefile",
122
+ "GNUmakefile": "Makefile",
123
+ # Terraform
124
+ ".tf": "Terraform",
125
+ ".tfvars": "Terraform",
126
+ # Vue
127
+ ".vue": "Vue",
128
+ # Svelte
129
+ ".svelte": "Svelte",
130
+ # Astro
131
+ ".astro": "Astro",
132
+ # Prisma
133
+ ".prisma": "Prisma",
134
+ # CMake
135
+ ".cmake": "CMake",
136
+ "CMakeLists.txt": "CMake",
137
+ # Nix
138
+ ".nix": "Nix",
139
+ # ReasonML / OCaml
140
+ ".ml": "OCaml",
141
+ ".mli": "OCaml",
142
+ ".re": "ReasonML",
143
+ }
144
+
145
+ # 无扩展名的特殊文件名映射(如 Dockerfile, Makefile)
146
+ _NAMELESS_MAP: dict[str, str] = {
147
+ "Dockerfile": "Dockerfile",
148
+ "Containerfile": "Dockerfile",
149
+ "Makefile": "Makefile",
150
+ "GNUmakefile": "Makefile",
151
+ "CMakeLists.txt": "CMake",
152
+ }
153
+
154
+
155
+ def detect_language(filename: str) -> str | None:
156
+ """
157
+ 根据文件名检测编程语言类型
158
+
159
+ 优先匹配文件扩展名,再匹配特殊文件名(如 Dockerfile, Makefile)。
160
+
161
+ Args:
162
+ filename: 文件名(可包含路径)
163
+
164
+ Returns:
165
+ 标准化语言名称,无法识别返回 None
166
+
167
+ Example:
168
+ >>> detect_language("main.py")
169
+ 'Python'
170
+ >>> detect_language("Dockerfile")
171
+ 'Dockerfile'
172
+ >>> detect_language("unknown.xyz")
173
+ None
174
+ """
175
+ base = os.path.basename(filename)
176
+
177
+ # 先尝试完全匹配特殊文件名
178
+ if base in _NAMELESS_MAP:
179
+ return _NAMELESS_MAP[base]
180
+
181
+ # 再按扩展名匹配
182
+ _, ext = os.path.splitext(base)
183
+ if ext:
184
+ return _LANGUAGE_MAP.get(ext.lower())
185
+
186
+ return None
@@ -0,0 +1,105 @@
1
+ """
2
+ timeverse-code-reader MCP 服务器 - 文件读取工具
3
+
4
+ 功能简述:
5
+ 使用 FastMCP 框架实现的标准 stdio MCP 服务器。
6
+ 提供 3 个文件读取工具,可通过任何 MCP 客户端调用。
7
+
8
+ 主要工具清单:
9
+ - read_file: 读取文件内容(支持行范围)
10
+ - list_directory: 列出目录内容
11
+ - get_file_info: 获取文件元数据
12
+
13
+ 使用示例:
14
+ # 作为独立进程运行
15
+ timeverse-code-reader
16
+
17
+ # 在 MCP 客户端中配置
18
+ {
19
+ "command": "timeverse-code-reader",
20
+ "args": []
21
+ }
22
+ """
23
+
24
+ # ==================== 导入依赖 ====================
25
+
26
+ from mcp.server import FastMCP
27
+
28
+ from timeverse_code_reader.handler import CodeReaderService
29
+
30
+ # ==================== MCP 服务器初始化 ====================
31
+
32
+ # 创建 MCP 服务器实例
33
+ app = FastMCP("timeverse-code-reader")
34
+
35
+ # ==================== 工具注册 ====================
36
+
37
+
38
+ @app.tool()
39
+ def read_file(
40
+ path: str,
41
+ start_line: int | None = None,
42
+ end_line: int | None = None,
43
+ ) -> str:
44
+ """
45
+ 读取指定文件的内容
46
+
47
+ Args:
48
+ path: 文件路径,支持绝对路径或基于工作区的相对路径
49
+ start_line: 起始行号(从 1 开始),不传则从文件开头读取
50
+ end_line: 结束行号,不传则读到文件结尾
51
+
52
+ Returns:
53
+ 文件内容的纯文本字符串
54
+
55
+ Raises:
56
+ FileNotFoundError: 文件不存在
57
+ ValueError: 行号参数不合法
58
+ """
59
+ return CodeReaderService.read_file(path, start_line, end_line)
60
+
61
+
62
+ @app.tool()
63
+ def list_directory(path: str) -> list[dict]:
64
+ """
65
+ 列出指定目录下的文件和子目录(不递归)
66
+
67
+ Args:
68
+ path: 目录路径,支持绝对路径或相对路径
69
+
70
+ Returns:
71
+ 条目列表,每项包含 name, type(file/dir), size
72
+
73
+ Raises:
74
+ FileNotFoundError: 目录不存在
75
+ NotADirectoryError: 路径不是目录
76
+ """
77
+ return CodeReaderService.list_directory(path)
78
+
79
+
80
+ @app.tool()
81
+ def get_file_info(path: str) -> dict:
82
+ """
83
+ 获取文件或目录的元数据
84
+
85
+ 返回文件大小、最后修改时间、编程语言类型、行数等信息。
86
+
87
+ Args:
88
+ path: 文件或目录路径
89
+
90
+ Returns:
91
+ 元数据字典,包含 exists, is_file, size_bytes, language 等
92
+ """
93
+ return CodeReaderService.get_file_info(path)
94
+
95
+
96
+ # ==================== 入口 ====================
97
+
98
+
99
+ def main():
100
+ """命令行入口"""
101
+ app.run(transport="stdio")
102
+
103
+
104
+ if __name__ == "__main__":
105
+ main()
@@ -0,0 +1,248 @@
1
+ Metadata-Version: 2.4
2
+ Name: timeverse-code-reader
3
+ Version: 0.1.1
4
+ Summary: TimeVerse File Reader MCP Tool - 读取文件内容、列出目录、获取文件元数据
5
+ Project-URL: Homepage, https://github.com/elimyliu/timeverse-code-reader
6
+ Project-URL: Issues, https://github.com/elimyliu/timeverse-code-reader/issues
7
+ Author: elimyliu
8
+ License: MIT
9
+ License-File: LICENSE
10
+ Keywords: code-reader,file-reader,mcp,model-context-protocol,timeverse
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Operating System :: MacOS
15
+ Classifier: Operating System :: Microsoft :: Windows
16
+ Classifier: Operating System :: POSIX :: Linux
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
+ Requires-Python: >=3.10
23
+ Requires-Dist: mcp>=1.0.0
24
+ Provides-Extra: dev
25
+ Requires-Dist: mypy>=1.0; extra == 'dev'
26
+ Requires-Dist: pytest-asyncio>=0.21; extra == 'dev'
27
+ Requires-Dist: pytest>=7.0; extra == 'dev'
28
+ Requires-Dist: ruff>=0.1.0; extra == 'dev'
29
+ Description-Content-Type: text/markdown
30
+
31
+ # TimeVerse Code Reader
32
+
33
+ [![PyPI](https://img.shields.io/pypi/v/timeverse-code-reader)](https://pypi.org/project/timeverse-code-reader/)
34
+ [![Python](https://img.shields.io/pypi/pyversions/timeverse-code-reader)](https://pypi.org/project/timeverse-code-reader/)
35
+ [![CI](https://github.com/elimyliu/timeverse-code-reader/actions/workflows/ci.yml/badge.svg)](https://github.com/elimyliu/timeverse-code-reader/actions/workflows/ci.yml)
36
+ [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
37
+
38
+ TimeVerse Code Reader 是一个基于 [MCP (Model Context Protocol)](https://modelcontextprotocol.io/) 的文件读取工具,专为 AI 编码助手和 LLM 应用设计。通过标准化的 MCP 协议,为 AI 模型提供精准、高效、安全的文件系统读取能力。
39
+
40
+ ---
41
+
42
+ ## 核心优势
43
+
44
+ ### 🚀 智能缓存机制
45
+ 内置 `FileCache` 采用 **mtime 校验 + LRU 淘汰** 策略:
46
+ - **mtime 自动失效**:文件修改后缓存自动失效,下次读取重新加载,保证始终返回最新内容
47
+ - **LRU 淘汰**:最多缓存 100 个文件,最近最少使用的自动淘汰,内存占用可控
48
+ - **大文件保护**:超过 10MB 的文件跳过缓存,防止挤占内存
49
+ - **会话级复用**:MCP 子进程长驻运行,缓存跨多次 Tool Call 复用,典型会话命中 10-30 次,大幅减少磁盘 I/O
50
+
51
+ ### 🔍 专注于代码阅读,不限于代码
52
+ 专为阅读源码场景优化,同时能处理任何文本文件:
53
+ - **按行范围读取**:支持 `start_line` / `end_line` 参数,只读取感兴趣的行段,减少 Token 消耗
54
+ - **40+ 编程语言识别**:自动检测 Python、TypeScript、Go、Rust、Java 等主流语言,返回标准化语言名称
55
+ - **智能编码处理**:统一 UTF-8 读取,`errors="replace"` 容错策略确保非完美编码文件也不崩溃
56
+ - **二进制文件识别**:通过前 1024 字节快速检测,避免将二进制文件以文本形式暴露给 LLM
57
+
58
+ ### 📂 完整文件系统元数据
59
+ 通过 `get_file_info` 一次获取文件全貌,赋能 AI 理解项目结构:
60
+ - 文件类型、大小(原始 + 格式化显示)
61
+ - 最后修改时间(ISO 8601,UTC)
62
+ - 语言检测结果、MIME 类型
63
+ - 行数统计、二进制文件标识
64
+
65
+ ### ⚡ 高性能设计
66
+ | 优化 | 实现 |
67
+ |------|------|
68
+ | 文件内容缓存 | mtime 校验 + LRU,避免重复磁盘 I/O |
69
+ | 缓存上限 100 | 内存开销控制在 ~30MB |
70
+ | 行范围不缓存 | 避免缓存被截取的"脏"内容 |
71
+ | 跳过不可访问条目 | 单个权限错误不影响整体操作 |
72
+ | 延迟二进制检测 | 仅检查前 1024 字节 |
73
+
74
+ ---
75
+
76
+ ## 对比 Filesystem MCP Server
77
+
78
+ [`@modelcontextprotocol/server-filesystem`](https://github.com/modelcontextprotocol/servers/tree/main/src/filesystem) 是 MCP 官方提供的文件系统操作参考实现。TimeVerse Code Reader 与它的定位不同,各有侧重:
79
+
80
+ ### 定位差异
81
+
82
+ | 维度 | TimeVerse Code Reader | Filesystem MCP Server |
83
+ |------|----------------------|-----------------------|
84
+ | **定位** | 专注于**安全读取**的只读工具 | 通用的文件系统**读写管理**工具 |
85
+ | **适用场景** | AI 编码助手阅读源码、分析项目结构 | 文件全生命周期管理(增删改查) |
86
+ | **运行时** | Python(`pip install`) | Node.js(`npx`) |
87
+ | **工具数量** | 3 个精炼只读工具 | 15 个全功能工具 |
88
+ | **写操作** | ❌ 不提供 | ✅ write / edit / move / delete |
89
+
90
+ ### TimeVerse Code Reader 的独特优势
91
+
92
+ | 特性 | TimeVerse Code Reader | Filesystem MCP Server |
93
+ |------|----------------------|-----------------------|
94
+ | **智能缓存** | ✅ mtime 校验 + LRU 淘汰,会话级复用 | ❌ 无内置缓存,每次读取都走磁盘 I/O |
95
+ | **按行范围读取** | ✅ `start_line` / `end_line` 任意范围截取 | ⚠️ 仅支持 `head`(前 N 行)或 `tail`(后 N 行),不可同时使用,无法指定中间范围 |
96
+ | **编程语言识别** | ✅ 40+ 语言自动检测,返回标准化名称 | ❌ 无语言识别能力 |
97
+ | **文件元数据丰富度** | ✅ 语言、行数、MIME、格式化大小、二进制标识 | ⚠️ 基础元数据(大小、时间、类型、权限) |
98
+ | **二进制文件检测** | ✅ 前 1024 字节快速识别 | ❌ 不提供二进制检测 |
99
+ | **安全性** | ✅ 只读设计,天然无破坏风险 | ⚠️ 需显式配置允许目录,写操作需谨慎 |
100
+ | **安装体积** | ✅ 纯 Python,轻量无外部依赖 | ⚠️ 需 Node.js 运行时 |
101
+
102
+ ### 选型建议
103
+
104
+ - **AI 编码/代码阅读场景**:选 TimeVerse Code Reader —— 只读更安全,缓存减少重复 I/O,语言识别和行范围读取更适合代码分析
105
+ - **文件全量管理场景**:选 Filesystem MCP Server —— 需要增删改、文件搜索、批量编辑等功能时更合适
106
+ - **两者可同时启用**:互不冲突,可在一个 MCP 客户端中同时配置两个服务器,各取所长
107
+
108
+ ## Tools
109
+
110
+ | 工具 | 描述 |
111
+ |------|------|
112
+ | `read_file` | 读取文件内容,支持按行范围读取 |
113
+ | `list_directory` | 列出目录下的文件和子目录(一级,不递归) |
114
+ | `get_file_info` | 获取文件或目录的完整元数据 |
115
+
116
+ ## 安装
117
+
118
+ ```bash
119
+ pip install timeverse-code-reader
120
+ ```
121
+
122
+ 或从源码安装:
123
+
124
+ ```bash
125
+ git clone https://github.com/elimyliu/timeverse-code-reader.git
126
+ cd timeverse-code-reader
127
+ pip install -e ".[dev]"
128
+ ```
129
+
130
+ ## 使用
131
+
132
+ ### 作为 CLI 工具
133
+
134
+ ```bash
135
+ timeverse-code-reader
136
+ ```
137
+
138
+ ### 传输方式
139
+
140
+ 支持两种传输模式,通过 `--transport` 参数选择:
141
+
142
+ #### stdio 模式(默认)
143
+
144
+ 通过标准输入输出与客户端通信,适合本地/嵌入使用:
145
+
146
+ ```bash
147
+ # 直接运行
148
+ timeverse-code-reader
149
+ # 或显式指定
150
+ timeverse-code-reader --transport stdio
151
+ ```
152
+
153
+ #### HTTP 模式
154
+
155
+ 通过 Streamable HTTP 远程调用,适合跨网络或独立部署:
156
+
157
+ ```bash
158
+ # 需要先安装 HTTP 依赖
159
+ pip install "timeverse-code-reader[http]"
160
+ # 启动 HTTP 服务
161
+ timeverse-code-reader --transport http --host 0.0.0.0 --port 8000
162
+ # 开发模式(启用热重载,代码变更自动重启)
163
+ timeverse-code-reader --transport http --host 0.0.0.0 --port 8000 --dev
164
+ ```
165
+
166
+ 启动后客户端通过 SSE 端点连接:
167
+
168
+ - SSE 端点:`http://<host>:<port>/sse`
169
+ - 消息端点:`http://<host>:<port>/messages/`
170
+
171
+ ### 与 TimeVerse Studio 集成
172
+
173
+ 在 TimeVerse Studio 的 MCP Server 配置中添加:
174
+
175
+ ```json
176
+ {
177
+ "mcpServers": {
178
+ "timeverse-code-reader": {
179
+ "command": "timeverse-code-reader",
180
+ "args": [],
181
+ "env": {
182
+ "PROJECT_ROOT": "/path/to/workspace"
183
+ }
184
+ }
185
+ }
186
+ }
187
+ ```
188
+
189
+ 如果使用 `uvx`(无需安装,直接运行):
190
+
191
+ ```json
192
+ {
193
+ "mcpServers": {
194
+ "timeverse-code-reader": {
195
+ "command": "uvx",
196
+ "args": ["timeverse-code-reader"]
197
+ }
198
+ }
199
+ }
200
+ ```
201
+
202
+ ### 其他 MCP 客户端集成
203
+
204
+ 所有支持 MCP 协议的客户端(Claude Desktop、Cursor、Windsurf、Trae 等)配置方式一致,只需在客户端的 MCP Server 配置中添加:
205
+
206
+ ```json
207
+ {
208
+ "mcpServers": {
209
+ "timeverse-code-reader": {
210
+ "command": "timeverse-code-reader",
211
+ "args": []
212
+ }
213
+ }
214
+ }
215
+ ```
216
+
217
+ 如果使用 `uvx`(无需安装,直接运行):
218
+
219
+ ```json
220
+ {
221
+ "mcpServers": {
222
+ "timeverse-code-reader": {
223
+ "command": "uvx",
224
+ "args": ["timeverse-code-reader"]
225
+ }
226
+ }
227
+ }
228
+ ```
229
+
230
+ 不同客户端的配置文件位置请参考其官方文档。需要 HTTP 模式时添加 `--transport http` 参数。
231
+
232
+ ### 环境变量
233
+
234
+ - `PROJECT_ROOT`(可选):工作区根目录路径,默认为当前工作目录。所有相对路径将基于此目录解析。
235
+
236
+ ## 开发
237
+
238
+ ```bash
239
+ pip install -e ".[dev]"
240
+ ruff check src/ tests/
241
+ ruff format src/ tests/
242
+ mypy src/
243
+ pytest -v
244
+ ```
245
+
246
+ ## License
247
+
248
+ MIT
@@ -0,0 +1,10 @@
1
+ timeverse_code_reader/__init__.py,sha256=Pi64WWIXU0Nmy6Ktz-Djpo-jXv5gDrU8x4g1E7X2iBU,65
2
+ timeverse_code_reader/cache.py,sha256=xxzW408PtwKGMTCNWSyoAKenoXWpmSwDWedwuVeNgDw,2878
3
+ timeverse_code_reader/handler.py,sha256=FEOkxpHo2h-wxG8EBtWA_o_UkTDcj62b-lcvH87Bfao,6860
4
+ timeverse_code_reader/language_map.py,sha256=SW8V8nMOKb9pDs98vNCBjIJ_enaGdGznMIkb9auMg6M,3946
5
+ timeverse_code_reader/server.py,sha256=8MSWPcpD5hjtd9tWHKe9dx1BM7A5-kYNhmqLqzD1UOs,2520
6
+ timeverse_code_reader-0.1.1.dist-info/METADATA,sha256=SOIbwaynpugV5rCJQDtvFL1X4pR6nCctTWQNbgfhJFY,8995
7
+ timeverse_code_reader-0.1.1.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
8
+ timeverse_code_reader-0.1.1.dist-info/entry_points.txt,sha256=HLeN1XzaHhqALORnxOiyoTWG89_7rnt6jnZ4cI-OE0c,76
9
+ timeverse_code_reader-0.1.1.dist-info/licenses/LICENSE,sha256=1PQpLpEMW2ZvpQi_e5F9Hwj2mzly0KShtQY9yH9ahnc,1065
10
+ timeverse_code_reader-0.1.1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.31.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ timeverse-code-reader = timeverse_code_reader.server:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 elimyliu
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.