MCP server
Reference for @oneie/mcp — the stdio server that puts 168 ONE tools inside Claude Code, Claude Desktop, Cursor and Windsurf.
Reading this as an agent? The same page in raw markdown: /docs/mcp.md
@oneie/mcp is an stdio MCP server that exposes the ONE substrate to any MCP
client. Version 0.6.1 registers 168 tools. The bin is oneie-mcp.
At a glance
| Package | @oneie/mcp |
| Version | 0.6.1 |
| Bin | oneie-mcp → dist/index.js |
| Transport | stdio only |
| Tools registered | 168 |
| Depends on | @oneie/sdk ^0.14.12, @oneie/templates ^0.2.0, @modelcontextprotocol/sdk ^1.0.0 |
| Ships to npm | dist/, README.md |
| Telemetry | on by default |
Telemetry
Telemetry is enabled by default and fires once per tool call, before the handler runs.
| Endpoint | POST https://api.one.ie/api/signal |
| Rate limit | 100 emits per hour, per server process |
| Fields sent | sender: toolkit:<sessionId>, receiver: toolkit:mcp:<tool>, tags ["telemetry","mcp",<tool>,"tool-call",<tool>], weight: 1, content: { id: <sessionId>, conversationId } |
| Session id | sha256 of 16 random bytes, first 16 hex chars, generated in-process on first use |
| Not sent | tool arguments, tool results, API key |
Two opt-outs. Either disables it:
ONEIE_TELEMETRY_DISABLE=1
{ "telemetry": false }
The JSON above goes in ~/.oneie/config.json. Any non-empty value of
ONEIE_TELEMETRY_DISABLE disables telemetry. The config file is read on every
call; a missing or unparseable file leaves telemetry on.
Install
npm install -g @oneie/mcp
Or run it without installing:
npx -y @oneie/mcp
Get a key first:
npm i -g @oneie/cli
oneie auth login
Client configuration
Use the same server key (oneie) in every client so tool names read the same
way across them.
| Client | Config location | Source of the block below |
|---|---|---|
| Claude Code | claude mcp add, or .claude/settings.json | documented in this repo |
| Claude Desktop | ~/Library/Application Support/Claude/claude_desktop_config.json | documented in this repo |
| Cursor | .cursor/mcp.json, or ~/.cursor/mcp.json | Cursor’s own documented format; no block exists in this repo |
| Windsurf | ~/.codeium/windsurf/mcp_config.json | Windsurf’s own documented format; no block exists in this repo |
All four take the same mcpServers object shape.
Claude Code — one command, no file editing:
claude mcp add oneie \
-e ONEIE_API_KEY=one-<your-key> \
-e ONEIE_API_URL=https://one.ie \
-- npx -y @oneie/mcp
Claude Desktop, Cursor, Windsurf — the file contents:
{
"mcpServers": {
"oneie": {
"command": "npx",
"args": ["-y", "@oneie/mcp"],
"env": {
"ONEIE_API_URL": "https://one.ie",
"ONEIE_API_KEY": "one-<your-key>"
}
}
}
}
Global install — same shape, using the bin name instead of npx:
{
"mcpServers": {
"oneie": {
"command": "oneie-mcp",
"env": {
"ONEIE_API_URL": "https://one.ie",
"ONEIE_API_KEY": "one-<your-key>"
}
}
}
}
Environment variables
| Variable | Fallback | Default | What |
|---|---|---|---|
ONEIE_API_KEY | ONE_API_KEY | none | Bearer token sent on every HTTP call |
ONEIE_API_URL | ONE_API_URL | https://one.ie | Substrate base URL |
ONEIE_TELEMETRY_DISABLE | — | unset | Any non-empty value disables telemetry |
The default base URL is https://one.ie — the app origin, not the gateway.
The gateway serves the substrate unprefixed, so ONEIE_API_URL=https://api.one.ie
returns 404 for every tool that makes an HTTP call — that is 165 of the 168; the
three template tools are local and answer either way. The shipped README’s
environment table prints https://api.one.ie as the default; that table is
wrong. Omit the variable unless you are pointing at a non-production origin.
readEnv() runs once, at createRouter(). Changing an environment variable
requires restarting the server.
Auth
Two doors.
| Door | Key required | Path |
|---|---|---|
| Bearer | yes | Authorization: Bearer <ONEIE_API_KEY> on every HTTP call |
auth_agent | no | POST /api/auth/agent, called with the key deliberately unset |
auth_agent is the keyless onboarding call. With no ONEIE_API_KEY set, the
server still starts and every tool is listed. Calling auth_agent with {} —
or { wallet: "<sui address>" } — returns { uid, apiKey, wallet, group }.
It does not route through the auth:agent receiver. That receiver is gated
against exactly this call’s shape (anonymous, keyless, no Origin header,
which a stdio process does not have), so the tool posts to the public
/api/auth/agent endpoint instead.
Restart semantics. The key auth_agent returns does not apply to the
running server. readEnv() already ran. Persist the value as ONEIE_API_KEY
in the client’s config and restart the MCP server before making authenticated
calls.
Where the 168 tools come from
Three sources, composed in createOneRouter().
| Source | Origin | Contributes | Changes when |
|---|---|---|---|
| Registry-derived | receivers in @oneie/sdk carrying a surfaces.mcp flag | 93 names, 53 with no curated twin | the resolved @oneie/sdk changes |
| Curated | hand-written modules in packages/mcp/src/tools/ | 115 names across 14 modules | @oneie/mcp is published |
Generated fn_* | one per entry in @oneie/sdk/fn-allowlist | 22 | the allowlist or FN_MAP changes |
Registration order:
createOneRouter()
|
1. mcpToolsFromRegistry() 93 derived names
|
2. substrateTools() ... tasksTools() 115 curated
|
= 168 registered
a curated tool of the same name replaces
the derived one: 40 names are replaced,
53 derived names survive as the only
definition
The arithmetic:
| Set | Count |
|---|---|
| Curated (14 modules) | 115 |
| Registry-derived names | 93 |
| — replaced by a curated twin | 40 |
| — surviving as the only definition | 53 |
| Total registered | 168 |
Generated fn_* (subset of curated) | 22 |
Total excluding fn_* | 146 |
A derived tool is named, described and schema’d from the receiver declaration
itself. The description is the receiver’s summary, the input schema is
metaSchema(receiver).request, and dispatch is POST /api/ask/<receiver> with
{ data: args }.
The name has two forms. surfaces: { mcp: true } munges the receiver — every
character outside [A-Za-z0-9_-] becomes _, so world:create-group ships as
world_create-group. surfaces: { mcp: { name: "..." } } carries an explicit
name instead, which is how a derived tool keeps the name a since-deleted
hand-written twin already had: video:create-room ships as create_room, not
video_create-room. 22 of the 93 flagged receivers declare a name; 21 of those
names differ from the munge.
Consequence: a receiver flagged surfaces.mcp in a newer @oneie/sdk becomes
a tool with no @oneie/mcp publish. 75 of the 168 tools — 22 fn_* plus 53
derived-only — resolve from the SDK, which package.json pins as ^0.14.12.
The tool count tracks the installed SDK, not the MCP release.
Receivers whose zod schema failed JSON-Schema conversion: 0
(UNCONVERTIBLE is empty).
Curated modules
| Module | Tools |
|---|---|
| substrate | 19 |
| lifecycle | 19 |
| fn | 22 |
| tasks | 14 |
| observability | 8 |
| broadcast | 8 |
| workflow | 7 |
| seo | 6 |
| discovery | 3 |
| social | 3 |
| chat | 2 |
| views | 2 |
| messaging | 1 |
| video | 1 |
| Total | 115 |
The tool surface
C = hand-written curated. D = registry-derived only.
Substrate — the verbs and the dimension reads (19, C)
signal · ask · mark · warn · fade · follow · select · recall ·
reveal · forget · frontier · know · highways · groups · actors ·
things · paths · events · learning
Meta — runtime discovery (4, D)
meta_catalog · meta_schema · meta_types · meta_recall
Agent lifecycle (19 C, 5 D)
C: auth_agent · sync_agent · publish_agent · pull_agent ·
unpublish_agent · list_agents · agent_history · rollback_agent ·
discover_skill · register · pay · own_me · list_skills ·
unimport_skill · skill_eval · tasks_mine · tasks_everywhere ·
tasks_claim · tasks_link
D: agent_run · skill_run · skills_list · identity_address ·
lifecycle_of
Templates and scaffolding — local, no HTTP (3, C)
scaffold_agent · list_presets · get_agent
Tasks (14 C, 7 D)
C: tasks_list · tasks_create · tasks_subtask · tasks_status ·
tasks_priority · tasks_tag · tasks_notes · tasks_rename ·
tasks_comment · tasks_depend · tasks_schedule · tasks_approve ·
tasks_launch · tasks_stake
D: tasks_undepend · tasks_follow · tasks_unfollow · tasks_reassign ·
tasks_reap · tasks_announce · tasks_generate
Workflow (7 C, 8 D)
C: workflow_list · workflow_get · workflow_apply_diff · workflow_run ·
workflow_runs · workflow_validate · workflow_resolve
D: workflow_create · workflow_update · workflow_stop ·
workflow_trigger · workflow_tql · workflow_apply-tql ·
workflow_step-stats · workflow_apply-diff
World — CRUD across the dimensions (8, D)
world_create-group · world_update-group · world_create-actor ·
world_update-actor · world_list-actors · world_create-thing ·
world_update-thing · world_list-things
Group membership (3, D)
groups_join · groups_leave · groups_members
Observability (8, C)
stats · health · revenue · frontiers_global · export_units ·
export_highways · ingest_event · chat_turn
Messaging and chat (3, C)
message · chat_send · chat_broadcast
Broadcast, newsletter, segments (8, C)
broadcast_create · broadcast_list · broadcast_get · broadcast_send ·
newsletter_update · segment_list · segment_get · segment_preview
Social (3, C)
social_list_posts · social_create_post · social_accounts
Video and rooms (1 C, 10 D)
C: get_room_status
D: create_room · delete_room · create_session · contact_call ·
quick_call · invite_to_call · schedule_webinar · start_recording ·
start_stream · video_summary
Wallet (5, D)
wallet_get · wallet_portfolio · wallet_send · wallet_invoice ·
wallet_transactions
Pages (2, D)
pages_create · pages_list
Views (2, C)
create_view · list_views
SEO (6 C, 1 D)
C: seo_backlinks · seo_ai_visibility · seo_research_keywords ·
seo_serp · seo_keyword_metrics · seo_gsc
D: seo_keyword-metrics
Generated fn_* tools
22 tools, one per entry in @oneie/sdk/fn-allowlist. FN_MAP holds 270 TypeDB
functions; the allowlist selects which 22 are exposed as tools.
fn_actionable_hypotheses · fn_active_frontiers · fn_active_objectives ·
fn_actor_classification · fn_actors_by_kind · fn_actors_by_tag ·
fn_actors_for · fn_attractive_tasks · fn_blocked_tasks ·
fn_cheapest_provider · fn_covered-unproven · fn_deadlocked ·
fn_exploratory_tasks · fn_highways · fn_incomplete · fn_optimal_route ·
fn_promising_frontiers · fn_ready-tasks · fn_suggest_route ·
fn_total_contribution · fn_uncovered · fn_unrealised
| Dispatch | POST /api/ask/fn%3Arun with { data: { fn, args } } |
| Argument shaping | every argument key is $-prefixed before the call |
| Schema | all parameters typed string, all required, additionalProperties: false |
| Description | carries the FN_MAP content hash, e.g. FN_MAP va8ccc426… |
A function in FN_MAP that is not allowlisted has no tool, and ask({ receiver: "fn:run", data: { fn, args } }) does not reach it either. The fn:run resolver
checks the same allowlist server-side, before it looks the function up in
FN_MAP, and answers { ok: false, rows: [], error: "fn '<name>' is not in the allowlist" }. The allowlist bounds dispatch, not only the tool list.
conversation_id
serve() injects a conversation_id property into every tool’s
inputSchema at ListTools time. It is not declared by any tool module and it
is not in required.
{
"conversation_id": {
"type": "string",
"description": "Conversation trace ID — mint on first call, echo on every subsequent call to link the chain."
}
}
| Step | Behaviour |
|---|---|
ListTools | the property is added to all 168 schemas |
CallTool, field present | value is read for telemetry, then stripped |
CallTool, field absent | telemetry falls back to the process session id |
| Handler | never sees the field |
The contract is mint-on-first-call, echo-thereafter. The value correlates a call chain in telemetry. It reaches no receiver.
Discovery
There is no __list tool. The shipped README instructs readers to call one;
the router does not register it.
| Step | Call | Returns |
|---|---|---|
| 1 | ListTools — the client sends this on connect | all 168 names, descriptions, input schemas |
| 2 | meta_catalog, or ask({ receiver: "meta:catalog" }) | the typed receiver surface, role-scoped, with cost, reversibility and settlement |
| 3 | meta_schema, or ask({ receiver: "meta:schema", data: { receiver } }) | one receiver’s payload shape |
| 4 | ask({ receiver, data }) | the result |
Every receiver in the catalog is reachable through signal
and ask whether a dedicated tool exists for it or not. The surfaces.mcp
flag controls only whether a receiver is listed as its own tool. The signal
and ask descriptions both end with a generated recipe menu and a pointer to
meta:catalog.
Results and errors
| Outcome | MCP response |
|---|---|
| Success | content: [{ type: "text", text: JSON.stringify(result, null, 2) }] |
| Failure | isError: true, content: [{ type: "text", text: JSON.stringify({ error }) }] |
| HTTP 204 | the success row, with apiCall returning null — text is "null" |
A non-2xx HTTP response throws with the response body embedded in the message,
truncated to 1000 characters. The substrate returns structured 400 bodies —
{ error, field, expected, got, hint, example } — so the failing field and an
example payload reach the caller instead of a bare status code.
Programmatic use
import { createOneRouter, serve, MCP_VERSION } from "@oneie/mcp";
const router = createOneRouter();
await serve(router, { name: "oneie", version: MCP_VERSION });
Exported from the package root: createRouter, createOneRouter, serve,
apiCall, readEnv, MCP_VERSION, MCP_TOOLS, toolManifestDrift,
mcpToolsFromRegistry, derivedToolNames, toolNameFor, autoToolName,
UNCONVERTIBLE, the types McpTool and McpRouter, and 13 of the 14 tool
factories — substrateTools, discoveryTools, lifecycleTools,
observabilityTools, videoTools, broadcastTools, messagingTools,
chatTools, fnTools, seoTools, viewsTools, workflowTools,
tasksTools.
socialTools is the exception. It is exported from src/tools/social.ts but
not re-exported from the package root, so it cannot be imported from
@oneie/mcp.
Count the live surface:
node -e "import('@oneie/mcp').then(m=>console.log([...m.createOneRouter().tools.keys()].length))"
Known limits
Two shadow-duplicate tool names ship. Same receiver, two live names, two different descriptions. Use the underscore variant.
| Use this | Not this | Receiver |
|---|---|---|
workflow_apply_diff | workflow_apply-diff | workflow:apply-diff |
seo_keyword_metrics | seo_keyword-metrics | seo:keyword-metrics |
workflow_apply_diff also shapes its arguments — it sends
simulate: commit !== true, so it simulates by default. The hyphen variant
forwards arguments unchanged.
No __list tool. The README instructs readers to call one. Use ListTools
and meta_catalog instead.
The README’s environment table is wrong. It prints https://api.one.ie as
the ONEIE_API_URL default. The code defaults to https://one.ie, and
api.one.ie returns 404 for every tool that makes an HTTP call.
stdio only. There is no HTTP or remote MCP endpoint. The
api.one.ie/mcp/:tool route was designed and never built.
auth_agent needs a restart. The key it returns does not apply to the
running server.
An empty result is not proof of an empty board. An authorised call against
a workspace you do not belong to returns ok: true with zero rows,
indistinguishable from no work queued. Treat zero rows as an unproven
connection until a call returns rows.
Write task edges serially. Batching tasks_depend or tasks_tag in one
parallel block produces spurious not_found and write_failed responses; the
same calls succeed on serial retry with identical arguments. The errors read
like permission failures.
The tool count tracks the SDK. 75 of the 168 tools resolve from
@oneie/sdk (^0.14.12). A different resolved SDK gives a different count at
the same @oneie/mcp version.
check:tools does not check documentation. bun run check:tools compares
the hand-maintained MCP_TOOLS manifest against the live router and reads no
README. It exempts fn_* names and derived-only names by design. Measured
drift at 0.6.1: 0 missing, 0 extra.