Skip to main content
Glama

unlock-mcp

npm unlock-mcp MCP server

Read-only MCP server exposing Unlock Protocol on-chain state. No private keys, no signing, no write calls — every tool only reads from the chain.

Install

Claude Desktop

Add to your claude_desktop_config.json:

{
  "mcpServers": {
    "unlock": {
      "command": "npx",
      "args": ["-y", "unlock-mcp"]
    }
  }
}

Claude Code

claude mcp add unlock -- npx -y unlock-mcp

Local development

If you're working on the server itself, point either of the above at a local checkout instead:

{
  "mcpServers": {
    "unlock": {
      "command": "node",
      "args": ["/absolute/path/to/unlock-mcp/dist/index.js"]
    }
  }
}

Related MCP server: Vaultfire MCP Server

Tools

unlock_check_membership

Checks whether a wallet holds a valid (non-expired) key for an Unlock Protocol lock.

Input

Type

Required

Description

lockAddress

0x address

yes

The PublicLock contract to check

walletAddress

0x address

yes

The wallet to check for a key

network

string

no

Defaults to "base"

requireContractVerdict

boolean

no

Defaults to false. Fail instead of falling back to the local clock comparison (see below)

Returns whether the wallet holds a valid key, and if so its expiration (ISO timestamp and a relative form like "in 2 years"), the tokenId, the lock name, and the network.

The valid/expired verdict comes from the lock's own getHasValidKey, not from comparing keyExpirationTimestampFor to the host's system clock — that avoids two agents on different machines (or with a skewed clock) disagreeing about the same key, and matches what the lock itself enforces. keyExpirationTimestampFor is still read and returned as expiresAt/expiresRelative to explain the verdict, and locks that predate getHasValidKey fall back to the local clock comparison. When the contract and the local comparison disagree, the response includes a verdictDisagreement field — { contractVerdict, localVerdict } — instead of silently picking one; it's absent, not false, when the two agree.

When the verdict falls back to the local clock comparison (getHasValidKey reverts or returns zero data — typically a lock that predates it), the response includes verdictSource: "local_clock"; it's absent, not false or present-and-undefined, when the contract itself produced the verdict. That fallback is a supported answer by default. Set requireContractVerdict: true to instead fail the call with a clear error whenever the verdict would have come from the local clock rather than the contract — useful for callers that would rather get no answer than one the lock itself didn't attest to. It has no effect when the contract answers normally.

keyExpirationTimestampFor changed signature across PublicLock versions — locks below publicLockVersion 10 take a key owner address, version 10 and up take a tokenId. The tool reads publicLockVersion() and calls whichever form the lock actually implements.

Distinct, plain-language results are returned for: a lock address that isn't a contract, a contract that isn't a PublicLock, a wallet with no key, and a wallet with an expired or valid key. These are all normal results, not errors — a definitive answer about the chain isn't a tool failure. The tool only reports an error for cases it genuinely can't answer: an unreachable/rate-limited RPC endpoint, or malformed input. No raw RPC error or revert reason is ever passed through.

Every value is read live from the chain at call time — including name() — so it reflects current on-chain state, not a block explorer's indexed snapshot (a lock's name is only set once at deploy time in most explorer UIs, but can change on-chain afterward; this tool always reads the current value).

unlock_get_lock

Reads a lock's public shape directly from the chain via RPC (not the subgraph — this is point-in-time state, and the RPC path already exists).

Input

Type

Required

Description

lockAddress

0x address

yes

The PublicLock contract to read

network

string

no

Defaults to "base"

Returns name, symbol, address, network, PublicLock version, key price (amount, raw value, and currency — the token's own symbol/decimals for an ERC-20, or the chain's native currency if the lock's tokenAddress() is the zero address), expiration duration in both seconds and human-readable form, max number of keys, and total keys sold (totalSupply(), a running counter of every key ever created — not the current valid supply).

expirationDuration and maxNumberOfKeys each use a max-uint256 sentinel for "unlimited" (never-expiring keys, or no cap on keys respectively) — the tool reports those explicitly as unlimited: true rather than surfacing the raw sentinel as a number.

Lock managers are deliberately not included: PublicLock exposes no enumerable getter for that role (it uses a plain OpenZeppelin AccessControl role, not AccessControlEnumerable) — only isLockManager(address), a point check against an address you'd already have to know. Getting the actual list would mean either replaying RoleGranted/RoleRevoked logs from deployment (not a cheap RPC read) or asking the subgraph, which this tool intentionally avoids so it stays pure on-chain state. Same three classification results as unlock_check_membership apply here for a bad address: not a contract, not a PublicLock, or a working lock.

unlock_list_keys

Lists every key a wallet holds across locks on a network, via Unlock's subgraph — enumerating a wallet's keys across all locks isn't something RPC can do without already knowing which locks to look at.

Input

Type

Required

Description

walletAddress

0x address

yes

The wallet to list keys for

network

string

no

Defaults to "base"

includeExpired

boolean

no

Include expired/cancelled keys too (default false, i.e. currently-valid keys only)

Returns, per key: lock address, lock name, tokenId, expiration (ISO timestamp, or "never" for a lifetime key), and whether it's currently valid. Results are sorted by expiration descending and capped at 100, with a note in the response if the cap was hit. A wallet holding no keys is a normal empty result, not an error.

Networks

Chains are configured as data in src/networks.ts — adding one is a new object, not a code change. Only Base is configured today.

The Base Unlock factory address was cross-checked against unlock-protocol/unlock (packages/networks/src/networks/base.ts) directly, since @unlock-protocol/networks on npm hasn't been republished since 0.0.25 (Dec 2024). As of this check, the two still agree — no divergence found.

RPC endpoints

Each network has a primary RPC and one or more fallbacks, tried in order — a failure on one (timeout, connection error, rate limit) automatically retries on the next. Base defaults to Unlock's own public RPC (rpc.unlock-protocol.com), falling back to the public Base RPC (mainnet.base.org).

To override the endpoints tried for a given network, set UNLOCK_MCP_RPC_URL_<NETWORK> (uppercased network name), e.g.:

UNLOCK_MCP_RPC_URL_BASE=https://your-rpc.example.com

The override is tried first; the built-in defaults remain as fallbacks behind it.

Subgraph endpoint

unlock_list_keys reads from Unlock's public subgraph, one endpoint per network (no fallback chain, since there's only one). To override it for a given network, set UNLOCK_MCP_SUBGRAPH_URL_<NETWORK> (uppercased network name), e.g.:

UNLOCK_MCP_SUBGRAPH_URL_BASE=https://your-subgraph.example.com

Development

npm install
npm run build
npm test

Available Tools

3 tools
unlock_check_membershipCheck Unlock Protocol membershipA

Check whether a wallet holds a valid (non-expired) key for a specific Unlock Protocol lock, read-only. Returns status (valid, expired, no_key, not_a_contract, or not_a_lock), lock name, tokenId, and expiration. Use this when you already know the lock address to check; use unlock_list_keys instead when you need every lock a wallet holds and don't know the addresses up front. The verdict normally comes from the lock's own getHasValidKey; on locks that predate it, the call falls back to comparing the read expiration to the local clock and reports verdictSource: "local_clock" (absent when the contract answered). Set requireContractVerdict to fail instead of accepting that fallback.

ParametersJSON Schema
NameRequiredDescriptionDefault
networkNoNetwork name (defaults to "base")base
lockAddressYesAddress of the PublicLock contract to check
walletAddressYesWallet address to check for a valid key
requireContractVerdictNoWhen true, fail with an error instead of falling back to comparing keyExpirationTimestampFor to the local clock when the lock's own getHasValidKey can't be read (e.g. it predates getHasValidKey). That fallback is a supported valid/expired answer by default (false) — set this to require a verdict sourced from the contract itself.

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden, and it delivers: it declares the operation as read-only, describes return statuses, and explains the getHasValidKey vs. local_clock fallback behavior plus the requireContractVerdict escape hatch. This is unusually transparent about how the verdict is produced.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is dense but every sentence earns its place: what it does, what it returns, when to use it, and the fallback behavior. It front-loads the core purpose before diving into edge-case mechanics.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Even though there is no output schema, the description enumerates the returned status values and fields. It also explains the fallback mechanism and the parameter that disables it, making the tool callable and interpretable without additional context.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the parameters are already documented. The description adds value by explaining what requireContractVerdict actually controls and how verdictSource relates to the fallback, going beyond the schema's description.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a precise action: check whether a wallet holds a valid key for a specific Unlock Protocol lock. It also differentiates itself from the sibling unlock_list_keys by noting the addressing condition, so an agent can distinguish the tools without opening schemas.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It explicitly says to use this tool when you already know the lock address, and to use unlock_list_keys when you need every lock a wallet holds and don't know the addresses up front. This gives clear selection criteria against the most likely alternative.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

unlock_get_lockGet Unlock Protocol lock detailsA

Read an Unlock Protocol lock's public shape from the chain, read-only: name, symbol, key price/currency, expiration duration, max keys, and total keys sold.

ParametersJSON Schema
NameRequiredDescriptionDefault
networkNoNetwork name (defaults to "base")base
lockAddressYesAddress of the PublicLock contract to read

TDQS

A4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden of behavioral disclosure. It explicitly states 'read-only' and 'public shape', making clear that the call has no side effects and requires no sensitive access. It also indicates the data is fetched 'from the chain'. It does not describe error behavior or response formatting, but for a simple read operation this is adequate.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

One tight sentence that leads with the verb, states the read-only nature, and uses a coherent colon-separated list of the lock attributes. No ffiller or redundant wording.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple two-parameter read tool with no output schema, the description covers the purpose, the read-only behavior, and the specific fields returned. It doesn't mention the network default (though the schema does) or the exact response envelope, but the listed fields sufficiently set expectations. Minor gaps only.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% and both parameters already have descriptions ('Network name (defaults to "base")' and 'Address of the PublicLock contract to read'). The description adds context about the lock fields that the address will resolve to, but it does not fundamentally enhance the meaning of the parameters themselves beyond the schema. Baseline 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb ('Read'), a clear resource ('Unlock Protocol lock's public shape from the chain'), and a precise scope ('read-only') with an explicit list of returned fields (name, symbol, key price/currency, expiration duration, max keys, total keys sold). This clearly differentiates the tool from its siblings, which handle membership checks and key listing.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies its use case: whenever an agent needs a lock's public on-chain details. However, it does not explicitly name when-to-use conditions or mention the sibling tools, so there is no direct guidance on choosing between unlock_get_lock, unlock_check_membership, and unlock_list_keys.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

unlock_list_keysList Unlock Protocol keys held by a walletA

List every Unlock Protocol key a wallet holds across locks on a network, via the Unlock subgraph, read-only. Returns each key's lock address and name, tokenId, expiration, and validity, sorted by expiration descending and capped at 100 results with a truncation flag. Use this when you need every membership a wallet holds and don't know the lock addresses up front; use unlock_check_membership instead when you already know which lock to check.

ParametersJSON Schema
NameRequiredDescriptionDefault
networkNoNetwork name (defaults to "base")base
walletAddressYesWallet address to list Unlock Protocol keys for
includeExpiredNoInclude expired and cancelled keys, not just currently valid ones (default false)

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden, and it delivers: it says the operation is read-only, uses the Unlock subgraph, returns specific fields, sorts by expiration descending, caps at 100 results, and includes a truncation flag. These are meaningful behavioral details beyond the schema.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three sentences with no filler: the action and scope come first, then return behavior and limits, then usage guidance with an alternative. Every sentence contributes distinct, useful information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

There is no output schema and no annotations, but the description compensates by disclosing return fields, sorting, cap, truncation flag, read-only nature, and the intended use case. This is complete for an agent to select and invoke the tool correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so all three parameters already have descriptions. The tool description does not add much parameter-specific meaning beyond restating the general purpose, which is acceptable but not an enhancement. Baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Uses a specific verb ('List') with a clear resource ('Unlock Protocol keys a wallet holds across locks on a network') and explicitly distinguishes itself from unlock_check_membership. The description states both scope and what results include, so an agent can identify the tool without opening the schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly states when to use this tool ('when you need every membership a wallet holds and don't know the lock addresses up front') and names the alternative ('use unlock_check_membership instead when you already know which lock to check'). This gives an agent direct routing guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 1 tool updatev0.4.0
    • Changedunlock_check_membership1 field changed
      • addedInput schema / properties / requireContractVerdict
        Added value: +{
        +  "default": false,
        +  "description": "When true, fail with an error instead of falling back to comparing keyExpirationTimestampFor to the local clock when the lock's own getHasValidKey can't be read (e.g. it predates getHasValidKey). That fallback is a supported valid/expired answer by default (false) — set this to require a verdict sourced from the contract itself.",
        +  "type": "boolean"
        +}
  2. 3 tool updatesv0.2.0
    • Changedunlock_check_membership2 fields changed
      • removedInput schema / properties / walletAddress / $ref
        Removed value: -"#/properties/lockAddress"
      • addedInput schema / properties / walletAddress / type
        Added value: +"string"
    • Addedunlock_get_lock
    • Addedunlock_list_keys
  3. 1 tool updatev0.1.0
    • First observedunlock_check_membership

TDQS

A4.4/5.0

Scored across 3 tools

Disambiguation5/5

Each tool has a clearly distinct role: check a known lock, read lock metadata, or enumerate all keys for a wallet. unlock_check_membership and unlock_list_keys overlap somewhat but their descriptions explicitly define when to use each.

Naming Consistency5/5

All tools share the unlock_ prefix and follow a consistent verb_noun pattern: check_membership, get_lock, list_keys. The naming is predictable and easy to route.

Tool Count5/5

Three tools is a well-scoped size for a read-only Unlock Protocol membership and lock information server. Each tool covers a distinct, necessary operation without bloat.

Completeness4/5

The core read-only workflows are covered: membership verification, wallet-wide key listing, and lock details. The main gap is that unlock_list_keys caps at 100 results with no pagination, so very large key sets cannot be fully enumerated.

Maintenance

ActivityMaintained
ResponsivenessUnresponsive

Related MCP Connectors

Related MCP Servers