Spaces:
Running
Running
File size: 3,610 Bytes
25a2c1c | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 | """Actual MCP stdio fixture. Only an owned disposable root and fixed tools."""
import argparse,asyncio,hashlib,hmac,json,os,subprocess,sys
from pathlib import Path,PurePosixPath
from mcp.server.fastmcp import FastMCP,Context
parser=argparse.ArgumentParser();parser.add_argument('--root',required=True);args=parser.parse_args()
ROOT=Path(args.root).resolve();TOKEN=os.environ['NEEDLE_MCP_TEST_TOKEN']
server=FastMCP('needle-disposable-fixture',log_level='ERROR')
def check(ctx,name):
meta=ctx.request_context.meta
provided=meta.model_dump().get('capability','') if meta else ''
if not isinstance(provided,str) or not hmac.compare_digest(provided,TOKEN):raise ValueError('Unauthorized MCP fixture request')
policy=json.loads((ROOT/'.git'/'needle-executor-policy.json').read_text())
if name not in policy['allowed_tools']:raise ValueError('Current executor permission denied')
def target(path):
try:relative=PurePosixPath(path).relative_to('/project')
except ValueError:raise ValueError('Target outside disposable workspace')
resolved=(ROOT/relative).resolve()
if '..' in relative.parts or not resolved.is_relative_to(ROOT) or relative.as_posix() not in {'app.py','test_app.py'}:raise ValueError('Target not authorized')
return resolved
def command(*args):
p=subprocess.run(args,cwd=ROOT,text=True,capture_output=True,timeout=15,env=dict(os.environ,PYTHONDONTWRITEBYTECODE='1'))
return {'returncode':p.returncode,'stdout':p.stdout.replace(str(ROOT),'/project'),'stderr':p.stderr.replace(str(ROOT),'/project')}
@server.tool()
def list_files(ctx:Context)->dict:
"""List repository files."""
check(ctx,'list_files');return {'files':['app.py','test_app.py']}
@server.tool()
def search_files(query:str,ctx:Context)->dict:
"""Search source text."""
check(ctx,'search_files');return {'matches':[f'app.py:{i}:{line}' for i,line in enumerate((ROOT/'app.py').read_text().splitlines(),1) if query in line]}
@server.tool()
def read_file(path:str,ctx:Context)->dict:
"""Read a source file."""
check(ctx,'read_file');return {'content':target(path).read_text()}
@server.tool()
def write_file(path:str,content:str,ctx:Context)->dict:
"""Write exact file contents."""
check(ctx,'write_file');data=content.encode('utf-8');target(path).write_bytes(data)
return {'bytes':len(data),'sha256':hashlib.sha256(data).hexdigest()}
@server.tool()
def run_tests(ctx:Context)->dict:
"""Execute the test suite."""
check(ctx,'run_tests');return command(sys.executable,'-m','pytest','-qq','--tb=line','--no-summary','-p','no:cacheprovider')
@server.tool()
def git_status(ctx:Context)->dict:
"""Inspect repository status."""
check(ctx,'git_status');return command('git','status','--short','--untracked-files=no')
@server.tool()
def git_diff(ctx:Context)->dict:
"""Inspect source changes."""
check(ctx,'git_diff');return command('git','diff','--','app.py')
@server.tool()
def git_commit(message:str,ctx:Context)->dict:
"""Commit changes with an explicit message."""
check(ctx,'git_commit');command('git','add','app.py');return command('git','commit','-qm',message)
@server.tool()
async def probe_delay(ctx:Context)->dict:
"""Test-only cancellation probe, never in the gateway catalogue."""
check(ctx,'probe_delay');await asyncio.sleep(2);return {'done':True}
@server.tool()
def probe_error(ctx:Context)->dict:
"""Test-only fixed error probe, never in the gateway catalogue."""
check(ctx,'probe_error');raise ValueError('Deliberate harmless fixture error')
if __name__=='__main__':server.run(transport='stdio')
|