中文 English

Jining Mido Information Technology Co., Ltd

From Zero to a Working MCP Server for Genesis Virtual World CRM 3D Data — and a Demo Station You Can Call Right Now

Genesis Virtual World CRMMCP server 3D worldMCP access 3D dataagent-virtual-worldAI agent enters virtual worldMCP Streamable HTTP in practiceMCP pitfallsself-hosted 3D platform MCPGenesis virtual world

The result first: connect any MCP-capable AI host (Claude Desktop, Cursor, Cline…) to my service, and its tool list gains eight world_ tools — the AI can step into a real multiplayer 3D world as a visible character: observe what's around, walk up to real players, and speak, while you watch everything in your browser.

The demo station is already running and open for testing (address and two ways to try it at the end). This article is the build log: how 3D-world structured data became MCP tools step by step, and the pitfalls along the way — every one taken from real records, not rose-tinted memory.

I. The starting point: the world already had an agent channel, but every client meant writing code

My 3D world (Genesis, browser-based and self-hostable) has had an AI agent channel for a while: drop a discovery file (/.well-known/virtual-world-agent.json), exchange a key for a session token, connect to /ws/agent — and the AI receives structured data: who is nearby (spatial radar), what each object is (object AI descriptions), what just happened (event streams), and can act — walk, talk, follow.

The catch: that channel's shape is HTTP + WebSocket + a pile of JSON conventions. Everyone who wanted an AI in the world had to write a client script against the docs. Developer teams don't mind — but most users of AI hosts just want to say "go wander that world."

MCP is exactly the tool for this: wrap a capability set as standard tools and every MCP host can call them. So my work was essentially one layer of translation — turn the existing agent channel into eight MCP tools, without inventing any new backend capability.

II. The stdio version: how the eight tools were cut

The cutting principle: the primitive actions an AI performs in a world — nothing more, nothing less:

ToolWhat it doesKey limits
world_discoverRead the world's public discovery doc: name, open for access, capabilities and rate limitsNo credentials needed
world_enterEnter the world (create session + open WebSocket; real players can see it from now on)Idempotent; calling again won't double-enter
world_observeThe AI's eyes: text descriptions and distances of nearby players/objects/waypointsGuests: 30 m, once per 2 s
world_saySpeak (visible as a bubble to players within 30 m)One per 5 s, 200 chars
world_walk_toWalk to coordinates (real walking animation)Once per 2 s; the only way to move
world_followFollow a player by idLong-running task, returns immediately
world_chat_historyRead recent chatHistorical data, not live push
world_leaveLeave (the character disappears)Re-entered automatically on next tool call

Server-side red lines kept intact: no teleport, no set_position — the MCP side never wraps around them. To move, the AI walks step by step like everyone else.

Plus one Resource (virtual-world://guide, a world guide) and two Prompts (a guided tour, an observation report) so the AI knows what this world is and its rules the moment it connects.

III. stdio pitfalls, from the records

Pit 1: stdout is the protocol channel — one stray character kills everything. In MCP stdio transport, stdout may contain only JSON-RPC. One casual console.log for debugging and the host reports a protocol parse error. All logging must go through console.error — this is now a written development constraint in the repo.

Pit 2: an initialize request missing one field gets no answer. When hand-writing a test client, initialize must carry protocolVersion, capabilities, and clientInfo. Missing any one and the server simply doesn't respond. That's the MCP protocol's requirement, not a package bug — but during debugging you can waste a long time searching the application layer.

Pit 3: the discovery doc broadcast http://, and the https site hit Mixed Content. When the reverse proxy didn't pass through X-Forwarded-Proto, endpoint URLs inside the discovery doc could be http://. An MCP client that blindly trusts the doc fails with Mixed Content in an https environment. Fix: the protocol is pinned to the AGENT_HOST config; the discovery doc only supplies paths.

Pit 4: session renewal must not reconnect. Key-tier sessions last 15 minutes and renew automatically. The subtle part: if renewal reconnected the WebSocket, the character's figure would flicker — visibly — for real players. So renewal swaps the token only, never the connection.

Pit 5: the "teleport" after an idle-timeout kick. Five minutes of inactivity gets a guest kicked by the server; the next tool call re-enters and the position is restored from the last persisted location — which looks like teleporting. This is normal behavior, but if it isn't documented, users will file it as a bug.

IV. Then the remote endpoint: platforms only accept HTTP

After the stdio version shipped to npm (agent-virtual-world, already in the official MCP Registry), directory sites and Chinese platforms imposed a new requirement: Smithery, Coze, Dify, Baidu Qianfan, and Tencent Yuanqi only accept Remote (HTTP) MCP. And a remote endpoint is friendlier to testers — nothing to install, one URL to fill.

So https://miduo100.com/mcp was born (Streamable HTTP, MCP 2025-03-26 spec).

The dependency lesson: the first implementation used the official SDK, which dragged in 34 packages. My deployment model is "upload the dependency directory directly" (the server can't run npm install), so "which packages to ship" became error-prone — and it did fail once: during cleanup, npm prune and manual directory deletion collided, qs got truncated, express failed to load, and the whole service wouldn't start.

The final call was to hand-write it with zero new dependencies: HTTP via the project's existing express, session IDs via Node's built-in crypto.randomUUID(), business logic calling the existing agent service layer directly. package.json unchanged. Strip the wrapping off MCP Streamable HTTP and it's JSON-RPC over HTTP plus one session header — about 500 lines covering everything I needed (initialize / tools / resources / prompts / ping).

V. Remote endpoint pitfalls, from the records

Pit 6: GET /mcp without a session returned 400 — and directory sites misjudged it. The original implementation replied with a JSON-RPC error. But Smithery, Glama, and PulseMCP all probe an endpoint with GET before listing it — a 400 risks a "endpoint invalid" rejection. Fix: no session returns 200 + a self-describing JSON (server / version / tools / hint / health), so opening it in a browser shows an explanation instead of an error.

Pit 7: POST without a Content-Type header left the body unparsed. The global express.json() only parses application/json, yet some MCP clients and probes send POST without that header — the body arrives unparsed and gets misread as a parse error. Fix: mount a lenient parser on the /mcp route only: express.json({ type: () => true, limit: '1mb' }).

Pit 8: one connection per IP meant every platform user got 429. Guest tier allowed one connection per IP — fine for stdio (each user is on their own machine's IP). But Smithery and Coze forward requests for all their users from a shared egress IP; capping at 1 meant "one user per platform at a time." Raised to 10, backed by ticket rate limiting and a global session cap.

Pit 9: the false lead — curl quote escaping. One debugging round, the server log kept reporting JSON parse errors with a two-character body ({\) — it looked like the server was broken. After much wandering, the culprit was PowerShell: curl.exe -d "{\"jsonrpc\":...}" had its escapes eaten, so the request itself was truncated. The symptom sat on the server; the fault sat in the test command. Writing the body to a file and sending --data-binary "@file.json" is the reliable way.

VI. The complete calling steps

Option A: host config (zero credentials, 30 seconds)

{
  "mcpServers": {
    "virtual-world": {
      "command": "npx",
      "args": ["-y", "agent-virtual-world"],
      "env": { "AGENT_HOST": "https://miduo100.com" }
    }
  }
}

Option B: remote endpoint (zero install) — in any Streamable HTTP-capable host, fill in https://miduo100.com/mcp. No credentials needed.

Once connected, one full tour by the AI follows this chain:

world_discover   → confirm the world is open; read capabilities and rate limits
world_enter      → enter (a 🤖 character appears in real players' world)
world_observe    → look around: who is nearby, how far, what objects are (with AI descriptions)
world_walk_to    → walk over (real walking animation, no teleporting)
world_say        → greet (players within 30 m see a speech bubble)
world_chat_history → check for replies (guest tier is pull-based)
world_leave      → leave

For the protocol level, the remote endpoint's minimal flow is three steps — initialize for the session header, tools/list for the inventory, tools/call to invoke:

# 1) Health check
curl -s https://miduo100.com/mcp/health
# {"ok":true,"server":"agent-virtual-world","version":"0.1.2","tools":8,...}

# 2) initialize (grab Mcp-Session-Id from the response headers; send it on every later request)
curl -s -X POST https://miduo100.com/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  --data-binary '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"demo","version":"0.0.1"}}}'

# 3) Call a tool (with Mcp-Session-Id)
curl -s -X POST https://miduo100.com/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "Mcp-Session-Id: <session id from step 2>" \
  --data-binary '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"world_discover","arguments":{}}}'

VII. Measured results

Run once against a local standalone instance and once against production, results identical: /mcp/health returns {"ok":true,"tools":8,"version":"0.1.2"}; initialize → tools/list yields 8 tools; world_discover reads the world "Genesis Virtual World"; world_observe returns a real player 2.9 meters away and roughly a hundred objects with descriptions; session reuse keeps the same sid; after DELETE /mcp, the old session correctly gets a 404. Third-party verification: Smithery's automated probe returned SUCCESS with 8 tools, 2 prompts, 1 resource — matching local numbers.

Honest limits: the guest tier is pull-based (no live push; world_chat_history is how you catch up), a 30-meter observation radius, and a 30-minute non-renewable session; world_observe output has a ~2KB budget, so when objects crowd in, distant ones degrade to name-only; an object without an AI description honestly reports "(no AI description)" rather than guessing from its name. The API-key tier unlocks the 200-meter radar and live push.

VIII. Come test it: two ways to play

Play one: be the observed. Open the demo station https://miduo100.com in your browser and walk in as a guest; then have your MCP-connected AI (configured via option A or B) execute "enter that world, find someone, say hello." Now watch your browser: a 🤖 character appears, walks toward you, and a speech bubble pops up over its head as it talks to you.

Play two: protocol level only. Walk the three curl steps from section six, or simply open https://miduo100.com/mcp in a browser (self-describing JSON without a session), and check /mcp/health for status.

Known behaviors, so they don't get filed as bugs: speech bubbles cover 30 meters — have the AI walk over first if it's far; guest connections and tickets per IP are rate-limited; in guest tier it can't hear you in real time (pull mode) — the key tier gives the full conversation experience.

Three repo mirrors hold identical content; the first two are faster for visitors in mainland China:

  • Gitee (MCP package): https://gitee.com/miduoxinxijeji/miduo.git
  • GitCode (mirror): https://gitcode.com/qq_35054471/virtual-world
  • GitHub: https://github.com/miduo100/3d-virtual-world

About Genesis

Genesis is a self-hosted 3D virtual world system built on Three.js + WebGL, helping individuals and businesses build their own 3D spaces. Accessible directly from a browser, compatible with both PC and mobile, it supports multiplayer online, federated teleportation, a shop system, and Agent integration—where an AI can enter your world as an embodied character. Your data runs on your own server, never passing through a third-party platform—so every world truly belongs to its owner.

Want your AI to step into a real 3D world? Genesis (the Genesis Virtual World CRM System) is a self-hostable Three.js 3D virtual-world base; MCP access and the demo station are both open. The official site (search "Genesis Virtual World CRM") has the full introduction.

About the name: Genesis in this article refers to the Genesis Virtual World CRM System — the same self-hosted 3D virtual-world product. If searching "Genesis" does not find us, search "Genesis Virtual World CRM" directly.
← Back to Articles