ajustes de mcp
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -26,3 +26,4 @@ Thumbs.db
|
|||||||
.idea/
|
.idea/
|
||||||
*.swp
|
*.swp
|
||||||
*.swo
|
*.swo
|
||||||
|
keepsailing.es/
|
||||||
12
mcp.json
12
mcp.json
@@ -6,6 +6,18 @@
|
|||||||
"env": {},
|
"env": {},
|
||||||
"timeout": 30,
|
"timeout": 30,
|
||||||
"startup_timeout": 10
|
"startup_timeout": 10
|
||||||
|
},
|
||||||
|
"playwright": {
|
||||||
|
"command": "npx",
|
||||||
|
"args": ["@playwright/mcp", "--headless"],
|
||||||
|
"timeout": 30,
|
||||||
|
"startup_timeout": 15
|
||||||
|
},
|
||||||
|
"fetch": {
|
||||||
|
"command": "uvx",
|
||||||
|
"args": ["mcp-server-fetch"],
|
||||||
|
"timeout": 30,
|
||||||
|
"startup_timeout": 15
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,17 +3,21 @@
|
|||||||
"acai-code": {
|
"acai-code": {
|
||||||
"command": "node",
|
"command": "node",
|
||||||
"args": ["mcp-server/stdio.js"],
|
"args": ["mcp-server/stdio.js"],
|
||||||
"env": {
|
"env": {},
|
||||||
"ACAI_WEB_URL": "http://localhost:8080",
|
|
||||||
"ACAI_WEBSITE": "mi-sitio",
|
|
||||||
"ACAI_PROJECT_DIR": "/ruta/al/proyecto"
|
|
||||||
},
|
|
||||||
"timeout": 30,
|
"timeout": 30,
|
||||||
"startup_timeout": 10
|
"startup_timeout": 10
|
||||||
},
|
},
|
||||||
"filesystem": {
|
"playwright": {
|
||||||
"command": "npx",
|
"command": "npx",
|
||||||
"args": ["@modelcontextprotocol/server-filesystem", "/tmp"]
|
"args": ["@playwright/mcp", "--headless"],
|
||||||
|
"timeout": 30,
|
||||||
|
"startup_timeout": 15
|
||||||
|
},
|
||||||
|
"fetch": {
|
||||||
|
"command": "uvx",
|
||||||
|
"args": ["mcp-server-fetch"],
|
||||||
|
"timeout": 30,
|
||||||
|
"startup_timeout": 15
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -214,26 +214,47 @@ class MCPManager:
|
|||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
def _namespace(self, server_name: str, tool_name: str) -> str:
|
def _namespace(self, server_name: str, tool_name: str) -> str:
|
||||||
"""Build namespaced tool name. Skip prefix in single-server mode."""
|
"""Build namespaced tool name. Skip prefix in single-server mode.
|
||||||
|
|
||||||
|
Uses '__' as separator because OpenAI requires tool names to
|
||||||
|
match ^[a-zA-Z0-9_-]+$ (no dots allowed).
|
||||||
|
"""
|
||||||
if self._single_server_mode:
|
if self._single_server_mode:
|
||||||
return tool_name
|
return tool_name
|
||||||
return f"{server_name}.{tool_name}"
|
safe_server = server_name.replace("-", "_")
|
||||||
|
safe_tool = tool_name.replace("-", "_")
|
||||||
|
return f"{safe_server}__{safe_tool}"
|
||||||
|
|
||||||
def _resolve_tool(self, namespaced_name: str) -> tuple[str, str]:
|
def _resolve_tool(self, namespaced_name: str) -> tuple[str, str]:
|
||||||
"""Resolve a namespaced tool name to (server_name, raw_tool_name)."""
|
"""Resolve a namespaced tool name to (server_name, raw_tool_name)."""
|
||||||
# Direct lookup in index
|
# Direct lookup in index
|
||||||
if namespaced_name in self._tool_index:
|
if namespaced_name in self._tool_index:
|
||||||
server_name = self._tool_index[namespaced_name]
|
server_name = self._tool_index[namespaced_name]
|
||||||
raw_name = namespaced_name
|
# Reverse the namespace to get original tool name
|
||||||
if not self._single_server_mode and "." in namespaced_name:
|
if not self._single_server_mode:
|
||||||
raw_name = namespaced_name.split(".", 1)[1]
|
safe_server = server_name.replace("-", "_")
|
||||||
return server_name, raw_name
|
prefix = safe_server + "__"
|
||||||
|
if namespaced_name.startswith(prefix):
|
||||||
|
# Find original tool name by matching against client's tools
|
||||||
|
suffix = namespaced_name[len(prefix):]
|
||||||
|
for original_name in self._clients[server_name].tools:
|
||||||
|
if original_name.replace("-", "_") == suffix:
|
||||||
|
return server_name, original_name
|
||||||
|
# Fallback: try suffix directly
|
||||||
|
return server_name, suffix
|
||||||
|
return server_name, namespaced_name
|
||||||
|
|
||||||
# Try splitting on first dot
|
# Try splitting on '__'
|
||||||
if "." in namespaced_name:
|
if "__" in namespaced_name:
|
||||||
server_name, raw_name = namespaced_name.split(".", 1)
|
parts = namespaced_name.split("__", 1)
|
||||||
if server_name in self._clients:
|
prefix, suffix = parts
|
||||||
return server_name, raw_name
|
for server_name in self._clients:
|
||||||
|
if server_name.replace("-", "_") == prefix:
|
||||||
|
# Find original tool name
|
||||||
|
for original_name in self._clients[server_name].tools:
|
||||||
|
if original_name.replace("-", "_") == suffix:
|
||||||
|
return server_name, original_name
|
||||||
|
return server_name, suffix
|
||||||
|
|
||||||
# Fallback: search all servers for the bare name
|
# Fallback: search all servers for the bare name
|
||||||
for server_name, client in self._clients.items():
|
for server_name, client in self._clients.items():
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ class BaseAgent:
|
|||||||
|
|
||||||
full_text = ""
|
full_text = ""
|
||||||
tool_calls: list[dict[str, Any]] = []
|
tool_calls: list[dict[str, Any]] = []
|
||||||
current_tool: dict[str, Any] = {}
|
current_tool: dict[str, Any] | None = None
|
||||||
|
|
||||||
async for chunk in self.model.stream(
|
async for chunk in self.model.stream(
|
||||||
messages=ctx.to_messages(),
|
messages=ctx.to_messages(),
|
||||||
@@ -96,7 +96,7 @@ class BaseAgent:
|
|||||||
session_id=session.session_id,
|
session_id=session.session_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
if chunk.tool_name and not current_tool.get("name"):
|
if chunk.tool_name and (current_tool is None or not current_tool.get("name")):
|
||||||
current_tool = {
|
current_tool = {
|
||||||
"id": chunk.tool_call_id,
|
"id": chunk.tool_call_id,
|
||||||
"name": chunk.tool_name,
|
"name": chunk.tool_name,
|
||||||
@@ -108,18 +108,23 @@ class BaseAgent:
|
|||||||
session_id=session.session_id,
|
session_id=session.session_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
if chunk.tool_arguments and current_tool:
|
if chunk.tool_arguments and current_tool is not None and not chunk.finish_reason:
|
||||||
|
# Accumulate partial argument chunks (NOT the final one)
|
||||||
current_tool["arguments"] += chunk.tool_arguments
|
current_tool["arguments"] += chunk.tool_arguments
|
||||||
|
|
||||||
if chunk.finish_reason == "tool_use" and current_tool.get("name"):
|
if chunk.finish_reason == "tool_use" and current_tool is not None and current_tool.get("name"):
|
||||||
# Parse arguments
|
# Final chunk carries complete arguments — use those if
|
||||||
|
# partial accumulation is empty, otherwise use accumulated
|
||||||
|
final_args = current_tool["arguments"] or chunk.tool_arguments or ""
|
||||||
try:
|
try:
|
||||||
args = json.loads(current_tool["arguments"]) if current_tool["arguments"] else {}
|
args = json.loads(final_args) if final_args else {}
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
|
logger.warning("Failed to parse tool args: %s", final_args[:200])
|
||||||
args = {}
|
args = {}
|
||||||
current_tool["parsed_arguments"] = args
|
current_tool["parsed_arguments"] = args
|
||||||
|
logger.debug("Tool call finalized: %s args=%s", current_tool["name"], json.dumps(args)[:200])
|
||||||
tool_calls.append(current_tool)
|
tool_calls.append(current_tool)
|
||||||
current_tool = {}
|
current_tool = None
|
||||||
|
|
||||||
if chunk.finish_reason == "end_turn":
|
if chunk.finish_reason == "end_turn":
|
||||||
break
|
break
|
||||||
@@ -168,6 +173,8 @@ class BaseAgent:
|
|||||||
status=ToolExecutionStatus.RUNNING,
|
status=ToolExecutionStatus.RUNNING,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
logger.info("Tool call: %s(%s)", tool_name, json.dumps(arguments)[:200])
|
||||||
|
|
||||||
start = time.monotonic()
|
start = time.monotonic()
|
||||||
try:
|
try:
|
||||||
if self.mcp.is_running and tool_name in self.mcp.tools:
|
if self.mcp.is_running and tool_name in self.mcp.tools:
|
||||||
|
|||||||
Reference in New Issue
Block a user