Quick Start

1. Initialize the Project

mkdir my-trading-skill
cd my-trading-skill
npm init -y

2. Install Dependencies

package.json:

{
  "name": "my-trading-skill",
  "version": "1.0.0",
  "type": "module",
  "main": "server.js",
  "scripts": {
    "start": "node server.js",
    "dev": "node --watch server.js"
  },
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1.0.0",
    "dotenv": "^16.6.1",
    "express": "^4.18.2"
  }
}

Install packages:

npm install

Core Implementation

Base Server Structure

import express from 'express';
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
import {
  CallToolRequestSchema,
  ListToolsRequestSchema
} from '@modelcontextprotocol/sdk/types.js';
import dotenv from 'dotenv';

dotenv.config();

const app = express();
app.use(express.json());

const PORT = process.env.PORT || 3015;
const TRADE_API_BASE = process.env.TRADE_API_BASE || 'https://test-dex-api.heima.network';

// Create MCP protocol handler
const mcpServer = new Server(
  {
    name: 'my-trading-skill',
    version: '1.0.0'
  },
  {
    capabilities: {
      tools: {}
    }
  }
);

Defining Trading Tools

The Skill exposes abilities to the AI Agent through Tools. Example: open position tool.

mcpServer.setRequestHandler(ListToolsRequestSchema, async () => {
  return {
    tools: [
      {
        name: 'open_position',
        description: 'Open a position on the DEX. Requires skill_token authorization.',
        inputSchema: {
          type: 'object',
          properties: {
            skill_token: { type: 'string', description: 'WildMeta authorization token' },
            dex: { type: 'string', description: 'DEX identifier (e.g. hyperliquid)' },
            coin: { type: 'string', description: 'Trading asset (e.g. BTC, ETH)' },
            isBuy: { type: 'boolean', description: 'true=long, false=short' },
            limitPx: { type: 'string', description: 'Limit order price' },
            sz: { type: 'string', description: 'Position size' },
            orderType: { type: 'object', description: 'Order type configuration' }
          },
          required: ['skill_token', 'dex', 'coin', 'isBuy', 'sz']
        }
      }
    ]
  };
});

Tool Handler Implementation

mcpServer.setRequestHandler(CallToolRequestSchema, async (request) => {
  const { name, arguments: args } = request.params;

  if (name === 'open_position') {
    return await handleOpenPosition(args);
  }

  throw new Error(`Unknown tool: ${name}`);
});

Trading Operations Implementation

Open Position

async function handleOpenPosition(args) {
  const { skill_token, dex, coin, isBuy, limitPx, sz, orderType } = args;

  if (!skill_token) {
    return {
      content: [{
        type: 'text',
        text: JSON.stringify({
          success: false,
          error: 'skill_token is required for authorization'
        })
      }]
    };
  }

  console.log(`📈 Open position: ${isBuy ? 'LONG' : 'SHORT'} ${sz} ${coin}`);

  try {
    const requestBody = {
      dex: dex || 'hyperliquid',
      coin,
      isBuy,
      limitPx: limitPx || '0',
      sz,
      orderType: orderType || {
        limit: { tif: 'Gtc' },
        trigger: { isMarket: false, triggerPx: '0', tpsl: '' }
      },
      cloid: `order_${Date.now()}`
    };

    const response = await fetch(`${TRADE_API_BASE}/openapi/trade/v1/open_position`, {
      method: 'POST',
      headers: {
        'X-Language': 'zh',
        'skill-token': skill_token,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(requestBody)
    });

    const data = await response.json();

    return {
      content: [{
        type: 'text',
        text: JSON.stringify({
          success: response.ok,
          order: requestBody,
          response: data
        })
      }]
    };
  } catch (error) {
    return {
      content: [{
        type: 'text',
        text: JSON.stringify({
          success: false,
          error: error.message
        })
      }]
    };
  }
}

HTTP Endpoints

The Skill must expose an HTTP endpoint for MCP communication:

// MCP endpoint
app.all('/message', async (req, res) => {
  try {
    const transport = new StreamableHTTPServerTransport({
      sessionIdGenerator: undefined,
      enableJsonResponse: true
    });

    res.on('close', () => transport.close());

    await mcpServer.connect(transport);
    await transport.handleRequest(req, res, req.body);
  } catch (error) {
    console.error('Request error:', error);
    res.status(500).json({ error: error.message });
  }
});

// Health check endpoint
app.get('/health', (req, res) => {
  res.json({
    status: 'healthy',
    server: 'my-trading-skill',
    version: '1.0.0',
    timestamp: new Date().toISOString()
  });
});

app.listen(PORT, () => {
  console.log(`🚀 Skill service running on port ${PORT}`);
});

Authorization Mechanism

Skill Token Workflow

1. User initiates a request
2. Frontend detects that a Skill requires signature authorization
3. Frontend requests wallet signature from the user
4. AI Agent automatically injects skill_token into tool parameters
5. Skill uses the token to call the WildMeta DEX API

Notes

  • skill_token is generated by WildMeta frontend via wallet signature

  • AI Agent automatically injects the token into tool calls

  • Skills must include this token when calling WildMeta DEX API


Security Best Practices

1. Token Validation

if (!skill_token) {
  return {
    content: [{
      type: 'text',
      text: JSON.stringify({
        success: false,
        error: 'skill_token is required'
      })
    }]
  };
}

2. Risk Controls

const MAX_SIZE = {
  BTC: 0.01,
  ETH: 0.1
};

if (parseFloat(sz) > MAX_SIZE[coin]) {
  return {
    success: false,
    error: `Position size exceeds max limit for ${coin}: ${MAX_SIZE[coin]}`
  };
}

3. Logging

console.log(`📈 Open position: ${isBuy ? 'LONG' : 'SHORT'} ${sz} ${coin}`);
console.log(`🔑 Token: ${skill_token.substring(0, 20)}...`);
console.log('📤 Request body:', JSON.stringify(requestBody, null, 2));
console.log('📥 Response:', data);

4. Error Handling

try {
  // trading logic
} catch (error) {
  console.error('❌ Trading error:', error);
  return {
    content: [{
      type: 'text',
      text: JSON.stringify({
        success: false,
        error: error.message,
        timestamp: new Date().toISOString()
      })
    }]
  };
}

Testing

Create a script: test.sh

#!/bin/bash

echo "=== Health Check ==="
curl -s http://localhost:3015/health | jq .

echo -e "\n=== List Tools ==="
curl -s -X POST http://localhost:3015/message \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/list"
  }' | jq .

echo -e "\n=== Test Open Position (requires valid skill_token) ==="
curl -s -X POST http://localhost:3015/message \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 2,
    "method": "tools/call",
    "params": {
      "name": "open_position",
      "arguments": {
        "skill_token": "test-token",
        "dex": "hyperliquid",
        "coin": "BTC",
        "isBuy": true,
        "sz": "0.001"
      }
    }
  }' | jq .

Full Example

For a complete example, refer to:

mcp-wildmeat-trader/server.js

It includes:

  • Full Skill server setup

  • Multiple tools (open, close, cancel order)

  • WildMeta DEX API integration

  • Error handling

  • Logging

  • Security checks

Last updated