npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@stellar-mcp/client

v0.2.0

Published

Type-safe programmatic client for Stellar MCP servers — discover tools, call contracts, sign and submit transactions

Downloads

361

Readme

@stellar-mcp/client

Type-safe programmatic client for Stellar MCP servers — discover tools, call Soroban contracts, and sign/submit transactions with pluggable signer adapters.

Overview

@stellar-mcp/client is the SDK layer for interacting with MCP servers generated by stellar mcp generate. It speaks the MCP JSON-RPC 2.0 protocol over HTTP and exposes a clean TypeScript API for:

  • Tool discovery — list all tools a server exposes and their schemas
  • Contract calls — invoke any server tool and receive typed results (including XDR for write operations)
  • Transaction signing — pluggable signer adapters for secret keys, Freighter browser wallets, and PasskeyKit smart wallets
  • Transaction confirmation — poll the Stellar RPC until a transaction finalises

Installation

npm install @stellar-mcp/client

Peer Dependencies

| Package | Required | Use case | |---|---|---| | @modelcontextprotocol/sdk | Yes | MCP transport layer | | @stellar/stellar-sdk | Yes | XDR signing, RPC calls | | @creit.tech/stellar-wallets-kit | Optional | Freighter browser signer | | passkey-kit | Optional | PasskeyKit smart wallet signer |

# Minimal (Node.js / server-side)
npm install @modelcontextprotocol/sdk @stellar/stellar-sdk

# With browser wallet support
npm install @creit.tech/stellar-wallets-kit

# With PasskeyKit smart wallet support
npm install passkey-kit

Quick Start — Type-Safe (Recommended)

Generate a typed client file from your running MCP server:

npx mcp-generate-types --url http://localhost:3001/mcp --out ./src/mcp-types.ts

The generated types reflect your contract's functions — tool names, arg shapes, and result types are all derived from whatever contract you passed to stellar mcp generate. The snippet below uses a token-factory contract as an example; a DEX, AMM, or any other Soroban contract would produce different tool names.

import { createMCPClient } from './src/mcp-types'; // generated — reflects your contract
import { secretKeySigner } from '@stellar-mcp/client';
import { Networks } from '@stellar/stellar-sdk';

const client = createMCPClient({
  url: 'http://localhost:3001/mcp',
  networkPassphrase: Networks.TESTNET,
  rpcUrl: 'https://soroban-testnet.stellar.org',
});

// Tool names, arg types, and result types all come from your contract.
// Example below is a token-factory contract — yours will differ.
const { data: admin } = await client.call('get-admin');       // contract-specific
const { data: count } = await client.call('get-token-count'); // contract-specific

const { xdr } = await client.call('deploy-token', {
  deployer: 'GABC...',
  config: { name: 'MyToken', symbol: 'MTK', decimals: 7, /* ... */ },
});

const result = await client.signAndSubmit(xdr!, {
  signer: secretKeySigner(process.env.SECRET_KEY!),
});
console.log(result.hash, result.status); // → 'SUCCESS'

client.close();

Quick Start — Untyped

Use MCPClient directly without code generation. Tool names and args are string / unknown.

import { MCPClient, secretKeySigner } from '@stellar-mcp/client';
import { Networks } from '@stellar/stellar-sdk';

const client = new MCPClient({
  url: 'http://localhost:3001/mcp',
  networkPassphrase: Networks.TESTNET,
  rpcUrl: 'https://soroban-testnet.stellar.org',
});

const tools = await client.listTools();
console.log(tools.map(t => t.name));

const { data: admin } = await client.call('get-admin');
const { xdr } = await client.call('deploy-token', { deployer: 'GABC...', config: { /* ... */ } });
const result = await client.signAndSubmit(xdr!, { signer: secretKeySigner(process.env.SECRET_KEY!) });

client.close();

mcp-generate-types CLI

Connects to a live MCP server and generates a typed TypeScript file with:

  • A TypeScript interface per tool (DeployTokenArgs, GetAdminArgs, …)
  • A ServerTools ToolMap that links each tool name to its arg and result types
  • A createMCPClient factory function — the recommended way to create clients
# Generate types from a running server
npx mcp-generate-types --url http://localhost:3001/mcp

# Custom output path
npx mcp-generate-types --url http://localhost:3001/mcp --out ./src/mcp-types.ts

# Help
npx mcp-generate-types --help

Options:

| Flag | Default | Description | |---|---|---| | --url | required | MCP server URL | | --out | mcp-types.ts | Output file path |

What the generated file looks like:

The CLI introspects your live server and emits one arg interface + one result interface per tool. The names come directly from your contract — get-admin and deploy-token below are specific to a token-factory contract. A DEX might generate swap, add-liquidity, etc.

// Auto-generated by @stellar-mcp/client — DO NOT EDIT
// (example output for a token-factory contract — your contract's tools will differ)

import { MCPClient, type MCPClientOptions, type ToolMap } from '@stellar-mcp/client';

// ─── Arg Types ────────────────────────────────────────────────────────────────

export interface DeployTokenArgs {      // one interface per tool, named after your contract fn
  deployer: string;
  config: {
    admin: string;
    name: string;
    symbol: string;
    decimals: number;
    token_type: { tag: 'Allowlist' } | { tag: 'Pausable' } | { tag: 'Regulated' };
    // ...
  };
}

export type GetAdminArgs = Record<string, never>;

// ─── Result Types ─────────────────────────────────────────────────────────────

export interface GetAdminResult {        // compiled from the tool's outputSchema
  xdr: string;
  simulationResult?: string;
}

export interface DeployTokenResult {
  xdr: string;                           // unsigned XDR — pass to signAndSubmit()
  simulationResult?: unknown;
}

// ─── Tool Map ─────────────────────────────────────────────────────────────────

interface ServerTools extends ToolMap {
  'get-admin':    { args: GetAdminArgs;    result: GetAdminResult };
  'deploy-token': { args: DeployTokenArgs; result: DeployTokenResult };
  // ... one entry per tool exposed by the server
}

export function createMCPClient(options: MCPClientOptions): MCPClient<ServerTools> {
  return new MCPClient<ServerTools>(options);
}

Result types are compiled from each tool's outputSchema — read-only tools get precise types (e.g. simulationResult?: string), write tools always include the unsigned xdr field.

Regenerate when the server changes:

npx mcp-generate-types --url http://localhost:3001/mcp --out ./src/mcp-types.ts

Schema Utilities

Helper functions for building UIs on top of MCP tool definitions. These work on the JSON Schema from ToolInfo.inputSchema and are presentation-layer agnostic — equally useful for Telegram bots, React forms, CLIs, or any custom interface.

import {
  extractArgs,
  buildToolArgs,
  parseArgValue,
  isReadOperation,
  argKey,
  type ArgDef,
} from '@stellar-mcp/client';

extractArgs(tool)

Flattens a tool's inputSchema into a sorted, display-ready ArgDef[]. Handles nested objects (recursively expanded with path tracking), discriminated unions (oneOf/anyOf with { tag: "Value" } pattern → enum fields), nullable types, and enum fields. Required args come before optional at every level.

const args = extractArgs(tool);
// [{ name: 'deployer', path: ['deployer'], type: 'string', required: true }, ...]

buildToolArgs(args, collected)

Reconstructs the nested args object from a flat key→value map, ready to pass to client.call(). Automatically wraps discriminated union values as { tag: value }.

const toolArgs = buildToolArgs(args, {
  deployer: 'GABC...',
  'config.admin': 'GDEF...',
  'config.decimals': 7,
});
// { deployer: 'GABC...', config: { admin: 'GDEF...', decimals: 7 } }

parseArgValue(value, arg)

Coerces a user-typed string into the correct JS type for the given ArgDef.

parseArgValue('42', { type: 'number', ... })   // → 42
parseArgValue('true', { type: 'boolean', ... }) // → true
parseArgValue('{"a":1}', { type: 'object', ... }) // → { a: 1 }
parseArgValue('', { type: 'string', nullable: true, ... }) // → null

isReadOperation(toolName)

Heuristic that returns true for tool names starting with read-only prefixes (get, list, query, fetch, find, search, is, has, check, count, show, view, read).

isReadOperation('get-admin')    // true
isReadOperation('list_tokens')  // true
isReadOperation('deploy-token') // false

ArgDef

interface ArgDef {
  name: string;
  path: string[];      // e.g. ['config', 'admin'] for nested fields
  type: string;        // 'string' | 'number' | 'boolean' | 'object' | 'array' | 'enum' | 'any'
  description: string;
  required: boolean;
  enum?: string[];     // present for enum / discriminated union fields
  group?: string;      // section header for nested object groups
  nullable?: boolean;
  unionTag?: boolean;  // if true, value must be wrapped as { tag: value }
}

API Reference

new MCPClient(options)

Creates a client instance. The connection is lazy — it opens on the first method call.

interface MCPClientOptions {
  url: string;               // Full MCP server URL, e.g. 'http://localhost:3001/mcp'
  networkPassphrase: string; // Stellar network passphrase (Networks.TESTNET / Networks.PUBLIC)
  rpcUrl: string;            // Soroban RPC endpoint
  timeout?: number;          // Request timeout in ms (default: 30 000)
}

client.listTools()

Discover all tools the server exposes.

const tools = await client.listTools();
// [{ name: 'deploy-token', description: '...', inputSchema: { ... } }, ...]

Returns ToolInfo[]:

interface ToolInfo {
  name: string;
  description: string;
  inputSchema: Record<string, unknown>;   // JSON Schema for tool arguments
  outputSchema?: Record<string, unknown>; // JSON Schema for the result (when server provides it)
}

client.call(toolName, args?)

Call any MCP tool by name.

// Read-only tool
const { data } = await client.call('get-token-count');

// Write tool — data contains the full response, xdr is extracted automatically
const { data, xdr, simulationResult } = await client.call('deploy-token', {
  deployer: 'GABC...',
  config: { /* ... */ },
});

Returns CallResult<TData>:

interface CallResult<TData = unknown> {
  data: TData;                // Full parsed JSON from server (typed when using createMCPClient)
  xdr?: string;               // Transaction XDR (write ops only)
  simulationResult?: unknown; // Simulation metadata, if server includes it
}

Throws MCPToolError if the server returns an error response.


client.simulate(toolName, args?)

Preview a transaction without signing or submitting. Returns the assembled XDR and the estimated fee in stroops — useful for showing users the cost before asking them to confirm.

const preview = await client.simulate('deploy-token', {
  deployer: 'GABC...',
  config: { /* ... */ },
});

console.log(`Estimated fee: ${preview.fee} stroops`);

// Only sign+submit if the user confirms:
const { hash } = await client.signAndSubmit(preview.xdr!, {
  signer: secretKeySigner(process.env.SECRET_KEY!),
});
const result = await client.waitForConfirmation(hash);

Returns SimulateResult<TData>:

| Field | Type | Description | |---|---|---| | xdr | string \| undefined | Assembled transaction XDR, ready to sign | | fee | string \| undefined | Estimated fee in stroops (extracted from XDR) | | simulationResult | TData \| undefined | Decoded return value (for read-only tools, this is the answer) |

For read-only tools (get-admin, get-token-count, etc.) xdr and fee are undefined — use simulationResult for the value.

This completes the full 4-step Soroban lifecycle originally specified:

simulate → inspect → signAndSubmit → waitForConfirmation


client.signAndSubmit(xdr, options)

Sign and submit a transaction using a signer adapter.

const result = await client.signAndSubmit(xdr!, {
  signer: secretKeySigner(process.env.SECRET_KEY!),
});
// { hash: 'a1b2...', status: 'SUCCESS', result: ... }

Returns SubmitResult:

interface SubmitResult {
  hash: string;
  status: string;   // 'SUCCESS' | 'FAILED' | ...
  result?: unknown;
}

client.waitForConfirmation(hash)

Poll the Soroban RPC until a transaction hash confirms. Useful if you submitted a transaction separately.

const result = await client.waitForConfirmation('a1b2c3...');

client.close()

Close the transport connection. Safe to call multiple times.


Signers

Signers are pluggable adapters that implement the Signer interface. Pass one to client.signAndSubmit().

secretKeySigner(secretKey) — Node.js & server-side

Delegates signing and submission to the MCP server's built-in sign-and-submit tool. The server handles auth entry signing, fresh sequence numbers, and LaunchTube submission. The secret key is passed per-request and never stored.

Security note: secretKeySigner transmits the secret key to the MCP server's sign-and-submit tool. Only use this with trusted, local, or TLS-secured servers. For untrusted servers, prefer connectFreighter() which signs client-side.

import { secretKeySigner } from '@stellar-mcp/client';
// or via entry point:
import { secretKeySigner } from '@stellar-mcp/client/signers/secret';

const result = await client.signAndSubmit(xdr!, {
  signer: secretKeySigner(process.env.SECRET_KEY!),
});

connectFreighter(networkPassphrase) — Browser only (recommended)

The recommended way to use Freighter in browser dApps. Connects to the wallet once and returns both the wallet address (for your UI) and a pre-connected signer (for transactions). The signer closes over the live wallet connection — it will not re-prompt for the address on each signAndSubmit() call.

Requires @creit.tech/stellar-wallets-kit as a peer dependency.

import { connectFreighter } from '@stellar-mcp/client';
import { Networks } from '@stellar/stellar-sdk';

// Connect once at startup or on button click
const { address, signer } = await connectFreighter(Networks.TESTNET);

// Show address in your UI
headerEl.textContent = address;

// Later, when user submits a transaction:
const result = await client.signAndSubmit(xdr!, { signer });
// Freighter prompts only for signing — no re-connect popup

Returns FreighterConnection:

interface FreighterConnection {
  address: string;  // wallet public key (G...)
  signer:  Signer;  // pre-connected, pass to signAndSubmit()
}

Flow:

  1. Creates StellarWalletsKit once
  2. Fetches wallet address once — stored in closure
  3. Returns { address, signer } immediately
  4. On each signAndSubmit: calls prepare-transaction → Freighter sign popup → RPC submit → poll

freighterSigner(options?) — Browser only (stateless variant)

The stateless variant — re-connects on every signAndSubmit() call. Useful in scripts or server-side rendering contexts where you don't need to display the wallet address before a transaction. For browser dApps, prefer connectFreighter() above.

Requires @creit.tech/stellar-wallets-kit as a peer dependency.

import { freighterSigner } from '@stellar-mcp/client';
// or via entry point:
import { freighterSigner } from '@stellar-mcp/client/signers/freighter';

const result = await client.signAndSubmit(xdr!, {
  signer: freighterSigner(),
});

passkeyKitSigner(options) — PasskeyKit smart wallets

Delegates to the MCP server's sign-and-submit tool with a smart wallet contract ID. The server reads its WALLET_SIGNER_SECRET for auth entry signing and uses the provided feePayerSecret for the transaction envelope.

Requires passkey-kit as a peer dependency on the MCP server side.

import { passkeyKitSigner } from '@stellar-mcp/client';
// or via entry point:
import { passkeyKitSigner } from '@stellar-mcp/client/signers/passkey';

const result = await client.signAndSubmit(xdr!, {
  signer: passkeyKitSigner({
    walletContractId: 'CABC...',
    feePayerSecret: process.env.FEE_PAYER_SECRET!,
  }),
});

Custom Signers

Implement the Signer interface to add your own signing logic:

import type { Signer, SignerContext, SubmitResult } from '@stellar-mcp/client';

const mySigner: Signer = {
  async execute(xdr: string, context: SignerContext): Promise<SubmitResult> {
    // context provides: rpcUrl, networkPassphrase, mcpCall()
    const signedXdr = await mySigningLib.sign(xdr, context.networkPassphrase);
    // submit via mcpCall or directly to RPC...
    return { hash: '...', status: 'SUCCESS' };
  },
};

Error Handling

All SDK errors extend MCPClientError:

| Class | Thrown when | |---|---| | MCPConnectionError | Cannot connect to MCP server (transport failure, timeout, handshake error) | | MCPToolError | Server returns isError: true or an error-shaped response — includes toolName | | TransactionError | Transaction submission or confirmation fails — includes hash when available |

import {
  MCPConnectionError,
  MCPToolError,
  TransactionError,
} from '@stellar-mcp/client';

try {
  const { xdr } = await client.call('deploy-token', params);
  await client.signAndSubmit(xdr!, { signer });
} catch (err) {
  if (err instanceof MCPToolError) {
    console.error(`Tool '${err.toolName}' failed:`, err.message);
  } else if (err instanceof MCPConnectionError) {
    console.error('Could not reach MCP server:', err.message);
  } else if (err instanceof TransactionError) {
    console.error('Transaction failed:', err.message, 'hash:', err.hash);
  }
}

Transport

The client automatically negotiates the transport protocol:

  1. StreamableHTTP (preferred) — single endpoint, stateless HTTP POST
  2. SSE (fallback) — legacy Server-Sent Events transport

Both protocols use the same URL. Generated MCP servers support both when started with USE_HTTP=true.


Logging

The SDK ships a built-in logger that is silent by default. Enable it for debugging:

import { logger } from '@stellar-mcp/client';

logger.setLevel('debug'); // 'debug' | 'info' | 'warn' | 'error' | 'silent'

Integration Tests

Integration tests run against a real MCP server and are gated on an environment variable to keep CI safe:

Server URL: MCP servers generated by stellar mcp generate run on http://localhost:3001/mcp by default (USE_HTTP=true PORT=3001). All examples below assume this URL. Override with the MCP_URL env var if your server is on a different port.

# Start your generated MCP server first:
USE_HTTP=true PORT=3001 node /path/to/generated/dist/index.js

# Build the SDK first (CLI tests require dist/cli/generate-types.js)
npm run build

# Read-only tests + CLI generate-types tests (no secret key required)
MCP_URL=http://localhost:3001/mcp RUN_INTEGRATION=1 npm run test:integration

# Full suite including deploy + submit
MCP_URL=http://localhost:3001/mcp \
  RUN_INTEGRATION=1 \
  TEST_ADMIN_ADDRESS=G... \
  TEST_SECRET_KEY=S... \
  npm run test:integration

| Variable | Default | Required | |---|---|---| | RUN_INTEGRATION | — | Must be 1 to run | | MCP_URL | http://localhost:3001/mcp | No | | RPC_URL | https://soroban-testnet.stellar.org | No | | NETWORK_PASSPHRASE | Test SDF Network ; September 2015 | No | | TEST_ADMIN_ADDRESS | — | Yes for write tests | | TEST_SECRET_KEY | — | Yes for write tests |


Development

npm run build          # compile to dist/
npm run dev            # watch mode
npm test               # unit tests (90 tests, no server required)
npm run lint           # ESLint
npm run format         # Prettier --write
npm run format:check   # Prettier --check (used in CI)
npm run typecheck      # tsc --noEmit

License

MIT