From 2879b71b7ab38d7fe21e278ccc7289d6ef5eb90a Mon Sep 17 00:00:00 2001 From: 1evi7eo Date: Sat, 24 Jan 2026 13:21:00 +0800 Subject: [PATCH 1/2] Initial Commit --- typescript/faucet-mcp/README.md | 211 ++++++++++++++++++++ typescript/faucet-mcp/index.js | 299 ++++++++++++++++++++++++++++ typescript/faucet-mcp/index.test.js | 277 ++++++++++++++++++++++++++ typescript/faucet-mcp/package.json | 41 ++++ 4 files changed, 828 insertions(+) create mode 100644 typescript/faucet-mcp/README.md create mode 100644 typescript/faucet-mcp/index.js create mode 100644 typescript/faucet-mcp/index.test.js create mode 100644 typescript/faucet-mcp/package.json diff --git a/typescript/faucet-mcp/README.md b/typescript/faucet-mcp/README.md new file mode 100644 index 0000000..d3b4287 --- /dev/null +++ b/typescript/faucet-mcp/README.md @@ -0,0 +1,211 @@ +# TBNB Faucet MCP Server (Node.js) + +A Node.js implementation of a Model Context Protocol (MCP) server that provides a faucet service for distributing testnet BNB tokens on the Binance Smart Chain testnet. + +## About + +This MCP server enables AI agents and other MCP clients to request and receive testnet BNB tokens by interacting with the BSC testnet blockchain. It follows the Anthropic MCP specification and can be integrated with any MCP-compatible client. + +## Prerequisites + +- Node.js 18.0 or higher +- npm or yarn package manager +- A BSC testnet wallet with TBNB for the faucet +- Access to BSC testnet RPC endpoint + +## Installation + +1. Navigate to the project directory: +```bash +cd faucet-mcp +``` + +2. Install dependencies: +```bash +npm install +``` + +## Configuration + +Set the following environment variables before running: + +- `FAUCET_PRIVATE_KEY`: The private key of your faucet wallet (required) +- `BSC_TESTNET_RPC`: BSC testnet RPC endpoint URL (optional, has default) + +Example: + +```bash +export FAUCET_PRIVATE_KEY="0x..." +export BSC_TESTNET_RPC="https://data-seed-prebsc-1-s1.binance.org:8545/" +``` + +Or create a `.env` file (not included in repo for security): + +``` +FAUCET_PRIVATE_KEY=0x... +BSC_TESTNET_RPC=https://data-seed-prebsc-1-s1.binance.org:8545/ +``` + +## Usage + +Run the server: + +```bash +node index.js +``` + +Or using npm: + +```bash +npm start +``` + +The server communicates via stdio, which is the standard transport for MCP servers. + +## Available Tools + +### send_tbnb + +Sends testnet BNB tokens to a recipient address. + +**Parameters:** +- `recipient` (string, required): BSC testnet address to receive tokens +- `amount` (number, optional): Amount of TBNB to send (default: 0.1, max: 1.0) + +**Returns:** +- Transaction hash +- Recipient address +- Amount sent +- Block number +- Transaction status + +### get_faucet_info + +Retrieves information about the faucet wallet. + +**Parameters:** None + +**Returns:** +- Faucet wallet address +- Current balance (Wei and TBNB) +- Network information +- RPC endpoint + +### get_balance + +Queries the TBNB balance of any BSC testnet address. + +**Parameters:** +- `address` (string, required): BSC testnet address to check + +**Returns:** +- Address (checksummed) +- Balance in Wei and TBNB +- Network name + +## MCP Client Integration + +To integrate this server with an MCP client, add it to your client configuration: + +```json +{ + "mcpServers": { + "tbnb-faucet": { + "command": "node", + "args": ["/absolute/path/to/index.js"], + "env": { + "FAUCET_PRIVATE_KEY": "0x...", + "BSC_TESTNET_RPC": "https://data-seed-prebsc-1-s1.binance.org:8545/" + } + } + } +} +``` + +## Network Configuration + +- **Network**: Binance Smart Chain Testnet +- **Chain ID**: 97 +- **Default RPC**: https://data-seed-prebsc-1-s1.binance.org:8545/ +- **Block Explorer**: https://testnet.bscscan.com + +## Error Handling + +The server includes comprehensive error handling: + +- Address validation (checksum and format) +- Amount validation (0 to 1.0 TBNB) +- Self-transfer prevention +- Network connectivity checks +- Transaction error reporting + +All errors are returned as structured JSON responses. + +## Security + +๐Ÿ”’ **Security Best Practices:** + +- Never commit your private key to version control +- Use environment variables or secure secret management systems +- This server is intended for testnet use only +- Consider implementing rate limiting for production deployments +- Monitor faucet balance and set up alerts + +## Troubleshooting + +### Common Issues + +**"Faucet wallet not configured"** +- Ensure `FAUCET_PRIVATE_KEY` environment variable is set +- Verify the private key is valid and starts with `0x` + +**Connection errors** +- Check your internet connection +- Verify the RPC endpoint is accessible +- Try an alternative BSC testnet RPC endpoint + +**Transaction failures** +- Ensure the faucet wallet has sufficient TBNB balance +- Verify the recipient address is valid +- Check network conditions (gas prices, congestion) + +**Module not found errors** +- Run `npm install` to install dependencies +- Ensure you're using Node.js 18.0 or higher + +## Development + +The server uses: +- `@modelcontextprotocol/sdk` for MCP protocol implementation +- `ethers.js` v6 for blockchain interactions + +## License + +MIT License + +## Testing + +Run unit tests: + +```bash +npm install +npm test +``` + +Run tests with coverage: + +```bash +npm run test:coverage +``` + +Run tests in watch mode: + +```bash +npm run test:watch +``` + +## Resources + +- [Model Context Protocol Documentation](https://modelcontextprotocol.io) +- [BNB Chain Documentation](https://docs.bnbchain.org) +- [Ethers.js Documentation](https://docs.ethers.org) diff --git a/typescript/faucet-mcp/index.js b/typescript/faucet-mcp/index.js new file mode 100644 index 0000000..6414234 --- /dev/null +++ b/typescript/faucet-mcp/index.js @@ -0,0 +1,299 @@ +#!/usr/bin/env node + +/** + * TBNB Faucet MCP Server + * Model Context Protocol server for distributing testnet BNB tokens + */ + +import { Server } from "@modelcontextprotocol/sdk/server/index.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { + CallToolRequestSchema, + ListToolsRequestSchema, +} from "@modelcontextprotocol/sdk/types.js"; +import { ethers } from "ethers"; + +// Configuration +const BSC_TESTNET_RPC = process.env.BSC_TESTNET_RPC || "https://data-seed-prebsc-1-s1.binance.org:8545/"; +const FAUCET_PRIVATE_KEY = process.env.FAUCET_PRIVATE_KEY || ""; + +if (!FAUCET_PRIVATE_KEY) { + console.error("WARNING: FAUCET_PRIVATE_KEY environment variable is not set"); +} + +// Initialize provider and wallet +const provider = new ethers.JsonRpcProvider(BSC_TESTNET_RPC); +let wallet = null; +let walletAddress = null; + +if (FAUCET_PRIVATE_KEY) { + wallet = new ethers.Wallet(FAUCET_PRIVATE_KEY, provider); + walletAddress = wallet.address; +} + +// MCP Server +const server = new Server( + { + name: "tbnb-faucet", + version: "1.0.0", + }, + { + capabilities: { + tools: {}, + }, + } +); + +// List available tools +server.setRequestHandler(ListToolsRequestSchema, async () => { + return { + tools: [ + { + name: "send_tbnb", + description: "Send testnet BNB tokens to a specified address on BSC testnet", + inputSchema: { + type: "object", + properties: { + recipient: { + type: "string", + description: "The BSC testnet address that will receive the TBNB tokens", + }, + amount: { + type: "number", + description: "Amount of TBNB to send (default: 0.1, maximum: 1.0)", + default: 0.1, + }, + }, + required: ["recipient"], + }, + }, + { + name: "get_faucet_info", + description: "Get information about the faucet including current balance", + inputSchema: { + type: "object", + properties: {}, + }, + }, + { + name: "get_balance", + description: "Get the TBNB balance of a BSC testnet address", + inputSchema: { + type: "object", + properties: { + address: { + type: "string", + description: "The BSC testnet address to check", + }, + }, + required: ["address"], + }, + }, + ], + }; +}); + +// Handle tool calls +server.setRequestHandler(CallToolRequestSchema, async (request) => { + const { name, arguments: args } = request.params; + + try { + switch (name) { + case "send_tbnb": + return await handleSendTbnb(args); + case "get_faucet_info": + return await handleGetFaucetInfo(); + case "get_balance": + return await handleGetBalance(args); + default: + throw new Error(`Unknown tool: ${name}`); + } + } catch (error) { + return { + content: [ + { + type: "text", + text: JSON.stringify( + { + error: error.message, + }, + null, + 2 + ), + }, + ], + isError: true, + }; + } +}); + +/** + * Send TBNB to a recipient address + */ +async function handleSendTbnb(args) { + if (!wallet) { + throw new Error("Faucet wallet not configured. Please set FAUCET_PRIVATE_KEY."); + } + + const recipient = args?.recipient; + const amount = args?.amount || 0.1; + + if (!recipient) { + throw new Error("Recipient address is required"); + } + + // Validate address + if (!ethers.isAddress(recipient)) { + throw new Error(`Invalid address: ${recipient}`); + } + + const recipientAddress = ethers.getAddress(recipient); + + // Prevent self-transfer + if (recipientAddress.toLowerCase() === walletAddress.toLowerCase()) { + throw new Error("Cannot send tokens to the faucet address itself"); + } + + // Validate amount + if (amount <= 0 || amount > 1.0) { + throw new Error("Amount must be between 0 and 1.0 TBNB"); + } + + try { + // Convert amount to Wei + const amountWei = ethers.parseEther(amount.toString()); + + // Get current gas price + const feeData = await provider.getFeeData(); + + // Send transaction + const tx = await wallet.sendTransaction({ + to: recipientAddress, + value: amountWei, + gasLimit: 21000, + gasPrice: feeData.gasPrice, + }); + + // Wait for transaction to be mined + const receipt = await tx.wait(); + + return { + content: [ + { + type: "text", + text: JSON.stringify( + { + success: true, + transactionHash: tx.hash, + recipient: recipientAddress, + amount: amount, + amountWei: amountWei.toString(), + blockNumber: receipt.blockNumber, + status: receipt.status === 1 ? "confirmed" : "failed", + }, + null, + 2 + ), + }, + ], + }; + } catch (error) { + throw new Error(`Transaction failed: ${error.message}`); + } +} + +/** + * Get faucet information and balance + */ +async function handleGetFaucetInfo() { + if (!walletAddress) { + throw new Error("Faucet wallet not configured"); + } + + try { + const balance = await provider.getBalance(walletAddress); + const balanceTbnb = ethers.formatEther(balance); + + // Get network info + const network = await provider.getNetwork(); + + return { + content: [ + { + type: "text", + text: JSON.stringify( + { + faucetAddress: walletAddress, + balanceWei: balance.toString(), + balanceTbnb: parseFloat(balanceTbnb), + network: { + name: "BSC Testnet", + chainId: network.chainId.toString(), + }, + rpcEndpoint: BSC_TESTNET_RPC, + }, + null, + 2 + ), + }, + ], + }; + } catch (error) { + throw new Error(`Failed to get faucet info: ${error.message}`); + } +} + +/** + * Get balance of an address + */ +async function handleGetBalance(args) { + const address = args?.address; + + if (!address) { + throw new Error("Address is required"); + } + + // Validate address + if (!ethers.isAddress(address)) { + throw new Error(`Invalid address: ${address}`); + } + + try { + const addressChecksum = ethers.getAddress(address); + const balance = await provider.getBalance(addressChecksum); + const balanceTbnb = ethers.formatEther(balance); + + return { + content: [ + { + type: "text", + text: JSON.stringify( + { + address: addressChecksum, + balanceWei: balance.toString(), + balanceTbnb: parseFloat(balanceTbnb), + network: "BSC Testnet", + }, + null, + 2 + ), + }, + ], + }; + } catch (error) { + throw new Error(`Failed to get balance: ${error.message}`); + } +} + +// Start server +async function main() { + const transport = new StdioServerTransport(); + await server.connect(transport); + + console.error("TBNB Faucet MCP Server running on stdio"); +} + +main().catch((error) => { + console.error("Fatal error:", error); + process.exit(1); +}); diff --git a/typescript/faucet-mcp/index.test.js b/typescript/faucet-mcp/index.test.js new file mode 100644 index 0000000..9f47c94 --- /dev/null +++ b/typescript/faucet-mcp/index.test.js @@ -0,0 +1,277 @@ +/** + * Unit tests for TBNB Faucet MCP Server (Example 3 - Node.js) + * + * Note: These tests focus on the business logic and validation + * rather than full integration with the MCP server, since the server + * initializes on import and uses stdio transport. + */ + +import { describe, test, expect, beforeEach, jest } from '@jest/globals'; + +describe('Address Validation Logic', () => { + test('should validate correct Ethereum address format', () => { + const validAddress = '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb0'; + // Ethereum addresses are 42 characters (0x + 40 hex chars) + expect(validAddress.length).toBe(42); + expect(validAddress.startsWith('0x')).toBe(true); + expect(/^0x[a-fA-F0-9]{40}$/.test(validAddress)).toBe(true); + }); + + test('should reject invalid address formats', () => { + const invalidAddresses = [ + 'invalid', + '0x123', + '742d35Cc6634C0532925a3b844Bc9e7595f0bEb', // Missing 0x + '0xGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG', // Invalid hex + '', + ]; + + invalidAddresses.forEach((addr) => { + const isValid = addr.length === 42 && addr.startsWith('0x') && /^0x[a-fA-F0-9]{40}$/.test(addr); + expect(isValid).toBe(false); + }); + }); +}); + +describe('Amount Validation Logic', () => { + test('should accept valid amounts between 0 and 1.0', () => { + const validAmounts = [0.01, 0.1, 0.5, 1.0]; + + validAmounts.forEach((amount) => { + const isValid = amount > 0 && amount <= 1.0; + expect(isValid).toBe(true); + }); + }); + + test('should reject zero or negative amounts', () => { + const invalidAmounts = [0, -0.1, -1.0]; + + invalidAmounts.forEach((amount) => { + const isValid = amount > 0 && amount <= 1.0; + expect(isValid).toBe(false); + }); + }); + + test('should reject amounts greater than 1.0', () => { + const invalidAmounts = [1.1, 2.0, 10.0]; + + invalidAmounts.forEach((amount) => { + const isValid = amount > 0 && amount <= 1.0; + expect(isValid).toBe(false); + }); + }); +}); + +describe('Self-Transfer Prevention Logic', () => { + test('should detect self-transfer attempts', () => { + const faucetAddress = '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb'; + const recipient = '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb'; + + const isSelfTransfer = recipient.toLowerCase() === faucetAddress.toLowerCase(); + expect(isSelfTransfer).toBe(true); + }); + + test('should allow transfers to different addresses', () => { + const faucetAddress = '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb'; + const recipient = '0x1234567890123456789012345678901234567890'; + + const isSelfTransfer = recipient.toLowerCase() === faucetAddress.toLowerCase(); + expect(isSelfTransfer).toBe(false); + }); +}); + +describe('Wei Conversion Logic', () => { + test('should convert TBNB to Wei correctly', () => { + // 1 TBNB = 10^18 Wei + const tbnbAmounts = [ + { tbnb: 0.1, wei: '100000000000000000' }, + { tbnb: 1.0, wei: '1000000000000000000' }, + { tbnb: 0.01, wei: '10000000000000000' }, + ]; + + tbnbAmounts.forEach(({ tbnb, wei }) => { + // Manual conversion for testing + const calculatedWei = (tbnb * Math.pow(10, 18)).toString(); + expect(calculatedWei).toBe(wei); + }); + }); + + test('should handle Wei to TBNB conversion', () => { + const weiAmounts = [ + { wei: '100000000000000000', tbnb: 0.1 }, + { wei: '1000000000000000000', tbnb: 1.0 }, + { wei: '500000000000000000', tbnb: 0.5 }, + ]; + + weiAmounts.forEach(({ wei, tbnb }) => { + // Manual conversion for testing + const calculatedTbnb = parseFloat(wei) / Math.pow(10, 18); + expect(calculatedTbnb).toBeCloseTo(tbnb, 10); + }); + }); +}); + +describe('Transaction Response Format', () => { + test('should format successful transaction response correctly', () => { + const mockResponse = { + success: true, + transactionHash: '0xabcdef1234567890', + recipient: '0x1234567890123456789012345678901234567890', + amount: 0.1, + amountWei: '100000000000000000', + blockNumber: 12345, + status: 'confirmed', + }; + + expect(mockResponse).toHaveProperty('success'); + expect(mockResponse).toHaveProperty('transactionHash'); + expect(mockResponse).toHaveProperty('recipient'); + expect(mockResponse).toHaveProperty('amount'); + expect(mockResponse).toHaveProperty('blockNumber'); + expect(mockResponse).toHaveProperty('status'); + expect(mockResponse.success).toBe(true); + expect(mockResponse.status).toBe('confirmed'); + }); + + test('should format error response correctly', () => { + const mockErrorResponse = { + error: 'Invalid address: invalid_address', + }; + + expect(mockErrorResponse).toHaveProperty('error'); + expect(typeof mockErrorResponse.error).toBe('string'); + }); +}); + +describe('Faucet Info Response Format', () => { + test('should format faucet info response correctly', () => { + const mockFaucetInfo = { + faucetAddress: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb', + balanceWei: '1000000000000000000', + balanceTbnb: 1.0, + network: { + name: 'BSC Testnet', + chainId: '97', + }, + rpcEndpoint: 'https://data-seed-prebsc-1-s1.binance.org:8545/', + }; + + expect(mockFaucetInfo).toHaveProperty('faucetAddress'); + expect(mockFaucetInfo).toHaveProperty('balanceWei'); + expect(mockFaucetInfo).toHaveProperty('balanceTbnb'); + expect(mockFaucetInfo).toHaveProperty('network'); + expect(mockFaucetInfo).toHaveProperty('rpcEndpoint'); + expect(mockFaucetInfo.network.chainId).toBe('97'); + }); +}); + +describe('Balance Query Response Format', () => { + test('should format balance query response correctly', () => { + const mockBalanceResponse = { + address: '0x1234567890123456789012345678901234567890', + balanceWei: '500000000000000000', + balanceTbnb: 0.5, + network: 'BSC Testnet', + }; + + expect(mockBalanceResponse).toHaveProperty('address'); + expect(mockBalanceResponse).toHaveProperty('balanceWei'); + expect(mockBalanceResponse).toHaveProperty('balanceTbnb'); + expect(mockBalanceResponse).toHaveProperty('network'); + expect(mockBalanceResponse.balanceTbnb).toBe(0.5); + }); +}); + +describe('Error Handling', () => { + test('should handle missing recipient address', () => { + const recipient = ''; + if (!recipient) { + expect(() => { + throw new Error('Recipient address is required'); + }).toThrow('Recipient address is required'); + } + }); + + test('should handle missing wallet configuration', () => { + const wallet = null; + if (!wallet) { + expect(() => { + throw new Error('Faucet wallet not configured. Please set FAUCET_PRIVATE_KEY.'); + }).toThrow('Faucet wallet not configured'); + } + }); + + test('should handle network errors gracefully', () => { + const networkError = new Error('Network error'); + expect(networkError.message).toBe('Network error'); + expect(networkError).toBeInstanceOf(Error); + }); +}); + +describe('MCP Tool Schema Validation', () => { + test('send_tbnb tool should have correct schema structure', () => { + const toolSchema = { + name: 'send_tbnb', + description: 'Send testnet BNB tokens to a specified address on BSC testnet', + inputSchema: { + type: 'object', + properties: { + recipient: { + type: 'string', + description: 'The BSC testnet address that will receive the TBNB tokens', + }, + amount: { + type: 'number', + description: 'Amount of TBNB to send (default: 0.1, maximum: 1.0)', + default: 0.1, + }, + }, + required: ['recipient'], + }, + }; + + expect(toolSchema).toHaveProperty('name'); + expect(toolSchema).toHaveProperty('description'); + expect(toolSchema).toHaveProperty('inputSchema'); + expect(toolSchema.inputSchema.type).toBe('object'); + expect(toolSchema.inputSchema.required).toContain('recipient'); + }); + + test('get_faucet_info tool should have correct schema structure', () => { + const toolSchema = { + name: 'get_faucet_info', + description: 'Get information about the faucet including current balance', + inputSchema: { + type: 'object', + properties: {}, + }, + }; + + expect(toolSchema).toHaveProperty('name'); + expect(toolSchema).toHaveProperty('description'); + expect(toolSchema).toHaveProperty('inputSchema'); + expect(toolSchema.inputSchema.type).toBe('object'); + }); + + test('get_balance tool should have correct schema structure', () => { + const toolSchema = { + name: 'get_balance', + description: 'Get the TBNB balance of a BSC testnet address', + inputSchema: { + type: 'object', + properties: { + address: { + type: 'string', + description: 'The BSC testnet address to check', + }, + }, + required: ['address'], + }, + }; + + expect(toolSchema).toHaveProperty('name'); + expect(toolSchema).toHaveProperty('description'); + expect(toolSchema).toHaveProperty('inputSchema'); + expect(toolSchema.inputSchema.required).toContain('address'); + }); +}); diff --git a/typescript/faucet-mcp/package.json b/typescript/faucet-mcp/package.json new file mode 100644 index 0000000..8e7ca75 --- /dev/null +++ b/typescript/faucet-mcp/package.json @@ -0,0 +1,41 @@ +{ + "name": "tbnb-faucet-mcp", + "version": "1.0.0", + "description": "MCP server for distributing testnet BNB tokens on BSC testnet", + "type": "module", + "main": "index.js", + "scripts": { + "start": "node index.js", + "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js", + "test:watch": "node --experimental-vm-modules node_modules/jest/bin/jest.js --watch", + "test:coverage": "node --experimental-vm-modules node_modules/jest/bin/jest.js --coverage" + }, + "keywords": [ + "mcp", + "model-context-protocol", + "bnb", + "testnet", + "faucet", + "blockchain" + ], + "author": "", + "license": "MIT", + "dependencies": { + "@modelcontextprotocol/sdk": "^0.5.0", + "ethers": "^6.9.0" + }, + "devDependencies": { + "@jest/globals": "^29.7.0", + "jest": "^29.7.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "jest": { + "testEnvironment": "node", + "transform": {}, + "moduleNameMapper": { + "^(\\.{1,2}/.*)\\.js$": "$1" + } + } +} From ff8d96b9c06468360e39a860c5aef23aa6cdf7c9 Mon Sep 17 00:00:00 2001 From: 1evi7eo Date: Sat, 24 Jan 2026 14:06:01 +0800 Subject: [PATCH 2/2] completed implementation --- python/trustless-agent-example/README.md | 418 ++++++++++++ .../trustless-agent-example/requirements.txt | 5 + python/trustless-agent-example/run.sh | 58 ++ .../templates/index.html | 642 ++++++++++++++++++ .../test_trustless_agents.py | 282 ++++++++ .../trustless_agents.py | 484 +++++++++++++ 6 files changed, 1889 insertions(+) create mode 100644 python/trustless-agent-example/README.md create mode 100644 python/trustless-agent-example/requirements.txt create mode 100644 python/trustless-agent-example/run.sh create mode 100644 python/trustless-agent-example/templates/index.html create mode 100644 python/trustless-agent-example/test_trustless_agents.py create mode 100644 python/trustless-agent-example/trustless_agents.py diff --git a/python/trustless-agent-example/README.md b/python/trustless-agent-example/README.md new file mode 100644 index 0000000..eca352a --- /dev/null +++ b/python/trustless-agent-example/README.md @@ -0,0 +1,418 @@ +# EIP-8004 Trustless Agents Implementation + +An implementation of EIP-8004 (ERC-8004) Trustless Agents standard, enabling AI agents to discover, choose, and interact with each other across organizational boundaries without pre-existing trust. + +## Overview + +EIP-8004 provides mechanisms for discovering and trusting agents in untrusted settings. It complements existing agent communication protocols like Model Context Protocol (MCP) and Agent2Agent (A2A) by addressing the gap in agent discovery and trust establishment. + +This implementation includes: +- **Identity Registry**: ERC-721 based agent identity management +- **Reputation Registry**: Feedback and scoring system for agents +- **Validation Registry**: Independent validator checks and verification +- **Interaction Logging**: Record of agent-to-agent interactions + +![Trustless Agent Interface](https://i.imgur.com/vnLWiMA.png) + +## Features + +- **Agent Registration**: Register agents with identity, capabilities, and metadata +- **Agent Discovery**: Find agents by type, capability, or reputation score +- **Reputation System**: Submit and track reputation feedback +- **Validation Requests**: Request and record validator checks +- **Interaction Tracking**: Log agent-to-agent interactions +- **Interactive Frontend**: Web interface for managing agents +- **MCP Integration**: Full Model Context Protocol support + +## Prerequisites + +- Python 3.8 or higher +- A BSC testnet wallet for agent operations +- Access to BSC testnet RPC endpoint + +## Installation + +1. Navigate to the trustless-agent-example directory: +```bash +cd trustless-agent-example +``` + +2. Run the setup script: +```bash +./run.sh +``` + +The script will: +- Create a virtual environment +- Install all dependencies +- Run tests +- Start the server + +## Configuration + +Create a `.env` file (or copy from `.env.example`): + +```bash +PRIVATE_KEY=0xYourPrivateKeyHere +BSC_TESTNET_RPC=https://data-seed-prebsc-1-s1.binance.org:8545/ +``` + +## Usage + +### Running the Application + +```bash +./run.sh +``` + +The application will start on `http://localhost:5002` + +### Available Tools + +#### `register_agent_identity` + +Register an agent identity in the Identity Registry. + +**Parameters:** +- `agent_name` (string, required): Name of the agent +- `agent_type` (string, required): Type of agent (payment, data, computation, oracle, etc.) +- `capabilities` (list, required): List of agent capabilities +- `metadata_uri` (string, optional): URI to agent metadata +- `description` (string, optional): Agent description + +**Example:** +```json +{ + "agent_name": "Payment Processor Agent", + "agent_type": "payment", + "capabilities": ["payment", "api-access", "multi-currency"], + "description": "Handles payment processing for AI services" +} +``` + +**Returns:** +```json +{ + "success": true, + "agent_id": "agent_1234567890", + "identity": { + "agent_id": "agent_1234567890", + "agent_name": "Payment Processor Agent", + "agent_type": "payment", + "capabilities": ["payment", "api-access"], + "status": "active" + } +} +``` + +#### `discover_agents` + +Discover agents matching criteria. + +**Parameters:** +- `agent_type` (string, optional): Filter by agent type +- `capability` (string, optional): Filter by specific capability +- `min_reputation` (float, optional): Minimum reputation score (default: 0.0) + +**Returns:** +```json +{ + "success": true, + "count": 5, + "agents": [ + { + "agent_id": "agent_1234567890", + "agent_name": "Payment Processor Agent", + "agent_type": "payment", + "capabilities": ["payment", "api-access"], + "reputation_score": 4.5 + } + ] +} +``` + +#### `submit_reputation_feedback` + +Submit reputation feedback for an agent. + +**Parameters:** +- `agent_id` (string, required): The agent ID to rate +- `rating` (float, required): Rating score (0.0 to 5.0) +- `feedback_type` (string, optional): Type of feedback (default: "general") +- `comment` (string, optional): Feedback comment + +**Returns:** +```json +{ + "success": true, + "feedback": { + "agent_id": "agent_1234567890", + "rating": 4.5, + "feedback_type": "performance", + "comment": "Excellent performance!" + } +} +``` + +#### `request_validation` + +Request validation for an agent. + +**Parameters:** +- `agent_id` (string, required): The agent ID to validate +- `validation_type` (string, required): Type of validation (stake, zk_proof, tee) +- `validation_data` (dict, required): Validation-specific data + +**Returns:** +```json +{ + "success": true, + "validation": { + "agent_id": "agent_1234567890", + "validation_type": "stake", + "status": "pending" + } +} +``` + +#### `record_agent_interaction` + +Record an interaction between agents. + +**Parameters:** +- `from_agent_id` (string, required): Source agent ID +- `to_agent_id` (string, required): Target agent ID +- `interaction_type` (string, required): Type of interaction +- `result` (string, optional): Interaction result (default: "success") +- `metadata` (dict, optional): Additional metadata + +**Returns:** +```json +{ + "success": true, + "interaction": { + "from_agent_id": "agent_1234567890", + "to_agent_id": "agent_9876543210", + "interaction_type": "payment", + "result": "success" + } +} +``` + +#### `get_agent_profile` + +Get complete agent profile including identity, reputation, and validations. + +**Parameters:** +- `agent_id` (string, required): The agent ID + +**Returns:** +```json +{ + "success": true, + "agent_id": "agent_1234567890", + "identity": {...}, + "reputation": { + "score": 4.5, + "feedback_count": 10, + "recent_feedback": [...] + }, + "validations": { + "count": 3, + "recent": [...] + }, + "interactions": { + "as_source": 15, + "as_target": 20 + } +} +``` + +## Demo Example + +### Step 1: Register an Agent + +**Input:** +```json +POST /api/register +{ + "agent_name": "Payment Processor", + "agent_type": "payment", + "capabilities": ["payment", "api-access"], + "description": "Handles payment processing" +} +``` + +**Output:** +```json +{ + "success": true, + "agent_id": "agent_1234567890", + "identity": { + "agent_name": "Payment Processor", + "status": "active" + } +} +``` + +### Step 2: Discover Agents + +**Input:** +```json +GET /api/discover?agent_type=payment&min_reputation=4.0 +``` + +**Output:** +```json +{ + "success": true, + "count": 1, + "agents": [ + { + "agent_id": "agent_1234567890", + "agent_name": "Payment Processor", + "reputation_score": 4.5 + } + ] +} +``` + +### Step 3: Submit Feedback + +**Input:** +```json +POST /api/feedback +{ + "agent_id": "agent_1234567890", + "rating": 4.5, + "feedback_type": "performance", + "comment": "Fast and reliable" +} +``` + +**Output:** +```json +{ + "success": true, + "feedback": { + "rating": 4.5, + "feedback_type": "performance" + } +} +``` + +### Step 4: Record Interaction + +**Input:** +```json +POST /api/interaction +{ + "from_agent_id": "agent_1111111111", + "to_agent_id": "agent_1234567890", + "interaction_type": "payment", + "result": "success" +} +``` + +**Output:** +```json +{ + "success": true, + "interaction": { + "interaction_type": "payment", + "result": "success" + } +} +``` + +## Network Information + +- **Network**: Binance Smart Chain Testnet +- **Chain ID**: 97 +- **Standard**: EIP-8004 (ERC-8004) Trustless Agents +- **RPC Endpoint**: https://data-seed-prebsc-1-s1.binance.org:8545/ +- **Explorer**: https://testnet.bscscan.com/ + +## Testing + +Run unit tests: + +```bash +source venv/bin/activate +pytest test_trustless_agents.py -v +``` + +Run tests with coverage: + +```bash +pytest test_trustless_agents.py --cov=trustless_agents --cov-report=html +``` + +## Architecture + +- **Backend**: Flask web server with MCP integration +- **Frontend**: HTML/CSS/JavaScript single-page application +- **Blockchain**: Web3.py for BSC testnet interaction +- **Registries**: In-memory storage (production would use on-chain contracts) +- **Protocol**: Model Context Protocol (MCP) for AI agent integration + +## Key Components + +### Identity Registry +- ERC-721 based agent identifiers +- Portable, censorship-resistant identities +- Metadata URI support + +### Reputation Registry +- On-chain and off-chain feedback +- Scoring system (0.0 to 5.0) +- Multiple feedback types + +### Validation Registry +- Generic validation hooks +- Support for stake-secured validation +- Zero-knowledge proof support +- Trusted execution environment oracles + +## Trust Models + +EIP-8004 supports pluggable, tiered trust models with security proportional to value at risk: +- **Low-stake**: Simple reputation-based trust +- **Medium-stake**: Reputation + validation +- **High-stake**: Reputation + validation + stake + +## Security Notes + +- โš ๏ธ **Never commit your private key** to version control +- Use environment variables for sensitive data +- This example is for testnet use only +- In production, registries should be on-chain contracts +- Implement proper access control for production use + +## Troubleshooting + +### Agent Registration Fails +- Verify agent address is configured +- Check RPC endpoint is accessible +- Ensure all required fields are provided + +### Discovery Returns No Results +- Check filter criteria are not too restrictive +- Verify agents are registered and active +- Lower minimum reputation threshold + +### Frontend Not Loading +- Verify Flask server is running on port 5002 +- Check browser console for errors +- Ensure templates directory exists + +## License + +This project is provided as-is for educational and demonstration purposes. + +## References + +- [EIP-8004: Trustless Agents](https://eips.ethereum.org/EIPS/eip-8004) +- [ERC-721: Non-Fungible Token Standard](https://eips.ethereum.org/EIPS/eip-721) +- [Model Context Protocol](https://modelcontextprotocol.io) +- [BNB Chain Documentation](https://docs.bnbchain.org) diff --git a/python/trustless-agent-example/requirements.txt b/python/trustless-agent-example/requirements.txt new file mode 100644 index 0000000..e3b0a1f --- /dev/null +++ b/python/trustless-agent-example/requirements.txt @@ -0,0 +1,5 @@ +flask>=3.0.0 +web3>=6.0.0 +mcp>=1.0.0 +pytest>=7.0.0 +pytest-cov>=4.0.0 diff --git a/python/trustless-agent-example/run.sh b/python/trustless-agent-example/run.sh new file mode 100644 index 0000000..f3466eb --- /dev/null +++ b/python/trustless-agent-example/run.sh @@ -0,0 +1,58 @@ +#!/bin/bash + +# EIP-8004 Trustless Agents - Run Script +# This script sets up the virtual environment and runs the trustless agents server + +set -e + +echo "๐Ÿš€ Setting up EIP-8004 Trustless Agents Server" +echo "================================================" + +# Check if Python 3 is available +if ! command -v python3 &> /dev/null; then + echo "โŒ Python 3 is required but not installed." + exit 1 +fi + +# Create virtual environment if it doesn't exist +if [ ! -d "venv" ]; then + echo "๐Ÿ“ฆ Creating virtual environment..." + python3 -m venv venv +fi + +# Activate virtual environment +echo "๐Ÿ”ง Activating virtual environment..." +source venv/bin/activate + +# Install dependencies +echo "๐Ÿ“ฅ Installing dependencies..." +pip install --upgrade pip +pip install -r requirements.txt + +# Check if .env file exists +if [ ! -f ".env" ]; then + echo "โš ๏ธ .env file not found. Creating from .env.example..." + cp .env.example .env + echo "โš ๏ธ Please edit .env file and add your PRIVATE_KEY before running!" + exit 1 +fi + +# Load environment variables +export $(cat .env | grep -v '^#' | xargs) + +# Check if PRIVATE_KEY is set +if [ -z "$PRIVATE_KEY" ] || [ "$PRIVATE_KEY" == "0xYourPrivateKeyHere" ]; then + echo "โŒ PRIVATE_KEY not set in .env file. Please configure it first." + exit 1 +fi + +# Run tests +echo "๐Ÿงช Running tests..." +python -m pytest test_trustless_agents.py -v + +# Start the server +echo "โœ… Starting trustless agents server..." +echo "๐ŸŒ Frontend will be available at http://localhost:5002" +echo "๐Ÿ“Š MCP server is running..." +echo "" +python trustless_agents.py diff --git a/python/trustless-agent-example/templates/index.html b/python/trustless-agent-example/templates/index.html new file mode 100644 index 0000000..43608a0 --- /dev/null +++ b/python/trustless-agent-example/templates/index.html @@ -0,0 +1,642 @@ + + + + + + EIP-8004 Trustless Agents + + + +
+
+

๐Ÿค– EIP-8004 Trustless Agents

+

Discover, Choose, and Interact with AI Agents

+
+ +
+

About EIP-8004

+

EIP-8004 enables AI agents to discover, choose, and interact with each other across organizational boundaries + without pre-existing trust. It includes Identity Registry (ERC-721), Reputation Registry, and Validation Registry + for building an open agent economy.

+
+ +
+ + + + +
+ +
+
+
+

Register Agent Identity

+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+
+ +
+

Registered Agents

+
+
Loading agents...
+
+
+
+
+ +
+
+
+

Discover Agents

+
+
+ + +
+
+ + +
+
+ + +
+ +
+
+ +
+

Discovered Agents

+
+
Use the form to discover agents
+
+
+
+
+ +
+
+

Submit Reputation Feedback

+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+
+
+ +
+
+

Record Agent Interaction

+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+
+
+ + +
+ + + + diff --git a/python/trustless-agent-example/test_trustless_agents.py b/python/trustless-agent-example/test_trustless_agents.py new file mode 100644 index 0000000..d045fc2 --- /dev/null +++ b/python/trustless-agent-example/test_trustless_agents.py @@ -0,0 +1,282 @@ +#!/usr/bin/env python3 +""" +Unit tests for EIP-8004 Trustless Agents +""" + +import os +import sys +import pytest +from unittest.mock import Mock, patch, MagicMock + +# Set test environment variables +os.environ["PRIVATE_KEY"] = "0x" + "1" * 64 +os.environ["BSC_TESTNET_RPC"] = "https://test-rpc.example.com" + +# Mock Web3 +mock_w3 = MagicMock() +mock_w3.is_connected.return_value = True + +MockWeb3 = MagicMock(return_value=mock_w3) +MockWeb3.HTTPProvider = MagicMock() + +_web3_patcher = patch("web3.Web3", MockWeb3) +_web3_patcher.start() + +import trustless_agents # noqa: E402 + + +class TestRegisterAgentIdentity: + """Tests for register_agent_identity tool""" + + def test_missing_agent_address(self): + """Test when agent address is not configured""" + with patch.object(trustless_agents, "agent_address", None): + result = trustless_agents.register_agent_identity( + "Test Agent", + "payment", + ["payment", "api"] + ) + assert result["success"] is False + assert "not configured" in result["error"].lower() + + def test_successful_registration(self): + """Test successful agent registration""" + trustless_agents.identity_registry.clear() + + with patch.object(trustless_agents, "agent_address", "0x1234567890123456789012345678901234567890"): + result = trustless_agents.register_agent_identity( + "Test Agent", + "payment", + ["payment", "api"], + "https://example.com/metadata", + "Test description" + ) + + assert result["success"] is True + assert "agent_id" in result + assert result["identity"]["agent_name"] == "Test Agent" + assert result["identity"]["agent_type"] == "payment" + assert result["identity"]["capabilities"] == ["payment", "api"] + assert result["identity"]["status"] == "active" + + +class TestDiscoverAgents: + """Tests for discover_agents tool""" + + def test_discover_all_agents(self): + """Test discovering all agents""" + trustless_agents.identity_registry.clear() + + with patch.object(trustless_agents, "agent_address", "0x123"): + # Register multiple agents + trustless_agents.register_agent_identity("Agent1", "payment", ["payment"]) + trustless_agents.register_agent_identity("Agent2", "data", ["data"]) + + result = trustless_agents.discover_agents() + + assert result["success"] is True + assert result["count"] == 2 + + def test_discover_by_type(self): + """Test discovering agents by type""" + trustless_agents.identity_registry.clear() + + with patch.object(trustless_agents, "agent_address", "0x123"): + trustless_agents.register_agent_identity("PaymentAgent", "payment", ["payment"]) + trustless_agents.register_agent_identity("DataAgent", "data", ["data"]) + + result = trustless_agents.discover_agents(agent_type="payment") + + assert result["success"] is True + assert result["count"] == 1 + assert result["agents"][0]["agent_type"] == "payment" + + def test_discover_by_capability(self): + """Test discovering agents by capability""" + trustless_agents.identity_registry.clear() + + with patch.object(trustless_agents, "agent_address", "0x123"): + trustless_agents.register_agent_identity("Agent1", "payment", ["payment", "api"]) + trustless_agents.register_agent_identity("Agent2", "data", ["data"]) + + result = trustless_agents.discover_agents(capability="api") + + assert result["success"] is True + assert result["count"] == 1 + assert "api" in result["agents"][0]["capabilities"] + + +class TestSubmitReputationFeedback: + """Tests for submit_reputation_feedback tool""" + + def test_agent_not_found(self): + """Test feedback for non-existent agent""" + trustless_agents.identity_registry.clear() + trustless_agents.reputation_registry.clear() + + result = trustless_agents.submit_reputation_feedback( + "invalid_id", + 5.0, + "general", + "Great agent!" + ) + + assert result["success"] is False + assert "not found" in result["error"].lower() + + def test_invalid_rating(self): + """Test feedback with invalid rating""" + trustless_agents.identity_registry.clear() + trustless_agents.reputation_registry.clear() + + with patch.object(trustless_agents, "agent_address", "0x123"): + create_result = trustless_agents.register_agent_identity("Test", "payment", ["payment"]) + agent_id = create_result["agent_id"] + + result = trustless_agents.submit_reputation_feedback( + agent_id, + 10.0, # Invalid rating > 5.0 + "general" + ) + + assert result["success"] is False + assert "between" in result["error"].lower() + + def test_successful_feedback(self): + """Test successful feedback submission""" + trustless_agents.identity_registry.clear() + trustless_agents.reputation_registry.clear() + + with patch.object(trustless_agents, "agent_address", "0x123"): + create_result = trustless_agents.register_agent_identity("Test", "payment", ["payment"]) + agent_id = create_result["agent_id"] + + result = trustless_agents.submit_reputation_feedback( + agent_id, + 4.5, + "performance", + "Excellent performance!" + ) + + assert result["success"] is True + assert result["feedback"]["rating"] == 4.5 + assert result["feedback"]["feedback_type"] == "performance" + + +class TestRequestValidation: + """Tests for request_validation tool""" + + def test_agent_not_found(self): + """Test validation for non-existent agent""" + trustless_agents.identity_registry.clear() + trustless_agents.validation_registry.clear() + + result = trustless_agents.request_validation( + "invalid_id", + "stake", + {"amount": 1000} + ) + + assert result["success"] is False + assert "not found" in result["error"].lower() + + def test_successful_validation_request(self): + """Test successful validation request""" + trustless_agents.identity_registry.clear() + trustless_agents.validation_registry.clear() + + with patch.object(trustless_agents, "agent_address", "0x123"): + create_result = trustless_agents.register_agent_identity("Test", "payment", ["payment"]) + agent_id = create_result["agent_id"] + + result = trustless_agents.request_validation( + agent_id, + "stake", + {"amount": 1000, "token": "BNB"} + ) + + assert result["success"] is True + assert result["validation"]["validation_type"] == "stake" + assert result["validation"]["status"] == "pending" + + +class TestRecordAgentInteraction: + """Tests for record_agent_interaction tool""" + + def test_agent_not_found(self): + """Test interaction with non-existent agent""" + trustless_agents.identity_registry.clear() + trustless_agents.interaction_log.clear() + + result = trustless_agents.record_agent_interaction( + "invalid_id", + "another_invalid_id", + "payment" + ) + + assert result["success"] is False + assert "not found" in result["error"].lower() + + def test_successful_interaction(self): + """Test successful interaction recording""" + trustless_agents.identity_registry.clear() + trustless_agents.interaction_log.clear() + + with patch.object(trustless_agents, "agent_address", "0x123"): + create_result1 = trustless_agents.register_agent_identity("Agent1", "payment", ["payment"]) + create_result2 = trustless_agents.register_agent_identity("Agent2", "data", ["data"]) + + agent_id1 = create_result1["agent_id"] + agent_id2 = create_result2["agent_id"] + + result = trustless_agents.record_agent_interaction( + agent_id1, + agent_id2, + "payment", + "success", + {"amount": 100} + ) + + assert result["success"] is True + assert result["interaction"]["interaction_type"] == "payment" + assert result["interaction"]["result"] == "success" + + +class TestGetAgentProfile: + """Tests for get_agent_profile tool""" + + def test_agent_not_found(self): + """Test getting profile of non-existent agent""" + trustless_agents.identity_registry.clear() + + result = trustless_agents.get_agent_profile("invalid_id") + + assert result["success"] is False + assert "not found" in result["error"].lower() + + def test_successful_profile_retrieval(self): + """Test successful profile retrieval""" + trustless_agents.identity_registry.clear() + trustless_agents.reputation_registry.clear() + trustless_agents.interaction_log.clear() + + with patch.object(trustless_agents, "agent_address", "0x123"): + create_result = trustless_agents.register_agent_identity("Test", "payment", ["payment"]) + agent_id = create_result["agent_id"] + + # Add some feedback + trustless_agents.submit_reputation_feedback(agent_id, 4.5, "general") + + result = trustless_agents.get_agent_profile(agent_id) + + assert result["success"] is True + assert result["agent_id"] == agent_id + assert "identity" in result + assert "reputation" in result + assert "validations" in result + assert "interactions" in result + assert result["reputation"]["score"] == 4.5 + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) diff --git a/python/trustless-agent-example/trustless_agents.py b/python/trustless-agent-example/trustless_agents.py new file mode 100644 index 0000000..917d50e --- /dev/null +++ b/python/trustless-agent-example/trustless_agents.py @@ -0,0 +1,484 @@ +#!/usr/bin/env python3 +""" +EIP-8004 Trustless Agents Implementation +Enables AI agents to discover, choose, and interact with each other +Implements Identity, Reputation, and Validation registries +""" + +import os +import json +import time +from datetime import datetime +from typing import Any, Dict, List, Optional +from flask import Flask, render_template, request, jsonify +from web3 import Web3 +from mcp.server.fastmcp import FastMCP + +app = Flask(__name__) + +# Initialize MCP server +mcp = FastMCP("EIP-8004 Trustless Agents Server") + +# Configuration +BSC_TESTNET_RPC = os.getenv("BSC_TESTNET_RPC", "https://data-seed-prebsc-1-s1.binance.org:8545/") +PRIVATE_KEY = os.getenv("PRIVATE_KEY", "") + +# Initialize Web3 +w3 = Web3(Web3.HTTPProvider(BSC_TESTNET_RPC)) + +if not w3.is_connected(): + raise ConnectionError("Failed to connect to BSC Testnet") + +# Get agent account +agent_account = None +agent_address = None +if PRIVATE_KEY: + agent_account = w3.eth.account.from_key(PRIVATE_KEY) + agent_address = agent_account.address + +# In-memory registries (in production, these would be on-chain) +identity_registry: Dict[str, Dict[str, Any]] = {} +reputation_registry: Dict[str, List[Dict[str, Any]]] = {} +validation_registry: Dict[str, List[Dict[str, Any]]] = {} + +# Agent interactions log +interaction_log: List[Dict[str, Any]] = [] +_agent_counter = 0 # Counter for unique agent IDs + + +@mcp.tool() +def register_agent_identity( + agent_name: str, + agent_type: str, + capabilities: List[str], + metadata_uri: str = "", + description: str = "" +) -> Dict[str, Any]: + """ + Register an agent identity in the Identity Registry (ERC-721 based). + + Args: + agent_name: Name of the agent + agent_type: Type of agent (e.g., "payment", "data", "computation") + capabilities: List of agent capabilities + metadata_uri: URI to agent metadata (optional) + description: Agent description (optional) + + Returns: + Agent identity registration details + """ + if not agent_address: + return { + "success": False, + "error": "Agent address not configured" + } + + try: + # Generate unique agent ID (in production, this would be an ERC-721 token ID) + global _agent_counter + _agent_counter += 1 + timestamp_ms = int(time.time() * 1000) + agent_id = f"agent_{timestamp_ms}_{_agent_counter}" + + # Create identity record + identity = { + "agent_id": agent_id, + "agent_address": agent_address, + "agent_name": agent_name, + "agent_type": agent_type, + "capabilities": capabilities, + "metadata_uri": metadata_uri, + "description": description, + "registered_at": datetime.now().isoformat(), + "status": "active" + } + + # Store in registry + identity_registry[agent_id] = identity + + return { + "success": True, + "agent_id": agent_id, + "identity": identity, + "message": "Agent identity registered successfully" + } + + except Exception as e: + return { + "success": False, + "error": str(e) + } + + +@mcp.tool() +def discover_agents( + agent_type: Optional[str] = None, + capability: Optional[str] = None, + min_reputation: float = 0.0 +) -> Dict[str, Any]: + """ + Discover agents matching criteria. + + Args: + agent_type: Filter by agent type (optional) + capability: Filter by specific capability (optional) + min_reputation: Minimum reputation score (default: 0.0) + + Returns: + List of matching agents with reputation scores + """ + try: + matching_agents = [] + + for agent_id, identity in identity_registry.items(): + if identity["status"] != "active": + continue + + # Filter by type + if agent_type and identity["agent_type"] != agent_type: + continue + + # Filter by capability + if capability and capability not in identity["capabilities"]: + continue + + # Calculate reputation score + reputation_score = calculate_reputation(agent_id) + + # Filter by minimum reputation + if reputation_score < min_reputation: + continue + + agent_info = identity.copy() + agent_info["reputation_score"] = reputation_score + matching_agents.append(agent_info) + + return { + "success": True, + "count": len(matching_agents), + "agents": matching_agents + } + + except Exception as e: + return { + "success": False, + "error": str(e) + } + + +@mcp.tool() +def submit_reputation_feedback( + agent_id: str, + rating: float, + feedback_type: str = "general", + comment: str = "" +) -> Dict[str, Any]: + """ + Submit reputation feedback for an agent (Reputation Registry). + + Args: + agent_id: The agent ID to rate + rating: Rating score (0.0 to 5.0) + feedback_type: Type of feedback (e.g., "performance", "reliability", "cost") + comment: Optional feedback comment + + Returns: + Feedback submission result + """ + if not agent_address: + return { + "success": False, + "error": "Agent address not configured" + } + + try: + if agent_id not in identity_registry: + return { + "success": False, + "error": "Agent not found" + } + + # Validate rating + if rating < 0.0 or rating > 5.0: + return { + "success": False, + "error": "Rating must be between 0.0 and 5.0" + } + + # Create feedback record + feedback = { + "agent_id": agent_id, + "rater_address": agent_address, + "rating": rating, + "feedback_type": feedback_type, + "comment": comment, + "timestamp": datetime.now().isoformat() + } + + # Store in reputation registry + if agent_id not in reputation_registry: + reputation_registry[agent_id] = [] + reputation_registry[agent_id].append(feedback) + + return { + "success": True, + "feedback": feedback, + "message": "Reputation feedback submitted" + } + + except Exception as e: + return { + "success": False, + "error": str(e) + } + + +@mcp.tool() +def request_validation( + agent_id: str, + validation_type: str, + validation_data: Dict[str, Any] +) -> Dict[str, Any]: + """ + Request validation for an agent (Validation Registry). + + Args: + agent_id: The agent ID to validate + validation_type: Type of validation (e.g., "stake", "zk_proof", "tee") + validation_data: Validation-specific data + + Returns: + Validation request result + """ + if not agent_address: + return { + "success": False, + "error": "Agent address not configured" + } + + try: + if agent_id not in identity_registry: + return { + "success": False, + "error": "Agent not found" + } + + # Create validation request + validation = { + "agent_id": agent_id, + "requester_address": agent_address, + "validation_type": validation_type, + "validation_data": validation_data, + "status": "pending", + "requested_at": datetime.now().isoformat() + } + + # Store in validation registry + if agent_id not in validation_registry: + validation_registry[agent_id] = [] + validation_registry[agent_id].append(validation) + + return { + "success": True, + "validation": validation, + "message": "Validation request submitted" + } + + except Exception as e: + return { + "success": False, + "error": str(e) + } + + +@mcp.tool() +def record_agent_interaction( + from_agent_id: str, + to_agent_id: str, + interaction_type: str, + result: str = "success", + metadata: Dict[str, Any] = None +) -> Dict[str, Any]: + """ + Record an interaction between agents. + + Args: + from_agent_id: Source agent ID + to_agent_id: Target agent ID + interaction_type: Type of interaction + result: Interaction result (success, failure, timeout) + metadata: Additional interaction metadata + + Returns: + Interaction record + """ + try: + if from_agent_id not in identity_registry or to_agent_id not in identity_registry: + return { + "success": False, + "error": "One or both agents not found" + } + + interaction = { + "from_agent_id": from_agent_id, + "to_agent_id": to_agent_id, + "interaction_type": interaction_type, + "result": result, + "metadata": metadata or {}, + "timestamp": datetime.now().isoformat() + } + + interaction_log.append(interaction) + + return { + "success": True, + "interaction": interaction, + "message": "Interaction recorded" + } + + except Exception as e: + return { + "success": False, + "error": str(e) + } + + +@mcp.tool() +def get_agent_profile(agent_id: str) -> Dict[str, Any]: + """ + Get complete agent profile including identity, reputation, and validations. + + Args: + agent_id: The agent ID + + Returns: + Complete agent profile + """ + try: + if agent_id not in identity_registry: + return { + "success": False, + "error": "Agent not found" + } + + identity = identity_registry[agent_id] + reputation_score = calculate_reputation(agent_id) + reputation_feedback = reputation_registry.get(agent_id, []) + validations = validation_registry.get(agent_id, []) + + # Count interactions + interactions_as_source = len([i for i in interaction_log if i["from_agent_id"] == agent_id]) + interactions_as_target = len([i for i in interaction_log if i["to_agent_id"] == agent_id]) + + return { + "success": True, + "agent_id": agent_id, + "identity": identity, + "reputation": { + "score": reputation_score, + "feedback_count": len(reputation_feedback), + "recent_feedback": reputation_feedback[-5:] if reputation_feedback else [] + }, + "validations": { + "count": len(validations), + "recent": validations[-5:] if validations else [] + }, + "interactions": { + "as_source": interactions_as_source, + "as_target": interactions_as_target + } + } + + except Exception as e: + return { + "success": False, + "error": str(e) + } + + +def calculate_reputation(agent_id: str) -> float: + """Calculate reputation score for an agent""" + if agent_id not in reputation_registry or not reputation_registry[agent_id]: + return 0.0 + + feedbacks = reputation_registry[agent_id] + if not feedbacks: + return 0.0 + + # Simple average rating + total_rating = sum(f["rating"] for f in feedbacks) + return total_rating / len(feedbacks) + + +# Flask routes for frontend +@app.route('/') +def index(): + """Render the trustless agents frontend""" + return render_template('index.html') + + +@app.route('/api/register', methods=['POST']) +def api_register(): + """API endpoint for agent registration""" + data = request.json + result = register_agent_identity( + data.get('agent_name', ''), + data.get('agent_type', ''), + data.get('capabilities', []), + data.get('metadata_uri', ''), + data.get('description', '') + ) + return jsonify(result) + + +@app.route('/api/discover', methods=['GET']) +def api_discover(): + """API endpoint for agent discovery""" + agent_type = request.args.get('agent_type', None) + capability = request.args.get('capability', None) + min_reputation = float(request.args.get('min_reputation', 0.0)) + + result = discover_agents(agent_type, capability, min_reputation) + return jsonify(result) + + +@app.route('/api/feedback', methods=['POST']) +def api_feedback(): + """API endpoint for reputation feedback""" + data = request.json + result = submit_reputation_feedback( + data.get('agent_id', ''), + float(data.get('rating', 0.0)), + data.get('feedback_type', 'general'), + data.get('comment', '') + ) + return jsonify(result) + + +@app.route('/api/agent/', methods=['GET']) +def api_get_agent(agent_id): + """API endpoint for agent profile""" + result = get_agent_profile(agent_id) + return jsonify(result) + + +@app.route('/api/interaction', methods=['POST']) +def api_interaction(): + """API endpoint for recording interactions""" + data = request.json + result = record_agent_interaction( + data.get('from_agent_id', ''), + data.get('to_agent_id', ''), + data.get('interaction_type', ''), + data.get('result', 'success'), + data.get('metadata', {}) + ) + return jsonify(result) + + +if __name__ == "__main__": + # Create templates directory if it doesn't exist + os.makedirs('templates', exist_ok=True) + + # Run Flask app + app.run(host='0.0.0.0', port=5002, debug=True)