VoidnetVoid Docs
MCP

MCP Publisher Guide

Build, deploy, and publish MCP servers on the Voidnet Console platform

This guide covers everything you need to build, deploy, and publish an MCP server on Voidnet Console — from server implementation to Stripe payout configuration.


Overview

A publisher is a developer who builds and hosts an MCP server and makes it available to buyers through the Voidnet Console marketplace. The Voidnet proxies buyer requests to your server, enforces rate limits and metering, and handles billing. Your server only needs to implement the standard MCP protocol and validate the publisher API key.

Your Apps / AI (Key or OAuth Token)  →  Voidnet  →  Your MCP Server

                                          Publisher API Key
                                          (always vnp-sk-*)

Buyers authenticate to the Voidnet using either an API key (vnb-sk-*) or an OAuth 2.0 access token (JWT). The Voidnet validates their identity, then proxies the request to your server with your publisher API key (vnp-sk-*) in the Authorization header. Your server never sees the buyer's credentials — only the publisher key you already trust.


Publisher API Key

Every published app gets a publisher API key. This key is generated in the Voidnet Console when you publish an app and is shown only once — save it securely.

vnp-sk-27fc6268dc64a9ba2c4cb92489e9175c9bf404e260beb268b976f1a56582eff3

How it's used

When the Voidnet proxies a request to your server, it sends the key in the Authorization header:

Authorization: Bearer vnp-sk-27fc6268...

Your server must validate this header on every request and reject invalid or missing keys with a 401 response.

Key lifecycle

  • Generate — Created when you publish an app or manually from the Console
  • Revoke — You can revoke keys at any time; revoked keys immediately stop working
  • Rotate — Generate a new key, update your server, then revoke the old one
  • Storage — Keys are encrypted at rest in the Voidnet database

Server Requirements

Your MCP server must meet these requirements to work with Voidnet Console:

1. HTTPS

All communication with the Voidnet is over HTTPS. You'll need a valid TLS certificate.

2. Single MCP endpoint

The Voidnet sends all MCP requests to a single endpoint on your server — typically /mcp. This single endpoint handles all MCP methods.

3. Protocol version

Voidnet Console requires MCP protocol version 2026-07-28 (stateless). Your server should:

  • Accept the MCP-Protocol-Version: 2026-07-28 header
  • Return 2026-07-28 in the server/discover response
  • Reject unsupported versions with 400 and error code -32022

No sessions, no initialize handshake, no GET/DELETE endpoints. The legacy 2025-11-25 stateful model is not supported.

4. Authentication

Your server must validate the Authorization: Bearer <key> header and return 401 for invalid or missing keys. See Authentication below.

5. Required headers

The Voidnet forwards these headers on every request:

HeaderDescription
MCP-Protocol-Version2026-07-28 (gateway's protocol version)
Mcp-MethodJSON-RPC method name (e.g., tools/call)
Mcp-NameTool/resource/prompt name for routing
AuthorizationBearer vnp-sk-... (your publisher API key)

Your server must validate that MCP-Protocol-Version matches the _meta.protocolVersion in the JSON-RPC request body. Mismatch returns 400 with error code -32020 (HeaderMismatch).


Authentication

The authentication contract between the Voidnet and your server is:

Request:
  Authorization: Bearer vnp-sk-27fc6268...

Response (valid key):
  200 OK

Response (invalid or missing key):
  401 Unauthorized
  {
    "jsonrpc": "2.0",
    "error": {
      "code": -32001,
      "message": "Unauthorized",
      "data": "Invalid or missing publisher API key"
    }
  }

Validate on every request. If the key is invalid, return 401 immediately — do not process the MCP request.


MCP Protocol (stateless 2026-07-28)

Your server must implement the following MCP 2026-07-28 methods. No initialize handshake, no sessions, no MCP-Session-Id headers.

server/discover (REQUIRED)

The Voidnet calls server/discover to discover your server's capabilities. This replaces the legacy initialize handshake.

Request:

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "server/discover"
}

Response:

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "supportedVersions": ["2026-07-28"],
    "capabilities": {
      "tools": { "listChanged": true },
      "resources": { "subscribe": false, "listChanged": true },
      "prompts": { "listChanged": true }
    },
    "serverInfo": { "name": "my-server", "version": "1.0.0" },
    "instructions": "Optional usage instructions for your server"
  }
}
  • supportedVersions: Must include 2026-07-28
  • capabilities: Your server's capabilities (tools, resources, prompts)
  • serverInfo: Your server's name and version
  • instructions: Optional human-readable instructions

tools/list

Returns the tools your server provides:

{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/list"
}

Response (include ttlMs and cacheScope for spec-sanctioned caching):

{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "tools": [
      {
        "name": "my_tool",
        "description": "Does something useful",
        "inputSchema": {
          "type": "object",
          "properties": {
            "input": { "type": "string", "description": "The input value" }
          },
          "required": ["input"]
        }
      }
    ],
    "ttlMs": 300000,
    "cacheScope": "private"
  }
}

tools/call

Invokes a tool with the provided arguments:

{
  "jsonrpc": "2.0",
  "id": 3,
  "method": "tools/call",
  "params": {
    "name": "my_tool",
    "arguments": { "input": "hello" }
  }
}

Response (include resultTypecomplete or input_required for MRTR):

{
  "jsonrpc": "2.0",
  "id": 3,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "Result: hello"
      }
    ],
    "resultType": "complete"
  }
}

resources/list (optional)

{
  "jsonrpc": "2.0",
  "id": 4,
  "method": "resources/list"
}

Response:

{
  "jsonrpc": "2.0",
  "id": 4,
  "result": {
    "resources": [
      {
        "uri": "data://my-resource",
        "name": "My Resource",
        "description": "A data resource",
        "mimeType": "text/plain"
      }
    ],
    "ttlMs": 300000,
    "cacheScope": "private"
  }
}

resources/read (optional)

{
  "jsonrpc": "2.0",
  "id": 5,
  "method": "resources/read",
  "params": { "uri": "data://my-resource" }
}

Response:

{
  "jsonrpc": "2.0",
  "id": 5,
  "result": {
    "uri": "data://my-resource",
    "mimeType": "text/plain",
    "text": "Resource content here",
    "ttlMs": 300000,
    "cacheScope": "private"
  }
}

prompts/list (optional)

{
  "jsonrpc": "2.0",
  "id": 6,
  "method": "prompts/list"
}

prompts/get (optional)

{
  "jsonrpc": "2.0",
  "id": 7,
  "method": "prompts/get",
  "params": { "name": "system_instruction", "arguments": { "task": "write a summary" } }
}

subscriptions/listen (optional)

For servers that support change notifications. The Voidnet calls this once per subscription:

{
  "jsonrpc": "2.0",
  "id": 8,
  "method": "subscriptions/listen",
  "params": { "notifications": [] }
}

Acknowledge with notifications/subscriptions/acknowledged.


Server Verification

When you publish an app in the Console, Voidnet performs a live verification of your server:

  1. The Voidnet calls server/discover to discover your server's capabilities
  2. It calls tools/list, resources/list, and prompts/list
  3. The discovered capabilities are cached and displayed in the Console

Your server must implement server/discover and the list methods above. Verification determines which tools, resources, and prompts buyers will see.

You can re-verify your server at any time from the app detail page — click Refresh in the MCP Configuration section.


Domain Verification

Before you can publish an app, you must verify ownership of your server's domain:

  1. Initiate — In the Console, start domain verification. A unique verification token is generated.

  2. Place the file — Host the verification file at your domain's root or /.well-known/ directory:

    https://your-domain.com/voidnet-site-verification-<token>.html
  3. Verify — Voidnet Console fetches the file and confirms the content matches the expected token.

  4. Done — The domain is marked as verified and can be used for publishing.

One verified domain can host multiple apps.


Tier Configuration

Every published app has configurable tiers:

Free Tier

  • Meter limit — Max requests per month before the free tier is exhausted
  • Rate limit per minute — Max requests per minute
  • Rate limit per day — Max requests per day
  • Same limits as free tier but higher
  • Price — Set in USD with monthly, yearly, or one-time billing
  • Requires a connected Stripe account (see below)

How tiers work

  1. A buyer discovers your app in the marketplace
  2. If you offer a free tier, the buyer can start using it immediately
  3. If you offer a paid tier, the buyer purchases a subscription
  4. The Voidnet enforces rate limits and metering based on the buyer's tier
  5. When a free tier buyer exhausts their meter, they're prompted to upgrade

Stripe Connect Billing

To accept payments, you need a Stripe Express account connected to Voidnet Console:

  1. Connect — In the Console Payments page, click "Connect with Stripe"
  2. Onboard — Complete Stripe Express onboarding (identity verification, bank account)
  3. Sync — Your Stripe account status appears in the Console

Payout structure

ComponentShare
Publisher payout80% (default)
Voidnet Console platform fee20% (default)

Per-app — the default 80/20 can be lowered on individual apps (e.g., founding-member 85/15 or negotiated deals) via Console. The split is locked at purchase time. Payouts are sent directly by Stripe to your connected bank account on Stripe's standard payout schedule.

Pricing model

  • The Voidnet uses the Marketplace / Direct Charges model
  • You are the merchant of record for each transaction
  • The Voidnet creates a PaymentIntent with an application_fee for Voidnet Console's per-app fee (default 20%)
  • You receive 80% (default) minus any Stripe processing fees — rounded: fee = floor(amount*percent/100), payout = amount - fee so the two always sum to gross exactly (platform absorbs the cent)

Testing Your Server

1. Local development

Run your MCP server locally with HTTPS (self-signed certs are fine for testing). The Console's verify-server tool supports localhost with rejectUnauthorized: false for external publishers. Internally, Voidnet engineers use http on localhost — external publishers must use https.

2. Verify server in Console

From the Publish MCP Interface page:

  1. Enter your App Name (unique per publisher)
  2. Select a verified domain — the server URL is auto-populated from your domain
  3. Add a description
  4. Click Verify Server
  5. The Console calls server/discovertools/listresources/listprompts/list and displays your capabilities
  6. Save your publisher API key — shown once in the success dialog
  7. Configure tiers and click Submit to publish

3. Test with curl

curl -X POST https://api.openvoidnet.com/v1-beta/mcp/your-username/your-app \
  -H "Authorization: Bearer vnb-sk-your-buyer-key" \
  -H "Content-Type: application/json" \
  -H "MCP-Protocol-Version: 2026-07-28" \
  -H "Mcp-Method: tools/list" \
  -H "Mcp-Name: tools/list" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/list"
  }'

Going Live

Prerequisites checklist

  • Server is running HTTPS with a valid certificate
  • Server validates the Authorization: Bearer <key> header
  • Server implements MCP 2026-07-28 protocol (stateless)
  • Server implements server/discover (required)
  • Server exposes at least one tool
  • Domain is verified
  • Stripe Express account is connected (if offering paid tier)

Publishing flow

  1. Go to Console → Publish Apps and select AI Tools (MCP)
  2. Enter your App Name (unique per publisher, like a GitHub repo name)
  3. Select a verified domain to set the server URL
  4. Add a description
  5. Click Verify Server — the Voidnet probes your MCP server with server/discover and discovers its capabilities
  6. Review the detected tools, resources, and prompts
  7. Save your publisher API key (vnp-sk-*) — it's shown only once in the success dialog. Without it, the Voidnet cannot proxy requests to your server
  8. Close the dialog and configure tiers — free, paid, or both, with rate limits and metering limits
  9. Click Submit — your app is created as a draft and immediately published to the marketplace
  10. Your app now appears in Published Apps and the Voidnet Console Marketplace

Drafts

Every app starts as a draft when you first verify the server. Drafts appear in the Publish Apps page under "Continue your drafts" and can be resumed at any time. Publishing (Submitting) sets the status to published, making the app visible in the marketplace.

StatusVisible in marketplaceModifiable
draftNoYes — can edit tiers, re-verify, or delete
publishedYesYes — can update, re-verify, or delete

Managing your app

  • View details — Console → Published Apps → click your app
  • API keys — Generate and revoke keys from the API Keys tab on the app detail page
  • Logo — Upload or remove an app logo from the app detail page (400×400 WebP, PNG, JPEG, or AVIF)
  • Re-verify — Refresh server capabilities from the Overview tab on the app detail page
  • Analytics — Track requests and revenue per app
  • Delete — Remove your app (irreversible) from either the Published Apps list or Publish Apps page

Reference: Server skeleton

A minimal MCP server in TypeScript/Node.js (stateless 2026-07-28):

import { createServer } from "https";
import { readFileSync } from "fs";

const PUBLISHER_API_KEY = process.env.PUBLISHER_API_KEY;

const server = createServer(
  { key: readFileSync("./key.pem"), cert: readFileSync("./cert.pem") },
  async (req, res) => {
  if (req.method === "GET" || req.method === "DELETE") {
    res.writeHead(405, { "Content-Type": "application/json" });
    res.end(JSON.stringify({ jsonrpc: "2.0", error: { code: -32601, message: "Method not allowed" } }));
    return;
  }

  // 1. Validate publisher API key
  const expected = `Bearer ${PUBLISHER_API_KEY}`;
  if (PUBLISHER_API_KEY && req.headers["authorization"] !== expected) {
    res.writeHead(401, { "Content-Type": "application/json" });
    res.end(JSON.stringify({ jsonrpc: "2.0", error: { code: -32001, message: "Unauthorized" } }));
    return;
  }

  // 2. Validate MCP-Protocol-Version header
  const protocolVersion = req.headers["mcp-protocol-version"] as string | undefined;
  if (protocolVersion !== "2026-07-28") {
    res.writeHead(400, { "Content-Type": "application/json" });
    res.end(JSON.stringify({ jsonrpc: "2.0", error: { code: -32022, message: "UnsupportedProtocolVersion", data: { supported: ["2026-07-28"], requested: protocolVersion } } }));
    return;
  }

  // 3. Parse MCP JSON-RPC and validate _meta protocolVersion matches header
  const body = await parseBody(req);
  const metaVersion = body.params?._meta?.protocolVersion ?? body.params?._meta?.["io.modelcontextprotocol/protocolVersion"];
  if (metaVersion && metaVersion !== protocolVersion) {
    res.writeHead(400, { "Content-Type": "application/json" });
    res.end(JSON.stringify({ jsonrpc: "2.0", error: { code: -32020, message: "HeaderMismatch" } }));
    return;
  }

  // 4. Dispatch — you must handle these for verification and store:
  //    server/discover, tools/list, tools/call, resources/list, resources/read,
  //    resources/templates/list, prompts/list, prompts/get, subscriptions/listen
  const response = await handleMcpRequest(body); // include ttlMs/cacheScope + resultType: "complete"

  res.writeHead(200, { "Content-Type": "application/json" });
  res.end(JSON.stringify(response));
});

server.listen(PORT, () => console.log(`MCP listening on :${PORT}`));

Direct publisher test (no gateway):

curl -X POST http://localhost:3051/mcp \
  -H "Authorization: Bearer vnp-sk-test..." \
  -H "MCP-Protocol-Version: 2026-07-28" \
  -H "Mcp-Method: server/discover" -H "Mcp-Name: server/discover" \
  -d '{"jsonrpc":"2.0","id":1,"method":"server/discover"}'

Coming next: @openvoidnet/mcp-sdk will wrap McpServer + HttpTransport so you don't copy this skeleton.

See the Voidnet test server for a complete reference implementation.

On this page