Skip to main content
Glama

postNote

Publish content to the Nostr social network by signing notes with a private key, enabling authenticated posting with optional tags and relay selection.

Instructions

Post a note using an existing private key (authenticated posting)

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
privateKeyYesPrivate key to sign the note with (hex format or nsec format)
contentYesContent of the note to post
tagsNoOptional tags to include with the note
relaysNoOptional list of relays to publish to

Implementation Reference

  • Core handler function that normalizes the private key, derives public key, creates a kind 1 text note event, signs it, and publishes to specified relays.
    export async function postNote( privateKey: string, content: string, tags: string[][] = [], relays: string[] = DEFAULT_RELAYS ): Promise<{ success: boolean, message: string, noteId?: string, publicKey?: string }> { try { // console.log(`Preparing to post authenticated note to ${relays.join(", ")}`); // Normalize private key const normalizedPrivateKey = normalizePrivateKey(privateKey); // Derive public key from private key const publicKey = getPublicKeyFromPrivate(normalizedPrivateKey); // Create a fresh pool for this request const pool = getFreshPool(relays); try { // Create the note event template const noteTemplate = createEvent({ kind: 1, // kind 1 is a text note content, tags }, publicKey); // Get event hash and sign it const eventId = await getEventHash(noteTemplate); const signature = await signEvent(eventId, normalizedPrivateKey); // Create complete signed event const signedNote = { ...noteTemplate, id: eventId, sig: signature }; // If no relays specified, just return success with event creation if (relays.length === 0) { return { success: true, message: 'Note created and signed successfully (no relays specified for publishing)', noteId: signedNote.id, publicKey: publicKey, }; } // Publish to relays - pool.publish returns array of promises const pubPromises = pool.publish(relays, signedNote); // Wait for all publish attempts to complete or timeout const results = await Promise.allSettled(pubPromises); // Check if at least one relay accepted the note const successCount = results.filter(r => r.status === 'fulfilled' && r.value?.success === true ).length; if (successCount === 0) { return { success: false, message: 'Failed to publish note to any relay', }; } return { success: true, message: `Note published to ${successCount}/${relays.length} relays`, noteId: signedNote.id, publicKey: publicKey, }; } catch (error) { console.error("Error posting note:", error); return { success: false, message: `Error posting note: ${error instanceof Error ? error.message : "Unknown error"}`, }; } finally { // Clean up any subscriptions and close the pool await pool.close(); } } catch (error) { return { success: false, message: `Fatal error: ${error instanceof Error ? error.message : "Unknown error"}`, }; } }
  • Zod schema defining input parameters for the postNote tool.
    export const postNoteToolConfig = { privateKey: z.string().describe("Private key to sign the note with (hex format or nsec format)"), content: z.string().describe("Content of the note to post"), tags: z.array(z.array(z.string())).optional().describe("Optional tags to include with the note"), relays: z.array(z.string()).optional().describe("Optional list of relays to publish to"), };
  • index.ts:1346-1402 (registration)
    MCP server registration of the postNote tool, including a thin wrapper handler that calls the core postNote function and formats the response.
    server.tool( "postNote", "Post a note using an existing private key (authenticated posting)", postNoteToolConfig, async ({ privateKey, content, tags, relays }) => { try { const result = await postNote(privateKey, content, tags, relays); if (result.success) { let response = `Note posted successfully!\n\n`; response += `${result.message}\n`; if (result.noteId) { response += `Note ID: ${result.noteId}\n`; } if (result.publicKey) { response += `Author: ${formatPubkey(result.publicKey)}\n`; } response += `Content: "${content}"\n`; if (tags && tags.length > 0) { response += `Tags: ${JSON.stringify(tags)}\n`; } if (relays && relays.length > 0) { response += `Relays: ${relays.join(", ")}\n`; } return { content: [ { type: "text", text: response, }, ], }; } else { return { content: [ { type: "text", text: `Failed to post note: ${result.message}`, }, ], }; } } catch (error) { console.error("Error in postNote tool:", error); return { content: [ { type: "text", text: `Error posting note: ${error instanceof Error ? error.message : "Unknown error"}`, }, ], }; } }, );
  • Helper function to normalize private key input, converting nsec to hex if necessary.
    function normalizePrivateKey(privateKey: string): string { if (privateKey.startsWith('nsec')) { // Validate nsec format before type assertion if (!/^nsec1[0-9a-z]+$/.test(privateKey)) { throw new Error('Invalid nsec format: must match pattern nsec1[0-9a-z]+'); } const decoded = nip19decode(privateKey as `${string}1${string}`); if (decoded.type !== 'nsec') { throw new Error('Invalid nsec format'); } return decoded.data; } // Validate hex format for non-nsec keys if (!/^[0-9a-f]{64}$/.test(privateKey)) { throw new Error('Invalid private key format: must be 64-character hex string or valid nsec format'); } return privateKey; }
  • Helper function to derive the public key from a private key using schnorr.
    function getPublicKeyFromPrivate(privateKey: string): string { return Buffer.from(schnorr.getPublicKey(privateKey)).toString('hex'); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/AustinKelsay/nostr-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server