@reso-standards/reso-mcp-server
RESO MCP Server
MCP server that exposes RESO tools for AI agents. Query OData servers, parse metadata, validate records, run compliance tests – all through the Model Context Protocol.
Works with any MCP client: Claude, Cursor, Windsurf, VS Code or your own application.
New here? The User Guide walks through using the server from an AI assistant: setting up credentials, searching listings and the data attached to them, adding and editing records, and keeping another system in sync. Every number in it came from a real query against a seeded reference server, and each exchange carries the tool call and response in a collapsible block. The tool-call blocks elide the
urlargument, which every data tool requires, to keep the dialogue readable.
Install
This package is deliberately not published to npm. It is a reference adapter meant to be read and cloned rather than installed as a dependency. See Architecture for what it demonstrates. Build it from the reso-tools monorepo:
git clone https://github.com/RESOStandards/reso-tools.git
cd reso-tools
npm install # the whole workspace
npm run build # every package in dependency order
Build from the repo root rather than from this directory. Each package resolves its siblings through
the dist their package.json points at, and a workspace symlink alone does not create that dist,
so a sibling has to be built before anything that imports it.
The built binary lives at reso-tools/reso-mcp-server/dist/index.js. Note its absolute path – you will point your MCP client at it below.
Quick Start
Claude Code / Claude Desktop
Add to your MCP settings (~/.claude/claude_desktop_config.json or via /mcp add). Replace /absolute/path/to/ with the directory where you cloned reso-tools:
{
"mcpServers": {
"reso": {
"command": "node",
"args": ["/absolute/path/to/reso-tools/reso-mcp-server/dist/index.js"]
}
}
}
Certification tools only:
{
"mcpServers": {
"reso-cert": {
"command": "node",
"args": ["/absolute/path/to/reso-tools/reso-mcp-server/dist/index.js", "--scope", "cert"]
}
}
}
Other MCP Clients
The server uses stdio transport, so the configuration is the same command + args across all clients:
- Cursor – Settings > MCP Servers
- VS Code –
.vscode/mcp.json - Windsurf – MCP settings
- JetBrains – Settings > AI Assistant > MCP
- Zed – Settings
- Continue, Cline, Amazon Q, Sourcegraph Cody
Docker
# From the repo root. The Dockerfile copies the root lockfile and every sibling it needs,
# so the build context has to be the repo rather than this directory.
docker build -t reso-mcp-server -f reso-mcp-server/Dockerfile .
docker run -i --rm --env-file ./reso-mcp-server/.env \
--add-host=host.docker.internal:host-gateway reso-mcp-server
The --env-file is not optional. The image sets no credential defaults, so without it every data
call refuses with the authentication message above.
Tools
authorize
Check that the configured credentials work. Takes no arguments: it reads the server environment, and for client credentials it performs the token exchange and then discards the token. Nothing is returned to the caller and nothing is checked against a data server.
authorize()
→ { mode, channel, tokenEndpoint }
It is a diagnostic rather than a prerequisite. Every other tool obtains its own token from the same credentials on each call, so there is no need to call this first.
query
Query a RESO OData server. Supports $filter, $select, $orderby, $top, $skip, $count and $expand.
query({ url, resource, filter?, select?, orderby?, top?, skip?, count?, expand? })
→ { value: [...records] }
No tool requires a credential, and nothing in this documentation passes one. The server reads one from its own environment instead, so nothing sensitive travels inside a tool call. That matters because an argument passed to a tool becomes part of the agent’s conversation history and of every transcript of it, while a credential the server holds never appears in a message.
Eight of the ten tools do still accept four optional credential properties, for a multi-tenant host that has no single environment to read. Each one is documented in the schema as optional, with the reason to omit it, and the schema tells a model not to ask anyone to paste a secret into the conversation. No flow here uses them.
Put it in a .env beside the server and point Node at the file:
{
"mcpServers": {
"reso": {
"command": "node",
"args": ["--env-file=/absolute/path/to/reso-tools/reso-mcp-server/.env",
"/absolute/path/to/reso-tools/reso-mcp-server/dist/index.js"]
}
}
}
# .env - gitignored, never committed
RESO_BASE_URL=https://api.example.com
RESO_AUTH_TOKEN=...
# or, for OAuth2 client credentials instead of a bearer token:
# RESO_CLIENT_ID=...
# RESO_CLIENT_SECRET=...
# RESO_TOKEN_URI=https://auth.example.com/oauth2/token
RESO_BASE_URL is required and is not a convenience. It names the one server the credential may be sent to, and a call targeting any other origin is refused. See Architecture for why, and for what a mismatch reports.
metadata
Fetch and parse OData $metadata. Returns entity types, fields, key properties and type information.
metadata({ url, resource? })
→ { namespace, entityTypes: [...] }
validate
Currently a stub. It counts the fields it was given and returns a message saying so. It does not
compare anything against the Data Dictionary, and it never returns failures, so an empty result does
not mean a record is valid. Wiring it to @reso-standards/reso-validation is a follow-up.
validate({ record, resource })
→ { resource, fieldsProvided, message }
parse-filter
Parse an OData $filter expression into an AST. Useful for understanding, validating or transforming filter expressions.
parse-filter({ filter })
→ { type: "logical", operator: "and", left: {...}, right: {...} }
run-compliance
Run RESO Certification compliance tests. Supports Add/Edit (RCP-010), EntityEvent (RCP-027) and Web API Core.
run-compliance({ endorsement, url, resource?, version?, mode?, resources? })
→ { status: "passed", steps: [...], duration: 450 }
metadata-report
Generate a RESO metadata compliance report. Checks entity types, fields and annotations.
metadata-report({ url, version? })
→ { description, version, generatedOn, resources, fields, lookups, actions, functions }
Scope
The --scope flag limits which tools are available:
| Scope | Tools |
|---|---|
all (default) |
All tools |
cert |
run-compliance, metadata-report |
Authentication
The server reads its credential from its own environment. Two modes, and a complete set of client credentials wins over a bearer token:
| Mode | Variables |
|---|---|
| Bearer token | RESO_AUTH_TOKEN |
| OAuth2 client credentials | RESO_CLIENT_ID, RESO_CLIENT_SECRET, RESO_TOKEN_URI, optional RESO_SCOPE |
RESO_BASE_URL is required alongside either, and binds the credential to one server. See
Install for the --env-file wiring and Architecture
in the User Guide for the full rules.
Two behaviors worth knowing before you debug something:
- A partial set is refused, never completed. Supplying a client id and a token URI without a secret fails by name instead of quietly falling back to a bearer token or borrowing the missing field from somewhere else.
- An environment credential is never sent to an unbound host. A call targeting an origin other
than
RESO_BASE_URLis refused, and the refusal names both origins and no value.
Development
From the reso-tools monorepo:
cd reso-tools/reso-mcp-server
npm install # preinstall builds sibling deps if their dist/ is missing
npm run build
npm run dev # Watch mode
Testing Locally
# Start the reference server
cd ../reso-reference-server && docker compose up -d
# Test the MCP server
# The reference server's mock IdP accepts any client id and secret.
cat > .env <<'ENV'
RESO_BASE_URL=http://localhost:8080
RESO_CLIENT_ID=test
RESO_CLIENT_SECRET=test
RESO_TOKEN_URI=http://localhost:8080/oauth/token
ENV
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}
{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"query","arguments":{"url":"http://localhost:8080","resource":"Property","top":3}}}' | node --env-file=.env dist/index.js
Related
- User Guide – dialogue-format walk-through with live examples
- Architecture – how this server works, where the SDK boundary is, and why it is only 1,002 lines. Read this before building your own.
reso-certification/– CLI and SDK for compliance testingreso-client/– OData client SDK- RESO Tools MCP Server ticket
License
See LICENSE in the repository root.