Tutorial Client-Server Communication
Tutorial: Client-Server Communication
Section titled “Tutorial: Client-Server Communication”This tutorial provides a complete, step-by-step guide to setting up a basic MCP client and server that communicate directly over the Nostr network using the @contextvm/sdk
.
Objective
Section titled “Objective”We will build two separate scripts:
server.ts
: An MCP server that exposes a simple “echo” tool.client.ts
: An MCP client that connects to the server, lists the available tools, and calls the “echo” tool.
Prerequisites
Section titled “Prerequisites”- You have completed the Quick Overview.
- You have two Nostr private keys (one for the server, one for the client). You can generate new keys using various tools, or by running
nostr-tools
commands.
1. The Server (server.ts
)
Section titled “1. The Server (server.ts)”First, let’s create the MCP server. This server will use the NostrServerTransport
to listen for requests on the Nostr network.
Create a new file named server.ts
:
import { McpServer, Tool } from "@modelcontextprotocol/sdk/server";import { NostrServerTransport } from "@ctxvm/sdk/transport";import { PrivateKeySigner } from "@ctxvm/sdk/signer";import { SimpleRelayPool } from "@ctxvm/sdk/relay";import { generateSecretKey, getPublicKey } from "nostr-tools/pure";
// --- Configuration ---// IMPORTANT: Replace with your own private keyconst SERVER_PRIVATE_KEY_HEX = process.env.SERVER_PRIVATE_KEY || "your-32-byte-server-private-key-in-hex";const RELAYS = ["wss://relay.damus.io", "wss://nos.lol"];
// --- Main Server Logic ---async function main() { // 1. Setup Signer and Relay Pool const signer = new PrivateKeySigner(SERVER_PRIVATE_KEY_HEX); const relayPool = new SimpleRelayPool(RELAYS); const serverPubkey = await signer.getPublicKey();
console.log(`Server Public Key: ${serverPubkey}`); console.log("Connecting to relays...");
// 2. Create and Configure the MCP Server const mcpServer = new McpServer({ name: "nostr-echo-server", version: "1.0.0", });
// 3. Define a simple "echo" tool server.registerTool( "echo", { title: "Echo Tool", description: "Echoes back the provided message", inputSchema: { message: z.string() }, }, async ({ message }) => ({ content: [{ type: "text", text: `Tool echo: ${message}` }], }), );
// 4. Configure the Nostr Server Transport const serverTransport = new NostrServerTransport({ signer, relayHandler: relayPool, isPublicServer: true, // Announce this server on the Nostr network serverInfo: { name: "CTXVM Echo Server", }, });
// 5. Connect the server await mcpServer.connect(serverTransport);
console.log("Server is running and listening for requests on Nostr..."); console.log("Press Ctrl+C to exit.");}
main().catch((error) => { console.error("Failed to start server:", error); process.exit(1);});
Running the Server
Section titled “Running the Server”To run the server, execute the following command in your terminal. Be sure to replace the placeholder private key or set the SERVER_PRIVATE_KEY
environment variable.
bun run server.ts
The server will start, print its public key, and wait for incoming client connections.
2. The Client (client.ts
)
Section titled “2. The Client (client.ts)”Next, let’s create the client that will connect to our server.
Create a new file named client.ts
:
import { Client } from "@modelcontextprotocol/sdk/client";import { NostrClientTransport } from "@ctxvm/sdk/transport";import { PrivateKeySigner } from "@ctxvm/sdk/signer";import { SimpleRelayPool } from "@ctxvm/sdk/relay";
// --- Configuration ---// IMPORTANT: Replace with the server's public key from the server outputconst SERVER_PUBKEY = "the-public-key-printed-by-server.ts";
// IMPORTANT: Replace with your own private keyconst CLIENT_PRIVATE_KEY_HEX = process.env.CLIENT_PRIVATE_KEY || "your-32-byte-client-private-key-in-hex";const RELAYS = ["wss://relay.damus.io", "wss://nos.lol"];
// --- Main Client Logic ---async function main() { // 1. Setup Signer and Relay Pool const signer = new PrivateKeySigner(CLIENT_PRIVATE_KEY_HEX); const relayPool = new SimpleRelayPool(RELAYS);
console.log("Connecting to relays...");
// 2. Configure the Nostr Client Transport const clientTransport = new NostrClientTransport({ signer, relayHandler: relayPool, serverPubkey: SERVER_PUBKEY, });
// 3. Create and connect the MCP Client const mcpClient = new Client(); await mcpClient.connect(clientTransport);
console.log("Connected to server!");
// 4. List the available tools console.log("\nListing available tools..."); const tools = await mcpClient.listTools(); console.log("Tools:", tools);
// 5. Call the "echo" tool console.log('\nCalling the "echo" tool...'); const echoResult = await mcpClient.callTool({ name: "echo", arguments: { message: "Hello, Nostr!" }, }); console.log("Echo result:", echoResult);
// 6. Close the connection await mcpClient.close(); console.log("\nConnection closed.");}
main().catch((error) => { console.error("Client failed:", error); process.exit(1);});
Running the Client
Section titled “Running the Client”Open a new terminal window (leave the server running in the first one). Before running the client, make sure to update the SERVER_PUBKEY
variable with the public key that your server.ts
script printed to the console.
Then, run the client:
bun run client.ts
Expected Output
Section titled “Expected Output”If everything is configured correctly, you should see the following output in the client’s terminal:
Connecting to relays...Connected to server!
Listing available tools...Tools: { tools: [ { name: 'echo', description: 'Replies with the input it received.', inputSchema: { ... } } ]}
Calling the "echo" tool...Echo result: You said: Hello, Nostr!
Connection closed.
And that’s it! You’ve successfully created an MCP client and server that communicate securely and decentrally over the Nostr network.