hanzo-mcp 0.3.8__py3-none-any.whl → 0.5.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.
Potentially problematic release.
This version of hanzo-mcp might be problematic. Click here for more details.
- hanzo_mcp/__init__.py +1 -1
- hanzo_mcp/cli.py +118 -170
- hanzo_mcp/cli_enhanced.py +438 -0
- hanzo_mcp/config/__init__.py +19 -0
- hanzo_mcp/config/settings.py +388 -0
- hanzo_mcp/config/tool_config.py +197 -0
- hanzo_mcp/prompts/__init__.py +117 -0
- hanzo_mcp/prompts/compact_conversation.py +77 -0
- hanzo_mcp/prompts/create_release.py +38 -0
- hanzo_mcp/prompts/project_system.py +120 -0
- hanzo_mcp/prompts/project_todo_reminder.py +111 -0
- hanzo_mcp/prompts/utils.py +286 -0
- hanzo_mcp/server.py +117 -99
- hanzo_mcp/tools/__init__.py +105 -32
- hanzo_mcp/tools/agent/__init__.py +8 -11
- hanzo_mcp/tools/agent/agent_tool.py +290 -224
- hanzo_mcp/tools/agent/prompt.py +16 -13
- hanzo_mcp/tools/agent/tool_adapter.py +9 -9
- hanzo_mcp/tools/common/__init__.py +17 -16
- hanzo_mcp/tools/common/base.py +79 -110
- hanzo_mcp/tools/common/batch_tool.py +330 -0
- hanzo_mcp/tools/common/context.py +26 -292
- hanzo_mcp/tools/common/permissions.py +12 -12
- hanzo_mcp/tools/common/thinking_tool.py +153 -0
- hanzo_mcp/tools/common/validation.py +1 -63
- hanzo_mcp/tools/filesystem/__init__.py +88 -57
- hanzo_mcp/tools/filesystem/base.py +32 -24
- hanzo_mcp/tools/filesystem/content_replace.py +114 -107
- hanzo_mcp/tools/filesystem/directory_tree.py +129 -105
- hanzo_mcp/tools/filesystem/edit.py +279 -0
- hanzo_mcp/tools/filesystem/grep.py +458 -0
- hanzo_mcp/tools/filesystem/grep_ast_tool.py +250 -0
- hanzo_mcp/tools/filesystem/multi_edit.py +362 -0
- hanzo_mcp/tools/filesystem/read.py +255 -0
- hanzo_mcp/tools/filesystem/write.py +156 -0
- hanzo_mcp/tools/jupyter/__init__.py +41 -29
- hanzo_mcp/tools/jupyter/base.py +66 -57
- hanzo_mcp/tools/jupyter/{edit_notebook.py → notebook_edit.py} +162 -139
- hanzo_mcp/tools/jupyter/notebook_read.py +152 -0
- hanzo_mcp/tools/shell/__init__.py +29 -20
- hanzo_mcp/tools/shell/base.py +87 -45
- hanzo_mcp/tools/shell/bash_session.py +731 -0
- hanzo_mcp/tools/shell/bash_session_executor.py +295 -0
- hanzo_mcp/tools/shell/command_executor.py +435 -384
- hanzo_mcp/tools/shell/run_command.py +284 -131
- hanzo_mcp/tools/shell/run_command_windows.py +328 -0
- hanzo_mcp/tools/shell/session_manager.py +196 -0
- hanzo_mcp/tools/shell/session_storage.py +325 -0
- hanzo_mcp/tools/todo/__init__.py +66 -0
- hanzo_mcp/tools/todo/base.py +319 -0
- hanzo_mcp/tools/todo/todo_read.py +148 -0
- hanzo_mcp/tools/todo/todo_write.py +378 -0
- hanzo_mcp/tools/vector/__init__.py +95 -0
- hanzo_mcp/tools/vector/infinity_store.py +365 -0
- hanzo_mcp/tools/vector/project_manager.py +361 -0
- hanzo_mcp/tools/vector/vector_index.py +115 -0
- hanzo_mcp/tools/vector/vector_search.py +215 -0
- {hanzo_mcp-0.3.8.dist-info → hanzo_mcp-0.5.0.dist-info}/METADATA +33 -1
- hanzo_mcp-0.5.0.dist-info/RECORD +63 -0
- {hanzo_mcp-0.3.8.dist-info → hanzo_mcp-0.5.0.dist-info}/WHEEL +1 -1
- hanzo_mcp/tools/agent/base_provider.py +0 -73
- hanzo_mcp/tools/agent/litellm_provider.py +0 -45
- hanzo_mcp/tools/agent/lmstudio_agent.py +0 -385
- hanzo_mcp/tools/agent/lmstudio_provider.py +0 -219
- hanzo_mcp/tools/agent/provider_registry.py +0 -120
- hanzo_mcp/tools/common/error_handling.py +0 -86
- hanzo_mcp/tools/common/logging_config.py +0 -115
- hanzo_mcp/tools/common/session.py +0 -91
- hanzo_mcp/tools/common/think_tool.py +0 -123
- hanzo_mcp/tools/common/version_tool.py +0 -120
- hanzo_mcp/tools/filesystem/edit_file.py +0 -287
- hanzo_mcp/tools/filesystem/get_file_info.py +0 -170
- hanzo_mcp/tools/filesystem/read_files.py +0 -199
- hanzo_mcp/tools/filesystem/search_content.py +0 -275
- hanzo_mcp/tools/filesystem/write_file.py +0 -162
- hanzo_mcp/tools/jupyter/notebook_operations.py +0 -514
- hanzo_mcp/tools/jupyter/read_notebook.py +0 -165
- hanzo_mcp/tools/project/__init__.py +0 -64
- hanzo_mcp/tools/project/analysis.py +0 -886
- hanzo_mcp/tools/project/base.py +0 -66
- hanzo_mcp/tools/project/project_analyze.py +0 -173
- hanzo_mcp/tools/shell/run_script.py +0 -215
- hanzo_mcp/tools/shell/script_tool.py +0 -244
- hanzo_mcp-0.3.8.dist-info/RECORD +0 -53
- {hanzo_mcp-0.3.8.dist-info → hanzo_mcp-0.5.0.dist-info}/entry_points.txt +0 -0
- {hanzo_mcp-0.3.8.dist-info → hanzo_mcp-0.5.0.dist-info}/licenses/LICENSE +0 -0
- {hanzo_mcp-0.3.8.dist-info → hanzo_mcp-0.5.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"""Write file tool implementation.
|
|
2
|
+
|
|
3
|
+
This module provides the Write tool for creating or overwriting files.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Annotated, TypedDict, Unpack, final, override
|
|
8
|
+
|
|
9
|
+
from fastmcp import Context as MCPContext
|
|
10
|
+
from fastmcp import FastMCP
|
|
11
|
+
from fastmcp.server.dependencies import get_context
|
|
12
|
+
from pydantic import Field
|
|
13
|
+
|
|
14
|
+
from hanzo_mcp.tools.filesystem.base import FilesystemBaseTool
|
|
15
|
+
|
|
16
|
+
FilePath = Annotated[
|
|
17
|
+
str,
|
|
18
|
+
Field(
|
|
19
|
+
description="The absolute path to the file to write (must be absolute, not relative)",
|
|
20
|
+
min_length=1,
|
|
21
|
+
),
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
Content = Annotated[
|
|
25
|
+
str,
|
|
26
|
+
Field(
|
|
27
|
+
description="The content to write to the file",
|
|
28
|
+
min_length=1,
|
|
29
|
+
),
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class WriteToolParams(TypedDict):
|
|
34
|
+
"""Parameters for the Write tool.
|
|
35
|
+
|
|
36
|
+
Attributes:
|
|
37
|
+
file_path: The absolute path to the file to write (must be absolute, not relative)
|
|
38
|
+
content: The content to write to the file
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
file_path: FilePath
|
|
42
|
+
content: Content
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@final
|
|
46
|
+
class Write(FilesystemBaseTool):
|
|
47
|
+
"""Tool for writing file contents."""
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
@override
|
|
51
|
+
def name(self) -> str:
|
|
52
|
+
"""Get the tool name.
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
Tool name
|
|
56
|
+
"""
|
|
57
|
+
return "write"
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
@override
|
|
61
|
+
def description(self) -> str:
|
|
62
|
+
"""Get the tool description.
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
Tool description
|
|
66
|
+
"""
|
|
67
|
+
return """Writes a file to the local filesystem.
|
|
68
|
+
|
|
69
|
+
Usage:
|
|
70
|
+
- This tool will overwrite the existing file if there is one at the provided path.
|
|
71
|
+
- If this is an existing file, you MUST use the Read tool first to read the file's contents. This tool will fail if you did not read the file first.
|
|
72
|
+
- ALWAYS prefer editing existing files in the codebase. NEVER write new files unless explicitly required.
|
|
73
|
+
- NEVER proactively create documentation files (*.md) or README files. Only create documentation files if explicitly requested by the User."""
|
|
74
|
+
|
|
75
|
+
@override
|
|
76
|
+
async def call(
|
|
77
|
+
self,
|
|
78
|
+
ctx: MCPContext,
|
|
79
|
+
**params: Unpack[WriteToolParams],
|
|
80
|
+
) -> str:
|
|
81
|
+
"""Execute the tool with the given parameters.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
ctx: MCP context
|
|
85
|
+
**params: Tool parameters
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
Tool result
|
|
89
|
+
"""
|
|
90
|
+
tool_ctx = self.create_tool_context(ctx)
|
|
91
|
+
self.set_tool_context_info(tool_ctx)
|
|
92
|
+
|
|
93
|
+
# Extract parameters
|
|
94
|
+
file_path: FilePath = params["file_path"]
|
|
95
|
+
content: Content = params["content"]
|
|
96
|
+
|
|
97
|
+
# Validate parameters
|
|
98
|
+
path_validation = self.validate_path(file_path)
|
|
99
|
+
if path_validation.is_error:
|
|
100
|
+
await tool_ctx.error(path_validation.error_message)
|
|
101
|
+
return f"Error: {path_validation.error_message}"
|
|
102
|
+
|
|
103
|
+
await tool_ctx.info(f"Writing file: {file_path}")
|
|
104
|
+
|
|
105
|
+
# Check if file is allowed to be written
|
|
106
|
+
allowed, error_msg = await self.check_path_allowed(file_path, tool_ctx)
|
|
107
|
+
if not allowed:
|
|
108
|
+
return error_msg
|
|
109
|
+
|
|
110
|
+
# Additional check already verified by is_path_allowed above
|
|
111
|
+
await tool_ctx.info(f"Writing file: {file_path}")
|
|
112
|
+
|
|
113
|
+
try:
|
|
114
|
+
path_obj = Path(file_path)
|
|
115
|
+
|
|
116
|
+
# Check if parent directory is allowed
|
|
117
|
+
parent_dir = str(path_obj.parent)
|
|
118
|
+
if not self.is_path_allowed(parent_dir):
|
|
119
|
+
await tool_ctx.error(f"Parent directory not allowed: {parent_dir}")
|
|
120
|
+
return f"Error: Parent directory not allowed: {parent_dir}"
|
|
121
|
+
|
|
122
|
+
# Create parent directories if they don't exist
|
|
123
|
+
path_obj.parent.mkdir(parents=True, exist_ok=True)
|
|
124
|
+
|
|
125
|
+
# Write the file
|
|
126
|
+
with open(path_obj, "w", encoding="utf-8") as f:
|
|
127
|
+
f.write(content)
|
|
128
|
+
|
|
129
|
+
await tool_ctx.info(
|
|
130
|
+
f"Successfully wrote file: {file_path} ({len(content)} bytes)"
|
|
131
|
+
)
|
|
132
|
+
return f"Successfully wrote file: {file_path} ({len(content)} bytes)"
|
|
133
|
+
except Exception as e:
|
|
134
|
+
await tool_ctx.error(f"Error writing file: {str(e)}")
|
|
135
|
+
return f"Error writing file: {str(e)}"
|
|
136
|
+
|
|
137
|
+
@override
|
|
138
|
+
def register(self, mcp_server: FastMCP) -> None:
|
|
139
|
+
"""Register this tool with the MCP server.
|
|
140
|
+
|
|
141
|
+
Creates a wrapper function with explicitly defined parameters that match
|
|
142
|
+
the tool's parameter schema and registers it with the MCP server.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
mcp_server: The FastMCP server instance
|
|
146
|
+
"""
|
|
147
|
+
tool_self = self # Create a reference to self for use in the closure
|
|
148
|
+
|
|
149
|
+
@mcp_server.tool(name=self.name, description=self.description)
|
|
150
|
+
async def write(
|
|
151
|
+
ctx: MCPContext,
|
|
152
|
+
file_path: FilePath,
|
|
153
|
+
content: Content,
|
|
154
|
+
) -> str:
|
|
155
|
+
ctx = get_context()
|
|
156
|
+
return await tool_self.call(ctx, file_path=file_path, content=content)
|
|
@@ -4,73 +4,85 @@ This package provides tools for working with Jupyter notebooks (.ipynb files),
|
|
|
4
4
|
including reading and editing notebook cells.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
from
|
|
7
|
+
from fastmcp import FastMCP
|
|
8
8
|
|
|
9
9
|
from hanzo_mcp.tools.common.base import BaseTool, ToolRegistry
|
|
10
|
-
from hanzo_mcp.tools.common.context import DocumentContext
|
|
11
10
|
from hanzo_mcp.tools.common.permissions import PermissionManager
|
|
12
|
-
from hanzo_mcp.tools.jupyter.
|
|
13
|
-
from hanzo_mcp.tools.jupyter.
|
|
11
|
+
from hanzo_mcp.tools.jupyter.notebook_edit import NoteBookEditTool
|
|
12
|
+
from hanzo_mcp.tools.jupyter.notebook_read import NotebookReadTool
|
|
14
13
|
|
|
15
14
|
# Export all tool classes
|
|
16
15
|
__all__ = [
|
|
17
|
-
"
|
|
18
|
-
"
|
|
16
|
+
"NotebookReadTool",
|
|
17
|
+
"NoteBookEditTool",
|
|
19
18
|
"get_jupyter_tools",
|
|
20
19
|
"register_jupyter_tools",
|
|
21
20
|
]
|
|
22
21
|
|
|
22
|
+
|
|
23
23
|
def get_read_only_jupyter_tools(
|
|
24
|
-
|
|
24
|
+
permission_manager: PermissionManager,
|
|
25
25
|
) -> list[BaseTool]:
|
|
26
26
|
"""Create instances of read only Jupyter notebook tools.
|
|
27
|
-
|
|
27
|
+
|
|
28
28
|
Args:
|
|
29
|
-
document_context: Document context for tracking file contents
|
|
30
29
|
permission_manager: Permission manager for access control
|
|
31
|
-
|
|
30
|
+
|
|
32
31
|
Returns:
|
|
33
32
|
List of Jupyter notebook tool instances
|
|
34
33
|
"""
|
|
35
34
|
return [
|
|
36
|
-
|
|
35
|
+
NotebookReadTool(permission_manager),
|
|
37
36
|
]
|
|
38
37
|
|
|
39
38
|
|
|
40
|
-
def get_jupyter_tools(
|
|
41
|
-
document_context: DocumentContext, permission_manager: PermissionManager
|
|
42
|
-
) -> list[BaseTool]:
|
|
39
|
+
def get_jupyter_tools(permission_manager: PermissionManager) -> list[BaseTool]:
|
|
43
40
|
"""Create instances of all Jupyter notebook tools.
|
|
44
|
-
|
|
41
|
+
|
|
45
42
|
Args:
|
|
46
|
-
document_context: Document context for tracking file contents
|
|
47
43
|
permission_manager: Permission manager for access control
|
|
48
|
-
|
|
44
|
+
|
|
49
45
|
Returns:
|
|
50
46
|
List of Jupyter notebook tool instances
|
|
51
47
|
"""
|
|
52
48
|
return [
|
|
53
|
-
|
|
54
|
-
|
|
49
|
+
NotebookReadTool(permission_manager),
|
|
50
|
+
NoteBookEditTool(permission_manager),
|
|
55
51
|
]
|
|
56
52
|
|
|
57
53
|
|
|
58
54
|
def register_jupyter_tools(
|
|
59
55
|
mcp_server: FastMCP,
|
|
60
|
-
document_context: DocumentContext,
|
|
61
56
|
permission_manager: PermissionManager,
|
|
62
|
-
|
|
63
|
-
) ->
|
|
64
|
-
"""Register
|
|
65
|
-
|
|
57
|
+
enabled_tools: dict[str, bool] | None = None,
|
|
58
|
+
) -> list[BaseTool]:
|
|
59
|
+
"""Register Jupyter notebook tools with the MCP server.
|
|
60
|
+
|
|
66
61
|
Args:
|
|
67
62
|
mcp_server: The FastMCP server instance
|
|
68
|
-
document_context: Document context for tracking file contents
|
|
69
63
|
permission_manager: Permission manager for access control
|
|
70
|
-
|
|
64
|
+
enabled_tools: Dictionary of individual tool enable states (default: None)
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
List of registered tools
|
|
71
68
|
"""
|
|
72
|
-
|
|
73
|
-
|
|
69
|
+
# Define tool mapping
|
|
70
|
+
tool_classes = {
|
|
71
|
+
"notebook_read": NotebookReadTool,
|
|
72
|
+
"notebook_edit": NoteBookEditTool,
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
tools = []
|
|
76
|
+
|
|
77
|
+
if enabled_tools:
|
|
78
|
+
# Use individual tool configuration
|
|
79
|
+
for tool_name, enabled in enabled_tools.items():
|
|
80
|
+
if enabled and tool_name in tool_classes:
|
|
81
|
+
tool_class = tool_classes[tool_name]
|
|
82
|
+
tools.append(tool_class(permission_manager))
|
|
74
83
|
else:
|
|
75
|
-
tools
|
|
84
|
+
# Use all tools (backward compatibility)
|
|
85
|
+
tools = get_jupyter_tools(permission_manager)
|
|
86
|
+
|
|
76
87
|
ToolRegistry.register_tools(mcp_server, tools)
|
|
88
|
+
return tools
|
hanzo_mcp/tools/jupyter/base.py
CHANGED
|
@@ -10,35 +10,36 @@ import re
|
|
|
10
10
|
from pathlib import Path
|
|
11
11
|
from typing import Any, final
|
|
12
12
|
|
|
13
|
-
from
|
|
13
|
+
from fastmcp import Context as MCPContext
|
|
14
14
|
|
|
15
15
|
from hanzo_mcp.tools.common.base import FileSystemTool
|
|
16
16
|
from hanzo_mcp.tools.common.context import ToolContext, create_tool_context
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
# Pattern to match ANSI escape sequences
|
|
20
|
-
ANSI_ESCAPE_PATTERN = re.compile(r
|
|
20
|
+
ANSI_ESCAPE_PATTERN = re.compile(r"\x1B\[[0-9;]*[a-zA-Z]")
|
|
21
|
+
|
|
21
22
|
|
|
22
23
|
# Function to clean ANSI escape codes from text
|
|
23
24
|
def clean_ansi_escapes(text: str) -> str:
|
|
24
25
|
"""Remove ANSI escape sequences from text.
|
|
25
|
-
|
|
26
|
+
|
|
26
27
|
Args:
|
|
27
28
|
text: Text containing ANSI escape sequences
|
|
28
|
-
|
|
29
|
+
|
|
29
30
|
Returns:
|
|
30
31
|
Text with ANSI escape sequences removed
|
|
31
32
|
"""
|
|
32
|
-
return ANSI_ESCAPE_PATTERN.sub(
|
|
33
|
+
return ANSI_ESCAPE_PATTERN.sub("", text)
|
|
33
34
|
|
|
34
35
|
|
|
35
36
|
@final
|
|
36
37
|
class NotebookOutputImage:
|
|
37
38
|
"""Representation of an image output in a notebook cell."""
|
|
38
|
-
|
|
39
|
+
|
|
39
40
|
def __init__(self, image_data: str, media_type: str):
|
|
40
41
|
"""Initialize a notebook output image.
|
|
41
|
-
|
|
42
|
+
|
|
42
43
|
Args:
|
|
43
44
|
image_data: Base64-encoded image data
|
|
44
45
|
media_type: Media type of the image (e.g., "image/png")
|
|
@@ -50,15 +51,15 @@ class NotebookOutputImage:
|
|
|
50
51
|
@final
|
|
51
52
|
class NotebookCellOutput:
|
|
52
53
|
"""Representation of an output from a notebook cell."""
|
|
53
|
-
|
|
54
|
+
|
|
54
55
|
def __init__(
|
|
55
|
-
self,
|
|
56
|
+
self,
|
|
56
57
|
output_type: str,
|
|
57
58
|
text: str | None = None,
|
|
58
|
-
image: NotebookOutputImage | None = None
|
|
59
|
+
image: NotebookOutputImage | None = None,
|
|
59
60
|
):
|
|
60
61
|
"""Initialize a notebook cell output.
|
|
61
|
-
|
|
62
|
+
|
|
62
63
|
Args:
|
|
63
64
|
output_type: Type of output
|
|
64
65
|
text: Text output (if any)
|
|
@@ -72,7 +73,7 @@ class NotebookCellOutput:
|
|
|
72
73
|
@final
|
|
73
74
|
class NotebookCellSource:
|
|
74
75
|
"""Representation of a source cell from a notebook."""
|
|
75
|
-
|
|
76
|
+
|
|
76
77
|
def __init__(
|
|
77
78
|
self,
|
|
78
79
|
cell_index: int,
|
|
@@ -80,10 +81,10 @@ class NotebookCellSource:
|
|
|
80
81
|
source: str,
|
|
81
82
|
language: str,
|
|
82
83
|
execution_count: int | None = None,
|
|
83
|
-
outputs: list[NotebookCellOutput] | None = None
|
|
84
|
+
outputs: list[NotebookCellOutput] | None = None,
|
|
84
85
|
):
|
|
85
86
|
"""Initialize a notebook cell source.
|
|
86
|
-
|
|
87
|
+
|
|
87
88
|
Args:
|
|
88
89
|
cell_index: Index of the cell in the notebook
|
|
89
90
|
cell_type: Type of cell (code or markdown)
|
|
@@ -100,81 +101,87 @@ class NotebookCellSource:
|
|
|
100
101
|
self.outputs = outputs or []
|
|
101
102
|
|
|
102
103
|
|
|
103
|
-
class JupyterBaseTool(FileSystemTool,ABC):
|
|
104
|
+
class JupyterBaseTool(FileSystemTool, ABC):
|
|
104
105
|
"""Base class for Jupyter notebook tools.
|
|
105
|
-
|
|
106
|
+
|
|
106
107
|
Provides common functionality for working with Jupyter notebooks, including
|
|
107
108
|
parsing, cell extraction, and output formatting.
|
|
108
109
|
"""
|
|
109
|
-
|
|
110
|
+
|
|
110
111
|
def create_tool_context(self, ctx: MCPContext) -> ToolContext:
|
|
111
112
|
"""Create a tool context with the tool name.
|
|
112
|
-
|
|
113
|
+
|
|
113
114
|
Args:
|
|
114
115
|
ctx: MCP context
|
|
115
|
-
|
|
116
|
+
|
|
116
117
|
Returns:
|
|
117
118
|
Tool context
|
|
118
119
|
"""
|
|
119
120
|
tool_ctx = create_tool_context(ctx)
|
|
120
121
|
return tool_ctx
|
|
121
|
-
|
|
122
|
+
|
|
122
123
|
def set_tool_context_info(self, tool_ctx: ToolContext) -> None:
|
|
123
124
|
"""Set the tool info on the context.
|
|
124
|
-
|
|
125
|
+
|
|
125
126
|
Args:
|
|
126
127
|
tool_ctx: Tool context
|
|
127
128
|
"""
|
|
128
129
|
tool_ctx.set_tool_info(self.name)
|
|
129
|
-
|
|
130
|
-
async def parse_notebook(
|
|
130
|
+
|
|
131
|
+
async def parse_notebook(
|
|
132
|
+
self, file_path: Path
|
|
133
|
+
) -> tuple[dict[str, Any], list[NotebookCellSource]]:
|
|
131
134
|
"""Parse a Jupyter notebook file.
|
|
132
|
-
|
|
135
|
+
|
|
133
136
|
Args:
|
|
134
137
|
file_path: Path to the notebook file
|
|
135
|
-
|
|
138
|
+
|
|
136
139
|
Returns:
|
|
137
140
|
Tuple of (notebook_data, processed_cells)
|
|
138
141
|
"""
|
|
139
142
|
with open(file_path, "r", encoding="utf-8") as f:
|
|
140
143
|
content = f.read()
|
|
141
144
|
notebook = json.loads(content)
|
|
142
|
-
|
|
145
|
+
|
|
143
146
|
# Get notebook language
|
|
144
|
-
language =
|
|
147
|
+
language = (
|
|
148
|
+
notebook.get("metadata", {}).get("language_info", {}).get("name", "python")
|
|
149
|
+
)
|
|
145
150
|
cells = notebook.get("cells", [])
|
|
146
151
|
processed_cells = []
|
|
147
152
|
|
|
148
153
|
for i, cell in enumerate(cells):
|
|
149
154
|
cell_type = cell.get("cell_type", "code")
|
|
150
|
-
|
|
155
|
+
|
|
151
156
|
# Skip if not code or markdown
|
|
152
157
|
if cell_type not in ["code", "markdown"]:
|
|
153
158
|
continue
|
|
154
|
-
|
|
159
|
+
|
|
155
160
|
# Get source
|
|
156
161
|
source = cell.get("source", "")
|
|
157
162
|
if isinstance(source, list):
|
|
158
163
|
source = "".join(source)
|
|
159
|
-
|
|
164
|
+
|
|
160
165
|
# Get execution count for code cells
|
|
161
166
|
execution_count = None
|
|
162
167
|
if cell_type == "code":
|
|
163
168
|
execution_count = cell.get("execution_count")
|
|
164
|
-
|
|
169
|
+
|
|
165
170
|
# Process outputs for code cells
|
|
166
171
|
outputs = []
|
|
167
172
|
if cell_type == "code" and "outputs" in cell:
|
|
168
173
|
for output in cell["outputs"]:
|
|
169
174
|
output_type = output.get("output_type", "")
|
|
170
|
-
|
|
175
|
+
|
|
171
176
|
# Process different output types
|
|
172
177
|
if output_type == "stream":
|
|
173
178
|
text = output.get("text", "")
|
|
174
179
|
if isinstance(text, list):
|
|
175
180
|
text = "".join(text)
|
|
176
|
-
outputs.append(
|
|
177
|
-
|
|
181
|
+
outputs.append(
|
|
182
|
+
NotebookCellOutput(output_type="stream", text=text)
|
|
183
|
+
)
|
|
184
|
+
|
|
178
185
|
elif output_type in ["execute_result", "display_data"]:
|
|
179
186
|
# Process text output
|
|
180
187
|
text = None
|
|
@@ -184,46 +191,48 @@ class JupyterBaseTool(FileSystemTool,ABC):
|
|
|
184
191
|
text = "".join(text_data)
|
|
185
192
|
else:
|
|
186
193
|
text = text_data
|
|
187
|
-
|
|
194
|
+
|
|
188
195
|
# Process image output
|
|
189
196
|
image = None
|
|
190
197
|
if "data" in output:
|
|
191
198
|
if "image/png" in output["data"]:
|
|
192
199
|
image = NotebookOutputImage(
|
|
193
200
|
image_data=output["data"]["image/png"],
|
|
194
|
-
media_type="image/png"
|
|
201
|
+
media_type="image/png",
|
|
195
202
|
)
|
|
196
203
|
elif "image/jpeg" in output["data"]:
|
|
197
204
|
image = NotebookOutputImage(
|
|
198
205
|
image_data=output["data"]["image/jpeg"],
|
|
199
|
-
media_type="image/jpeg"
|
|
206
|
+
media_type="image/jpeg",
|
|
200
207
|
)
|
|
201
|
-
|
|
208
|
+
|
|
202
209
|
outputs.append(
|
|
203
210
|
NotebookCellOutput(
|
|
204
|
-
output_type=output_type,
|
|
205
|
-
text=text,
|
|
206
|
-
image=image
|
|
211
|
+
output_type=output_type, text=text, image=image
|
|
207
212
|
)
|
|
208
213
|
)
|
|
209
|
-
|
|
214
|
+
|
|
210
215
|
elif output_type == "error":
|
|
211
216
|
# Format error traceback
|
|
212
217
|
ename = output.get("ename", "")
|
|
213
218
|
evalue = output.get("evalue", "")
|
|
214
219
|
traceback = output.get("traceback", [])
|
|
215
|
-
|
|
220
|
+
|
|
216
221
|
# Handle raw text strings and lists of strings
|
|
217
222
|
if isinstance(traceback, list):
|
|
218
223
|
# Clean ANSI escape codes and join the list but preserve the formatting
|
|
219
|
-
clean_traceback = [
|
|
224
|
+
clean_traceback = [
|
|
225
|
+
clean_ansi_escapes(line) for line in traceback
|
|
226
|
+
]
|
|
220
227
|
traceback_text = "\n".join(clean_traceback)
|
|
221
228
|
else:
|
|
222
229
|
traceback_text = clean_ansi_escapes(str(traceback))
|
|
223
|
-
|
|
230
|
+
|
|
224
231
|
error_text = f"{ename}: {evalue}\n{traceback_text}"
|
|
225
|
-
outputs.append(
|
|
226
|
-
|
|
232
|
+
outputs.append(
|
|
233
|
+
NotebookCellOutput(output_type="error", text=error_text)
|
|
234
|
+
)
|
|
235
|
+
|
|
227
236
|
# Create cell object
|
|
228
237
|
processed_cell = NotebookCellSource(
|
|
229
238
|
cell_index=i,
|
|
@@ -231,19 +240,19 @@ class JupyterBaseTool(FileSystemTool,ABC):
|
|
|
231
240
|
source=source,
|
|
232
241
|
language=language,
|
|
233
242
|
execution_count=execution_count,
|
|
234
|
-
outputs=outputs
|
|
243
|
+
outputs=outputs,
|
|
235
244
|
)
|
|
236
|
-
|
|
245
|
+
|
|
237
246
|
processed_cells.append(processed_cell)
|
|
238
|
-
|
|
247
|
+
|
|
239
248
|
return notebook, processed_cells
|
|
240
|
-
|
|
249
|
+
|
|
241
250
|
def format_notebook_cells(self, cells: list[NotebookCellSource]) -> str:
|
|
242
251
|
"""Format notebook cells as a readable string.
|
|
243
|
-
|
|
252
|
+
|
|
244
253
|
Args:
|
|
245
254
|
cells: List of processed notebook cells
|
|
246
|
-
|
|
255
|
+
|
|
247
256
|
Returns:
|
|
248
257
|
Formatted string representation of the cells
|
|
249
258
|
"""
|
|
@@ -255,13 +264,13 @@ class JupyterBaseTool(FileSystemTool,ABC):
|
|
|
255
264
|
cell_header += f" (execution_count: {cell.execution_count})"
|
|
256
265
|
if cell.cell_type == "code" and cell.language != "python":
|
|
257
266
|
cell_header += f" [{cell.language}]"
|
|
258
|
-
|
|
267
|
+
|
|
259
268
|
# Add cell to result
|
|
260
269
|
result.append(f"{cell_header}:")
|
|
261
270
|
result.append(f"```{cell.language if cell.cell_type == 'code' else ''}")
|
|
262
271
|
result.append(cell.source)
|
|
263
272
|
result.append("```")
|
|
264
|
-
|
|
273
|
+
|
|
265
274
|
# Add outputs if any
|
|
266
275
|
if cell.outputs:
|
|
267
276
|
result.append("Outputs:")
|
|
@@ -278,7 +287,7 @@ class JupyterBaseTool(FileSystemTool,ABC):
|
|
|
278
287
|
result.append("```")
|
|
279
288
|
if output.image:
|
|
280
289
|
result.append(f"[Image output: {output.image.media_type}]")
|
|
281
|
-
|
|
290
|
+
|
|
282
291
|
result.append("") # Empty line between cells
|
|
283
|
-
|
|
292
|
+
|
|
284
293
|
return "\n".join(result)
|