Skip to content

Tools

Tools wrap REST API endpoints. Each tool maps to one HTTP request. Schemas are .mjs files with two named exports: a static main block and an optional handlers factory function.

A schema file has two parts: the declarative main export that describes what tools do, and the optional handlers export that transforms requests and responses.

The main export is a static, JSON-serializable object. No functions, no dynamic values, no imports.

FieldTypeDescription
namespacestringProvider identifier, lowercase letters only (/^[a-z]+$/).
namestringSchema name in PascalCase (e.g. SmartContractExplorer).
descriptionstringWhat this schema does, 1-2 sentences.
versionstringMust match 3.\d+.\d+ (semver, major must be 3).
rootstringBase URL for all tools. Must start with https:// (no trailing slash).
toolsobjectTool definitions. Keys are camelCase tool names. Maximum 8 tools.
FieldTypeDescription
docsstring[]Documentation URLs for the API provider.
tagsstring[]Categorization tags for tool discovery.
requiredServerParamsstring[]Environment variable names needed at runtime (e.g. API keys).
requiredLibrariesstring[]npm packages needed by handlers.
headersobjectDefault HTTP headers applied to all tools.
sharedListsobject[]Shared list references for dynamic enum values. See Shared Lists.
export const main = {
namespace: 'etherscan',
name: 'SmartContractExplorer',
description: 'Explore verified smart contracts on EVM chains via Etherscan APIs',
version: '3.0.0',
root: 'https://api.etherscan.io',
docs: [ 'https://docs.etherscan.io/' ],
tags: [ 'ethereum', 'blockchain' ],
requiredServerParams: [ 'ETHERSCAN_API_KEY' ],
tools: {
// tool definitions here
}
}

Each key in tools is the tool name in camelCase. The tool name becomes part of the fully qualified MCP tool name.

FieldTypeRequiredDescription
methodstringYesHTTP method: GET, POST, PUT, DELETE.
pathstringYesURL path appended to root. May contain {{key}} placeholders.
descriptionstringYesWhat this tool does. Appears in the MCP tool description.
parametersarrayYesInput parameter definitions. Can be empty [].

The path supports {{key}} placeholders that are replaced by insert parameters at call-time:

// Static path
path: '/api'
// Single placeholder
path: '/api/v1/{{address}}/transactions'
// Multiple placeholders
path: '/api/v1/{{chainId}}/address/{{address}}/balances'

Every {{key}} placeholder must have a corresponding parameter with location: 'insert'.

Each parameter has two blocks: position (where the value goes) and z (how it is validated).

TypeDescriptionExample
string()Any string value'string()'
number()Numeric value'number()'
boolean()True or false'boolean()'
enum(A,B,C)One of the listed values'enum(mainnet,testnet)'
array()Array of values'array()'
PatternDescriptionVisible to User
{{USER_PARAM}}User provides the value at call-timeYes
{{SERVER_PARAM:KEY}}Injected from environment variableNo
Fixed stringSent automatically with every requestNo
OptionDescriptionExample
min(n)Minimum value or length'min(1)'
max(n)Maximum value or length'max(100)'
optional()Parameter is not required'optional()'
default(value)Default when omitted'default(100)'
// User-provided address with length validation
{
position: { key: 'address', value: '{{USER_PARAM}}', location: 'query' },
z: { primitive: 'string()', options: [ 'min(42)', 'max(42)' ] }
}
// Fixed parameter (invisible to user)
{
position: { key: 'module', value: 'contract', location: 'query' },
z: { primitive: 'string()', options: [] }
}
// API key injected from environment
{
position: { key: 'apikey', value: '{{SERVER_PARAM:ETHERSCAN_API_KEY}}', location: 'query' },
z: { primitive: 'string()', options: [] }
}

The optional handlers export is a factory function that receives injected dependencies and returns handler objects per tool.

export const handlers = ( { sharedLists, libraries } ) => ({
toolName: {
preRequest: async ( { struct, payload } ) => {
// modify the request before it is sent
return { struct, payload }
},
postRequest: async ( { response, struct, payload } ) => {
// transform the response after receiving it
return { response }
}
}
})
ParameterTypeDescription
sharedListsobjectResolved shared list data, keyed by list name. Read-only (deep-frozen).
librariesobjectLoaded npm packages from requiredLibraries, keyed by package name.
HandlerWhenInputMust Return
preRequestBefore the API call{ struct, payload }{ struct, payload }
postRequestAfter the API call{ response, struct, payload }{ response }
  1. Handlers are optional. Tools without handlers make direct API calls.
  2. Zero import statements. All dependencies come through the factory function.
  3. No restricted globals. fetch, fs, process, eval are forbidden.
  4. Return shape must match. preRequest returns { struct, payload }. postRequest returns { response }.

A full Etherscan schema with two tools, API key injection, and a postRequest handler:

export const main = {
namespace: 'etherscan',
name: 'SmartContractExplorer',
description: 'Ethereum blockchain explorer API',
version: '3.0.0',
docs: [ 'https://docs.etherscan.io/' ],
tags: [ 'ethereum', 'blockchain' ],
root: 'https://api.etherscan.io',
requiredServerParams: [ 'ETHERSCAN_API_KEY' ],
headers: { 'Accept': 'application/json' },
tools: {
getContractAbi: {
method: 'GET',
path: '/api',
description: 'Get the ABI of a verified smart contract',
parameters: [
{
position: { key: 'module', value: 'contract', location: 'query' },
z: { primitive: 'string()', options: [] }
},
{
position: { key: 'action', value: 'getabi', location: 'query' },
z: { primitive: 'string()', options: [] }
},
{
position: { key: 'address', value: '{{USER_PARAM}}', location: 'query' },
z: { primitive: 'string()', options: [ 'min(42)', 'max(42)' ] }
},
{
position: { key: 'apikey', value: '{{SERVER_PARAM:ETHERSCAN_API_KEY}}', location: 'query' },
z: { primitive: 'string()', options: [] }
}
]
},
getSourceCode: {
method: 'GET',
path: '/api',
description: 'Get the Solidity source code of a verified smart contract',
parameters: [
{
position: { key: 'module', value: 'contract', location: 'query' },
z: { primitive: 'string()', options: [] }
},
{
position: { key: 'action', value: 'getsourcecode', location: 'query' },
z: { primitive: 'string()', options: [] }
},
{
position: { key: 'address', value: '{{USER_PARAM}}', location: 'query' },
z: { primitive: 'string()', options: [ 'min(42)', 'max(42)' ] }
},
{
position: { key: 'apikey', value: '{{SERVER_PARAM:ETHERSCAN_API_KEY}}', location: 'query' },
z: { primitive: 'string()', options: [] }
}
]
}
}
}
export const handlers = ( { sharedLists } ) => ({
getContractAbi: {
postRequest: async ( { response } ) => {
return { response: JSON.parse( response['result'] ) }
}
},
getSourceCode: {
postRequest: async ( { response } ) => {
const { result } = response
const [ first ] = result
const { SourceCode, ABI, ContractName, CompilerVersion, OptimizationUsed } = first
return {
response: {
contractName: ContractName,
compilerVersion: CompilerVersion,
optimizationUsed: OptimizationUsed === '1',
sourceCode: SourceCode,
abi: ABI
}
}
}
}
})
ConstraintValueRationale
Max tools per schema8Keeps schemas focused. Split large APIs into multiple schema files.
Version major3Must match 3.\d+.\d+.
Namespace pattern^[a-z]+$Letters only. No numbers, hyphens, or underscores.
Root URL protocolhttps://HTTP is not allowed.
Root URL trailing slashForbiddenroot must not end with /.
Schema file importsZeroAll dependencies are injected via the handlers factory.