Skip to main content
Glama
StevenGeller

LDK MCP Server

by StevenGeller

ldk_generate_invoice

Create Lightning invoices with real payment hashes for testing iOS wallet development, specifying amount, description, and expiry time.

Instructions

Generate a Lightning invoice with real payment hash for testing

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
amountSatsYesAmount in satoshis
descriptionNoInvoice description
expirySecondsNoInvoice expiry time in seconds (default: 3600)

Implementation Reference

  • The execute handler function that implements the core logic: converts amount to millisatoshis, generates invoice via LightningService, formats response with success details and SwiftUI example code.
      execute: async (args: any): Promise<ToolResult> => {
        try {
          const amountMsat = args.amountSats * 1000;
          const invoice = await lightningService.generateInvoice(
            amountMsat,
            args.description,
            args.expirySeconds || 3600
          );
    
          return {
            content: [{
              type: 'text',
              text: JSON.stringify({
                success: true,
                invoice: invoice.bolt11,
                paymentHash: invoice.paymentHash,
                amountSats: args.amountSats,
                expiryTime: invoice.expiryTime,
                description: invoice.description,
                swiftExample: `
    // Swift code to generate and display Lightning invoice using LDK
    import SwiftUI
    import LightningDevKit
    import CoreImage.CIFilterBuiltins
    
    class InvoiceGenerator {
        let channelManager: Bindings.ChannelManager
        let logger: Bindings.Logger
        let keys: Bindings.KeysManager
        
        init(channelManager: Bindings.ChannelManager, logger: Bindings.Logger, keys: Bindings.KeysManager) {
            self.channelManager = channelManager
            self.logger = logger
            self.keys = keys
        }
        
        func createInvoice(amountMsat: UInt64?, description: String, expirySecs: UInt32 = 3600) -> Result<Bindings.Bolt11Invoice, Error> {
            let invoiceParams = Bindings.InvoiceBuilder.newInvoiceBuilder()
            
            if let amount = amountMsat {
                invoiceParams.amountMsat(amount)
            }
            
            invoiceParams.description(description.asLdkStr())
            invoiceParams.expiryTime(expirySecs)
            
            do {
                let invoice = try invoiceParams.build()
                return .success(invoice)
            } catch {
                return .failure(error)
            }
        }
    }
    
    struct InvoiceView: View {
        let invoice = "${invoice.bolt11}"
        let amountSats = ${args.amountSats}
        @State private var isCopied = false
        
        var body: some View {
            VStack(spacing: 20) {
                Text("Lightning Invoice")
                    .font(.title)
                
                // QR Code
                Image(uiImage: generateQRCode(from: invoice))
                    .interpolation(.none)
                    .resizable()
                    .scaledToFit()
                    .frame(width: 250, height: 250)
                    .overlay(
                        RoundedRectangle(cornerRadius: 16)
                            .stroke(Color.secondary.opacity(0.3), lineWidth: 1)
                    )
                
                // Amount
                VStack(spacing: 4) {
                    Text("\\(amountSats)")
                        .font(.system(size: 36, weight: .bold, design: .rounded))
                    Text("sats")
                        .font(.subheadline)
                        .foregroundColor(.secondary)
                }
                
                // Invoice details
                Text("${args.description || 'Lightning Payment'}")
                    .font(.subheadline)
                    .foregroundColor(.secondary)
                    .multilineTextAlignment(.center)
                
                // Copy button with feedback
                Button(action: {
                    UIPasteboard.general.string = invoice
                    withAnimation(.easeInOut(duration: 0.2)) {
                        isCopied = true
                    }
                    DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
                        withAnimation(.easeInOut(duration: 0.2)) {
                            isCopied = false
                        }
                    }
                }) {
                    Label(
                        isCopied ? "Copied!" : "Copy Invoice",
                        systemImage: isCopied ? "checkmark.circle.fill" : "doc.on.doc"
                    )
                    .frame(minWidth: 150)
                }
                .buttonStyle(.borderedProminent)
                .tint(isCopied ? .green : .accentColor)
                
                // Share button
                ShareLink(item: invoice) {
                    Label("Share", systemImage: "square.and.arrow.up")
                }
                .buttonStyle(.bordered)
            }
            .padding()
        }
        
        func generateQRCode(from string: String) -> UIImage {
            let context = CIContext()
            let filter = CIFilter.qrCodeGenerator()
            filter.message = Data(string.utf8)
            
            if let outputImage = filter.outputImage {
                let transform = CGAffineTransform(scaleX: 10, y: 10)
                let scaledImage = outputImage.transformed(by: transform)
                
                if let cgImage = context.createCGImage(scaledImage, from: scaledImage.extent) {
                    return UIImage(cgImage: cgImage)
                }
            }
            
            return UIImage(systemName: "xmark.circle") ?? UIImage()
        }
    }`.trim()
              }, null, 2)
            }]
          };
        } catch (error) {
          return {
            content: [{
              type: 'text',
              text: JSON.stringify({
                success: false,
                error: error instanceof Error ? error.message : 'Unknown error'
              }, null, 2)
            }],
            isError: true
          };
        }
      }
  • Input schema defining the parameters for generating the invoice: amountSats (required number >=1), optional description (string), optional expirySeconds (number, default 3600).
    inputSchema: {
      type: 'object',
      properties: {
        amountSats: {
          type: 'number',
          description: 'Amount in satoshis',
          minimum: 1
        },
        description: {
          type: 'string',
          description: 'Invoice description'
        },
        expirySeconds: {
          type: 'number',
          description: 'Invoice expiry time in seconds (default: 3600)',
          default: 3600
        }
      },
      required: ['amountSats']
    },
  • src/index.ts:38-62 (registration)
    Registration of the tool in the main tools array used by the MCP server for handling listTools and callTool requests.
    const tools = [
      generateInvoiceTool,
      payInvoiceTool,
      getChannelStatusTool,
      getNodeInfoTool,
      backupStateTool,
      keychainTestTool,
      backgroundTestTool,
      pushNotificationTool,
      biometricAuthTool,
      createChannelTool,
      closeChannelTool,
      getBalanceTool,
      decodeInvoiceTool,
      listPaymentsTool,
      estimateFeeTool,
      generateMnemonicTool,
      deriveAddressTool,
      getSwiftCodeTool,
      getArchitectureTool,
      testScenarioTool,
      networkGraphTool,
      eventHandlingTool,
      chainSyncTool,
    ];
  • src/index.ts:13-13 (registration)
    Import of the generateInvoiceTool for registration in the MCP server.
    import { generateInvoiceTool } from './tools/generateInvoice.js';
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden of behavioral disclosure. It mentions 'real payment hash' and 'for testing,' which hints at functionality but lacks details on critical behaviors: whether this is a read-only or write operation, if it requires network connectivity, what happens on failure, or any rate limits. For a tool that likely interacts with a Lightning network, this is insufficient.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, efficient sentence that front-loads the core purpose without unnecessary words. It's appropriately sized for the tool's complexity, making it easy for an AI agent to parse quickly.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the lack of annotations and output schema, the description is incomplete. It doesn't explain what the tool returns (e.g., an invoice string or error details), behavioral traits like side effects or permissions, or how it fits into the broader testing workflow. For a tool with 3 parameters and no structured output, more context is needed.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema description coverage is 100%, so the input schema already documents all parameters (amountSats, description, expirySeconds) with descriptions and defaults. The description adds no additional parameter semantics beyond implying invoice generation, which aligns with the schema but doesn't provide extra value. This meets the baseline score of 3 for high schema coverage.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose: 'Generate a Lightning invoice with real payment hash for testing.' It specifies the action (generate), resource (Lightning invoice), and context (for testing). However, it doesn't explicitly differentiate from sibling tools like 'ldk_pay_invoice' or 'ldk_decode_invoice', which is why it doesn't achieve a perfect score.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides minimal usage guidance by mentioning 'for testing,' which implies a context but doesn't specify when to use this tool versus alternatives (e.g., 'ldk_pay_invoice' for actual payments or 'ldk_decode_invoice' for parsing). There's no explicit when/when-not advice or prerequisite information, leaving gaps for an AI agent to infer proper usage.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

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/StevenGeller/ldk-mcp'

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