Overview
This page provides a complete, working example of a basic MCP server that uses ATXP to charge for tool calls. This code for this example implementation can be found at circuitandchisel/atxp-minimal-demo.
Project setup
In this tutorial, we’ll build a TypeScript MCP server that exposes two tools that can only be called after a successful payment is made using ATXP. We’ll use Express to build a streamable HTTP MCP server and run it locally using ngrok to enable us to connect to it from Goose.
Our MCP server will expose a simple addition tool that adds two numbers together, with each calculation costing 0.01 USDC.
Initialize the project
First, we need to initialize a new project. We’ll call it atxp-math-server
and intialize a new Node.js project.
mkdir atxp-math-server
cd atxp-math-server
npm init -y
Install dependencies
We need to install a few dependencies:
npm install express @modelcontextprotocol/sdk zod dotenv bignumber --save
We also need to install the ATXP express SDK:
npm install @atxp/express --save
Because we’re using TypeScript, we need to install a few extra development dependencies:
npm install typescript tsx @types/express @types/node --save-dev
Before we can start writing code, we need to configure our project.
First, we need to create a tsconfig.json
file with the following content at the root of our project to configure the TypeScript compiler.
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"outDir": "./build",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
We also need to modify our project’s package.json
file. We’ll add a few scripts to make it easier to build and run our project and set some project build settings.
{
"name": "atxp-math-server",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "tsc && chmod 755 build/index.js",
"start": "node build/index.js",
"dev": "tsx --watch src/index.ts",
"test": "echo \"Error: no test specified\" && exit 1"
},
"files": [
"build"
],
"bin": {
"atxp-math-server": "build/index.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"type": "commonjs",
"type": "module",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.17.1",
"express": "^5.1.0",
"zod": "^3.25.76",
"dotenv": "^17.2.1",
"@atxp/express": "^0.6.4"
},
"devDependencies": {
"@types/express": "^5.0.3",
"@types/node": "^24.2.0",
"typescript": "^5.9.2"
}
}
Set up your ATXP account
In order to receive payments, you need to create an ATXP account. This account will be used to receive payments in USDC when your MCP server’s tools are called.
Create an ATXP account
If you don’t have an ATXP account yet, create one and copy your wallet address.
Store wallet address in an environment variable
In order to use your wallet address in your MCP server, you need to set it in an environment variable. The best way to do this is to create a .env
file in the root of your project and add the following line:
ATXP_CONNECTION=<YOUR_CONNECTION_STRING>
Never commit your wallet address to version control. It is a good idea to add your .env
to your .gitignore
file to prevent it from being committed.
Develop the MCP server
We’re ready to start coding our MCP server now. Let’s start by creating our source code directory and server code file.
mkdir src
touch src/index.ts
Set up Express
We’ll use Express to build a streamable HTTP MCP server. To do this, we’ll need the following code in our index.ts
file:
import express, { Request, Response } from "express";
import { z } from "zod";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import dotenv from 'dotenv';
// Load environment variables from .env file
dotenv.config();
// Create our McpServer instance with the appropriate name and version
const server = new McpServer({
name: "atxp-math-server",
version: "1.0.0",
});
// Create our Express application
const app = express();
// Configure our Express application to parse JSON bodies
app.use(express.json());
// Create our transport instance
const transport: StreamableHTTPServerTransport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined, // set to undefined for stateless servers
});
// Setup routes for the server
const setupServer = async () => {
await server.connect(transport);
};
// Setup the URL endpoint that will handle MCP requests
app.post('/', async (req: Request, res: Response) => {
console.log('Received MCP request:', req.body);
try {
await transport.handleRequest(req, res, req.body);
} catch (error) {
console.error('Error handling MCP request:', error);
if (!res.headersSent) {
res.status(500).json({
jsonrpc: '2.0',
error: {
code: -32603,
message: 'Internal server error',
},
id: null,
});
}
}
});
// Start the server
const PORT = process.env.PORT || 3000;
setupServer().then(() => {
app.listen(PORT, () => {
console.log(`MCP Streamable HTTP Server listening on port ${PORT}`);
});
}).catch(error => {
console.error('Failed to set up the server:', error);
process.exit(1);
});
At this point, we should be able to build our MCP server without errors by running npm run build
. We can even start it locally by running npm run start
or npm run dev
to start it in development mode. However, we haven’t defined any tools yet, so it won’t be very useful.
The first tool we’ll add to our MCP server is the addition tool. This tool will take two numbers and return their sum.
// Create our McpServer instance with the appropriate name and version
const server = new McpServer({
name: "atxp-math-server",
version: "1.0.0",
});
// Create our addition tool
server.tool(
"add",
"Use this tool to add two numbers together.",
{
a: z.number().describe("The first number to add"),
b: z.number().describe("The second number to add"),
},
async ({ a, b }) => {
return {
content: [
{
type: "text",
text: `${a + b}`,
},
],
};
}
);
// Create our Express application
const app = express();
We can now build our MCP server using npm run build
to verify that it compiles without errors. At this point, we could connect a client like Goose to it and test that our tool does in fact add two numbers together, but it will be more exciting to see it in action after we’ve integrated payments. Skip forward a few steps to see how to connect to our MCP server and test it out if you want to try it out without payments.
We’ve already installed the ATXP server SDK, so we can use it to add payment requirements to our tools. First, we need to import the necessary functions from the SDK.
import express, { Request, Response } from "express";
import { z } from "zod";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import { atxpExpress, requirePayment, ATXPPaymentDestination } from '@atxp/express';
import BigNumber from "bignumber.js";
// Create our McpServer instance with the appropriate name and version
const server = new McpServer({
name: "atxp-math-server",
version: "1.0.0",
});
We’ll also need to set our wallet address in an environment variable. This is the wallet that payments to use the tool will be sent to.
export ATXP_CONNECTION=<YOUR_ATXP_CONNECTION_STRING>
In our MCP server code, we’ll read this wallet address from the environment variable.
// Create our Express application
const app = express();
// Configure our Express application to parse JSON bodies
app.use(express.json());
// Read your wallet address from the environment variable
const ATXP_CONNECTION = process.env.ATXP_CONNECTION
// Create our transport instance
const transport: StreamableHTTPServerTransport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined, // set to undefined for stateless servers
});
The ATXP server SDK provides an Express router that must be used to add payment capabilities to our MCP server. We need to configure our Express application to use this router.
// Create our Express application
const app = express();
// Configure our Express application to parse JSON bodies
app.use(express.json());
// Read your wallet address from the environment variable
const ATXP_CONNECTION = process.env.ATXP_CONNECTION
// Configure our Express application to use the ATXP router
app.use(atxpExpress({
paymentDestination: new ATXPPaymentDestination(ATXP_CONNECTION),
payeeName: 'Add',
}))
// Create our transport instance
const transport: StreamableHTTPServerTransport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined, // set to undefined for stateless servers
});
Finally, we need to modify our tool definition to require a payment before the tool is executed. We will do this by the requirePayment
function from the ATXP server SDK. This function takes a price
parameter that specifies the amount of USDC to charge for the tool call.
// Create our addition tool
server.tool(
"add",
"Use this tool to add two numbers together.",
{
a: z.number().describe("The first number to add"),
b: z.number().describe("The second number to add"),
},
async ({ a, b }) => {
// Require payment (in USDC) for the tool call
await requirePayment({price: BigNumber(0.01)});
return {
content: [
{
type: "text",
text: `${a + b}`,
},
],
};
}
);
Our MCP server is now ready to be used and will require a payment of $0.01 USDC for each call to the add
tool. Now let’s test the payment integration by running the MCP server locally and connecting to it using Goose.
Testing the MCP server
In order to test our MCP server locally, we need to run it and expose it to the internet. We’ll use ngrok to do this. ngrok is a tool that creates a secure tunnel to your local server so that it can be accessed from the internet. This is useful for testing local development servers before deploying them to a production environment. If you don’t have ngrok installed, you can install it and use it for free by following these instructions.
Run the MCP server
We will need two terminal sessions; one to run the MCP server and one to use ngrok to expose the MCP server to the internet.
In the first terminal session, we’ll run the MCP server, which will start listening on port 3000.
In the second terminal session, we’ll use ngrok to expose the MCP server to the internet so that we can connect to it using Goose. We’ll use the http
protocol and the port that the MCP server is listening on (3000).
ngrok http http://127.0.0.1:3000
This ngrok command will open a secure tunnel to your local MCP server and print out a URL that you can use to connect to it. The URL you are looking for is the https
URL. If you are using a free ngrok account (or are not authenticated with ngrok), the URL you are looking for will be something like https://<random-id>.ngrok-free.app
.
Connect to the MCP server
Now that we have the MCP server running and ngrok has exposed it to the internet, we can connect to it using Goose.
After you’ve set up Goose, you can connect to your local MCP server through the ngrok tunnel URL by going to Extensions in the Goose sidebar and clicking Add custom extension.
Provide a name for your Goose extension (e.g. “ATXP Math Server”), select Streamable HTTP as the type of extension, and paste the ngrok tunnel URL into the Endpoint field.
Then click Add Extension to save your new extension. Your browser will open a new tab in which you must authorize an ATXP wallet to pay for using your MCP server’s tool. Once you’ve authorized the wallet, you are ready to have your configured LLM use, and pay for, your MCP server’s tool.
Go through the payment flow
Now that you’ve connected Goose to your locally running MCP server, you can test the payment flow by sending a message in Goose such as “Add 1 and 2”.
Goose will show you a message that the tool is being called and that you need to pay make a payment at the supplied URL in order to use the tool.
Click on the URL to open the payment page in a new tab. Complete the payment in your browser and then return to Goose. Upon succesful payment, your browser will show you the details of the transaction.
Send another message to your configured LLM such as “I have completed the payment. Try again.” and you’ll see that the tool call is successful.
Congratulations! You’ve successfully built and tested a paid MCP server using ATXP. You can now charge for tool usage in your own MCP servers.
Resources