Skip to content

Commit bb1325d

Browse files
Copilotgarland3
andauthored
Add expandable tool details showing descriptions and input schemas (#48)
* Initial plan * Add tool descriptions and input schemas to backend API and frontend UI Co-authored-by: garland3 <[email protected]> * Add test for tool details in config API Co-authored-by: garland3 <[email protected]> * Address code review feedback: extract constants and improve documentation Co-authored-by: garland3 <[email protected]> * Remove dropdown expand/collapse section for individual tools Co-authored-by: garland3 <[email protected]> --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: garland3 <[email protected]>
1 parent 00386c6 commit bb1325d

File tree

3 files changed

+231
-129
lines changed

3 files changed

+231
-129
lines changed

backend/routes/config_routes.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,15 @@
1313

1414
router = APIRouter(prefix="/api", tags=["config"])
1515

16+
# Canvas tool description constant
17+
CANVAS_TOOL_DESCRIPTION = (
18+
"Display final rendered content in a visual canvas panel. "
19+
"Use this for: 1) Complete code (not code discussions), "
20+
"2) Final reports/documents (not report discussions), "
21+
"3) Data visualizations, 4) Any polished content that should be "
22+
"viewed separately from the conversation."
23+
)
24+
1625

1726
@router.get("/banners")
1827
async def get_banners(current_user: str = Depends(get_current_user)):
@@ -126,6 +135,20 @@ async def get_config(
126135
tools_info.append({
127136
'server': 'canvas',
128137
'tools': ['canvas'],
138+
'tools_detailed': [{
139+
'name': 'canvas',
140+
'description': CANVAS_TOOL_DESCRIPTION,
141+
'inputSchema': {
142+
'type': 'object',
143+
'properties': {
144+
'content': {
145+
'type': 'string',
146+
'description': 'The content to display in the canvas. Can be markdown, code, or plain text.'
147+
}
148+
},
149+
'required': ['content']
150+
}
151+
}],
129152
'tool_count': 1,
130153
'description': 'Canvas for showing final rendered content: complete code, reports, and polished documents. Use this to finalize your work. Most code and reports will be shown here.',
131154
'is_exclusive': False,
@@ -140,9 +163,20 @@ async def get_config(
140163

141164
# Only include servers that have tools and user has access to
142165
if server_tools: # Only show servers with actual tools
166+
# Build detailed tool information including descriptions and input schemas
167+
tools_detailed = []
168+
for tool in server_tools:
169+
tool_detail = {
170+
'name': tool.name,
171+
'description': tool.description or '',
172+
'inputSchema': getattr(tool, 'inputSchema', {}) or {}
173+
}
174+
tools_detailed.append(tool_detail)
175+
143176
tools_info.append({
144177
'server': server_name,
145178
'tools': [tool.name for tool in server_tools],
179+
'tools_detailed': tools_detailed,
146180
'tool_count': len(server_tools),
147181
'description': server_config.get('description', f'{server_name} tools'),
148182
'is_exclusive': server_config.get('is_exclusive', False),
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
"""Test that tool details (description and inputSchema) are included in config API response."""
2+
3+
import pytest
4+
import sys
5+
import os
6+
7+
# Ensure backend is on path
8+
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
9+
10+
from modules.mcp_tools.client import MCPToolManager
11+
12+
13+
class FakeTool:
14+
"""Mock tool object for testing."""
15+
def __init__(self, name, description="", inputSchema=None):
16+
self.name = name
17+
self.description = description
18+
self.inputSchema = inputSchema or {"type": "object", "properties": {}}
19+
20+
21+
@pytest.fixture
22+
def mock_mcp_manager(monkeypatch):
23+
"""Create a mock MCP manager with test data."""
24+
manager = MCPToolManager()
25+
26+
# Mock available_tools with detailed tool information
27+
manager.available_tools = {
28+
"test_server": {
29+
"tools": [
30+
FakeTool(
31+
"test_tool",
32+
"This is a test tool description",
33+
{
34+
"type": "object",
35+
"properties": {
36+
"arg1": {
37+
"type": "string",
38+
"description": "First argument"
39+
},
40+
"arg2": {
41+
"type": "number",
42+
"description": "Second argument"
43+
}
44+
},
45+
"required": ["arg1"]
46+
}
47+
)
48+
],
49+
"config": {
50+
"description": "Test server",
51+
"is_exclusive": False,
52+
"author": "Test Author"
53+
}
54+
}
55+
}
56+
57+
manager.available_prompts = {}
58+
return manager
59+
60+
61+
def test_tools_detailed_includes_description_and_schema(mock_mcp_manager):
62+
"""Test that tools_detailed field contains description and inputSchema."""
63+
server_tools = mock_mcp_manager.available_tools["test_server"]["tools"]
64+
server_config = mock_mcp_manager.available_tools["test_server"]["config"]
65+
66+
# Simulate what the config endpoint does
67+
tools_detailed = []
68+
for tool in server_tools:
69+
tool_detail = {
70+
'name': tool.name,
71+
'description': tool.description or '',
72+
'inputSchema': getattr(tool, 'inputSchema', {}) or {}
73+
}
74+
tools_detailed.append(tool_detail)
75+
76+
# Verify the structure
77+
assert len(tools_detailed) == 1
78+
assert tools_detailed[0]['name'] == 'test_tool'
79+
assert tools_detailed[0]['description'] == 'This is a test tool description'
80+
assert 'inputSchema' in tools_detailed[0]
81+
assert 'properties' in tools_detailed[0]['inputSchema']
82+
assert 'arg1' in tools_detailed[0]['inputSchema']['properties']
83+
assert tools_detailed[0]['inputSchema']['properties']['arg1']['type'] == 'string'
84+
assert tools_detailed[0]['inputSchema']['properties']['arg1']['description'] == 'First argument'
85+
86+
87+
def test_canvas_tool_has_detailed_info():
88+
"""Test that canvas pseudo-tool has detailed information."""
89+
canvas_tools_detailed = [{
90+
'name': 'canvas',
91+
'description': 'Display final rendered content in a visual canvas panel. Use this for: 1) Complete code (not code discussions), 2) Final reports/documents (not report discussions), 3) Data visualizations, 4) Any polished content that should be viewed separately from the conversation.',
92+
'inputSchema': {
93+
'type': 'object',
94+
'properties': {
95+
'content': {
96+
'type': 'string',
97+
'description': 'The content to display in the canvas. Can be markdown, code, or plain text.'
98+
}
99+
},
100+
'required': ['content']
101+
}
102+
}]
103+
104+
# Verify canvas tool structure
105+
assert len(canvas_tools_detailed) == 1
106+
assert canvas_tools_detailed[0]['name'] == 'canvas'
107+
assert 'description' in canvas_tools_detailed[0]
108+
assert len(canvas_tools_detailed[0]['description']) > 0
109+
assert 'inputSchema' in canvas_tools_detailed[0]
110+
assert 'content' in canvas_tools_detailed[0]['inputSchema']['properties']

0 commit comments

Comments
 (0)