Model Context Protocol (MCP) integration

Model Context Protocol integration. Two standard `/mcp` servers, both connector-credentials only — the knowledge base, deals and intents on agents (18 tools) and payments reads plus `propose_*` proposals the account holder confirms in the app on the test / live payments hosts (19 tools) — plus the bespoke `/api/mcp/*` REST surface. Connector keys, OAuth, the live opt-in, and wiring for Claude Code, Claude Desktop, claude.ai connectors and curl.

Guides/Integration

YieldFabric exposes MCP surfaces so AI clients (Claude, Cursor, other MCP-compatible hosts) can use YF as a knowledge base, read wallet state, and propose money movements that the account holder confirms in the app — through stable tools.

This is an optional AI-tooling surface. Regular web apps and backend services use auth REST; JWT-selected payments-direct GraphQL for payments mutations and mode-bound PAY/MQ/RPC reads; the GraphQL gateway for cross-subgraph/shared-core queries; and same-instance message-status polling for settlement.

Three surfaces — pick by client

Surface
Knowledge base, deals & intents
Where
agents service — POST https://agents.yieldfabric.com/mcp
Protocol
Standard MCP (JSON-RPC 2.0, Streamable HTTP); connector credentials only
Use it from
Claude Code, Claude Desktop, claude.ai custom connectors, MCP Inspector — any real MCP client. 19 tools: 16 knowledge-graph tools (including the counted document-ledger findings) plus deal_list, deal_get, intent_status.
Surface
Payments reads + proposals
Where
payments service — POST https://pay.test.yieldfabric.com/mcp (test chain 153) / POST https://pay.live.yieldfabric.com/mcp (live chain 151)
Protocol
Standard MCP, same bridge; connector credentials only
Use it from
The same clients. 19 tools: 14 reads (whoami, wait_for_settlement, assets, contacts, pipeline tasks, deal agreements), four propose_* proposals and intent_status. Nothing on this server executes a payment — a proposal is confirmed by the account holder in the app.
Surface
Wallet / payments tools (bespoke)
Where
payments service — https://pay.test.yieldfabric.com/api/mcp/* (and the pay.live… twin)
Protocol
Bespoke REST (tools/call, tools/list, …)
Use it from
YF-internal agent runtimes and custom integrations that speak the bespoke shape. Not connectable from standard MCP clients — it has no JSON-RPC envelope.

If you're wiring Claude: you want the two /mcp surfaces — knowledge on agents, money on payments; a client connects both with one connector credential.

Connector credentials — what both /mcp servers admit

Every request to either /mcp — initialize included — needs a bearer that resolves to a connector session (session_kind: connector). There are two ways to hold one:

  • A connector API key. Mint it once, from a direct personal session:

    curl -X POST https://auth.yieldfabric.com/auth/api-key/generate \
      -H "Authorization: Bearer $ONE_TIME_TOKEN" \
      -H 'Content-Type: application/json' \
      -d '{"service_name":"claude-code","kind":"connector","allowed_chains":["153"]}'
    # → { "api_key": "yf_api_…", "kind": "connector", "allowed_chains": ["153"], … }
    

    kind is decided once, on the key row. allowed_chains is the set of chains its sessions may run on (omitted ⇒ the test chain for a consumer-role caller). Send the raw yf_api_… value as the bearer; each server exchanges it at auth for a short-lived session and caches the result briefly (payments: at most 60 s per key and chain), so the key never expires mid-session and revoking it (POST /auth/api-keys/{id}/revoke) takes effect within minutes, not at the key's expiry. One connector key serves both servers.

  • An OAuth grant. Add the server URL with no header: the 401 challenge points at /.well-known/oauth-protected-resource, and the consent flow is auth's. The access token carries the server's resource URI in aud (https://agents.yieldfabric.com/mcp or https://pay.test.yieldfabric.com/mcp / https://pay.live.yieldfabric.com/mcp); each server checks its own audience, so a token minted for the agents server is 401 on payments and vice versa.

Everything else — a personal yf_api_… key, a login JWT, a delegation JWT, a service token, a token minted for another resource — answers 401 with WWW-Authenticate: Bearer resource_metadata="…/.well-known/oauth-protected-resource", error="invalid_token" and a JSON {"error": "…"} carrying one of two texts. Which one depends only on the credential, not on which server you hit: both servers run the same two checks in the same order — the audience first, the session kind last — so one bearer gets one text wherever it is presented.

  • "This session is not a connector credential: mint a connector API key (POST /auth/api-key/generate {"kind": "connector"}) or connect via OAuth" — a personal yf_api_… key (the default kind). The server exchanges the key itself, so the exchange binds it to that server and no audience check runs; the resulting session is then found not to be a connector.
  • "Token was not issued for this resource (<this server's /mcp URL>); connect through this server's OAuth flow" — any bearer that is not a yf_api_… key and does not name this server's resource URI in aud: a login JWT, a delegation JWT, a service token, an OAuth token minted for the other server, or a connector JWT you exchanged at auth yourself instead of handing the server the key. Only OAuth grants made for that server carry its audience.

A bearer auth itself rejects — an expired OAuth access token, a revoked or unknown yf_api_… key, a tampered JWT — is the same 401 with the same challenge on both servers (the body names the validation failure rather than one of the two texts above), so an MCP client that follows the challenge simply re-runs OAuth, or you re-mint the key.

Never 403. When auth cannot be reached while exchanging or validating, the answer is 503 with Retry-After: 5 and no challenge — retry, do not re-authorize.

What a connector session may do. Read (both servers) and propose (payments). It carries no CryptoOperations / PolicyExecution, and it is refused before any handler runs wherever a write would execute: a payments GraphQL mutation from a connector session is FORBIDDEN with extensions.code: connector_read_only (httpStatus: 403); every write-shaped payments REST route (anything but GET / HEAD / OPTIONS, a few POST-shaped reads excepted) is 403 {"error": "FORBIDDEN: connector credentials can only propose …", "code": "connector_read_only"}; and the bespoke /api/mcp/tools/call direct writes answer the same 403 {"error": "FORBIDDEN: …"} before the handler runs. No message id is ever minted for such a call. Confirming an intent, minting a delegation and managing keys all require a direct personal session.

Live money is opt-in per person. While users.live_connectors_enabled is false (the default), no connector credential of yours can be generated for, minted on or refreshed onto a live chain: a connector key naming 151 is 403 {"error", "code": "live_connector_opt_in_required", "chain_id": "151"} at generation, exchange and refresh, and an OAuth grant for the pay.live… resource is refused with 400 invalid_target. The flag is read with GET /auth/users/me/live-connectors (any self-scoped session, a connector included, so it can learn why a live mint was refused) and written only with PUT /auth/users/me/live-connectors {"enabled": true} from a direct personal session at the live verification floor (KycVerified; otherwise 403 live_verification_required). Disabling never needs the floor and ends live mints and rotations from the next call. Never enable it from an agent; the account holder does it in the app.

The payments surface (POST /mcp on pay.test… / pay.live…)

The URL is the chain. A connector key session lands on the chain the exchange bound it to; a session minted for another chain answers every tools/call with an isError result whose code is CHAIN_MODE_MISMATCH — route the credential to the matching host. Every downstream call the tools make carries an on-behalf-of token minted for that callee — the bearer never leaves the host.

claude mcp add --transport http yieldfabric-payments https://pay.test.yieldfabric.com/mcp \
  --header "Authorization: Bearer yf_api_..."   # a CONNECTOR key

Catalog (19 tools)

Tool
whoami
Reads / proposes
Entity, role, session_kind, chain_id + mode (LIVE / TEST), wallet id and address, permissions, gates.
Tool
wait_for_settlement {message_id, timeout_s ≤ 60}
Reads / proposes
{state: pending | executed | settled | failed, executed_at, post_processed_at, error?, retry_after_s?, timed_out} — the settled predicate of the message-status endpoint, held server-side (default 30 s).
Tool
list_assets {limit?, cursor?}, get_asset {asset_id}
Reads / proposes
The asset catalog of this chain.
Tool
list_contacts {limit?, cursor?}, get_contact {contact_id}, list_connection_requests {status?, limit?, cursor?}
Reads / proposes
Connections with entity / wallet enrichment.
Tool
pipeline_list_tasks {domain?}, pipeline_describe_task {task_name}, pipeline_propose {steps} (a validator — no side effect), pipeline_find_predecessors {task_name}
Reads / proposes
The deal-plan task registry.
Tool
yieldfabric_dealAgreement {deal_id}, yieldfabric_dealAgreementGraph {deal_id}, agreement_next_scheduled_payment {deal_id}
Reads / proposes
The deal-flow agreement projection.
Tool
propose_send {asset_id, amount, destination_id? | destination_wallet_id? | contract_id?, obligor?}
Reads / proposes
Proposes an instant send from this session's wallet on this chain. amount is human-readable (scaled at dispatch).
Tool
propose_accept {payment_id}
Reads / proposes
Proposes accepting a pending payment.
Tool
propose_swap_credit_for_cash {denomination, amount, obligor, deadline?}
Reads / proposes
Proposes an obligor-payment swap (deadline RFC 3339, default 30 days).
Tool
propose_complete_swap {swap_id}
Reads / proposes
Proposes completing an open swap.
Tool
intent_status {intent_id, workspace_id?}
Reads / proposes
The intent's status, message_id once submitted, result / error, confirm_url and recovery (none / in_progress). An intent the caller may not see is not_found.

Every propose_* also takes request_id? (folded into the proposal identity — pass a distinct value to open a second, intentionally identical proposal), working_group_id? (the workspace the intent is filed in; default the caller's personal workspace), title? and rationale? (what the confirming person reads). Reads are annotated readOnlyHint: true, idempotentHint: true; proposals readOnlyHint: false, destructiveHint: false, idempotentHint: true — nothing here is destructive, the destructive act is the human's confirmation. The seven direct writes (instant send, accept, swaps, connection requests) are absent from this catalog, not present-but-refusing: calling one is JSON-RPC -32602 Unknown tool.

Propose → confirm → settle

  1. propose_send {…} → {intent_id, workspace_id, status: "proposed", expires_at, identity_hash?, confirm_url, created}. The intent pins the chain (this host's), the wallet (the session's default — a walletless session is CHAIN_ACCOUNT_REQUIRED) and the actor from the validated claims, and expires 15 minutes after filing. The same proposal while one is open returns the same intent with created: false.
  2. The account holder opens confirm_url (https://app.yieldfabric.com/private?reader=intent/<workspace_id>/<intent_id>) and confirms from a direct personal session in the app. Nothing an agent holds can confirm.
  3. intent_status {intent_id, workspace_id} — proposed → approved → submitting → submitted → completed | failed; only proposed can become cancelled or expired. From submitted on, message_id is set.
  4. wait_for_settlement {message_id} until settled (or failed), then read the resulting records.

Results and failures

Every tool advertises an outputSchema derived from its response type, and a successful result carries the same document as text and as structuredContent. A failure is a result, not a protocol error: HTTP 200, isError: true, with {"error": {"code", "message"}} in the text block — and, because these tools advertise an output schema, no structuredContent on the error (the error document cannot validate against the success schema, and conforming clients validate whatever structuredContent they are given). Codes: invalid_params, not_found, downstream_failed, obo_unavailable, obo_refused, tool_failed, CHAIN_MODE_MISMATCH, CHAIN_ACCOUNT_REQUIRED, intent_refused (agents refused the caller), intent_conflict (agents answered 409). The list tools take limit (≤ 100) and cursor and answer next_cursor while more remain; the whole serialised result is capped at 256 KiB. The skill's references/payments.md §Standard MCP carries the same catalog with examples; the wire reference is the payments MCP spec.

The knowledge-base surface (POST https://agents.yieldfabric.com/mcp)

Standard MCP over stateless Streamable HTTP: initialize, ping, tools/list, tools/call; notifications get 202; GET /mcp is 405 (no server-push stream). Admission is the connector rule above — the same connector key or an OAuth grant whose aud is https://agents.yieldfabric.com/mcp. Eighteen tools: fifteen read and reason over the knowledge graphs YieldFabric already built (file → KG; the connector never re-processes), and three read the deals and financial intents the caller is party to. Everything is scoped to what the caller can access.

The knowledge tools come in two halves, and the split is the thing to understand before wiring anything to them:

  • Ranked (search, ask) returns the best matches and never a total, so an empty result means "nothing scored highly" — not that something is absent.
  • Exhaustive (count, enumerate, count_in_set) returns a total over a defined scope, with no LLM in the path. These are the tools that can establish absence, and the frozen-set family is what makes such a claim checkable later.

Reading the corpus

Tool
graphs {limit?, cursor?}
What it does
List the post-processed knowledge graphs you can access, grouped by the room / workspace they live in — each room with its kind / label (deal room / workspace / private workspace / direct message / platform), its graphs' KG:<uuid> ids, names, and entity/document counts. Paged (limit default 50, cap 100; largest rooms and graphs first): count is this page, total the whole count. The entry point for building a picture.
Tool
fetch {id, offset?}
What it does
One item by id: KG:<uuid> → a structural picture of a graph (entity types, schema triples, most-connected entities, relationship vocabulary); DOC:<uuid> → the document's text, windowed at up to 40k chars — shorter when a window would not fit one result — (page the rest with offset/next_offset); FRAME:<kg>:<frame> → one entity; FILE:<room>:<file> → an original upload (text as text, paged with offset / has_more; images viewable — an image too large to inline is downscaled and served as JPEG, with served_as / converted_from / downscaled_to in the companion text block; audio/video/PDF by identity only — read those via their DOC:). DOC/KG/FRAME results carry their source.
Tool
entities {query?, type?, graph?, limit?, cursor?}
What it does
The graph's nodes — parties, instruments, legal concepts, organisations. No args → type overview; query/type/graph → matching entities, each with its source. Paged (limit default 25, cap 100; keyset cursor; next_cursor, also under its older name nextCursor).
Tool
explore {entity, limit?, cursor?}
What it does
Traverse from one entity — its connected entities, the relationship, and slot roles so you can read edge direction ('who are the parties to X', 'what is Y connected to'). Connections are paged (limit default 60, cap 100; next_cursor while more remain); an ambiguous name answers ambiguous: true with candidates.
Tool
search {query, limit?, cursor?, graph?, set?, as_of?, effort?}
What it does
Find relevant material across all your rooms at once. Each hit carries source (which knowledge graph + which room) and often structure (the graph nodes that passage grounds). Paged over the ranked window: limit 1–25 (default 10), an offset cursor valid for the same query and scope, next_cursor while deeper ranks remain, at most 100 deep.
Tool
ask {question, graph?, set?, as_of?}
What it does
Grounded answer with citations — citations and a sources roll-up name the graph + room each fact came from (sync, ~5–30 s). Answers from one room at a time: the room holding the most graphs unless you pass graph or set, with scope_note naming what was skipped. Use search when the question spans rooms.
Tool
whoami
What it does
Which YF account the connector is authenticated as (entity id, email, role, acting-as group, wallet and chain defaults), and which rooms it can see.
Tool
files {room, limit?, name?, cursor?}
What it does
The original files filed in one room (notes, photographs, recordings, documents as uploaded), newest first, each with a FILE:<room>:<file> id fetch accepts — for seeing a photo, reading a short note whose exact words matter, or confirming what a room holds. Paged (limit default 50, cap 100; keyset cursor over upload time — after / next_after / has_more are the older names); name filters by filename substring before paging.

Counted answers, and claims that stay checkable

Tool
count {term, mode?, graph?, room?, as_of?}
What it does
Exhaustive tally of how many documents contain a term, over everything you can access (or one graph / one room). documents: 0 here is a defensible absence finding; an empty search is not. capped: true means the scan hit its bound and the number is a floor.
Tool
enumerate {term, mode?, graph?, room?, as_of?, limit?, cursor?}
What it does
Every matching document id, in stable order, never ranked — pages of at most 100 (limit default 100; keyset cursor, next_cursor / the older after / next_after); exhausted: true on the last page is what makes a coverage claim auditable (a page cut short by size is not exhausted). Refuses outright rather than truncating when a term is too broad.
Tool
freeze_set {terms, mode?, as_of?}
What it does
Freeze an immutable, hash-pinned membership snapshot: "these 412 documents, on this date, under this predicate". Returns {job_id, status: "running"} immediately (see the async pattern below). The one tool here that writes — it files a freeze job — so it is annotated readOnlyHint: false, idempotentHint: false, destructiveHint: false.
Tool
set_status {job_id}
What it does
Poll a freeze job. status is running, done or failed (the vocabulary of both the tool description and its outputSchema); done carries the set_id, failed the error.
Tool
count_in_set {set_id, terms, mode?, as_of?, limit?, cursor?}
What it does
Exhaustive tally inside a frozen set, with set_documents as the denominator — a total over known membership rather than over whatever a ranking surfaced. results is paged over the terms you pass (limit default and cap 100 terms per call; pass the same terms with next_cursor).
Tool
get_set {set_id, limit?, cursor?}
What it does
One set's terms, freeze time, document count and its members — the frozen DOC: ids, paged (limit default 50, cap 100) — confirm what it covers before relying on a count against it.
Tool
sets {limit?, cursor?}
What it does
The frozen sets you can read, newest first: your own, plus any another workspace has shared with you (shared: true). Paged (limit default 50, cap 100): count is this page, total and shared_count are over all. A shared set is often the only way to reach documents in a workspace you otherwise cannot see.

count, enumerate, freeze_set and count_in_set all take a mode: exact (default — no stemming, so an ACN or a clause number matches only itself), phrase (stemming, OR and -negation), or fuzzy (substring, which matches inside words — never present a fuzzy count as a finding).

Every result names its source. Hits, entities, and citations carry a source: {kg, room} object — which knowledge graph, and which room / workspace / private notebook it came from — so the model can always tell you where a fact originated, and search spans every room you can access in one call. Results drawn from a set another workspace shared with you are the one exception: they carry source: {set: "SET:<uuid>"} and no citation link, because you were given a set, not the room holding it. Retrieval is scoped to what the caller can access (rationale: the agents MCP architecture note).

Deals and intents (reads)

Tool
deal_list {workspace_id, thread_id?, include_terminal?, limit?, cursor?}
What it does
The deals of ONE workspace the caller can see — deals they created, or, as an active member of that room, all of it — newest first, optionally one thread_id (matched exactly — omit the key to list every thread; an empty string names no thread and lists nothing) (include_terminal adds completed, cancelled and failed ones; default false), paged with limit (default 20, cap 100) / a keyset cursor (next_cursor while more remain; truncated: true when a page was cut short by size). Each row carries id, group_id (the workspace), thread_id, status (drafting, committed, pipelined, completed, cancelled, failed), label, the snapshot, the selected_* plan fields, created_by and the timestamps. A workspace the caller is not a member of yields only the deals they created there — never an error.
Tool
deal_get {deal_id}
What it does
One deal by id (the same 22 fields), under the same visibility rule.
Tool
intent_status {intent_id, workspace_id?}
What it does
The authoritative status of a financial intent — the row the payments connector's intent_status reads, returned whole (the REST intent document: 52 fields, the workspace as group_id): status (the state machine above), tool_name and arguments as filed, the pinned chain_id / wallet_id / actor_entity_id, expires_at, approved_at, message_id, result, error, confirm_url, recovery (none / in_progress). workspace_id is an assertion when given (a mismatch is not_found) and no filter when omitted. Visible to the proposer, the pinned actor, or an active member of the workspace it is filed in.

These are store-level authorised reads: anything outside the caller's view is not_found, and existence is not disclosed. No write tool is on this surface — a financial proposal is filed through the payments connector's propose_* tools (that is where chain and wallet context lives), and the deal commit / cancel and intent-propose tools stay on the internal /api/mcp/tools/call route (below).

Results, paging and size

Every tool whose result is JSON advertises an outputSchema and returns the document as text and as structuredContent. A failure is isError: true with no structuredContent (as on payments); its text block is {"error": {"code", "message"}} on deal_list / deal_get / intent_status (invalid_arguments, not_found, downstream_failed) and a plain message on the fifteen knowledge tools. The one exception to the schema rule is fetch: a FILE: result can be an image content block, so fetch advertises no output schema and answers content blocks only. Every list-shaped tool (search, files, graphs, entities, explore, enumerate, count_in_set, get_set, sets, deal_list) takes limit (per-tool default, hard cap 100 — 25 on search — an integer past the cap is clamped, never refused; a limit that is not an integer, such as the string "12", is refused rather than silently defaulted) and an opaque cursor, and returns next_cursor only while more exist; a cursor the tool did not issue is refused rather than read as page one. An oversized page is cut by the server inside its own paging with truncated: true (plus next_cursor to resume at the first item dropped), and the transport's 256 KiB cap is only the backstop behind that. fetch DOC: and fetch FILE: text keep their offset / next_offset window, cut to the same budget.

Auth: two ways in

1. Connector API key header (backend/dev — Claude Code, Claude Desktop). Mint a yf_api_… key with "kind": "connector" as shown above (the general API-key pattern is in docs/building-with-yf.md §API keys), then pass it as the bearer — the server exchanges it per request, so it never expires mid-session. A personal key (the default kind) is 401 here with the hint to mint a connector one. Do not put a raw JWT in config: access JWTs die in 15 minutes.

claude mcp add --transport http yieldfabric https://agents.yieldfabric.com/mcp \
  --header "Authorization: Bearer yf_api_..."   # a CONNECTOR key

or in .mcp.json:

{
  "mcpServers": {
    "yieldfabric": {
      "type": "http",
      "url": "https://agents.yieldfabric.com/mcp",
      "headers": { "Authorization": "Bearer yf_api_..." }
    }
  }
}

2. OAuth login (claude.ai custom connectors — the "Connect" button). Add the connector in claude.ai → Settings → Connectors → Add custom connector → paste https://agents.yieldfabric.com/mcp. The browser opens YieldFabric's login (any sign-in method — email/password, MetaMask, passkey, Dynamic), shows a consent card, and the connector is live; token renewal is silent thereafter. Claude Code/Desktop can use the same flow — omit the --header and run /mcp to authenticate. The grant is a connector session bound to the test chain (a knowledge connector never needs a chain) whose aud is this server's URL — it is not accepted by the payments server, which issues its own.

Under the hood (all standard, nothing to configure client-side): anonymous POST /mcp → 401 + WWW-Authenticate → /.well-known/oauth-protected-resource (agents) → RFC 8414 metadata + RFC 7591 dynamic registration + PKCE code flow (auth service /oauth/*) → consent page (app.../oauth/authorize) → 1-hour MCP access tokens with 30-day silent refresh. The authorization server is documented in auth's OAuth spec.

Calling it manually (curl)

# Handshake (YF_CONNECTOR_API_KEY holds a kind: connector key)
curl -s -X POST https://agents.yieldfabric.com/mcp \
  -H "Authorization: Bearer $YF_CONNECTOR_API_KEY" -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","clientInfo":{"name":"curl","version":"0"}}}'

# Discover tools
curl -s -X POST https://agents.yieldfabric.com/mcp \
  -H "Authorization: Bearer $YF_CONNECTOR_API_KEY" -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'

# Ask the knowledge base (grounded answer + citations)
curl -s -X POST https://agents.yieldfabric.com/mcp \
  -H "Authorization: Bearer $YF_CONNECTOR_API_KEY" -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"ask","arguments":{"question":"What are the key terms of the Acme facility?"}}}'

Async pattern: freeze_set is the one tool here that parks its work. It returns {job_id, status: "running"} immediately — poll set_status with that id every few seconds until the status is terminal (done or failed), then use the returned set_id. Freezing is a single database statement and normally takes seconds; don't wait synchronously, and don't re-submit freeze_set while waiting, which just mints a second snapshot. MCP clients abort tools idle for ~5 minutes.

The wallet/payments surface (/api/mcp/*, bespoke REST)

Served by the payments service. This is not the standard MCP protocol — there is no JSON-RPC envelope, so Claude and other standard MCP clients cannot connect to it directly; it exists for YF-internal agent runtimes and custom integrations.

Endpoint
/api/mcp/resources/wallet/{uri}
Method
GET
Purpose
Fetch wallet AI-view (YAML) by wallet://{wallet_id} URI.
Endpoint
/api/mcp/tools/call
Method
POST
Purpose
Execute one of the 26 wallet/payment/connection/pipeline/agreement/proposal tools (slash name or the public name).
Endpoint
/api/mcp/prompts/get
Method
POST
Purpose
Fetch a context prompt (wallet_context_prompt, connections_context_prompt).
Endpoint
/api/mcp/tools/list
Method
GET
Purpose
Enumerate the 26 tools with input schemas.
Endpoint
/api/mcp/prompts/list
Method
GET
Purpose
Enumerate the 2 prompts with arguments.
Endpoint
/api/mcp/resources/list
Method
GET
Purpose
Enumerate resource templates (wallet://).
Endpoint
/api/mcp/initialize
Method
POST
Purpose
Bespoke handshake — returns server info + capabilities.

Every endpoint is JWT-gated (Authorization: Bearer <jwt>) and runs under the caller's permissions. The 15-minute JWT TTL means this surface suits programmatic callers that mint tokens per session — not static client config. A connector session can read and propose here, but the seven direct writes are refused before the handler runs with 403 {"error": "FORBIDDEN: connector credentials can only propose …"} — it proposes instead.

Wallet-required tools do not deploy an account implicitly. A walletless user or delegation receives CHAIN_ACCOUNT_REQUIRED with the effective entity and chain context. Activate that user/group through auth's POST /entities/{user|group}/{id}/chain-accounts/{chain_id}/activation, wait for ready, refresh on the same chain, and retry the tool once. Route the JWT to the matching pay.test... or pay.live... surface.

Tool catalog (26): yieldfabric/list_assets, get_asset, send_instant_payment, accept_payment, swap_credit_for_cash, complete_swap, list_contacts, get_contact, list_connection_requests, create_connection_request, accept_connection, send_invitation, pipeline/list_tasks, pipeline/describe_task, pipeline/propose, pipeline/find_predecessors, agreement/get, agreement/get_graph, agreement/next_scheduled_payment, whoami, wait_for_settlement, and — under their public names, no slash alias — propose_send, propose_accept, propose_swap_credit_for_cash, propose_complete_swap, intent_status. Read tools/list for the authoritative input schemas (the same registry drives the standard /mcp catalog, so the schemas agree).

curl -s -X POST https://pay.test.yieldfabric.com/api/mcp/tools/call \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"name": "yieldfabric/list_contacts", "arguments": {}}'

The agents service also keeps a bespoke POST /api/mcp/tools/call route (knowledge-document, deal and intent tools, yieldfabric/* names) for the same internal callers, with its routes, status codes and bodies unchanged. Its yieldfabric/deal/list, yieldfabric/deal/get and yieldfabric/intent/status are the same implementations as the /mcp deal_list, deal_get and intent_status above, under their legacy names and with their legacy bodies (intent/status keeps its compact eight-key document rather than the /mcp entry's full one) — so reads there are authorised too, and a deal or intent outside the caller's view answers the route's existing 404. The legacy deal writes on that route (yieldfabric/deal/commit, yieldfabric/deal/cancel, yieldfabric/intent/propose_from_candidate) pass the same check before they touch anything. The /mcp surface above is the external-client story.

Use MCP for agent tooling, not app data flow

MCP is useful when a model-facing tool client needs a compact wallet view, typed knowledge access, prompts, and a safe way to propose a payment for a person to confirm. For product flows controlled by your own app, prefer the direct public APIs:

  • Auth REST for sign-in, token refresh, profile lookup, and API keys.
  • Payments-direct GraphQL for payments mutations and instance-local reads.
  • The GraphQL gateway for cross-subgraph and shared-core queries.
  • Same-instance payments REST polling for settlement confirmation.

See also