#!/usr/bin/env python3
"""Capture real HTTP MCP behavior on an isolated, fresh T3 server.
Usage: python3 proof-driver.py CHECKOUT OUTPUT_DIRECTORY PORT
Requires dependencies installed with `vp i`. No live database is used.
"""
import datetime,json,os,pathlib,signal,sqlite3,subprocess,sys,time,urllib.request
checkout=pathlib.Path(sys.argv[1]).resolve(); output=pathlib.Path(sys.argv[2]).resolve(); port=int(sys.argv[3])
output.mkdir(parents=True,exist_ok=False); home=output/'home'; userdata=home/'userdata';userdata.mkdir(parents=True)
settings={'defaultModelSelection':{'instanceId':'devReplay','model':'showcase'},'providerInstances':{p:{'driver':p,'enabled':False} for p in ['codex','claudeAgent','cursor','grok','muse','pi','opencode','antigravity']}}
(userdata/'settings.json').write_text(json.dumps(settings))
sha=subprocess.check_output(['git','rev-parse','HEAD'],cwd=checkout,text=True).strip()
log=(output/'server-private.log').open('w')
env={**os.environ,'T3CODE_DEV_SEED':'0','T3CODE_AUTO_BOOTSTRAP_PROJECT_FROM_CWD':'0'}
server=subprocess.Popen(['vp','run','dev:server','--no-seed','--home-dir',str(home),'--port',str(port)],cwd=checkout,env=env,stdout=log,stderr=subprocess.STDOUT,start_new_session=True)
started=time.monotonic(); lines=[]; cast=[json.dumps({'version':2,'width':110,'height':52,'timestamp':int(time.time()),'title':'Real HTTP MCP argument validation'})]
def emit(s):
 lines.append(s); print(s,flush=True);cast.append(json.dumps([round(time.monotonic()-started,3),'o',s+'\r\n']))
session=None;seq=0
try:
 for _ in range(120):
  if server.poll() is not None: raise RuntimeError('Server stopped; inspect private log')
  if 'Listening on http://' in (output/'server-private.log').read_text():break
  time.sleep(1)
 else: raise RuntimeError('Server did not start')
 token=subprocess.check_output(['node','apps/server/src/bin.ts','auth','session','issue','--subject','mcp-client','--token-only','--base-dir',str(home)],cwd=checkout,text=True).strip()
 def rpc(method,params):
  global session,seq
  seq+=1; headers={'Authorization':'Bearer '+token,'Accept':'application/json, text/event-stream','Content-Type':'application/json','MCP-Protocol-Version':'2025-06-18'}
  if session:headers['Mcp-Session-Id']=session
  req=urllib.request.Request(f'http://127.0.0.1:{port}/mcp',data=json.dumps({'jsonrpc':'2.0','id':seq,'method':method,'params':params}).encode(),headers=headers)
  with urllib.request.urlopen(req,timeout=60) as r:
   session=r.headers.get('Mcp-Session-Id',session);data=r.read().decode()
  if data.startswith(('event:','data:')):data=next(x[6:] for x in data.splitlines() if x.startswith('data: '))
  return json.loads(data)
 rpc('initialize',{'protocolVersion':'2025-06-18','capabilities':{},'clientInfo':{'name':'proof-driver','version':'1'}})
 def invoke(name,args):
  emit('\n> '+name+' '+json.dumps(args))
  response=rpc('tools/call',{'name':name,'arguments':args})
  if 'error' in response:result=response['error']
  else:
   value=response['result']; result=value.get('structuredContent')
   if result is None:result=json.loads(value['content'][0]['text'])
  if name=='t3_thread_read' and 'thread' in result:
   t=result['thread'];result={'thread':{k:t[k] for k in ['threadId','status','latestRunId','activeRunId','runCount','itemCount']},'recentRuns':[{k:r[k] for k in ['runId','status']} for r in result['recentRuns']],'items':[{k:i[k] for k in ['type','text'] if k in i} for i in result['items']]}
  else:result={k:v for k,v in result.items() if k not in ['modelSelection','link','projectId']}
  emit(json.dumps(result,indent=2));return result
 def count():
  with sqlite3.connect('file:'+str(userdata/'statev2.sqlite')+'?mode=ro',uri=True) as db:
   return db.execute('select count(*) from orchestration_v2_projection_threads').fetchone()[0]
 emit('REAL SERVER CAPTURE / HTTP MCP / loopback / fresh synthetic data')
 emit('Commit: '+sha)
 emit('Environment: isolated server; replay provider; no UI or mocked handlers')
 emit('Thread counts: read-only query of the V2 projection in this isolated database')
 before=count();emit('\nThreads before invalid call: '+str(before))
 invalid=invoke('t3_thread_launch',{'title':'Repro: prompt instead of message','scratch':True,'prompt':'say hi'})
 after=count();emit('Threads after invalid call: '+str(after)+' (delta '+str(after-before)+')')
 if 'threadId' in invalid:invoke('t3_thread_read',{'threadId':invalid['threadId']})
 valid=invoke('t3_thread_launch',{'title':'Repro: prompt instead of message','scratch':True,'message':'say hi'})
 if 'threadId' in valid:invoke('t3_thread_read',{'threadId':valid['threadId']})
 emit('\nCapture complete. AI-assisted; verified with the tools listed above.')
 (output/'transcript.txt').write_text('\n'.join(lines)+'\n')
 (output/'session.cast').write_text('\n'.join(cast)+'\n')
finally:
 os.killpg(server.pid,signal.SIGINT)
 try:server.wait(timeout=15)
 except subprocess.TimeoutExpired:os.killpg(server.pid,signal.SIGKILL);server.wait()
 log.close()
