2323import subprocess
2424import argparse
2525import time
26-
26+ import sys
2727import httpx
28- from pydantic_ai import Agent
29- from pydantic_ai .mcp import MCPServerSSE
28+ import pathlib
3029
31- import logfire
30+ from pydantic_ai import Agent
31+ from pydantic_ai .mcp import MCPServerStdio
3232
3333import config as oss_fuzz_mcp_config
3434
35- logfire .configure (send_to_logfire = 'if-token-present' )
36- logfire .instrument_pydantic_ai ()
37-
3835# Configure logging
3936logging .basicConfig (
4037 level = logging .INFO ,
41- format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s" )
38+ format = "[CLIENT] %(asctime)s - %(name)s - %(levelname)s - %(message)s" ,
39+ stream = sys .stderr )
4240logger = logging .getLogger ("mcp-server" )
4341
4442MCP_SERVER_URL = "http://localhost:8000/sse"
178176"""
179177
180178
181- async def chat_with_agent (prompt : str ) -> list :
179+ async def run_agent_loop (prompt : str ) -> list :
182180 """
183- Send a message to the LLM with access to the MCP tools .
181+ Performs a run with the LLM .
184182
185183 Args:
186184 prompt: The user's message
@@ -190,23 +188,24 @@ async def chat_with_agent(prompt: str) -> list:
190188 """
191189 nodes = []
192190 try :
193- server = MCPServerSSE (url = MCP_SERVER_URL ,
194- timeout = 5200.0 ,
195- read_timeout = 5000.0 )
191+ server = MCPServerStdio (
192+ 'python3' ,
193+ [str (pathlib .Path (__file__ ).parent .resolve ()) + '/oss_fuzz_server.py' ],
194+ timeout = 5200.0 )
196195
197- agent = Agent (model = "openai:gpt-4o " , toolsets = [server ], retries = 30 )
196+ agent = Agent (model = "openai:gpt-4 " , toolsets = [server ], retries = 30 )
198197
199198 # Run the agent with the MCP server context
200199 logger .info ('Starting agent run' )
201200 async with agent .iter (prompt ) as agent_run :
202201 logger .info ('Agent run started' )
203-
204202 async for node in agent_run :
205- logger .info ('Running node %s' , node .__class__ .__name__ )
203+ logger .info ('Running node [%d] %s' , len ( nodes ) , node .__class__ .__name__ )
206204 time .sleep (3 )
207205 nodes .append (node )
208206 except Exception as e :
209207 logger .info ('Error during agent run: %s' , e )
208+ sys .exit (1 )
210209
211210 return nodes
212211
@@ -365,7 +364,7 @@ async def does_project_build(project: str) -> bool:
365364 return True
366365
367366
368- async def fix_project_build (project : str ):
367+ async def fix_project_build (project : str , max_tries : int = 3 ):
369368 """Runs an agent to fix the build of an OSS-Fuzz project."""
370369
371370 project_language = _detect_language (project )
@@ -382,8 +381,11 @@ async def fix_project_build(project: str):
382381 if oss_fuzz_filetree :
383382 extra_project_text += f'The files in the OSS-Fuzz project for { project } are:\n { oss_fuzz_filetree } \n '
384383
385- nodes = await chat_with_agent (
386- f"""Fix the OSS-Fuzz project { project } that currently has a broken build.
384+ nodes = []
385+ for _attempt in range (max_tries ):
386+ logger .info ('Attempt %d to fix project %s' , _attempt + 1 , project )
387+ nodes += await run_agent_loop (
388+ f"""Fix the OSS-Fuzz project { project } that currently has a broken build.
387389Use the build logs from OSS-Fuzz's project { project } and determine why it fails, then
388390proceed to adjust Dockerfile and build.sh scripts until the project builds.
389391
@@ -410,7 +412,10 @@ async def fix_project_build(project: str):
410412- Continue adjusting the files in { oss_fuzz_mcp_config .BASE_OSS_FUZZ_DIR } /projects/{ project } / until "fuzzer-check" passes.
411413""" )
412414
413- fix_success = await does_project_build (project )
415+ fix_success = await does_project_build (project )
416+ if fix_success :
417+ logger .info ('Project %s build fixed successfully.' , project )
418+ break
414419 return nodes , fix_success
415420
416421
@@ -501,7 +506,7 @@ async def add_run_tests_command(project_name: str):
501506
502507 os .chmod (run_tests_path , 0o755 )
503508
504- await chat_with_agent (f"""
509+ await run_agent_loop (f"""
505510You are an expert software security engineer that is specialized in OSS-Fuzz.
506511You are tasked with adding a run_tests.sh script to an OSS-Fuzz project.
507512This script should run the tests of the project, and ensure that the project is working correctly.
@@ -530,7 +535,7 @@ async def expand_existing_project(project_name: str):
530535 logger .info ('Failed to prepare %s. Exiting.' , project_name )
531536 return
532537
533- nodes = await chat_with_agent (
538+ nodes = await run_agent_loop (
534539 f"""You are a security engineer that is an expert in fuzzing development, and your goal is to expand on the
535540fuzzing harnesses of OSS-Fuzz project { project_name } .
536541Use the tools to understand the fuzzing harnesses of the { project_name } 's OSS-Fuzz integration.
@@ -571,7 +576,8 @@ def _log_nodes(logfile, nodes, header_text=''):
571576
572577async def fix_oss_fuzz_projects (projects_to_fix = None ,
573578 max_projects_to_fix = 4 ,
574- language = '' ):
579+ language = '' ,
580+ max_tries = 3 ):
575581 """Fixes the build of a list of OSS-Fuzz projects."""
576582
577583 if projects_to_fix is None :
@@ -598,7 +604,7 @@ async def fix_oss_fuzz_projects(projects_to_fix=None,
598604 continue
599605 except :
600606 continue
601- nodes , fix_success = await fix_project_build (project )
607+ nodes , fix_success = await fix_project_build (project , max_tries )
602608 responses .append ({'project' : project , 'fix_success' : fix_success })
603609 if nodes :
604610 _log_nodes (f'responses-fix-build-{ project } .json' ,
@@ -707,7 +713,7 @@ async def initiate_project_creation(project: str, project_repo: str,
707713available to extract code coverage of the project when you're creating the harness, and either
708714add more fuzzing harnesses to the project or extend the harness to cover more functions."""
709715
710- nodes = await chat_with_agent (
716+ nodes = await run_agent_loop (
711717 f"""You are an expert software security engineer and you are tasked with creating an OSS-Fuzz project.
712718I have set up an initial project structure at { oss_fuzz_mcp_config .BASE_OSS_FUZZ_DIR } /projects/{ project } /. This structure
713719includes a Dockerfile, build.sh, and project.yaml file. The Dockerfile clones the target
@@ -882,6 +888,11 @@ def parse_arguments():
882888 fix_builds = subparsers .add_parser (
883889 'fix-builds' ,
884890 help = 'Fix the builds of OSS-Fuzz projects that are currently broken.' )
891+ fix_builds .add_argument (
892+ '--max_attempts' ,
893+ type = int ,
894+ default = 3 ,
895+ help = 'Maximum number of attempts to fix each project (default: 3)' )
885896
886897 fix_builds .add_argument ('--max-projects' ,
887898 type = int ,
@@ -939,7 +950,8 @@ async def main():
939950
940951 initialize_oss_fuzz ()
941952 if args .command == 'fix-builds' :
942- await fix_oss_fuzz_projects (args .projects , args .max_projects , args .language )
953+ await fix_oss_fuzz_projects (args .projects , args .max_projects , args .language ,
954+ args .max_attempts )
943955 elif args .command == 'create-project' :
944956 logger .info ('Creating OSS-Fuzz project for URL: %s' , args .project_url )
945957 await create_oss_fuzz_integration_for_project (args .project_url ,
0 commit comments