Implement Model Context Protocol (MCP) to enable AI Agents to send messages and interact with the StateSet Commerce Network
// mcp-server/src/stateset-mcp-server.ts
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListResourcesRequestSchema,
ListToolsRequestSchema,
ReadResourceRequestSchema
} from '@modelcontextprotocol/sdk/types.js';
import { StatesetClient } from 'stateset-node';
interface StateSetMCPConfig {
apiKey: string;
networkEndpoint: string;
chainId: string;
}
class StateSetMCPServer {
private server: Server;
private statesetClient: StatesetClient;
constructor(config: StateSetMCPConfig) {
this.server = new Server({
name: 'stateset-commerce-mcp',
version: '1.0.0',
}, {
capabilities: {
resources: {},
tools: {},
},
});
this.statesetClient = new StatesetClient({
apiKey: config.apiKey,
networkEndpoint: config.networkEndpoint,
chainId: config.chainId
});
this.setupHandlers();
}
private setupHandlers() {
// List available tools
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'send_message',
description: 'Send a message through the StateSet Commerce Network',
inputSchema: {
type: 'object',
properties: {
recipient: { type: 'string', description: 'Recipient address or agent ID' },
message: { type: 'string', description: 'Message content' },
messageType: {
type: 'string',
enum: ['order', 'payment', 'inventory', 'general'],
description: 'Type of message'
},
metadata: { type: 'object', description: 'Additional message metadata' }
},
required: ['recipient', 'message', 'messageType']
}
},
{
name: 'create_order',
description: 'Create a new order on the StateSet Commerce Network',
inputSchema: {
type: 'object',
properties: {
customerId: { type: 'string' },
items: { type: 'array' },
totalAmount: { type: 'string' },
metadata: { type: 'object' }
},
required: ['customerId', 'items', 'totalAmount']
}
},
{
name: 'query_network_state',
description: 'Query the current state of the StateSet Commerce Network',
inputSchema: {
type: 'object',
properties: {
module: {
type: 'string',
enum: ['orders', 'payments', 'inventory', 'agents']
},
query: { type: 'object' }
},
required: ['module']
}
},
{
name: 'execute_transaction',
description: 'Execute a transaction on the StateSet Commerce Network',
inputSchema: {
type: 'object',
properties: {
module: { type: 'string' },
action: { type: 'string' },
params: { type: 'object' },
signer: { type: 'string' }
},
required: ['module', 'action', 'params']
}
}
]
}));
// Handle tool calls
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
switch (request.params.name) {
case 'send_message':
return await this.handleSendMessage(request.params.arguments);
case 'create_order':
return await this.handleCreateOrder(request.params.arguments);
case 'query_network_state':
return await this.handleQueryNetworkState(request.params.arguments);
case 'execute_transaction':
return await this.handleExecuteTransaction(request.params.arguments);
default:
throw new Error(`Unknown tool: ${request.params.name}`);
}
});
// List available resources
this.server.setRequestHandler(ListResourcesRequestSchema, async () => ({
resources: [
{
uri: 'stateset://network/status',
name: 'Network Status',
description: 'Current status of the StateSet Commerce Network',
mimeType: 'application/json'
},
{
uri: 'stateset://agents/directory',
name: 'Agent Directory',
description: 'List of registered AI agents on the network',
mimeType: 'application/json'
},
{
uri: 'stateset://modules/info',
name: 'Module Information',
description: 'Information about available network modules',
mimeType: 'application/json'
}
]
}));
// Handle resource reads
this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
const { uri } = request.params;
switch (uri) {
case 'stateset://network/status':
return await this.getNetworkStatus();
case 'stateset://agents/directory':
return await this.getAgentDirectory();
case 'stateset://modules/info':
return await this.getModuleInfo();
default:
throw new Error(`Unknown resource: ${uri}`);
}
});
}
private async handleSendMessage(args: any) {
try {
// Create a message transaction on the StateSet Network
const message = {
typeUrl: '/stateset.core.message.MsgSendMessage',
value: {
sender: args.signer || 'ai-agent-default',
recipient: args.recipient,
content: args.message,
messageType: args.messageType,
metadata: JSON.stringify(args.metadata || {}),
timestamp: new Date().toISOString()
}
};
// Broadcast the message to the network
const result = await this.statesetClient.signAndBroadcast(
args.signer || 'ai-agent-default',
[message],
'auto'
);
return {
content: [{
type: 'text',
text: `Message sent successfully. Transaction hash: ${result.transactionHash}`
}],
metadata: {
transactionHash: result.transactionHash,
height: result.height,
gasUsed: result.gasUsed
}
};
} catch (error) {
return {
content: [{
type: 'text',
text: `Failed to send message: ${error.message}`
}],
isError: true
};
}
}
private async handleCreateOrder(args: any) {
try {
const order = await this.statesetClient.orders.create({
customerId: args.customerId,
items: args.items,
totalAmount: args.totalAmount,
status: 'CREATED',
metadata: args.metadata
});
return {
content: [{
type: 'text',
text: `Order created successfully. Order ID: ${order.id}`
}],
metadata: {
orderId: order.id,
status: order.status,
createdAt: order.createdAt
}
};
} catch (error) {
return {
content: [{
type: 'text',
text: `Failed to create order: ${error.message}`
}],
isError: true
};
}
}
private async handleQueryNetworkState(args: any) {
try {
let result;
switch (args.module) {
case 'orders':
result = await this.statesetClient.orders.list(args.query || {});
break;
case 'payments':
result = await this.statesetClient.payments.list(args.query || {});
break;
case 'inventory':
result = await this.statesetClient.inventory.list(args.query || {});
break;
case 'agents':
result = await this.statesetClient.agents.list(args.query || {});
break;
default:
throw new Error(`Unknown module: ${args.module}`);
}
return {
content: [{
type: 'text',
text: JSON.stringify(result, null, 2)
}],
metadata: {
module: args.module,
count: result.length || result.total
}
};
} catch (error) {
return {
content: [{
type: 'text',
text: `Query failed: ${error.message}`
}],
isError: true
};
}
}
private async handleExecuteTransaction(args: any) {
try {
const msg = {
typeUrl: `/stateset.core.${args.module}.${args.action}`,
value: args.params
};
const result = await this.statesetClient.signAndBroadcast(
args.signer || 'ai-agent-default',
[msg],
'auto'
);
return {
content: [{
type: 'text',
text: `Transaction executed successfully. Hash: ${result.transactionHash}`
}],
metadata: {
transactionHash: result.transactionHash,
height: result.height,
gasUsed: result.gasUsed,
code: result.code
}
};
} catch (error) {
return {
content: [{
type: 'text',
text: `Transaction failed: ${error.message}`
}],
isError: true
};
}
}
private async getNetworkStatus() {
const status = await this.statesetClient.getNetworkStatus();
return {
contents: [{
uri: 'stateset://network/status',
mimeType: 'application/json',
text: JSON.stringify(status, null, 2)
}]
};
}
private async getAgentDirectory() {
const agents = await this.statesetClient.agents.list({ limit: 100 });
return {
contents: [{
uri: 'stateset://agents/directory',
mimeType: 'application/json',
text: JSON.stringify(agents, null, 2)
}]
};
}
private async getModuleInfo() {
const modules = {
orders: {
description: 'Order management module',
actions: ['create', 'fulfill', 'cancel', 'return']
},
payments: {
description: 'Payment processing module',
actions: ['create', 'process', 'refund']
},
inventory: {
description: 'Inventory management module',
actions: ['update', 'reserve', 'release']
},
messages: {
description: 'Message passing module',
actions: ['send', 'broadcast', 'query']
}
};
return {
contents: [{
uri: 'stateset://modules/info',
mimeType: 'application/json',
text: JSON.stringify(modules, null, 2)
}]
};
}
async start() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error('StateSet MCP Server started');
}
}
// Start the server
const config: StateSetMCPConfig = {
apiKey: process.env.STATESET_API_KEY!,
networkEndpoint: process.env.STATESET_NETWORK_ENDPOINT || 'https://rpc.stateset.zone',
chainId: process.env.STATESET_CHAIN_ID || 'stateset-1'
};
const server = new StateSetMCPServer(config);
server.start().catch(console.error);
// mcp-server/package.json
{
"name": "@stateset/mcp-server",
"version": "1.0.0",
"description": "MCP Server for StateSet Commerce Network",
"main": "dist/index.js",
"type": "module",
"scripts": {
"build": "tsc",
"start": "node dist/stateset-mcp-server.js",
"dev": "tsx src/stateset-mcp-server.ts"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^0.5.0",
"stateset-node": "^1.0.0",
"@cosmjs/stargate": "^0.32.0",
"@cosmjs/proto-signing": "^0.32.0"
},
"devDependencies": {
"@types/node": "^20.0.0",
"typescript": "^5.0.0",
"tsx": "^4.0.0"
}
}
// agents/mcp-client-integration.ts
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
import { Agent } from '../response-api-reference/framework';
interface MCPAgentConfig {
agentId: string;
mcpServerPath: string;
capabilities: string[];
}
export class MCPEnabledAgent extends Agent {
private mcpClient: Client;
private connected: boolean = false;
constructor(config: MCPAgentConfig) {
super(config);
this.mcpClient = new Client({
name: `agent-${config.agentId}`,
version: '1.0.0',
}, {
capabilities: {}
});
}
async connectToMCP() {
try {
const transport = new StdioClientTransport({
command: 'node',
args: [this.config.mcpServerPath]
});
await this.mcpClient.connect(transport);
this.connected = true;
// List available tools
const tools = await this.mcpClient.listTools();
console.log('Available MCP tools:', tools);
return true;
} catch (error) {
console.error('Failed to connect to MCP server:', error);
return false;
}
}
async sendNetworkMessage(
recipient: string,
message: string,
messageType: 'order' | 'payment' | 'inventory' | 'general',
metadata?: any
) {
if (!this.connected) {
throw new Error('MCP client not connected');
}
const result = await this.mcpClient.callTool('send_message', {
recipient,
message,
messageType,
metadata
});
return result;
}
async createOrder(orderData: {
customerId: string;
items: any[];
totalAmount: string;
metadata?: any;
}) {
if (!this.connected) {
throw new Error('MCP client not connected');
}
const result = await this.mcpClient.callTool('create_order', orderData);
return result;
}
async queryNetworkState(module: string, query?: any) {
if (!this.connected) {
throw new Error('MCP client not connected');
}
const result = await this.mcpClient.callTool('query_network_state', {
module,
query
});
return result;
}
async executeNetworkTransaction(
module: string,
action: string,
params: any,
signer?: string
) {
if (!this.connected) {
throw new Error('MCP client not connected');
}
const result = await this.mcpClient.callTool('execute_transaction', {
module,
action,
params,
signer
});
return result;
}
// Override the process method to include MCP capabilities
async process(input: string, context: any = {}) {
// Check if the input requires network interaction
if (this.requiresNetworkAction(input)) {
const action = await this.determineNetworkAction(input, context);
switch (action.type) {
case 'send_message':
return await this.sendNetworkMessage(
action.recipient,
action.message,
action.messageType,
action.metadata
);
case 'create_order':
return await this.createOrder(action.orderData);
case 'query_state':
return await this.queryNetworkState(action.module, action.query);
case 'execute_transaction':
return await this.executeNetworkTransaction(
action.module,
action.action,
action.params,
action.signer
);
}
}
// Fall back to standard agent processing
return await super.process(input, context);
}
private requiresNetworkAction(input: string): boolean {
const networkKeywords = [
'send', 'message', 'order', 'create', 'payment',
'inventory', 'network', 'blockchain', 'transaction'
];
return networkKeywords.some(keyword =>
input.toLowerCase().includes(keyword)
);
}
private async determineNetworkAction(input: string, context: any) {
// Use the agent's LLM to determine the appropriate network action
const prompt = `
Given the user input: "${input}"
And context: ${JSON.stringify(context)}
Determine if this requires a network action and return the appropriate action object.
Available actions:
1. send_message: { type: 'send_message', recipient, message, messageType, metadata }
2. create_order: { type: 'create_order', orderData: { customerId, items, totalAmount, metadata } }
3. query_state: { type: 'query_state', module, query }
4. execute_transaction: { type: 'execute_transaction', module, action, params, signer }
Return null if no network action is required.
`;
// This would use the agent's LLM to determine the action
// For now, we'll implement basic pattern matching
if (input.includes('send') && input.includes('message')) {
return {
type: 'send_message',
recipient: this.extractRecipient(input),
message: this.extractMessage(input),
messageType: 'general',
metadata: {}
};
}
if (input.includes('create') && input.includes('order')) {
return {
type: 'create_order',
orderData: this.extractOrderData(input, context)
};
}
// Add more pattern matching as needed
return null;
}
// Helper methods for extracting data from input
private extractRecipient(input: string): string {
// Implement logic to extract recipient from input
return 'default-recipient';
}
private extractMessage(input: string): string {
// Implement logic to extract message content
return input;
}
private extractOrderData(input: string, context: any): any {
// Implement logic to extract order data
return {
customerId: context.customerId || 'default-customer',
items: [],
totalAmount: '0',
metadata: {}
};
}
}
// proto/stateset/message/v1/message.proto
syntax = "proto3";
package stateset.message.v1;
import "google/protobuf/timestamp.proto";
import "cosmos/base/v1beta1/coin.proto";
// Message represents a message sent through the StateSet network
message Message {
string id = 1;
string sender = 2;
string recipient = 3;
string content = 4;
string message_type = 5;
string metadata = 6; // JSON string
google.protobuf.Timestamp created_at = 7;
MessageStatus status = 8;
}
enum MessageStatus {
MESSAGE_STATUS_UNSPECIFIED = 0;
MESSAGE_STATUS_PENDING = 1;
MESSAGE_STATUS_DELIVERED = 2;
MESSAGE_STATUS_READ = 3;
MESSAGE_STATUS_FAILED = 4;
}
// MsgSendMessage defines a message for sending a message through the network
message MsgSendMessage {
string sender = 1;
string recipient = 2;
string content = 3;
string message_type = 4;
string metadata = 5;
}
message MsgSendMessageResponse {
string message_id = 1;
}
// Query service for messages
service Query {
rpc Message(QueryMessageRequest) returns (QueryMessageResponse);
rpc Messages(QueryMessagesRequest) returns (QueryMessagesResponse);
rpc MessagesByRecipient(QueryMessagesByRecipientRequest) returns (QueryMessagesByRecipientResponse);
}
message QueryMessageRequest {
string message_id = 1;
}
message QueryMessageResponse {
Message message = 1;
}
message QueryMessagesRequest {
cosmos.base.query.v1beta1.PageRequest pagination = 1;
}
message QueryMessagesResponse {
repeated Message messages = 1;
cosmos.base.query.v1beta1.PageResponse pagination = 2;
}
message QueryMessagesByRecipientRequest {
string recipient = 1;
cosmos.base.query.v1beta1.PageRequest pagination = 2;
}
message QueryMessagesByRecipientResponse {
repeated Message messages = 1;
cosmos.base.query.v1beta1.PageResponse pagination = 2;
}
// Msg service for message transactions
service Msg {
rpc SendMessage(MsgSendMessage) returns (MsgSendMessageResponse);
}
// types/message-protocol.ts
export enum MessageType {
ORDER = 'order',
PAYMENT = 'payment',
INVENTORY = 'inventory',
GENERAL = 'general',
SYSTEM = 'system',
AGENT_HANDOFF = 'agent_handoff',
WORKFLOW_TRIGGER = 'workflow_trigger'
}
export interface BaseMessage {
id: string;
sender: string;
recipient: string;
messageType: MessageType;
timestamp: string;
metadata: Record<string, any>;
}
export interface OrderMessage extends BaseMessage {
messageType: MessageType.ORDER;
content: {
orderId: string;
action: 'create' | 'update' | 'cancel' | 'fulfill';
orderData?: any;
};
}
export interface PaymentMessage extends BaseMessage {
messageType: MessageType.PAYMENT;
content: {
paymentId: string;
amount: string;
currency: string;
status: 'pending' | 'completed' | 'failed';
};
}
export interface AgentHandoffMessage extends BaseMessage {
messageType: MessageType.AGENT_HANDOFF;
content: {
fromAgent: string;
toAgent: string;
context: any;
reason: string;
};
}
export interface WorkflowTriggerMessage extends BaseMessage {
messageType: MessageType.WORKFLOW_TRIGGER;
content: {
workflowId: string;
triggerType: string;
parameters: Record<string, any>;
};
}
// security/mcp-auth.ts
export class MCPAuthProvider {
private validatedAgents: Map<string, AgentCredentials> = new Map();
async authenticateAgent(agentId: string, signature: string): Promise<boolean> {
// Verify agent signature using public key
const agent = await this.getAgentCredentials(agentId);
if (!agent) return false;
return this.verifySignature(agentId, signature, agent.publicKey);
}
async authorizeAction(agentId: string, action: string, resource: string): Promise<boolean> {
const permissions = await this.getAgentPermissions(agentId);
return permissions.some(p =>
p.action === action &&
(p.resource === '*' || p.resource === resource)
);
}
private async getAgentCredentials(agentId: string): Promise<AgentCredentials | null> {
// Fetch from StateSet network or cache
return this.validatedAgents.get(agentId) || null;
}
private async getAgentPermissions(agentId: string): Promise<Permission[]> {
// Query permissions from the network
const agent = await this.statesetClient.agents.get(agentId);
return agent.permissions || [];
}
private verifySignature(agentId: string, signature: string, publicKey: string): boolean {
// Implement signature verification
return true; // Placeholder
}
}
interface AgentCredentials {
agentId: string;
publicKey: string;
permissions: Permission[];
}
interface Permission {
action: string;
resource: string;
}
// security/message-encryption.ts
export class MessageEncryption {
async encryptMessage(message: any, recipientPublicKey: string): Promise<string> {
// Implement end-to-end encryption for sensitive messages
// Using recipient's public key
return JSON.stringify(message); // Placeholder
}
async decryptMessage(encryptedMessage: string, privateKey: string): Promise<any> {
// Decrypt message using private key
return JSON.parse(encryptedMessage); // Placeholder
}
}
// tests/mcp-server.test.ts
import { describe, it, expect } from 'vitest';
import { StateSetMCPServer } from '../src/stateset-mcp-server';
describe('StateSet MCP Server', () => {
let server: StateSetMCPServer;
beforeEach(() => {
server = new StateSetMCPServer({
apiKey: 'test-key',
networkEndpoint: 'http://localhost:26657',
chainId: 'test-chain'
});
});
it('should list available tools', async () => {
const tools = await server.listTools();
expect(tools).toContain('send_message');
expect(tools).toContain('create_order');
expect(tools).toContain('query_network_state');
expect(tools).toContain('execute_transaction');
});
it('should send a message successfully', async () => {
const result = await server.handleSendMessage({
recipient: 'agent-123',
message: 'Test message',
messageType: 'general',
metadata: { test: true }
});
expect(result.isError).toBeFalsy();
expect(result.metadata.transactionHash).toBeDefined();
});
it('should handle network errors gracefully', async () => {
// Simulate network error
server.statesetClient = null;
const result = await server.handleSendMessage({
recipient: 'agent-123',
message: 'Test message',
messageType: 'general'
});
expect(result.isError).toBeTruthy();
expect(result.content[0].text).toContain('Failed to send message');
});
});
class MCPErrorHandler {
static handleError(error: any, context: string): MCPError {
if (error.code === 'NETWORK_ERROR') {
return new MCPError(
'Network communication failed',
'NETWORK_ERROR',
{ originalError: error, context }
);
}
if (error.code === 'UNAUTHORIZED') {
return new MCPError(
'Agent not authorized for this action',
'AUTH_ERROR',
{ originalError: error, context }
);
}
return new MCPError(
'An unexpected error occurred',
'UNKNOWN_ERROR',
{ originalError: error, context }
);
}
}
class MCPError extends Error {
constructor(
message: string,
public code: string,
public details: any
) {
super(message);
this.name = 'MCPError';
}
}
class MCPRateLimiter {
private requests: Map<string, number[]> = new Map();
constructor(
private maxRequests: number = 100,
private windowMs: number = 60000 // 1 minute
) {}
async checkLimit(agentId: string): Promise<boolean> {
const now = Date.now();
const requests = this.requests.get(agentId) || [];
// Remove old requests outside the window
const validRequests = requests.filter(time => now - time < this.windowMs);
if (validRequests.length >= this.maxRequests) {
return false;
}
validRequests.push(now);
this.requests.set(agentId, validRequests);
return true;
}
}
class MCPMonitor {
private metrics: Map<string, any> = new Map();
recordToolCall(toolName: string, agentId: string, duration: number, success: boolean) {
const key = `tool_call_${toolName}`;
const current = this.metrics.get(key) || {
total: 0,
success: 0,
failed: 0,
avgDuration: 0
};
current.total++;
if (success) current.success++;
else current.failed++;
current.avgDuration = (current.avgDuration * (current.total - 1) + duration) / current.total;
this.metrics.set(key, current);
// Send to monitoring service
this.sendToMonitoring({
metric: 'mcp.tool_call',
tags: { tool: toolName, agent: agentId, success: success.toString() },
value: duration
});
}
private sendToMonitoring(metric: any) {
// Implement sending to Prometheus, DataDog, etc.
console.log('Metric:', metric);
}
}