npcsh 1.0.16__py3-none-any.whl → 1.0.18__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.
npcsh/mcp_server.py CHANGED
@@ -14,7 +14,6 @@ from typing import Optional, Dict, Any, List, Union, Callable
14
14
  from mcp.server.fastmcp import FastMCP
15
15
  import importlib
16
16
  # npcpy imports
17
- from npcpy.gen.response import get_litellm_response
18
17
 
19
18
 
20
19
  import os
@@ -28,15 +27,27 @@ except:
28
27
  from typing import Optional, Dict, Any, List, Union, Callable, get_type_hints
29
28
  # Add these imports to the top of your file
30
29
  from functools import wraps
30
+ import sys
31
+
32
+ from npcpy.llm_funcs import generate_group_candidates, abstract, extract_facts, zoom_in, execute_llm_command, gen_image
33
+ from npcpy.memory.search import search_similar_texts, execute_search_command, execute_rag_command, answer_with_rag, execute_brainblast_command
34
+ from npcpy.data.load import load_file_contents
35
+ from npcpy.memory.command_history import CommandHistory
36
+ from npcpy.data.image import capture_screenshot
37
+ from npcpy.data.web import search_web
38
+
39
+ from npcsh._state import NPCSH_DB_PATH
40
+
41
+ command_history = CommandHistory(db=NPCSH_DB_PATH)
42
+
31
43
  # Initialize the MCP server
32
- mcp = FastMCP("npcpy_enhanced")
44
+ mcp = FastMCP("npcsh_mcp")
33
45
 
34
46
  # Define the default workspace
35
47
  DEFAULT_WORKSPACE = os.path.join(os.getcwd(), "workspace")
36
48
  os.makedirs(DEFAULT_WORKSPACE, exist_ok=True)
37
49
 
38
50
  # ==================== SYSTEM TOOLS ====================
39
-
40
51
  @mcp.tool()
41
52
  async def run_server_command(command: str) -> str:
42
53
  """
@@ -54,45 +65,44 @@ async def run_server_command(command: str) -> str:
54
65
  cwd=DEFAULT_WORKSPACE,
55
66
  shell=True,
56
67
  capture_output=True,
57
- text=True
68
+ text=True,
69
+ timeout=30 # Add timeout to prevent hanging
58
70
  )
59
- return result.stdout or result.stderr
71
+ return result.stdout or result.stderr or "Command completed with no output"
72
+ except subprocess.TimeoutExpired:
73
+ return "Command timed out after 30 seconds"
60
74
  except Exception as e:
61
75
  return str(e)
76
+
77
+
78
+
62
79
  def make_async_wrapper(func: Callable) -> Callable:
63
- """Create an async wrapper for sync functions that fixes schema validation issues."""
80
+ """Create an async wrapper for sync functions."""
64
81
 
65
82
  @wraps(func)
66
- async def async_wrapper(*args, **kwargs):
67
- # Direct parameter dict case (most common failure)
68
- if len(args) == 1 and isinstance(args[0], dict):
69
- params = args[0]
70
-
71
- # Fix for search_web - add required kwargs parameter
72
- if "kwargs" not in params:
73
- # Create a new dict with the kwargs parameter added
74
- params = {**params, "kwargs": ""}
75
-
76
- # Call the function with the parameters
77
- if asyncio.iscoroutinefunction(func):
78
- return await func(**params)
79
- else:
80
- return await asyncio.to_thread(func, **params)
83
+ async def async_wrapper(**kwargs):
84
+ func_name = func.__name__
85
+ print(f"MCP SERVER DEBUG: {func_name} called with kwargs={kwargs}", flush=True)
81
86
 
82
- # Normal function call or other cases
83
- if asyncio.iscoroutinefunction(func):
84
- return await func(*args, **kwargs)
85
- else:
86
- return await asyncio.to_thread(func, *args, **kwargs)
87
+ try:
88
+ result = func(**kwargs)
89
+ print(f"MCP SERVER DEBUG: {func_name} returned type={type(result)}, result={result[:500] if isinstance(result, str) else result}", flush=True)
90
+ return result
91
+
92
+ except Exception as e:
93
+ print(f"MCP SERVER DEBUG: {func_name} exception: {e}", flush=True)
94
+ import traceback
95
+ traceback.print_exc()
96
+ return f"Error in {func_name}: {e}"
87
97
 
88
- # Preserve function metadata
89
98
  async_wrapper.__name__ = func.__name__
90
99
  async_wrapper.__doc__ = func.__doc__
91
100
  async_wrapper.__annotations__ = func.__annotations__
92
101
 
93
102
  return async_wrapper
94
103
 
95
- # Update your register_module_tools function to use this improved wrapper
104
+
105
+
96
106
  def register_module_tools(module_name: str) -> None:
97
107
  """
98
108
  Register all suitable functions from a module as MCP tools with improved argument handling.
@@ -133,45 +143,39 @@ def load_module_functions(module_name: str) -> List[Callable]:
133
143
 
134
144
  print("Loading tools from npcpy modules...")
135
145
 
136
- # Load modules from npcpy.routes
137
- try:
138
- from npcpy.routes import routes
139
- for route_name, route_func in routes.items():
140
- if callable(route_func):
141
- async_func = make_async_wrapper(route_func)
142
- try:
143
- mcp.tool()(async_func)
144
- print(f"Registered route: {route_name}")
145
- except Exception as e:
146
- print(f"Failed to register route {route_name}: {e}")
147
- except ImportError as e:
148
- print(f"Warning: Could not import routes: {e}")
149
146
 
150
147
 
151
- # Load npc_compiler functions
152
- print("Loading functions from npcpy.npc_compiler...")
153
- try:
154
- import importlib.util
155
- if importlib.util.find_spec("npcpy.npc_compiler"):
156
- register_module_tools("npcpy.npc_compiler")
157
- except ImportError:
158
- print("npcpy.npc_compiler not found, skipping...")
159
148
 
160
- # Load npc_sysenv functions
161
- #print("Loading functions from npcpy.npc_sysenv...")
162
- #register_module_tools("npcpy.npc_sysenv")
163
- register_module_tools("npcpy.memory.search")
164
149
 
165
- register_module_tools("npcpy.work.plan")
166
- register_module_tools("npcpy.work.trigger")
167
- register_module_tools("npcpy.work.desktop")
150
+ def register_selected_npcpy_tools():
151
+ tools = [generate_group_candidates,
152
+ abstract,
153
+ extract_facts,
154
+ zoom_in,
155
+ execute_llm_command,
156
+ gen_image,
157
+ load_file_contents,
158
+ capture_screenshot,
159
+ search_web, ]
168
160
 
169
- #print("Loading functions from npcpy.command_history...")
170
- #register_module_tools("npcpy.memory.command_history")
161
+ for func in tools:
162
+ # Ensure a docstring exists for schema generation
163
+ if not (getattr(func, "__doc__", None) and func.__doc__.strip()):
164
+ fallback_doc = f"Tool wrapper for {func.__name__}."
165
+ try:
166
+ func.__doc__ = fallback_doc
167
+ except Exception:
168
+ pass # Some builtins may not allow setting __doc__
169
+
170
+ try:
171
+ async_func = make_async_wrapper(func)
172
+ mcp.tool()(async_func)
173
+ print(f"Registered npcpy tool: {func.__name__}")
174
+ except Exception as e:
175
+ print(f"Failed to register npcpy tool {func.__name__}: {e}")
176
+ register_selected_npcpy_tools()
171
177
 
172
178
 
173
- #print("Loading functions from npcpy.npc_sysenv...")
174
- #register_module_tools("npcpy.llm_funcs")
175
179
 
176
180
 
177
181
  # ==================== MAIN ENTRY POINT ====================
npcsh/npc.py CHANGED
@@ -22,11 +22,10 @@ from npcpy.llm_funcs import check_llm_command
22
22
  from sqlalchemy import create_engine
23
23
 
24
24
  # Import the key functions from npcsh
25
- from npcsh.npcsh import (
25
+ from npcsh._state import (
26
26
  setup_shell,
27
27
  execute_slash_command,
28
28
  execute_command,
29
- process_pipeline_command,
30
29
  )
31
30
 
32
31
  def load_npc_by_name(npc_name: str = "sibiji", db_path: str = NPCSH_DB_PATH) -> Optional[NPC]:
@@ -60,6 +59,8 @@ def load_npc_by_name(npc_name: str = "sibiji", db_path: str = NPCSH_DB_PATH) ->
60
59
  return None
61
60
 
62
61
  def main():
62
+ from npcsh.routes import router
63
+
63
64
  parser = argparse.ArgumentParser(
64
65
  description="NPC Command Line Utilities. Call a command or provide a prompt for the default NPC.",
65
66
  usage="npc <command> [command_args...] | <prompt> [--npc NAME] [--model MODEL] [--provider PROV]"
@@ -173,7 +174,6 @@ def main():
173
174
  npc_instance.model = effective_model
174
175
  if args.provider:
175
176
  npc_instance.provider = effective_provider
176
-
177
177
  try:
178
178
  if is_valid_command:
179
179
  # Handle slash command using npcsh's execute_slash_command
@@ -187,7 +187,8 @@ def main():
187
187
  full_command_str,
188
188
  stdin_input=None,
189
189
  state=shell_state,
190
- stream=NPCSH_STREAM_OUTPUT
190
+ stream=NPCSH_STREAM_OUTPUT,
191
+ router = router
191
192
  )
192
193
 
193
194
  # Process and display the result