Update claude_agent_pipe.py
Add OpenWebUI Tools/MCP passthrough, fix multi-arity bug, bump to 0.2
- Fix _build_kb_mcp_server: the no-knowledge branch returned a 2-tuple
(None, []) while every call site unpacked 3 values, raising
ValueError: not enough values to unpack (expected 3, got 2) on any
turn without an attached knowledge base. Now consistently returns
(None, [], {}).
- Add __tools__ passthrough: wrap OpenWebUI's Tools / external tool
servers (incl. MCP via mcpo) attached to the Workspace Model as an
in-process MCP server ("owui-tools"), merged alongside the existing
"helm-kb" server. Lets Claude Code use whatever tools/connectors are
configured in OpenWebUI natively, without hardcoding a server URL in
the pipe — stays in sync if the attached tools change later.
New: _JSON_SCHEMA_TYPE_MAP, _build_owui_tools_mcp_server().
Known limitation: OpenWebUI's built-in tools (web_search,
image_generation, execute_code) aren't included in __tools__ yet
(upstream limitation); only user-defined Tools and external/MCP
tool servers are. Not an issue here since Claude Code already ships
its own WebSearch/WebFetch.
- version: 0.1 -> 0.2, contributors: Speedliner (author unchanged)
This commit is contained in:
parent
5bbc1fcdf9
commit
1cb590db23
|
|
@ -2,7 +2,8 @@
|
|||
title: Claude Code
|
||||
description: Run Claude Code's agent loop from inside OpenWebUI chats via the Claude Agent SDK.
|
||||
author: Thomas Friedel
|
||||
version: 0.1
|
||||
contributors: Speedliner
|
||||
version: 0.2
|
||||
license: MIT
|
||||
requirements: claude-agent-sdk>=0.1.60, anthropic>=0.40.0
|
||||
"""
|
||||
|
|
@ -379,7 +380,7 @@ def _build_kb_mcp_server(
|
|||
that OpenWebUI's middleware already filtered by the user's grants.
|
||||
"""
|
||||
if not knowledge:
|
||||
return None, []
|
||||
return None, [], {}
|
||||
|
||||
collection_names = [k["id"] for k in knowledge]
|
||||
display = ", ".join(k["name"] for k in knowledge)
|
||||
|
|
@ -737,6 +738,82 @@ def _build_kb_mcp_server(
|
|||
return server, tool_names, tools_by_name
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# OpenWebUI Tools/MCP passthrough — wraps whatever Tools or external tool
|
||||
# servers (incl. MCP, via mcpo) are attached to the Workspace Model as an
|
||||
# in-process MCP server, so Claude Code gets the same toolbox OpenWebUI's
|
||||
# native tool-calling would use. Whatever's attached in
|
||||
# Workspace -> Models -> Tools shows up here automatically at request time —
|
||||
# no hardcoded server URL/config needed, so it stays in sync if the
|
||||
# attached tools/connections change later.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
_JSON_SCHEMA_TYPE_MAP = {
|
||||
"string": str,
|
||||
"integer": int,
|
||||
"number": float,
|
||||
"boolean": bool,
|
||||
"array": list,
|
||||
"object": dict,
|
||||
}
|
||||
|
||||
|
||||
def _build_owui_tools_mcp_server(tools: Optional[Dict[str, Any]]):
|
||||
"""Wrap OpenWebUI's __tools__ (Tools + external/MCP tool servers attached
|
||||
to the Workspace Model) as an in-process MCP server. Each entry already
|
||||
carries an OpenAI-style JSON-schema `spec` and an async-wrapped
|
||||
`callable` — OpenWebUI has already injected any special
|
||||
__user__/__event_emitter__ params via apply_extra_params_to_tool_function,
|
||||
so we just need to call it.
|
||||
|
||||
Note: OpenWebUI's built-in tools (web_search, image_generation,
|
||||
execute_code) are currently NOT included in __tools__ — only
|
||||
user-defined Tools and external/MCP tool servers are. Claude Code
|
||||
already ships its own WebSearch/WebFetch tools, so this mainly matters
|
||||
for custom/internal tool servers.
|
||||
"""
|
||||
if not tools:
|
||||
return None, []
|
||||
|
||||
sdk_tools = []
|
||||
tool_names: List[str] = []
|
||||
for name, entry in tools.items():
|
||||
spec = entry.get("spec") or {}
|
||||
params = spec.get("parameters", {}).get("properties", {}) or {}
|
||||
# Flat type mapping — covers the vast majority of OpenWebUI tool
|
||||
# specs. Nested/array-item schemas aren't modeled; extend here if a
|
||||
# specific tool needs richer typing.
|
||||
input_schema = {
|
||||
pname: _JSON_SCHEMA_TYPE_MAP.get(pinfo.get("type"), str)
|
||||
for pname, pinfo in params.items()
|
||||
}
|
||||
callable_fn = entry.get("callable")
|
||||
description = spec.get("description") or f"OpenWebUI tool: {name}"
|
||||
|
||||
def _make_handler(fn: Callable) -> Callable:
|
||||
async def _handler(args: Dict[str, Any]) -> Dict[str, Any]:
|
||||
try:
|
||||
result = fn(**(args or {}))
|
||||
if asyncio.iscoroutine(result):
|
||||
result = await result
|
||||
except Exception as exc:
|
||||
log.exception("OpenWebUI tool failed")
|
||||
return {
|
||||
"content": [{"type": "text", "text": f"Tool failed: {exc}"}]
|
||||
}
|
||||
return {"content": [{"type": "text", "text": str(result)}]}
|
||||
|
||||
return _handler
|
||||
|
||||
sdk_tools.append(
|
||||
tool(name, description, input_schema)(_make_handler(callable_fn))
|
||||
)
|
||||
tool_names.append(f"mcp__owui-tools__{name}")
|
||||
|
||||
server = create_sdk_mcp_server("owui-tools", "0.1", tools=sdk_tools)
|
||||
return server, tool_names
|
||||
|
||||
|
||||
def _anthropic_kb_tool_defs(
|
||||
knowledge: List[Dict[str, str]], has_kb_ids: bool
|
||||
) -> List[Dict[str, Any]]:
|
||||
|
|
@ -1124,9 +1201,7 @@ class Pipe:
|
|||
final = await stream.get_final_message()
|
||||
except Exception as exc:
|
||||
log.exception("Fast path failed")
|
||||
yield (
|
||||
f"\n\n**Fast-path error:** `{type(exc).__name__}: {exc}`\n"
|
||||
)
|
||||
yield (f"\n\n**Fast-path error:** `{type(exc).__name__}: {exc}`\n")
|
||||
return
|
||||
|
||||
if final.stop_reason != "tool_use":
|
||||
|
|
@ -1370,6 +1445,7 @@ class Pipe:
|
|||
__files__: Optional[List[Dict[str, Any]]] = None,
|
||||
__user__: Optional[Dict[str, Any]] = None,
|
||||
__metadata__: Optional[Dict[str, Any]] = None,
|
||||
__tools__: Optional[Dict[str, Any]] = None,
|
||||
) -> AsyncGenerator[str, None]:
|
||||
# Auth selection:
|
||||
# 1. If CLAUDE_CODE_OAUTH_TOKEN valve is set → use subscription.
|
||||
|
|
@ -1415,7 +1491,12 @@ class Pipe:
|
|||
user_dict=__user__,
|
||||
event_emitter=__event_emitter__,
|
||||
)
|
||||
allowed_tools = allowed_tools + kb_tool_names
|
||||
|
||||
# OpenWebUI Tools / external tool servers (incl. MCP via mcpo)
|
||||
# attached to the Workspace Model → same passthrough treatment.
|
||||
owui_server, owui_tool_names = _build_owui_tools_mcp_server(__tools__)
|
||||
|
||||
allowed_tools = allowed_tools + kb_tool_names + owui_tool_names
|
||||
|
||||
options_kwargs: Dict[str, Any] = {
|
||||
"cwd": str(workdir),
|
||||
|
|
@ -1435,8 +1516,14 @@ class Pipe:
|
|||
options_kwargs["resume"] = resume_id
|
||||
if self.valves.MAX_TURNS:
|
||||
options_kwargs["max_turns"] = self.valves.MAX_TURNS
|
||||
|
||||
mcp_servers: Dict[str, Any] = {}
|
||||
if kb_server is not None:
|
||||
options_kwargs["mcp_servers"] = {"helm-kb": kb_server}
|
||||
mcp_servers["helm-kb"] = kb_server
|
||||
if owui_server is not None:
|
||||
mcp_servers["owui-tools"] = owui_server
|
||||
if mcp_servers:
|
||||
options_kwargs["mcp_servers"] = mcp_servers
|
||||
|
||||
# Extend Claude Code's default agent-loop system prompt with whatever
|
||||
# the Workspace Model configured. `append` keeps the agentic prompt
|
||||
|
|
|
|||
Loading…
Reference in New Issue