onmcp/scripts/test_cpp_progressive_discov...

83 lines
4.9 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
"$ROOT/scripts/build_cpp.sh" >/dev/null
mkdir -p "$ROOT/reports"
AMAP_PORT=19881
CAL_PORT=19882
MCPD_PORT=19880
"$ROOT/build/cpp/mock_mcp_server" --port "$AMAP_PORT" >"$ROOT/reports/progressive_amap.log" 2>&1 &
AMAP_PID=$!
"$ROOT/build/cpp/mock_calendar_server" --port "$CAL_PORT" >"$ROOT/reports/progressive_calendar.log" 2>&1 &
CAL_PID=$!
for _ in $(seq 1 50); do
if curl -fsS -H 'Content-Type: application/json' --data '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}' "http://127.0.0.1:$AMAP_PORT/rpc" >/dev/null 2>&1 \
&& curl -fsS -H 'Content-Type: application/json' --data '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}' "http://127.0.0.1:$CAL_PORT/rpc" >/dev/null 2>&1; then
break
fi
sleep 0.1
done
"$ROOT/build/cpp/mcpd" --port "$MCPD_PORT" \
--upstream "http://127.0.0.1:$AMAP_PORT/rpc" \
--service "calendar=http://127.0.0.1:$CAL_PORT/rpc,calendar." \
>"$ROOT/reports/progressive_mcpd.log" 2>&1 &
MCPD_PID=$!
cleanup() {
kill "$MCPD_PID" "$AMAP_PID" "$CAL_PID" >/dev/null 2>&1 || true
wait "$MCPD_PID" "$AMAP_PID" "$CAL_PID" 2>/dev/null || true
}
trap cleanup EXIT
for _ in $(seq 1 50); do
if curl -fsS "http://127.0.0.1:$MCPD_PORT/healthz" >/dev/null 2>&1; then break; fi
sleep 0.1
done
ROOT="$ROOT" MCPD_PORT="$MCPD_PORT" python3 - <<'PY'
import json, os, sys, urllib.error, urllib.request
root=os.environ['ROOT']; port=os.environ['MCPD_PORT']; endpoint=f'http://127.0.0.1:{port}/rpc'; seq=0; checks=[]
def rpc(method, params=None, token='demo-token'):
global seq; seq += 1
payload={'jsonrpc':'2.0','id':seq,'method':method,'params':params or {}}
req=urllib.request.Request(endpoint, data=json.dumps(payload, ensure_ascii=False).encode(), headers={'Content-Type':'application/json','X-Agent-Token':token}, method='POST')
try:
with urllib.request.urlopen(req, timeout=5) as resp: return resp.status, json.loads(resp.read().decode())
except urllib.error.HTTPError as e:
try: body=json.loads(e.read().decode())
except Exception: body={}
return e.code, body
except urllib.error.URLError as e:
return 0, {'error': str(e)}
def check(name, cond, detail):
checks.append({'name':name,'pass':bool(cond),'detail':detail}); print(('[PASS]' if cond else '[FAIL]'), name, detail)
# Readiness through tools/list so both upstream schemas can be loaded eventually.
for _ in range(50):
code_ready, body_ready = rpc('tools/list', {'compact': True})
if code_ready == 200 and body_ready.get('result',{}).get('tools'):
break
else:
check('readiness', False, {'code':code_ready,'body':body_ready}); sys.exit(1)
code_all, all_body = rpc('tools/list', {'compact': True})
all_tools = all_body.get('result', {}).get('tools', [])
all_bytes = len(json.dumps(all_body, ensure_ascii=False).encode('utf-8'))
code_search, search_body = rpc('ohmcp/tools.search', {'query': '苏州 天气 酒店', 'limit': 3})
search_tools = search_body.get('result', {}).get('tools', [])
search_names = [t.get('name','') for t in search_tools]
search_bytes = len(json.dumps(search_body, ensure_ascii=False).encode('utf-8'))
check('search returns related subset', code_search == 200 and search_tools and len(search_tools) <= 3 and any('weather' in n or 'hotel' in n for n in search_names), search_names)
check('search response smaller than full compact list', search_bytes < all_bytes, {'search_bytes': search_bytes, 'all_compact_bytes': all_bytes})
first_schema = search_tools[0].get('schema_id','') if search_tools else ''
code_schema, schema_body = rpc('ohmcp/tools.get_schema', {'schema_id': first_schema})
result_schema = schema_body.get('result', {})
check('get_schema by schema_id', code_schema == 200 and result_schema.get('schema_id') == first_schema and result_schema.get('inputSchema'), result_schema)
code_schema_name, schema_name_body = rpc('ohmcp/tools.get_schema', {'name': 'amap.maps_weather'})
check('get_schema by name', code_schema_name == 200 and schema_name_body.get('result', {}).get('name') == 'amap.maps_weather', schema_name_body)
code_acl, acl_body = rpc('ohmcp/tools.get_schema', {'name': 'amap.hotel_search'}, token='weather-token')
check('get_schema respects tool acl', code_acl == 403, {'code':code_acl,'body':acl_body})
code_service_acl, service_acl_body = rpc('ohmcp/tools.search', {'service_id': 'calendar', 'query':'日程'}, token='weather-token')
check('search respects service acl', code_service_acl == 403, {'code':code_service_acl,'body':service_acl_body})
summary={'test':'progressive discovery','pass':sum(1 for c in checks if c['pass']),'fail':sum(1 for c in checks if not c['pass']),'checks':checks,'bytes':{'full_compact':all_bytes,'search':search_bytes}}
out=os.path.join(root,'reports','progressive_discovery_summary.json')
open(out,'w',encoding='utf-8').write(json.dumps(summary, ensure_ascii=False, indent=2))
print('summary:', out)
if summary['fail']: sys.exit(1)
PY