Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 8 additions & 12 deletions packages/examples/src/tokenUnlocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -248,33 +248,29 @@ async function getUnlockEvents(assetId: string) {

async function main() {
try {
const solanaId = "b3d5d66c-26a2-404c-9325-91dc714a722b";

// 1. Get all supported token unlock assets
const supportedAssets = await getSupportedAssets();
console.log("\n");

// 2. Get allocations for first two supported assets
if (supportedAssets.length >= 2) {
const assetIDs = `${supportedAssets[0].id},${supportedAssets[1].id}`;
const assetIDs = `${solanaId},${supportedAssets[0].id},${supportedAssets[1].id}`;
await getAssetAllocations(assetIDs);
console.log("\n");
}

// 3. Get vesting schedule for the first supported asset
if (supportedAssets.length > 0 && supportedAssets[0].id) {
await getVestingSchedule(supportedAssets[0].id);
console.log("\n");
}
await getVestingSchedule(solanaId);
console.log("\n");

// 4. Get token unlocks for the first supported asset
if (supportedAssets.length > 0 && supportedAssets[0].id) {
await getTokenUnlocks(supportedAssets[0].id);
console.log("\n");
}
await getTokenUnlocks(solanaId);
console.log("\n");

// 5. Get unlock events for the first supported asset
if (supportedAssets.length > 0 && supportedAssets[0].id) {
await getUnlockEvents(supportedAssets[0].id);
}
await getUnlockEvents(solanaId);
} catch (error) {
console.error("An error occurred:", error);
}
Expand Down
21 changes: 21 additions & 0 deletions packages/mcp/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
FROM node:22.12-alpine AS builder

COPY . /app

WORKDIR /app

RUN --mount=type=cache,target=/root/.npm npm install

FROM node:22-alpine AS release

WORKDIR /app

COPY --from=builder /app/dist /app/dist
COPY --from=builder /app/package.json /app/package.json
COPY --from=builder /app/package-lock.json /app/package-lock.json

ENV NODE_ENV=production

RUN npm ci --ignore-scripts --omit-dev

ENTRYPOINT ["node", "dist/index.js"]
121 changes: 121 additions & 0 deletions packages/mcp/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
# Messari Ask MCP Server

An MCP server implementation that integrates the Messari API to provide Claude with unparalleled real-time, crypto research.

TODO: ADD SCREENSHOT
TODO: Finish the integration and prove it works in Claude desktop or console.claude.ai
- Have it working in local mcp inspector (times out sometimes...)
- Command to run in inspector: `npx @modelcontextprotocol/inspector -e MESSARI_API_KEY=<YOUR_API_KEY_HERE> node dist/index.j`


## Tools

- **messari-copilot**
- Engage in a conversation with the Messari Copilot API for crypto research insights.
- **Inputs:**
- `messages` (array): An array of conversation messages.
- Each message must include:
- `role` (string): The role of the message (e.g., `system`, `user`, `assistant`).
- `content` (string): The content of the message.

## Configuration

### Step 1:

Clone the MCP repository:

```bash
git clone https://github.com/messari/MessariKit.git
```

Navigate to the `mcp` directory and install the necessary dependencies:

```bash
cd packages/mcp && pnpm install
```

To use the Messari Action Provider, you need to obtain a Messari API key by following these steps:

1. Sign up for a Messari account at [messari.io](https://messari.io/)
2. After signing up, navigate to [messari.io/account/api](https://messari.io/account/api)
3. Generate your API key from the account dashboard

For more detailed information about authentication, refer to the [Messari API Authentication documentation](https://docs.messari.io/reference/authentication).

Different subscription tiers provide different levels of access to the API. See the [Rate Limiting](#rate-limiting) section for details.

### Step 3: Configure Claude Desktop

1. Download Claude desktop [here](https://claude.ai/download).

2. Add this to your `claude_desktop_config.json`:

```json
{
"mcpServers": {
"messari-copilot": {
"command": "npx",
"args": [
"-y",
"@messari/modelcontextprovider"
],
"env": {
"MESSARI_API_KEY": "YOUR_API_KEY_HERE"
}
}
}
}
```

You can access the file using:

```bash
vim ~/Library/Application\ Support/Claude/claude_desktop_config.json
```

### Step 4: Build the Docker Image

Docker build:

```bash
docker build -t mcp/messari-copilot:latest -f src/messari-copilot/Dockerfile .
```

### Step 5: Testing

Let’s make sure Claude for Desktop is picking up the two tools we’ve exposed in our `messari-copilot` server. You can do this by looking for the hammer icon:

![Claude Visual Tools](perplexity-ask/assets/visual-indicator-mcp-tools.png)

After clicking on the hammer icon, you should see the tools that come with the Filesystem MCP Server:

![Available Integration](perplexity-ask/assets/available_tools.png)

If you see both of these this means that the integration is active. Congratulations! This means Claude can now ask Messari. You can then simply use it as you would use the Messari copilot via the web app.

### Step 6: Advanced parameters

Currently, the search parameters used are the default ones. You can modify any search parameter in the API call directly in the `index.ts` script. For this, please refer to the official [API documentation](https://docs.messari.io/reference/chat-completion).

### Rate Limiting

The Messari API has rate limits based on your subscription tier:

| Subscription Tier | Daily Request Limit |
|-------------------|---------------------|
| Free (Unpaid) | 2 requests per day |
| Lite | 10 requests per day |
| Pro | 20 requests per day |
| Enterprise | 50 requests per day |

If you need more than 50 requests per day, you can contact Messari's sales team to discuss a custom credit allocation system for your specific needs.

### Troubleshooting

The Claude documentation provides an excellent [troubleshooting guide](https://modelcontextprotocol.io/docs/tools/debugging) you can refer to. However, you can still reach out to us at [email protected] for any additional support or [file a bug](https://github.com/messari/MessariKit/issues).


## License

This MCP server is licensed under the MIT License. This means you are free to use, modify, and distribute the software, subject to the terms and conditions of the MIT License. For more details, please see the LICENSE file in the project repository.

59 changes: 59 additions & 0 deletions packages/mcp/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
{
"name": "@messari/modelcontextprotocol",
"version": "0.1.0",
"private": false,
"description": "Messari MCP provides a type-safe, intuitive interface for accessing Messari's MCP API.",
"author": "Messari Engineering",
"keywords": [
"messari",
"crypto",
"mcp",
"claude",
"anthropic",
"agent",
"ai",
"agent",
"bitcoin",
"ethereum"
],
"main": "dist/index.js",
"types": "dist/index.d.ts",
"exports": {
".": {
"import": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
},
"require": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
}
},
"files": ["dist", "README.md"],
"scripts": {
"build": "tsc",
"clean": "rimraf dist",
"test:run": "vitest run",
"test:watch": "vitest",
"dev": "tsx src/index.ts"
},
"dependencies": {
"@messari/sdk": "workspace:*",
"@modelcontextprotocol/sdk": "^1.7.0",
"dotenv": "^16.4.7"
},
"devDependencies": {
"@types/node": "^22.13.5",
"rimraf": "^5.0.10",
"tsx": "^4.19.3",
"typescript": "^5.7.3"
},
"license": "MIT",
"pnpm": {
"overrides": {
"prismjs@<1.30.0": ">=1.30.0",
"@babel/runtime@<7.26.10": ">=7.26.10"
}
}
}
113 changes: 113 additions & 0 deletions packages/mcp/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
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 { MessariClient } from "@messari/sdk";
import dotenv from "dotenv";

import { copilotTool } from "./tools";

// Load environment variables from .env file
dotenv.config();

// Get API key from environment variable
const API_KEY = process.env.MESSARI_API_KEY;

if (!API_KEY) {
// Check if API key is available
console.error("Error: MESSARI_API_KEY environment variable is not set.");
console.error("Please create a .env file with your API key or set it in your environment.");
process.exit(1);
}

const main = async () => {
try {
// Create an MCP server
const server = new Server(
{
name: "Messari AI ToolKit MCP Server",
version: "0.1.0",
},
{
capabilities: {
tools: {
listChanged: false, // Tool list is static
},
},
instructions: "You are a helpful assistant that can answer questions related to crypto research.",
},
);

// Initialize the Messari client
const messariClient = new MessariClient({
apiKey: API_KEY,
});

/**
* Registers a handler for listing available tools.
* When the client requests a list of tools, this handler returns the Perplexity Ask Tool.
*/
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [copilotTool],
}));

/**
* Registers a handler for calling a specific tool.
* Processes requests by validating input and invoking the appropriate tool.
*
* @param {object} request - The incoming tool call request.
* @returns {Promise<object>} The response containing the tool's result or an error.
*/
server.setRequestHandler(CallToolRequestSchema, async (request) => {
try {
const { name, arguments: args } = request.params;
if (!args) {
throw new Error("No arguments provided");
}

switch (name) {
case copilotTool.name: {
if (!Array.isArray(args.messages)) {
throw new Error("Invalid arguments for messari-copilot: 'messages' must be an array");
}
const response = await messariClient.ai.createChatCompletion({
messages: args.messages,
});
return {
content: [{ type: "text", text: response.messages[0].content }],
isError: false,
};
}
default:
// Respond with an error if an unknown tool is requested
return {
content: [{ type: "text", text: `Unknown tool: ${name}` }],
isError: true,
};
}
} catch (error) {
// Return error details in the response
return {
content: [
{
type: "text",
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
},
],
isError: true,
};
}
});

const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Messari MCP Server running on stdio");
} catch (error) {
console.error("Fatal error running Messari MCP Server:", error);
process.exit(1);
}
};

main().catch((error) => {
console.error("Messari MCP Server error:", error);
process.exit(1);
});
38 changes: 38 additions & 0 deletions packages/mcp/src/tools.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import type { Tool } from "@modelcontextprotocol/sdk/types.js";

export const copilotTool: Tool = {
name: "messari-copilot",
description: `This tool queries Messari AI for comprehensive crypto research across these datasets:
1. News/Content - Latest crypto news, blogs, podcasts
2. Exchanges - CEX/DEX volumes, market share, assets listed
3. Onchain Data - Active addresses, transaction fees, total transactions.
4. Token Unlocks - Upcoming supply unlocks, vesting schedules, and token emission details
5. Market Data - Asset prices, trading volume, market cap, TVL, and historical performance
6. Fundraising - Investment data, funding rounds, venture capital activity.
7. Protocol Research - Technical analysis of how protocols work, tokenomics, and yield mechanisms
8. Social Data - Twitter followers and Reddit subscribers metrics, growth trends

Examples:
- "Which DEXs have the highest trading volume this month?"
- "When is Arbitrum's next major token unlock?"
- "How does Morpho generate yield for users?"
- "Which cryptocurrency has gained the most Twitter followers in 2023?"
- "What did Vitalik Buterin say about rollups in his recent blog posts?"`,
inputSchema: {
type: "object",
properties: {
messages: {
type: "array",
items: {
type: "object",
properties: {
role: { type: "string" },
content: { type: "string" },
},
required: ["role", "content"],
},
},
},
required: ["messages"],
},
};
Loading
Loading