About
Nvnm — Model Context Protocol server
README
CI License: Apache 2.0 Latest Release Cosign Signed
A typed Model Context Protocol bridge between AI agents and the NVNM Chain (Inveniam's L2 on MANTRA). It exposes 23 curated tools — EVM reads, anchor reads, prepare-sign-submit writes, and guided onboarding — with normalized responses, per-tool authorization, and zero key custody. Intended for application developers, LLM-agent authors, and pipeline operators who need a stable, audited surface against an EVM chain rather than raw JSON-RPC.
A Go-based Model Context Protocol (MCP) server that exposes the NVNM Chain (Inveniam's L2 on MANTRA) through a curated set of typed tools, with special emphasis on the chain's built-in anchoring interface.
This is not a generic JSON-RPC passthrough. It provides stable, typed, high-value MCP tools with normalized responses designed for both human and LLM consumers.
Request Flow
The HTTP transport layers defense-in-depth middleware around the MCP SDK. Order is outermost first; each layer can short-circuit before the request reaches a tool handler.
┌─────────────────────────────────────────────┐
MCP client ──▶ │ originGuard DNS-rebinding defense │
│ failGuarded pre-auth IP rate limit │
│ limitRequestBody body size cap │
│ AuthMiddleware apikey or fusionauth │
│ rateLimitMiddleware per-client bucket │
│ mcp.Server (SDK) JSON-RPC dispatch │
│ tool handler ABI encode / decode │
│ evm client retry, breaker, trace │
└──────────────────────┬──────────────────────┘
▼
EVM JSON-RPC
(https://evm.testnet.nvnmchain.io)
Source: internal/mcp/server.go.
Documentation
This README is the technical entry point. For deeper context, follow the links below.
OSS foundation (repo root):
| File | Purpose |
|---|---|
| LICENSE | Apache 2.0 license text |
| NOTICE | Required attribution notice |
| CONTRIBUTING.md | Contribution workflow, DCO, branch / PR conventions |
| CODE_OF_CONDUCT.md | Community standards |
| SECURITY.md | Vulnerability disclosure policy |
| CHANGELOG.md | Per-release notes (Keep a Changelog format) |
Technical references (docs/):
| File | Purpose |
|---|---|
| docs/ONBOARDING.md | New-contributor orientation — reading order, intentional design decisions, local quality gate (start here) |
| docs/DESIGN.md | Architecture decisions; multi-chain deployment model; target-chain reference |
| docs/TOOL_REFERENCE.md | Per-tool schema reference for all 21 MCP tools |
| docs/METAMASK_GUIDE.md | Signing and submitting anchor writes with MetaMask (step-by-step) |
| docs/standards/CODING_STANDARDS.md | Go coding standards and conventions for contributors |
| docs/TESTING.md | Layered testing strategy — unit, golden, integration, HTTP E2E, load, and Docker smoke |
| docs/RUNBOOK.md | Operational guide — startup, env-var migration, admin REST API |
| docs/INCIDENT_RUNBOOK.md | Per-alert investigation playbook; what to do when each Prometheus rule fires |
| docs/SECURITY_AUDIT.md | Frozen-snapshot security assessment with remediation results |
| docs/OWASP_AUDIT.md | OWASP Top 10:2021 audit — per-category findings and remediation status |
| docs/SECURITY_CONSUMER_GUIDANCE.md | Threats that fall on the consuming LLM agent / application, not the server |
| docs/DATA_HANDLING.md | Privacy-by-design technical reference (what is and isn't stored) |
| docs/NVNM_MCP_Privacy_Policy_Jul_2026.pdf | Counsel-finalized Privacy Policy for the hosted Service (published legal artifact) |
| docs/TERMS.md | Terms of Service for the hosted Service (Apache 2.0 governs the Software) |
| docs/KEY_CUSTODY_THREAT_MODEL.md | Rationale for the zero-key-custody design — no agent-mediated signing |
| docs/LICENSE_EXCEPTIONS.md | Dependency-license policy and recorded GPL/LGPL/AGPL exception dispositions |
What this server is not
Cold-reader assumptions to head off up front. The server is deliberately scoped narrow.
- Not a chain node. The server talks JSON-RPC to an upstream EVM RPC endpoint. It does not consensus, mine, or hold state beyond connection caches.
- Not a wallet. No private keys are ever held server-side. Write tools build complete unsigned transactions and return both a
raw_tx(for HSM / CLI signers) and awallet_tx_request(for MetaMask / EIP-1193 wallets); the caller signs and broadcasts. - Not a custodian or escrow. The server stores no per-user balance, no document content, no off-chain user records. The only persistent state is the API key store (hashed at rest). All "onboarding state" surfaced by the wizard tools is derived from on-chain balance + nonce at call time.
- Not an orchestrator. Tools return a
next_actionshint array so agents can chain calls themselves. The server does not call other tools internally and does not run multi-step flows on the caller's behalf.
Target Chain
| Property | Value (testnet) |
|---|---|
| Network | NVNM Chain -- Inveniam L2 (MANTRA-secured consumer chain) |
| EVM chain ID | 787111 (0xc02a7) |
| Cosmos chain ID | nvnm-testnet-1 |
| Native token | mantraUSD |
| Gas token | wmantraUSD (wrapped mantraUSD, held in EVM wallets) -- pays gas fees |
| EVM RPC | https://evm.testnet.nvnmchain.io |
| Cosmos RPC | https://rpc.testnet.nvnmchain.io |
| EVM explorer | https://explorer.evm.testnet.nvnmchain.io |
| MCP service (hosted) | https://mcp-testnet.nvnmchain.io (testnet); https://mcp.nvnmchain.io (mainnet) |
| Anchor precompile | 0x0000000000000000000000000000000000000A00 |
Mainnet identifiers (EVM chain ID
1611, Cosmosnvnm-1,*.nvnmchain.ioendpoints) and the full testnet+mainnet reference live in docs/DESIGN.md § Target Chain. The server runs as one instance per network.
Status
Current release: v1.0.0-rc13. The server is feature-complete and security-hardened — 21 typed tools (EVM reads, anchor reads, prepare-sign-submit writes, guided onboarding) with provider-agnostic auth (API keys or FusionAuth JWTs), per-tool RBAC, optional keyless reads, pre-auth and per-client rate limiting, DNS-rebinding defense, operator-configured data-retention purge, an OWASP Top-10 self-audit, and SBOM + Cosign-signed multi-arch images. See CHANGELOG.md for the full release history and docs/SECURITY_AUDIT.md for the security assessment.
HTTP transport supports two auth providers (API keys or FusionAuth JWTs) with per-client identity flowing into all authenticated audit logs and OTel spans. Under keyless reads (MCP_KEYLESS_READS=true, the Inveniam-hosted default) only evm_send_raw_transaction authenticates and carries a per-client identifier; the anchor_prepare_* tools are auth-exempt and anonymous reads carry no client_id. API keys are stored sha256-hashed at rest and indexed by hash in memory (Phase 8.6); the validator compares hash bytes under constant time and flattens hit/miss timing with a placeholder compare on the miss path (Phase 8.7). A pre-auth IP failure-rate limiter throttles credential stuffing before the auth check runs; per-client MCP rate limiting (post-auth) returns HTTP 429 when exceeded. Per-tool authorization (RBAC) gates each handler on reader / writer / admin / automation roles. Origin-header validation (Phase 8.5) provides DNS-rebinding defense at the outermost middleware position; allowlist via NVNM_ALLOWED_ORIGINS. Write access is gated by RBAC role and ENABLE_WRITE_TOOLS; obtaining human confirmation before submitting a signed transaction is the caller/agent's responsibility (stated in the server's initialize instructions). The signature remains the security boundary — the server holds no keys and cannot alter a signed transaction. A dedicated admin REST API (default-bound to 127.0.0.1:8081) enables runtime key management without server restarts.
Write tools construct complete unsigned transactions with both raw_tx (for HSM/CLI signers) and wallet_tx_request (for MetaMask / EIP-1193 wallets); private keys never touch the server -- see Write Architecture. Phase 8.4 made EIP-1559 (type-2) the default transaction format; callers that need legacy type-0 set prefer_legacy_tx: true on the prepare-tool input. Every tool response carries a next_actions hint array (Phase 8.3) so agents can chain calls from response-embedded affordances rather than server-side orchestration. Every tool carries an MCP ToolAnnotations payload (Phase 8.2) so clients can tell read-only tools from state-changing ones without inferring spec defaults. Five Phase 8.8 onboarding tools (nvnm_overview, wallet_status, nvnm_setup_wizard, nvnm_setup_verify_hash, nvnm_setup_verify_signature) walk first-time agents through wallet generation, funding, and on-chain state derivation; the wizard's funded_active state is explicit that "has sent any transaction" is not the same as "has anchored" because the wizard reads only balance and nonce, never transaction contents. OpenTelemetry instrumentation provides traces, metrics, and health check endpoints -- see Observability.
The EVM RPC stack uses github.com/defiweb/go-eth (MIT) -- go-ethereum was removed in 2026-05-13 to comply with the project's dependency-license policy; see docs/LICENSE_EXCEPTIONS.md and docs/SECURITY_AUDIT.md for the migration record. Dependencies are vendored (vendor/) and CI builds with -mod=vendor for supply-chain safety.
Prerequisites
- Go 1.26+
- Access to the NVNM Chain EVM RPC endpoint
- (Optional)
golangci-lintfor linting - (Optional)
pre-commitfor git hooks - (Optional) Docker for containerized deployment
Quick Start
# Clone and enter the project
git clone https://github.com/NVNM-Chain/nvnm-mcp-server.git
cd nvnm-mcp-server
# Install dev tools and pre-commit hooks
make setup-dev
# See all available commands
make help
# Build
make build
# Run the test suite (unit + MCP E2E; hermetic, no chain access needed)
make test
# Verify the same coverage gate CI enforces (-race + >=80% total coverage)
make coverage-check
# Configure (minimum required)
export NVNM_EVM_RPC_URL=https://evm.testnet.nvnmchain.io
export NVNM_CHAIN_ID=787111
export ANCHOR_ABI_PATH=abi/anchoring.json
# Run (stdio transport -- for local MCP client integration)
make run
# Run (HTTP transport -- for remote/production deployment)
make run-http
Connect to an MCP client
Stdio (local):
{
"mcpServers": {
"nvnm-chain": {
"command": "/path/to/nvnm-mcp-server",
"args": ["--transport", "stdio"]
}
}
}
HTTP (remote):
# Server runs on :8080 by default
export MCP_HTTP_ADDR=:8080
./nvnm-mcp-server --transport http
Authentication (HTTP transport)
When using HTTP transport, authentication is strongly recommended. The server supports two auth providers, selected by AUTH_PROVIDER (default: apikey).
API Key Provider (default)
export AUTH_PROVIDER=apikey # default, can be omitted
# Create an API key for a client
make key-create NAME=my-agent
# List all keys
make key-list
# Configure the server to use keys:
export MCP_API_KEYS_FILE=.mcp-keys.json # Multi-key file (recommended)
# OR
export MCP_API_KEY=your-secret-key # Single key (dev/test only)
FusionAuth Provider (OAuth/JWT)
export AUTH_PROVIDER=fusionauth
export FUSIONAUTH_URL=https://auth.example.com
export FUSIONAUTH_APPLICATION_ID=your-app-uuid
# Optional:
export FUSIONAUTH_ISSUER=https://auth.example.com # defaults to FUSIONAUTH_URL
export JWT_CLOCK_SKEW=60s # default 60s
export JWT_ROLES_CLAIM=roles # default "roles"
The automation role grants write access alongside writer and admin; it is intended for unattended pipelines.
The authenticated client ID — the API key's label, or a keyed HMAC of the JWT sub (the raw sub, which is email-reversible, is never logged) — flows into audit logs and OTel spans for authenticated requests.
Setting up the FusionAuth side
To use the fusionauth provider, configure these in your FusionAuth instance (this is what an operator/identity admin sets up; the server only validates the resulting tokens):
- Create an Application. Its Application ID (UUID) becomes
FUSIONAUTH_APPLICATION_ID. FusionAuth issues access tokens withaudset to this ID, which the server validates against — so this lines up automatically. - Define these Application Roles (exact names — the server's RBAC checks them literally):
reader,writer,admin,automation. FusionAuth emits a user's application roles in the access-tokenrolesclaim once the user is registered to the Application. - Register consumers to the Application and assign roles:
reader— read tools onlywriter— reads + broadcast writes (evm_send_raw_transaction)admin— the above +anchor_prepare_grant_roleautomation— write access for unattended pipelines (same aswriter+adminfor broadcast access); assign only to non-interactive callers.
- JWKS is the standard
<FUSIONAUTH_URL>/.well-known/jwks.json(auto-discovered unlessFUSIONAUTH_JWKS_URLis set); ensure it is reachable from the server. - Clients obtain a JWT from FusionAuth (interactive login, or a client-credentials / Entity grant) and present it as
Authorization: Bearer <jwt>.
Gotchas:
- The
rolesclaim must actually populate. An emptyrolesarray makes RBAC a no-op — writes would not be role-gated. For machine-to-machine / Entity grants you may need a JWT-populate lambda to injectroles. MCP_CLIENT_ID_HMAC_KEYis required infusionauthmode (startup fails loud without it). It keys the one-way HMAC that turns the JWTsubinto the loggedclient_id, keeping the email-reversiblesubout of logs.- The server validates
iss(==FUSIONAUTH_ISSUER, defaultFUSIONAUTH_URL) andaud(==FUSIONAUTH_APPLICATION_ID) on every token; a mismatch is rejected.
Configuration
All configuration is via environment variables. No config files required.
Required
| Variable | Description |
|---|---|
NVNM_EVM_RPC_URL |
Primary EVM JSON-RPC endpoint |
NVNM_CHAIN_ID |
Expected chain ID (787111 for NVNM testnet, 1611 for mainnet) |
NVNM_CHAIN_ENVIRONMENT |
Chain environment label: testnet or mainnet. Required to disambiguate the per-instance chain pin; legacy INVENIAM_* env vars are hard-rejected at startup with a pointer to docs/RUNBOOK.md#env-var-migration |
Authentication (HTTP transport)
| Variable | Default | Description |
|---|---|---|
AUTH_PROVIDER |
apikey |
Auth provider: apikey or fusionauth |
MCP_API_KEYS_FILE |
(none) | Path to JSON key store file (API key mode). |
MCP_API_KEY |
(none) | Single API key for dev/test (API key mode). |
FUSIONAUTH_URL |
(none) | FusionAuth base URL (required for fusionauth mode). |
FUSIONAUTH_APPLICATION_ID |
(none) | FusionAuth application UUID (required for fusionauth mode). |
FUSIONAUTH_ISSUER |
FUSIONAUTH_URL |
Expected JWT issuer (FusionAuth mode). |
FUSIONAUTH_JWKS_URL |
auto | JWKS endpoint (defaults to FUSIONAUTH_URL/.well-known/jwks.json). |
JWT_CLOCK_SKEW |
60s |
Leeway for JWT expiry checks (FusionAuth mode). |
JWT_ROLES_CLAIM |
roles |
JWT claim name for roles (FusionAuth mode). |
When auth is configured, HTTP requests must include Authorization: Bearer <token>.
Admin Key Management API
| Variable | Default | Description |
|---|---|---|
ADMIN_API_KEY |
(none) | Admin bearer token. Enables the admin REST API on a separate port for runtime key CRUD. |
ADMIN_API_ADDR |
:8081 |
Admin API listen address. |
When set (with HTTP transport), a separate server exposes POST/GET/PATCH/DELETE /admin/keys for runtime key management. Changes take effect immediately. See docs/RUNBOOK.md for full endpoint reference.
Optional
| Variable | Default | Description |
|---|---|---|
NVNM_EVM_ARCHIVE_RPC_URL |
(none) | Archive node RPC for historical queries |
ANCHOR_ADDRESS |
0x0000000000000000000000000000000000000A00 |
Anchor precompile address |
ANCHOR_ABI_PATH |
(none) | Path to anchor ABI JSON file |
REQUEST_TIMEOUT |
15s |
Timeout for upstream RPC calls |
LOG_LEVEL |
info |
Log level: debug, info, warn, error |
ENABLE_WRITE_TOOLS |
false |
Enable write (prepare) tools |
MCP_RELAY_ALLOW_ANY |
false |
Authenticated-path escape hatch: when true, evm_send_raw_transaction skips the anchor-precompile relay scope and broadcasts to any destination. Default false pins the relay to the anchor precompile. No effect under keyless writes (and a boot error if combined with MCP_KEYLESS_WRITES=true). See Write Architecture. |
MCP_TRANSPORT |
stdio |
Transport: stdio or http |
MCP_HTTP_ADDR |
:8080 |
Listen address for HTTP transport |
OTLP_INSECURE |
false |
Use TLS for the OTLP gRPC connection. Set to true only for sidecar / localhost collectors that do not support TLS. |
WRITE_APPROVAL_DEFAULT |
— | Removed. Setting this env var aborts startup with a migration error. See docs/RUNBOOK.md#write-approval-removal. |
Observability
| Variable | Default | Description |
|---|---|---|
OTEL_EXPORTER_OTLP_ENDPOINT |
(none) | OTel Collector endpoint (e.g. localhost:4317). Enables OTLP trace + metric export |
OTEL_SERVICE_NAME |
nvnm-mcp-server |
Service name in traces and metrics |
ENABLE_PROMETHEUS |
true |
Expose /metrics endpoint on metrics port |
ENABLE_STDOUT_TELEMETRY |
false |
Dump spans/metrics to stderr (dev only) |
METRICS_ADDR |
:9090 |
Listen address for health + metrics endpoints |
Resilience
| Variable | Default | Description |
|---|---|---|
MCP_RATE_LIMIT |
60 |
Per-client MCP request rate limit (requests per second). Returns HTTP 429 when exceeded. |
MCP_RATE_BURST |
10 |
Per-client burst capacity for the MCP rate limiter. |
RPC_MAX_RETRIES |
3 |
Maximum retry attempts for transient RPC errors |
RPC_INITIAL_BACKOFF |
500ms |
Initial backoff duration between retries |
RPC_MAX_BACKOFF |
10s |
Maximum backoff duration between retries |
RPC_RATE_LIMIT |
100 |
Upstream RPC rate limit (requests per second) |
RPC_RATE_BURST |
20 |
Burst capacity for upstream RPC rate limiter |
CIRCUIT_BREAKER_THRESHOLD |
5 |
Consecutive failures to trip circuit breaker |
CIRCUIT_BREAKER_TIMEOUT |
30s |
Time in open state before half-open probe |
OTEL_TRACE_SAMPLE_RATIO |
1.0 |
Fraction of traces sampled (0.0-1.0) |
MCP Tools
23 tools in total. First-time agents should call nvnm_overview first; it returns the canonical 6-step journey across the rest of the surface.
Phase 8.8: Onboarding (5 tools)
| Tool | Description |
|---|---|
nvnm_overview |
Lobby tool. Chain identity, privacy-by-design property, 6-step agent journey, prereqs. No chain calls. |
wallet_status |
Balance + nonce for an address; three-state status (unfunded / funded_unused / funded_active). |
nvnm_setup_wizard |
Four-state guided onboarding with language-specific samples (Python / JS / Go) that store keys via keyring / .env / 0o600 files (never print them). |
nvnm_setup_verify_hash |
Stateless challenge: caller proves they can hash a per-address challenge. |
nvnm_setup_verify_signature |
Stateless challenge: caller proves they can EIP-191 sign the same challenge. |
Phase 1: Generic EVM
| Tool | Description |
|---|---|
evm_get_chain_id |
Chain ID, latest block number, RPC label |
evm_get_block |
Block by number or hash, with optional full transactions |
evm_get_transaction |
Transaction details by hash |
evm_get_transaction_receipt |
Receipt with status, gas, logs, created contract |
evm_get_balance |
Balance at address (wei + ether) |
evm_get_code |
Contract bytecode at address |
evm_get_logs |
Filtered event logs |
evm_call_contract |
Read-only contract call with ABI decoding |
Phase 2: Anchor Reads
| Tool | Description |
|---|---|
anchor_info |
Precompile config status: address, ABI loaded, method count |
anchor_get_registry |
Fetch a registry by numeric ID (ID-only, single-valued) |
anchor_get_registries |
Paginated list of registries, optionally filtered by name (client-side scan finds every match, then pages over them) |
anchor_get_records |
Flexible record query: by version, by checksum, by registry, with pagination |
Phase 3: Anchor Writes
Write tools follow a prepare-sign-submit pattern. The MCP server constructs complete unsigned transactions but never holds private keys. Requires ENABLE_WRITE_TOOLS=true. See Write Architecture below.
| Tool | Description |
|---|---|
anchor_prepare_add_registry |
Build unsigned tx to create a new registry |
anchor_prepare_add_record |
Build unsigned tx to anchor a document (checksum + URI) |
anchor_prepare_update_record_status |
Build unsigned tx to change a record's status (e.g. Active, Superseded, Revoked) |
anchor_prepare_grant_role |
Build unsigned tx to grant admin/editor role |
anchor_prepare_revoke_role |
Build unsigned tx to revoke admin/editor role |
evm_send_raw_transaction |
Broadcast a signed transaction, return tx hash |
Write Architecture (Phase 3)
The MCP server handles all blockchain complexity (ABI encoding, nonce lookup, gas estimation, transaction construction) but signing stays with the caller. This keeps private keys out of the MCP server entirely.
Flow:
Prepare -- Caller invokes
anchor_prepare_add_registry(from, name, description, metadata). The MCP server ABI-encodes the call, fetches the nonce for the sender address, estimates gas, and returns a complete unsigned transaction with two signing paths.Sign & Submit (choose one):
Path A -- MetaMask / browser wallet (recommended for human users):
const prepared = await callMCPTool("anchor_prepare_add_record", {
from, registry, uri, checksum, checksum_algo, metadata // checksum_algo + metadata are required (metadata must be non-empty and not "{}")
});
// Pass wallet_tx_request directly to MetaMask
const txHash = await window.ethereum.request({
method: "eth_sendTransaction",
params: [prepared.wallet_tx_request],
});
// Confirm on-chain
const receipt = await callMCPTool("evm_get_transaction_receipt", { tx_hash: txHash });
MetaMask signs and broadcasts directly. The response includes wallet_tx_request with all numeric fields as 0x-prefixed hex quantities ready for EIP-1193 wallets. You do not call evm_send_raw_transaction in this path.
Path B -- Local/headless signer (CLI, HSM, automation):
prepared = mcp.call("anchor_prepare_add_record", {...})
# Sign the raw_tx bytes externally
signed_hex = my_signer.sign(prepared["raw_tx"])
# Broadcast via MCP server
result = mcp.call("evm_send_raw_transaction", {"signed_tx": signed_hex})
receipt = mcp.call("evm_get_transaction_receipt", {"tx_hash": result["tx_hash"]})
Broadcast allowlist (relay scope):
evm_send_raw_transactionis a scoped anchoring relay, not a general-purpose broadcaster. The server decodes the signed transaction and rejects it unless its destination is the anchor precompile (ANCHOR_ADDRESS); other contracts, externally-owned accounts, value transfers, and contract creation are refused without broadcast. This holds on both the keyless-write and authenticated/self-host paths — the caller's signature cannot be relayed to move funds or reach arbitrary contracts. Self-hosters who need to broadcast non-anchor transactions can opt out withMCP_RELAY_ALLOW_ANY=true(authenticated path only; forbidden under keyless writes). Reads are never restricted.
- Verify -- Use
evm_get_transaction_receipt(tx_hash)andanchor_get_recordsto confirm the anchor is on-chain.
This pattern means:
- Private keys never touch the MCP server
- Browser wallet users get native MetaMask confirmation prompts
- Signing can happen in an HSM, Vault, hardware wallet, or any secure enclave
- The MCP server handles all the ABI encoding, nonce, and gas estimation
Observability
The server includes vendor-agnostic observability via OpenTelemetry, with out-of-the-box support for Prometheus/Grafana and CloudWatch/X-Ray.
Endpoints
The health and metrics server runs on a separate port (default :9090), independent of the MCP transport.
| Endpoint | Purpose |
|---|---|
GET /healthz |
Liveness probe -- returns 200 OK if the process is running |
GET /readyz |
Readiness probe -- returns 200 OK if the EVM RPC is reachable and the ABI is loaded |
GET /metrics |
Prometheus scrape endpoint (when ENABLE_PROMETHEUS=true) |
What Gets Instrumented
- Every MCP tool call gets a trace span and metrics (duration histogram, call counter, error counter, active request gauge)
- Every upstream EVM RPC call gets a child trace span with method name and duration
- Structured logs include request IDs and can be correlated with OTel traces
- Sensitive data — the
SafeAddr/SafeURL/SafeTxDatahelpers redact addresses, URLs, and tx data on debug log lines (internal/logging/redact.go). Note the write-audit broadcast lines (INFO/WARN) deliberately record the recovered on-chain signer, destination, and value in full — these are public on-chain data and form the write-audit trail, not debug output. See docs/DATA_HANDLING.md for the exact redaction scope.
Deployment Integration
- Kubernetes: Liveness/readiness probes on
:9090.ServiceMonitor/PodMonitorfor Prometheus auto-discovery. OTel Collector as DaemonSet or sidecar. - AWS ECS/Fargate: ALB health check on
/readyz.aws-otel-collectorsidecar for CloudWatch/X-Ray. Structured JSON logs viaawslogsdriver. - Azure: Compatible with AKS probes and Azure Monitor via OTel Collector.
Development
Running make with no arguments displays the full help menu.
make help # Show all available commands (also the default)
make build # Build binary
make run # Run with stdio transport
make run-http # Run with HTTP transport
make run-local # Build and run locally with HTTP + testnet config
make test # Run all tests
make test-unit # Unit tests only (-short)
make test-coverage # Tests with -race + coverage report
make coverage-check # test-coverage + enforce the 80% total coverage gate (same as CI)
make test-verbose # Verbose test output
make check-all # format + vet + lint
make format # gofmt + goimports
make vet # go vet
make lint # golangci-lint
make pre-commit # Run pre-commit hooks on all files
make install-hooks # Install pre-commit git hooks
make setup-dev # Install dev deps + hooks
make ci # install-dev + check-all + test-coverage
make release-check # clean + ci + build
make info # Show project info
make docker-build # Build Docker image (host arch only)
make docker-buildx # Multi-arch Docker build (amd64 + arm64) via buildx -- local manual operation
make docker-push # Multi-arch build and push to registry -- local manual operation, requires registry login
make docker-run # Run in Docker
make docker-smoke # Build, run, verify healthz + MCP, tear down
make test-load # Run k6 load tests (requires k6)
make test-integration # Integration tests against live testnet
make seed-test-data # Create test registry with phoney records on-chain
make clean # Remove build artifacts
API Key Management
make key-create NAME=my-agent # Create a new API key
make key-list # List all keys (ID, enabled, roles, created)
make key-disable NAME=my-agent # Disable a key (rejected at auth)
make key-enable NAME=my-agent # Re-enable a disabled key
Keys are stored in .mcp-keys.json (gitignored). Set MCP_API_KEYS_FILE=.mcp-keys.json to use them.
Local testing
The default suite is hermetic — no chain access, no credentials — and is the same one CI runs on every PR:
make test # Unit + golden + MCP E2E tests (fast, no network)
make coverage-check # -race + coverage report + the 80% total-coverage gate
open coverage.html # Inspect per-line coverage after coverage-check/test-coverage
CI fails any PR whose total statement coverage drops below 80%
(scripts/check_coverage.sh), so run make coverage-check before pushing.
Every new feature needs unit tests for its success/error paths and, if it
changes the MCP surface, E2E tests through the HTTP transport — see
AGENTS.md for the full requirements.
Optional local layers:
# Postgres-backed internal/mcp tests (audit log, quotas, migrations) —
# they skip unless a DSN is set. Any disposable Postgres 16 works:
export NVNM_TEST_PG_DSN='postgres://nvnm:nvnm@localhost:5432/nvnm_test?sslmode=disable' # pragma: allowlist secret -- throwaway local dev creds
make test
make test-integration # Live-testnet integration tests (needs network + .env credentials)
make docker-smoke # Build image, boot container, verify healthz/readyz + MCP init
For comprehensive testing documentation, including test architecture, framework details, and latest results, see docs/TESTING.md.
Deployment
Docker
make docker-build
docker run --rm \
-e NVNM_EVM_RPC_URL=https://evm.testnet.nvnmchain.io \
-e NVNM_CHAIN_ID=787111 \
-e ANCHOR_ABI_PATH=/app/abi/anchoring.json \
-e MCP_TRANSPORT=http \
-p 8080:8080 \
-p 9090:9090 \
nvnm-mcp-server
Kubernetes
Plain YAML manifests are available in deploy/k8s/:
# Apply with default namespace (nvnm-mcp; renamed from inveniam-mcp in PR #6, 2026-06-02 — see RUNBOOK § "K8s manifest migration")
kubectl apply -k deploy/k8s/
# Apply to a specific namespace
kubectl apply -k deploy/k8s/ -n your-namespace
A Helm chart is available in deploy/helm/nvnm-mcp-server/:
helm install nvnm-mcp deploy/helm/nvnm-mcp-server/ \
--set env.NVNM_EVM_RPC_URL=https://evm.testnet.nvnmchain.io \
--set env.NVNM_CHAIN_ID=787111
Prometheus alerting rules are in deploy/prometheus/alerts.yaml and a Grafana dashboard in deploy/grafana/dashboard.json.
AWS (ECS/Fargate)
The Docker image is designed for deployment on ECS/Fargate with HTTP transport. Configure environment variables through ECS task definitions. The server exposes health check endpoints on :9090 (/healthz, /readyz). Add an aws-otel-collector sidecar for CloudWatch/X-Ray telemetry.
MANTRA Validator Nodes
The server can run directly on MANTRA validator nodes, connecting to localhost RPC endpoints for minimal latency. Use stdio transport if co-located with an MCP client, or HTTP transport for remote access.
Project Structure
cmd/
nvnm-mcp-server/ Entrypoint
key-mgmt/ API key management CLI
query-anchor/ Anchor query dev CLI
seed-test-data/ Test-data seeding dev CLI
internal/
auth/ Client identity context propagation
config/ Environment-based configuration
logging/ slog-based structured logging + redaction
errors/ Shared sentinel errors + error sanitization
evm/ Generic EVM RPC client layer + tracing wrapper
anchor/ Inveniam anchor adapter (with address validation)
mcp/ MCP tool registration, handlers, auth middleware, key store, admin API
telemetry/ OTel providers, MCP middleware, health server, metrics
version/ Canonical version constant (single source of truth)
abi/
anchoring.json Anchor precompile ABI
deploy/
k8s/ Kubernetes manifests (Deployment, Service, HPA, ServiceMonitor, NetworkPolicy)
helm/nvnm-mcp-server/ Helm chart
grafana/ Grafana dashboard JSON
prometheus/ Prometheus alerting rules
tests/
load/ k6 load test scripts
docs/
DESIGN.md Architecture and design decisions
SECURITY_AUDIT.md Security assessment and remediation results
OWASP_AUDIT.md OWASP Top 10 self-audit (Phase 8.12)
DATA_HANDLING.md Privacy-by-design technical reference
NVNM_MCP_Privacy_Policy_Jul_2026.pdf Finalized Privacy Policy (legal artifact)
TERMS.md Terms of Service for the hosted Service
INCIDENT_RUNBOOK.md Per-alert investigation playbook
KEY_CUSTODY_THREAT_MODEL.md Rationale for zero-key-custody design
SECURITY_CONSUMER_GUIDANCE.md Operator-facing security guidance
LICENSE_EXCEPTIONS.md Project-scoped license exception register
METAMASK_GUIDE.md End-user MetaMask integration walkthrough
TESTING.md Test framework, strategy, and results
TOOL_REFERENCE.md MCP tool schema reference
RUNBOOK.md Operational runbook
.github/
workflows/ci.yml CI pipeline
dependabot.yml Automated dependency updates
.pre-commit-config.yaml Pre-commit hooks
.golangci.yml Linter configuration
License
Apache License 2.0 -- see LICENSE and NOTICE. Contributions are accepted under the same terms; see CONTRIBUTING.md.
Installing Nvnm
This server has no published package — it is built from source. Open the repository and follow its README.
▸ github.com/NVNM-Chain/nvnm-mcp-serverFAQ
Is Nvnm MCP free?
Yes, Nvnm MCP is free — one-click install via Unyly at no cost.
Does Nvnm need an API key?
No, Nvnm runs without API keys or environment variables.
Is Nvnm hosted or self-hosted?
Self-hosted: the server runs locally on your machine via the install command above.
How do I install Nvnm in Claude Desktop, Claude Code or Cursor?
Open Nvnm on unyly.org, pick your client tab (Claude Desktop, Claude Code, Cursor) and press Install — the config is generated automatically, no JSON editing.
Related MCPs
GitHub
PRs, issues, code search, CI status
by GitHubFilesystem
Secure file operations with configurable access controls.
Memory
Knowledge graph-based persistent memory system.
Template MCP Server
A CLI tool to create a new Model Context Protocol server project with TypeScript support, dual transport options, and an extensible structure
by mcpdotdirectAmap Maps Mcp Server
MCP server for using the AMap Maps API
by duxiaohuiSupabase
Database, auth and storage
by SupabaseEverything
Reference / test server with prompts, resources, and tools.
Git
Tools to read, search, and manipulate Git repositories.
Sequential Thinking
Dynamic and reflective problem-solving through thought sequences.
Time
Time and timezone conversion capabilities.
Compare Nvnm with
Not sure what to pick?
Find your stack in 60 seconds
Author?
Embed badge for your README
Browse similar
All development MCPs
