Repository navigation
Releases
Releases · IvanBBaev/x-mcp
x-mcp-ai 0.8.0
First published release on npm as x-mcp-ai.
Added
- Infrastructure layer for the MCP server: configuration contract (20
X_MCP_*environment
variables with defaults and validation, including a named-profiles file), two-axis policy engine (12operation:domaincells,
five presets, deny > allow > preset), session credit budget (warn/hard), the
registry-as-data tool contract (ToolDef), the host-scoped HTTP client with rate-limit
tracking, the error taxonomy, and the render/resolve/paginate helpers. - Packaging and presentation layer:
README,SECURITY,CONTRIBUTING, this changelog,
the MCP Registry manifest (server.json), and repository metadata inpackage.json
(mcpName,repository,homepage,bugs,keywords,trademark). - Continuous integration (build/lint/format/test on Node 22 and 24), CodeQL scanning, and
Dependabot for npm and GitHub Actions. - OAuth 2.0 user-context authentication: PKCE
authorizeflow (CSRFstate, one-shot
loopback listener,--manualheadless mode), single-flight token refresh with
persist-before-use, an atomic0600file token store with fail-closed locking, and a
doctordiagnostics subcommand. - OS-keychain token storage (
X_MCP_TOKEN_KEYCHAIN=1) as an alternative backend to the
token file, on macOS (security) and Linux (secret-tool). The secret always travels
on the child process's stdin, never in argv, and never appears in an error, cause, or
warning. It fails closed — an unsupported platform or a missing secret tool is a typed
error at startup, with no in-memory fallback — and the two backends are mutually
exclusive by construction. One selector answers "which backend does this configuration
call for?" for bothserveandauthorize, so an operator cannot authorize into one
store and serve from the other. Documented limitation: the keychain backend has no
cross-process refresh lock (single-flight holds within a process, and it warns once);
operators running several processes against one account should useX_MCP_TOKEN_FILE. - Eight write/timeline tools —
x_post_create,x_post_delete,x_like_set,
x_repost_set,x_bookmark_set,x_timeline_home,x_timeline_mentions,
x_timeline_user— joining the six Phase-1 read tools (14 registered in total). - Final policy presets (
read-only/engage/publish/manage/full) with a
strict escalation chain; DM cells are excluded from every preset and require an
explicit allow. - Twenty-five Phase-3 tools across six packages (39 registered in total): social graph
(x_follow_set,x_mute_set,x_block_set,x_followers_list,x_following_list,
x_user_search), lists (ten merged tools incl. standalonex_list_delete), media
(x_media_uploadchunked INIT→APPEND→FINALIZE with optional alt text,
x_media_status), DMs (three event lookups +x_dm_send, reachable only via an
explicitwrite:dm/read:dmallow), and full-archive search/counts
(x_search_archive,x_post_counts_archive, budget-guarded), and usage reporting
(x_usage_get). x_usage_getreports the current billing cycle's post-READ COUNTS against the monthly
project cap — with an optional per-day and per-app breakdown — alongside this process's
local credit-spend estimate. The two halves are deliberately never reconciled: X
publishes no spend API, so the platform figures are counts and never money, and the
local estimate is never seeded or corrected from them (COST-2). The tool reads the
budget and cannot change it (COST-1).- Structured tool output: every tool advertises a JSON-Schema
outputSchemain
tools/listand returnsstructuredContentalongside the text block, both built from
the same render object. The composition root refuses to start if a registered tool has
no published schema. - Availability class-gating at registration time (
X_MCP_AVAILABILITY): tools declaring
a specially-provisioned class (pilot/premium-user/enterprise) are excluded from
registration unless the operator opts in; the pay-per-use base classes always register. - Generated tool reference (
docs/reference/tools.md) built from the registry, plus a
drift gate (npm run docs:check) that fails when the checked-in reference no longer
matches the code. Regenerate withnpm run docs:gen. - Context-budget gate: the bytes a client spends on
tools/list(schemas, descriptions,
annotations) are measured and capped at 80 kB — currently 78.4 kB — so a new tool
cannot silently crowd out the model's working context. x_bookmarks_list— reads the authenticated user's own bookmarks as a compact page.
Completes the pair againstx_bookmark_set, which could add a bookmark with no way to
read one back. Resolvesmebefore the read, so a failure to identify the account is
reported as "the bookmarks endpoint cannot be addressed; nothing was read" rather than
as an empty page.x_post_hide_reply— hides or unhides a reply in a conversation the authenticated user
started, opening the previously emptywrite:moderationpolicy cell. The action is
absolute (hide/unhideset a state rather than toggling one), so re-issuing it
after an ambiguous network failure is safe; the 403 for "you are not the conversation
author" is decorated to say exactly that instead of surfacing a bare platform refusal.- Manifest lockstep guard:
server.jsonis checked against the configuration contract,
so an environment variable cannot be added to the code without being advertised (or
deliberately excluded). - CI job
gatesrunning the three checks above.
Changed
npm run checknow also runsnpm run gates(docs drift, context budget, manifest
lockstep) in addition to verify, coverage, and the production-dependency audit.
Fixed
- Truncated responses on stdin EOF: the server now flushes buffered stdout before exit,
bounded by a 1 s timeout, instead of racing process teardown. A large final response is
delivered whole rather than cut mid-frame.
Security
- Credential egress is now governed by a hardcoded allowlist (
*.x.com, HTTPS only) that
is independent ofX_MCP_BASE_URL. Previously the auth-attachment predicate scoped the
credential to the configured origin, which an operator-supplied base URL controls —
soX_MCP_BASE_URL=https://…together withX_MCP_ALLOW_INSECURE_BASE_URL=1would
have sent the bearer token, the refresh token, and theauthorizecode plus PKCE
verifier to that host. Two independent layers now enforce the split: the request-time
header decision withholds the credential from any non-allowlisted host (such a session
still runs, unauthenticated), and startup refuses an OAuth 2.0 session whose token
endpoint would fall outside the list, because there is no unauthenticated mode to
degrade to.X_MCP_ALLOW_INSECURE_BASE_URLrelaxes where requests go, never where
credentials go. Seedocs/04-security.mdT10 §4.4. - Policy denials no longer name the environment variable that would unlock the blocked
cell — for any cell, not just the sensitive ones. The message still names the cell (and
still carries it indata.cell), so it stays actionable; the syntax now lives only in
docs/10-operator-guide.md§6.3, where a human reads it. The previous split leaked the
half an attacker was missing:x_auth_statusalready returns the full policy matrix, so a
prompt-injected planner could read the blocked cell's name there, trigger any
low-sensitivity denial to learn the variable and its exact syntax, and relay a complete
escalation recipe. Withholding only works if it is total. - Third-party platform text is sanitized on every path into the model's context, not
just the error path.x_usage_get's degraded-200 branch quotederrors[].title/.detail
into its report note verbatim on the theory that X's own text is trusted; it now goes
through the same C0/C1, bidi and invisible-code-point strip and the same length cap as
every other untrusted string. Origin is not a property of the bytes. - The keychain helper is spawned with an explicit environment allowlist, so no
X_MCP_*
variable — and no loader-injection variable (DYLD_INSERT_LIBRARIES,LD_PRELOAD,
NODE_OPTIONS) — is inherited by the child. A variable added in future is excluded by
default rather than by someone remembering to exclude it. - Dependency advisories: a lockfile-only
npm audit fixcloses two high advisories in
the MCP SDK's transitive tree (fast-urihost confusion,ip-addressSSRF /
trust-boundary bypass) plusjs-yaml,honoandundici. Declared runtime dependencies
are unchanged (@modelcontextprotocol/sdk^1.12.0+zod^3.23.8); the installed SDK
moves 1.29.0 → 1.30.0. Neither high advisory is reachable from this server — both sit
under the SDK's HTTP/SSE transport and JSON-Schema validator, and this server is
stdio-only — but the audit gate does not grade reachability on purpose.
Decided
- OAuth 1.0a request signing is dropped (go/no-go resolved NO-GO 2026-07-31); v1 media
upload uses the OAuth 2.0/2/media/uploadpath instead. See
docs/decisions/0001-oauth1-go-no-go.md. - The eleven catalogued-but-unregistered tools are resolved 2026-08-09:
x_bookmarks_list
andx_post_hide_replyship, the other nine are cut from the v1 catalogue. Four were
audience reads thatx_search_recentalready answers withconversation_id:/
quotes_of:; two listed blocks/mutes, which an agent never needs because the
corresponding writes are absolute; three were Spaces (live audio) and WOEID trends,
both out of shape for this server. This also closes WP-3.5's spaces/trends go/no-go as
NO-GO. Seedocs/decisions/0002-remaining-catalogued-tools.md.
Platform changes absorbed
- X retired the Free/Basic/Pro subscription tiers for new developers (2026-02-06);
pay-per-use credit pricing is the only model available to the t...