Skip to content

feat: Agent-Friendly MCP Layer 1 (v0.2.0)#4

Merged
BarisSozen merged 14 commits into
mainfrom
feat/agent-friendly-layer1
May 18, 2026
Merged

feat: Agent-Friendly MCP Layer 1 (v0.2.0)#4
BarisSozen merged 14 commits into
mainfrom
feat/agent-friendly-layer1

Conversation

@BarisSozen
Copy link
Copy Markdown
Member

@BarisSozen BarisSozen commented May 18, 2026

Agent-Friendly MCP — Layer 1 (v0.2.0)

Hardens the Hashlock MCP server for autonomous-agent use. Spec + plan: Hashlock-Tech/hashlock-markets docs/superpowers/{specs,plans}/2026-05-18-agent-friendly-layer1*.

What shipped

  • Fixed get_htlc (cca80a2): switched the broken getHTLCStatus query to getHTLCs (the htlcs query the backend actually serves). getHTLCStatus requested htlcStatus.initiatorHTLC, a field absent from the flat HTLCStatusResult schema, so GraphQL validation rejected every call — the only read tool had never worked against the deployed backend. Replaced the false-green test (it mocked the rejected shape) with real array-shape + empty-array tests.
  • Structured error envelope (4c93e4e, a27f8d9): every tool now returns { error: { code, message, is_retryable, recovery_hint, details } } as normal content on failure (8 classified codes; UNKNOWN never masks the original message). Deliberately not an MCP isError protocol fault — documented in toErrorEnvelope.
  • In-process idempotency guard (b058f2b, f139fe8): optional client_request_id on the 5 write tools; a context-losing agent retrying the same write within a session replays the first result instead of double-submitting on-chain. Concurrent same-key calls dedup (in-flight Promise cache); failed writes stay retryable. Best-effort/session-scoped; durable backend dedupe tracked in hashlock-markets#355.
  • Discovery tools (d960d97, f5fa561): read-only list_supported_pairs, list_open_rfqs, list_my_trades — lets an agent rebuild state after context loss and discover markets without hardcoding.
  • Description homogenization (76e3019, b3ed9a9): create_htlc/withdraw_htlc/refund_htlc/respond_rfq brought to the USE WHEN / DO NOT USE WHEN / PARAM NOTES template; per-tool pin tests (mutation-verified) guard the LLM-routing markers; create_rfq intent-compiler untouched.
  • Testability (6f0307a): pure okContent helper so handler shape is unit-testable.
  • v0.2.0 positioning sweep (24c514d) + version sync / page-bound enforcement (0d27854).

Test plan

  • npm run lint (tsc --noEmit) — clean
  • npm test — 65/65 pass (5 files)
  • npm run build (tsup) — emits dist/index.js + dist/index.d.ts
  • CodeRabbit review on this PR
  • After review: push origin/main, then npm + MCP-registry publish (operator action — intentionally not done here)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Release version 0.2.0 with atomic settlement layer capabilities.
    • Expanded cross-chain support for Ethereum and Sui; Base, Arbitrum, Solana, and TON planned.
    • New tools for atomic settlement workflows: RFQ creation/response and HTLC management.
    • Idempotency support for write operations via request ID deduplication.
    • Query supported trading pairs across networks.
    • Enhanced error classification with recovery guidance.
  • Documentation

    • Updated README and installation guide emphasizing Hashlock as atomic settlement platform for cross-chain trading.

Review Change Stack

BarisSozen and others added 12 commits May 18, 2026 17:56
Version bump 0.1.12 -> 0.2.0, createRFQ baseChain/quoteChain cast (SDK type lag), README/smithery/llms-install/registry metadata refresh. Clean base for the agent-friendly Layer 1 work.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…for all inputs

getHTLCStatus queries htlcStatus.initiatorHTLC, absent from the flat HTLCStatusResult schema, so GraphQL validation rejected EVERY call. Switch to getHTLCs (htlcs query, actually served). Replace the false-green test that mocked the rejected shape.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Heuristic classifier (UNAUTHORIZED/RATE_LIMITED/UPSTREAM_RPC_ERROR/RFQ_EXPIRED/NO_LIQUIDITY/TRADE_NOT_FOUND/VALIDATION_ERROR/UNKNOWN) with recovery hints; UNKNOWN never masks the original message.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…envelope

Bare /50\d/ and /network/ misclassified validation errors (e.g. 'amount 500
invalid', 'unsupported network') as retryable UPSTREAM_RPC_ERROR. Scope to
5xx phrases/status-anchored numerics + specific network-failure phrases;
validation phrases now fall through to VALIDATION_ERROR (non-retryable).
Add negative + non-Error-throw tests. Document that the error envelope is
intentionally structured content, not MCP isError.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…respond_rfq to template + pin tests

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ed assertions

Old block only checked file-wide substring presence (any single USE WHEN passed all per-tool tests; >=6 floor pre-satisfied), so it could not catch a per-tool description regression. Replace with per-tool assertions anchored to text unique to each description, mirroring the create_rfq pin bar. Mutation-verified: removing a tool's DO NOT USE WHEN line now fails that tool's test.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Best-effort, same-process scope; prevents context-loss retries from double-triggering writes. Durable dedupe tracked as a backend issue.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cached the resolved value only, so two concurrent same client_request_id calls both missed the cache and both executed the write (MCP does not serialize tool requests). Cache the in-flight Promise and evict on rejection so op runs exactly once per key under concurrency while keeping failed writes retryable.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Holistic-review pre-publish fixes: (1) McpServer announced stale 0.1.12 while package.json is 0.2.0 — every client/registry would see the wrong version. (2) list_open_rfqs/list_my_trades page/pageSize descriptions promised 1-100 / 1-based but zod was z.number().optional() — tighten to .int().min(1).max(100) so an out-of-range arg fails fast with a clear schema error instead of an opaque downstream one.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 18, 2026

Warning

Rate limit exceeded

@BarisSozen has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 3 minutes and 49 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 9a359924-76ed-4f73-9711-9ae02664d6c7

📥 Commits

Reviewing files that changed from the base of the PR and between 0d27854 and b00f1e2.

⛔ Files ignored due to path filters (2)
  • package-lock.json is excluded by !**/package-lock.json
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (7)
  • package.json
  • server.json
  • src/__tests__/errors.test.ts
  • src/__tests__/idempotency.test.ts
  • src/index.ts
  • src/lib/errors.ts
  • src/lib/idempotency.ts
📝 Walkthrough

Walkthrough

This PR upgrades Hashlock MCP to version 0.2.0, introducing infrastructure for error classification, session-level idempotency, and standardized tool response handling. New utility libraries are created, integrated into tool handlers, and covered by comprehensive tests. Package metadata and documentation are updated to reflect the atomic-settlement positioning.

Changes

Version 0.2.0 Release: Error Handling, Idempotency & Tool Infrastructure

Layer / File(s) Summary
Version and Branding Updates
package.json, README.md, llms-install.md, smithery.yaml, server.json, .gitignore
Version bumped to 0.2.0 across all metadata. Documentation rewritten to emphasize atomic settlement, cross-chain HTLCs, and agent workflows. Keywords updated to highlight agent-economy and atomic-settlement. New server.json manifest added declaring MCP server capabilities, tools, and environment requirements. .gitignore updated for build artifacts.
Tool Content Response Wrapper
src/lib/result.ts, src/__tests__/result.test.ts
New ToolContent type and okContent() helper standardize MCP tool response envelopes by wrapping values as pretty-printed JSON text blocks.
Error Classification and Handler Wrapping
src/lib/errors.ts, src/__tests__/errors.test.ts
Introduces ErrorCode union and Classification interface with regex-based error-message matching rules. classifyError() maps errors to codes and retryability flags. toErrorEnvelope() wraps thrown values into structured error content. wrapTool() catches exceptions in async handlers and returns error envelopes as normal content rather than MCP errors. Comprehensive tests verify classification logic, regression guards against false-positive upstream detection, and proper envelope structure.
In-Process Idempotency Guard
src/lib/idempotency.ts, src/__tests__/idempotency.test.ts
Session-scoped IdempotencyGuard using in-memory Map for per-key operation deduplication. Caches in-flight promises, returns same promise for concurrent calls, evicts failed entries to permit retries. Tests verify per-key caching, cross-key isolation, failure non-caching, concurrent handling, and eviction semantics.
Supported Pairs Constant
src/lib/pairs.ts, src/__tests__/pairs.test.ts
Exports SUPPORTED_PAIRS array and SUPPORTED_PAIRS_LINE formatted string as single sources of truth for chain-qualified asset pairs. Tests pin expected pair set and validate prose line format.
Tool Handler Integration
src/index.ts
Integrates all new utilities. Server version updated to 0.2.0. Idempotency guard initialized at module scope. HTLC and RFQ write tools gain optional client_request_id schema fields and wrap SDK calls with idempotency.remember(). New read-only list_supported_pairs tool added. All tools refactored to use okContent() responses and wrapTool() handler wrapping. Tool descriptions enriched with routing markers (USE WHEN/DO NOT USE WHEN) and refined parameter guidance.
Tool Handler Validation
src/__tests__/tools.test.ts
HTLC retrieval tests shift from getHTLCStatus to getHTLCs with array results. New tests validate listRFQs and listTrades pagination and status variables. Regression tests load src/index.ts and assert tool descriptions retain routing markers and semantic phrases.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A settlement layer springs to life today,
With guards for idempotency to light the way,
Error codes classified, each tool now wrapped,
And pairs so canonical, nothing is lapped—
Version 0.2 hops forward, atoms settle in place! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: Agent-Friendly MCP Layer 1 (v0.2.0)' directly and clearly summarizes the main change: hardening the Hashlock MCP server for autonomous-agent use with a major version bump to v0.2.0.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/agent-friendly-layer1

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/index.ts (1)

205-208: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Reject SUI RFQs that omit the chain.

The tool description says SUI legs require baseChain/quoteChain, but the schema still accepts baseToken: "SUI" or quoteToken: "SUI" with no chain and only fails downstream. Add a local guard before hl.createRFQ(...) so ambiguous SUI requests return a structured validation error immediately.

🛡️ Suggested guard
   wrapTool(async ({ baseToken, baseChain, quoteToken, quoteChain, side, amount, expiresIn, isBlind, client_request_id }) => {
+    if (baseToken.toUpperCase() === 'SUI' && !baseChain) {
+      throw new Error('baseChain is required when baseToken is SUI');
+    }
+    if (quoteToken.toUpperCase() === 'SUI' && !quoteChain) {
+      throw new Error('quoteChain is required when quoteToken is SUI');
+    }
+
     // TODO: SDK type def (CreateRFQInput) lags backend — baseChain/quoteChain
     // are accepted by the GraphQL `createRFQ` mutation but not yet typed in
     // `@hashlock-tech/sdk`@0.2.0. Cast to bypass DTS build; remove once SDK

Also applies to: 215-221

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/index.ts` around lines 205 - 208, Add a pre-check before calling
hl.createRFQ that rejects any RFQ where either baseToken or quoteToken equals
"SUI" (case-insensitive) while the corresponding baseChain or quoteChain is
missing/empty; detect tokens via baseToken/quoteToken and chains via
baseChain/quoteChain, and return/throw a structured validation error (matching
the existing Zod-style error shape) immediately instead of letting the request
proceed to hl.createRFQ. Ensure the same guard is applied to the other
hl.createRFQ invocation in this module so all ambiguous SUI RFQs fail fast with
a clear validation message.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@server.json`:
- Line 4: The manifest's "version" value (currently "0.1.12" in server.json) is
out of sync with package.json's version ("0.2.0"); update the manifest version
to match package.json (or vice‑versa if package.json is wrong) so both version
fields are identical, and ensure the change is applied to the "version" key in
server.json and the package.json "version" token (and add a brief check in
release notes or CI to keep them in sync).

In `@src/index.ts`:
- Around line 29-30: The idempotency guard created by createIdempotencyGuard()
is currently keyed only by client_request_id, causing different write tools
(e.g., respond_rfq, create_htlc and the other four write functions referenced)
to share results; change the idempotency key composition to include the
tool/operation name and a stable payload fingerprint (hash of the serialized
arguments) so keys are namespaced per operation, and update the guard logic to
reject an attempt to reuse an existing key when the stored fingerprint differs
from the new payload (return an explicit mismatch/error rather than the cached
result); update all usages where idempotency is invoked (the variable
idempotency and calls around respond_rfq/create_htlc and the other four write
tools) and add a regression test that reuses the same client_request_id across
two different tools/argument sets to assert the second call is rejected or
flagged as a mismatch.

In `@src/lib/errors.ts`:
- Around line 32-33: The code currently sets message with String(err) which
loses message fields on plain object throwables; update the message extraction
in src/lib/errors.ts (the const message assignment and the similar occurrence
around lines 58-59) to: if err is an Error use err.message; else if err is an
object with a "message" property use that property (String((err as
any).message)); otherwise fall back to String(err). Modify the assignments near
the RULES loop and the second occurrence so both preserve object.message when
present.

In `@src/lib/idempotency.ts`:
- Around line 14-25: The idempotency Map named "cache" used by remember(key, op)
never evicts successful entries causing unbounded memory growth; replace it with
a bounded cache (either integrate an LRU with max-size or add TTL+LRU logic) so
entries expire: implement a capped LRU (or use a library like lru-cache) keyed
by the same key string and store the Promise values, or add a timestamp per
entry and evict stale entries on access and enforce a max size by removing the
least-recently-used item when inserting; ensure failed ops still delete the key
(keep the existing catch that calls cache.delete(key)) and update references
from the Map to the new cache implementation in remember.

---

Outside diff comments:
In `@src/index.ts`:
- Around line 205-208: Add a pre-check before calling hl.createRFQ that rejects
any RFQ where either baseToken or quoteToken equals "SUI" (case-insensitive)
while the corresponding baseChain or quoteChain is missing/empty; detect tokens
via baseToken/quoteToken and chains via baseChain/quoteChain, and return/throw a
structured validation error (matching the existing Zod-style error shape)
immediately instead of letting the request proceed to hl.createRFQ. Ensure the
same guard is applied to the other hl.createRFQ invocation in this module so all
ambiguous SUI RFQs fail fast with a clear validation message.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: a450cac1-faea-48ac-a46a-90219b1d0230

📥 Commits

Reviewing files that changed from the base of the PR and between 4361ef0 and 0d27854.

📒 Files selected for processing (16)
  • .gitignore
  • README.md
  • llms-install.md
  • package.json
  • server.json
  • smithery.yaml
  • src/__tests__/errors.test.ts
  • src/__tests__/idempotency.test.ts
  • src/__tests__/pairs.test.ts
  • src/__tests__/result.test.ts
  • src/__tests__/tools.test.ts
  • src/index.ts
  • src/lib/errors.ts
  • src/lib/idempotency.ts
  • src/lib/pairs.ts
  • src/lib/result.ts

Comment thread server.json Outdated
Comment thread src/index.ts
Comment thread src/lib/errors.ts Outdated
Comment thread src/lib/idempotency.ts
BarisSozen and others added 2 commits May 18, 2026 21:02
CI: package.json had @hashlock-tech/sdk ^0.2.0 (v0.2.0 sweep) but lockfile + all tests/build ran against 0.1.x — pin to ^0.1.4 (the verified version) and regenerate lockfile; SDK 0.2.0 upgrade is a separate tested change. CodeRabbit: server.json version 0.1.12→0.2.0 (registry-publish blocker); errors.ts preserves .message for non-Error throwables (was [object Object]); idempotency key now scoped by tool+payload so a reused client_request_id can't replay an unrelated result; bounded the idempotency Map (FIFO cap 1000) to prevent unbounded growth.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…h guard

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@BarisSozen
Copy link
Copy Markdown
Member Author

Re the outside-diff comment on src/index.ts ~205-208 (reject SUI RFQs that omit the chain): deferred deliberately, not ignored. create_rfq is intentionally untouched in this PR — its intent-compiler description + the pin tests guarding it are load-bearing in the Agent-Friendly MCP Layer 1 scope. The schema-level SUI-chain refinement is a reasonable hardening but changes pre-existing create_rfq behavior, so it is filed as a focused follow-up to be implemented + pin-tested in isolation: #5.

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.

1 participant