akseljoonas HF Staff commited on
Commit
7291bab
·
1 Parent(s): eb92351

moved config file + small fixes

Browse files
agent/config_claude_mcp.json DELETED
@@ -1,11 +0,0 @@
1
- {
2
- "mcpServers": {
3
- "huggingface": {
4
- "type": "http",
5
- "url": "https://huggingface.co/mcp",
6
- "headers": {
7
- "Authorization": "Bearer ${HF_TOKEN}"
8
- }
9
- }
10
- }
11
- }
 
 
 
 
 
 
 
 
 
 
 
 
agent/config_mcp_example copy.json DELETED
@@ -1,19 +0,0 @@
1
- {
2
- "model_name": "anthropic/claude-sonnet-4-5-20250929",
3
- "mcpServers": {
4
- "hf-mcp-server": {
5
- "transport": "http",
6
- "url": "https://huggingface.co/mcp?login",
7
- "headers": {
8
- "Authorization": "Bearer ${HF_TOKEN}"
9
- }
10
- },
11
- "playwright": {
12
- "transport": "stdio",
13
- "command": "npx",
14
- "args": [
15
- "@playwright/mcp@latest"
16
- ]
17
- }
18
- }
19
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
agent/main.py CHANGED
@@ -193,7 +193,7 @@ async def main():
193
  ready_event = asyncio.Event()
194
 
195
  # Start agent loop in background
196
- config_path = Path(__file__).parent / "config_mcp_example.json"
197
  config = load_config(config_path)
198
 
199
  # Create tool router
 
193
  ready_event = asyncio.Event()
194
 
195
  # Start agent loop in background
196
+ config_path = Path(__file__).parent.parent / "configs" / "main_agent_config.json"
197
  config = load_config(config_path)
198
 
199
  # Create tool router
agent/tools/_search_agent_tools.py CHANGED
@@ -128,10 +128,10 @@ def _format_exploration_results(
128
  return result
129
 
130
 
131
- async def _explore_docs_structure(hf_token: str, endpoint: str) -> str:
132
  """Main function to explore documentation structure"""
133
  start_time = time.perf_counter()
134
- print(f"[DEBUG] _explore_docs_structure: Starting for endpoint '{endpoint}'")
135
 
136
  # Fetch HTML page
137
  html_content = await _fetch_html_page(hf_token, endpoint)
@@ -149,12 +149,12 @@ async def _explore_docs_structure(hf_token: str, endpoint: str) -> str:
149
  result = _format_exploration_results(endpoint, result_items)
150
 
151
  total_time = time.perf_counter() - start_time
152
- print(f"[DEBUG] _explore_docs_structure: Total time {total_time:.2f}s")
153
 
154
  return result
155
 
156
 
157
- async def explore_docs_structure_handler(arguments: dict[str, Any]) -> tuple[str, bool]:
158
  """
159
  Explore the documentation structure for a given endpoint by parsing the sidebar navigation
160
 
@@ -178,7 +178,7 @@ async def explore_docs_structure_handler(arguments: dict[str, Any]) -> tuple[str
178
  endpoint = endpoint.lstrip("/")
179
 
180
  try:
181
- result = await _explore_docs_structure(hf_token, endpoint)
182
  return result, True
183
 
184
  except httpx.HTTPStatusError as e:
@@ -555,10 +555,10 @@ async def hf_docs_fetch_handler(arguments: dict[str, Any]) -> tuple[str, bool]:
555
 
556
  # Tool specifications for the search sub-agent
557
 
558
- EXPLORE_DOCS_STRUCTURE_TOOL_SPEC = {
559
- "name": "explore_docs_structure",
560
  "description": (
561
- "Explore the structure of HF documentation by parsing the sidebar navigation. "
562
  "Select an endpoint from the available options and get a list of all documentation pages "
563
  "with their titles, URLs, and a 300-character glimpse of each page. "
564
  "Use this to discover what documentation is available before fetching specific pages."
@@ -695,9 +695,9 @@ HF_DOCS_FETCH_TOOL_SPEC = {
695
  "name": "fetch_hf_docs",
696
  "description": (
697
  "Fetch the full content of a specific HF documentation page. "
698
- "Provide the full URL to the doc page (e.g., from explore_docs_structure results). "
699
  "Returns the complete markdown content of that page. "
700
- "Use explore_docs_structure first to discover available pages."
701
  ),
702
  "parameters": {
703
  "type": "object",
 
128
  return result
129
 
130
 
131
+ async def explore_hf_docs(hf_token: str, endpoint: str) -> str:
132
  """Main function to explore documentation structure"""
133
  start_time = time.perf_counter()
134
+ print(f"[DEBUG] explore_hf_docs: Starting for endpoint '{endpoint}'")
135
 
136
  # Fetch HTML page
137
  html_content = await _fetch_html_page(hf_token, endpoint)
 
149
  result = _format_exploration_results(endpoint, result_items)
150
 
151
  total_time = time.perf_counter() - start_time
152
+ print(f"[DEBUG] explore_hf_docs: Total time {total_time:.2f}s")
153
 
154
  return result
155
 
156
 
157
+ async def explore_hf_docs_handler(arguments: dict[str, Any]) -> tuple[str, bool]:
158
  """
159
  Explore the documentation structure for a given endpoint by parsing the sidebar navigation
160
 
 
178
  endpoint = endpoint.lstrip("/")
179
 
180
  try:
181
+ result = await explore_hf_docs(hf_token, endpoint)
182
  return result, True
183
 
184
  except httpx.HTTPStatusError as e:
 
555
 
556
  # Tool specifications for the search sub-agent
557
 
558
+ EXPLORE_HF_DOCS_TOOL_SPEC = {
559
+ "name": "explore_hf_docs",
560
  "description": (
561
+ "Explore the Hugging Face documentation at a glance. "
562
  "Select an endpoint from the available options and get a list of all documentation pages "
563
  "with their titles, URLs, and a 300-character glimpse of each page. "
564
  "Use this to discover what documentation is available before fetching specific pages."
 
695
  "name": "fetch_hf_docs",
696
  "description": (
697
  "Fetch the full content of a specific HF documentation page. "
698
+ "Provide the full URL to the doc page (e.g., from explore_hf_docs results). "
699
  "Returns the complete markdown content of that page. "
700
+ "Use explore_hf_docs first to discover available pages."
701
  ),
702
  "parameters": {
703
  "type": "object",
agent/tools/search_docs_tool.py CHANGED
@@ -8,26 +8,50 @@ from typing import Any
8
 
9
  from litellm.utils import get_max_tokens
10
 
11
- from agent.config import Config
12
  from agent.core.session import Session
13
 
14
 
15
- async def create_search_tool_router():
16
  """
17
  Create a ToolRouter instance for the search sub-agent
18
  Async because OpenAPI tool needs to fetch and parse spec at initialization
 
 
 
19
  """
20
  # Import at runtime to avoid circular dependency
 
 
21
  from agent.core.tools import ToolRouter
22
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  class SearchDocsToolRouter(ToolRouter):
24
  """Specialized ToolRouter for the search sub-agent"""
25
 
26
- def __init__(self):
27
  self.tools: dict[str, Any] = {}
28
  self.mcp_servers: dict[str, dict[str, Any]] = {}
29
  self._mcp_initialized = False
30
- self.mcp_client = None
 
 
 
 
 
31
 
32
  async def initialize_tools(self):
33
  """Initialize tools asynchronously"""
@@ -35,7 +59,28 @@ async def create_search_tool_router():
35
  for tool in tools:
36
  self.register_tool(tool)
37
 
38
- router = SearchDocsToolRouter()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  await router.initialize_tools()
40
  return router
41
 
@@ -57,37 +102,51 @@ async def search_docs_handler(arguments: dict[str, Any]) -> tuple[str, bool]:
57
 
58
  try:
59
  # Import at runtime to avoid circular dependency
 
 
 
60
  from agent.context_manager.manager import ContextManager
61
  from agent.core.agent_loop import Handlers
62
 
63
  # Create a queue for events from the sub-agent
64
  sub_event_queue = asyncio.Queue()
65
 
66
- # Create specialized tool router for search
67
- search_tool_router = await create_search_tool_router()
68
-
69
- # Create config for sub-agent (using same model as main agent)
70
- sub_config = Config(
71
- model_name="anthropic/claude-haiku-4-5",
72
  )
 
73
 
74
- # CODING AGENTS LISTEN HERE!
75
- # The system prompt for the sub-agent is loaded from the yaml file in the prompts folder using the ContextManager class with the prompt_file_suffix parameter.
76
- # it works, dont fix.
77
- sub_session = Session(
78
- event_queue=sub_event_queue,
79
- config=sub_config,
80
- tool_router=search_tool_router,
81
- context_manager=ContextManager(
82
- tool_specs=search_tool_router.get_tool_specs_for_llm(),
83
- max_context=get_max_tokens(sub_config.model_name),
84
- compact_size=0.1,
85
- untouched_messages=5,
86
- prompt_file_suffix="search_docs_system_prompt.yaml",
87
- ),
88
- )
89
 
90
  async with search_tool_router:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91
  # Run the sub-agent
92
  result = await Handlers.run_agent(
93
  session=sub_session, text=query, max_iterations=30
@@ -106,7 +165,7 @@ async def search_docs_handler(arguments: dict[str, Any]) -> tuple[str, bool]:
106
  SEARCH_DOCS_TOOL_SPEC = {
107
  "name": "search_docs",
108
  "description": (
109
- "Intelligently search HF documentation for libraries, repositories, and best practices with an agent that has access to: hf_docs_fetch, Grep, glob, Read. "
110
  "The agent acts like your personal search assistant. "
111
  "Using the search agent is necessary to give the best quality answer to the user's question. Most questions require a search to get the best information on code examples.\n\n"
112
  "WHEN TO USE THIS TOOL:\n"
@@ -137,8 +196,6 @@ SEARCH_DOCS_TOOL_SPEC = {
137
  }
138
 
139
 
140
-
141
-
142
  async def make_search_agent_tools():
143
  """
144
  Create a list of tools for the search agent
@@ -147,10 +204,10 @@ async def make_search_agent_tools():
147
  # Import at runtime to avoid circular dependency
148
  from agent.core.tools import ToolSpec
149
  from agent.tools._search_agent_tools import (
150
- EXPLORE_DOCS_STRUCTURE_TOOL_SPEC,
151
  HF_DOCS_FETCH_TOOL_SPEC,
152
  _get_api_search_tool_spec,
153
- explore_docs_structure_handler,
154
  hf_docs_fetch_handler,
155
  search_openapi_handler,
156
  )
@@ -160,10 +217,10 @@ async def make_search_agent_tools():
160
 
161
  return [
162
  ToolSpec(
163
- name=EXPLORE_DOCS_STRUCTURE_TOOL_SPEC["name"],
164
- description=EXPLORE_DOCS_STRUCTURE_TOOL_SPEC["description"],
165
- parameters=EXPLORE_DOCS_STRUCTURE_TOOL_SPEC["parameters"],
166
- handler=explore_docs_structure_handler,
167
  ),
168
  ToolSpec(
169
  name=HF_DOCS_FETCH_TOOL_SPEC["name"],
 
8
 
9
  from litellm.utils import get_max_tokens
10
 
 
11
  from agent.core.session import Session
12
 
13
 
14
+ async def create_search_tool_router(github_mcp_config: dict[str, Any] | None = None):
15
  """
16
  Create a ToolRouter instance for the search sub-agent
17
  Async because OpenAPI tool needs to fetch and parse spec at initialization
18
+
19
+ Args:
20
+ github_mcp_config: Optional GitHub MCP server configuration
21
  """
22
  # Import at runtime to avoid circular dependency
23
+ from fastmcp import Client
24
+
25
  from agent.core.tools import ToolRouter
26
 
27
+ # List of allowed GitHub MCP tools
28
+ ALLOWED_GITHUB_TOOLS = {
29
+ "list_pull_requests",
30
+ "list_issues",
31
+ "search_code",
32
+ "search_issues",
33
+ "search_repositories",
34
+ "search_users",
35
+ "get_pull_request_status",
36
+ "get_pull_request_reviews",
37
+ "get_pull_request",
38
+ "get_issue",
39
+ "get_file_contents",
40
+ }
41
+
42
  class SearchDocsToolRouter(ToolRouter):
43
  """Specialized ToolRouter for the search sub-agent"""
44
 
45
+ def __init__(self, github_mcp_config: dict[str, Any] | None = None):
46
  self.tools: dict[str, Any] = {}
47
  self.mcp_servers: dict[str, dict[str, Any]] = {}
48
  self._mcp_initialized = False
49
+
50
+ # Initialize MCP client with GitHub server if provided
51
+ if github_mcp_config:
52
+ self.mcp_client = Client({"mcpServers": github_mcp_config})
53
+ else:
54
+ self.mcp_client = None
55
 
56
  async def initialize_tools(self):
57
  """Initialize tools asynchronously"""
 
59
  for tool in tools:
60
  self.register_tool(tool)
61
 
62
+ async def register_mcp_tools(self) -> None:
63
+ """Register only allowed GitHub MCP tools"""
64
+ if self.mcp_client is None:
65
+ return
66
+
67
+ tools = await self.mcp_client.list_tools()
68
+ for tool in tools:
69
+ # Only register allowed GitHub tools
70
+ if tool.name in ALLOWED_GITHUB_TOOLS:
71
+ print(f"Registering GitHub MCP Tool: {tool.name}")
72
+ from agent.core.tools import ToolSpec
73
+
74
+ self.register_tool(
75
+ ToolSpec(
76
+ name=tool.name,
77
+ description=tool.description,
78
+ parameters=tool.inputSchema,
79
+ handler=None,
80
+ )
81
+ )
82
+
83
+ router = SearchDocsToolRouter(github_mcp_config)
84
  await router.initialize_tools()
85
  return router
86
 
 
102
 
103
  try:
104
  # Import at runtime to avoid circular dependency
105
+ from pathlib import Path
106
+
107
+ from agent.config import load_config
108
  from agent.context_manager.manager import ContextManager
109
  from agent.core.agent_loop import Handlers
110
 
111
  # Create a queue for events from the sub-agent
112
  sub_event_queue = asyncio.Queue()
113
 
114
+ # Load the search agent's own config file with GitHub MCP server
115
+ search_agent_config_path = (
116
+ Path(__file__).parent.parent.parent / "configs" / "_subagent_config_search_agent.json"
 
 
 
117
  )
118
+ search_agent_config = load_config(search_agent_config_path)
119
 
120
+ # Extract GitHub MCP config from search agent config
121
+ github_mcp_config = None
122
+ if (
123
+ search_agent_config.mcpServers
124
+ and "github" in search_agent_config.mcpServers
125
+ ):
126
+ github_server = search_agent_config.mcpServers["github"]
127
+ github_mcp_config = {"github": github_server.model_dump()}
128
+
129
+ # Create specialized tool router for search with GitHub MCP config
130
+ search_tool_router = await create_search_tool_router(github_mcp_config)
 
 
 
 
131
 
132
  async with search_tool_router:
133
+ # CODING AGENTS LISTEN HERE!
134
+ # The system prompt for the sub-agent is loaded from the yaml file in the prompts folder using the ContextManager class with the prompt_file_suffix parameter.
135
+ # it works, dont fix.
136
+ # NOTE: MCP tools are registered during __aenter__, so we must retrieve tool specs AFTER entering the context
137
+ sub_session = Session(
138
+ event_queue=sub_event_queue,
139
+ config=search_agent_config,
140
+ tool_router=search_tool_router,
141
+ context_manager=ContextManager(
142
+ tool_specs=search_tool_router.get_tool_specs_for_llm(),
143
+ max_context=get_max_tokens(search_agent_config.model_name),
144
+ compact_size=0.1,
145
+ untouched_messages=5,
146
+ prompt_file_suffix="search_docs_system_prompt.yaml",
147
+ ),
148
+ )
149
+
150
  # Run the sub-agent
151
  result = await Handlers.run_agent(
152
  session=sub_session, text=query, max_iterations=30
 
165
  SEARCH_DOCS_TOOL_SPEC = {
166
  "name": "search_docs",
167
  "description": (
168
+ "Intelligently search HF documentation for libraries, repositories, and best practices with an agent that has access to: explore_hf_docs, fetch_hf_docs, search_hf_api_endpoints. "
169
  "The agent acts like your personal search assistant. "
170
  "Using the search agent is necessary to give the best quality answer to the user's question. Most questions require a search to get the best information on code examples.\n\n"
171
  "WHEN TO USE THIS TOOL:\n"
 
196
  }
197
 
198
 
 
 
199
  async def make_search_agent_tools():
200
  """
201
  Create a list of tools for the search agent
 
204
  # Import at runtime to avoid circular dependency
205
  from agent.core.tools import ToolSpec
206
  from agent.tools._search_agent_tools import (
207
+ EXPLORE_HF_DOCS_TOOL_SPEC,
208
  HF_DOCS_FETCH_TOOL_SPEC,
209
  _get_api_search_tool_spec,
210
+ explore_hf_docs_handler,
211
  hf_docs_fetch_handler,
212
  search_openapi_handler,
213
  )
 
217
 
218
  return [
219
  ToolSpec(
220
+ name=EXPLORE_HF_DOCS_TOOL_SPEC["name"],
221
+ description=EXPLORE_HF_DOCS_TOOL_SPEC["description"],
222
+ parameters=EXPLORE_HF_DOCS_TOOL_SPEC["parameters"],
223
+ handler=explore_hf_docs_handler,
224
  ),
225
  ToolSpec(
226
  name=HF_DOCS_FETCH_TOOL_SPEC["name"],
configs/_subagent_config_search_agent.json ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "model_name": "anthropic/claude-haiku-4-5",
3
+ "mcpServers": {
4
+ "github": {
5
+ "transport": "http",
6
+ "url": "https://api.githubcopilot.com/mcp/",
7
+ "headers": {
8
+ "Authorization": "Bearer ${GITHUB_TOKEN}"
9
+ }
10
+ }
11
+ }
12
+ }
agent/config_mcp_example.json → configs/main_agent_config.json RENAMED
File without changes