Skip to content
Merged
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
2 changes: 1 addition & 1 deletion agent/the-graph-agent-scaffold-eth/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ yarn install
3. Set up environment variables in `.env.local`:

```bash
# The Graph Protocol API Key
# The Graph Protocol API Key (used for both regular GraphQL and MCP functionality)
GRAPH_API_KEY=your-graph-api-key-here

# OpenAI API Key
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,12 @@ export async function POST(req: Request) {
You have access to several tools:
1. The chat app has a built-in block explorer so you can link to (for example) /blockexplorer/transaction/<transaction-hash>
2. You can query The Graph protocol subgraphs using the querySubgraph action
3. You can check balances using the contract interactor:
3. You can use The Graph's MCP (Model Context Protocol) for advanced subgraph discovery and querying:
- searchSubgraphs: Find relevant subgraphs by keyword
- getContractSubgraphs: Find subgraphs that index a specific contract
- getSubgraphSchema: Get the schema for a subgraph to understand available data
- executeMCPQuery: Execute GraphQL queries through MCP
4. You can check balances using the contract interactor:
- For native token balance: Use the "getBalance" action with the user's address
- For ERC20 token balances: Use the "getBalance" action with the token contract address and user's address

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"use client";

import { useEffect, useState } from "react";

interface NoSSRProps {
children: React.ReactNode;
fallback?: React.ReactNode;
}

/**
* NoSSR component that only renders children on the client side
* Prevents hydration mismatches for components that use browser-only APIs
*/
export const NoSSR = ({ children, fallback = null }: NoSSRProps) => {
const [mounted, setMounted] = useState(false);

useEffect(() => {
setMounted(true);
}, []);

if (!mounted) {
return <>{fallback}</>;
}

return <>{children}</>;
};
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { Toaster } from "react-hot-toast";
import { WagmiProvider } from "wagmi";
import { Footer } from "~~/components/Footer";
import { Header } from "~~/components/Header";
import { NoSSR } from "~~/components/NoSSR";
import { BlockieAvatar } from "~~/components/scaffold-eth";
import { RainbowKitSiweNextAuthProviderWithSession } from "~~/components/scaffold-eth/RainbowKitSiweNextAuthProviderWithSession";
import { useInitializeNativeCurrencyPrice } from "~~/hooks/scaffold-eth";
Expand Down Expand Up @@ -47,18 +48,20 @@ export const ScaffoldEthAppWithProviders = ({ children }: { children: React.Reac
}, []);

return (
<WagmiProvider config={wagmiConfig}>
<QueryClientProvider client={queryClient}>
<RainbowKitSiweNextAuthProviderWithSession>
<ProgressBar height="3px" color="#2299dd" />
<RainbowKitProvider
avatar={BlockieAvatar}
theme={mounted ? (isDarkMode ? darkTheme() : lightTheme()) : lightTheme()}
>
<ScaffoldEthApp>{children}</ScaffoldEthApp>
</RainbowKitProvider>
</RainbowKitSiweNextAuthProviderWithSession>
</QueryClientProvider>
</WagmiProvider>
<NoSSR fallback={<div>Loading...</div>}>
<WagmiProvider config={wagmiConfig}>
<QueryClientProvider client={queryClient}>
<RainbowKitSiweNextAuthProviderWithSession>
<ProgressBar height="3px" color="#2299dd" />
<RainbowKitProvider
avatar={BlockieAvatar}
theme={mounted ? (isDarkMode ? darkTheme() : lightTheme()) : lightTheme()}
>
<ScaffoldEthApp>{children}</ScaffoldEthApp>
</RainbowKitProvider>
</RainbowKitSiweNextAuthProviderWithSession>
</QueryClientProvider>
</WagmiProvider>
</NoSSR>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# The Graph MCP Usage Examples

This document provides examples of how to interact with The Graph's Model Context Protocol (MCP) through the chat interface.

## MCP Actions Available

### 1. Search Subgraphs

Find relevant subgraphs by keyword

**Example queries:**

- "Find subgraphs related to Uniswap"
- "Search for DeFi lending subgraphs"
- "Show me NFT marketplace subgraphs"

### 2. Get Contract Subgraphs

Find subgraphs that index a specific contract address

**Example queries:**

- "Find subgraphs for contract 0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984 on mainnet" (UNI token)
- "What subgraphs index the USDC contract on Ethereum?"
- "Show me subgraphs for Compound's cUSDC contract"

### 3. Get Subgraph Schema

Understand what data is available in a subgraph

**Example queries:**

- "Show me the schema for subgraph 5zvR82QoaXYFyDEKLZ9t6v9adgnptxYpKpSbxtgVENFV"
- "What entities are available in the Uniswap V3 subgraph?"
- "Get the schema for the Aave subgraph"

### 4. Execute MCP Query

Run GraphQL queries through MCP

**Example queries:**

- "Query the top 10 Uniswap pools by volume using MCP"
- "Get recent swaps from Uniswap V3 subgraph via MCP"
- "Show me liquidations from Aave using MCP query"

## Sample Chat Interactions

### Discovery Workflow

```
User: "I want to analyze USDC liquidity across different protocols"
```
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,13 @@ import scaffoldConfig from "~~/scaffold.config";

const { onlyLocalBurnerWallet, targetNetworks } = scaffoldConfig;

// Check if we're on the server side
const isServer = typeof window === "undefined";

const wallets = [
metaMaskWallet,
walletConnectWallet,
// Only include WalletConnect on client side to prevent SSR issues
...(!isServer ? [walletConnectWallet] : []),
ledgerWallet,
coinbaseWallet,
rainbowWallet,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
import { ActionProvider, WalletProvider } from "@coinbase/agentkit";
import { z } from "zod";

// MCP Client for The Graph
class GraphMCPClient {
private baseUrl: string;
private headers: Record<string, string>;

constructor(graphApiKey: string) {
this.baseUrl = "https://subgraphs.mcp.thegraph.com";
this.headers = {
Authorization: `Bearer ${graphApiKey}`,
"Content-Type": "application/json",
};
}

async searchSubgraphs(keyword: string) {
const response = await fetch(`${this.baseUrl}/search`, {
method: "POST",
headers: this.headers,
body: JSON.stringify({
method: "search_subgraphs_by_keyword",
params: { keyword },
}),
});

if (!response.ok) {
throw new Error(`MCP search failed: ${response.status}`);
}

return response.json();
}

async getTopSubgraphsForContract(contractAddress: string, chain: string) {
const response = await fetch(`${this.baseUrl}/contract-subgraphs`, {
method: "POST",
headers: this.headers,
body: JSON.stringify({
method: "get_top_subgraph_deployments",
params: { contract_address: contractAddress, chain },
}),
});

if (!response.ok) {
throw new Error(`MCP contract subgraph lookup failed: ${response.status}`);
}

return response.json();
}

async getSubgraphSchema(subgraphId: string) {
const response = await fetch(`${this.baseUrl}/schema`, {
method: "POST",
headers: this.headers,
body: JSON.stringify({
method: "get_schema_by_subgraph_id",
params: { subgraph_id: subgraphId },
}),
});

if (!response.ok) {
throw new Error(`MCP schema fetch failed: ${response.status}`);
}

return response.json();
}

async executeQuery(subgraphId: string, query: string, variables?: Record<string, any>) {
const response = await fetch(`${this.baseUrl}/query`, {
method: "POST",
headers: this.headers,
body: JSON.stringify({
method: "execute_query_by_subgraph_id",
params: { subgraph_id: subgraphId, query, variables },
}),
});

if (!response.ok) {
throw new Error(`MCP query execution failed: ${response.status}`);
}

return response.json();
}
}

// Schema definitions
const searchSubgraphsSchema = z.object({
keyword: z.string().describe("Keyword to search for in subgraph names and descriptions"),
});

const getContractSubgraphsSchema = z.object({
contractAddress: z.string().describe("The contract address to find subgraphs for"),
chain: z.string().describe("The blockchain network (e.g., 'mainnet', 'polygon', 'arbitrum-one')"),
});

const getSchemaSchema = z.object({
subgraphId: z.string().describe("The subgraph ID to get the schema for"),
});

const executeMCPQuerySchema = z.object({
subgraphId: z.string().describe("The subgraph ID to query"),
query: z.string().describe("The GraphQL query string"),
variables: z.record(z.any()).optional().describe("Optional variables for the GraphQL query"),
});

export class GraphMCPProvider implements ActionProvider<WalletProvider> {
name = "graph-mcp";
actionProviders = [];
supportsNetwork = () => true;

private mcpClient: GraphMCPClient;

constructor() {
const graphApiKey = process.env.GRAPH_API_KEY;
if (!graphApiKey) {
throw new Error("GRAPH_API_KEY not found in environment variables");
}
this.mcpClient = new GraphMCPClient(graphApiKey);
}

getActions(walletProvider: WalletProvider) {
return [
{
name: "searchSubgraphs",
description: "Search for subgraphs by keyword using The Graph's MCP. Returns relevant subgraphs with metadata.",
schema: searchSubgraphsSchema,
severity: "info" as const,
invoke: async ({ keyword }: z.infer<typeof searchSubgraphsSchema>) => {
try {
const result = await this.mcpClient.searchSubgraphs(keyword);
return JSON.stringify(result, null, 2);
} catch (error) {
return JSON.stringify({
error: error instanceof Error ? error.message : "Failed to search subgraphs",
});
}
},
},
{
name: "getContractSubgraphs",
description: "Find the top subgraphs that index a specific contract address on a given blockchain.",
schema: getContractSubgraphsSchema,
severity: "info" as const,
invoke: async ({ contractAddress, chain }: z.infer<typeof getContractSubgraphsSchema>) => {
try {
const result = await this.mcpClient.getTopSubgraphsForContract(contractAddress, chain);
return JSON.stringify(result, null, 2);
} catch (error) {
return JSON.stringify({
error: error instanceof Error ? error.message : "Failed to get contract subgraphs",
});
}
},
},
{
name: "getSubgraphSchema",
description: "Get the GraphQL schema for a specific subgraph, showing available entities and fields.",
schema: getSchemaSchema,
severity: "info" as const,
invoke: async ({ subgraphId }: z.infer<typeof getSchemaSchema>) => {
try {
const result = await this.mcpClient.getSubgraphSchema(subgraphId);
return JSON.stringify(result, null, 2);
} catch (error) {
return JSON.stringify({
error: error instanceof Error ? error.message : "Failed to get subgraph schema",
});
}
},
},
{
name: "executeMCPQuery",
description: "Execute a GraphQL query against a subgraph using The Graph's MCP protocol.",
schema: executeMCPQuerySchema,
severity: "info" as const,
invoke: async ({ subgraphId, query, variables = {} }: z.infer<typeof executeMCPQuerySchema>) => {
try {
const result = await this.mcpClient.executeQuery(subgraphId, query, variables);
return JSON.stringify(result, null, 2);
} catch (error) {
return JSON.stringify({
error: error instanceof Error ? error.message : "Failed to execute MCP query",
});
}
},
},
];
}
}

export const graphMCPProvider = () => new GraphMCPProvider();
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { contractInteractor } from "./agentkit/action-providers/contract-interactor";
import { graphMCPProvider } from "./agentkit/action-providers/graph-mcp-provider";
import { SUBGRAPH_ENDPOINTS, graphQuerierProvider } from "./agentkit/action-providers/graph-querier";
import { tokenApiProvider } from "./agentkit/action-providers/token-api-provider";
import { agentKitToTools } from "./agentkit/framework-extensions/ai-sdk";
Expand Down Expand Up @@ -26,6 +27,7 @@ export async function createAgentKit() {
walletActionProvider(),
contractInteractor(foundry.id),
graphQuerierProvider(),
graphMCPProvider(),
tokenApiProvider(),
],
});
Expand Down