@radiustechsystems/mcp-sdk
TypeScript icon, indicating that this package has built-in type declarations

1.0.0 • Public • Published

Radius MCP SDK

npm version License: MIT

Token-gate your MCP tools, resources, and prompts with just 3 lines of code. Works seamlessly with the Radius MCP Server to enable decentralized, token-based access control across the MCP ecosystem.

TESTNET RELEASE
This SDK is currently configured for use with Radius Testnet.

Overview

The Radius MCP SDK is the authorization component of the Radius ecosystem. It allows MCP server developers to protect their tools, resources, and prompts with ERC-1155 token requirements. The SDK verifies cryptographic proofs generated by the Radius MCP Server and checks on-chain token ownership, enabling a decentralized marketplace of token-gated AI tools.

How It Works

  1. Simple Integration: Add SDK to your MCP server with 3 lines of code
  2. Proof Verification: SDK validates EIP-712 signatures from the Radius MCP Server
  3. Token Checking: Direct on-chain verification of token ownership
  4. Intelligent Errors: Guides Claude through authentication and purchase flow
  5. Performance: Built-in caching and request deduplication

Installation

pnpm add @radiustechsystems/mcp-sdk
# or
npm install @radiustechsystems/mcp-sdk
# or
yarn add @radiustechsystems/mcp-sdk

Features

  • 🔐 Cryptographic Security: EIP-712 signature verification
  • 🧠 AI-Friendly: Structured error responses that guide Claude through the flow
  • 📝 TypeScript First: Full type safety with comprehensive interfaces
  • 🛡️ Security Enhanced: Chain ID validation and replay protection
  • ⚡ High Performance: Built-in caching and request deduplication
  • 🎯 Simple Integration: Just 3 lines of code to protect any MCP tool

Quick Start

import { RadiusMcpSdk } from '@radiustechsystems/mcp-sdk';

// Simple usage - just provide the contract address!
// Defaults to Radius Testnet (chainId: 1223953)
const radius = new RadiusMcpSdk({
  contractAddress: '0x5448Dc20ad9e0cDb5Dd0db25e814545d1aa08D96'
});

// Protect any MCP tool, resource, or prompt
handler: radius.protect(TOKEN_ID, yourHandler)

Working Example

Check out the /examples/fastmcp directory for a complete working example that you can run in under 60 seconds!

Dependencies

The SDK has minimal dependencies:

  • viem - For Ethereum interactions and EIP-712 signature verification
  • @evmauth/eip712-authn - For EIP-712 authentication handling

Complete Usage Example

How It Works: The 3-Call Flow

// 1. Claude tries to use your protected tool
await yourTool({ query: "analyze market data" });
// Response: EVMAUTH_PROOF_MISSING error with required tokens

// 2. Claude calls Radius MCP Server (handles everything in one call!)
const response = await authenticate_and_purchase({ 
  tokenIds: [101]  // From error's requiredTokens field
});
// Returns: { proof, purchases } - proof for auth + purchase info (if tokens were bought)

// 3. Claude retries with authentication proof
const result = await yourTool({ 
  query: "analyze market data",
  __evmauth: response.proof  // Proof from authenticate_and_purchase
});
// Success! Tool executes and returns results

The __evmauth Namespace

The SDK uses a reserved __evmauth namespace to:

  1. Keep auth separate: Authentication data never mixes with business logic
  2. Maintain full security: EIP-712 signature verification with cryptographic guarantees
  3. Simplify handlers: Your tool handlers receive clean requests without auth data
  4. Guide Claude: Clear error messages show exactly how to use the namespace

From Developer's Perspective

// Your entire integration:
const radius = new RadiusMcpSdk({ ...config });

server.addTool({
  name: 'market_analyzer',
  handler: radius.protect(101, async (args) => {
    // Your tool logic here - only runs if user owns token 101
    return analyzeMarket(args.query);
  })
});

Testnet Configuration

Chain ID: 1223953
RPC URL: https://rpc.testnet.radiustech.xyz

Configuration

Basic Configuration

const radius = new RadiusMcpSdk({
  contractAddress: '0x5448Dc20ad9e0cDb5Dd0db25e814545d1aa08D96'
});

Advanced Configuration

const radius = new RadiusMcpSdk({
  contractAddress: '0x5448Dc20ad9e0cDb5Dd0db25e814545d1aa08D96',
  chainId: 1223953,
  rpcUrl: 'https://rpc.testnet.radiustech.xyz',
  cache: {
    ttl: 300,           // Cache TTL in seconds
    maxSize: 1000,      // Max cache entries
    disabled: false     // Set true to disable caching
  },
  debug: false          // Enable debug logging
});

Configuration Options

Option Type Default Description
contractAddress string Required ERC-1155 contract address
chainId number 1223953 Blockchain network ID (Radius Testnet)
rpcUrl string Radius Testnet RPC RPC endpoint for ownership checks
cache.ttl number 60 Cache TTL in seconds
cache.maxSize number 1000 Maximum cache entries
cache.disabled boolean false Disable caching
debug boolean false Enable debug logging

Framework Examples

FastMCP Integration

import { FastMCP } from 'fastmcp';
import { RadiusMcpSdk } from '@radiustechsystems/mcp-sdk';
import { z } from 'zod';

// Simple initialization - defaults to Radius Testnet
const radius = new RadiusMcpSdk({
  contractAddress: '0x5448Dc20ad9e0cDb5Dd0db25e814545d1aa08D96'
});

// Or explicitly set chain/RPC for other networks
const radiusCustom = new RadiusMcpSdk({
  contractAddress: '0x5448Dc20ad9e0cDb5Dd0db25e814545d1aa08D96',
  chainId: 1223953,
  rpcUrl: 'https://rpc.testnet.radiustech.xyz'
});

const server = new FastMCP({
  name: 'Premium Analytics',
  version: '1.0.0'
});

// Protected tool
server.addTool({
  name: 'premium_analytics',
  description: 'Advanced market analytics (requires token)',
  inputSchema: z.object({
    market: z.string(),
    timeframe: z.string()
  }),
  handler: radius.protect(101, async (args) => {
    const data = await fetchPremiumData(args.market, args.timeframe);
    return { content: [{ type: 'text', text: JSON.stringify(data) }] };
  })
});

// Protected resource
server.addResource({
  name: 'premium_dataset',
  description: 'Premium market dataset (requires token)',
  handler: radius.protect(102, async () => {
    return {
      contents: [
        { uri: 'dataset://premium/2024', text: loadPremiumData() }
      ]
    };
  })
});

// Protected prompt
server.addPrompt({
  name: 'expert_trading_prompt',
  description: 'Expert trading strategies (requires token)',
  handler: radius.protect(103, async () => {
    return {
      messages: [
        { role: 'system', content: 'You are an expert trader...' },
        { role: 'user', content: 'Analyze this market...' }
      ]
    };
  })
});

Multi-Token Protection

ANY Token Logic

// User needs ANY of these tokens
handler: radius.protect([201, 202, 203], async (args) => {
  // User has at least one of the required tokens
  return performEnterpriseAnalytics(args.query);
})

Specific Token Requirements

// Different tools require different tokens
const TOKENS = {
  BASIC_ANALYTICS: 101,
  PREMIUM_ANALYTICS: 102,
  ENTERPRISE_ANALYTICS: [201, 202, 203] // ANY of these
};

// Basic tier
server.addTool({
  name: 'basic_analytics',
  handler: radius.protect(TOKENS.BASIC_ANALYTICS, basicHandler)
});

// Premium tier
server.addTool({
  name: 'premium_analytics',
  handler: radius.protect(TOKENS.PREMIUM_ANALYTICS, premiumHandler)
});

// Enterprise tier
server.addTool({
  name: 'enterprise_analytics',
  handler: radius.protect(TOKENS.ENTERPRISE_ANALYTICS, enterpriseHandler)
});

Integration with the Radius MCP Server

This SDK works in tandem with the Radius MCP Server to create a complete token-gating ecosystem:

  1. Radius MCP Server

    • Handles OAuth authentication with AI clients
    • Manages user wallets via Privy
    • Generates cryptographic proofs
    • Processes token purchases
    • One instance per AI client
  2. Radius MCP SDK (this repo)

    • Verifies proofs from the Radius MCP Server
    • Checks on-chain token ownership
    • Protects your MCP tools/resources/prompts
    • Guides AI through the flow

Error Handling

Error Codes

The SDK uses specific error codes for different failure scenarios:

  • EVMAUTH_PROOF_MISSING - No proof provided in the request
  • PROOF_EXPIRED - Proof has expired (proofs expire after 30 seconds by default)
  • PROOF_INVALID - Proof format is invalid
  • CHAIN_MISMATCH - Chain ID in proof doesn't match SDK configuration
  • CONTRACT_MISMATCH - Contract address in proof doesn't match SDK configuration
  • SIGNATURE_INVALID - EIP-712 signature verification failed
  • SIGNER_MISMATCH - Signature doesn't match the claimed wallet address
  • NONCE_INVALID - Nonce is malformed or outside acceptable time window
  • PAYMENT_REQUIRED - User doesn't own required tokens

Structured Error Responses

The SDK provides comprehensive error responses that guide MCP clients such as Claude through the authentication flow:

// When proof is missing
{
  "error": {
    "code": "EVMAUTH_PROOF_MISSING",
    "message": "You need to authenticate with Radius MCP Server first",
    "details": {
      "contractAddress": "0x5448Dc20ad9e0cDb5Dd0db25e814545d1aa08D96",
      "chainId": 1223953,
      "requiredTokens": [101]
    },
    "claude_action": {
      "description": "You need to authenticate and potentially purchase tokens",
      "steps": [
        "Call the authenticate_and_purchase tool on Radius MCP Server with the required tokenIds",
        "The tool will check if you own the tokens and purchase them if needed",
        "Copy the entire proof object from the response",
        "Include it as \"__evmauth\": <proof> in this tool's arguments",
        "Retry this tool call with the proof included"
      ],
      "tool": {
        "server": "radius-mcp-server",
        "name": "authenticate_and_purchase",
        "arguments": {
          "tokenIds": [101]
        }
      }
    }
  }
}

// When token ownership is required (after authentication)
{
  "error": {
    "code": "PAYMENT_REQUIRED",
    "message": "Token ownership required",
    "details": {
      "requiredTokens": [101],
      "contractAddress": "0x5448Dc20ad9e0cDb5Dd0db25e814545d1aa08D96",
      "chainId": 1223953,
      "checkedWallet": "0x742d35Cc6634C0532925a3b844Bc9e7Ed1A0aC0E"
    },
    "claude_action": {
      "description": "You don't own the required tokens. You need to purchase them.",
      "steps": [
        "Call the authenticate_and_purchase tool again on Radius MCP Server",
        "It will automatically purchase the missing tokens",
        "Use the new proof from the response",
        "Retry this tool call with the new proof"
      ],
      "tool": {
        "server": "radius-mcp-server",
        "name": "authenticate_and_purchase",
        "arguments": {
          "tokenIds": [101]
        }
      }
    }
  }
}

Architecture

Namespace Design

The SDK uses a reserved __evmauth namespace for authentication data. This design ensures:

  • Tool handlers receive clean requests without authentication parameters
  • Authentication logic is completely separated from business logic
  • Full cryptographic security with EIP-712 signatures
  • Claude receives clear guidance on how to provide authentication

Security Model

The SDK implements a multi-layered security approach:

  1. EIP-712 Signature Verification: Validates cryptographic proofs using standard Ethereum signing
  2. Chain ID Validation: Prevents cross-chain replay attacks
  3. Domain Validation: Ensures proof was created for the correct contract
  4. Timestamp Validation: Prevents replay of expired proofs
  5. Nonce Validation: Ensures proof freshness and prevents replay attacks
  6. Fail-Closed Design: Denies access on any validation failure

Performance Optimizations

  1. Intelligent Caching: LRU cache with TTL for token ownership results
  2. Request Deduplication: Prevents duplicate RPC calls for same token/wallet
  3. Connection Pooling: Efficient RPC client with retry logic

Proof Flow

sequenceDiagram
    participant C as Claude
    participant T as Tool (Your MCP)
    participant S as Radius MCP SDK
    participant R as RPC Node
    participant E as Radius MCP Server

    C->>T: Call protected tool
    T->>S: protect() wrapper
    S->>S: Extract proof from request
    
    alt No proof
        S->>C: EVMAUTH_PROOF_MISSING
        C->>E: authenticate_and_purchase
        E->>C: Return proof and purchase info
        C->>T: Retry with proof
    end
    
    S->>S: Verify EIP-712 signature
    S->>S: Validate timestamp and nonce
    S->>S: Check cache for token
    
    alt Cache miss
        S->>R: balanceOf(wallet, tokenId)
        R->>S: Return balance
        S->>S: Cache result
    end
    
    alt Has tokens
        S->>T: Execute handler
        T->>C: Return result
    else No tokens
        S->>C: PAYMENT_REQUIRED
        C->>E: authenticate_and_purchase
        E->>C: Purchase complete with new proof
        C->>T: Retry with new proof
    end

TypeScript Support

Exported Types and Utilities

The SDK exports the following types and utilities:

// Main SDK class
export { RadiusMcpSdk }

// Types
export type { 
  RadiusConfig,
  CacheConfig,
  MCPHandler,
  MCPRequest,
  MCPResponse,
  EVMAuthProof,
  EVMAuthErrorResponse,
  ProofErrorCode,
  RadiusError,
  RadiusErrorCode
}

// Utilities from viem
export { isAddress }
export type { Address }

// Version constant
export const VERSION

Full Type Safety

import { RadiusMcpSdk, type RadiusConfig, type EVMAuthProof, VERSION } from '@radiustechsystems/mcp-sdk';

// Version constant is exported
console.log('SDK Version:', VERSION); // '1.0.0'

// Configuration is fully typed
const config: RadiusConfig = {
  contractAddress: '0x5448Dc20ad9e0cDb5Dd0db25e814545d1aa08D96',
  chainId: 1223953,
  rpcUrl: 'https://rpc.testnet.radiustech.xyz',
  cache: {
    ttl: 300,
    maxSize: 1000,
    disabled: false
  }
};

// Proof structure is typed
const proof: EVMAuthProof = {
  challenge: {
    domain: {
      name: 'EVMAuth',
      version: '1',
      chainId: 1223953,
      verifyingContract: '0x5448Dc20ad9e0cDb5Dd0db25e814545d1aa08D96'
    },
    types: {
      EIP712Domain: [/* ... */],
      EVMAuthRequest: [/* ... */]
    },
    primaryType: 'EVMAuthRequest',
    message: {
      serverName: 'radius-mcp-server',
      resourceName: 'mcp_tool',
      requiredTokens: '[]',
      walletAddress: '0x...',
      nonce: '123456-randomhex',
      issuedAt: '1234567890',
      expiresAt: '1234567890',
      purpose: 'mcp_tool_access'
    }
  },
  signature: '0x...' as `0x${string}`
};

Handler Types

import type { MCPHandler, MCPRequest, MCPResponse } from '@radiustechsystems/mcp-sdk';

// Your handlers are properly typed
const myHandler: MCPHandler = async (
  request: MCPRequest,
  extra?: any
): Promise<MCPResponse> => {
  // TypeScript knows the structure
  return {
    content: [{ type: 'text', text: 'Hello!' }]
  };
};

Advanced Usage

Public API

The RadiusMcpSdk class exposes only one public method:

  • protect(tokenId: number | number[], handler: MCPHandler): MCPHandler - Wraps a handler with token protection

All other functionality (proof verification, token checking, caching) is handled internally and not exposed in the public API.

Dynamic Token Requirements

// Token requirements based on user tier
const getTokenRequirement = (userTier: string): number | number[] => {
  switch (userTier) {
    case 'basic': return 101;
    case 'premium': return 102;
    case 'enterprise': return [201, 202, 203];
    default: throw new Error('Invalid tier');
  }
};

server.addTool({
  name: 'tiered_analytics',
  handler: async (request) => {
    const userTier = request.params.tier;
    const tokenRequirement = getTokenRequirement(userTier);
    
    return radius.protect(tokenRequirement, async (args) => {
      return performTieredAnalysis(args, userTier);
    })(request);
  }
});

Testing

Unit Testing

import { RadiusMcpSdk } from '@radiustechsystems/mcp-sdk';
import { vi, describe, it, expect } from 'vitest';

describe('Radius MCP SDK', () => {
  it('should require proof for protected handlers', async () => {
    const radius = new RadiusMcpSdk({
      contractAddress: '0x5448Dc20ad9e0cDb5Dd0db25e814545d1aa08D96',
      chainId: 1223953,
      rpcUrl: 'https://rpc.testnet.radiustech.xyz'
    });

    const handler = vi.fn();
    const protectedHandler = radius.protect(101, handler);

    const request = { params: { arguments: {} } };
    const response = await protectedHandler(request);

    expect(response.content[0].text).toContain('EVMAUTH_PROOF_MISSING');
    expect(handler).not.toHaveBeenCalled();
  });
});

Performance Monitoring

Debug Mode

const radius = new RadiusMcpSdk({
  contractAddress: '0x...',
  chainId: 1223953,
  rpcUrl: 'https://...',
  debug: true  // Enable debug logging
});

// Debug mode logs:
// - Token balance check results
// - Cache hit/miss information
// - RPC call details
// - Proof validation steps

Troubleshooting

Common Issues

  1. "EVMAUTH_PROOF_MISSING" Errors

    • Ensure Radius MCP Server is connected
    • Check that proof is included in __evmauth parameter
    • Verify proof hasn't expired (default 30 seconds)
  2. "Chain ID mismatch" Errors

    • Verify SDK chainId matches the network
    • Ensure proof was created for the same chain
  3. RPC Connection Issues

    • Verify RPC URL is correct and accessible
    • Check network connectivity
    • Ensure RPC endpoint supports eth_call

For AI Agents (Claude, GPT, etc.)

Important: The __evmauth Parameter

When using tools protected by Radius MCP SDK:

  1. The __evmauth parameter is ALWAYS accepted - Even if a tool's schema doesn't list __evmauth as a parameter, ALL Radius MCP SDK-protected tools accept it.

  2. It's automatically stripped - The SDK removes the __evmauth parameter before calling the actual tool logic, so tool developers never see it.

  3. Example usage:

    // First, authenticate and get a proof
    const { proof } = await authenticate_and_purchase({
      tokenIds: [101]  // Required tokens from the error
    });
    
    // Then use it with ANY protected tool
    const result = await any_protected_tool({
      // ... normal parameters ...,
      __evmauth: proof  // This ALWAYS works!
    });
  4. Common mistake: Don't worry if you don't see __evmauth in a tool's parameter list. It's a special parameter that's always accepted by the Radius MCP SDK.

Package Sidebar

Install

npm i @radiustechsystems/mcp-sdk

Weekly Downloads

17

Version

1.0.0

License

MIT

Unpacked Size

61.2 kB

Total Files

15

Last publish

Collaborators

  • radiustechsystems