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

@vanilla-game/muninn-mcp

v0.6.0

Published

Read-only MCP server for one Muninn Minecraft backend

Readme

muninn-mcp

Read-only Model Context Protocol server for one Muninn plugin Minecraft backend.

One process always represents exactly one game backend. It has one Muninn base URL, one backend bearer token, and one discovered server_id. Run a separate process for each backend because installed plugins and capabilities can differ.

The default MCP transport is the official stateful Streamable HTTP transport from the MCP TypeScript SDK. Stdio remains available as a fallback.

Requirements

  • Node.js 20 or newer
  • a reachable Muninn plugin HTTP API
  • the backend bearer token from plugins/Muninn/config.yml

This MCP server requires Muninn plugin v0.5.0 or newer. Its OpenAPI document defines CoreProtect block actions and summaries, exact next_offset pagination, and CMI player search/sort parameters. The OpenAPI document shipped by the running plugin remains the backend contract source of truth.

HTTP-first start

npm ci
npm run build

MUNINN_BASE_URL=http://127.0.0.1:8781 \
MUNINN_AUTH_TOKEN='<Muninn backend token>' \
MUNINN_EXPECTED_SERVER_ID=survival \
MUNINN_MCP_AUTH_TOKEN='<separate MCP client token>' \
npm start

Defaults:

  • transport: http
  • bind: 127.0.0.1
  • port: 3000
  • MCP endpoint: http://127.0.0.1:3000/mcp
  • readiness endpoint: http://127.0.0.1:3000/healthz

The incoming MUNINN_MCP_AUTH_TOKEN is deliberately separate from MUNINN_AUTH_TOKEN. The first protects MCP clients → this process; the second protects this process → the Minecraft backend. Never reuse them.

An HTTP MCP client connects to the URL and supplies the static token:

{
  "mcpServers": {
    "muninn-survival": {
      "url": "http://127.0.0.1:3000/mcp",
      "headers": {
        "Authorization": "Bearer <MUNINN_MCP_AUTH_TOKEN>"
      }
    }
  }
}

The exact client configuration envelope is client-specific; the URL, standard Authorization header, and Streamable HTTP protocol are not.

Readiness needs no token and exposes no configuration secrets:

curl http://127.0.0.1:3000/healthz

The process first discovers the backend through /health and /capabilities. It only starts listening after backend identity and capabilities have passed fail-fast validation.

Stdio fallback

MUNINN_TRANSPORT=stdio \
MUNINN_BASE_URL=http://127.0.0.1:8781 \
MUNINN_AUTH_TOKEN='<Muninn backend token>' \
MUNINN_EXPECTED_SERVER_ID=survival \
npm start

Example stdio client entry:

{
  "mcpServers": {
    "muninn-survival": {
      "command": "node",
      "args": ["/absolute/path/to/muninn-mcp/dist/index.js"],
      "env": {
        "MUNINN_TRANSPORT": "stdio",
        "MUNINN_BASE_URL": "http://127.0.0.1:8781",
        "MUNINN_AUTH_TOKEN": "<Muninn backend token>",
        "MUNINN_EXPECTED_SERVER_ID": "survival"
      }
    }
  }
}

Configuration

MCP transport

| Variable | Default | Meaning | |---|---:|---| | MUNINN_TRANSPORT | http | http or stdio. | | MUNINN_MCP_BIND | 127.0.0.1 | HTTP listener hostname or IP. | | MUNINN_MCP_PORT | 3000 | HTTP listener port, 1–65535. | | MUNINN_MCP_PATH | /mcp | Exact Streamable HTTP endpoint path. | | MUNINN_MCP_AUTH_TOKEN | unset | Incoming static bearer. Optional only on loopback; required for every non-loopback HTTP bind. | | MUNINN_MCP_ALLOWED_HOSTS | loopback hosts | Comma-separated hostnames without ports. Required for wildcard binds such as 0.0.0.0. | | MUNINN_MCP_ALLOWED_ORIGINS | none | Exact comma-separated browser origins allowed for CORS. Browser Origin requests are rejected by default. |

For a private-network listener:

MUNINN_MCP_BIND=0.0.0.0 \
MUNINN_MCP_ALLOWED_HOSTS=minecraft-admin.internal,192.0.2.20 \
MUNINN_MCP_AUTH_TOKEN='<high-entropy token>' \
npm start

The built-in listener is plain HTTP. Do not expose it directly to the public internet. Keep it on loopback/private networking or place TLS and appropriate network controls in front of it.

Muninn backend

| Variable | Required | Default | Meaning | |---|---:|---:|---| | MUNINN_BASE_URL | yes | — | Backend origin or API root. A bare origin gets /api/v1/ appended. | | MUNINN_AUTH_TOKEN | yes | — | Bearer token accepted by the Muninn plugin. | | MUNINN_EXPECTED_SERVER_ID | no | — | Fail-fast backend identity pin; strongly recommended. | | MUNINN_TIMEOUT_MS | no | 15000 | Per-request timeout, 100–120000 ms. | | MUNINN_DEFAULT_PAGE_SIZE | no | 100 | Explicit default for paginated tools. | | MUNINN_MAX_PAGE_SIZE | no | 100 | MCP-side page cap, maximum 1000. | | MUNINN_MAX_LOOKUP_SECONDS | no | 2592000 | MCP-side CoreProtect time-window cap. | | MUNINN_MAX_RADIUS | no | 128 | MCP-side CoreProtect radius cap. |

Backend limits remain authoritative and may be stricter.

HTTP security and lifecycle

  • Host validation is port-independent and deny-by-default.
  • Browser requests with an Origin header are denied unless the exact origin is allowlisted. CORS never uses * and never enables credentials.
  • Incoming auth uses constant-time comparison of SHA-256 token digests.
  • Request bodies are parsed only after MCP authentication and are capped at 256 KiB.
  • Neither backend nor incoming bearer values are logged or returned in errors, including nested backend payloads.
  • Each initialize request gets a cryptographically random stateful MCP session, its own official StreamableHTTPServerTransport, and its own McpServer.
  • Subsequent POST/GET/DELETE requests require a valid Mcp-Session-Id.
  • HTTP DELETE terminates a session. SIGINT/SIGTERM stop accepting requests, close all active transports/SSE streams, and close the HTTP server.
  • Sessions are in memory and are not resumable across process restarts; clients initialize again after a restart.

Capability-aware tools

Only tools whose endpoint is present in an enabled module's capability report are registered. CoreProtect tools also require the corresponding feature flag. Restart the process after backend plugin/capability changes to refresh tools/list.

All tools are annotated read-only, non-destructive, and idempotent. The Paper batch endpoint uses HTTP POST but does not mutate game state.

Core and composite

  • server_status
  • investigate_block — bounded Paper/CoreProtect/WorldGuard context with independent probe results

CoreProtect primitives

  • coreprotect_block_lookup — bounded native CoreProtect lookup for block, interaction, and entity-kill events. Include/exclude arrays become single CSV query parameters and are applied by CoreProtect before pagination. The legacy scalar player remains supported.
  • coreprotect_block_summary — bounded count and optional grouping by PLAYER, MATERIAL, and/or ACTION; reports the scan limit and explicit complete/truncated metadata so a partial matched value is never mistaken for a total.
  • coreprotect_container_lookup
  • coreprotect_item_lookup
  • coreprotect_inventory_lookup
  • coreprotect_chat_lookup
  • coreprotect_command_lookup
  • coreprotect_session_lookup
  • coreprotect_sign_lookup
  • coreprotect_username_lookup

Paper primitives

  • paper_get_container
  • paper_batch_containers (1–64 locations)
  • paper_get_player_inventory
  • paper_get_player_ender_chest
  • paper_get_player_state — includes typed Unix-second first_played and ISO first_played_iso fields for both online and known offline players
  • paper_get_player_stats
  • paper_list_players
  • paper_get_server_info
  • paper_list_entities
  • paper_get_block

CMI primitives

  • cmi_list_players — optional case-insensitive query, sort (name/last_login/last_logoff), and order (asc/desc) are applied before pagination
  • cmi_get_player — includes the typed ban.active flag when CMI exposes a ban record, distinguishing an active ban from historical/raw metadata
  • cmi_get_player_homes
  • cmi_list_warps
  • cmi_list_jails

WorldGuard primitives

  • worldguard_list_regions
  • worldguard_get_region
  • worldguard_regions_at
  • worldguard_flags_at

ProtectionStones primitives

  • protectionstones_region_at — exact world/x/y/z lookup returning the nearest claim or region: null; claim data includes owners, members, protection-block, home, parent, hidden, and read-only commerce state
  • protectionstones_player_regions — bounded claims for an exact nickname or UUID, optionally restricted to one loaded world; include_member defaults to true

| MCP tool | Backend endpoint | Required input | Optional input | |---|---|---|---| | protectionstones_region_at | GET /api/v1/protectionstones/region-at | world, x, y, z | — | | protectionstones_player_regions | GET /api/v1/protectionstones/player/{player}/regions | exact nickname or UUID in player | world, include_member (default true), offset, limit |

Paginated tools always send explicit bounded offset/limit values. CoreProtect lookup responses return the backend's exact has_more marker; when has_more=true, pass next_offset unchanged to the follow-up call. It is absent on the final page. Other paginated endpoints retain their documented pagination shape.

CoreProtect block lookup filters

| MCP input | Backend query | Semantics | |---|---|---| | player: string | player | Legacy scalar actor include filter. Mutually exclusive with players; the only actor filter allowed with at=true. | | players: string[] | players CSV | 1–64 included actors. Mutually exclusive with player. | | exclude_players: string[] | exclude_players CSV | 1–64 excluded actors. Does not provide the required player/area scope by itself. | | materials: string[] | materials CSV | 1–64 included Bukkit block materials. | | exclude_materials: string[] | exclude_materials CSV | 1–64 excluded Bukkit block materials. Does not provide the required player/area scope by itself. | | entities: string[] | entities CSV | 1–64 included Bukkit entity types. Implies ENTITY_KILL when actions is omitted. | | exclude_entities: string[] | exclude_entities CSV | 1–64 excluded Bukkit entity types. Does not provide the required player/area scope by itself. | | actions | actions CSV | One or more of BLOCK_BREAK, BLOCK_PLACE, INTERACTION, ENTITY_KILL. | | world, x, y, z, radius | same names | Existing bounded location filter; world and all coordinates are supplied together. | | at: true | at=true | Exact-block history. Only scalar player, location, radius, and pagination inputs are allowed. | | offset, limit | same names | Existing bounded pagination. |

Without at=true, every request requires either concrete scalar player / players, or complete world/x/y/z coordinates with radius. Material, entity, action, and exclusion filters refine that player/area scope; they never enable an unrestricted global lookup because the public CoreProtect API v12 does not support one. The special #global actor also cannot replace the required scope. Array values must be unique, non-empty, comma-free, and contain at most 64 entries.

The response remains the existing BlockEvent page. entity_type can be absent for a generic filtered entity kill when the backend uses CoreProtect API v12.

For example, this finds selected ore events for one player while keeping the material restriction server-side:

{
  "seconds": 86400,
  "player": "Steve",
  "materials": ["DIAMOND_ORE", "DEEPSLATE_DIAMOND_ORE"],
  "actions": ["BLOCK_BREAK"],
  "offset": 0,
  "limit": 100
}

This example combines multiple actors with native exclusions and entity/action filters:

{
  "seconds": 86400,
  "players": ["Steve", "Alex"],
  "exclude_players": ["Automation"],
  "materials": ["DIAMOND_ORE"],
  "exclude_materials": ["STONE"],
  "entities": ["ZOMBIE", "SKELETON"],
  "exclude_entities": ["ARMOR_STAND"],
  "actions": ["BLOCK_BREAK", "ENTITY_KILL"],
  "offset": 0,
  "limit": 100
}

For a smaller answer when only totals are needed, use coreprotect_block_summary and inspect completeness before treating matched as a total:

{
  "seconds": 86400,
  "player": "Steve",
  "materials": ["DIAMOND_ORE", "DEEPSLATE_DIAMOND_ORE"],
  "actions": ["BLOCK_BREAK"],
  "group_by": ["MATERIAL", "ACTION"],
  "max_scan_events": 10000,
  "include_rolled_back": false
}

Errors

Muninn envelopes become readable MCP tool errors with stable codes, safe details, HTTP status, backend ID, guidance, and retryability where applicable: UNAUTHORIZED, NOT_FOUND, BAD_REQUEST, SYNC_TIMEOUT, MODULE_DISABLED, FEATURE_UNAVAILABLE, LIMIT_EXCEEDED, PLAYER_OFFLINE, and INTERNAL.

Transport-side errors include TIMEOUT, NETWORK_ERROR, CANCELLED, INVALID_RESPONSE, and SERVER_ID_MISMATCH.

HTTP routing/auth errors use bounded JSON/JSON-RPC bodies and never echo Host, Origin, authorization values, request bodies, or internal exceptions.

Verification

npm ci
npm run check
npm test

The suite covers:

  • HTTP-default and stdio process startup/shutdown;
  • official StreamableHTTPClientTransport initialize, initialized notification, tools/list, tools/call, SSE, and DELETE lifecycle;
  • incoming auth 401, Host/Origin/CORS policy, invalid methods and sessions;
  • non-zero invalid configuration;
  • recursive secret redaction;
  • all 31 atomic tool-to-endpoint mappings and capability filtering.

Real plugin Docker harness

The cross-project test starts the adjacent real Paper+CoreProtect+CMI+WorldGuard fixture, starts the built HTTP MCP process, connects through the official Streamable HTTP client, and removes all temporary processes/containers. Its field evaluation covers a server overview, the Paper first_played profile contract, CMI fuzzy search/sort, an action-filtered rare material summary, native CoreProtect actor/entity include-exclude filters, and a combined WorldGuard-region/CoreProtect-session scenario. The test prints MUNINN_FIELD_EVAL JSON with actual MCP tool call counts and UTF-8 output bytes per scenario and in total:

npm run test:e2e:harness

In this monorepo the plugin is discovered automatically at ../plugin:

MUNINN_PLUGIN_DIR=/absolute/path/to/plugin npm run test:e2e:harness

An already-running backend can be tested directly:

MUNINN_E2E_BASE_URL=http://127.0.0.1:8781 \
MUNINN_E2E_AUTH_TOKEN='<backend token>' \
MUNINN_E2E_SERVER_ID=survival \
npm run test:e2e

The stdio fallback remains covered by the normal test suite.

Releases and npm publication

Release Please watches conventional commits on main, maintains a release PR, updates CHANGELOG.md, package.json, package-lock.json, and the root release manifest, then creates an mcp-vX.Y.Z GitHub Release when that release PR is merged. The same workflow checks and tests the released commit before publishing @vanilla-game/muninn-mcp as a public npm package with provenance.

The package is published through npm Trusted Publishing. Configure the publisher for:

  • GitHub organization: Vanilla-Game
  • repository: muninn
  • workflow filename: release-please.yml
  • allowed action: npm publish

The workflow uses GitHub OIDC through id-token: write and does not accept a long-lived npm token. The npm package repository URL must continue to match this monorepo and its mcp directory.

By default Release Please uses the workflow's GITHUB_TOKEN. An optional RELEASE_PLEASE_TOKEN GitHub secret can supply a GitHub App or fine-grained PAT when repository policy requires release PR events to trigger other workflows.

License

MIT