83 lines
4.9 KiB
Bash
Executable File
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
|