AI agents and MCP
AI agents and MCP
The Fabrica MCP server gives AI agents read-only access to the data behind the Fabrica protocol: tokenized properties, the lending market, account portfolios, protocol-wide statistics, parcel geometry, and map images. It implements the Model Context Protocol (MCP), the open standard that assistants and agent frameworks such as Claude, ChatGPT, and Cursor use to call external tools, so any MCP client that supports remote servers can connect to it.
It serves two audiences: people who use an AI assistant and want it to answer questions about Fabrica properties from live data, and developers building agents that reason over tokenized real property.
Everything the server exposes is public data already available through the Fabrica GraphQL API. No API key, account, or wallet is required, and the server never asks for one.
Hosted Endpoints
| Network | URL | Data |
|---|---|---|
| Ethereum mainnet | https://mcp.fabrica.land/mcp | Real US parcels. Responses carry a legal notice. |
| Sepolia testnet | https://mcp-testnet.fabrica.land/mcp | Test properties only. |
The network is fixed per URL: an agent connected to the testnet server cannot reach mainnet data, and vice versa. Add both as separate servers if you want to try things on testnet before mainnet.
The hosted server is stateless. Each request is served by a fresh server instance with no session, the transport is Streamable HTTP with JSON responses, and only POST is accepted (a GET returns 405, and a request body over 1 MB is refused with 413). Browser-based clients can call it directly, since responses carry permissive CORS headers. The hosted endpoints are deployed from the main branch of the public repository, so they track its current tool set, and both serve the property card described below.
Connecting
Claude (claude.ai and the desktop app): Settings, then Connectors, then Add custom connector, and paste the URL.
Claude Code:
claude mcp add --transport http fabrica https://mcp.fabrica.land/mcp
claude mcp add --transport http fabrica-testnet https://mcp-testnet.fabrica.land/mcpCursor (.cursor/mcp.json):
{
"mcpServers": {
"fabrica": { "url": "https://mcp.fabrica.land/mcp" }
}
}Any other client that supports remote MCP servers over Streamable HTTP is configured the same way.
Running Locally
The server also runs locally over stdio from a clone of the repository. It is not published to npm, so install from source (Node 20 or later):
git clone https://github.com/fabrica-land/fabrica-mcp.git
cd fabrica-mcp
npm install
npm run buildThen point your client at the built entry point. With Claude Code:
claude mcp add fabrica -- node /absolute/path/to/fabrica-mcp/dist/index.jsWith Claude Desktop (claude_desktop_config.json), here selecting Sepolia:
{
"mcpServers": {
"fabrica": {
"command": "node",
"args": ["/absolute/path/to/fabrica-mcp/dist/index.js"],
"env": { "FABRICA_NETWORK": "sepolia" }
}
}
}A local server connects to Ethereum mainnet unless told otherwise. All environment variables are optional:
| Variable | Default | Purpose |
|---|---|---|
FABRICA_NETWORK | ethereum | ethereum or sepolia |
FABRICA_API_URL | https://api.fabrica.land/graphql | Fabrica GraphQL API the tools read from |
FABRICA_METASTREET_SUBGRAPH_URL | selected per network | Subgraph the server reads lending-pool statistics from |
FABRICA_MEDIA_URL | https://media.fabrica.land | Media service that renders the map images |
FABRICA_MCP_APPS | enabled on Sepolia, disabled on mainnet | enabled or disabled: whether get_property also serves the property card |
What the Server Can and Cannot Do
Every tool is read-only, and says so in the standard MCP way: each tool is registered with a human-readable title and with annotations readOnlyHint: true, destructiveHint: false, and idempotentHint: true, so a client that gates state-changing tools behind confirmation can treat every Fabrica tool as a plain read. The server queries the public Fabrica GraphQL API (plus a lending-pool subgraph for pool statistics and the Fabrica media service for map images) and returns formatted results. It holds no keys, signs nothing, and cannot transfer a token, list a property, place a bid, or open or repay a loan. An agent that wants to act on what it learns has to do so through the application or directly against the smart contracts with the user's own account.
Mainnet legal notice. On the mainnet endpoint, the server's instructions carry a legal notice written as a statement of facts rather than as directions to the agent. It states that Fabrica properties are real parcels of land in the United States, each held in a trust whose beneficial interest is represented by an ERC-1155 token, and that acquiring, transferring, listing, or borrowing against a token has real-world legal and financial consequences: owning a token means owning the beneficial interest in the trust that holds the property, with the trustee designated by the token holder and by default the holder; owners can carry obligations tied to the land, such as property taxes, maintenance, and environmental liability; transfers and sales are legally binding and can trigger tax events such as capital gains or transfer taxes; the operating agreement (the trust instrument) attached to each token sets out the owner's rights and obligations, and its URL is included in property details; and the data is informational, not financial or legal advice. Property search, property detail, and protocol statistics responses on mainnet also carry a one-sentence version of the notice. The testnet server states instead that its properties are test tokens with no real-world legal or financial effect.
Default filters. Property searches and protocol statistics exclude burned tokens, premints (properties still being added), and tokens below a minimum numeric encoding of Property checks, together with a small set of known spam entries. The default minimum is 72,032, the same threshold the Fabrica marketplace applies to its public listings; the API pairs any minimum with a verified-deed requirement, so the default leaves out every property the marketplace would not sell, including any whose recovery status is not Normal. The minScore input of search_properties (the minimum numeric encoding) overrides the default. A search with ownedBy and no minScore lists every property in that account, as the owner's own holdings appear in the application, and get_portfolio applies no minimum.
Errors. Tools return an error field with a plain-language message instead of raising, and a result that carries one is flagged with the MCP isError marker so clients can tell failure from data. A property that does not exist is reported as such; a failure of the underlying API is reported as an API error carrying the API's own message, so an agent can tell the two apart. A request to the underlying API that does not answer within 20 seconds is reported as an error that suggests retrying, and a map image larger than 5 MB is refused with a request for smaller dimensions. The county outline in get_property_map is an extra layer: if it cannot be fetched, the parcel geometry is still returned and the failure is listed under warnings.
Tool Reference
Eleven tools, all read-only. Inputs marked with an asterisk are required; every other input is optional. Property tools accept either a tokenId or the property slug from its Fabrica URL (for example us/nevada/elko-county/elko/apn-063025003).
| Tool | Inputs | Returns |
|---|---|---|
search_properties | region (US state code), minAcres, maxAcres, minScore, hasListings, hasLoans, ownedBy (account address), limit (default 20, max 100), offset | Matching properties with location, acreage, the numeric encoding of Property checks, estimated value, listing price, loan flag, owner, and property link, plus the total match count for paging (an offset past the end says so rather than reporting no matches) |
get_property | tokenId or slug | Full detail: location, legal description and trust name, operating agreement URL, Property checks (how many pass, with each check's result and the numeric encoding), valuation with per-source pricing, ownership and holders, active loans and DeFi lending pool capacity, active listings and bids (in US dollars when the API prices them so, otherwise in the order's own currency), the owner's description and photos (up to eight, with resized thumbnails) and parcel map image links in both themes, recent activity, whether the property is still a premint, and warnings for recovery, default, or liquidation status. Boundary geometry comes from get_property_map |
get_property_map | tokenId or slug, includeCountyBounds (default true) | GeoJSON FeatureCollection with the parcel boundary (or a point when no boundary exists) and the county outline |
get_property_image | tokenId or slug, theme (dark by default, or light), width, height (100 to 1280 pixels, default 640) | Inline static map image of the parcel boundary, or a pin when no boundary exists |
explain_confidence_score | tokenId or score | Explains Property checks (its title is "Explain Property checks"; the tool name keeps the former identifier): a digit-by-digit breakdown of the numeric encoding's five groups against the 75,342 maximum, with the individual check results when a token is given. The result states that the encoding is positional, not a percentage or a grade, and no percentage is reported |
get_lending_market | status (active, repaid, liquidated, all), borrower, lender, since (ISO date), limit (default 20, max 100) | Market summary (loans by status, loan volume per currency, average APR of active loans), DeFi lending pool statistics (value locked, in use, available, utilization, loan counts), matching loans with their terms and venue, and the ten most recent loan events |
get_borrow_quote | tokenId or slug | DeFi lending pool availability for the property (the advertised maximum principal in whole USDC, rounded down, subject to available pool liquidity, and the available durations) and any active loan on it. The amount is an estimate; the amount and rate are set by a live quote when borrowing |
get_portfolio | address* | Properties held, lending positions as borrower and as lender, credit history, marketplace activity, and total portfolio value for an account, scoped to the server's network and token contract. The outstanding-loans total is null when a loan has no dollar price |
get_portfolio_image | address*, theme, width, height | Inline static map with every property held by the account plotted as points |
get_activity | tokenId, slug, or address; type (for example loan, transfer, sale, mint); limit | Event timeline for a property or an account: mints, transfers, sales, loans started, repaid, or liquidated, and configuration changes |
get_protocol_stats | none | Protocol-wide figures: token count and estimated value, states represented, loan counts and loan volume per currency, lending-pool statistics, active listings, and the token contract address (on mainnet, the retired peer-to-peer lending contracts are listed separately as historical) |
Monetary values are formatted in US dollars where the source provides a dollar figure. Activity amounts are derived from the event's own currency amount and decimals: stablecoin amounts read as dollars and other currencies in their own unit (for example WETH), and the API's dollar figure is used only as a fallback. Loan principals are stated in the loan's own currency, and loan volumes are totaled per currency rather than summed across currencies or presented as dollars. Each loan and lending event names its venue: pool loans read "Pool-based lending", and loans made through the former peer-to-peer integration are labeled as retired. Borrow quotes list only the DeFi lending pool, since that integration has shut down (see Peer-to-Peer Lending). Loan counts are reported by status; the server does not publish an aggregate repayment rate. This table is derived from the server source and checked against the hosted endpoints; the tools/list response of the deployment you are connected to is authoritative.
Property Card (MCP App)
In clients that support MCP Apps, such as Claude on the web and in the desktop app, get_property renders a property card inline in the conversation instead of a block of JSON. The card shows:
- a gallery with the parcel map first, followed by the owner's photos when the property has any
- the property name, location, and acreage
- how many Property checks pass (for example "16 of 18 pass", as on fabrica.land), estimated value, listing price, and borrowing capacity
- Buy (when the property is listed and its token has been issued), Make an offer, Contact owner, and View on Fabrica buttons
Every button opens the property's page on Fabrica, where the user completes the action and signs with their own account. The server never prepares or submits a transaction, so the card changes how the data is shown, not what the server can do. Clients without MCP Apps support receive the same data as JSON.
The card is served as an MCP resource at ui://fabrica/property-card, and the get_property tool points to it through its metadata; a client that supports MCP Apps picks it up automatically. It renders in the client's sandbox and loads images only from Fabrica's IPFS gateway, the Fabrica media service, and the map-tile provider behind it. Both hosted endpoints serve the card. A local server serves it by default on Sepolia and not on mainnet; set FABRICA_MCP_APPS=enabled to turn it on there, or disabled to turn it off.
Repository
The server is developed in the public repository github.com/fabrica-land/fabrica-mcp under the MIT license. The repository also holds a checked-in snapshot of the GraphQL schema that every query is validated against, so an upstream API change surfaces as a reviewable diff rather than a runtime failure.
Documentation server
https://docs.fabrica.land/mcp gives agents this documentation and the API reference. Add it the same way when an agent needs to answer questions about the protocol, the trust, or the API.
Other resources for agents
fabrica.land/llms.txt: a summary of Fabrica written for language models, served astext/markdown, listing the hosted MCP servers, the GraphQL endpoint, the token contract and the documentation. Every page of fabrica.land points to it withLink: </llms.txt>; rel="describedby"andrel="llms-txt"headers, and a request for the homepage withAccept: text/markdownreturns it in place of the HTML (other pages are not served as markdown)- GraphQL API at
https://api.fabrica.land/graphql: public, no API key, introspection enabled
Related
- Build on Fabrica, the permissionless primitives on which an agent's findings can be acted
- Property checks, what each check tests and how to read the numeric encoding that
explain_confidence_scoredecodes - Property Valuation and Price Oracle, where the estimated values come from
- Fabrica API Reference, the REST surface alongside the GraphQL API the server reads
Updated 1 day ago