Skip to content

Commit 4b34c14

Browse files
authored
Merge pull request #7 from kmjones1979/main
add subgraph mcp action provider and fix some ssr issues
2 parents 7222c8c + 12b5057 commit 4b34c14

File tree

8 files changed

+300
-16
lines changed

8 files changed

+300
-16
lines changed

agent/the-graph-agent-scaffold-eth/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ yarn install
7878
3. Set up environment variables in `.env.local`:
7979

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

8484
# OpenAI API Key

agent/the-graph-agent-scaffold-eth/packages/nextjs/app/api/chat/route.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,12 @@ export async function POST(req: Request) {
3939
You have access to several tools:
4040
1. The chat app has a built-in block explorer so you can link to (for example) /blockexplorer/transaction/<transaction-hash>
4141
2. You can query The Graph protocol subgraphs using the querySubgraph action
42-
3. You can check balances using the contract interactor:
42+
3. You can use The Graph's MCP (Model Context Protocol) for advanced subgraph discovery and querying:
43+
- searchSubgraphs: Find relevant subgraphs by keyword
44+
- getContractSubgraphs: Find subgraphs that index a specific contract
45+
- getSubgraphSchema: Get the schema for a subgraph to understand available data
46+
- executeMCPQuery: Execute GraphQL queries through MCP
47+
4. You can check balances using the contract interactor:
4348
- For native token balance: Use the "getBalance" action with the user's address
4449
- For ERC20 token balances: Use the "getBalance" action with the token contract address and user's address
4550
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
"use client";
2+
3+
import { useEffect, useState } from "react";
4+
5+
interface NoSSRProps {
6+
children: React.ReactNode;
7+
fallback?: React.ReactNode;
8+
}
9+
10+
/**
11+
* NoSSR component that only renders children on the client side
12+
* Prevents hydration mismatches for components that use browser-only APIs
13+
*/
14+
export const NoSSR = ({ children, fallback = null }: NoSSRProps) => {
15+
const [mounted, setMounted] = useState(false);
16+
17+
useEffect(() => {
18+
setMounted(true);
19+
}, []);
20+
21+
if (!mounted) {
22+
return <>{fallback}</>;
23+
}
24+
25+
return <>{children}</>;
26+
};

agent/the-graph-agent-scaffold-eth/packages/nextjs/components/ScaffoldEthAppWithProviders.tsx

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { Toaster } from "react-hot-toast";
99
import { WagmiProvider } from "wagmi";
1010
import { Footer } from "~~/components/Footer";
1111
import { Header } from "~~/components/Header";
12+
import { NoSSR } from "~~/components/NoSSR";
1213
import { BlockieAvatar } from "~~/components/scaffold-eth";
1314
import { RainbowKitSiweNextAuthProviderWithSession } from "~~/components/scaffold-eth/RainbowKitSiweNextAuthProviderWithSession";
1415
import { useInitializeNativeCurrencyPrice } from "~~/hooks/scaffold-eth";
@@ -47,18 +48,20 @@ export const ScaffoldEthAppWithProviders = ({ children }: { children: React.Reac
4748
}, []);
4849

4950
return (
50-
<WagmiProvider config={wagmiConfig}>
51-
<QueryClientProvider client={queryClient}>
52-
<RainbowKitSiweNextAuthProviderWithSession>
53-
<ProgressBar height="3px" color="#2299dd" />
54-
<RainbowKitProvider
55-
avatar={BlockieAvatar}
56-
theme={mounted ? (isDarkMode ? darkTheme() : lightTheme()) : lightTheme()}
57-
>
58-
<ScaffoldEthApp>{children}</ScaffoldEthApp>
59-
</RainbowKitProvider>
60-
</RainbowKitSiweNextAuthProviderWithSession>
61-
</QueryClientProvider>
62-
</WagmiProvider>
51+
<NoSSR fallback={<div>Loading...</div>}>
52+
<WagmiProvider config={wagmiConfig}>
53+
<QueryClientProvider client={queryClient}>
54+
<RainbowKitSiweNextAuthProviderWithSession>
55+
<ProgressBar height="3px" color="#2299dd" />
56+
<RainbowKitProvider
57+
avatar={BlockieAvatar}
58+
theme={mounted ? (isDarkMode ? darkTheme() : lightTheme()) : lightTheme()}
59+
>
60+
<ScaffoldEthApp>{children}</ScaffoldEthApp>
61+
</RainbowKitProvider>
62+
</RainbowKitSiweNextAuthProviderWithSession>
63+
</QueryClientProvider>
64+
</WagmiProvider>
65+
</NoSSR>
6366
);
6467
};
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# The Graph MCP Usage Examples
2+
3+
This document provides examples of how to interact with The Graph's Model Context Protocol (MCP) through the chat interface.
4+
5+
## MCP Actions Available
6+
7+
### 1. Search Subgraphs
8+
9+
Find relevant subgraphs by keyword
10+
11+
**Example queries:**
12+
13+
- "Find subgraphs related to Uniswap"
14+
- "Search for DeFi lending subgraphs"
15+
- "Show me NFT marketplace subgraphs"
16+
17+
### 2. Get Contract Subgraphs
18+
19+
Find subgraphs that index a specific contract address
20+
21+
**Example queries:**
22+
23+
- "Find subgraphs for contract 0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984 on mainnet" (UNI token)
24+
- "What subgraphs index the USDC contract on Ethereum?"
25+
- "Show me subgraphs for Compound's cUSDC contract"
26+
27+
### 3. Get Subgraph Schema
28+
29+
Understand what data is available in a subgraph
30+
31+
**Example queries:**
32+
33+
- "Show me the schema for subgraph 5zvR82QoaXYFyDEKLZ9t6v9adgnptxYpKpSbxtgVENFV"
34+
- "What entities are available in the Uniswap V3 subgraph?"
35+
- "Get the schema for the Aave subgraph"
36+
37+
### 4. Execute MCP Query
38+
39+
Run GraphQL queries through MCP
40+
41+
**Example queries:**
42+
43+
- "Query the top 10 Uniswap pools by volume using MCP"
44+
- "Get recent swaps from Uniswap V3 subgraph via MCP"
45+
- "Show me liquidations from Aave using MCP query"
46+
47+
## Sample Chat Interactions
48+
49+
### Discovery Workflow
50+
51+
```
52+
User: "I want to analyze USDC liquidity across different protocols"
53+
```

agent/the-graph-agent-scaffold-eth/packages/nextjs/services/web3/wagmiConnectors.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,13 @@ import scaffoldConfig from "~~/scaffold.config";
1313

1414
const { onlyLocalBurnerWallet, targetNetworks } = scaffoldConfig;
1515

16+
// Check if we're on the server side
17+
const isServer = typeof window === "undefined";
18+
1619
const wallets = [
1720
metaMaskWallet,
18-
walletConnectWallet,
21+
// Only include WalletConnect on client side to prevent SSR issues
22+
...(!isServer ? [walletConnectWallet] : []),
1923
ledgerWallet,
2024
coinbaseWallet,
2125
rainbowWallet,
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
import { ActionProvider, WalletProvider } from "@coinbase/agentkit";
2+
import { z } from "zod";
3+
4+
// MCP Client for The Graph
5+
class GraphMCPClient {
6+
private baseUrl: string;
7+
private headers: Record<string, string>;
8+
9+
constructor(graphApiKey: string) {
10+
this.baseUrl = "https://subgraphs.mcp.thegraph.com";
11+
this.headers = {
12+
Authorization: `Bearer ${graphApiKey}`,
13+
"Content-Type": "application/json",
14+
};
15+
}
16+
17+
async searchSubgraphs(keyword: string) {
18+
const response = await fetch(`${this.baseUrl}/search`, {
19+
method: "POST",
20+
headers: this.headers,
21+
body: JSON.stringify({
22+
method: "search_subgraphs_by_keyword",
23+
params: { keyword },
24+
}),
25+
});
26+
27+
if (!response.ok) {
28+
throw new Error(`MCP search failed: ${response.status}`);
29+
}
30+
31+
return response.json();
32+
}
33+
34+
async getTopSubgraphsForContract(contractAddress: string, chain: string) {
35+
const response = await fetch(`${this.baseUrl}/contract-subgraphs`, {
36+
method: "POST",
37+
headers: this.headers,
38+
body: JSON.stringify({
39+
method: "get_top_subgraph_deployments",
40+
params: { contract_address: contractAddress, chain },
41+
}),
42+
});
43+
44+
if (!response.ok) {
45+
throw new Error(`MCP contract subgraph lookup failed: ${response.status}`);
46+
}
47+
48+
return response.json();
49+
}
50+
51+
async getSubgraphSchema(subgraphId: string) {
52+
const response = await fetch(`${this.baseUrl}/schema`, {
53+
method: "POST",
54+
headers: this.headers,
55+
body: JSON.stringify({
56+
method: "get_schema_by_subgraph_id",
57+
params: { subgraph_id: subgraphId },
58+
}),
59+
});
60+
61+
if (!response.ok) {
62+
throw new Error(`MCP schema fetch failed: ${response.status}`);
63+
}
64+
65+
return response.json();
66+
}
67+
68+
async executeQuery(subgraphId: string, query: string, variables?: Record<string, any>) {
69+
const response = await fetch(`${this.baseUrl}/query`, {
70+
method: "POST",
71+
headers: this.headers,
72+
body: JSON.stringify({
73+
method: "execute_query_by_subgraph_id",
74+
params: { subgraph_id: subgraphId, query, variables },
75+
}),
76+
});
77+
78+
if (!response.ok) {
79+
throw new Error(`MCP query execution failed: ${response.status}`);
80+
}
81+
82+
return response.json();
83+
}
84+
}
85+
86+
// Schema definitions
87+
const searchSubgraphsSchema = z.object({
88+
keyword: z.string().describe("Keyword to search for in subgraph names and descriptions"),
89+
});
90+
91+
const getContractSubgraphsSchema = z.object({
92+
contractAddress: z.string().describe("The contract address to find subgraphs for"),
93+
chain: z.string().describe("The blockchain network (e.g., 'mainnet', 'polygon', 'arbitrum-one')"),
94+
});
95+
96+
const getSchemaSchema = z.object({
97+
subgraphId: z.string().describe("The subgraph ID to get the schema for"),
98+
});
99+
100+
const executeMCPQuerySchema = z.object({
101+
subgraphId: z.string().describe("The subgraph ID to query"),
102+
query: z.string().describe("The GraphQL query string"),
103+
variables: z.record(z.any()).optional().describe("Optional variables for the GraphQL query"),
104+
});
105+
106+
export class GraphMCPProvider implements ActionProvider<WalletProvider> {
107+
name = "graph-mcp";
108+
actionProviders = [];
109+
supportsNetwork = () => true;
110+
111+
private mcpClient: GraphMCPClient;
112+
113+
constructor() {
114+
const graphApiKey = process.env.GRAPH_API_KEY;
115+
if (!graphApiKey) {
116+
throw new Error("GRAPH_API_KEY not found in environment variables");
117+
}
118+
this.mcpClient = new GraphMCPClient(graphApiKey);
119+
}
120+
121+
getActions(walletProvider: WalletProvider) {
122+
return [
123+
{
124+
name: "searchSubgraphs",
125+
description: "Search for subgraphs by keyword using The Graph's MCP. Returns relevant subgraphs with metadata.",
126+
schema: searchSubgraphsSchema,
127+
severity: "info" as const,
128+
invoke: async ({ keyword }: z.infer<typeof searchSubgraphsSchema>) => {
129+
try {
130+
const result = await this.mcpClient.searchSubgraphs(keyword);
131+
return JSON.stringify(result, null, 2);
132+
} catch (error) {
133+
return JSON.stringify({
134+
error: error instanceof Error ? error.message : "Failed to search subgraphs",
135+
});
136+
}
137+
},
138+
},
139+
{
140+
name: "getContractSubgraphs",
141+
description: "Find the top subgraphs that index a specific contract address on a given blockchain.",
142+
schema: getContractSubgraphsSchema,
143+
severity: "info" as const,
144+
invoke: async ({ contractAddress, chain }: z.infer<typeof getContractSubgraphsSchema>) => {
145+
try {
146+
const result = await this.mcpClient.getTopSubgraphsForContract(contractAddress, chain);
147+
return JSON.stringify(result, null, 2);
148+
} catch (error) {
149+
return JSON.stringify({
150+
error: error instanceof Error ? error.message : "Failed to get contract subgraphs",
151+
});
152+
}
153+
},
154+
},
155+
{
156+
name: "getSubgraphSchema",
157+
description: "Get the GraphQL schema for a specific subgraph, showing available entities and fields.",
158+
schema: getSchemaSchema,
159+
severity: "info" as const,
160+
invoke: async ({ subgraphId }: z.infer<typeof getSchemaSchema>) => {
161+
try {
162+
const result = await this.mcpClient.getSubgraphSchema(subgraphId);
163+
return JSON.stringify(result, null, 2);
164+
} catch (error) {
165+
return JSON.stringify({
166+
error: error instanceof Error ? error.message : "Failed to get subgraph schema",
167+
});
168+
}
169+
},
170+
},
171+
{
172+
name: "executeMCPQuery",
173+
description: "Execute a GraphQL query against a subgraph using The Graph's MCP protocol.",
174+
schema: executeMCPQuerySchema,
175+
severity: "info" as const,
176+
invoke: async ({ subgraphId, query, variables = {} }: z.infer<typeof executeMCPQuerySchema>) => {
177+
try {
178+
const result = await this.mcpClient.executeQuery(subgraphId, query, variables);
179+
return JSON.stringify(result, null, 2);
180+
} catch (error) {
181+
return JSON.stringify({
182+
error: error instanceof Error ? error.message : "Failed to execute MCP query",
183+
});
184+
}
185+
},
186+
},
187+
];
188+
}
189+
}
190+
191+
export const graphMCPProvider = () => new GraphMCPProvider();

agent/the-graph-agent-scaffold-eth/packages/nextjs/utils/chat/tools.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { contractInteractor } from "./agentkit/action-providers/contract-interactor";
2+
import { graphMCPProvider } from "./agentkit/action-providers/graph-mcp-provider";
23
import { SUBGRAPH_ENDPOINTS, graphQuerierProvider } from "./agentkit/action-providers/graph-querier";
34
import { tokenApiProvider } from "./agentkit/action-providers/token-api-provider";
45
import { agentKitToTools } from "./agentkit/framework-extensions/ai-sdk";
@@ -26,6 +27,7 @@ export async function createAgentKit() {
2627
walletActionProvider(),
2728
contractInteractor(foundry.id),
2829
graphQuerierProvider(),
30+
graphMCPProvider(),
2931
tokenApiProvider(),
3032
],
3133
});

0 commit comments

Comments
 (0)