Skip to content
Open
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
105 changes: 83 additions & 22 deletions app/[transport]/route.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,35 @@
import { v4 as uuidv4 } from "uuid";
import { deserializeAddress } from "@meshsdk/core";
import { createMcpHandler } from "@vercel/mcp-adapter";
import z from "zod";
import { getAddressBalance, getAddressUtxos, getAddressTransactions, getAccount, getAccountRewards, getAccountUtxos } from "@/app/_utils/blockfrost";
import type { ProviderContext } from "@/app/_lib/blockfrostProvider";

// In-memory store for session keys
const sessionKeys = new Map(); // uuid -> { mainnetKey, preprodKey }

const handler = createMcpHandler(
(server) => {
server.tool(
"setupSession",
"Setup a session with optional Blockfrost keys. Returns a session UUID.",
{
mainnetKey: z.string().optional(),
preprodKey: z.string().optional(),
},
({ mainnetKey, preprodKey }) => {
const sessionId = uuidv4();
sessionKeys.set(sessionId, { mainnetKey, preprodKey });
return {
content: [
{
type: "text",
text: JSON.stringify({ sessionId }),
},
],
};
}
);
server.tool(
"deserializeAddress",
"Deserialize an address into a structured object",
Expand All @@ -26,9 +51,15 @@ const handler = createMcpHandler(
{
address: z.string(),
network: z.number(),
sessionId: z.string().uuid().optional(),
},
async ({ address, network }) => {
const result = await getAddressBalance(address, network);
async ({ address, network, sessionId }) => {
let context: ProviderContext = { network };
if (sessionId && sessionKeys.has(sessionId)) {
const { mainnetKey, preprodKey } = sessionKeys.get(sessionId);
context = { network, mainnetKey, preprodKey };
}
const result = await getAddressBalance(address, context);
return {
content: [
{
Expand All @@ -45,9 +76,15 @@ const handler = createMcpHandler(
{
address: z.string(),
network: z.number(),
sessionId: z.string().uuid().optional(),
},
async ({ address, network }) => {
const result = await getAddressUtxos(address, network);
async ({ address, network, sessionId }) => {
let context: ProviderContext = { network };
if (sessionId && sessionKeys.has(sessionId)) {
const { mainnetKey, preprodKey } = sessionKeys.get(sessionId);
context = { network, mainnetKey, preprodKey };
}
const result = await getAddressUtxos(address, context);
return {
content: [
{
Expand All @@ -62,19 +99,25 @@ const handler = createMcpHandler(
"getAddressTransactions",
"Get the transactions for an address on a given network (0: preprod, 1: mainnet)",
{
address: z.string(),
network: z.number(),
address: z.string(),
network: z.number(),
sessionId: z.string().uuid().optional(),
},
async ({ address, network }) => {
const result = await getAddressTransactions(address, network);
return {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2),
}
]
};
async ({ address, network, sessionId }) => {
let context: ProviderContext = { network };
if (sessionId && sessionKeys.has(sessionId)) {
const { mainnetKey, preprodKey } = sessionKeys.get(sessionId);
context = { network, mainnetKey, preprodKey };
}
const result = await getAddressTransactions(address, context);
return {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2),
}
]
};
}
);
server.tool(
Expand All @@ -83,9 +126,15 @@ const handler = createMcpHandler(
{
stake_address: z.string(),
network: z.number(),
sessionId: z.string().uuid().optional(),
},
async ({ stake_address, network }) => {
const result = await getAccount(stake_address, network);
async ({ stake_address, network, sessionId }) => {
let context: ProviderContext = { network };
if (sessionId && sessionKeys.has(sessionId)) {
const { mainnetKey, preprodKey } = sessionKeys.get(sessionId);
context = { network, mainnetKey, preprodKey };
}
const result = await getAccount(stake_address, context);
return {
content: [
{
Expand All @@ -102,9 +151,15 @@ const handler = createMcpHandler(
{
stake_address: z.string(),
network: z.number(),
sessionId: z.string().uuid().optional(),
},
async ({ stake_address, network }) => {
const result = await getAccountRewards(stake_address, network);
async ({ stake_address, network, sessionId }) => {
let context: ProviderContext = { network };
if (sessionId && sessionKeys.has(sessionId)) {
const { mainnetKey, preprodKey } = sessionKeys.get(sessionId);
context = { network, mainnetKey, preprodKey };
}
const result = await getAccountRewards(stake_address, context);
return {
content: [
{
Expand All @@ -121,9 +176,15 @@ const handler = createMcpHandler(
{
stake_address: z.string(),
network: z.number(),
sessionId: z.string().uuid().optional(),
},
async ({ stake_address, network }) => {
const result = await getAccountUtxos(stake_address, network);
async ({ stake_address, network, sessionId }) => {
let context: ProviderContext = { network };
if (sessionId && sessionKeys.has(sessionId)) {
const { mainnetKey, preprodKey } = sessionKeys.get(sessionId);
context = { network, mainnetKey, preprodKey };
}
const result = await getAccountUtxos(stake_address, context);
return {
content: [
{
Expand Down
14 changes: 10 additions & 4 deletions app/_lib/blockfrostProvider.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import { env } from "@/app/env";
import { BlockfrostProvider } from "@meshsdk/core";

export function getProvider(network: number) {
export type ProviderContext = {
network: number;
mainnetKey?: string;
preprodKey?: string;
};

export function getProvider(context: ProviderContext) {
return new BlockfrostProvider(
network == 0
? env.NEXT_PUBLIC_BLOCKFROST_API_KEY_PREPROD
: env.NEXT_PUBLIC_BLOCKFROST_API_KEY_MAINNET,
context.network === 0
? (context.preprodKey || env.NEXT_PUBLIC_BLOCKFROST_API_KEY_PREPROD)
: (context.mainnetKey || env.NEXT_PUBLIC_BLOCKFROST_API_KEY_MAINNET)
);
}
32 changes: 16 additions & 16 deletions app/_utils/blockfrost.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { getProvider } from "@/app/_lib/blockfrostProvider";
import { getProvider, ProviderContext } from "@/app/_lib/blockfrostProvider";

export async function getAddressBalance(address: string, network: number) {
const provider = getProvider(network);
export async function getAddressBalance(address: string, context: ProviderContext) {
const provider = getProvider(context);
try {
const balance = await provider.get(`/addresses/${address}`);
return balance;
Expand All @@ -15,8 +15,8 @@ export async function getAddressBalance(address: string, network: number) {
}
}

export async function getAddressUtxos(address: string, network: number) {
const provider = getProvider(network);
export async function getAddressUtxos(address: string, context: ProviderContext) {
const provider = getProvider(context);
try {
const utxos = await provider.get(`/addresses/${address}/utxos`);
return utxos;
Expand All @@ -30,8 +30,8 @@ export async function getAddressUtxos(address: string, network: number) {
}
}

export async function getAddressTransactions(address: string, network: number) {
const provider = getProvider(network);
export async function getAddressTransactions(address: string, context: ProviderContext) {
const provider = getProvider(context);
try {
const txs = await provider.get(`/addresses/${address}/transactions`);
return txs;
Expand All @@ -45,10 +45,10 @@ export async function getAddressTransactions(address: string, network: number) {
}
}

export async function getAccount(stakeAddress: string, network: number) {
const provider = getProvider(network);
export async function getAccount(stakeAddress: string, context: ProviderContext) {
const provider = getProvider(context);

console.log(`Fetching account for stake address ${stakeAddress} on network ${network}`);
console.log(`Fetching account for stake address ${stakeAddress} on network ${context.network}`);
try {
const account = await provider.get(`/accounts/${stakeAddress}`);
console.log(`Account for stake address ${stakeAddress}:`, account);
Expand All @@ -63,10 +63,10 @@ export async function getAccount(stakeAddress: string, network: number) {
}
}

export async function getAccountRewards(stakeAddress: string, network: number) {
const provider = getProvider(network);
export async function getAccountRewards(stakeAddress: string, context: ProviderContext) {
const provider = getProvider(context);

console.log(`Fetching rewards for stake address ${stakeAddress} on network ${network}`);
console.log(`Fetching rewards for stake address ${stakeAddress} on network ${context.network}`);
try {
const rewards = await provider.get(`/accounts/${stakeAddress}/rewards`);
console.log(`Rewards for stake address ${stakeAddress}:`, rewards);
Expand All @@ -81,10 +81,10 @@ export async function getAccountRewards(stakeAddress: string, network: number) {
}
}

export async function getAccountUtxos(stakeAddress: string, network: number) {
const provider = getProvider(network);
export async function getAccountUtxos(stakeAddress: string, context: ProviderContext) {
const provider = getProvider(context);

console.log(`Fetching UTXOs for stake address ${stakeAddress} on network ${network}`);
console.log(`Fetching UTXOs for stake address ${stakeAddress} on network ${context.network}`);
try {
const utxos = await provider.get(`/accounts/${stakeAddress}/utxos`);
console.log(`UTXOs for stake address ${stakeAddress}:`, utxos);
Expand Down
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"react": "^19.0.0",
"react-dom": "^19.0.0",
"redis": "^5.6.0",
"uuid": "^11.1.0",
"zod": "^3.25.76"
},
"devDependencies": {
Expand Down