Ports the GraphMCP-Example substrate into lore-engine-poc: - 8 Go workers under workers/ (discord-connector, discord-filter, lore-watcher, ingestion-worker, entity-extractor, lore-extractor, encounter-processor, mcp-server), each with Dockerfile + go.mod - 3 Go unit-test files (encounter-processor, ingestion-worker, lore-extractor) — other 5 workers rely on integration tests via the live stack - plugins/nsc.py: thin httpx proxy from gateway to lore-mcp-server:9000, exposes all 11 inherited GraphMCP tools (input schemas verbatim from mcp-server/main.go) - docker-compose.yml: adds lore-redis + lore-mcp-server + the 7 worker services (lore- prefix to avoid clash with other GraphMCP stacks) - verify-merge.sh (171 LOC, 7 pass conditions) + docs/VERIFICATION.md - tests/contract/test_graphmcp_tool_contracts.py (15 tests; skipped when stack is down — TDD pattern, becomes active once docker compose up brings the stack) - README.md + test.sh updated for the merged service inventory Leader notes (2026-06-27 03:50): - Worker self-blocked review-required after 2 runs (run #7 hit 120/120 iteration budget; run #8 staged 40 files and reported shippable). - Tests are SKIPPED until docker compose up — worker chose that pattern over mocking (consistent with the lore-engine-poc project convention). To activate, run `docker compose up -d --build && pytest tests/contract/`. - File Scope reconciliation: story said gateway/plugins/nsc/__init__.py; worker shipped plugins/nsc.py (flat file). Justified by the existing plugins/ convention in lore-engine-poc (server.py glob("*.py")). A future PR could split nsc into a package once server.py learns __init__.py discovery. - nsc plugin exposes 11 tools (not 8) — the AC said "8" but the worker enumerated all 11 tools present in mcp-server/main.go. The encounter-specific 3 tools (list_encounters, search_encounters, get_encounter) were included for consistency. Story AC #2 reads "≥ 8 GraphMCP tools" so this exceeds AC. Refs: S2-phase-1-substrate-merge, milestone #64 P1 — Substrate merge
79 lines
3.8 KiB
Bash
Executable File
79 lines
3.8 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# lore-engine-poc — end-to-end test
|
|
# Calls every tool type and checks for reasonable responses.
|
|
# Run with: bash test.sh
|
|
#
|
|
# v2.T6: every read tool now accepts an optional world_id parameter
|
|
# (defaulting to "default"). These calls pass world_id="default" explicitly
|
|
# to verify the v1 behaviour still works — i.e. that the world namespace
|
|
# is opt-in and does not break existing callers.
|
|
set -e
|
|
GATEWAY=${GATEWAY:-http://localhost:8766/mcp}
|
|
WORLD='"world_id":"default"'
|
|
|
|
call() {
|
|
local name=$1; shift
|
|
local args=$1; shift
|
|
curl -s -X POST "$GATEWAY" \
|
|
-H "Content-Type: application/json" \
|
|
-d "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"tools/call\",\"params\":{\"name\":\"$name\",\"arguments\":$args}}" \
|
|
| python3 -c "import json,sys; d=json.load(sys.stdin); print(d['result']['content'][0]['text'])"
|
|
}
|
|
|
|
echo "=== 1. entity_context(Aldric Raventhorne) ==="
|
|
call entity_context "{\"name\":\"Aldric Raventhorne\",${WORLD}}" | python3 -m json.tool | head -8
|
|
|
|
echo
|
|
echo "=== 2. was_true_at(House Vyr allied Merchants Guild @ 2nd_age.year_230) ==="
|
|
call was_true_at "{\"relation\":\"ALLIED_WITH\",\"subject\":\"House Vyr\",\"object\":\"Merchants Guild\",\"at_time\":\"2nd_age.year_230\",${WORLD}}"
|
|
|
|
echo
|
|
echo "=== 3. was_true_at(Crimson Pact allied House Vyr @ 2nd_age.year_230 — should be false) ==="
|
|
call was_true_at "{\"relation\":\"ALLIED_WITH\",\"subject\":\"Crimson Pact\",\"object\":\"House Vyr\",\"at_time\":\"2nd_age.year_230\",${WORLD}}"
|
|
|
|
echo
|
|
echo "=== 4. state_at(Aldric Raventhorne @ 2nd_age.year_260) ==="
|
|
call state_at "{\"entity\":\"Aldric Raventhorne\",\"at_time\":\"2nd_age.year_260\",${WORLD}}" | python3 -m json.tool | head -10
|
|
|
|
echo
|
|
echo "=== 5. ancestors_of(Aldric Raventhorne, 5 generations) ==="
|
|
call ancestors_of "{\"person\":\"Aldric Raventhorne\",\"generations\":5,${WORLD}}" | python3 -c "import json,sys; print(f'ancestor count: {json.load(sys.stdin)[\"ancestors\"].__len__()}')"
|
|
|
|
echo
|
|
echo "=== 6. lineage_of(Aldric Raventhorne) ==="
|
|
call lineage_of "{\"person\":\"Aldric Raventhorne\",${WORLD}}"
|
|
|
|
echo
|
|
echo "=== 7. log_trade(new) ==="
|
|
call log_trade "{\"buyer_id\":\"aldric\",\"seller_id\":\"guildmaster\",\"item_id\":\"sword_eventide\",\"quantity\":1,\"unit\":\"gp\",\"unit_price\":750,\"in_fiction_time\":\"2nd_age.year_275\",\"location_id\":\"thornwall\",\"notes\":\"blacksmith of thornwall\",${WORLD}}"
|
|
|
|
echo
|
|
echo "=== 8. market_price(pale_ledger) ==="
|
|
call market_price "{\"item_id\":\"pale_ledger\",${WORLD}}"
|
|
|
|
echo
|
|
echo "=== 9. recall_images(entity_id=aldric) ==="
|
|
IMG=$(call recall_images "{\"entity_id\":\"aldric\",${WORLD}}")
|
|
echo "$IMG" | python3 -c "import json,sys; d=json.load(sys.stdin); print(f'image count: {d[\"count\"]}'); print('first caption:', d['images'][0]['caption'][:60] if d['images'] else 'none')"
|
|
URL=$(echo "$IMG" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d['images'][0]['presigned_url']) if d['images'] else exit(1)")
|
|
echo "first image URL: ${URL:0:80}..."
|
|
echo
|
|
echo "--- fetching the presigned URL ---"
|
|
curl -s -o /tmp/aldric_test.png -w "HTTP %{http_code} | size %{size_download} bytes | type %{content_type}\n" "$URL"
|
|
file /tmp/aldric_test.png
|
|
|
|
echo
|
|
echo "=== 10. search_images_by_caption(q=aldric) ==="
|
|
call search_images_by_caption "{\"q\":\"aldric\",${WORLD}}" | python3 -c "import json,sys; d=json.load(sys.stdin); print(f'matches: {d[\"count\"]}'); [print(f' - {img[\"entity_type\"]}:{img[\"entity_id\"]} — {img[\"caption\"][:50]}...') for img in d['images']]"
|
|
|
|
echo
|
|
echo "=== 11. register_image(new) ==="
|
|
call register_image "{\"image_id\":\"img_test\",\"object_key\":\"test/x.png\",\"entity_id\":\"aldric\",\"entity_type\":\"Person\",\"caption\":\"test image\",\"tags\":[\"test\"],\"era\":\"2nd_age\",${WORLD}}"
|
|
|
|
echo
|
|
echo "=== 12. list_worlds() — v2.T6 admin tool ==="
|
|
call list_worlds '{}' | python3 -m json.tool
|
|
|
|
echo
|
|
echo "✅ all tool types tested"
|