X API — MCP (reads) + REST (writes), BYOK
X API — MCP(读操作)+ REST(写操作),BYOK
Connects the user's own X developer app so this agent can:
- Read via the official hosted X MCP () — 24 tools
- Write via the X v2 REST API () — post / delete / like / retweet / reply / DM
Why BYOK (not a shared Starchild app): X write access is per-app rate-limited
(~app-wide caps), pay-per-use billed to the app owner, and TOS liability sits with
the app owner. A shared app = single point of failure + neighbors starving each
other + concentrated billing + content-moderation liability. So every user brings
their own app + OAuth token. The token lives only on this machine (
,
symlinked to the persistent workspace — see STEP 3),
never proxied.
连接用户自有的X开发者应用,使Agent能够:
- 通过官方托管的X MCP()执行读操作——包含24种工具
- 通过X v2 REST API()执行写操作——发布/删除/点赞/转发/回复/发送私信
为何选择BYOK(而非共享Starchild应用):X的写访问权限按应用进行速率限制(~应用级上限),按使用量计费并由应用所有者承担,同时服务条款责任也归属于应用所有者。共享应用意味着单点故障、资源竞争、集中计费以及内容审核责任集中。因此每个用户需使用自己的应用+OAuth令牌。令牌仅存储在本地机器(
,通过符号链接指向持久化工作区——见步骤3),绝不通过代理传输。
When to use this skill
何时使用本技能
Use when the user wants to act on X with their OWN account/app:
- "Connect my X / Twitter account so you can post for me"
- "Set up the X API with my dev app", "use my X developer keys"
- "Search recent tweets / read my timeline / read mentions" (with their own app)
- "Post / reply / like / retweet / DM on X as me"
- "Bookmark this tweet"
Do NOT use — pick the right alternative instead:
- "Summarize this tweet" / "what's @user posting" / cashtag scan, no account needed
→ use the skill (read-only scraping via twitterapi.io, no OAuth)
- "Add the X / Grok model" → that's (chat model), unrelated
- The user has no X developer app and doesn't want one → skill, or explain
that writes require their own app
Disambiguator:
= read-only, no setup, anyone's public tweets.
=
the user's OWN authenticated account, can WRITE, needs one-time OAuth setup.
当用户希望使用自己的账户/应用在X上执行操作时使用:
- "连接我的X/Twitter账户,让你帮我发布内容"
- "使用我的开发者应用配置X API"、"使用我的X开发者密钥"
- "搜索最近的推文/读取我的时间线/读取提及我的内容"(使用自有应用)
- "以我的身份在X上发布/回复/点赞/转发/发送私信"
- "收藏这条推文"
请勿使用——请选择合适的替代方案:
- "总结这条推文" / "@user发布了什么" / 股票代码扫描,无需账户
→ 使用技能(通过twitterapi.io进行只读抓取,无需OAuth)
- "添加X/Grok 模型" → 请使用(聊天模型),与本技能无关
- 用户没有X开发者应用且不想创建 → 使用技能,或告知用户写操作需要自有应用
区分说明:
= 只读、无需设置、可抓取任意公开推文;
= 用户自有认证账户、可执行写操作、需一次性OAuth设置。
- skill — read-only X scraping (no account, no OAuth) for any public tweet
config/context/references/model-onboarding.md
— if the user actually meant a chat
model (Grok), not the X data API
- 技能——无需账户和OAuth的X只读抓取(适用于任意公开推文)
config/context/references/model-onboarding.md
——若用户实际指的是聊天模型(Grok)而非X数据API
Preflight — build compatibility (check before you start)
预检查——构建兼容性(开始前确认)
This skill drives two platform capabilities. Confirm both before setup:
- Native MCP client (required for reads). starchild-clawd must include
and read from agent.yaml. Quick check: run
— if it returns a status block, the client exists. If is unknown or
errors, the running build predates MCP support → reads via MCP won't work. The REST
write path (xurl) still works regardless; or update the platform image.
- Per-turn hot-reload (required for automatic token refresh). Needed so the
~2h token rotation reconnects automatically (STEP 7). Builds without it still work
but need a manual after each refresh — documented as the fallback in
STEP 7. There is no clean runtime probe; assume current builds have it, treat
"MCP silently dies ~2h after a refresh and only revives it" as the
signal that the build lacks the per-turn hook.
If neither MCP capability is present and the user only needs to read public tweets,
stop here and use the
skill instead — it needs no build support.
本技能依赖两项平台能力,请在设置前确认:
- 原生MCP客户端(读操作必需)。starchild-clawd必须包含,并从agent.yaml读取。快速检查:运行——若返回状态块,则客户端存在;若未知或报错,则当前构建不支持MCP→无法通过MCP执行读操作。REST写路径(xurl)不受影响;或更新平台镜像。
- 每轮热重载(自动令牌刷新必需)。用于实现约2小时一次的令牌轮换自动重连(步骤7)。无此功能的构建仍可使用,但每次刷新后需手动执行——步骤7中有相应的备用方案说明。目前无运行时探测方法;默认假设当前构建已具备此功能,若出现“MCP在刷新约2小时后无响应,仅可恢复”的情况,则说明构建缺少每轮钩子。
若两项MCP能力均不具备,且用户仅需读取公开推文,请停止操作并使用
技能——该技能无需构建支持。
Reads vs writes — two separate systems
读操作与写操作——两个独立系统
| Capability | Channel | Validated |
|---|
| search posts/users/news, timeline, mentions, trends, bookmarks | MCP (24 tools) | connected |
| post / delete / reply / like / retweet / follow / DM | REST (NOT in MCP) | post+delete tested |
The MCP
returns ONLY the 24 read/bookmark tools. Write endpoints are a
separate REST API documented at docs.x.com — they are NOT discoverable through
MCP. They are listed in this skill below.
| 能力 | 渠道 | 验证状态 |
|---|
| 搜索推文/用户/新闻、时间线、提及、趋势、书签 | MCP(24种工具) | 已连接 |
| 发布/删除/回复/点赞/转发/关注/发送私信 | REST (不在MCP中) | 发布+删除已测试 |
MCP的
仅返回24种读/书签工具。写端点是
独立的REST API,文档位于docs.x.com——无法通过MCP发现。本技能下方列出了这些端点。
STEP 1 — User creates OAuth 2.0 credentials in the X portal (manual; X has NO API for this)
步骤1——用户在X门户创建OAuth 2.0凭据(手动操作;X无相关API)
X exposes no API to create/configure an app. These steps are unavoidably manual in
the web portal. Make them copy-paste exact.
- Go to developer.x.com → create/open an app.
- The default keys shown (API Key / API Secret / Bearer Token) are OAuth 1.0a — NOT what we use.
- App Settings tab → User authentication settings → Set up. Fill EXACTLY:
- App permissions: Read and write (add DM if the user wants DMs)
- Type of App: Web App, Automated App or Bot → Confidential client (this is what produces a Client Secret; Native/Public gives none)
- Callback URI / Redirect URL:
http://localhost:8080/callback
(must match xurl's listener byte-for-byte)
- Website URL: (pure display metadata; not used in auth; NOT required unique — every user can use the same value, zero technical impact)
- Save → X shows Client ID + Client Secret ONCE. Secret shown once only; copy immediately.
X未提供创建/配置应用的API。这些步骤必须在网页门户中手动完成,请严格按照以下步骤操作:
- 访问developer.x.com → 创建/打开应用。
- 默认显示的密钥(API Key / API Secret / Bearer Token)为OAuth 1.0a——非本技能所需。
- 应用设置标签页 → 用户认证设置 → 设置。严格填写以下内容:
- 应用权限:读取和写入(若用户需要私信功能,请添加私信权限)
- 应用类型:Web应用、自动化应用或机器人 → 保密客户端(此类型会生成Client Secret;原生/公开客户端无此密钥)
- 回调URI/重定向URL:
http://localhost:8080/callback
(必须与xurl的监听器完全一致)
- 网站URL:(纯展示元数据;不用于认证;无需唯一——所有用户均可使用同一值,无技术影响)
- 保存 → X将一次性显示Client ID + Client Secret。Secret仅显示一次,请立即复制。
What the two URLs actually control (so you can answer the user)
两个URL的实际作用(用于解答用户疑问)
- Callback URI = where X returns the OAuth authorization code. xurl spins up a
local listener on ; the code flows browser→xurl on the user's own
machine, never through any external server. Both sides must be identical or X
returns .
- Website URL = display-only metadata. Not part of auth, not validated for
uniqueness. Safe for all users to set to .
- 回调URI:X返回OAuth授权码的地址。xurl在上启动本地监听器;授权码从浏览器流向用户本地机器上的xurl,绝不经过外部服务器。双方的回调URI必须完全一致,否则X会返回错误。
- 网站URL:仅为展示元数据。不参与认证,无需验证唯一性。所有用户均可安全设置为。
STEP 2 — Collect credentials securely
步骤2——安全收集凭据
(NEVER ask for keys in chat):
- — X App Client ID (OAuth 2.0)
- — X App Client Secret (OAuth 2.0)
Remind the user: it's the OAuth 2.0 pair (after enabling User authentication
settings + Confidential), NOT API Key/Secret (1.0a), NOT Bearer Token.
- ——X应用的Client ID(OAuth 2.0)
- ——X应用的Client Secret(OAuth 2.0)
提醒用户:需提供OAuth 2.0密钥对(启用用户认证设置+保密客户端后生成),而非API Key/Secret(1.0a)或Bearer Token。
STEP 3 — Install xurl + persist the credential store + register the app
步骤3——安装xurl + 持久化凭据存储 + 注册应用
⚠️
Persistence is REQUIRED, not optional. xurl keeps BOTH the app registration
AND the OAuth token in
(i.e.
).
is
ephemeral — it is
wiped on every container restart, so without this step the user loses the connection
(and even the app registration) on the next restart and has to re-OAuth from scratch.
Keep the real file in the persistent workspace and symlink it back each boot. xurl
writes through symlinks (verified), so every xurl call transparently uses the
persistent file — no per-command
prefix needed.
bash
npm install -g @xdevplatform/xurl # validated v1.2.2
⚠️
持久化是必需的,而非可选。xurl将应用注册信息和OAuth令牌均存储在
(即
)。
是
临时目录——每次容器重启都会被清空,因此若不执行此步骤,用户将在下次重启后丢失连接(甚至应用注册信息),需重新进行OAuth设置。请将实际文件保存在持久化工作区,并在每次启动时创建符号链接指向该文件。xurl支持通过符号链接写入(已验证),因此所有xurl调用都会透明地使用持久化文件——无需为每个命令添加
前缀。
bash
npm install -g @xdevplatform/xurl # 已验证版本v1.2.2
Persist install + credential store across container restarts (idempotent).
跨容器重启持久化安装+凭据存储(幂等操作)。
Append ONCE to workspace/setup.sh (which re-runs at every boot):
将以下内容追加到workspace/setup.sh(每次启动时会重新运行):
cat >> setup.sh <<'SH'
npm install -g @xdevplatform/xurl
cat >> setup.sh <<'SH'
npm install -g @xdevplatform/xurl
X (xurl) credential store: /root is ephemeral, keep ~/.xurl in the workspace
X(xurl)凭据存储:/root是临时目录,将~/.xurl保存在工作区
XURL_STORE=/data/workspace/.config/x-mcp/xurl_store
mkdir -p "$(dirname "$XURL_STORE")"; chmod 700 "$(dirname "$XURL_STORE")" 2>/dev/null || true
if [ -f /root/.xurl ] && [ ! -L /root/.xurl ]; then mv /root/.xurl "$XURL_STORE"; fi
ln -sf "$XURL_STORE" /root/.xurl
SH
XURL_STORE=/data/workspace/.config/x-mcp/xurl_store
mkdir -p "$(dirname "$XURL_STORE")"; chmod 700 "$(dirname "$XURL_STORE")" 2>/dev/null || true
if [ -f /root/.xurl ] && [ ! -L /root/.xurl ]; then mv /root/.xurl "$XURL_STORE"; fi
ln -sf "$XURL_STORE" /root/.xurl
SH
Run it now so the store is live for this session BEFORE registering the app:
立即运行上述脚本,使存储在本次会话中生效,然后再注册应用:
XURL_STORE=/data/workspace/.config/x-mcp/xurl_store
mkdir -p "$(dirname "$XURL_STORE")"; chmod 700 "$(dirname "$XURL_STORE")" 2>/dev/null || true
if [ -f /root/.xurl ] && [ ! -L /root/.xurl ]; then mv /root/.xurl "$XURL_STORE"; fi
ln -sf "$XURL_STORE" /root/.xurl
xurl auth apps add starchild-x
--client-id "$X_OAUTH_CLIENT_ID"
--client-secret "$X_OAUTH_CLIENT_SECRET"
--redirect-uri "
http://localhost:8080/callback"
xurl auth status # confirm app registered, redirect_uri shows [app config]
ls -la /data/workspace/.config/x-mcp/xurl_store # confirm store lives in workspace
The OAuth token from STEP 4 lands in this same persistent file, so once connected the
connection survives restarts. (`agent.yaml`'s MCP bearer is separate and already lives
in the workspace; the refresh task in STEP 7 keeps both in sync.)
XURL_STORE=/data/workspace/.config/x-mcp/xurl_store
mkdir -p "$(dirname "$XURL_STORE")"; chmod 700 "$(dirname "$XURL_STORE")" 2>/dev/null || true
if [ -f /root/.xurl ] && [ ! -L /root/.xurl ]; then mv /root/.xurl "$XURL_STORE"; fi
ln -sf "$XURL_STORE" /root/.xurl
xurl auth apps add starchild-x \
--client-id "$X_OAUTH_CLIENT_ID" \
--client-secret "$X_OAUTH_CLIENT_SECRET" \
--redirect-uri "
http://localhost:8080/callback"
xurl auth status # 确认应用已注册,redirect_uri显示[app config]
ls -la /data/workspace/.config/x-mcp/xurl_store # 确认存储文件位于工作区
步骤4生成的OAuth令牌也会存储在这个持久化文件中,因此一旦连接成功,连接状态将在重启后保留。(agent.yaml中的MCP bearer是单独存储的,且已保存在工作区;步骤7中的刷新任务会保持两者同步。)
STEP 4 — Headless OAuth (this machine has no browser → user pastes the URL back)
步骤4——无头OAuth(本机无浏览器→用户粘贴URL)
xurl auth oauth2 --headless
prints an authorize URL, then BLOCKS on stdin waiting
for the pasted code. The code is bound to a
PKCE code_verifier that lives only in
that process's memory — if the process dies before the user pastes back, the code
can never be exchanged. So the whole flow must keep ONE xurl process alive from
"print URL" all the way through "exchange code".
xurl auth oauth2 --headless
会打印授权URL,然后阻塞等待标准输入中的粘贴代码。该代码与仅存在于进程内存中的
PKCE code_verifier绑定——若用户粘贴代码前进程终止,则代码无法被交换。因此整个流程必须保持一个xurl进程从“打印URL”到“交换代码”全程存活。
Persistent driver pattern
持久化驱动模式
Run a single Python driver that holds xurl's stdin open via
,
writes the authorize URL to a file, waits up to ~10 min for a code file, then feeds
the code and captures the result. Key shape:
python
proc = subprocess.Popen(["xurl","auth","oauth2","--app","starchild-x","--headless"], # no USERNAME (see token-key note)
stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, bufsize=0)
运行单个Python驱动程序,通过
保持xurl的标准输入打开,将授权URL写入文件,等待最多约10分钟获取代码文件,然后传入代码并捕获结果。核心代码结构如下:
python
proc = subprocess.Popen(["xurl","auth","oauth2","--app","starchild-x","--headless"], # 无需USERNAME(见令牌密钥说明)
stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, bufsize=0)
1) read stdout until the oauth2/authorize line → write it to /tmp/xurl_auth_url.txt
1) 读取标准输出直到oauth2/authorize行→写入/tmp/xurl_auth_url.txt
2) poll /tmp/xurl_code_input.txt (up to 600s) for the pasted callback URL/code
2) 轮询/tmp/xurl_code_input.txt(最多600秒)获取粘贴的回调URL/代码
3) proc.stdin.write(code+"\n"); flush → 4) read result → write /tmp/xurl_oauth_done.json
3) proc.stdin.write(code+"\
**Use the driver shipped with this skill** at `skills/x-mcp/scripts/oauth_driver.py`
rather than re-writing it inline — that keeps the no-USERNAME launch and the 600s
timeout consistent. Launch it so it survives the 180s foreground limit — stdout
redirected to a file, detached, and don't block the foreground bash on it:
```bash
"); flush → 4) 读取结果→写入/tmp/xurl_oauth_done.json
**请使用本技能附带的驱动程序**,位于`skills/x-mcp/scripts/oauth_driver.py`,而非自行编写——这样可确保无USERNAME启动和600秒超时的一致性。启动时需使其不受180秒前台限制——将标准输出重定向到文件,脱离终端,且不阻塞前台bash:
```bash
clean any stale driver/xurl FIRST (see driver rule 1), then:
首先清理任何陈旧的驱动/xurl进程(见驱动规则1),然后:
python3 skills/x-mcp/scripts/oauth_driver.py >/tmp/oauth_driver_console.log 2>&1 &
sleep 8
cat /tmp/xurl_auth_url.txt # the URL THIS live driver just wrote — give the user THIS one
pgrep -f oauth_driver.py >/dev/null && echo "driver alive, waiting for code"
The driver runs `xurl auth oauth2 --app starchild-x --headless` (no USERNAME arg),
captures the authorize URL to `/tmp/xurl_auth_url.txt`, waits up to 600s (~10 min) for
`/tmp/xurl_code_input.txt`, feeds the code, and writes the outcome to
`/tmp/xurl_oauth_done.json`.
> **Token key = the resolved X handle.** Regardless of whether a USERNAME is passed,
> xurl resolves the account via `/2/users/me` and stores the token under the resolved
> X handle (e.g. `oauth2_tokens['ud_noel']`), not under an empty-string key. Read the
> key dynamically rather than hardcoding it. Use this helper everywhere a token is
> read — it expects exactly one token, and if it finds several it stops and lists the
> handles so you can confirm which account to use or clear stale ones:
> ```python
> import yaml, os
>
> def _xurl_token(app="starchild-x"):
> d = yaml.safe_load(open(os.path.expanduser("~/.xurl")))
> toks = d["apps"][app]["oauth2_tokens"] # {'<handle>': {...}}
> keys = list(toks)
> if not keys:
> raise SystemExit(f"No OAuth token for app '{app}' — run STEP 4 first.")
> if len(keys) > 1:
> raise SystemExit(
> f"Multiple X accounts authorized under '{app}': {keys}. "
> "Confirm which handle to use (or remove stale tokens) before continuing.")
> return keys[0], toks[keys[0]]["oauth2"] # (handle, {access_token, refresh_token, expiration_time})
> ```
python3 skills/x-mcp/scripts/oauth_driver.py >/tmp/oauth_driver_console.log 2>&1 &
sleep 8
cat /tmp/xurl_auth_url.txt # 此为当前运行的驱动刚生成的URL——请将此URL提供给用户
pgrep -f oauth_driver.py >/dev/null && echo "驱动已启动,等待代码输入"
驱动程序运行`xurl auth oauth2 --app starchild-x --headless`(无USERNAME参数),捕获授权URL并写入`/tmp/xurl_auth_url.txt`,等待最多600秒(约10分钟)获取`/tmp/xurl_code_input.txt`,传入代码,并将结果写入`/tmp/xurl_oauth_done.json`。
> **令牌密钥 = 解析后的X用户名**。无论是否传入USERNAME,xurl都会通过`/2/users/me`解析账户,并将令牌存储在解析后的X用户名(例如`oauth2_tokens['ud_noel']`)下,而非空字符串密钥。请动态读取密钥,而非硬编码。请在所有需要读取令牌的地方使用以下辅助函数——它会检查是否存在且仅存在一个令牌,若存在多个则停止并列出用户名,以便确认使用哪个账户或清理陈旧令牌:
> ```python
> import yaml, os
>
> def _xurl_token(app="starchild-x"):
> d = yaml.safe_load(open(os.path.expanduser("~/.xurl")))
> toks = d["apps"][app]["oauth2_tokens"] # {'<用户名>': {...}}
> keys = list(toks)
> if not keys:
> raise SystemExit(f"应用'{app}'无OAuth令牌——请先运行步骤4。")
> if len(keys) > 1:
> raise SystemExit(
> f"应用'{app}'下授权了多个X账户:{keys}。 "
> "请确认使用哪个用户名(或清理陈旧令牌)后再继续。")
> return keys[0], toks[keys[0]]["oauth2"] # (用户名, {access_token, refresh_token, expiration_time})
> ```
OAuth driver rules
OAuth驱动规则
- Run exactly one driver at a time. Before starting, kill any stale
/ PIDs by exact PID, excluding the current shell.
Avoid here — that pattern also matches the cleanup
command's own shell. Use:
bash
SELF=$$
for pat in "xurl auth oauth2" "oauth_driver.py"; do
for pid in $(pgrep -f "$pat"); do [ "$pid" != "$SELF" ] && kill "$pid" 2>/dev/null; done
done
rm -f /tmp/xurl_auth_url.txt /tmp/xurl_code_input.txt /tmp/xurl_oauth_done.json /tmp/xurl_oauth.pid
- Give the user ONLY the URL the LIVE driver just wrote — read it from
AFTER this driver started. Never paste a URL from an
earlier attempt or from chat history.
- If you ever restart, the previous authorize URL is DEAD. Tell the user to
discard it and use only the newest one.
- Verify the matches if exchange fails: the in the user's
pasted callback must equal the in the URL the live driver generated. A
mismatch means the callback came from an older authorize URL.
Tell the user, clearly, in the visible reply:
- Open the
https://x.com/i/oauth2/authorize?...
URL (the one the live driver wrote)
in a browser on ANY device.
- Click Authorize app.
- The browser redirects to
http://localhost:8080/callback?state=...&code=...
— the
page will fail to load (it's THIS remote box's localhost, the user's browser can't
reach it). That is EXPECTED. The code is in the address bar.
- Copy the full redirected URL from the address bar and paste it back in chat.
Feed it to the waiting driver:
bash
echo '<full redirected URL with code=>' > /tmp/xurl_code_input.txt
The driver exchanges it for a token in
and writes
/tmp/xurl_oauth_done.json
.
A warning "could not resolve username via /2/users/me" is NOT a failure — that's the
enrollment trap below.
- 每次仅运行一个驱动程序。启动前,杀死所有陈旧的 / 进程通过精确PID,排除当前shell。避免使用——该命令也会匹配清理命令自身的shell。请使用:
bash
SELF=$$
for pat in "xurl auth oauth2" "oauth_driver.py"; do
for pid in $(pgrep -f "$pat"); do [ "$pid" != "$SELF" ] && kill "$pid" 2>/dev/null; done
done
rm -f /tmp/xurl_auth_url.txt /tmp/xurl_code_input.txt /tmp/xurl_oauth_done.json /tmp/xurl_oauth.pid
- 仅向用户提供当前运行的驱动刚生成的URL——驱动启动后从读取。绝不要粘贴之前尝试的URL或聊天历史中的URL。
- 若重启驱动,之前的授权URL将失效。告知用户丢弃旧URL,仅使用最新的URL。
- 若交换失败,请验证是否匹配:用户粘贴的回调URL中的必须与当前驱动生成的URL中的一致。不匹配意味着回调来自旧的授权URL。
请在可见回复中清晰告知用户:
- 在任意设备的浏览器中打开
https://x.com/i/oauth2/authorize?...
URL(当前驱动刚生成的那个)。
- 点击授权应用。
- 浏览器将重定向到
http://localhost:8080/callback?state=...&code=...
——页面无法加载是正常的(这是远程机器的localhost,用户浏览器无法访问)。代码位于地址栏中。
- 复制地址栏中的完整重定向URL并粘贴回聊天。
将其传入等待中的驱动:
bash
echo '<包含code的完整重定向URL>' > /tmp/xurl_code_input.txt
驱动会将其交换为令牌并存储在
中,同时写入
/tmp/xurl_oauth_done.json
。警告“could not resolve username via /2/users/me”并非失败——这是下方提到的注册陷阱。
STEP 5 — The enrollment gate
xurl --app starchild-x /2/users/me
returns:
json
{"reason":"client-not-enrolled","title":"Client Forbidden",
"detail":"...App that is attached to a Project..."}
OAuth succeeded but the app lacks v2 API access. Fix in the portal (manual):
- Free tier: in the Dashboard you can Move to Pay-per-use directly on the
free app — that grants v2 access. The required gate is Pay-per-use /
Production enrollment. Pay-per-use may require a card on file.
- After enrolling, no re-authorization needed — token persists. Just re-run the
test. Propagation can take a minute or two.
Verify:
xurl --app starchild-x /2/users/me
must return the user object (id, name,
username), not
.
xurl --app starchild-x /2/users/me
返回:
json
{"reason":"client-not-enrolled","title":"Client Forbidden",
"detail":"...App that is attached to a Project..."}
OAuth成功,但应用缺少v2 API访问权限。请在门户中手动修复:
- 免费 tier:在仪表板中可直接将免费应用迁移到按使用付费——这将授予v2访问权限。所需的关卡是按使用付费/生产注册。按使用付费可能需要绑定银行卡。
- 注册完成后,无需重新授权——令牌将保留。只需重新运行测试即可。生效可能需要一两分钟。
验证:
xurl --app starchild-x /2/users/me
必须返回用户对象(id、name、username),而非
。
STEP 6 — Wire MCP into agent.yaml (native MCP client; NO agent code change)
步骤6——将MCP配置到agent.yaml(原生MCP客户端;无需修改Agent代码)
starchild-clawd has a native MCP client (
: stdio / streamable-http / sse +
static
). Add under
(or the user's agent.yaml):
yaml
defaults:
mcp_servers: # MAPPING keyed by server name (NOT a YAML list)
xmcp:
transport: streamable-http
url: https://api.x.com/mcp
headers:
Authorization: "Bearer <ACCESS_TOKEN_FROM_~/.xurl>"
timeout: 30
is a
mapping (server name → definition), not a list. A list
form (
) is silently ignored (logged
must be a mapping, got list
).
Then
to confirm the 24 tools register as
.
starchild-clawd拥有原生MCP客户端(
:stdio / streamable-http / sse + 静态
)。请在
(或用户的agent.yaml)下添加:
yaml
defaults:
mcp_servers: # 按服务器名称映射(非YAML列表)
xmcp:
transport: streamable-http
url: https://api.x.com/mcp
headers:
Authorization: "Bearer <来自~/.xurl的ACCESS_TOKEN>"
timeout: 30
是
映射(服务器名称→定义),而非列表。列表形式(
)会被静默忽略(日志中会显示
must be a mapping, got list
)。
STEP 7 — Token expiry & auto-refresh
步骤7——令牌过期与自动刷新
The OAuth2
access token expires in ~2 hours (
scope grants a
refresh_token, so renewal is possible). The bearer in agent.yaml
is static —
nothing refreshes the TOKEN VALUE on its own.
If you only paste the current token
into agent.yaml and never refresh, MCP dies in ~2 hours. You MUST run a refresh loop
that rewrites the bearer. (Reconnecting with the new bearer IS automatic on current
clawd — see "Automatic reload on current clawd" below — but the token itself still has to be
refreshed and written.)
Refresh with xurl, then rewrite the bearer in agent.yaml (reconnect is automatic):
OAuth2
访问令牌约2小时后过期(
权限会授予refresh_token,因此可进行续期)。agent.yaml
中的bearer是静态的——没有机制自动刷新令牌值。
若仅将当前令牌粘贴到agent.yaml而不进行刷新,MCP将在约2小时后失效。必须运行刷新循环来重写bearer。(在当前clawd版本中,使用新bearer重连是自动的——见下方“当前clawd的自动重载”——但令牌仍需刷新并写入。)
使用xurl刷新令牌,然后重写agent.yaml中的bearer(重连自动进行):
read current access token after xurl refreshes it
xurl刷新后读取当前访问令牌
xurl --app starchild-x /2/users/me >/dev/null 2>&1 # xurl auto-refreshes when near expiry on use
xurl --app starchild-x /2/users/me >/dev/null 2>&1 # xurl会在令牌即将过期时自动刷新
read the token via the _xurl_token() helper from STEP 4 (single-token check + handle-keyed lookup)
通过步骤4中的_xurl_token()辅助函数读取令牌(单令牌检查+按用户名查找)
ACCESS=$(python3 -c "
import yaml, os
d = yaml.safe_load(open(os.path.expanduser('~/.xurl')))
t = d['apps']['starchild-x']['oauth2_tokens']
keys = list(t)
assert len(keys) == 1, f'expected one X account, found {keys} — confirm/clean before refresh'
print(t[keys[0]]['oauth2']['access_token'])
")
ACCESS=$(python3 -c "
import yaml, os
d = yaml.safe_load(open(os.path.expanduser('~/.xurl')))
t = d['apps']['starchild-x']['oauth2_tokens']
keys = list(t)
assert len(keys) == 1, f'预期一个X账户,找到{keys}——刷新前请确认/清理'
print(t[keys[0]]['oauth2']['access_token'])
")
patch agent.yaml header to the fresh token (a small python/yaml rewrite).
将新令牌写入agent.yaml的header(使用python/yaml进行小范围重写)。
On current clawd that's all — the next chat turn auto-reconnects (see below).
在当前clawd版本中,仅此一步即可——下一次聊天轮次会自动重连(见下方)。
- Confidential client refresh:
POST https://api.x.com/2/oauth2/token
with HTTP
Basic auth (base64(client_id:client_secret)
) and body
grant_type=refresh_token&refresh_token=<rt>
. Returns a fresh
(expires_in 7200) AND a NEW rotated .
- refresh_token ROTATES every refresh — the old one is invalidated. You MUST
persist the new refresh_token (back into ) or the next refresh fails.
- ✅ Prefer xurl's own on-use refresh (
xurl --app starchild-x /2/users/me
): xurl
refreshes when near expiry and writes the rotated token back to itself,
through the symlink (verified) — persistence stays intact, you don't touch the file.
- 🚫 If you DO write yourself, NEVER use an atomic-rename write
( + /). Since is a symlink to the
persistent workspace store (STEP 3), a rename replaces the symlink with a regular
file in ephemeral — persistence silently breaks and the token vanishes on
the next restart. (This is the standard "safe" pattern the ChatGPT/Grok
use — correct for real files, fatal for a symlink.) Verified: clobbers
the symlink; in-place and xurl both write through it. So write
in place only:
python
# safe: follows the symlink, lands in the persistent workspace store
with open(os.path.expanduser("~/.xurl"), "w") as f:
yaml.safe_dump(data, f)
- 保密客户端刷新:向
https://api.x.com/2/oauth2/token
发送POST请求,使用HTTP Basic认证(base64(client_id:client_secret)
),请求体为grant_type=refresh_token&refresh_token=<rt>
。返回新的(有效期7200秒)以及新的轮换后的。
- refresh_token每次刷新都会轮换——旧令牌将失效。必须将新的refresh_token持久化(写回),否则下一次刷新将失败。
- ✅ 优先使用xurl自带的使用时刷新(
xurl --app starchild-x /2/users/me
):xurl会在令牌即将过期时自动刷新,并将轮换后的令牌写回,通过符号链接(已验证)——持久化保持完整,无需手动操作文件。
- 🚫 若自行写入,绝不要使用原子重命名写入( + /)。由于是指向持久化工作区存储的符号链接(步骤3),重命名会将符号链接替换为临时目录中的普通文件——持久化会静默失效,令牌将在下次重启后消失。(这是ChatGPT/Grok的使用的标准“安全”模式——对真实文件有效,但对符号链接致命。)已验证:会覆盖符号链接;原地和xurl都会通过符号链接写入。因此仅可原地写入:
python
# 安全方式:跟随符号链接,写入持久化工作区存储
with open(os.path.expanduser("~/.xurl"), "w") as f:
yaml.safe_dump(data, f)
Automatic reload on current clawd
当前clawd的自动重载
Current starchild-clawd calls
at the START of every
and
turn (mtime-gated, cheap single stat). So after the refresh task
rewrites the bearer in agent.yaml,
the very next chat turn auto-reconnects with the
new token — the user NEVER runs . The MCP server config signature
includes sorted headers, so a bearer change is correctly classified as a reconnect.
So the refresh task only needs to do TWO things:
- refresh the token (Basic auth) and persist the rotated refresh_token to
(in-place write or via xurl only — NOT atomic-rename, see the symlink warning above),
- write the new access_token into the agent.yaml bearer
(agent.yaml is already in the workspace, so atomic write is fine there).
No
, no manual step. (A scheduled task can't reload the connection
itself — task run.py is a separate process and can't touch the in-process MCP
manager singleton — but it doesn't need to: the per-turn hook handles reconnection.)
Older clawd builds without per-turn hot-reload: if running a build that predates
the per-turn
call, the live connection won't pick up the new
bearer until someone runs
— in that case have the refresh task additionally
trigger a reload, or instruct the user to run
after each ~2h refresh.
Set up the refresh as a
scheduled_task every ~60–90 min (token lives ~2h):
refresh token (Basic auth) → persist rotated refresh_token to
→ write new
access_token into the agent.yaml bearer. On current clawd that is fully automatic.
Token store path:
→
apps.starchild-x.oauth2_tokens['<resolved-handle>'].oauth2.{access_token,refresh_token,expiration_time}
.
The key is the X handle xurl resolves from
(e.g.
),
not an
empty string — read it dynamically via the
helper in STEP 4 (normally
one token per app; the helper flags the case of several so you can pick/clean).
is a symlink to the persistent
/data/workspace/.config/x-mcp/xurl_store
(set up in STEP 3) — read/write
as normal; persistence is transparent.
当前starchild-clawd会在每次
和
轮次
开始时调用
(基于修改时间的检查,成本极低)。因此刷新任务重写agent.yaml中的bearer后,
下一次聊天轮次会自动使用新令牌重连——用户无需运行。MCP服务器配置签名包含排序后的headers,因此bearer的变化会被正确识别为重连。
因此刷新任务仅需完成两件事:
- 刷新令牌(Basic认证)并将轮换后的refresh_token持久化到(仅可原地写入或通过xurl——不可使用原子重命名,见符号链接警告),
- 将新的access_token写入agent.yaml的 bearer(agent.yaml已在工作区,因此原子写入是安全的)。
无需
,无需手动步骤。(定时任务无法自行重载连接——任务run.py是独立进程,无法触及进程内的MCP管理器单例——但无需如此:每轮钩子会处理重连。)
设置定时任务,每约60–90分钟刷新一次(令牌有效期约2小时):刷新令牌(Basic认证)→ 将轮换后的refresh_token持久化到
→ 将新的access_token写入agent.yaml的bearer。在当前clawd版本中,这一过程完全自动。
令牌存储路径:
→
apps.starchild-x.oauth2_tokens['<解析后的用户名>'].oauth2.{access_token,refresh_token,expiration_time}
。密钥是xurl通过
解析得到的X用户名(例如
),
非空字符串——请通过步骤4中的
辅助函数动态读取(通常每个应用对应一个令牌;辅助函数会标记多个令牌的情况,以便选择/清理)。
是指向持久化
/data/workspace/.config/x-mcp/xurl_store
的符号链接(步骤3中设置)——正常读写
即可;持久化是透明的。
REST WRITE ENDPOINTS (not in MCP — call via xurl with the same token)
REST写端点(不在MCP中——使用同一令牌通过xurl调用)
All authenticated with the OAuth2 bearer.
handles auth+refresh automatically:
| Action | Method + endpoint | Body / notes | Status |
|---|
| Post a tweet | | | tested |
| Reply | | {"text":"...","reply":{"in_reply_to_tweet_id":"<id>"}}
| documented |
| Quote | | {"text":"...","quote_tweet_id":"<id>"}
| documented |
| Delete tweet | | — | tested |
| Like | POST /2/users/{user_id}/likes
| | documented |
| Unlike | DELETE /2/users/{user_id}/likes/{tweet_id}
| — | documented |
| Retweet | POST /2/users/{user_id}/retweets
| | documented |
| Follow | POST /2/users/{user_id}/following
| {"target_user_id":"<id>"}
| documented |
| DM | POST /2/dm_conversations/with/{participant_id}/messages
| | documented |
Example (post via xurl):
bash
xurl --app starchild-x -X POST /2/tweets -d '{"text":"hello from my agent"}'
for likes/retweets = the authed user's id from
.
Endpoints marked "documented" are from docs.x.com and not yet round-trip tested here —
verify on first real use and mark tested.
所有端点均使用OAuth2 bearer认证。xurl会自动处理认证+刷新:
| 操作 | 方法 + 端点 | 请求体 / 说明 | 状态 |
|---|
| 发布推文 | | | 已测试 |
| 回复 | | {"text":"...","reply":{"in_reply_to_tweet_id":"<id>"}}
| 已文档化 |
| 引用推文 | | {"text":"...","quote_tweet_id":"<id>"}
| 已文档化 |
| 删除推文 | | — | 已测试 |
| 点赞 | POST /2/users/{user_id}/likes
| | 已文档化 |
| 取消点赞 | DELETE /2/users/{user_id}/likes/{tweet_id}
| — | 已文档化 |
| 转发 | POST /2/users/{user_id}/retweets
| | 已文档化 |
| 关注用户 | POST /2/users/{user_id}/following
| {"target_user_id":"<id>"}
| 已文档化 |
| 发送私信 | POST /2/dm_conversations/with/{participant_id}/messages
| | 已文档化 |
示例(通过xurl发布推文):
bash
xurl --app starchild-x -X POST /2/tweets -d '{"text":"hello from my agent"}'
点赞/转发操作的
= 从
获取的认证用户id。标记为“已文档化”的端点来自docs.x.com,尚未在此处进行往返测试——首次实际使用时请验证并标记为已测试。
The 24 MCP READ tools (register as )
Search/news:
,
,
,
,
,
.
Posts:
,
,
,
,
.
Users:
,
,
,
,
,
,
.
Bookmarks (write-ish):
,
get_users_bookmarks_by_folder_id
,
get_users_bookmark_folders
,
,
create_users_bookmark_folder
,
.
Common params:
,
(comma list),
.
takes a
(supports operators like
,
,
).
搜索/新闻:
,
,
,
,
,
。
推文:
,
,
,
,
。
用户:
,
,
,
,
,
,
。
书签(类写操作):
,
get_users_bookmarks_by_folder_id
,
get_users_bookmark_folders
,
,
create_users_bookmark_folder
,
。
通用参数:
,
(逗号分隔列表),
。
接受
参数(支持
、
、
等操作符)。
- "I only see an OAuth 1.0a app / no Client ID." You haven't enabled User
authentication settings. Until you do, Keys & tokens shows only 1.0a. See STEP 1.
- . Portal Callback URI ≠
http://localhost:8080/callback
.
Must be identical.
- No Client Secret offered. Type of App was Native/Public. Switch to
Confidential (Web App / Bot).
- after OAuth succeeds. App lacks v2 access → Move to
Pay-per-use (STEP 5).
- The callback page won't load. Expected — it's the remote box's localhost. The
code is in the address bar; paste the full URL back.
- MCP worked, then stopped ~2h later. Access token expired and the bearer in
agent.yaml went stale. Set up the refresh task that rewrites the bearer (STEP 7) —
on current clawd the next chat turn then reconnects automatically, no .
- Can I post through MCP? No. Writes go through REST (see table). MCP is
reads + bookmarks only.
- “我仅看到OAuth 1.0a应用/无Client ID。” 你尚未启用用户认证设置。启用前,密钥与令牌页面仅显示1.0a密钥。见步骤1。
- 错误。 门户中的回调URI与
http://localhost:8080/callback
不一致。必须完全相同。
- 未提供Client Secret。 应用类型设置为原生/公开客户端。请切换为保密客户端(Web应用/机器人)。
- OAuth成功后出现错误。 应用缺少v2访问权限→迁移到按使用付费(步骤5)。
- 回调页面无法加载。 正常现象——这是远程机器的localhost。代码位于地址栏,请粘贴完整URL。
- MCP可正常使用,但约2小时后停止响应。 访问令牌过期,agent.yaml中的bearer失效。请设置重写bearer的刷新任务(步骤7)——在当前clawd版本中,下一次聊天轮次会自动重连,无需。
- 能否通过MCP发布内容? 不能。写操作需通过REST (见表格)。MCP仅支持读操作+书签操作。
- Never echo the access_token or refresh_token to chat. They are persistent
credentials in . When showing config, mask the bearer
(). The token never goes through the proxy or into chat history.
- Never ask for the Client ID / Secret in chat. Always collect via
(STEP 2).
- Don't auto-poll / rush the OAuth step. After printing the authorize URL, WAIT
for the user to say they approved and paste the redirected URL back. Writing the
code file () before they've pasted just makes the driver
wait on empty input.
- On , don't retry blindly. It's a portal enrollment gate
(STEP 5), not a transient error — retrying the same call keeps failing. Guide the
user to Move to Pay-per-use, then re-test once.
- Writes are REST, not MCP. Don't look for a MCP tool — it doesn't
exist. Use the REST table.
- Token rotates on every refresh. Always persist the NEW refresh_token or the
next refresh dies (STEP 7).
- 切勿在聊天中回显access_token或refresh_token。 它们是存储在中的持久化凭据。展示配置时,请屏蔽bearer(例如)。令牌绝不会通过代理传输或进入聊天历史。
- 切勿在聊天中索要Client ID/Secret。 请始终通过收集(步骤2)。
- 不要自动轮询/急于完成OAuth步骤。 打印授权URL后,请等待用户告知已授权并粘贴重定向URL。在用户粘贴前写入代码文件()只会让驱动等待空输入。
- 若出现错误,请勿盲目重试。 这是门户注册关卡(步骤5),而非临时错误——重复调用仍会失败。指导用户迁移到按使用付费,然后重新测试一次。
- 写操作通过REST完成,而非MCP。 不要寻找 MCP工具——不存在此工具。请使用REST表格中的端点。
- 令牌每次刷新都会轮换。 请始终持久化新的refresh_token,否则下一次刷新会失败(步骤7)。
Update policy (X changes → bump this skill)
更新策略(X变更→更新本技能)
Update + version-bump (then PR, never push main) when any of these change:
- MCP tool set (new/renamed/removed tools, schema changes) → MINOR
- REST write endpoints (path/body changes) → MINOR, MAJOR if a signature breaks
- X portal enrollment flow (UI/steps for Pay-per-use, auth settings) → PATCH/MINOR
- xurl CLI flags/behavior → PATCH/MINOR
出现以下任意情况时,请更新并升级版本(然后提交PR,切勿直接推送到主分支):
- MCP工具集(新增/重命名/移除工具、 schema变更)→ 次版本号升级
- REST写端点(路径/请求体变更)→ 次版本号升级,若签名中断则主版本号升级
- X门户注册流程(按使用付费的UI/步骤、认证设置)→ 修订版本/次版本号升级
- xurl CLI 参数/行为变更→ 修订版本/次版本号升级
Independence (skill-only, no agent code change)
独立性(仅需技能,无需修改Agent代码)
This skill needs
no changes to agent code. It relies on the already-native MCP
client (
) plus pure config (agent.yaml
) and a scheduled
refresh task — all orchestrated by the agent following this skill. The ONLY prereq is
that the running build includes the MCP client. Read-only scraping without the user's
own app → use the
skill instead.
Validated: xurl 1.2.2 · MCP server xmcp 1.0.0 (protocol 2025-06-18) · 24 tools ·
access-level read-write-directmessages · default redirect
http://localhost:8080/callback
.
本技能
无需修改Agent代码。它依赖已有的原生MCP客户端(
)以及纯配置(agent.yaml的
)和定时刷新任务——所有操作均由Agent按照本技能的指导完成。唯一前提是当前构建包含MCP客户端。若无需用户自有应用仅需只读抓取,请使用
技能。
已验证:xurl 1.2.2 · MCP服务器xmcp 1.0.0(协议2025-06-18)· 24种工具 · 访问权限read-write-directmessages · 默认重定向
http://localhost:8080/callback
。",