Skip to content

Full-surface MCP server + Celaut .service (MCP HTTP + REST, seed/unsigned signer) - #1

Open
agenticaihome wants to merge 31 commits into
masterfrom
feat/celaut-mcp-service
Open

Full-surface MCP server + Celaut .service (MCP HTTP + REST, seed/unsigned signer)#1
agenticaihome wants to merge 31 commits into
masterfrom
feat/celaut-mcp-service

Conversation

@agenticaihome

Copy link
Copy Markdown
Owner

What

Mirrors the pattern already shipped for celaut-skills and reputation-system: a full-surface MCP server plus a Celaut .service microVM that exposes the entire Source Application library — every read, every write, and the pure helpers — backed by a SEED signer or an UNSIGNED signer.

Additions

mcp/ — stdio MCP server (full surface, 27 tools)

  • core.mjs — framework-agnostic reads + pure helpers, a Svelte-free port of src/lib/ergo/sourceFetch.ts + sourceObject.ts. Explorer box search and block-timestamp come from reputation-system/node (the headless, Node-safe entry — same searchBoxes the app uses, including the required reputation-proof ergoTreeTemplateHash filter). Type NFT ids copied verbatim from envs.ts (placeholders preserved; non-real types return clean empty arrays).
  • lib.mjsmakeSigner() from env (SOURCE_SIGNER_MODE=seed|unsigned), fetchMainBox(), describeResult() — the reputation-system/mcp/lib.mjs pattern with a SOURCE_ env prefix.
  • writes.mjs — port of src/lib/ergo/sourceStore.ts to the Node signer path via create_profile_with_signer / create_opinion_with_signer.
  • tools.mjs — shared TOOLS + HANDLERS (used by both transports, so they never drift).
  • server.mjs — stdio bootstrap (console routed to stderr so the library's logs never corrupt the JSON-RPC stream).

.service/ — Celaut microVM (MCP HTTP and REST)

  • server-http.mjs — binds 0.0.0.0:8080:
    • GET /health
    • * /mcp — the same MCP tool surface over Streamable HTTP
    • * /api/* — a clean JSON REST mirror of every method (reads via GET, writes via POST)
  • core/lib/writes/tools.mjs — copies of the mcp/ modules
  • Dockerfile, service.json (port 8080 http, init.entry_path app/start.sh, resources + api.ergoplatform.com network tag), start.sh, pack_config.json, package.json, README.md

reputation-system is wired as a dependency the same way celaut-skills does — here pinned to github:agenticaihome/reputation-system#fix/seed-signer-derivation (the branch that ships the /node entry + Nautilus-compatible BIP-39/32 SeedSigner).

Tools / endpoints

27 MCP tools: get_source_config; reads fetch_file_sources_by_hash, fetch_invalid_file_sources, fetch_unavailable_sources, fetch_profile_opinions, fetch_file_sources_by_profile, fetch_invalid_file_sources_by_profile, fetch_unavailable_sources_by_profile, fetch_profile_opinions_by_author, search_by_hash, load_profile_data; helpers group_by_download_source, group_by_profile, calculate_profile_trust, aggregate_source_score, get_primary_url, get_all_urls, list_hash_algorithms, validate_hash, compute_hash; writes create_profile_box, add_file_source, confirm_source, update_file_source, mark_invalid_source, mark_unavailable_source, trust_profile.

REST routes mirror all of the above — see .service/README.md for the full table and the write→opinion mapping.

Write → opinion mapping

  • add_file_source / confirm_source → opinion vs FILE_SOURCE_TYPE_NFT_ID (R5=fileHash, R8=true, R9=source entry)
  • mark_invalid_source → vs INVALID_FILE_SOURCE_TYPE_NFT_ID (R5=sourceBoxId, R8=false)
  • mark_unavailable_source → vs UNAVAILABLE_SOURCE_TYPE_NFT_ID (R5=sourceUrl, R8=false)
  • trust_profile → vs PROFILE_OPINION_TYPE_NFT_ID (R5=profileTokenId, R8=isTrusted)
  • create_profile_boxcreate_profile vs PROFILE_TYPE_NFT_ID

Every opinion is split from the author's PROFILE box (mainBoxId).

Env vars

  • SOURCE_SIGNER_MODE = unsigned (default) | seed
  • unsigned: SOURCE_ADDRESS
  • seed: SOURCE_MNEMONIC (+ optional SOURCE_MNEMONIC_PASSWORD, SOURCE_NODE_URI, SOURCE_ADDRESS_INDEX)
  • SOURCE_EXPLORER_API (default https://api.ergoplatform.com), PORT (default 8080)

How to run

# stdio MCP
cd mcp && npm install && npm run mcp
# Celaut service (HTTP + REST)
cd .service && npm install && npm start    # 0.0.0.0:8080

Verification evidence (all real, mainnet)

  • node --check passes on all 10 .mjs files.
  • reputation-system/node resolves: searchBoxes, getTimestampFromBlockId, SeedSigner, UnsignedSigner, create_opinion_with_signer, create_profile_with_signer.
  • stdio MCP tools/list27 tools; stdout is 100% JSON-RPC (console redirected to stderr).
  • HTTP GET /health{"status":"ok",...,"signerMode":"unsigned"}.
  • MCP over /mcp tools/list27 tools; tools/call compute_hash("abc",sha256)ba7816bf…015ad (correct).
  • REST reads: GET /api/config returns type NFT ids; GET /api/sources?hash=… → clean []; GET /api/hash-algorithmssha3_256,blake2b,sha256,keccak256,__custom__.
  • UNSIGNED write (POST /api/sources and add_file_source) against a real on-chain profile box → submitted:false, full unsigned tx (2 inputs, 1 dataInput, 4 outputs, first output = reputation contract), no mnemonic in env.

No seed-mode writes were run, no txs submitted, no wallets/secrets touched, and the reputation-system repo was not modified.

Honest gaps

  • update_file_source: the browser flow spends the previous FILE_SOURCE box via update_opinion (Nautilus-only). reputation-system/node exposes create_*_with_signer but not update_opinion_with_signer, so this publishes a new FILE_SOURCE opinion for the same hash instead of spending the old box. Documented in code + .service/README.md.
  • Placeholder Type NFTs: INVALID_FILE_SOURCE, UNAVAILABLE_SOURCE, PROFILE_OPINION are still all-zero placeholders in envs.ts (preserved verbatim). Reads/writes wire up correctly but won't match/clear real boxes until those NFTs are minted; FILE_SOURCE + PROFILE are real and return live data.
  • Timestamps: block-timestamp lookups degrade to 0 on failure (non-critical) rather than throwing, a small robustness change vs the original.

🤖 Generated with Claude Code

0xf965 and others added 30 commits March 31, 2026 21:14
- Replaced hardcoded blake2b256 with downloadAndHash() from hashUtils
- calculateHashFromUrl now uses the selected hash algorithm (sha3_256, blake2b, sha256, keccak256)
- Chunked files: downloadAndHash fetches all chunks from manifest, concatenates, then hashes the complete content
- handleAddSource validation also uses correct algorithm + chunked support
- handleFileUpload uses computeHash with selected algorithm
- Removed unused blake2b256 and uint8ArrayToHex imports
- Custom algorithm shows 'select a known hash algorithm first' error
…lgorithm-chunked

fix: calculateHashFromUrl uses selected algorithm + chunked file support
1. Hash Validation Toggle:
   - Added hashValidationEnabled store (persisted, default false)
   - Added checkbox toggle in SettingsModal under 'Verification' section
   - FileSourceCreation now respects the setting — skips URL hash
     verification when disabled
   - Exported hashValidationEnabled from index.ts

2. Profile Detection Fallback:
   - Added fallbackProfileDetection() for wallets where the
     reputation-system library fails to detect profiles due to
     R5 UTF-8 encoding edge case (R5 stores hex(utf8(tokenId))
     instead of raw bytes)
   - Queries Explorer API directly with ergoTreeTemplateHash + R4/R7
     register filters, then checks both raw hex and UTF-8 decoded R5
   - Falls back automatically when standard detection returns nothing

3. Fixed createProfileBox parameter order bug in sourceStore.ts
   (explorerUri was in wrong position)
…gs-hash-toggle-and-profile-detection

feat: hash validation toggle + fallback profile detection
Josemi's wallet has a JUDGE-type profile (from GoP) but the source app
was filtering by PROFILE_TYPE_NFT_ID only. Now:

1. fetchAllUserProfiles called with empty types array [] to accept any type
2. Fallback detection no longer filters by R4 register

This matches Game of Prompts behavior where profiles of any type are detected.
…-any-profile-type

fix: accept any profile type for wallet detection
- Update svelte.config.js with dynamic BASE_PATH for GitHub Pages
- Add esRawPlugin to vite.config.ts for .es contract files
- Clean up vite.config.ts (remove unused config block)
- Update deploy.yml to follow standardized template
- Layout already has ssr=false and prerender=true
…b-pages-deploy

feat: GitHub Pages deployment with dynamic base path and esRawPlugin
…meline, modals, etc.)

Previously only 4 components were exported. Now all components
from src/lib/components/ are available to library consumers,
matching the full web app experience.
…t-all-library-components

feat: export all library components
… validation

FileSourceCreation now includes:
- Hash Function ID selector with known algorithms
- Content Hash + Content Format fields
- Raw Format (toggleable via content-equals-raw checkbox)
- Chunked file manifest support
- Hash validation per algorithm
- URL parameter pre-fill support

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…t-all-library-components

feat: updated FileSourceCreation with hash/format fields + library exports
Runs `npm run package` and commits updated dist/ when src/lib changes.
No more need to run `npm run package` locally.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Mirror the celaut-skills / reputation-system pattern for source-application:

mcp/ (stdio MCP, full surface)
- core.mjs   framework-agnostic reads + pure helpers (Svelte-free port of
             sourceFetch.ts + sourceObject.ts); Explorer search via
             reputation-system/node
- lib.mjs    makeSigner (SOURCE_SIGNER_MODE=seed|unsigned), fetchMainBox,
             describeResult
- writes.mjs port of sourceStore.ts via create_*_with_signer
- tools.mjs  shared TOOLS + HANDLERS (27 tools)
- server.mjs stdio bootstrap (console routed to stderr to keep JSON-RPC clean)

.service/ (Celaut microVM)
- server-http.mjs  0.0.0.0:8080 — /health, /mcp (Streamable HTTP, same tools),
                   and a REST API (/api/*) mirroring every method
- core/lib/writes/tools.mjs  copies of the mcp/ modules
- Dockerfile, service.json (port 8080 http, api.ergoplatform.com net tag),
  start.sh, pack_config.json, package.json, README.md

Both transports back every read and write with a SEED signer or an UNSIGNED
signer; unsigned mode keeps no key in the process and returns an unsigned tx.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…me fork branch

The .service pinned reputation-system to
github:agenticaihome/reputation-system#fix/seed-signer-derivation — a feature
branch on a fork that could be deleted, breaking installs. Upstream
reputation-systems/reputation-system:master already carries the full /node
export (NautilusSigner/SeedSigner/UnsignedSigner + create_*_with_signer) and
the @Scure BIP-39/32 Nautilus-compatible derivation, with dist/ committed.
Re-point both root and .service to github:reputation-systems/reputation-system
(canonical, undeletable). Verified: install exposes reputation-system/node and
dist/signer.js contains the @Scure derivation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…vice (DRY)

Addresses the review on reputation-systems#13: the ~470-line mcp/core.mjs and its byte-for-byte
copy under .service/ hand-ported logic that already lives in src/lib/ergo/*, so
the two would drift.

Reads now reuse src — no re-implementation:
- mcp/_entry.mjs re-exports the read surface straight from src/lib/ergo/
  {sourceFetch,sourceObject,envs,hashUtils,utils}.ts.
- mcp/build.mjs (esbuild, `npm run build:mcp`) compiles it into one Node-loadable
  ESM module, mcp/_generated/lib.bundle.mjs (committed so the sealed VM needs no
  build toolchain). Browser-only edges are rewritten: reputation-system →
  reputation-system/node (external, same package writes.mjs uses → no drift),
  $app/environment → stub (browser=false), dompurify → passthrough stub
  (read-only data, only JSON.parsed).
- mcp/core.mjs is now an 84-line thin adapter (was 470) that re-exports the
  bundle and defaults explorerUri.

Writes keep the necessary thin Node signer adapter (writes.mjs/lib.mjs) — that's
glue, not duplication (sourceStore.ts is browser-ergo-bound).

Dedup mcp/ ↔ .service/: removed the four copied modules from .service/;
server-http.mjs now imports ../mcp/{core,lib,writes,tools}.mjs. The Dockerfile
preserves the sibling layout in the VM (/app/service + /app/mcp) so ../mcp
resolves identically. One source of truth, no committed duplicate logic.

deps: align mcp/ reputation-system to upstream github:reputation-systems/
reputation-system (matching root + .service; the fork pin was missed in 8c78ab7);
add esbuild devDependency + build:mcp script.

Net: ~1,250 lines of hand-maintained/duplicated read logic removed; ~150 lines of
build glue + an 84-line adapter added (plus the generated bundle artifact).

Verified: npm install; build:mcp; node --check all .mjs; core.mjs loads with no
svelte/$app/dompurify errors; stdio + HTTP tools/list both return the same 27
tools; REST read returns a clean []; unsigned-mode write returns an unsigned
EIP-12 tx. No on-chain submit, no secrets.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants