@mgcrea/mcp-x-api 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +0,0 @@
1
- {"version":3,"file":"server-BxRyFZIc.js","names":["message","isRecord","createHttpServer","isRecord","isRecord","PLACEMENTS","isRecord","isRecord","createServer"],"sources":["../src/build-info.ts","../src/client/errors.ts","../src/client/http.ts","../src/config.ts","../src/client/ads.ts","../src/client/ads-shape.ts","../src/client/tokens.ts","../src/client/oauth.ts","../src/client/auth.ts","../src/client/cache.ts","../src/client/cost.ts","../src/client/shape.ts","../src/client/x.ts","../src/compose/weighted.ts","../src/compose/intent.ts","../src/compose/open.ts","../src/tools/ads/util.ts","../src/tools/util.ts","../src/tools/ads/accounts.ts","../src/tools/ads/analytics.ts","../src/tools/ads/audiences.ts","../src/tools/ads/campaigns.ts","../src/tools/ads/targeting.ts","../src/tools/ads/index.ts","../src/tools/auth.ts","../src/tools/compose.ts","../src/tools/posts.ts","../src/tools/search.ts","../src/tools/timelines.ts","../src/tools/usage.ts","../src/tools/users.ts","../src/tools/index.ts","../src/server.ts"],"sourcesContent":["// Build-time / runtime identity for the running server. `name`/`version` are\n// read from package.json at startup (always accurate); `gitCommit` /\n// `gitCommitDate` are injected by tsdown's `define` substitution at build time\n// and fall back to \"unknown\" when running from source (e.g. vitest).\n\nimport { readFileSync } from \"node:fs\";\n\n// oxlint-disable no-underscore-dangle -- bundler-injected build-time constants.\ndeclare const __GIT_COMMIT__: string;\ndeclare const __GIT_COMMIT_DATE__: string;\n\ntype PackageJson = { name: string; version: string };\n\nconst readPackageJson = (): PackageJson => {\n try {\n const pkgUrl = new URL(\"../package.json\", import.meta.url);\n return JSON.parse(readFileSync(pkgUrl, \"utf8\")) as PackageJson;\n } catch {\n return { name: \"@mgcrea/mcp-x-api\", version: \"0.0.0\" };\n }\n};\n\nconst pkg = readPackageJson();\n\nexport type BuildInfo = {\n name: string;\n version: string;\n gitCommit: string;\n gitCommitDate: string;\n};\n\nexport const BUILD_INFO: BuildInfo = {\n name: pkg.name,\n version: pkg.version,\n gitCommit: typeof __GIT_COMMIT__ === \"string\" ? __GIT_COMMIT__ : \"unknown\",\n gitCommitDate: typeof __GIT_COMMIT_DATE__ === \"string\" ? __GIT_COMMIT_DATE__ : \"unknown\",\n};\n","/**\n * X answers errors in two shapes depending on the endpoint: a problem-details\n * object (`{ title, detail, status, type }`) on v2, and a legacy\n * `{ errors: [{ message, code }] }` on a few others. Both are modelled here so\n * the client can quote whichever it got.\n */\nexport type XApiError = {\n title?: string;\n detail?: string;\n type?: string;\n status?: number;\n message?: string;\n code?: number | string;\n /** Ads application errors carry the offending field here. */\n details?: unknown;\n /** Present on partial responses, e.g. one deleted post in a batch lookup. */\n resource_type?: string;\n parameter?: string;\n value?: string;\n};\n\nexport class XApiRequestError extends Error {\n override readonly name = \"XApiRequestError\";\n readonly status: number;\n readonly errors: XApiError[] | unknown;\n\n constructor(message: string, opts: { status: number; errors?: XApiError[] | unknown }) {\n super(message);\n this.status = opts.status;\n this.errors = opts.errors;\n }\n}\n\n/**\n * Thrown when a tool needs a logged-in user and only an app-only Bearer token\n * is available. The message carries the fix, because \"401 Unauthorized\" tells\n * you nothing about which of two credentials was missing.\n */\nexport class UserContextRequiredError extends Error {\n override readonly name = \"UserContextRequiredError\";\n\n constructor(what: string, reason?: string) {\n super(\n `${what} needs an OAuth2 user context — an app-only Bearer token cannot reach it. ` +\n `Set X_API_CLIENT_ID and run \\`x-api-mcp login\\` once (it opens a browser and stores a ` +\n `refresh token in your config directory with mode 600), then retry.` +\n (reason ? ` (${reason})` : \"\"),\n );\n }\n}\n\n/** Thrown when a write tool is reached while X_API_ALLOW_WRITES is off. */\nexport class WritesDisabledError extends Error {\n override readonly name = \"WritesDisabledError\";\n\n constructor(what: string) {\n super(\n `${what} is a write operation, but writes are disabled. Set X_API_ALLOW_WRITES=1 to enable ` +\n `mutating tools. Note that x_compose_post posts for free via a web intent and needs no ` +\n `flag at all.`,\n );\n }\n}\n\n/**\n * Thrown when the Ads API is reachable but this account cannot use it — no Ads\n * entitlement on the app, or no ads account behind the logged-in user. Separate\n * from `UserContextRequiredError` because logging in again does not fix it: the\n * missing piece is an approval, not a token.\n */\nexport class AdsAccessError extends Error {\n override readonly name = \"AdsAccessError\";\n readonly details: Record<string, unknown>;\n\n constructor(message: string, details: Record<string, unknown> = {}) {\n super(message);\n this.details = details;\n }\n}\n\n/**\n * A local guard that fires *before* the request goes out, so an agent in a loop\n * cannot spend past the ceiling. Carries the arithmetic so the number is\n * auditable rather than mysterious.\n */\nexport class BudgetExceededError extends Error {\n override readonly name = \"BudgetExceededError\";\n readonly details: Record<string, unknown>;\n\n constructor(opts: { estimateUsd: number; spentUsd: number; limitUsd: number; what: string }) {\n super(\n `${opts.what} would cost about $${opts.estimateUsd.toFixed(3)}, which takes this session ` +\n `past the $${opts.limitUsd.toFixed(2)} budget (about $${opts.spentUsd.toFixed(3)} spent ` +\n `so far). Raise or unset X_API_MONTHLY_BUDGET_USD, or ask for fewer results.`,\n );\n this.details = { ...opts };\n }\n}\n\n/**\n * A local check that failed before we sent anything to X. Carries the state it\n * read, so the caller sees why rather than just that something was wrong.\n */\nexport class PreconditionError extends Error {\n override readonly name = \"PreconditionError\";\n readonly details: Record<string, unknown>;\n\n constructor(message: string, details: Record<string, unknown> = {}) {\n super(message);\n this.details = details;\n }\n}\n","import type { Logger } from \"#/client/auth\";\n\nexport type QueryValue = string | number | boolean | string[] | undefined;\nexport type Query = Record<string, QueryValue>;\n\n/**\n * What the last response told us about how much of an endpoint's budget is left.\n *\n * `scope` and `api` are optional because the v2 API reports exactly one family\n * of rate-limit headers and has no need to distinguish them. The Ads API\n * reports three (endpoint, account and cost), so it fills them in.\n */\nexport type RateLimitSnapshot = {\n endpoint: string;\n limit?: number;\n remaining?: number;\n /** Unix seconds, as X reports it. */\n reset?: number;\n resetAt?: string;\n scope?: \"endpoint\" | \"account\" | \"cost\";\n api?: \"v2\" | \"ads\";\n};\n\n/** Anything that can report rate limits, so a tool can merge several clients. */\nexport type RateLimitReporter = {\n rateLimitStatus(): RateLimitSnapshot[];\n};\n\nexport const sleep = (ms: number): Promise<void> =>\n new Promise((resolve) => setTimeout(resolve, ms));\n\nexport const backoffMs = (attempt: number): number => Math.min(1000 * 2 ** attempt, 8000);\n\nexport const retryAfterMs = (res: Response): number | undefined => {\n const header = res.headers.get(\"Retry-After\");\n if (header === null) return undefined;\n const seconds = Number(header);\n return Number.isFinite(seconds) ? Math.max(seconds, 0) * 1000 : undefined;\n};\n\nexport const safeJsonParse = (text: string): unknown => {\n try {\n return text ? JSON.parse(text) : undefined;\n } catch {\n return text;\n }\n};\n\nexport const numberOrUndefined = (value: string | null): number | undefined => {\n if (value === null) return undefined;\n const n = Number(value);\n return Number.isFinite(n) ? n : undefined;\n};\n\n/**\n * X takes comma-separated lists for both field selection\n * (`tweet.fields=id,text,created_at`) and batch lookups (`ids=1,2,3`) — not\n * repeated keys. Same join as JSON:API happens to need, different reason.\n */\nexport const buildQuery = (query: Query | undefined): string => {\n if (!query) return \"\";\n const params = new URLSearchParams();\n for (const [key, value] of Object.entries(query)) {\n if (value === undefined) continue;\n if (Array.isArray(value)) {\n if (value.length === 0) continue;\n params.append(key, value.join(\",\"));\n continue;\n }\n params.append(key, String(value));\n }\n const qs = params.toString();\n return qs ? `?${qs}` : \"\";\n};\n\n/**\n * Collapse a concrete path to the shape X documents its rate limits against, so\n * `/2/tweets/1799…` and `/2/tweets/1798…` share one bucket instead of leaking a\n * new entry per post.\n */\nexport const endpointKey = (method: string, path: string): string =>\n `${method} ${path.replace(/\\/\\d{5,}/g, \"/:id\").replace(/\\?.*$/, \"\")}`;\n\nexport type RetryPolicy = {\n maxRetries: number;\n label: string;\n logger?: Logger | undefined;\n onUnauthorized?: (() => void) | undefined;\n};\n\n/** Run `perform` until it yields a non-retryable response or the budget runs out. */\nexport const withRetry = async (\n perform: () => Promise<Response>,\n policy: RetryPolicy,\n): Promise<Response> => {\n let attempt = 0;\n\n for (;;) {\n policy.logger?.debug?.(`[x-api] ${policy.label} (attempt ${attempt + 1})`);\n const res = await perform();\n\n if (res.status === 401 && policy.onUnauthorized && attempt < policy.maxRetries) {\n policy.logger?.warn?.(`[x-api] HTTP 401 — refreshing token and retrying`);\n policy.onUnauthorized();\n attempt += 1;\n continue;\n }\n\n if ((res.status === 429 || res.status >= 500) && attempt < policy.maxRetries) {\n const delay = retryAfterMs(res) ?? backoffMs(attempt);\n policy.logger?.warn?.(`[x-api] HTTP ${res.status} — retrying in ${delay}ms`);\n await sleep(delay);\n attempt += 1;\n continue;\n }\n\n return res;\n }\n};\n","import { readFileSync, statSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\n\nimport { z } from \"zod\";\n\nexport const DEFAULT_BASE_URL = \"https://api.x.com\";\n\nexport const DEFAULT_ADS_BASE_URL = \"https://ads-api.x.com\";\n\n/**\n * The Ads API sandbox: free, isolated, and the only sane place to exercise the\n * write tools. Note the host — X's own docs say `ads-api-sandbox.x.com`, which\n * has no DNS record at all. `ads-api-sandbox.twitter.com` is the one that\n * resolves, so this is not a typo waiting to be \"fixed\".\n */\nexport const SANDBOX_ADS_BASE_URL = \"https://ads-api-sandbox.twitter.com\";\n\n/**\n * A fixed loopback port, deliberately not an ephemeral one. Unlike most OAuth\n * providers, X matches the callback URL against the value registered in the\n * developer portal byte-for-byte, so a random port can never be authorized.\n */\nexport const DEFAULT_REDIRECT_URI = \"http://127.0.0.1:8723/callback\";\n\n/**\n * `offline.access` is what makes a refresh token come back at all — without it\n * the user has to re-login every two hours. `tweet.write` is appended at\n * startup when the paid write backend is enabled, so a read-only install never\n * asks for a permission it cannot use.\n */\nexport const DEFAULT_SCOPES = [\"tweet.read\", \"users.read\", \"bookmark.read\", \"offline.access\"];\n\n/**\n * X moved to pay-per-use on 2026-02-06; there is no free tier for new\n * developers. These are list prices in USD, overridable via the config file's\n * `pricing` key because a table baked into a schema with no escape hatch is\n * wrong the day X changes it.\n *\n * The 24h dedup window is the load-bearing rule: within one UTC day, re-reading\n * a resource id you already paid for is free. That is why the ledger keys on\n * (kind, id, utcDay) rather than counting requests.\n */\nexport const DEFAULT_PRICING = {\n postRead: 0.005,\n userRead: 0.01,\n /** Your own posts and profile — five times cheaper than reading someone else's. */\n ownedRead: 0.001,\n postCreate: 0.015,\n /** A post containing a URL costs 40x a post read. Not a typo. */\n postCreateWithUrl: 0.2,\n monthlyReadCap: 2_000_000,\n effectiveFrom: \"2026-02-06\",\n};\n\nconst PricingSchema = z\n .object({\n postRead: z.number().nonnegative().default(DEFAULT_PRICING.postRead),\n userRead: z.number().nonnegative().default(DEFAULT_PRICING.userRead),\n ownedRead: z.number().nonnegative().default(DEFAULT_PRICING.ownedRead),\n postCreate: z.number().nonnegative().default(DEFAULT_PRICING.postCreate),\n postCreateWithUrl: z.number().nonnegative().default(DEFAULT_PRICING.postCreateWithUrl),\n monthlyReadCap: z.number().int().positive().default(DEFAULT_PRICING.monthlyReadCap),\n effectiveFrom: z.string().default(DEFAULT_PRICING.effectiveFrom),\n })\n .strict();\n\nexport type Pricing = z.infer<typeof PricingSchema>;\n\nconst ConfigSchema = z\n .object({\n bearerToken: z.string().min(1).optional(),\n clientId: z.string().min(1).optional(),\n clientSecret: z.string().min(1).optional(),\n redirectUri: z.string().min(1).default(DEFAULT_REDIRECT_URI),\n scopes: z.array(z.string().min(1)).min(1).default(DEFAULT_SCOPES),\n tokenFile: z.string().min(1),\n allowWrites: z.boolean().default(false),\n writeBackend: z.enum([\"intent\", \"api\"]).default(\"intent\"),\n autoOpenBrowser: z.boolean().default(true),\n enableFullArchive: z.boolean().default(false),\n defaultMaxResults: z.number().int().min(1).max(100).default(10),\n monthlyBudgetUsd: z.number().nonnegative().optional(),\n cacheEnabled: z.boolean().default(true),\n cacheMaxEntries: z.number().int().min(0).max(100_000).default(5000),\n maxRetries: z.number().int().nonnegative().max(10).default(3),\n baseUrl: z.string().min(1).default(DEFAULT_BASE_URL),\n pricing: PricingSchema.default(DEFAULT_PRICING),\n adsEnabled: z.boolean().default(false),\n adsAllowWrites: z.boolean().default(false),\n adsBaseUrl: z.string().min(1).default(DEFAULT_ADS_BASE_URL),\n adsAccountId: z.string().min(1).optional(),\n adsMaxDownloadBytes: z.number().int().positive().default(25_000_000),\n })\n .strict()\n .superRefine((cfg, ctx) => {\n // Deliberately NOT an error when no credentials are set. An MCP server that\n // exits on startup shows up in the client as a bare \"Connection closed\",\n // with stderr swallowed — so the one message that would have explained the\n // problem never reaches anyone. Worse, it makes the free tools\n // (x_compose_post, x_validate_post, x_build_search_query) unreachable even\n // though they need no credentials at all, and leaves no way to discover\n // that OAuth needs X_API_CLIENT_ID. The server starts; `x_auth_status`\n // and the startup banner report what is missing.\n if (cfg.writeBackend === \"api\" && !cfg.clientId) {\n ctx.addIssue({\n code: \"custom\",\n message:\n \"X_API_WRITE_BACKEND=api needs a user context: set X_API_CLIENT_ID and run \" +\n \"`x-api-mcp login`. The default backend (intent) needs no credentials at all — it \" +\n \"returns an x.com/intent/tweet URL you click, which costs nothing.\",\n });\n }\n\n // The Ads API is user-context only: an app-only Bearer token cannot reach\n // /12/accounts at all. Saying so here is much cheaper than letting every\n // ads call fail with an auth error that reads like a bad token.\n if (cfg.adsEnabled && !cfg.clientId) {\n ctx.addIssue({\n code: \"custom\",\n message:\n \"X_ADS_ENABLED=1 needs an OAuth 2.0 user context: set X_API_CLIENT_ID and run \" +\n \"`x-api-mcp login`. The Ads API does not accept an app-only Bearer token.\",\n });\n }\n\n if (cfg.adsAllowWrites && !cfg.adsEnabled) {\n ctx.addIssue({\n code: \"custom\",\n message:\n \"X_ADS_ALLOW_WRITES=1 has no effect without X_ADS_ENABLED=1 — the ads tools are not \" +\n \"registered at all. Set both, or neither.\",\n });\n }\n });\n\nexport type Config = z.infer<typeof ConfigSchema>;\n\n/**\n * The on-disk config document. Keys are camelCase to mirror `Config` rather than\n * the env var names: this is a typed JSON file, not a shell.\n *\n * `.strict()` on purpose — a typo'd `clientID` must be an error. Silently\n * ignoring an unknown key looks exactly like \"that setting had no effect\",\n * which is the worst way to learn your credentials came from somewhere else.\n */\nconst FileConfigSchema = z\n .object({\n bearerToken: z.string().min(1).optional(),\n clientId: z.string().min(1).optional(),\n clientSecret: z.string().min(1).optional(),\n redirectUri: z.string().min(1).optional(),\n scopes: z.array(z.string().min(1)).min(1).optional(),\n tokenFile: z.string().min(1).optional(),\n allowWrites: z.boolean().optional(),\n writeBackend: z.enum([\"intent\", \"api\"]).optional(),\n autoOpenBrowser: z.boolean().optional(),\n enableFullArchive: z.boolean().optional(),\n defaultMaxResults: z.number().int().min(1).max(100).optional(),\n monthlyBudgetUsd: z.number().nonnegative().optional(),\n cacheEnabled: z.boolean().optional(),\n cacheMaxEntries: z.number().int().min(0).max(100_000).optional(),\n maxRetries: z.number().int().nonnegative().max(10).optional(),\n baseUrl: z.string().min(1).optional(),\n pricing: PricingSchema.optional(),\n adsEnabled: z.boolean().optional(),\n adsAllowWrites: z.boolean().optional(),\n adsBaseUrl: z.string().min(1).optional(),\n adsAccountId: z.string().min(1).optional(),\n adsMaxDownloadBytes: z.number().int().positive().optional(),\n })\n .strict();\n\nexport type FileConfig = z.infer<typeof FileConfigSchema>;\n\nconst parseBool = (value: string | undefined): boolean | undefined => {\n const t = trimmed(value);\n if (t === undefined) return undefined;\n return [\"1\", \"true\", \"yes\", \"on\"].includes(t.toLowerCase());\n};\n\nconst parseIntOpt = (value: string | undefined): number | undefined => {\n if (value === undefined || value.trim() === \"\") return undefined;\n const n = Number(value);\n return Number.isInteger(n) ? n : undefined;\n};\n\nconst parseFloatOpt = (value: string | undefined): number | undefined => {\n if (value === undefined || value.trim() === \"\") return undefined;\n const n = Number(value);\n return Number.isFinite(n) ? n : undefined;\n};\n\n/** Scopes are space-separated in OAuth but commas are what people actually type. */\nconst parseList = (value: string | undefined): string[] | undefined => {\n const t = trimmed(value);\n if (t === undefined) return undefined;\n const items = t\n .split(/[,\\s]+/)\n .map((s) => s.trim())\n .filter(Boolean);\n return items.length > 0 ? items : undefined;\n};\n\nconst trimmed = (value: string | undefined): string | undefined => {\n const t = value?.trim();\n return t ? t : undefined;\n};\n\nconst message = (err: unknown): string => (err instanceof Error ? err.message : String(err));\n\n/** `readFileSync` does not expand `~`, but it is the natural thing to write in a config file. */\nexport const expandTilde = (path: string): string =>\n path === \"~\" || path.startsWith(\"~/\") ? join(homedir(), path.slice(1)) : path;\n\n/**\n * Where the config file lives, most specific first: an explicit override, then\n * the XDG location, then the conventional `~/.config`.\n */\nexport const resolveConfigPath = (env: NodeJS.ProcessEnv = process.env): string => {\n const explicit = trimmed(env.X_API_CONFIG);\n if (explicit) return expandTilde(explicit);\n const base = trimmed(env.XDG_CONFIG_HOME) ?? join(homedir(), \".config\");\n return join(expandTilde(base), \"x-api\", \"config.json\");\n};\n\n/** The OAuth token file sits beside the config file unless told otherwise. */\nexport const resolveTokenPath = (env: NodeJS.ProcessEnv = process.env): string =>\n join(dirname(resolveConfigPath(env)), \"tokens.json\");\n\n/**\n * These files hold a bearer token or a refresh token, so being readable by\n * other users is worth saying out loud. It is a warning and not an error:\n * refusing to start would be a worse trade for someone on a single-user machine.\n */\nexport const warnIfGroupReadable = (path: string): void => {\n if (process.platform === \"win32\") return; // mode bits mean nothing here\n try {\n if (statSync(path).mode & 0o077) {\n process.stderr.write(`[x-api] ${path} is readable by other users. Run: chmod 600 ${path}\\n`);\n }\n } catch {\n // Not worth failing startup over; the read below reports anything that matters.\n }\n};\n\n/**\n * Read the config file, treating \"absent\" as \"contributes nothing\". Every other\n * failure throws and names the path, so a malformed file is never mistaken for\n * a missing one — that confusion would send you hunting for credentials that\n * were sitting right there.\n */\nconst readConfigFile = (path: string): FileConfig => {\n let raw: string;\n try {\n raw = readFileSync(path, \"utf8\");\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return {};\n throw new Error(`Could not read the config file (${path}): ${message(err)}`, { cause: err });\n }\n\n warnIfGroupReadable(path);\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (err) {\n throw new Error(`The config file (${path}) is not valid JSON: ${message(err)}`, { cause: err });\n }\n\n const result = FileConfigSchema.safeParse(parsed);\n if (!result.success) {\n const issues = result.error.issues\n .map((issue) => `${issue.path.join(\".\") || \"(root)\"}: ${issue.message}`)\n .join(\"; \");\n throw new Error(`The config file (${path}) is not valid: ${issues}`);\n }\n return result.data;\n};\n\n/**\n * Environment first, config file second, **per field** — not whole-source.\n * Docker and CI inject the environment and must keep working untouched, while a\n * one-off `X_API_ALLOW_WRITES=0` still has to override a file that says `true`.\n * Merging field by field is the only rule that gives both.\n */\nexport const loadConfig = (\n env: NodeJS.ProcessEnv = process.env,\n configPath: string = resolveConfigPath(env),\n): Config => {\n const file = readConfigFile(configPath);\n const tokenFile = trimmed(env.X_API_TOKEN_FILE) ?? file.tokenFile ?? resolveTokenPath(env);\n return ConfigSchema.parse({\n bearerToken: trimmed(env.X_API_BEARER_TOKEN) ?? file.bearerToken,\n clientId: trimmed(env.X_API_CLIENT_ID) ?? file.clientId,\n clientSecret: trimmed(env.X_API_CLIENT_SECRET) ?? file.clientSecret,\n redirectUri: trimmed(env.X_API_REDIRECT_URI) ?? file.redirectUri,\n scopes: parseList(env.X_API_SCOPES) ?? file.scopes,\n tokenFile: expandTilde(tokenFile),\n allowWrites: parseBool(env.X_API_ALLOW_WRITES) ?? file.allowWrites,\n writeBackend: trimmed(env.X_API_WRITE_BACKEND) ?? file.writeBackend,\n autoOpenBrowser: parseBool(env.X_API_AUTO_OPEN_BROWSER) ?? file.autoOpenBrowser,\n enableFullArchive: parseBool(env.X_API_ENABLE_FULL_ARCHIVE) ?? file.enableFullArchive,\n defaultMaxResults: parseIntOpt(env.X_API_DEFAULT_MAX_RESULTS) ?? file.defaultMaxResults,\n monthlyBudgetUsd: parseFloatOpt(env.X_API_MONTHLY_BUDGET_USD) ?? file.monthlyBudgetUsd,\n cacheEnabled: parseBool(env.X_API_CACHE_ENABLED) ?? file.cacheEnabled,\n cacheMaxEntries: parseIntOpt(env.X_API_CACHE_MAX_ENTRIES) ?? file.cacheMaxEntries,\n maxRetries: parseIntOpt(env.X_API_MAX_RETRIES) ?? file.maxRetries,\n baseUrl: trimmed(env.X_API_BASE_URL) ?? file.baseUrl,\n pricing: file.pricing,\n adsEnabled: parseBool(env.X_ADS_ENABLED) ?? file.adsEnabled,\n adsAllowWrites: parseBool(env.X_ADS_ALLOW_WRITES) ?? file.adsAllowWrites,\n adsBaseUrl: trimmed(env.X_ADS_BASE_URL) ?? file.adsBaseUrl,\n adsAccountId: trimmed(env.X_ADS_ACCOUNT_ID) ?? file.adsAccountId,\n adsMaxDownloadBytes: parseIntOpt(env.X_ADS_MAX_DOWNLOAD_BYTES) ?? file.adsMaxDownloadBytes,\n });\n};\n\n/**\n * Whether the ads tools should be registered. The Ads API rides the same OAuth\n * 2.0 user token as bookmarks and the home timeline — the `ads.read` /\n * `ads.write` scopes are what separate them — so a client id is the hard\n * requirement, not a second set of credentials.\n */\nexport const hasAdsAccess = (config: Config): boolean =>\n config.adsEnabled && Boolean(config.clientId);\n\n/** Whether anything at all is configured that can reach the X API. */\nexport const hasApiCredentials = (config: Config): boolean =>\n Boolean(config.bearerToken ?? config.clientId);\n\n/**\n * What to do when nothing is configured. Returned by `x_auth_status` and\n * printed at startup, because this is the state a first-time user lands in and\n * the server can no longer signal it by refusing to start.\n */\nexport const setupInstructions = (config: Config): string[] => [\n \"No X credentials are configured, so the tools that call the X API are not registered.\",\n \"The free local tools still work: x_compose_post (posts via a browser click, no credentials, \" +\n \"no cost), x_validate_post, and x_build_search_query.\",\n // The portal moved with the February 2026 pricing change; developer.x.com is\n // legacy, and sending people there is the fastest way to lose them.\n \"Create an app at https://console.x.com (this replaced the old developer.x.com portal). Both \" +\n \"credentials below are on the app's Keys and Tokens screen.\",\n \"To enable reading and search, set X_API_BEARER_TOKEN to the app's Bearer Token. That alone \" +\n \"covers post lookup, search, profiles and timelines — OAuth is not needed for any of it.\",\n \"To enable bookmarks, your home timeline and API writes, also set X_API_CLIENT_ID. When \" +\n \"creating the app choose Type of App = Native App: that makes it a public PKCE client with \" +\n `no client secret, which is what this server expects. Register the callback URL ` +\n `${config.redirectUri} byte for byte (X's docs say to use 127.0.0.1 rather than localhost), ` +\n \"then run `x-api-mcp login` or call x_auth_login.\",\n \"Enroll the app in the Pay-per-use package and the Production environment. An app left in the \" +\n \"legacy Free/Development state logs in successfully and then fails every call with 403 \" +\n \"client-not-enrolled.\",\n \"Note that X removed its free tier on 2026-02-06: creating an app is free, but reads are \" +\n \"pay-per-use and need prepurchased credits in the console.\",\n];\n\n/**\n * What to do when ads is enabled but the account cannot reach the Ads API.\n * Surfaced by `x_auth_status`, because the two steps people miss are invisible\n * from the error alone: the app needs the Ads Project attached, and any token\n * minted *before* approval does not carry the entitlement.\n */\nexport const adsSetupInstructions = (config: Config): string[] => [\n \"The Ads API is separate from the X API v2: it needs its own approval, even though it uses \" +\n \"the same OAuth 2.0 login.\",\n \"At https://console.x.com open your app, then Project Access → MANAGE → Ads Project. That \" +\n \"attaches Ads API access to the app id.\",\n \"Request Ads API access for the app using X's Ads API Access Form. Standard Access covers \" +\n \"campaigns, creatives, audiences and analytics.\",\n // The step everyone misses. An old token authenticates fine and then fails\n // every ads call, which reads as a scope problem and is not one.\n \"After approval is granted, run `x-api-mcp login` again. A token minted before approval \" +\n \"does not carry the entitlement, and re-using it fails every call.\",\n `Ads calls are billed separately from X's pay-per-use reads, so they do not appear in ` +\n `x_usage_report — but the campaigns they manage spend your advertising budget.`,\n `Point X_ADS_BASE_URL at ${SANDBOX_ADS_BASE_URL} for a free sandbox before touching a live ` +\n `account. Set X_ADS_ALLOW_WRITES=1 to register the campaign-mutating tools; without it they ` +\n `do not exist.`,\n ...(config.adsAccountId\n ? []\n : [\n \"X_ADS_ACCOUNT_ID is unset. That is fine when you have exactly one ads account — it is \" +\n \"resolved automatically — but with several you must pass accountId per call or set it.\",\n ]),\n];\n\n/**\n * The scopes actually requested at login. `tweet.write` is only asked for when\n * the paid write backend is on, so a reader never holds a permission it cannot\n * use — and the consent screen stays honest about what the server will do.\n */\nexport const effectiveScopes = (config: Config): string[] => {\n const scopes = [...config.scopes];\n if (config.allowWrites && config.writeBackend === \"api\" && !scopes.includes(\"tweet.write\")) {\n scopes.push(\"tweet.write\");\n }\n // Same rule for ads: ask for read access only when the tools are registered,\n // and for write access only when the write tools are. A read-only ads install\n // never puts \"manage your ad campaigns\" on the consent screen.\n if (config.adsEnabled && !scopes.includes(\"ads.read\")) scopes.push(\"ads.read\");\n if (config.adsEnabled && config.adsAllowWrites && !scopes.includes(\"ads.write\")) {\n scopes.push(\"ads.write\");\n }\n return scopes;\n};\n","import { gunzipSync } from \"node:zlib\";\n\nimport type { Logger, TokenProvider } from \"#/client/auth\";\nimport { PreconditionError, type XApiError, XApiRequestError } from \"#/client/errors\";\nimport {\n buildQuery,\n endpointKey,\n numberOrUndefined,\n safeJsonParse,\n withRetry,\n type Query,\n type RateLimitSnapshot,\n} from \"#/client/http\";\nimport { DEFAULT_ADS_BASE_URL } from \"#/config\";\n\nexport type AdsApiClientOptions = {\n baseUrl?: string;\n /**\n * The same provider the v2 client uses. The Ads API is always asked for the\n * `\"user\"` context: there is no app-only path to `/12/accounts`.\n */\n tokenProvider: TokenProvider;\n maxRetries?: number;\n maxDownloadBytes?: number;\n fetch?: typeof fetch;\n logger?: Logger;\n userAgent?: string;\n};\n\nexport type CursorPage<T> = {\n data: T[];\n pages: number;\n nextCursor?: string;\n totalCount?: number;\n};\n\n/** The default page size X uses. Its maximum is 1000. */\nconst DEFAULT_COUNT = 200;\n\n/**\n * Hosts the async-analytics download is allowed to reach. The URL comes out of\n * an X response rather than from us, and following a server-supplied URL\n * unchecked is an SSRF primitive — not something to leave open in a project\n * whose pitch is a small attack surface.\n */\nconst DOWNLOAD_HOSTS = [\".x.com\", \".twimg.com\", \".twitter.com\", \".amazonaws.com\"];\n\n/**\n * `endpointKey` collapses long digit runs, which is right for v2 post ids and\n * useless here: ads ids are alphanumeric (`18ce54d4x5t`). Collapse the resource\n * segments by name instead, so one bucket per endpoint rather than per entity.\n */\nconst adsEndpointKey = (method: string, path: string): string =>\n endpointKey(\n method,\n path.replace(\n /\\/(accounts|campaigns|line_items|promoted_tweets|targeting_criteria|custom_audiences|funding_instruments)\\/[A-Za-z0-9_-]+/g,\n \"/$1/:id\",\n ),\n );\n\nconst isRec = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null;\n\n/**\n * Fetch-based client for the X Ads API v12. Deliberately not an `XApiClient`\n * subclass: the two share transport concerns and nothing else. Ads paginates by\n * cursor rather than `next_token`, answers errors in two envelopes neither of\n * which is v2's problem-details, reports three families of rate-limit headers,\n * and needs its own diagnostics — the v2 prose about the Pay-per-use package is\n * actively misleading here. What genuinely is shared lives in `./http.js`.\n *\n * Writes send their parameters in the query string, never a JSON body: that is\n * what the Ads API takes on POST and PUT, and what X's own SDKs send.\n */\nexport class AdsApiClient {\n readonly sandbox: boolean;\n readonly baseUrl: string;\n private readonly tokenProvider: TokenProvider;\n private readonly maxRetries: number;\n private readonly maxDownloadBytes: number;\n private readonly fetchImpl: typeof fetch;\n private readonly logger: Logger | undefined;\n private readonly userAgent: string;\n private readonly rateLimits = new Map<string, RateLimitSnapshot>();\n\n constructor(opts: AdsApiClientOptions) {\n this.baseUrl = (opts.baseUrl ?? DEFAULT_ADS_BASE_URL).replace(/\\/+$/, \"\");\n this.sandbox = /ads-api-sandbox\\./.test(this.baseUrl);\n this.tokenProvider = opts.tokenProvider;\n this.maxRetries = opts.maxRetries ?? 3;\n this.maxDownloadBytes = opts.maxDownloadBytes ?? 25_000_000;\n this.fetchImpl = opts.fetch ?? fetch;\n this.logger = opts.logger;\n this.userAgent = opts.userAgent ?? \"mcp-x-api-js\";\n }\n\n rateLimitStatus(): RateLimitSnapshot[] {\n return [...this.rateLimits.values()];\n }\n\n /**\n * Ads reports three independent budgets, and a 429 can come from any of them.\n * Recording only the endpoint family would leave the account-level limit —\n * the one that actually bites during a bulk read — invisible.\n */\n private recordRateLimit(method: string, path: string, res: Response): void {\n const families = [\n { scope: \"endpoint\" as const, prefix: \"x-rate-limit\" },\n { scope: \"account\" as const, prefix: \"x-account-rate-limit\" },\n { scope: \"cost\" as const, prefix: \"x-cost-rate-limit\" },\n ];\n for (const { scope, prefix } of families) {\n const limit = numberOrUndefined(res.headers.get(`${prefix}-limit`));\n const remaining = numberOrUndefined(res.headers.get(`${prefix}-remaining`));\n const reset = numberOrUndefined(res.headers.get(`${prefix}-reset`));\n if (limit === undefined && remaining === undefined && reset === undefined) continue;\n const endpoint = adsEndpointKey(method, path);\n this.rateLimits.set(`${scope} ${endpoint}`, {\n endpoint,\n api: \"ads\",\n scope,\n ...(limit !== undefined ? { limit } : {}),\n ...(remaining !== undefined ? { remaining } : {}),\n ...(reset !== undefined ? { reset, resetAt: new Date(reset * 1000).toISOString() } : {}),\n });\n }\n }\n\n async request<T = unknown>(method: string, path: string, query?: Query): Promise<T> {\n const url = `${this.baseUrl}${path}${buildQuery(query)}`;\n\n const res = await withRetry(\n async () => {\n const token = await this.tokenProvider.getToken(\"user\");\n return this.fetchImpl(url, {\n method,\n headers: {\n Accept: \"application/json\",\n Authorization: `Bearer ${token}`,\n \"User-Agent\": this.userAgent,\n },\n });\n },\n {\n maxRetries: this.maxRetries,\n label: `${method} ${url}`,\n logger: this.logger,\n onUnauthorized: () => this.tokenProvider.invalidate(\"user\"),\n },\n );\n\n this.recordRateLimit(method, path, res);\n const text = await res.text();\n\n if (!res.ok) {\n throw new XApiRequestError(this.errorMessage(res, method, path, text), {\n status: res.status,\n errors: this.parseErrors(text),\n });\n }\n\n if (res.status === 204 || text.trim() === \"\") return null as T;\n return safeJsonParse(text) as T;\n }\n\n get<T = unknown>(path: string, query?: Query): Promise<T> {\n return this.request<T>(\"GET\", path, query);\n }\n\n post<T = unknown>(path: string, query?: Query): Promise<T> {\n return this.request<T>(\"POST\", path, query);\n }\n\n put<T = unknown>(path: string, query?: Query): Promise<T> {\n return this.request<T>(\"PUT\", path, query);\n }\n\n del<T = unknown>(path: string, query?: Query): Promise<T> {\n return this.request<T>(\"DELETE\", path, query);\n }\n\n /**\n * GET a collection, following `next_cursor` until the pages run out or a\n * bound is hit.\n *\n * Unlike v2, the cursor is a top-level field rather than nested under `meta`,\n * it is `null` (not absent) on the last page, and it goes back out as\n * `cursor`. Everything else about the loop matches `XApiClient.paginate`,\n * which is why this is a separate method rather than a shared one — the\n * differences are exactly the parts that matter.\n */\n async paginateCursor<T = unknown>(\n path: string,\n query: Query,\n opts: { maxItems: number; maxPages?: number },\n ): Promise<CursorPage<T>> {\n type Envelope = { data?: T[]; next_cursor?: unknown; total_count?: unknown };\n const maxPages = opts.maxPages ?? 5;\n const collected: T[] = [];\n let cursor: string | undefined;\n let pages = 0;\n let nextCursor: string | undefined;\n let totalCount: number | undefined;\n\n for (;;) {\n const res: Envelope = await this.request<Envelope>(\"GET\", path, {\n count: DEFAULT_COUNT,\n ...query,\n ...(cursor ? { cursor } : {}),\n });\n pages += 1;\n if (Array.isArray(res?.data)) collected.push(...res.data);\n if (typeof res?.total_count === \"number\") totalCount = res.total_count;\n\n const next = res?.next_cursor;\n cursor = typeof next === \"string\" && next ? next : undefined;\n nextCursor = cursor;\n\n if (!cursor || collected.length >= opts.maxItems || pages >= maxPages) break;\n }\n\n return {\n data: collected.slice(0, opts.maxItems),\n pages,\n ...(nextCursor ? { nextCursor } : {}),\n ...(totalCount !== undefined ? { totalCount } : {}),\n };\n }\n\n /**\n * Fetch a finished analytics job's result file and decompress it.\n *\n * Three things here are load-bearing. The URL is a presigned object-store\n * link on a different host, so it must go out with **no** Authorization\n * header — signing it makes the store reject it. The host is checked first,\n * because the URL came from a remote response. And both the compressed and\n * decompressed sizes are capped: a 25 MB gzip of repetitive JSON expands to\n * hundreds of megabytes, so an uncapped gunzip here is an OOM waiting for a\n * big enough report.\n */\n async downloadGzipped(url: string): Promise<{ text: string; bytes: number }> {\n let parsed: URL;\n try {\n parsed = new URL(url);\n } catch {\n throw new PreconditionError(`The analytics result URL is not a valid URL: ${url}`, { url });\n }\n const host = parsed.hostname.toLowerCase();\n if (parsed.protocol !== \"https:\" || !DOWNLOAD_HOSTS.some((d) => host.endsWith(d))) {\n throw new PreconditionError(\n `Refusing to download the analytics result from ${host}: it is not an X-owned host. ` +\n `This URL came from an API response, so an unexpected host is worth stopping on.`,\n { host, allowed: DOWNLOAD_HOSTS },\n );\n }\n\n const res = await this.fetchImpl(url, {\n headers: { Accept: \"application/json\", \"User-Agent\": this.userAgent },\n });\n if (!res.ok) {\n throw new XApiRequestError(\n `Downloading the analytics result failed: HTTP ${res.status} ${res.statusText}. These ` +\n `URLs expire — re-read the job with x_ads_get_stats_jobs for a fresh one.`,\n { status: res.status },\n );\n }\n\n const declared = numberOrUndefined(res.headers.get(\"content-length\"));\n if (declared !== undefined && declared > this.maxDownloadBytes) {\n throw new PreconditionError(\n `The analytics result is ${declared} bytes, over the ${this.maxDownloadBytes}-byte limit. ` +\n `Re-run the job over fewer entity_ids, a shorter date range, or a coarser granularity, ` +\n `or raise X_ADS_MAX_DOWNLOAD_BYTES.`,\n { bytes: declared, limit: this.maxDownloadBytes },\n );\n }\n\n const buf = Buffer.from(await res.arrayBuffer());\n if (buf.byteLength > this.maxDownloadBytes) {\n throw new PreconditionError(\n `The analytics result is ${buf.byteLength} bytes, over the ${this.maxDownloadBytes}-byte ` +\n `limit. Narrow the job, or raise X_ADS_MAX_DOWNLOAD_BYTES.`,\n { bytes: buf.byteLength, limit: this.maxDownloadBytes },\n );\n }\n\n try {\n const out = gunzipSync(buf, { maxOutputLength: this.maxDownloadBytes * 20 });\n return { text: out.toString(\"utf8\"), bytes: out.byteLength };\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ERR_BUFFER_TOO_LARGE\") {\n throw new PreconditionError(\n `The analytics result decompressed past ${this.maxDownloadBytes * 20} bytes and was ` +\n `discarded. Re-run the job over a narrower range.`,\n { limit: this.maxDownloadBytes * 20 },\n );\n }\n throw err;\n }\n }\n\n private parseErrors(text: string): XApiError[] | unknown {\n const parsed = safeJsonParse(text);\n if (isRec(parsed) && Array.isArray(parsed.errors)) return parsed.errors as XApiError[];\n return parsed;\n }\n\n /**\n * Ads answers in two envelopes. The gateway rejects bad auth with the legacy\n * v1.1 shape and a *numeric* code — and with HTTP 400, not 401. Past that,\n * application errors use CAPS_CASE string codes. Both are quoted here, and\n * each status gets the sentence that actually fixes it: a bare\n * \"UNAUTHORIZED_ACCESS\" sends people to re-check a token that is usually fine.\n */\n private errorMessage(res: Response, method: string, path: string, text: string): string {\n const base = `X Ads API ${method} ${path} failed: HTTP ${res.status} ${res.statusText}`.trim();\n const parsed = this.parseErrors(text);\n const detail = Array.isArray(parsed)\n ? parsed\n .map((e: XApiError) =>\n [e.code, e.message ?? e.detail, e.parameter].filter(Boolean).join(\" — \"),\n )\n .filter(Boolean)\n .join(\"; \")\n : \"\";\n const suffix = detail ? ` (${detail})` : \"\";\n\n // The gateway's own rejection, before any ads logic runs.\n if (res.status === 400 && /\"code\":\\s*2\\d\\d/.test(text)) {\n return (\n `${base} — X's gateway rejected the credentials outright. The access token is missing or ` +\n `malformed; run \\`x-api-mcp login\\` again${suffix}`\n );\n }\n if (res.status === 401) {\n return (\n `${base} — authenticated request refused. Most often the stored token predates your Ads ` +\n `API approval, or was minted before ads.read was in scope: run \\`x-api-mcp login\\` again ` +\n `so the new token carries the ads scopes${suffix}`\n );\n }\n if (res.status === 403) {\n // Two separate gates, and passing the first does not pass the second.\n // Attaching the Ads Project in the console enables X's own hosted Ads MCP\n // (ads-api.x.com/mcp) immediately, which makes it look like access is\n // working — but these REST endpoints stay closed until the Ads API Access\n // Form is approved by a human. Verified: one token gets 200 from /mcp and\n // UNAUTHORIZED_CLIENT_APPLICATION from /12/accounts at the same moment.\n return (\n `${base} — the token is valid, but this app is not approved for the Ads REST API. ` +\n `Attaching the Ads Project at console.x.com is only half of it: that switch enables X's ` +\n `hosted Ads MCP, while these /12/ endpoints additionally need X's Ads API Access Form to ` +\n `be approved, which is a human review rather than a toggle. Once it is granted, run ` +\n `\\`x-api-mcp login\\` again — a token minted before approval does not carry it` +\n suffix\n );\n }\n if (res.status === 404) {\n // A sandbox account id 404s against production and vice versa, and\n // nothing in the response says which side you are on.\n return (\n `${base} — no such entity at ${this.baseUrl}. Check the account id, and check you are ` +\n `pointed at the right environment: X_ADS_BASE_URL is currently ` +\n `${this.sandbox ? \"the SANDBOX\" : \"PRODUCTION\"} (${this.baseUrl}), and ids do not carry ` +\n `across${suffix}`\n );\n }\n if (res.status === 429) {\n const snapshot = [...this.rateLimits.values()].find(\n (s) => s.endpoint === adsEndpointKey(method, path),\n );\n const window = snapshot\n ? ` (${snapshot.remaining ?? 0}/${snapshot.limit ?? \"?\"} remaining on the ` +\n `${snapshot.scope} budget${snapshot.resetAt ? `, resets ${snapshot.resetAt}` : \"\"})`\n : \"\";\n return `${base} — rate limited${window}. Wait for the window to reset, or ask for less.`;\n }\n return base + suffix;\n }\n}\n","export const isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null && !Array.isArray(value);\n\n/** X states every money field in millionths of a currency unit. */\nexport const MICRO = 1_000_000;\n\nexport const toMicro = (major: number): number => Math.round(major * MICRO);\nexport const fromMicro = (micro: number): number => Math.round((micro / MICRO) * 100) / 100;\n\nconst MICRO_SUFFIX = \"_amount_local_micro\";\n\n/**\n * Pair every `*_amount_local_micro` field with the value a human would say.\n *\n * This is not cosmetic. A model that reads `daily_budget_amount_local_micro:\n * 50000000` and reasons about it concludes the budget is fifty million, and the\n * next thing it proposes is scaled by a factor of a million. The write path\n * guards against the same mistake by refusing micro inputs; this is the other\n * half, and without it the guard only covers one direction.\n *\n * The micro field is kept rather than replaced, so the raw value X returned\n * stays auditable.\n */\nexport const shapeMoney = <T>(value: T, currency?: string): T => {\n if (Array.isArray(value)) return value.map((item) => shapeMoney(item, currency)) as T;\n if (!isRecord(value)) return value;\n\n const out: Record<string, unknown> = {};\n for (const [key, raw] of Object.entries(value)) {\n out[key] = shapeMoney(raw, currency);\n if (!key.endsWith(MICRO_SUFFIX) || typeof raw !== \"number\") continue;\n const base = key.slice(0, -MICRO_SUFFIX.length);\n out[base] = fromMicro(raw);\n if (currency) out[`${base}_currency`] = currency;\n }\n return out as T;\n};\n\n/**\n * Strip the Ads API's envelope down to what a caller wants.\n *\n * Every ads response echoes the request back under `request`, which is pure\n * noise in a tool result — the caller just sent it. `next_cursor` and\n * `total_count` are lifted out by the client's pagination instead.\n */\nexport const adsData = (raw: unknown): unknown => (isRecord(raw) ? raw.data : undefined);\n","import { mkdirSync, renameSync, statSync, unlinkSync, writeFileSync } from \"node:fs\";\nimport { readFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\n\nimport { warnIfGroupReadable } from \"#/config\";\n\n/**\n * Bumped when the shape changes incompatibly. A file from a future or unknown\n * version is treated as absent rather than guessed at — re-running `login` is a\n * 20-second cost, while misreading a token file fails in confusing ways.\n */\nexport const TOKEN_FILE_VERSION = 1;\n\nexport type StoredTokens = {\n version: number;\n /** A changed client id invalidates the whole file: the tokens belong to that app. */\n clientId: string;\n scopes: string[];\n accessToken: string;\n refreshToken?: string;\n /**\n * One generation back. X invalidates the old refresh token the instant a\n * refresh succeeds, so if we crash between receiving new tokens and writing\n * them, the on-disk token is already dead. Keeping the previous one lets a\n * failed refresh retry once instead of forcing the user through a browser.\n */\n previousRefreshToken?: string;\n /** Milliseconds since epoch. */\n expiresAt: number;\n obtainedAt: number;\n userId?: string;\n username?: string;\n};\n\nexport type TokenStore = {\n read(): StoredTokens | undefined;\n write(tokens: StoredTokens): void;\n clear(): void;\n path: string;\n};\n\nconst message = (err: unknown): string => (err instanceof Error ? err.message : String(err));\n\nexport const createTokenStore = (path: string): TokenStore => ({\n path,\n\n read() {\n let raw: string;\n try {\n raw = readFileSync(path, \"utf8\");\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return undefined;\n throw new Error(`Could not read the token file (${path}): ${message(err)}`, { cause: err });\n }\n\n warnIfGroupReadable(path);\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n // A corrupt token file is recoverable by logging in again, so this is a\n // warning path rather than a fatal one.\n process.stderr.write(`[x-api] ${path} is not valid JSON — run \\`x-api-mcp login\\` again.\\n`);\n return undefined;\n }\n\n if (\n typeof parsed !== \"object\" ||\n parsed === null ||\n (parsed as StoredTokens).version !== TOKEN_FILE_VERSION\n ) {\n return undefined;\n }\n return parsed as StoredTokens;\n },\n\n write(tokens) {\n mkdirSync(dirname(path), { recursive: true, mode: 0o700 });\n // Write to a temp file and rename: the rename is atomic, so a concurrent\n // reader never sees a half-written file, and the mode is 0600 from the\n // first byte rather than briefly world-readable.\n const tmp = join(dirname(path), `.tokens.${process.pid}.tmp`);\n writeFileSync(tmp, `${JSON.stringify(tokens, null, 2)}\\n`, { mode: 0o600 });\n renameSync(tmp, path);\n },\n\n clear() {\n try {\n unlinkSync(path);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") throw err;\n }\n },\n});\n\n/** True when the stored tokens cannot serve the app or scopes we now need. */\nexport const tokensAreStale = (\n tokens: StoredTokens | undefined,\n clientId: string,\n requiredScopes: string[],\n): { stale: true; reason: string } | { stale: false } => {\n if (!tokens) return { stale: true, reason: \"no stored tokens\" };\n if (tokens.clientId !== clientId) {\n return { stale: true, reason: \"the stored tokens belong to a different X_API_CLIENT_ID\" };\n }\n const missing = requiredScopes.filter((scope) => !tokens.scopes.includes(scope));\n if (missing.length > 0) {\n // Better to say so now than to let X answer 403 for a reason the user\n // cannot see from the error.\n return { stale: true, reason: `the stored tokens lack the scope(s): ${missing.join(\", \")}` };\n }\n return { stale: false };\n};\n\n/** Mode bits of the token file, for tests and `x_auth_status`. */\nexport const fileMode = (path: string): number | undefined => {\n try {\n return statSync(path).mode & 0o777;\n } catch {\n return undefined;\n }\n};\n","import { createHash, randomBytes, timingSafeEqual } from \"node:crypto\";\nimport { createServer as createHttpServer } from \"node:http\";\n\nimport type { Logger } from \"#/client/auth\";\nimport { PreconditionError } from \"#/client/errors\";\nimport type { StoredTokens, TokenStore } from \"#/client/tokens\";\nimport { TOKEN_FILE_VERSION } from \"#/client/tokens\";\nimport type { Config } from \"#/config\";\nimport { effectiveScopes } from \"#/config\";\n\nexport const AUTHORIZE_URL = \"https://x.com/i/oauth2/authorize\";\nconst TOKEN_PATH = \"/2/oauth2/token\";\nconst CALLBACK_TIMEOUT_MS = 120_000;\n\nconst base64url = (buf: Buffer): string =>\n buf.toString(\"base64\").replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\");\n\nexport type PkcePair = { verifier: string; challenge: string };\n\n/**\n * PKCE S256. 32 random bytes base64url-encode to exactly 43 characters, the\n * minimum the spec allows for a verifier.\n */\nexport const createPkcePair = (random: (n: number) => Buffer = randomBytes): PkcePair => {\n const verifier = base64url(random(32));\n const challenge = base64url(createHash(\"sha256\").update(verifier).digest());\n return { verifier, challenge };\n};\n\nexport const buildAuthorizeUrl = (opts: {\n clientId: string;\n redirectUri: string;\n scopes: string[];\n state: string;\n challenge: string;\n}): string => {\n const params = new URLSearchParams({\n response_type: \"code\",\n client_id: opts.clientId,\n redirect_uri: opts.redirectUri,\n // Scopes are space-separated in OAuth 2.0, unlike almost everything else\n // in the X API, which uses commas.\n scope: opts.scopes.join(\" \"),\n state: opts.state,\n code_challenge: opts.challenge,\n code_challenge_method: \"S256\",\n });\n return `${AUTHORIZE_URL}?${params.toString()}`;\n};\n\n/** Constant-time compare, so a mismatched state cannot be probed byte by byte. */\nconst statesMatch = (a: string, b: string): boolean => {\n const left = Buffer.from(a);\n const right = Buffer.from(b);\n return left.length === right.length && timingSafeEqual(left, right);\n};\n\nexport type TokenResponse = {\n access_token: string;\n refresh_token?: string;\n expires_in?: number;\n scope?: string;\n token_type?: string;\n};\n\nexport type OAuthClient = {\n exchangeCode(code: string, verifier: string): Promise<TokenResponse>;\n refresh(refreshToken: string): Promise<TokenResponse>;\n};\n\nconst formPost = async (\n fetchImpl: typeof fetch,\n config: Config,\n body: URLSearchParams,\n): Promise<TokenResponse> => {\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n Accept: \"application/json\",\n };\n // Confidential clients authenticate with Basic; public PKCE clients send only\n // client_id in the body. X accepts either depending on how the app is set up.\n if (config.clientSecret) {\n const basic = Buffer.from(`${config.clientId}:${config.clientSecret}`).toString(\"base64\");\n headers.Authorization = `Basic ${basic}`;\n }\n\n const res = await fetchImpl(`${config.baseUrl.replace(/\\/+$/, \"\")}${TOKEN_PATH}`, {\n method: \"POST\",\n headers,\n body: body.toString(),\n });\n const text = await res.text();\n if (!res.ok) {\n throw new Error(\n `X rejected the OAuth token request: HTTP ${res.status} — ${text.slice(0, 400)}`,\n );\n }\n return JSON.parse(text) as TokenResponse;\n};\n\nexport const createOAuthClient = (config: Config, fetchImpl: typeof fetch = fetch): OAuthClient => {\n if (!config.clientId) {\n throw new PreconditionError(\"X_API_CLIENT_ID is required for the OAuth2 flow.\");\n }\n const clientId = config.clientId;\n\n return {\n exchangeCode: (code, verifier) =>\n formPost(\n fetchImpl,\n config,\n new URLSearchParams({\n grant_type: \"authorization_code\",\n code,\n redirect_uri: config.redirectUri,\n code_verifier: verifier,\n client_id: clientId,\n }),\n ),\n refresh: (refreshToken) =>\n formPost(\n fetchImpl,\n config,\n new URLSearchParams({\n grant_type: \"refresh_token\",\n refresh_token: refreshToken,\n client_id: clientId,\n }),\n ),\n };\n};\n\nexport const toStoredTokens = (\n res: TokenResponse,\n opts: {\n clientId: string;\n requestedScopes: string[];\n now: number;\n previousRefreshToken?: string | undefined;\n userId?: string | undefined;\n username?: string | undefined;\n },\n): StoredTokens => ({\n version: TOKEN_FILE_VERSION,\n clientId: opts.clientId,\n scopes: res.scope ? res.scope.split(/\\s+/).filter(Boolean) : opts.requestedScopes,\n accessToken: res.access_token,\n ...(res.refresh_token ? { refreshToken: res.refresh_token } : {}),\n ...(opts.previousRefreshToken ? { previousRefreshToken: opts.previousRefreshToken } : {}),\n // X access tokens last about two hours; default conservatively if unstated.\n expiresAt: opts.now + (res.expires_in ?? 7200) * 1000,\n obtainedAt: opts.now,\n ...(opts.userId ? { userId: opts.userId } : {}),\n ...(opts.username ? { username: opts.username } : {}),\n});\n\nconst SUCCESS_PAGE = `<!doctype html><meta charset=\"utf-8\"><title>x-api-mcp</title>\n<body style=\"font-family:system-ui;max-width:32rem;margin:4rem auto;line-height:1.5\">\n<h1>Signed in</h1><p>x-api-mcp stored your token. You can close this tab and return to your terminal.</p>\n</body>`;\n\nconst FAILURE_PAGE = `<!doctype html><meta charset=\"utf-8\"><title>x-api-mcp</title>\n<body style=\"font-family:system-ui;max-width:32rem;margin:4rem auto;line-height:1.5\">\n<h1>Sign-in failed</h1><p>Check the terminal running x-api-mcp for the reason.</p>\n</body>`;\n\n/**\n * Run the browser half of the flow: open the authorize URL, listen on the\n * loopback port for exactly one callback, and hand back the code.\n *\n * The port is fixed rather than ephemeral because X matches the redirect URI\n * against the value registered in the developer portal byte for byte — an\n * ephemeral port could never be authorized.\n */\nexport const awaitCallback = (opts: {\n redirectUri: string;\n state: string;\n timeoutMs?: number;\n logger?: Logger | undefined;\n}): { url: Promise<never> | undefined; code: Promise<string>; close: () => void } => {\n const url = new URL(opts.redirectUri);\n const port = Number(url.port);\n const expectedPath = url.pathname;\n\n let settle: { resolve: (code: string) => void; reject: (err: Error) => void };\n const code = new Promise<string>((resolve, reject) => {\n settle = { resolve, reject };\n });\n\n const server = createHttpServer((req, res) => {\n const requestUrl = new URL(req.url ?? \"/\", `http://127.0.0.1:${port}`);\n if (requestUrl.pathname !== expectedPath) {\n res.writeHead(404).end(\"Not found\");\n return;\n }\n\n const returnedState = requestUrl.searchParams.get(\"state\") ?? \"\";\n const returnedCode = requestUrl.searchParams.get(\"code\");\n const error = requestUrl.searchParams.get(\"error\");\n\n if (error) {\n res.writeHead(400, { \"content-type\": \"text/html\" }).end(FAILURE_PAGE);\n settle.reject(new Error(`X denied the authorization: ${error}`));\n return;\n }\n if (!statesMatch(returnedState, opts.state)) {\n res.writeHead(400, { \"content-type\": \"text/html\" }).end(FAILURE_PAGE);\n settle.reject(\n new Error(\"The callback did not come from the login that was started (state mismatch).\"),\n );\n return;\n }\n if (!returnedCode) {\n res.writeHead(400, { \"content-type\": \"text/html\" }).end(FAILURE_PAGE);\n settle.reject(new Error(\"The callback carried no authorization code.\"));\n return;\n }\n\n res.writeHead(200, { \"content-type\": \"text/html\" }).end(SUCCESS_PAGE);\n settle.resolve(returnedCode);\n });\n\n const timer = setTimeout(() => {\n settle.reject(\n new Error(\n `No callback arrived within ${(opts.timeoutMs ?? CALLBACK_TIMEOUT_MS) / 1000}s. ` +\n `Re-run the login and complete it in the browser.`,\n ),\n );\n }, opts.timeoutMs ?? CALLBACK_TIMEOUT_MS);\n timer.unref?.();\n\n server.on(\"error\", (err: NodeJS.ErrnoException) => {\n settle.reject(\n err.code === \"EADDRINUSE\"\n ? new Error(\n `Port ${port} is already in use, so the OAuth callback cannot be received. Free it, ` +\n `or set X_API_REDIRECT_URI to another loopback URL — and register that exact URL ` +\n `in the X developer console at console.x.com, which must match byte for byte.`,\n )\n : err,\n );\n });\n server.listen(port, \"127.0.0.1\");\n\n const close = (): void => {\n clearTimeout(timer);\n server.close();\n };\n void code.finally(close).catch(() => {});\n\n return { url: undefined, code, close };\n};\n\nexport type LoginResult = { tokens: StoredTokens; authorizeUrl: string };\n\n/** The whole login: PKCE, browser, callback, exchange, identify, persist. */\nexport const startLoginFlow = async (opts: {\n config: Config;\n store: TokenStore;\n fetch?: typeof fetch;\n openBrowser?: (url: string) => Promise<{ opened: boolean; reason?: string }>;\n logger?: Logger;\n now?: () => number;\n timeoutMs?: number;\n}): Promise<LoginResult> => {\n const { config, store } = opts;\n if (!config.clientId) {\n throw new PreconditionError(\n \"X_API_CLIENT_ID is required to log in. Create an OAuth 2.0 app in the X developer \" +\n \"portal, enable PKCE, and add this exact callback URL: \" +\n config.redirectUri,\n );\n }\n\n const fetchImpl = opts.fetch ?? fetch;\n const now = opts.now ?? Date.now;\n const scopes = effectiveScopes(config);\n const { verifier, challenge } = createPkcePair();\n const state = base64url(randomBytes(16));\n const authorizeUrl = buildAuthorizeUrl({\n clientId: config.clientId,\n redirectUri: config.redirectUri,\n scopes,\n state,\n challenge,\n });\n\n const listener = awaitCallback({\n redirectUri: config.redirectUri,\n state,\n ...(opts.timeoutMs !== undefined ? { timeoutMs: opts.timeoutMs } : {}),\n ...(opts.logger ? { logger: opts.logger } : {}),\n });\n\n // Print before opening: on a headless box the printed URL is the whole flow.\n opts.logger?.warn?.(`Open this URL to authorize x-api-mcp:\\n${authorizeUrl}`);\n if (opts.openBrowser) await opts.openBrowser(authorizeUrl);\n\n const code = await listener.code;\n const oauth = createOAuthClient(config, fetchImpl);\n const res = await oauth.exchangeCode(code, verifier);\n\n // One owned read ($0.001) to learn who just logged in, so x_auth_status and\n // the timeline tools do not have to ask again.\n let userId: string | undefined;\n let username: string | undefined;\n try {\n const me = await fetchImpl(`${config.baseUrl.replace(/\\/+$/, \"\")}/2/users/me`, {\n headers: { Authorization: `Bearer ${res.access_token}`, Accept: \"application/json\" },\n });\n const body = (await me.json()) as { data?: { id?: string; username?: string } };\n userId = body.data?.id;\n username = body.data?.username;\n } catch {\n // Identity is a convenience, not a requirement — the token works regardless.\n }\n\n const tokens = toStoredTokens(res, {\n clientId: config.clientId,\n requestedScopes: scopes,\n now: now(),\n ...(userId ? { userId } : {}),\n ...(username ? { username } : {}),\n });\n store.write(tokens);\n return { tokens, authorizeUrl };\n};\n","import { UserContextRequiredError } from \"#/client/errors\";\nimport type { OAuthClient } from \"#/client/oauth\";\nimport { toStoredTokens } from \"#/client/oauth\";\nimport type { StoredTokens, TokenStore } from \"#/client/tokens\";\nimport { tokensAreStale } from \"#/client/tokens\";\n\nexport type Logger = {\n debug?: (message: string) => void;\n warn?: (message: string) => void;\n error?: (message: string) => void;\n};\n\n/**\n * X has two credentials that are not interchangeable, so every request has to\n * say which one it wants:\n *\n * - `\"app\"` — the app-only Bearer token. Reaches everything public: post\n * lookup, search, user profiles, user timelines.\n * - `\"user\"` — an OAuth2 access token for a logged-in account. The only way to\n * reach bookmarks and the home timeline, and the only way to write.\n *\n * This is the one place the shape diverges from a single-credential API: the\n * token provider is asked for a context rather than just \"the token\".\n */\nexport type AuthContext = \"app\" | \"user\";\n\nexport type UserAuthStatus =\n | { authenticated: false; reason: string }\n | {\n authenticated: true;\n username?: string;\n userId?: string;\n scopes: string[];\n expiresAt: number;\n };\n\nexport type AuthStatus = {\n app: boolean;\n user: UserAuthStatus;\n};\n\nexport type TokenProvider = {\n /** Bearer value for the requested context. Throws `UserContextRequiredError` if unavailable. */\n getToken(context: AuthContext): Promise<string>;\n /** Called on a 401 to force the next call to remint or refresh. */\n invalidate(context: AuthContext): void;\n /** Powers `x_auth_status` and the startup banner. */\n describe(): AuthStatus;\n};\n\n/**\n * The app-only Bearer token, copied from the developer portal. It never\n * expires and cannot be reminted, so `invalidate` is a no-op: a 401 here means\n * the token is wrong, and retrying with the same string would only burn the\n * retry budget.\n */\nexport const bearerTokenProvider = (token: string): TokenProvider => ({\n getToken: async (context) => {\n if (context === \"user\") {\n throw new UserContextRequiredError(\n \"This tool\",\n \"only an app-only Bearer token is configured\",\n );\n }\n return token;\n },\n invalidate: () => {},\n describe: () => ({\n app: true,\n user: { authenticated: false, reason: \"no OAuth2 client id configured\" },\n }),\n});\n\n/**\n * An OAuth2 user token, refreshed on demand.\n *\n * Two rules make this safe against X's rotating refresh tokens, which are\n * invalidated the instant a refresh succeeds:\n *\n * 1. **Persist before use.** The new pair is written to disk before the access\n * token is handed to the caller. Handing it out first and then crashing\n * leaves the on-disk refresh token already dead, forcing a re-login.\n * 2. **Keep one generation.** A refresh that fails on the current token is\n * retried once with `previousRefreshToken`, which recovers exactly the\n * crash-between-response-and-write window rather than dumping the user back\n * into a browser.\n *\n * A single in-flight promise coordinates concurrent callers: unlike a locally\n * signed JWT, an OAuth refresh is a network call that must not be issued twice\n * — the second would present a token the first had just invalidated.\n */\nexport const userTokenProvider = (opts: {\n store: TokenStore;\n oauth: OAuthClient;\n clientId: string;\n requiredScopes: string[];\n logger?: Logger | undefined;\n now?: () => number;\n}): TokenProvider => {\n const now = opts.now ?? Date.now;\n /** Refresh a minute early, so a token cannot expire mid-flight. */\n const SKEW_MS = 60_000;\n let inFlight: Promise<string> | undefined;\n\n const refresh = async (tokens: StoredTokens): Promise<string> => {\n const candidates = [tokens.refreshToken, tokens.previousRefreshToken].filter(\n (t): t is string => typeof t === \"string\" && t.length > 0,\n );\n if (candidates.length === 0) {\n throw new UserContextRequiredError(\n \"This tool\",\n \"the stored token has expired and carries no refresh token — the `offline.access` \" +\n \"scope was probably not granted\",\n );\n }\n\n let lastError: unknown;\n for (const [index, candidate] of candidates.entries()) {\n try {\n const res = await opts.oauth.refresh(candidate);\n const next = toStoredTokens(res, {\n clientId: opts.clientId,\n requestedScopes: tokens.scopes,\n now: now(),\n previousRefreshToken: candidate,\n ...(tokens.userId ? { userId: tokens.userId } : {}),\n ...(tokens.username ? { username: tokens.username } : {}),\n });\n // Rule 1: on disk before it is used.\n opts.store.write(next);\n return next.accessToken;\n } catch (err) {\n lastError = err;\n if (index === 0 && candidates.length > 1) {\n opts.logger?.warn?.(\n \"[x-api] refresh failed on the current token; retrying with the previous generation\",\n );\n }\n }\n }\n throw new UserContextRequiredError(\n \"This tool\",\n `refreshing the stored token failed (${lastError instanceof Error ? lastError.message : String(lastError)})`,\n );\n };\n\n const resolve = async (): Promise<string> => {\n const tokens = opts.store.read();\n const staleness = tokensAreStale(tokens, opts.clientId, opts.requiredScopes);\n if (staleness.stale) throw new UserContextRequiredError(\"This tool\", staleness.reason);\n\n const current = tokens as StoredTokens;\n if (current.expiresAt - SKEW_MS > now()) return current.accessToken;\n return refresh(current);\n };\n\n return {\n getToken: async (context) => {\n if (context === \"app\") {\n // An OAuth2 user token can read everything an app-only token can, so\n // serving app-context reads from it is correct, not a fallback hack.\n return resolve();\n }\n if (!inFlight) {\n inFlight = resolve().finally(() => {\n inFlight = undefined;\n });\n }\n return inFlight;\n },\n invalidate: () => {\n // Force the next call to refresh by expiring the cached copy on disk.\n const tokens = opts.store.read();\n if (tokens) opts.store.write({ ...tokens, expiresAt: 0 });\n inFlight = undefined;\n },\n describe: () => {\n const tokens = opts.store.read();\n const staleness = tokensAreStale(tokens, opts.clientId, opts.requiredScopes);\n if (staleness.stale) {\n return { app: false, user: { authenticated: false, reason: staleness.reason } };\n }\n const current = tokens as StoredTokens;\n return {\n app: false,\n user: {\n authenticated: true,\n ...(current.username ? { username: current.username } : {}),\n ...(current.userId ? { userId: current.userId } : {}),\n scopes: current.scopes,\n expiresAt: current.expiresAt,\n },\n };\n },\n };\n};\n\n/**\n * Combine whichever providers are configured, dispatching by context. Either\n * side may be absent — a Bearer-only install is the common case, and a\n * user-only install is legitimate too (an OAuth2 access token can read\n * everything an app-only token can).\n */\nexport const compositeTokenProvider = (parts: {\n app?: TokenProvider;\n user?: TokenProvider;\n}): TokenProvider => ({\n getToken: async (context) => {\n if (context === \"user\") {\n if (!parts.user) {\n throw new UserContextRequiredError(\"This tool\", \"no OAuth2 client id is configured\");\n }\n return parts.user.getToken(\"user\");\n }\n // Public reads work with either credential, so fall back to the user token\n // rather than failing when only OAuth2 is set up.\n if (parts.app) return parts.app.getToken(\"app\");\n if (parts.user) return parts.user.getToken(\"user\");\n throw new Error(\n \"No credentials configured. Set X_API_BEARER_TOKEN, or X_API_CLIENT_ID and run \" +\n \"`x-api-mcp login`.\",\n );\n },\n invalidate: (context) => {\n if (context === \"user\") parts.user?.invalidate(\"user\");\n else parts.app?.invalidate(\"app\");\n },\n describe: () => ({\n app: parts.app?.describe().app ?? false,\n user: parts.user?.describe().user ?? {\n authenticated: false,\n reason: \"no OAuth2 client id configured\",\n },\n }),\n});\n\n/** The test double: one token, both contexts, no network. */\nexport const staticTokenProvider = (token: string): TokenProvider => ({\n getToken: async () => token,\n invalidate: () => {},\n describe: () => ({\n app: true,\n user: { authenticated: true, username: \"test\", userId: \"1\", scopes: [], expiresAt: 0 },\n }),\n});\n","// A response cache that exists for a pricing reason, not just a latency one.\n//\n// X bills per resource read, but deduplicates within a 24-hour UTC window: if\n// you read post 123 twice on the same UTC day, you pay once. Caching by\n// (kind, id, utcDay) therefore mirrors X's own billing rule exactly — a hit is\n// free because it *would* have been free anyway.\n\nexport type ResourceKind = \"post\" | \"user\" | \"owned\";\n\nexport type CacheStats = {\n day: string;\n entries: number;\n hits: number;\n misses: number;\n hit_rate: number;\n};\n\nexport type DayCache = {\n get(kind: ResourceKind, id: string): unknown | undefined;\n set(kind: ResourceKind, id: string, value: unknown): void;\n stats(): CacheStats;\n};\n\n/**\n * The dedup window is the UTC calendar day, not a rolling 24 hours — so the\n * whole cache turns over at once at UTC midnight rather than expiring entry by\n * entry. Comparing a stored day string is both cheaper and more faithful than\n * per-entry timestamps.\n */\nexport const utcDay = (now: number): string => new Date(now).toISOString().slice(0, 10);\n\nconst keyOf = (kind: ResourceKind, id: string): string => `${kind}:${id}`;\n\nexport const createDayCache = (opts: {\n maxEntries: number;\n enabled?: boolean;\n now?: () => number;\n}): DayCache => {\n const now = opts.now ?? Date.now;\n const enabled = opts.enabled ?? true;\n let day = utcDay(now());\n let hits = 0;\n let misses = 0;\n // Insertion-ordered `Map` doubles as the LRU: re-inserting on a hit moves an\n // entry to the back, so the oldest key is always the first one iterated.\n const entries = new Map<string, unknown>();\n\n const rollover = (): void => {\n const today = utcDay(now());\n if (today !== day) {\n entries.clear();\n day = today;\n }\n };\n\n return {\n get(kind, id) {\n if (!enabled) return undefined;\n rollover();\n const key = keyOf(kind, id);\n if (!entries.has(key)) {\n misses += 1;\n return undefined;\n }\n hits += 1;\n const value = entries.get(key);\n entries.delete(key);\n entries.set(key, value);\n return value;\n },\n set(kind, id, value) {\n if (!enabled || opts.maxEntries === 0) return;\n rollover();\n const key = keyOf(kind, id);\n entries.delete(key);\n entries.set(key, value);\n while (entries.size > opts.maxEntries) {\n const oldest = entries.keys().next();\n if (oldest.done) break;\n entries.delete(oldest.value);\n }\n },\n stats() {\n rollover();\n const total = hits + misses;\n return {\n day,\n entries: entries.size,\n hits,\n misses,\n hit_rate: total === 0 ? 0 : Math.round((hits / total) * 100) / 100,\n };\n },\n };\n};\n","import { utcDay, type CacheStats, type ResourceKind } from \"#/client/cache\";\nimport type { Pricing } from \"#/config\";\n\n/**\n * Which resource ids we have already been billed for today.\n *\n * This is deliberately NOT the response cache. The cache stores payloads and is\n * LRU-bounded; this stores only ids and is unbounded. If they were one\n * structure, evicting a payload under memory pressure would make the estimator\n * re-count an id X already charged us for, and over-report spend. Ids are ~20\n * bytes, so keeping every one for a day costs kilobytes in practice.\n */\nexport type Ledger = {\n /** Split ids into the ones today's read actually bills for and the ones already paid. */\n record(kind: ResourceKind, ids: string[]): { billable: string[]; free: string[] };\n /** Estimate a read before issuing it, for the budget guard. */\n estimate(kind: ResourceKind, ids: string[]): number;\n /** Estimate by count, for reads whose result ids are unknown in advance (searches). */\n estimateCount(kind: ResourceKind, count: number): number;\n recordCreate(hasUrl: boolean): void;\n spentUsd(): number;\n report(cache: CacheStats): UsageReport;\n};\n\nexport type CostNote = {\n billable_post_reads?: number;\n billable_user_reads?: number;\n owned_reads?: number;\n free_from_cache?: number;\n estimated_usd: number;\n note?: string;\n};\n\nexport type UsageReport = {\n day: string;\n since_process_start: {\n billable_post_reads: number;\n billable_user_reads: number;\n owned_reads: number;\n free_from_dedup: number;\n posts_created: number;\n estimated_usd: number;\n };\n read_cap: { monthly_cap: number; reads_this_session: number; cap_used_pct: number };\n budget?: { limit_usd: number; remaining_usd: number };\n cache: CacheStats;\n pricing: Pricing;\n disclaimer: string;\n};\n\nconst rate = (pricing: Pricing, kind: ResourceKind): number =>\n kind === \"post\" ? pricing.postRead : kind === \"user\" ? pricing.userRead : pricing.ownedRead;\n\nconst round = (n: number): number => Math.round(n * 1000) / 1000;\n\nexport const createLedger = (opts: {\n pricing: Pricing;\n budgetUsd?: number | undefined;\n now?: () => number;\n}): Ledger => {\n const now = opts.now ?? Date.now;\n let day = utcDay(now());\n let paid = new Set<string>();\n const counts = { post: 0, user: 0, owned: 0, freeFromDedup: 0, created: 0 };\n let spent = 0;\n\n const rollover = (): void => {\n const today = utcDay(now());\n if (today !== day) {\n // Yesterday's ids stop being free at UTC midnight, exactly as X's\n // dedup window does. Counters are cumulative for the session and survive.\n paid = new Set();\n day = today;\n }\n };\n\n return {\n record(kind, ids) {\n rollover();\n const billable: string[] = [];\n const free: string[] = [];\n for (const id of ids) {\n const key = `${kind}:${id}`;\n if (paid.has(key)) {\n free.push(id);\n counts.freeFromDedup += 1;\n continue;\n }\n paid.add(key);\n billable.push(id);\n counts[kind] += 1;\n spent += rate(opts.pricing, kind);\n }\n return { billable, free };\n },\n estimate(kind, ids) {\n rollover();\n const unpaid = ids.filter((id) => !paid.has(`${kind}:${id}`));\n return unpaid.length * rate(opts.pricing, kind);\n },\n estimateCount(kind, count) {\n return count * rate(opts.pricing, kind);\n },\n recordCreate(hasUrl) {\n counts.created += 1;\n spent += hasUrl ? opts.pricing.postCreateWithUrl : opts.pricing.postCreate;\n },\n spentUsd: () => round(spent),\n report(cache) {\n rollover();\n const reads = counts.post + counts.user + counts.owned;\n return {\n day,\n since_process_start: {\n billable_post_reads: counts.post,\n billable_user_reads: counts.user,\n owned_reads: counts.owned,\n free_from_dedup: counts.freeFromDedup,\n posts_created: counts.created,\n estimated_usd: round(spent),\n },\n read_cap: {\n monthly_cap: opts.pricing.monthlyReadCap,\n reads_this_session: reads,\n cap_used_pct: Math.round((reads / opts.pricing.monthlyReadCap) * 10000) / 100,\n },\n ...(opts.budgetUsd !== undefined\n ? {\n budget: {\n limit_usd: opts.budgetUsd,\n remaining_usd: round(Math.max(0, opts.budgetUsd - spent)),\n },\n }\n : {}),\n cache,\n pricing: opts.pricing,\n disclaimer:\n \"Estimated locally from X's published pay-per-use rates and counted only since this \" +\n \"process started — it is not persisted across restarts, and does not know about spend \" +\n \"from other clients. The X developer console (console.x.com) is the authoritative record.\",\n };\n },\n };\n};\n\n/** Build the per-result cost note that every read tool attaches. */\nexport const costNote = (\n ledger: Ledger,\n kind: ResourceKind,\n billable: number,\n free: number,\n pricing: Pricing,\n): CostNote => {\n const usd = round(billable * rate(pricing, kind));\n const field =\n kind === \"post\"\n ? (\"billable_post_reads\" as const)\n : kind === \"user\"\n ? (\"billable_user_reads\" as const)\n : (\"owned_reads\" as const);\n return {\n [field]: billable,\n free_from_cache: free,\n estimated_usd: usd,\n ...(free > 0\n ? { note: `${free} already read today — X does not bill those again until UTC midnight.` }\n : {}),\n } as CostNote;\n};\n","// X answers with a `data` / `includes` / `meta` triple. The interesting parts of\n// a post — who wrote it, what it quotes, where its t.co links actually go —\n// are not in `data` at all; they sit in a sidecar `includes` array keyed by id.\n//\n// Handing that to a model raw does two bad things: it spends a large multiple of\n// the tokens the content is worth, and it makes the model perform a join\n// (author_id → includes.users[].id) that it can silently get wrong. So every\n// read tool returns posts that already have their author, quoted post, media and\n// expanded URLs inlined, and `includes` is never returned at all.\n\nexport type Rec = Record<string, unknown>;\n\nexport const isRecord = (value: unknown): value is Rec =>\n typeof value === \"object\" && value !== null && !Array.isArray(value);\n\nconst str = (value: unknown): string | undefined =>\n typeof value === \"string\" && value ? value : undefined;\n\nconst num = (value: unknown): number | undefined =>\n typeof value === \"number\" && Number.isFinite(value) ? value : undefined;\n\n/** Three lookup tables built once per response. */\nexport type Includes = {\n users: Map<string, Rec>;\n tweets: Map<string, Rec>;\n media: Map<string, Rec>;\n};\n\nconst indexBy = (items: unknown, key: string): Map<string, Rec> => {\n const map = new Map<string, Rec>();\n if (!Array.isArray(items)) return map;\n for (const item of items) {\n if (!isRecord(item)) continue;\n const id = str(item[key]);\n if (id) map.set(id, item);\n }\n return map;\n};\n\n/**\n * Build the lookup tables from one `includes` block or several (paginated reads\n * return one per page).\n *\n * These are `Map`s rather than an `Array.find` per lookup on purpose: a 100-post\n * page with 100 distinct authors would otherwise be quadratic, and the whole\n * point of this module is that it stays cheap on the largest responses.\n */\nexport const buildIncludesIndex = (includes: Rec | Rec[] | undefined): Includes => {\n const blocks = Array.isArray(includes) ? includes : includes ? [includes] : [];\n const index: Includes = { users: new Map(), tweets: new Map(), media: new Map() };\n for (const block of blocks) {\n for (const [id, user] of indexBy(block.users, \"id\")) index.users.set(id, user);\n for (const [id, tweet] of indexBy(block.tweets, \"id\")) index.tweets.set(id, tweet);\n for (const [key, media] of indexBy(block.media, \"media_key\")) index.media.set(key, media);\n }\n return index;\n};\n\n/** Pull the `includes` block out of a raw response envelope. */\nexport const includesOf = (response: unknown): Rec | undefined =>\n isRecord(response) && isRecord(response.includes) ? response.includes : undefined;\n\n/**\n * \"@handle (Display Name)\" in one field, because the model reads it as a name\n * and never has to look one up. An unresolved author degrades rather than\n * throwing — a deleted or suspended account is routine, not exceptional.\n */\nconst formatAuthor = (authorId: string | undefined, index: Includes): string => {\n if (!authorId) return \"@unknown\";\n const user = index.users.get(authorId);\n const username = user ? str(user.username) : undefined;\n if (!username) return `@unknown (id ${authorId})`;\n const name = user ? str(user.name) : undefined;\n return name ? `@${username} (${name})` : `@${username}`;\n};\n\nconst postUrl = (authorId: string | undefined, id: string, index: Includes): string => {\n const username = authorId ? str(index.users.get(authorId)?.username) : undefined;\n // `i/web` is X's own canonical fallback and redirects correctly, so an\n // unresolved author still yields a link that works.\n return `https://x.com/${username ?? \"i/web\"}/status/${id}`;\n};\n\n/**\n * Replace each t.co link with where it actually points.\n *\n * Splices run right-to-left by `start` so earlier offsets stay valid as the\n * string changes length. The offsets are UTF-16 code units, which is exactly\n * what `String.prototype.slice` counts — no conversion needed. That is worth\n * saying out loud, because \"fixing\" this to use code points is a natural-looking\n * change that would corrupt every post containing an emoji.\n */\nconst expandUrls = (text: string, raw: Rec): string => {\n const entities = isRecord(raw.entities) ? raw.entities : undefined;\n const urls = entities && Array.isArray(entities.urls) ? entities.urls : [];\n const spans = urls\n .filter(isRecord)\n .map((u) => ({\n start: num(u.start),\n end: num(u.end),\n expanded: str(u.expanded_url) ?? str(u.url),\n }))\n .filter(\n (u): u is { start: number; end: number; expanded: string } =>\n u.start !== undefined && u.end !== undefined && u.expanded !== undefined,\n )\n .toSorted((a, b) => b.start - a.start);\n\n let out = text;\n for (const span of spans) {\n if (span.start < 0 || span.end > out.length || span.start > span.end) continue;\n out = out.slice(0, span.start) + span.expanded + out.slice(span.end);\n }\n return out;\n};\n\nconst shapeMedia = (raw: Rec, index: Includes): string[] | undefined => {\n const attachments = isRecord(raw.attachments) ? raw.attachments : undefined;\n const keys = attachments && Array.isArray(attachments.media_keys) ? attachments.media_keys : [];\n const items: string[] = [];\n for (const key of keys) {\n const k = str(key);\n if (!k) continue;\n const media = index.media.get(k);\n if (!media) {\n items.push(`media (not expanded): ${k}`);\n continue;\n }\n const type = str(media.type) ?? \"media\";\n // Videos and GIFs carry `preview_image_url`; photos carry `url`.\n const url = str(media.url) ?? str(media.preview_image_url);\n const alt = str(media.alt_text);\n items.push(`${type}${url ? `: ${url}` : \"\"}${alt ? ` (alt: ${alt})` : \"\"}`);\n }\n return items.length > 0 ? items : undefined;\n};\n\nconst shapeMetrics = (raw: Rec): ShapedPost[\"metrics\"] => {\n const m = isRecord(raw.public_metrics) ? raw.public_metrics : undefined;\n if (!m) return undefined;\n const metrics = {\n likes: num(m.like_count),\n reposts: num(m.retweet_count),\n replies: num(m.reply_count),\n quotes: num(m.quote_count),\n views: num(m.impression_count),\n };\n const entries = Object.entries(metrics).filter(([, v]) => v !== undefined);\n return entries.length > 0 ? (Object.fromEntries(entries) as ShapedPost[\"metrics\"]) : undefined;\n};\n\n/** A referenced post, resolved one level deep only. */\nexport type ShapedRef = {\n id: string;\n author?: string;\n text?: string;\n created_at?: string;\n};\n\nexport type ShapedPost = {\n id: string;\n url: string;\n author: string;\n created_at?: string;\n text: string;\n lang?: string;\n metrics?: { likes?: number; reposts?: number; replies?: number; quotes?: number; views?: number };\n quotes?: ShapedRef;\n replies_to?: ShapedRef;\n reposts?: ShapedRef;\n media?: string[];\n conversation_id?: string;\n};\n\n/**\n * Resolve one referenced post from `includes.tweets`. Deliberately one hop and\n * no recursion: X only sideloads a single level anyway, and a\n * quote-of-a-quote-of-a-quote expanded in place is a context-window bomb for no\n * added meaning.\n */\nconst shapeRef = (id: string, index: Includes): ShapedRef => {\n const raw = index.tweets.get(id);\n if (!raw) return { id }; // deleted, protected, or simply not requested\n const text = str(raw.text);\n const authorId = str(raw.author_id);\n return {\n id,\n ...(authorId ? { author: formatAuthor(authorId, index) } : {}),\n ...(text ? { text: expandUrls(text, raw) } : {}),\n ...(str(raw.created_at) ? { created_at: str(raw.created_at) } : {}),\n };\n};\n\nexport const shapePost = (raw: Rec, index: Includes): ShapedPost => {\n const id = str(raw.id) ?? \"\";\n const authorId = str(raw.author_id);\n const refs = Array.isArray(raw.referenced_tweets) ? raw.referenced_tweets.filter(isRecord) : [];\n\n const refOf = (type: string): ShapedRef | undefined => {\n const ref = refs.find((r) => str(r.type) === type);\n const refId = ref ? str(ref.id) : undefined;\n return refId ? shapeRef(refId, index) : undefined;\n };\n\n const reposts = refOf(\"retweeted\");\n const rawText = str(raw.text) ?? \"\";\n // A retweet's own `text` is a truncated \"RT @someone: …\". Show the original's\n // text instead, so the model reads content rather than an ellipsis.\n const text = reposts?.text ? reposts.text : expandUrls(rawText, raw);\n\n const metrics = shapeMetrics(raw);\n const media = shapeMedia(raw, index);\n const quotes = refOf(\"quoted\");\n const repliesTo = refOf(\"replied_to\");\n\n return {\n id,\n url: postUrl(authorId, id, index),\n author: formatAuthor(authorId, index),\n ...(str(raw.created_at) ? { created_at: str(raw.created_at) } : {}),\n text,\n ...(str(raw.lang) ? { lang: str(raw.lang) } : {}),\n ...(metrics ? { metrics } : {}),\n ...(quotes ? { quotes } : {}),\n ...(repliesTo ? { replies_to: repliesTo } : {}),\n ...(reposts ? { reposts } : {}),\n ...(media ? { media } : {}),\n ...(str(raw.conversation_id) ? { conversation_id: str(raw.conversation_id) } : {}),\n };\n};\n\nexport type ShapedUser = {\n id: string;\n username: string;\n name?: string;\n url: string;\n description?: string;\n verified?: boolean;\n protected?: boolean;\n location?: string;\n created_at?: string;\n metrics?: { followers?: number; following?: number; posts?: number; listed?: number };\n};\n\nexport const shapeUser = (raw: Rec): ShapedUser => {\n const username = str(raw.username) ?? \"\";\n const m = isRecord(raw.public_metrics) ? raw.public_metrics : undefined;\n const metrics = m\n ? {\n followers: num(m.followers_count),\n following: num(m.following_count),\n posts: num(m.tweet_count),\n listed: num(m.listed_count),\n }\n : undefined;\n const hasMetrics = metrics && Object.values(metrics).some((v) => v !== undefined);\n\n return {\n id: str(raw.id) ?? \"\",\n username,\n ...(str(raw.name) ? { name: str(raw.name) } : {}),\n url: `https://x.com/${username}`,\n ...(str(raw.description) ? { description: str(raw.description) } : {}),\n ...(typeof raw.verified === \"boolean\" ? { verified: raw.verified } : {}),\n ...(typeof raw.protected === \"boolean\" ? { protected: raw.protected } : {}),\n ...(str(raw.location) ? { location: str(raw.location) } : {}),\n ...(str(raw.created_at) ? { created_at: str(raw.created_at) } : {}),\n ...(hasMetrics ? { metrics } : {}),\n };\n};\n\nexport type ShapedPosts = {\n posts: ShapedPost[];\n result_count?: number;\n next_token?: string;\n /** Ids X refused to return — deleted, protected, or suspended. */\n not_found?: string[];\n};\n\n/**\n * X reports per-id failures in a top-level `errors` array *alongside* a 200, so\n * asking for five posts and getting three back is a success with a footnote.\n * Surfacing the missing ids beats letting the model wonder where they went.\n */\nconst notFoundIds = (response: unknown): string[] | undefined => {\n if (!isRecord(response) || !Array.isArray(response.errors)) return undefined;\n const ids = response.errors\n .filter(isRecord)\n .map((e) => str(e.value) ?? str(e.resource_id))\n .filter((v): v is string => v !== undefined);\n return ids.length > 0 ? ids : undefined;\n};\n\n/** Flatten a list-of-posts response. Accepts a single- or multi-page envelope. */\nexport const shapePostsResponse = (response: unknown): ShapedPosts => {\n const index = buildIncludesIndex(includesOf(response));\n const data = isRecord(response) && Array.isArray(response.data) ? response.data : [];\n const meta = isRecord(response) && isRecord(response.meta) ? response.meta : undefined;\n const notFound = notFoundIds(response);\n\n return {\n posts: data.filter(isRecord).map((raw) => shapePost(raw, index)),\n ...(meta && num(meta.result_count) !== undefined\n ? { result_count: num(meta.result_count) }\n : {}),\n ...(meta && str(meta.next_token) ? { next_token: str(meta.next_token) } : {}),\n ...(notFound ? { not_found: notFound } : {}),\n };\n};\n\n/** Flatten a single-post response. */\nexport const shapePostResponse = (response: unknown): ShapedPost | { error: string } => {\n const index = buildIncludesIndex(includesOf(response));\n const data = isRecord(response) && isRecord(response.data) ? response.data : undefined;\n if (!data) {\n const notFound = notFoundIds(response);\n return {\n error: notFound\n ? `X returned no post for id ${notFound.join(\", \")} — it is deleted, protected, or from a suspended account.`\n : \"X returned no post for that id.\",\n };\n }\n return shapePost(data, index);\n};\n\nexport type ShapedUsers = { users: ShapedUser[]; not_found?: string[] };\n\nexport const shapeUsersResponse = (response: unknown): ShapedUsers => {\n const raw = isRecord(response) ? response.data : undefined;\n const list = Array.isArray(raw) ? raw : isRecord(raw) ? [raw] : [];\n const notFound = notFoundIds(response);\n return {\n users: list.filter(isRecord).map(shapeUser),\n ...(notFound ? { not_found: notFound } : {}),\n };\n};\n\n/** Assemble the shaped form from a paginated read, where includes arrive per page. */\nexport const shapePaginatedPosts = (\n data: unknown[],\n includes: Rec[],\n nextToken?: string,\n): ShapedPosts => {\n const index = buildIncludesIndex(includes);\n return {\n posts: data.filter(isRecord).map((raw) => shapePost(raw, index)),\n result_count: data.length,\n ...(nextToken ? { next_token: nextToken } : {}),\n };\n};\n","import type { AuthContext, Logger, TokenProvider } from \"#/client/auth\";\nimport { type XApiError, XApiRequestError } from \"#/client/errors\";\nimport {\n buildQuery,\n endpointKey,\n numberOrUndefined,\n safeJsonParse,\n withRetry,\n type Query,\n type RateLimitSnapshot,\n} from \"#/client/http\";\nimport { DEFAULT_BASE_URL } from \"#/config\";\n\n// Re-exported so the many call sites that import these from `./x.js` keep\n// working; the definitions moved to `./http.js` when the Ads client needed them.\nexport type { Query, QueryValue, RateLimitSnapshot } from \"#/client/http\";\n\nexport type RequestOptions = {\n query?: Query;\n body?: unknown;\n /** Which credential to send. Defaults to the app-only Bearer token. */\n auth?: AuthContext;\n};\n\nexport type XApiClientOptions = {\n baseUrl?: string;\n tokenProvider: TokenProvider;\n maxRetries?: number;\n fetch?: typeof fetch;\n logger?: Logger;\n userAgent?: string;\n};\n\n/**\n * Minimal fetch-based client for the X API v2. Paths are absolute (`/2/tweets`).\n * Retries a 401 (invalidating the token first) and 429/5xx with exponential\n * backoff honoring `Retry-After`, and records every response's rate-limit\n * headers so a 429 can say what it is waiting for.\n */\nexport class XApiClient {\n private readonly baseUrl: string;\n private readonly tokenProvider: TokenProvider;\n private readonly maxRetries: number;\n private readonly fetchImpl: typeof fetch;\n private readonly logger: Logger | undefined;\n private readonly userAgent: string;\n private readonly rateLimits = new Map<string, RateLimitSnapshot>();\n\n constructor(opts: XApiClientOptions) {\n this.baseUrl = (opts.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/+$/, \"\");\n this.tokenProvider = opts.tokenProvider;\n this.maxRetries = opts.maxRetries ?? 3;\n this.fetchImpl = opts.fetch ?? fetch;\n this.logger = opts.logger;\n this.userAgent = opts.userAgent ?? \"mcp-x-api-js\";\n }\n\n /** Everything the last response said about each endpoint's remaining budget. */\n rateLimitStatus(): RateLimitSnapshot[] {\n return [...this.rateLimits.values()];\n }\n\n private recordRateLimit(method: string, path: string, res: Response): void {\n const limit = numberOrUndefined(res.headers.get(\"x-rate-limit-limit\"));\n const remaining = numberOrUndefined(res.headers.get(\"x-rate-limit-remaining\"));\n const reset = numberOrUndefined(res.headers.get(\"x-rate-limit-reset\"));\n if (limit === undefined && remaining === undefined && reset === undefined) return;\n const endpoint = endpointKey(method, path);\n this.rateLimits.set(endpoint, {\n endpoint,\n ...(limit !== undefined ? { limit } : {}),\n ...(remaining !== undefined ? { remaining } : {}),\n ...(reset !== undefined ? { reset, resetAt: new Date(reset * 1000).toISOString() } : {}),\n });\n }\n\n async request<T = unknown>(method: string, path: string, opts: RequestOptions = {}): Promise<T> {\n const url = `${this.baseUrl}${path}${buildQuery(opts.query)}`;\n const hasBody = opts.body !== undefined;\n const auth: AuthContext = opts.auth ?? \"app\";\n\n const res = await withRetry(\n async () => {\n const token = await this.tokenProvider.getToken(auth);\n return this.fetchImpl(url, {\n method,\n headers: {\n Accept: \"application/json\",\n Authorization: `Bearer ${token}`,\n \"User-Agent\": this.userAgent,\n ...(hasBody ? { \"Content-Type\": \"application/json\" } : {}),\n },\n ...(hasBody ? { body: JSON.stringify(opts.body) } : {}),\n });\n },\n {\n maxRetries: this.maxRetries,\n label: `${method} ${url}`,\n logger: this.logger,\n onUnauthorized: () => this.tokenProvider.invalidate(auth),\n },\n );\n\n this.recordRateLimit(method, path, res);\n const text = await res.text();\n\n if (!res.ok) {\n throw new XApiRequestError(this.errorMessage(res, method, path, text), {\n status: res.status,\n errors: this.parseErrors(text),\n });\n }\n\n if (res.status === 204 || text.trim() === \"\") return null as T;\n return safeJsonParse(text) as T;\n }\n\n get<T = unknown>(path: string, query?: Query, auth: AuthContext = \"app\"): Promise<T> {\n return this.request<T>(\"GET\", path, { query, auth });\n }\n\n post<T = unknown>(path: string, body?: unknown, auth: AuthContext = \"user\"): Promise<T> {\n return this.request<T>(\"POST\", path, { body, auth });\n }\n\n del<T = unknown>(path: string, auth: AuthContext = \"user\"): Promise<T> {\n return this.request<T>(\"DELETE\", path, { auth });\n }\n\n /**\n * GET a collection, following `meta.next_token` until the pages run out or a\n * bound is hit.\n *\n * Both bounds exist because unbounded pagination here spends real money: at\n * $0.005 a post, walking a busy hashtag to the end is a three-figure mistake\n * an agent can make in one call. `maxItems` is the one callers actually set.\n *\n * X carries the cursor in `meta.next_token` and expects it back as\n * `pagination_token`, so — unlike a `links.next` API — the original query has\n * to be re-sent on every page rather than replaced.\n */\n async paginate<T = unknown>(\n path: string,\n query: Query,\n opts: { maxItems: number; maxPages?: number; auth?: AuthContext },\n ): Promise<{ data: T[]; pages: number; nextToken?: string; includes: Rec[] }> {\n type Envelope = {\n data?: T[];\n includes?: Rec;\n meta?: { next_token?: unknown };\n };\n const maxPages = opts.maxPages ?? 10;\n const collected: T[] = [];\n const includes: Rec[] = [];\n let token: string | undefined;\n let pages = 0;\n let nextToken: string | undefined;\n\n for (;;) {\n const res: Envelope = await this.request<Envelope>(\"GET\", path, {\n query: { ...query, ...(token ? { pagination_token: token } : {}) },\n ...(opts.auth ? { auth: opts.auth } : {}),\n });\n pages += 1;\n if (Array.isArray(res?.data)) collected.push(...res.data);\n if (res?.includes) includes.push(res.includes);\n\n const next = res?.meta?.next_token;\n token = typeof next === \"string\" && next ? next : undefined;\n nextToken = token;\n\n if (!token || collected.length >= opts.maxItems || pages >= maxPages) break;\n }\n\n return {\n data: collected.slice(0, opts.maxItems),\n pages,\n ...(nextToken ? { nextToken } : {}),\n includes,\n };\n }\n\n private parseErrors(text: string): XApiError[] | unknown {\n const parsed = safeJsonParse(text);\n if (parsed && typeof parsed === \"object\" && \"errors\" in parsed) {\n return (parsed as { errors: XApiError[] }).errors;\n }\n return parsed;\n }\n\n /**\n * The three statuses that actually happen get a sentence naming the fix. A\n * bare \"HTTP 403\" sends people to the wrong place — usually to re-check a\n * token that was fine, when the real answer is that their access tier does\n * not include the endpoint.\n */\n private errorMessage(res: Response, method: string, path: string, text: string): string {\n const base = `X API ${method} ${path} failed: HTTP ${res.status} ${res.statusText}`.trim();\n const parsed = this.parseErrors(text);\n const detail = Array.isArray(parsed)\n ? parsed\n .map((e: XApiError) => [e.title, e.detail ?? e.message].filter(Boolean).join(\" — \"))\n .filter(Boolean)\n .join(\"; \")\n : this.problemDetail(parsed);\n\n if (res.status === 401) {\n return (\n `${base} — the token was rejected. Check X_API_BEARER_TOKEN, or re-run ` +\n `\\`x-api-mcp login\\` if this call needed a user context` +\n (detail ? ` (${detail})` : \"\")\n );\n }\n if (res.status === 403) {\n // Ordered by what actually causes this. An app left in the legacy\n // Free/Development state authenticates fine and then 403s on every\n // user-context call, which reads as a scope problem and is not one —\n // sending people to re-check scopes here wastes a lot of their time.\n return (\n `${base} — authenticated, but the request was refused. Most often this means the app is ` +\n `not enrolled: at console.x.com open the app, and make sure it is in the Pay-per-use ` +\n `package and the Production environment (a \"client-not-enrolled\" or \"client-forbidden\" ` +\n `detail below confirms this). Otherwise, your access tier or this token's scopes do not ` +\n `cover the endpoint — full-archive search needs a paid tier, bookmarks need bookmark.read` +\n (detail ? ` (${detail})` : \"\")\n );\n }\n if (res.status === 429) {\n const snapshot = this.rateLimits.get(endpointKey(method, path));\n const window = snapshot\n ? ` (${snapshot.remaining ?? 0}/${snapshot.limit ?? \"?\"} remaining` +\n (snapshot.resetAt ? `, resets ${snapshot.resetAt}` : \"\") +\n `)`\n : \"\";\n return `${base} — rate limited${window}. Wait for the window to reset, or lower maxResults.`;\n }\n return base + (detail ? ` — ${detail}` : \"\");\n }\n\n private problemDetail(parsed: unknown): string {\n if (!parsed || typeof parsed !== \"object\") return \"\";\n const p = parsed as XApiError;\n return [p.title, p.detail].filter(Boolean).join(\" — \");\n }\n}\n\ntype Rec = Record<string, unknown>;\n","// X does not count characters; it counts *weighted* characters, per the\n// twitter-text v3 config. Everything is weight 200 by default and only a few\n// ranges are 100, which is why 140 Japanese characters exactly fills a post\n// while 280 Latin ones do. Getting this wrong means handing the user a draft\n// the composer then rejects, which is precisely the failure this module exists\n// to prevent.\n//\n// Constants are transcribed from twitter-text's config/v3.json. That repo has\n// been unmaintained since 2021, so its emoji regex only knows Unicode ~13; we\n// use Intl.Segmenter instead, which tracks the runtime's Unicode version and\n// counts newer ZWJ sequences correctly rather than over-charging them.\n\n/** Code point ranges that weigh 100 (i.e. one character). Everything else is 200. */\nconst LIGHT_RANGES: readonly (readonly [number, number])[] = [\n [0, 4351], // Latin, Greek, Cyrillic, Hebrew, Arabic, Thai, Hangul Jamo\n [8192, 8205], // General punctuation spaces, ZWNJ/ZWJ\n [8208, 8223], // Dashes and quotation marks\n [8242, 8247], // Primes\n];\n\nconst DEFAULT_WEIGHT = 200;\nconst SCALE = 100;\nexport const MAX_WEIGHTED_LENGTH = 280;\n\n/**\n * Every URL costs the same whatever its real length: X rewrites it to t.co.\n * There is a single value now — the old http/https split was deprecated once\n * every t.co link became https.\n */\nexport const TCO_URL_LENGTH = 23;\n\n/**\n * Conservative URL detection. Scheme-ful URLs plus bare `domain.tld/path` for\n * the TLDs people actually paste. Deliberately narrow: over-matching would\n * silently under-count a draft (charging 23 for something X treats as plain\n * text), and a draft rejected at the composer is worse than one that looks a\n * few characters longer than it is.\n */\nconst URL_PATTERN =\n /\\bhttps?:\\/\\/[^\\s<>\"']+|\\b(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+(?:com|org|net|io|dev|co|ai|app|xyz|me|gg|so|sh|to|tv|fr|uk|de|jp)\\b(?:\\/[^\\s<>\"']*)?/gi;\n\nconst isLight = (codePoint: number): boolean =>\n LIGHT_RANGES.some(([start, end]) => codePoint >= start && codePoint <= end);\n\nconst EMOJI = /\\p{Extended_Pictographic}/u;\n\nconst segmenter = new Intl.Segmenter(undefined, { granularity: \"grapheme\" });\n\n/** Weight one grapheme cluster in twitter-text's internal units. */\nconst clusterWeight = (cluster: string): number => {\n const first = cluster.codePointAt(0);\n if (first === undefined) return 0;\n // A multi-code-point cluster is a ZWJ sequence, a skin-tone modifier or a\n // combining sequence — X charges the whole thing as one 200-weight unit.\n // This is what makes 👨‍👩‍👧‍👦 count 2 rather than 22.\n if (EMOJI.test(cluster) || [...cluster].length > 1) return DEFAULT_WEIGHT;\n return isLight(first) ? SCALE : DEFAULT_WEIGHT;\n};\n\nexport type WeightedLength = {\n /** 0-280+, in the units X shows the user. */\n weighted: number;\n remaining: number;\n valid: boolean;\n urls: { url: string; countedAs: number }[];\n};\n\n/**\n * Count a draft the way X will.\n *\n * NFC normalization comes first because twitter-text normalizes first: without\n * it a decomposed \"café\" counts 5 instead of 4, and the user is told they have\n * one character less than they do.\n */\nexport const weightedLength = (text: string): WeightedLength => {\n const normalized = text.normalize(\"NFC\");\n\n const urls: { url: string; countedAs: number }[] = [];\n const spans: [number, number][] = [];\n for (const match of normalized.matchAll(URL_PATTERN)) {\n if (match.index === undefined) continue;\n urls.push({ url: match[0], countedAs: TCO_URL_LENGTH });\n spans.push([match.index, match.index + match[0].length]);\n }\n\n let total = urls.length * TCO_URL_LENGTH * SCALE;\n\n // Walk the text, skipping the spans already charged as t.co links.\n let index = 0;\n let spanIndex = 0;\n while (index < normalized.length) {\n const span = spans[spanIndex];\n if (span && index === span[0]) {\n index = span[1];\n spanIndex += 1;\n continue;\n }\n const nextStart = span ? span[0] : normalized.length;\n const chunk = normalized.slice(index, nextStart);\n for (const { segment } of segmenter.segment(chunk)) {\n total += clusterWeight(segment);\n }\n index = nextStart;\n }\n\n const weighted = Math.ceil(total / SCALE);\n return {\n weighted,\n remaining: MAX_WEIGHTED_LENGTH - weighted,\n valid: weighted > 0 && weighted <= MAX_WEIGHTED_LENGTH,\n urls,\n };\n};\n","import { MAX_WEIGHTED_LENGTH, weightedLength } from \"#/compose/weighted\";\n\n/**\n * X's web intent: a documented, credential-free URL that opens the composer\n * pre-filled. Nothing is posted until a human clicks Post, which is why it\n * needs no auth, consumes no API quota and costs nothing.\n *\n * The path is `/intent/tweet`, not `/intent/post`. X renamed Tweet to Post\n * throughout its docs prose but never changed the URL, and `/intent/post` is\n * undocumented with known edge-case bugs. `twitter.com` 301s to `x.com`, so\n * there is no reason to emit the legacy domain.\n */\nexport const INTENT_BASE_URL = \"https://x.com/intent/tweet\";\n\nexport type IntentInput = {\n text: string;\n /** Appended by the composer and counted against the 280. */\n url?: string | undefined;\n /** Without the leading '#'. */\n hashtags?: string[] | undefined;\n /** Handle without the leading '@'. */\n via?: string | undefined;\n /** The post id being replied to. */\n inReplyTo?: string | undefined;\n lang?: string | undefined;\n};\n\nconst stripLeading = (value: string, char: string): string =>\n value.startsWith(char) ? value.slice(1) : value;\n\n/**\n * What the composer will actually contain, in X's documented assembly order:\n * text, then url, then hashtags, then \"via @handle\".\n *\n * Validating `text` alone and then handing back a URL the composer rejects is\n * exactly the bug this module exists to prevent, so counting happens on this\n * string rather than on the input.\n */\nexport const assembleComposerText = (input: IntentInput): string => {\n const parts = [input.text.trim()];\n if (input.url) parts.push(input.url);\n for (const tag of input.hashtags ?? []) {\n const clean = stripLeading(tag.trim(), \"#\");\n if (clean) parts.push(`#${clean}`);\n }\n if (input.via) parts.push(`via @${stripLeading(input.via.trim(), \"@\")}`);\n return parts.filter(Boolean).join(\" \");\n};\n\nexport const buildIntentUrl = (input: IntentInput): string => {\n const params = new URLSearchParams();\n if (input.text) params.set(\"text\", input.text);\n if (input.url) params.set(\"url\", input.url);\n const hashtags = (input.hashtags ?? []).map((t) => stripLeading(t.trim(), \"#\")).filter(Boolean);\n // Documented as a comma-separated list *without* the '#' characters.\n if (hashtags.length > 0) params.set(\"hashtags\", hashtags.join(\",\"));\n if (input.via) params.set(\"via\", stripLeading(input.via.trim(), \"@\"));\n if (input.inReplyTo) params.set(\"in_reply_to\", input.inReplyTo);\n if (input.lang) params.set(\"lang\", input.lang);\n return `${INTENT_BASE_URL}?${params.toString()}`;\n};\n\nexport type IntentValidation = {\n valid: boolean;\n weighted: number;\n remaining: number;\n composed: string;\n intent_url: string;\n warnings: string[];\n error?: string;\n};\n\nexport const validateIntent = (input: IntentInput): IntentValidation => {\n const composed = assembleComposerText(input);\n const { weighted, remaining, valid, urls } = weightedLength(composed);\n const warnings: string[] = [];\n\n if (urls.length > 0) {\n warnings.push(\n `${urls.length} URL${urls.length > 1 ? \"s\" : \"\"} counted as ${urls.length * 23} characters ` +\n `(X rewrites every link to a fixed-length t.co URL, whatever its real length).`,\n );\n }\n if ((input.hashtags?.length ?? 0) > 4) {\n warnings.push(\"More than four hashtags reads as spam and tends to suppress reach.\");\n }\n if (input.via?.trim().startsWith(\"@\")) {\n warnings.push(\"Stripped the leading '@' from `via` — X expects a bare handle.\");\n }\n if (input.inReplyTo && !/^\\d+$/.test(input.inReplyTo)) {\n warnings.push(\n `inReplyTo \"${input.inReplyTo}\" is not a post id. Ids are digits only — the trailing ` +\n \"number in a post's URL.\",\n );\n }\n\n return {\n valid,\n weighted,\n remaining,\n composed,\n intent_url: buildIntentUrl(input),\n warnings,\n ...(valid\n ? {}\n : {\n error:\n weighted === 0\n ? \"The post is empty.\"\n : `The assembled post is ${weighted} weighted characters, ${-remaining} over X's ` +\n `${MAX_WEIGHTED_LENGTH} limit. Note that the url, hashtags and via parts all count.`,\n }),\n };\n};\n","import { execFile } from \"node:child_process\";\nimport { existsSync } from \"node:fs\";\n\nexport type OpenResult = { opened: boolean; reason?: string };\n\n/** Only ever X. This must not become a generic \"open whatever the model asked for\". */\nconst ALLOWED_ORIGINS = new Set([\"https://x.com\", \"https://twitter.com\"]);\n\nconst isHeadless = (): boolean => {\n if (existsSync(\"/.dockerenv\")) return true;\n if (process.platform === \"linux\" && !process.env.DISPLAY && !process.env.WAYLAND_DISPLAY) {\n return true;\n }\n return false;\n};\n\nconst command = (url: string): { file: string; args: string[] } => {\n switch (process.platform) {\n case \"darwin\":\n return { file: \"open\", args: [url] };\n case \"win32\":\n // The empty string is `start`'s title argument; without it a quoted URL\n // becomes the window title and nothing opens.\n return { file: \"cmd\", args: [\"/c\", \"start\", \"\", url] };\n default:\n return { file: \"xdg-open\", args: [url] };\n }\n};\n\n/**\n * Best effort, and deliberately non-throwing: the intent URL is the deliverable,\n * and Docker, SSH and headless CI are all normal places to run this server.\n * Callers always return the URL regardless of what this says.\n */\nexport const openInBrowser = async (url: string): Promise<OpenResult> => {\n let origin: string;\n try {\n origin = new URL(url).origin;\n } catch {\n return { opened: false, reason: \"not a valid URL\" };\n }\n if (!ALLOWED_ORIGINS.has(origin)) {\n return { opened: false, reason: `refusing to open a non-X origin (${origin})` };\n }\n if (isHeadless()) {\n return { opened: false, reason: \"headless environment — open the URL yourself\" };\n }\n\n const { file, args } = command(url);\n return new Promise<OpenResult>((resolve) => {\n // execFile, not exec: the URL is an argv element and never touches a shell,\n // so its query string cannot be interpreted as shell syntax.\n execFile(file, args, { timeout: 5000 }, (err) => {\n resolve(err ? { opened: false, reason: `${file} failed: ${err.message}` } : { opened: true });\n });\n });\n};\n","import { z } from \"zod\";\n\nimport type { AdsApiClient } from \"#/client/ads\";\nimport { adsData, isRecord, shapeMoney } from \"#/client/ads-shape\";\nimport { AdsAccessError, PreconditionError } from \"#/client/errors\";\nimport type { AdsContext } from \"#/tools/index\";\n\n/**\n * Ads calls are not metered by X's pay-per-use rates, so they carry a fixed\n * note rather than a ledger entry. Saying it on every result is deliberate: the\n * absence of a cost line would otherwise read as \"not measured\", and the real\n * point is that the tool is free while the campaigns it manages are not.\n */\nexport const adsCostNote = (): { estimated_usd: number; note: string } => ({\n estimated_usd: 0,\n note:\n \"Ads API calls are not billed under X's pay-per-use read pricing, so this costs nothing and \" +\n \"does not appear in x_usage_report. The campaigns it manages spend your advertising budget.\",\n});\n\nexport const accountIdArg = z\n .string()\n .min(1)\n .regex(/^[A-Za-z0-9]+$/, 'An ads account id is letters and digits, e.g. \"18ce54d4x5t\".')\n .optional()\n .describe(\n 'The ads account to act on, e.g. \"18ce54d4x5t\". Omit it when you have exactly one account ' +\n \"or X_ADS_ACCOUNT_ID is set — it is resolved automatically. List them with x_ads_get_accounts.\",\n );\n\nexport const entityIdArg = z\n .string()\n .min(1)\n .regex(/^[A-Za-z0-9]+$/)\n .describe('An ads entity id, e.g. a campaign or line item id like \"8v7jo\".');\n\nexport const adsCountArg = z\n .number()\n .int()\n .min(1)\n .max(1000)\n .default(200)\n .describe(\"How many records to return (1-1000). X's own default is 200.\");\n\nexport const adsConfirmArg = z\n .literal(true)\n .describe(\n \"Must be true. Explicit acknowledgement that this changes a live advertising campaign and \" +\n \"can spend your advertising budget.\",\n );\n\nexport const activateArg = z\n .boolean()\n .default(false)\n .describe(\n \"Create this ACTIVE instead of PAUSED. Defaults to false, which is the safe path: a PAUSED \" +\n \"entity spends nothing until you activate it with x_ads_set_entity_status. Set true only \" +\n \"when you intend spending to start the moment this call returns.\",\n );\n\n/**\n * Budgets are taken in major units and converted here, so a caller never sees a\n * `*_micro` field on the way in. A factor of a million is not a mistake anyone\n * catches by reading a number back, so the safest design is one where it cannot\n * be expressed.\n */\nexport const budgetArg = z\n .number()\n .positive()\n .max(10_000)\n .describe(\n \"Budget in MAJOR units of the funding instrument's currency — 50 means 50.00, not 50 \" +\n \"million. Do NOT multiply by 1,000,000; this server converts to X's \" +\n \"*_amount_local_micro field for you. Capped at 10,000 per call.\",\n );\n\n/** ISO-8601, which the Ads API requires at whole-hour boundaries. */\nexport const adsTimeArg = z\n .string()\n .regex(\n /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:00:00Z$/,\n 'X requires whole hours in ISO-8601 UTC, e.g. \"2026-08-01T00:00:00Z\".',\n )\n .describe('An ISO-8601 UTC time on a whole hour, e.g. \"2026-08-01T00:00:00Z\".');\n\n/** Shape one ads entity or list for a tool result. */\nexport const shapeAds = (raw: unknown, currency?: string): unknown =>\n shapeMoney(adsData(raw) ?? raw, currency);\n\n/**\n * Resolve which ads account a call is about.\n *\n * Mirrors `resolveOwnUserId` for timelines: prefer what the caller said, fall\n * back to configuration, and only then ask X — caching the answer so it happens\n * at most once per process. Unlike that case there is no token file to persist\n * into, so the memo lives in the closure and dies with the process, which is\n * fine for something that costs one request.\n *\n * The multi-account branch deliberately refuses rather than guessing. Agency\n * users hit it immediately, and silently picking the first account would create\n * campaigns in the wrong client's account — a mistake that spends real money\n * and is not obvious from the response.\n */\nexport const createAccountResolver = (\n client: AdsApiClient,\n ads: AdsContext,\n): ((explicit?: string) => Promise<string>) => {\n let memo: string | undefined;\n\n return async (explicit?: string): Promise<string> => {\n if (explicit) return explicit;\n if (ads.accountId) return ads.accountId;\n if (memo) return memo;\n\n const raw = await client.get(\"/12/accounts\", { count: 50 });\n const list = isRecord(raw) && Array.isArray(raw.data) ? raw.data : [];\n\n if (list.length === 0) {\n throw new AdsAccessError(\n \"The logged-in account has access to no ads accounts, so there is nothing to act on. \" +\n \"Either this X user has not been granted a role on an ads account (that is done in \" +\n \"ads.x.com, not the developer console), or the app is not approved for the Ads API \" +\n \"yet. Check x_auth_status for the setup steps.\" +\n (ads.sandbox ? \" In the sandbox, call x_ads_create_sandbox_account to make one.\" : \"\"),\n { baseUrl: ads.baseUrl, sandbox: ads.sandbox },\n );\n }\n\n if (list.length > 1) {\n const accounts = list.filter(isRecord).map((a) => ({ id: a.id, name: a.name }));\n throw new PreconditionError(\n `This login can reach ${list.length} ads accounts, so there is no safe default. Pass ` +\n `accountId explicitly, or set X_ADS_ACCOUNT_ID.`,\n { accounts },\n );\n }\n\n const only = list[0];\n const id = isRecord(only) && typeof only.id === \"string\" ? only.id : undefined;\n if (!id) {\n throw new AdsAccessError(\"X returned an ads account with no id, so it cannot be addressed.\", {\n received: only,\n });\n }\n memo = id;\n return id;\n };\n};\n","import { z } from \"zod\";\n\nimport type { DayCache, ResourceKind } from \"#/client/cache\";\nimport type { CostNote, Ledger } from \"#/client/cost\";\nimport {\n BudgetExceededError,\n PreconditionError,\n UserContextRequiredError,\n WritesDisabledError,\n XApiRequestError,\n} from \"#/client/errors\";\n\nexport type ToolResult = {\n content: { type: \"text\"; text: string }[];\n isError?: boolean;\n};\n\nexport const ok = (data: unknown): ToolResult => ({\n content: [{ type: \"text\", text: JSON.stringify(data ?? { ok: true }, null, 2) }],\n});\n\nexport const fail = (message: string, extra?: unknown): ToolResult => ({\n content: [\n {\n type: \"text\",\n text: JSON.stringify({ error: message, ...(extra ? { details: extra } : {}) }, null, 2),\n },\n ],\n isError: true,\n});\n\n/** Render a thrown value as a tool error, preserving X's own detail. */\nexport const toFailure = (err: unknown): ToolResult => {\n if (err instanceof XApiRequestError) {\n return fail(err.message, { status: err.status, errors: err.errors });\n }\n if (err instanceof BudgetExceededError || err instanceof PreconditionError) {\n return fail(err.message, err.details);\n }\n if (err instanceof UserContextRequiredError || err instanceof WritesDisabledError) {\n return fail(err.message);\n }\n if (err instanceof Error) {\n const details = (err as Error & { details?: unknown }).details;\n return fail(err.message, details);\n }\n return fail(\"Unknown error\", err);\n};\n\n/** Run a tool body, JSON-formatting the result and turning errors into a tool error. */\nexport const wrap = async <T>(fn: () => Promise<T>): Promise<ToolResult> => {\n try {\n return ok(await fn());\n } catch (err) {\n return toFailure(err);\n }\n};\n\n/**\n * Every read tool takes this. `maxResults` defaults low and says why in its\n * own description — an agent that reads the schema learns the cost model\n * without anyone having to document it elsewhere.\n */\nexport const maxResultsArg = z\n .number()\n .int()\n .min(1)\n .max(100)\n .default(10)\n .describe(\n \"How many results to return (1-100). Defaults to 10 because X bills about $0.005 per post \" +\n \"read, so 100 results costs roughly $0.50. Raise it deliberately.\",\n );\n\nexport const postIdArg = z\n .string()\n .regex(/^\\d+$/, \"A post id is digits only — the number at the end of a post's URL.\")\n .describe('A post (tweet) id: the digits ending its URL, e.g. \"1799000000000000001\".');\n\nexport const usernameArg = z\n .string()\n .regex(/^@?[A-Za-z0-9_]{1,15}$/, \"An X handle is 1-15 characters of letters, digits or _.\")\n .describe('An X handle, with or without the leading @, e.g. \"mgcrea\".');\n\nexport const userIdArg = z\n .string()\n .regex(/^\\d+$/)\n .describe('A numeric X user id, e.g. \"44196397\". Prefer `username` unless you already have one.');\n\nexport const paginationTokenArg = z\n .string()\n .min(1)\n .optional()\n .describe(\"The `next_token` from a previous call, to fetch the following page.\");\n\nexport const confirmArg = z\n .literal(true)\n .describe(\"Must be true. Explicit acknowledgement that this posts to X and costs money.\");\n\n/** Drop undefined values so we never send `{\"tweet.fields\": undefined}` upstream. */\nexport const compact = <T extends Record<string, unknown>>(obj: T): Partial<T> =>\n Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== undefined)) as Partial<T>;\n\nexport const stripAt = (handle: string): string =>\n handle.startsWith(\"@\") ? handle.slice(1) : handle;\n\n/**\n * The expansions and field sets every post read asks for. Kept in one place\n * because the shaping layer's output is only as good as what was requested —\n * omitting `author_id` here silently degrades every post to \"@unknown\".\n */\nexport const POST_QUERY = {\n expansions: [\n \"author_id\",\n \"referenced_tweets.id\",\n \"referenced_tweets.id.author_id\",\n \"attachments.media_keys\",\n ],\n \"tweet.fields\": [\n \"created_at\",\n \"public_metrics\",\n \"entities\",\n \"conversation_id\",\n \"lang\",\n \"referenced_tweets\",\n ],\n \"user.fields\": [\"username\", \"name\", \"verified\"],\n \"media.fields\": [\"type\", \"url\", \"preview_image_url\", \"alt_text\"],\n};\n\nexport const USER_QUERY = {\n \"user.fields\": [\n \"description\",\n \"public_metrics\",\n \"verified\",\n \"created_at\",\n \"location\",\n \"protected\",\n ],\n};\n\nexport type ToolDeps = {\n cache: DayCache;\n ledger: Ledger;\n budgetUsd?: number | undefined;\n};\n\n/**\n * Guard a read against the configured budget *before* issuing it, so a runaway\n * agent cannot discover the ceiling by crossing it.\n */\nexport const assertWithinBudget = (deps: ToolDeps, what: string, estimateUsd: number): void => {\n if (deps.budgetUsd === undefined) return;\n const spent = deps.ledger.spentUsd();\n if (spent + estimateUsd > deps.budgetUsd) {\n throw new BudgetExceededError({\n estimateUsd,\n spentUsd: spent,\n limitUsd: deps.budgetUsd,\n what,\n });\n }\n};\n\n/**\n * Serve whatever today's cache already holds and fetch only the rest.\n *\n * This mirrors X's own billing rule rather than merely optimizing: within one\n * UTC day the cached ids would not have been billed again anyway, so a hit is\n * genuinely free rather than just fast.\n */\nexport const cachedByIds = async <T>(\n deps: ToolDeps,\n kind: ResourceKind,\n ids: string[],\n fetchMissing: (missing: string[]) => Promise<Map<string, T>>,\n label: string,\n): Promise<{ items: T[]; cost: CostNote; notFound: string[] }> => {\n const cached = new Map<string, T>();\n const missing: string[] = [];\n for (const id of ids) {\n const hit = deps.cache.get(kind, id) as T | undefined;\n if (hit !== undefined) cached.set(id, hit);\n else missing.push(id);\n }\n\n if (missing.length > 0) {\n assertWithinBudget(deps, label, deps.ledger.estimate(kind, missing));\n const fetched = await fetchMissing(missing);\n for (const [id, item] of fetched) {\n deps.cache.set(kind, id, item);\n cached.set(id, item);\n }\n // Bill only what came back: X does not charge for an id it could not serve.\n deps.ledger.record(kind, [...fetched.keys()]);\n }\n\n const items: T[] = [];\n const notFound: string[] = [];\n for (const id of ids) {\n const item = cached.get(id);\n if (item !== undefined) items.push(item);\n else notFound.push(id);\n }\n\n const billable = missing.filter((id) => cached.has(id)).length;\n const free = ids.length - missing.length;\n return {\n items,\n cost: buildCostNote(kind, billable, free, deps),\n notFound,\n };\n};\n\nconst buildCostNote = (\n kind: ResourceKind,\n billable: number,\n free: number,\n deps: ToolDeps,\n): CostNote => {\n const usd = deps.ledger.estimateCount(kind, billable);\n const field =\n kind === \"post\"\n ? \"billable_post_reads\"\n : kind === \"user\"\n ? \"billable_user_reads\"\n : \"owned_reads\";\n return {\n [field]: billable,\n free_from_cache: free,\n estimated_usd: Math.round(usd * 1000) / 1000,\n ...(free > 0\n ? { note: `${free} already read today — X does not bill those again until UTC midnight.` }\n : {}),\n } as CostNote;\n};\n\n/** Record the cost of a read whose ids were only known after the fact (searches). */\nexport const recordResultCost = (deps: ToolDeps, kind: ResourceKind, ids: string[]): CostNote => {\n const { billable, free } = deps.ledger.record(kind, ids);\n return buildCostNote(kind, billable.length, free.length, deps);\n};\n","import type { McpServer } from \"@modelcontextprotocol/server\";\nimport { z } from \"zod\";\n\nimport type { AdsApiClient } from \"#/client/ads\";\nimport { isRecord } from \"#/client/ads-shape\";\nimport { accountIdArg, adsCostNote, adsCountArg, shapeAds } from \"#/tools/ads/util\";\nimport type { AdsContext } from \"#/tools/index\";\nimport { wrap } from \"#/tools/util\";\n\nexport const registerAdsAccountTools = (\n server: McpServer,\n client: AdsApiClient,\n ads: AdsContext,\n resolveAccount: (explicit?: string) => Promise<string>,\n): void => {\n server.registerTool(\n \"x_ads_get_accounts\",\n {\n title: \"X: Ads Get Accounts\",\n description:\n \"List the advertising accounts this login can reach, with their name, currency, timezone \" +\n \"and approval status. Start here: every other ads tool needs an account id, and the \" +\n \"currency and timezone returned here decide how budgets and analytics dates are read.\",\n inputSchema: z.object({\n count: adsCountArg,\n withDeleted: z\n .boolean()\n .default(false)\n .describe(\"Include deleted accounts. Off by default.\"),\n }),\n annotations: { readOnlyHint: true },\n },\n async ({ count, withDeleted }) =>\n wrap(async () => {\n const page = await client.paginateCursor(\n \"/12/accounts\",\n {\n count,\n ...(withDeleted ? { with_deleted: true } : {}),\n },\n { maxItems: count },\n );\n return {\n accounts: page.data,\n environment: ads.sandbox ? \"sandbox\" : \"production\",\n ...(page.nextCursor ? { next_cursor: page.nextCursor } : {}),\n cost: adsCostNote(),\n };\n }),\n );\n\n server.registerTool(\n \"x_ads_get_funding_instruments\",\n {\n title: \"X: Ads Get Funding Instruments\",\n description:\n \"List an account's funding instruments — the payment sources campaigns draw from. A \" +\n \"campaign cannot be created without one, and the instrument's `currency` is the currency \" +\n \"every budget on that campaign is stated in. Check `able_to_fund` before using one.\",\n inputSchema: z.object({ accountId: accountIdArg, count: adsCountArg }),\n annotations: { readOnlyHint: true },\n },\n async ({ accountId, count }) =>\n wrap(async () => {\n const id = await resolveAccount(accountId);\n const page = await client.paginateCursor(\n `/12/accounts/${id}/funding_instruments`,\n { count },\n { maxItems: count },\n );\n return {\n account_id: id,\n funding_instruments: shapeAds({ data: page.data }),\n ...(page.nextCursor ? { next_cursor: page.nextCursor } : {}),\n cost: adsCostNote(),\n };\n }),\n );\n\n // Sandbox only: on production these entities are created by X, not by callers,\n // and offering the tool anywhere else would be offering a guaranteed failure.\n if (!ads.sandbox) return;\n\n server.registerTool(\n \"x_ads_create_sandbox_account\",\n {\n title: \"X: Ads Create Sandbox Account\",\n description:\n \"Create a throwaway ads account in the sandbox, complete with a funding instrument, so \" +\n \"the campaign tools can be exercised without spending anything. Sandbox only — this tool \" +\n \"is not registered against production.\",\n inputSchema: z.object({}),\n annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false },\n },\n async () =>\n wrap(async () => {\n const created = await client.post(\"/12/accounts\");\n const data = isRecord(created) ? created.data : undefined;\n const id = isRecord(data) && typeof data.id === \"string\" ? data.id : undefined;\n return {\n account: data,\n ...(id\n ? {\n next_step:\n `Pass accountId: \"${id}\" to the other ads tools, or set X_ADS_ACCOUNT_ID to it. ` +\n `Call x_ads_get_funding_instruments to find the funding instrument id you will ` +\n `need to create a campaign.`,\n }\n : {}),\n cost: adsCostNote(),\n };\n }),\n );\n};\n","import type { McpServer } from \"@modelcontextprotocol/server\";\nimport { z } from \"zod\";\n\nimport type { AdsApiClient } from \"#/client/ads\";\nimport { isRecord } from \"#/client/ads-shape\";\nimport { PreconditionError } from \"#/client/errors\";\nimport { accountIdArg, adsCostNote, adsTimeArg, entityIdArg } from \"#/tools/ads/util\";\nimport { compact, wrap } from \"#/tools/util\";\n\nconst ENTITIES = [\n \"ACCOUNT\",\n \"CAMPAIGN\",\n \"FUNDING_INSTRUMENT\",\n \"LINE_ITEM\",\n \"PROMOTED_ACCOUNT\",\n \"PROMOTED_TWEET\",\n] as const;\n\nconst METRIC_GROUPS = [\n \"ENGAGEMENT\",\n \"BILLING\",\n \"VIDEO\",\n \"MEDIA\",\n \"WEB_CONVERSION\",\n \"MOBILE_CONVERSION\",\n \"LIFE_TIME_VALUE_MOBILE_CONVERSION\",\n] as const;\n\n/**\n * The synchronous endpoint accepts only these three. `PUBLISHER_NETWORK` is a\n * valid line-item placement but not a valid analytics placement, and passing it\n * here is rejected — worth pinning in the schema rather than discovering at\n * runtime.\n */\nconst PLACEMENTS = [\"ALL_ON_TWITTER\", \"SPOTLIGHT\", \"TREND\"] as const;\n\nconst SEGMENTATIONS = [\"AGE\", \"GENDER\", \"METROS\", \"PLATFORMS\", \"CONVERSION_TAGS\"] as const;\n\nconst HOUR = 60 * 60 * 1000;\nconst SYNC_MAX_DAYS = 7;\nconst SYNC_MAX_ENTITIES = 20;\n\n/** X answers job ids as JSON numbers too large for a JS number to hold exactly. */\nconst jobIdOf = (job: Record<string, unknown>): string | undefined => {\n if (typeof job.id_str === \"string\" && job.id_str) return job.id_str;\n if (typeof job.id === \"string\" && job.id) return job.id;\n return undefined;\n};\n\nexport const registerAdsAnalyticsTools = (\n server: McpServer,\n client: AdsApiClient,\n resolveAccount: (explicit?: string) => Promise<string>,\n): void => {\n server.registerTool(\n \"x_ads_get_stats\",\n {\n title: \"X: Ads Get Stats\",\n description:\n \"Performance metrics for up to 20 entities over at most 7 days, returned immediately. \" +\n 'This is the fast path — use it for \"how did this campaign do last week\". For longer ' +\n \"ranges, segmentation, or more than 20 entities, use the async job tools instead. Times \" +\n \"must be whole hours, and endTime is exclusive.\",\n inputSchema: z.object({\n accountId: accountIdArg,\n entity: z.enum(ENTITIES).describe(\"What kind of thing the ids refer to.\"),\n entityIds: z\n .array(entityIdArg)\n .min(1)\n .max(SYNC_MAX_ENTITIES)\n .describe(`The entities to report on. X allows at most ${SYNC_MAX_ENTITIES} per call.`),\n startTime: adsTimeArg.describe(\"Start of the window, ISO-8601 UTC on a whole hour.\"),\n endTime: adsTimeArg.describe(\"End of the window, exclusive. At most 7 days after start.\"),\n granularity: z\n .enum([\"DAY\", \"HOUR\", \"TOTAL\"])\n .default(\"DAY\")\n .describe(\n \"How finely to bucket. DAY and TOTAL expect startTime at midnight in the ACCOUNT's \" +\n \"timezone, which is not necessarily UTC — check it with x_ads_get_accounts.\",\n ),\n placement: z\n .enum(PLACEMENTS)\n .default(\"ALL_ON_TWITTER\")\n .describe(\"One placement per call. PUBLISHER_NETWORK is not valid here.\"),\n metricGroups: z\n .array(z.enum(METRIC_GROUPS))\n .min(1)\n .default([\"ENGAGEMENT\"])\n .describe(\"Which metric families to return. BILLING carries spend.\"),\n }),\n annotations: { readOnlyHint: true },\n },\n async ({\n accountId,\n entity,\n entityIds,\n startTime,\n endTime,\n granularity,\n placement,\n metricGroups,\n }) =>\n wrap(async () => {\n // Checked locally so a too-wide window fails instantly with the fix,\n // rather than as an opaque INVALID_PARAMETER from X.\n const span = Date.parse(endTime) - Date.parse(startTime);\n if (span <= 0) {\n throw new PreconditionError(\"endTime must be after startTime.\", { startTime, endTime });\n }\n if (span > SYNC_MAX_DAYS * 24 * HOUR) {\n throw new PreconditionError(\n `The synchronous stats endpoint covers at most ${SYNC_MAX_DAYS} days, and this asks ` +\n `for ${Math.round(span / (24 * HOUR))}. Narrow the window, or use ` +\n `x_ads_create_stats_job, which reaches 90 days.`,\n { startTime, endTime, maxDays: SYNC_MAX_DAYS },\n );\n }\n\n const id = await resolveAccount(accountId);\n const raw = await client.get(`/12/stats/accounts/${id}`, {\n entity,\n entity_ids: entityIds,\n start_time: startTime,\n end_time: endTime,\n granularity,\n placement,\n metric_groups: metricGroups,\n });\n return {\n account_id: id,\n entity,\n granularity,\n placement,\n start_time: startTime,\n end_time: endTime,\n stats: isRecord(raw) ? raw.data : raw,\n cost: adsCostNote(),\n };\n }),\n );\n\n server.registerTool(\n \"x_ads_create_stats_job\",\n {\n title: \"X: Ads Create Stats Job\",\n description:\n \"Queue an asynchronous analytics job, for what the synchronous endpoint cannot do: up to \" +\n \"90 days (45 when segmented), segmentation by age, gender, platform or metro, and more \" +\n \"than 20 entities. Returns a job id — poll it with x_ads_get_stats_jobs until its status \" +\n \"is SUCCESS, then fetch the numbers with x_ads_download_stats_job. Queuing a job spends \" +\n \"nothing and changes nothing.\",\n inputSchema: z.object({\n accountId: accountIdArg,\n entity: z.enum(ENTITIES).describe(\"What kind of thing the ids refer to.\"),\n entityIds: z.array(entityIdArg).min(1).max(200).describe(\"The entities to report on.\"),\n startTime: adsTimeArg.describe(\"Start of the window, ISO-8601 UTC on a whole hour.\"),\n endTime: adsTimeArg.describe(\"End of the window, exclusive.\"),\n granularity: z.enum([\"DAY\", \"HOUR\", \"TOTAL\"]).default(\"DAY\").describe(\"Bucket size.\"),\n placement: z.enum(PLACEMENTS).default(\"ALL_ON_TWITTER\").describe(\"One placement per job.\"),\n metricGroups: z\n .array(z.enum(METRIC_GROUPS))\n .min(1)\n .default([\"ENGAGEMENT\"])\n .describe(\"Which metric families to return.\"),\n segmentation: z\n .enum(SEGMENTATIONS)\n .optional()\n .describe(\n \"Break the numbers down by this dimension. Segmented jobs are capped at 45 days. \" +\n \"METROS additionally needs `country`.\",\n ),\n country: z\n .string()\n .optional()\n .describe(\"Targeting-value id of a country. Required when segmentation is METROS.\"),\n }),\n annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false },\n },\n async (args) =>\n wrap(async () => {\n if (args.segmentation === \"METROS\" && !args.country) {\n throw new PreconditionError(\n \"Segmenting by METROS needs a `country`. Find its id with \" +\n \"x_ads_search_targeting_options type=locations, locationType=COUNTRIES.\",\n );\n }\n const id = await resolveAccount(args.accountId);\n const raw = await client.post(\n `/12/stats/jobs/accounts/${id}`,\n compact({\n entity: args.entity,\n entity_ids: args.entityIds,\n start_time: args.startTime,\n end_time: args.endTime,\n granularity: args.granularity,\n placement: args.placement,\n metric_groups: args.metricGroups,\n segmentation_type: args.segmentation,\n country: args.country,\n }),\n );\n const job = isRecord(raw) && isRecord(raw.data) ? raw.data : {};\n return {\n account_id: id,\n job,\n job_id: jobIdOf(job),\n next_step:\n \"Poll x_ads_get_stats_jobs until this job's status is SUCCESS, then call \" +\n \"x_ads_download_stats_job with its url. Jobs typically take seconds to minutes.\",\n cost: adsCostNote(),\n };\n }),\n );\n\n server.registerTool(\n \"x_ads_get_stats_jobs\",\n {\n title: \"X: Ads Get Stats Jobs\",\n description:\n \"List this account's analytics jobs and their status. A job is ready when its status is \" +\n \"SUCCESS, at which point it carries a `url` to pass to x_ads_download_stats_job. Those \" +\n \"URLs expire, so re-read the job rather than reusing an old one.\",\n inputSchema: z.object({\n accountId: accountIdArg,\n jobIds: z\n .array(z.string().min(1))\n .max(200)\n .optional()\n .describe(\"Only these job ids. Omit to list recent jobs.\"),\n count: z.number().int().min(1).max(200).default(50).describe(\"How many jobs to return.\"),\n }),\n annotations: { readOnlyHint: true },\n },\n async ({ accountId, jobIds, count }) =>\n wrap(async () => {\n const id = await resolveAccount(accountId);\n const raw = await client.get(\n `/12/stats/jobs/accounts/${id}`,\n compact({ count, ...(jobIds?.length ? { job_ids: jobIds } : {}) }),\n );\n const jobs = isRecord(raw) && Array.isArray(raw.data) ? raw.data : [];\n const ready = jobs.filter(isRecord).filter((j) => j.status === \"SUCCESS\").length;\n return {\n account_id: id,\n jobs,\n ready_count: ready,\n ...(jobs.length > 0 && ready === 0\n ? { note: \"No job has finished yet. Poll again in a few seconds.\" }\n : {}),\n cost: adsCostNote(),\n };\n }),\n );\n\n server.registerTool(\n \"x_ads_download_stats_job\",\n {\n title: \"X: Ads Download Stats Job\",\n description:\n \"Fetch and decompress a finished analytics job's results. By default it returns a \" +\n \"per-entity summary rather than every row, because a segmented 90-day job is far larger \" +\n \"than a useful answer. Set raw to true for the underlying rows, bounded by maxRows. If \" +\n \"the download is refused as too large, re-run the job over fewer entities, a shorter \" +\n \"range, or a coarser granularity.\",\n inputSchema: z.object({\n url: z\n .string()\n .url()\n .describe(\"The `url` from a SUCCESS job in x_ads_get_stats_jobs. These expire.\"),\n raw: z\n .boolean()\n .default(false)\n .describe(\"Return the underlying rows instead of the summary.\"),\n maxRows: z\n .number()\n .int()\n .min(1)\n .max(2000)\n .default(200)\n .describe(\"Cap on rows returned when raw is true.\"),\n }),\n annotations: { readOnlyHint: true },\n },\n async ({ url, raw, maxRows }) =>\n wrap(async () => {\n const { text, bytes } = await client.downloadGzipped(url);\n const parsed: unknown = JSON.parse(text);\n const rows = isRecord(parsed) && Array.isArray(parsed.data) ? parsed.data : [];\n\n if (raw) {\n return {\n rows: rows.slice(0, maxRows),\n row_count: rows.length,\n truncated: rows.length > maxRows,\n decompressed_bytes: bytes,\n cost: adsCostNote(),\n };\n }\n\n // Summarise rather than echo: the caller almost always wants totals per\n // entity, and returning tens of thousands of rows to get them is a\n // context bill with no matching benefit.\n const summary = rows.filter(isRecord).map((row) => {\n const series = Array.isArray(row.id_data) ? row.id_data : [];\n const totals: Record<string, number> = {};\n for (const segment of series) {\n if (!isRecord(segment) || !isRecord(segment.metrics)) continue;\n for (const [metric, values] of Object.entries(segment.metrics)) {\n if (!Array.isArray(values)) continue;\n const sum = values.reduce<number>(\n (acc, v) => acc + (typeof v === \"number\" ? v : 0),\n 0,\n );\n totals[metric] = (totals[metric] ?? 0) + sum;\n }\n }\n return { id: row.id, segments: series.length, totals };\n });\n\n return {\n entities: summary,\n row_count: rows.length,\n decompressed_bytes: bytes,\n note:\n \"Totals are summed across every bucket and segment in the job. Pass raw: true for \" +\n \"the per-bucket rows.\",\n cost: adsCostNote(),\n };\n }),\n );\n};\n","import type { McpServer } from \"@modelcontextprotocol/server\";\nimport { z } from \"zod\";\n\nimport type { AdsApiClient } from \"#/client/ads\";\nimport { accountIdArg, adsCostNote, adsCountArg, shapeAds } from \"#/tools/ads/util\";\nimport { wrap } from \"#/tools/util\";\n\nexport const registerAdsAudienceTools = (\n server: McpServer,\n client: AdsApiClient,\n resolveAccount: (explicit?: string) => Promise<string>,\n): void => {\n server.registerTool(\n \"x_ads_get_audiences\",\n {\n title: \"X: Ads Get Audiences\",\n description:\n \"List an account's custom audiences with their size and targetability. Use an audience's \" +\n \"id as the targetingValue of a CUSTOM_AUDIENCE criterion. Read-only: uploading audience \" +\n \"members means handling personal data and is deliberately not exposed here. Note that \" +\n '\"tailored audiences\" is the old name for these and its endpoints are long gone.',\n inputSchema: z.object({ accountId: accountIdArg, count: adsCountArg }),\n annotations: { readOnlyHint: true },\n },\n async ({ accountId, count }) =>\n wrap(async () => {\n const id = await resolveAccount(accountId);\n const page = await client.paginateCursor(\n `/12/accounts/${id}/custom_audiences`,\n { count },\n { maxItems: count },\n );\n return {\n account_id: id,\n audiences: shapeAds({ data: page.data }),\n ...(page.nextCursor ? { next_cursor: page.nextCursor } : {}),\n cost: adsCostNote(),\n };\n }),\n );\n};\n","import type { McpServer } from \"@modelcontextprotocol/server\";\nimport { z } from \"zod\";\n\nimport type { AdsApiClient } from \"#/client/ads\";\nimport { isRecord, toMicro } from \"#/client/ads-shape\";\nimport {\n accountIdArg,\n activateArg,\n adsConfirmArg,\n adsCostNote,\n adsCountArg,\n adsTimeArg,\n budgetArg,\n entityIdArg,\n shapeAds,\n} from \"#/tools/ads/util\";\nimport type { AdsContext } from \"#/tools/index\";\nimport { compact, wrap } from \"#/tools/util\";\n\n/** Line-item enums, verbatim from the v12 reference. */\nconst OBJECTIVES = [\n \"APP_ENGAGEMENTS\",\n \"APP_INSTALLS\",\n \"REACH\",\n \"FOLLOWERS\",\n \"ENGAGEMENTS\",\n \"VIDEO_VIEWS\",\n \"PREROLL_VIEWS\",\n \"WEBSITE_CLICKS\",\n] as const;\n\nconst PRODUCT_TYPES = [\"MEDIA\", \"PROMOTED_ACCOUNT\", \"PROMOTED_TWEETS\"] as const;\n\nconst PLACEMENTS = [\n \"ALL_ON_TWITTER\",\n \"PUBLISHER_NETWORK\",\n \"TAP_BANNER\",\n \"TAP_FULL\",\n \"TAP_FULL_LANDSCAPE\",\n \"TAP_NATIVE\",\n \"TAP_MRECT\",\n \"TWITTER_PROFILE\",\n \"TWITTER_REPLIES\",\n \"TWITTER_SEARCH\",\n \"TWITTER_TIMELINE\",\n] as const;\n\nconst ENTITY_KINDS = [\"campaign\", \"line_item\", \"promoted_tweet\"] as const;\n\nconst PATH_FOR: Record<(typeof ENTITY_KINDS)[number], string> = {\n campaign: \"campaigns\",\n line_item: \"line_items\",\n promoted_tweet: \"promoted_tweets\",\n};\n\n/**\n * What a create tool sends for `entity_status`, and the sentence explaining it.\n * PAUSED is the default because an ACTIVE campaign begins spending the moment\n * it exists, and an agent that mis-set a budget should not discover that from\n * the invoice. `activateImmediately` is the deliberate way out.\n */\nconst statusFor = (activate: boolean): { status: \"ACTIVE\" | \"PAUSED\"; note: string } =>\n activate\n ? {\n status: \"ACTIVE\",\n note: \"Created ACTIVE, as requested — delivery and spending start now.\",\n }\n : {\n status: \"PAUSED\",\n note:\n \"Created PAUSED, so it is spending nothing. Activate it with x_ads_set_entity_status \" +\n \"when you are ready.\",\n };\n\nexport const registerAdsCampaignTools = (\n server: McpServer,\n client: AdsApiClient,\n ads: AdsContext,\n resolveAccount: (explicit?: string) => Promise<string>,\n): void => {\n /** The currency every budget on a campaign is denominated in. */\n const currencyOf = async (\n accountId: string,\n fundingInstrumentId: string,\n ): Promise<string | undefined> => {\n try {\n const raw = await client.get(\n `/12/accounts/${accountId}/funding_instruments/${fundingInstrumentId}`,\n );\n const data = isRecord(raw) ? raw.data : undefined;\n return isRecord(data) && typeof data.currency === \"string\" ? data.currency : undefined;\n } catch {\n // Never fail a write because the currency lookup did — the echo is a\n // courtesy, and a missing one is much cheaper than a lost campaign.\n return undefined;\n }\n };\n\n server.registerTool(\n \"x_ads_get_campaigns\",\n {\n title: \"X: Ads Get Campaigns\",\n description:\n \"List an account's campaigns with their budgets, funding instrument and status. Budgets \" +\n \"come back in both major units (`daily_budget`) and X's raw millionths \" +\n \"(`daily_budget_amount_local_micro`) — read the former. Note that in v12 campaigns carry \" +\n \"no start or end date; flight dates live on their line items.\",\n inputSchema: z.object({\n accountId: accountIdArg,\n campaignIds: z\n .array(entityIdArg)\n .max(200)\n .optional()\n .describe(\"Only these campaign ids. Omit to list them all.\"),\n count: adsCountArg,\n withDeleted: z.boolean().default(false).describe(\"Include deleted campaigns.\"),\n }),\n annotations: { readOnlyHint: true },\n },\n async ({ accountId, campaignIds, count, withDeleted }) =>\n wrap(async () => {\n const id = await resolveAccount(accountId);\n const page = await client.paginateCursor(\n `/12/accounts/${id}/campaigns`,\n compact({\n count,\n ...(campaignIds?.length ? { campaign_ids: campaignIds } : {}),\n ...(withDeleted ? { with_deleted: true } : {}),\n }),\n { maxItems: count },\n );\n return {\n account_id: id,\n campaigns: shapeAds({ data: page.data }),\n ...(page.nextCursor ? { next_cursor: page.nextCursor } : {}),\n cost: adsCostNote(),\n };\n }),\n );\n\n server.registerTool(\n \"x_ads_get_line_items\",\n {\n title: \"X: Ads Get Line Items\",\n description:\n \"List an account's line items — the ad groups that carry the objective, bid, placements \" +\n \"and flight dates under a campaign. Targeting and creatives attach to a line item, not \" +\n \"to its campaign, so this is the id you need for x_ads_get_targeting_criteria and \" +\n \"x_ads_create_promoted_tweet.\",\n inputSchema: z.object({\n accountId: accountIdArg,\n campaignIds: z\n .array(entityIdArg)\n .max(200)\n .optional()\n .describe(\"Only line items under these campaigns.\"),\n lineItemIds: z.array(entityIdArg).max(200).optional().describe(\"Only these line item ids.\"),\n count: adsCountArg,\n withDeleted: z.boolean().default(false).describe(\"Include deleted line items.\"),\n }),\n annotations: { readOnlyHint: true },\n },\n async ({ accountId, campaignIds, lineItemIds, count, withDeleted }) =>\n wrap(async () => {\n const id = await resolveAccount(accountId);\n const page = await client.paginateCursor(\n `/12/accounts/${id}/line_items`,\n compact({\n count,\n ...(campaignIds?.length ? { campaign_ids: campaignIds } : {}),\n ...(lineItemIds?.length ? { line_item_ids: lineItemIds } : {}),\n ...(withDeleted ? { with_deleted: true } : {}),\n }),\n { maxItems: count },\n );\n return {\n account_id: id,\n line_items: shapeAds({ data: page.data }),\n ...(page.nextCursor ? { next_cursor: page.nextCursor } : {}),\n cost: adsCostNote(),\n };\n }),\n );\n\n server.registerTool(\n \"x_ads_get_promoted_tweets\",\n {\n title: \"X: Ads Get Promoted Tweets\",\n description:\n \"List the posts promoted under an account's line items. Each entry pairs a line item with \" +\n \"the post id it is promoting; look the post itself up with x_get_post if you need its text.\",\n inputSchema: z.object({\n accountId: accountIdArg,\n lineItemIds: z\n .array(entityIdArg)\n .max(200)\n .optional()\n .describe(\"Only promoted posts under these line items.\"),\n count: adsCountArg,\n }),\n annotations: { readOnlyHint: true },\n },\n async ({ accountId, lineItemIds, count }) =>\n wrap(async () => {\n const id = await resolveAccount(accountId);\n const page = await client.paginateCursor(\n `/12/accounts/${id}/promoted_tweets`,\n compact({ count, ...(lineItemIds?.length ? { line_item_ids: lineItemIds } : {}) }),\n { maxItems: count },\n );\n return {\n account_id: id,\n promoted_tweets: page.data,\n ...(page.nextCursor ? { next_cursor: page.nextCursor } : {}),\n cost: adsCostNote(),\n };\n }),\n );\n\n // Everything below changes live advertising objects. Registered only when\n // X_ADS_ALLOW_WRITES is on, so with the defaults these tools do not exist.\n if (!ads.allowWrites) return;\n\n server.registerTool(\n \"x_ads_create_campaign\",\n {\n title: \"X: Ads Create Campaign\",\n description:\n \"Create a campaign. SPENDS MONEY once activated. Budgets are given in MAJOR currency \" +\n \"units — 50 means 50.00 — and converted to X's millionths for you; never pass a \" +\n \"pre-multiplied figure. The campaign is created PAUSED unless you set \" +\n \"activateImmediately, so the normal flow is: create, add a line item, add targeting, then \" +\n \"activate. In v12 a campaign has no dates of its own; set them on the line item.\",\n inputSchema: z.object({\n accountId: accountIdArg,\n fundingInstrumentId: entityIdArg.describe(\n \"Which funding instrument pays for this. List them with x_ads_get_funding_instruments.\",\n ),\n name: z.string().min(1).max(255).describe(\"Campaign name, up to 255 characters.\"),\n dailyBudget: budgetArg.describe(\n \"Daily budget in MAJOR currency units of the funding instrument (50 means 50.00). Do \" +\n \"NOT multiply by 1,000,000.\",\n ),\n totalBudget: budgetArg\n .optional()\n .describe(\"Optional lifetime cap, in the same major units as dailyBudget.\"),\n activateImmediately: activateArg,\n confirm: adsConfirmArg,\n }),\n annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false },\n },\n async ({\n accountId,\n fundingInstrumentId,\n name,\n dailyBudget,\n totalBudget,\n activateImmediately,\n }) =>\n wrap(async () => {\n const id = await resolveAccount(accountId);\n const { status, note } = statusFor(activateImmediately);\n const dailyMicro = toMicro(dailyBudget);\n const totalMicro = totalBudget === undefined ? undefined : toMicro(totalBudget);\n\n const created = await client.post(\n `/12/accounts/${id}/campaigns`,\n compact({\n funding_instrument_id: fundingInstrumentId,\n name,\n daily_budget_amount_local_micro: dailyMicro,\n total_budget_amount_local_micro: totalMicro,\n entity_status: status,\n }),\n );\n\n const currency = await currencyOf(id, fundingInstrumentId);\n return {\n account_id: id,\n campaign: shapeAds(created, currency),\n entity_status: status,\n // Echo the arithmetic rather than only the result: a budget is the\n // one field where being off by a factor of a million is both easy\n // and expensive, and this makes it checkable at a glance.\n budget_sent: {\n daily_budget: dailyBudget,\n daily_budget_amount_local_micro: dailyMicro,\n ...(totalBudget !== undefined\n ? { total_budget: totalBudget, total_budget_amount_local_micro: totalMicro }\n : {}),\n ...(currency ? { currency } : {}),\n },\n next_step: note,\n environment: ads.sandbox ? \"sandbox\" : \"production\",\n cost: adsCostNote(),\n };\n }),\n );\n\n server.registerTool(\n \"x_ads_update_campaign\",\n {\n title: \"X: Ads Update Campaign\",\n description:\n \"Change a campaign's name, budget or status. Budgets are in MAJOR currency units, as on \" +\n \"create. Raising a daily budget on an ACTIVE campaign increases spending immediately.\",\n inputSchema: z.object({\n accountId: accountIdArg,\n campaignId: entityIdArg.describe(\"The campaign to change.\"),\n name: z.string().min(1).max(255).optional().describe(\"New name.\"),\n dailyBudget: budgetArg.optional().describe(\"New daily budget, in major currency units.\"),\n totalBudget: budgetArg.optional().describe(\"New lifetime cap, in major currency units.\"),\n entityStatus: z\n .enum([\"ACTIVE\", \"PAUSED\"])\n .optional()\n .describe(\"ACTIVE resumes delivery and spending; PAUSED stops it.\"),\n confirm: adsConfirmArg,\n }),\n annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true },\n },\n async ({ accountId, campaignId, name, dailyBudget, totalBudget, entityStatus }) =>\n wrap(async () => {\n const id = await resolveAccount(accountId);\n const dailyMicro = dailyBudget === undefined ? undefined : toMicro(dailyBudget);\n const totalMicro = totalBudget === undefined ? undefined : toMicro(totalBudget);\n const updated = await client.put(\n `/12/accounts/${id}/campaigns/${campaignId}`,\n compact({\n name,\n daily_budget_amount_local_micro: dailyMicro,\n total_budget_amount_local_micro: totalMicro,\n entity_status: entityStatus,\n }),\n );\n return {\n account_id: id,\n campaign: shapeAds(updated),\n ...(dailyBudget !== undefined\n ? {\n budget_sent: {\n daily_budget: dailyBudget,\n daily_budget_amount_local_micro: dailyMicro,\n },\n }\n : {}),\n cost: adsCostNote(),\n };\n }),\n );\n\n server.registerTool(\n \"x_ads_delete_campaign\",\n {\n title: \"X: Ads Delete Campaign\",\n description:\n \"Delete a campaign. This also stops its line items. X keeps deleted campaigns visible to \" +\n \"`withDeleted` reads but they cannot be revived — pause the campaign instead if you may \" +\n \"want it back.\",\n inputSchema: z.object({\n accountId: accountIdArg,\n campaignId: entityIdArg.describe(\"The campaign to delete.\"),\n confirm: adsConfirmArg,\n }),\n annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true },\n },\n async ({ accountId, campaignId }) =>\n wrap(async () => {\n const id = await resolveAccount(accountId);\n const res = await client.del(`/12/accounts/${id}/campaigns/${campaignId}`);\n return {\n account_id: id,\n deleted: campaignId,\n campaign: shapeAds(res),\n cost: adsCostNote(),\n };\n }),\n );\n\n server.registerTool(\n \"x_ads_create_line_item\",\n {\n title: \"X: Ads Create Line Item\",\n description:\n \"Create a line item under a campaign — the ad group carrying the objective, bid, \" +\n \"placements and flight dates. Created PAUSED unless activateImmediately is set. Bids are \" +\n \"in MAJOR currency units. A line item with no targeting criteria and no promoted post \" +\n \"will not deliver, so this is usually the second of three calls.\",\n inputSchema: z.object({\n accountId: accountIdArg,\n campaignId: entityIdArg.describe(\"The campaign this belongs to.\"),\n name: z.string().min(1).max(255).optional().describe(\"Line item name.\"),\n objective: z.enum(OBJECTIVES).describe(\"What the line item optimises for.\"),\n productType: z.enum(PRODUCT_TYPES).describe(\"The kind of ad. Usually PROMOTED_TWEETS.\"),\n placements: z\n .array(z.enum(PLACEMENTS))\n .min(1)\n .describe(\"Where ads may appear. ALL_ON_TWITTER is the usual choice.\"),\n startTime: adsTimeArg.describe(\"When delivery starts, ISO-8601 UTC on a whole hour.\"),\n endTime: adsTimeArg.optional().describe(\"When delivery stops. Omit to run open-ended.\"),\n bid: budgetArg\n .optional()\n .describe(\"Bid in MAJOR currency units. Omit to let X bid automatically.\"),\n totalBudget: budgetArg.optional().describe(\"Lifetime cap for this line item.\"),\n activateImmediately: activateArg,\n confirm: adsConfirmArg,\n }),\n annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false },\n },\n async (args) =>\n wrap(async () => {\n const id = await resolveAccount(args.accountId);\n const { status, note } = statusFor(args.activateImmediately);\n const bidMicro = args.bid === undefined ? undefined : toMicro(args.bid);\n const created = await client.post(\n `/12/accounts/${id}/line_items`,\n compact({\n campaign_id: args.campaignId,\n name: args.name,\n objective: args.objective,\n product_type: args.productType,\n placements: args.placements,\n start_time: args.startTime,\n end_time: args.endTime,\n bid_amount_local_micro: bidMicro,\n total_budget_amount_local_micro:\n args.totalBudget === undefined ? undefined : toMicro(args.totalBudget),\n entity_status: status,\n }),\n );\n return {\n account_id: id,\n line_item: shapeAds(created),\n entity_status: status,\n ...(args.bid !== undefined\n ? { bid_sent: { bid: args.bid, bid_amount_local_micro: bidMicro } }\n : {}),\n next_step:\n `${note} Attach targeting with x_ads_create_targeting_criterion and a post with ` +\n `x_ads_create_promoted_tweet before activating, or it will not deliver.`,\n cost: adsCostNote(),\n };\n }),\n );\n\n server.registerTool(\n \"x_ads_update_line_item\",\n {\n title: \"X: Ads Update Line Item\",\n description:\n \"Change a line item's name, bid, dates or status. Bids and budgets are in MAJOR currency \" +\n \"units, as on create.\",\n inputSchema: z.object({\n accountId: accountIdArg,\n lineItemId: entityIdArg.describe(\"The line item to change.\"),\n name: z.string().min(1).max(255).optional().describe(\"New name.\"),\n bid: budgetArg.optional().describe(\"New bid, in major currency units.\"),\n totalBudget: budgetArg.optional().describe(\"New lifetime cap, in major currency units.\"),\n startTime: adsTimeArg.optional().describe(\"New start time.\"),\n endTime: adsTimeArg.optional().describe(\"New end time.\"),\n entityStatus: z.enum([\"ACTIVE\", \"PAUSED\"]).optional().describe(\"Resume or stop delivery.\"),\n confirm: adsConfirmArg,\n }),\n annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true },\n },\n async (args) =>\n wrap(async () => {\n const id = await resolveAccount(args.accountId);\n const updated = await client.put(\n `/12/accounts/${id}/line_items/${args.lineItemId}`,\n compact({\n name: args.name,\n bid_amount_local_micro: args.bid === undefined ? undefined : toMicro(args.bid),\n total_budget_amount_local_micro:\n args.totalBudget === undefined ? undefined : toMicro(args.totalBudget),\n start_time: args.startTime,\n end_time: args.endTime,\n entity_status: args.entityStatus,\n }),\n );\n return { account_id: id, line_item: shapeAds(updated), cost: adsCostNote() };\n }),\n );\n\n server.registerTool(\n \"x_ads_delete_line_item\",\n {\n title: \"X: Ads Delete Line Item\",\n description:\n \"Delete a line item. Irreversible — pause it instead if you may want it back. Its \" +\n \"targeting criteria and promoted posts stop delivering with it.\",\n inputSchema: z.object({\n accountId: accountIdArg,\n lineItemId: entityIdArg.describe(\"The line item to delete.\"),\n confirm: adsConfirmArg,\n }),\n annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true },\n },\n async ({ accountId, lineItemId }) =>\n wrap(async () => {\n const id = await resolveAccount(accountId);\n const res = await client.del(`/12/accounts/${id}/line_items/${lineItemId}`);\n return {\n account_id: id,\n deleted: lineItemId,\n line_item: shapeAds(res),\n cost: adsCostNote(),\n };\n }),\n );\n\n server.registerTool(\n \"x_ads_create_promoted_tweet\",\n {\n title: \"X: Ads Create Promoted Tweet\",\n description:\n \"Promote existing posts under a line item. The posts must already exist — compose one \" +\n \"first with x_compose_post, or pick an id from x_get_user_posts. Promotion begins when \" +\n \"the line item is active.\",\n inputSchema: z.object({\n accountId: accountIdArg,\n lineItemId: entityIdArg.describe(\"The line item that will carry these posts.\"),\n postIds: z\n .array(z.string().regex(/^\\d+$/))\n .min(1)\n .max(50)\n .describe('Post ids to promote, e.g. [\"1799000000000000001\"].'),\n confirm: adsConfirmArg,\n }),\n annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false },\n },\n async ({ accountId, lineItemId, postIds }) =>\n wrap(async () => {\n const id = await resolveAccount(accountId);\n const created = await client.post(`/12/accounts/${id}/promoted_tweets`, {\n line_item_id: lineItemId,\n tweet_ids: postIds,\n });\n return {\n account_id: id,\n line_item_id: lineItemId,\n promoted_tweets: shapeAds(created),\n cost: adsCostNote(),\n };\n }),\n );\n\n server.registerTool(\n \"x_ads_delete_promoted_tweet\",\n {\n title: \"X: Ads Delete Promoted Tweet\",\n description:\n \"Stop promoting a post by removing it from its line item. The post itself is untouched \" +\n \"and stays on the timeline — use x_delete_post to remove that.\",\n inputSchema: z.object({\n accountId: accountIdArg,\n promotedTweetId: entityIdArg.describe(\n \"The promoted-tweet id from x_ads_get_promoted_tweets, not the post id.\",\n ),\n confirm: adsConfirmArg,\n }),\n annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true },\n },\n async ({ accountId, promotedTweetId }) =>\n wrap(async () => {\n const id = await resolveAccount(accountId);\n const res = await client.del(`/12/accounts/${id}/promoted_tweets/${promotedTweetId}`);\n return {\n account_id: id,\n deleted: promotedTweetId,\n promoted_tweet: shapeAds(res),\n note: \"The post itself is unaffected and is still on the timeline.\",\n cost: adsCostNote(),\n };\n }),\n );\n\n server.registerTool(\n \"x_ads_set_entity_status\",\n {\n title: \"X: Ads Set Entity Status\",\n description:\n \"Activate or pause a campaign or line item. This is the switch that starts and stops \" +\n \"spending: ACTIVE begins delivery immediately at the entity's configured budget. Check \" +\n \"the budget with x_ads_get_campaigns before activating something you did not just create.\",\n inputSchema: z.object({\n accountId: accountIdArg,\n entityType: z\n .enum([\"campaign\", \"line_item\"])\n .describe(\"Which kind of entity the id refers to.\"),\n entityId: entityIdArg.describe(\"The campaign or line item id.\"),\n status: z\n .enum([\"ACTIVE\", \"PAUSED\"])\n .describe(\"ACTIVE starts delivery and spending. PAUSED stops it.\"),\n confirm: adsConfirmArg,\n }),\n annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true },\n },\n async ({ accountId, entityType, entityId, status }) =>\n wrap(async () => {\n const id = await resolveAccount(accountId);\n const updated = await client.put(`/12/accounts/${id}/${PATH_FOR[entityType]}/${entityId}`, {\n entity_status: status,\n });\n return {\n account_id: id,\n entity_type: entityType,\n entity_id: entityId,\n entity_status: status,\n entity: shapeAds(updated),\n note:\n status === \"ACTIVE\"\n ? `Now ACTIVE${ads.sandbox ? \" (sandbox — nothing is really spent)\" : \" — delivery and spending have started\"}.`\n : \"Now PAUSED. It is spending nothing.\",\n cost: adsCostNote(),\n };\n }),\n );\n};\n","import type { McpServer } from \"@modelcontextprotocol/server\";\nimport { z } from \"zod\";\n\nimport type { AdsApiClient } from \"#/client/ads\";\nimport {\n accountIdArg,\n adsConfirmArg,\n adsCostNote,\n adsCountArg,\n entityIdArg,\n shapeAds,\n} from \"#/tools/ads/util\";\nimport type { AdsContext } from \"#/tools/index\";\nimport { compact, wrap } from \"#/tools/util\";\n\n/**\n * The twelve targeting-option lookup endpoints that exist under\n * `/12/targeting_criteria/`. Deliberately a closed list: guessing a thirteenth\n * (`keywords` is the one people reach for) 404s, and keyword research lives at\n * `/12/insights/keywords/search` on a different path entirely.\n */\nconst OPTION_TYPES = [\n \"app_store_categories\",\n \"conversations\",\n \"devices\",\n \"events\",\n \"interests\",\n \"languages\",\n \"locations\",\n \"network_operators\",\n \"platform_versions\",\n \"platforms\",\n \"tv_markets\",\n \"tv_shows\",\n] as const;\n\nconst LOCATION_TYPES = [\"COUNTRIES\", \"REGIONS\", \"METROS\", \"CITIES\", \"POSTAL_CODES\"] as const;\n\nexport const registerAdsTargetingTools = (\n server: McpServer,\n client: AdsApiClient,\n ads: AdsContext,\n resolveAccount: (explicit?: string) => Promise<string>,\n): void => {\n server.registerTool(\n \"x_ads_get_targeting_criteria\",\n {\n title: \"X: Ads Get Targeting Criteria\",\n description:\n \"Read the targeting attached to one or more line items — the interests, locations, \" +\n \"keywords, follower look-alikes and audiences that decide who sees the ads. Targeting \" +\n \"hangs off line items, never off campaigns.\",\n inputSchema: z.object({\n accountId: accountIdArg,\n lineItemIds: z\n .array(entityIdArg)\n .min(1)\n .max(200)\n .describe(\"The line items whose targeting you want. At least one is required.\"),\n count: adsCountArg,\n }),\n annotations: { readOnlyHint: true },\n },\n async ({ accountId, lineItemIds, count }) =>\n wrap(async () => {\n const id = await resolveAccount(accountId);\n const page = await client.paginateCursor(\n `/12/accounts/${id}/targeting_criteria`,\n { count, line_item_ids: lineItemIds },\n { maxItems: count },\n );\n return {\n account_id: id,\n targeting_criteria: page.data,\n ...(page.nextCursor ? { next_cursor: page.nextCursor } : {}),\n cost: adsCostNote(),\n };\n }),\n );\n\n server.registerTool(\n \"x_ads_search_targeting_options\",\n {\n title: \"X: Ads Search Targeting Options\",\n description:\n \"Look up the valid values for a targeting type before using one. X's targeting takes \" +\n 'opaque ids, not names — a location is something like \"96683cc9126741d1\", not \"Paris\" ' +\n \"— so this is the step that turns an intention into a `targetingValue` you can pass to \" +\n \"x_ads_create_targeting_criterion. There is no keyword option here: keywords are free \" +\n \"text and need no lookup.\",\n inputSchema: z.object({\n type: z\n .enum(OPTION_TYPES)\n .describe(\"Which targeting dimension to search. Each maps to one X lookup endpoint.\"),\n q: z\n .string()\n .min(1)\n .optional()\n .describe('Free-text filter, e.g. \"Paris\" for locations or \"cycling\" for interests.'),\n locationType: z\n .enum(LOCATION_TYPES)\n .optional()\n .describe(\"For type=locations only: which granularity of place to return.\"),\n locale: z\n .string()\n .min(2)\n .optional()\n .describe('For type=tv_shows, which requires it, e.g. \"en-US\".'),\n eventTypes: z\n .array(z.string().min(1))\n .optional()\n .describe(\"For type=events, which requires it.\"),\n countryCode: z\n .string()\n .length(2)\n .optional()\n .describe('Two-letter country filter where the endpoint supports one, e.g. \"FR\".'),\n count: adsCountArg,\n }),\n annotations: { readOnlyHint: true },\n },\n async ({ type, q, locationType, locale, eventTypes, countryCode, count }) =>\n wrap(async () => {\n const page = await client.paginateCursor(\n `/12/targeting_criteria/${type}`,\n compact({\n count,\n q,\n location_type: locationType,\n locale,\n event_types: eventTypes,\n country_code: countryCode,\n }),\n { maxItems: count },\n );\n return {\n type,\n options: page.data,\n ...(page.nextCursor ? { next_cursor: page.nextCursor } : {}),\n note:\n \"Pass an option's `targeting_value` (or `id`) as targetingValue to \" +\n \"x_ads_create_targeting_criterion.\",\n cost: adsCostNote(),\n };\n }),\n );\n\n if (!ads.allowWrites) return;\n\n server.registerTool(\n \"x_ads_create_targeting_criterion\",\n {\n title: \"X: Ads Create Targeting Criterion\",\n description:\n \"Add one targeting criterion to a line item. Look the value up first with \" +\n \"x_ads_search_targeting_options — X takes opaque ids for most types, and an invented one \" +\n \"is rejected. Criteria of different types intersect (AND) while criteria of the same type \" +\n \"union (OR), so adding two locations widens the audience while adding a location and an \" +\n \"interest narrows it. Broadening targeting on an active line item increases spending.\",\n inputSchema: z.object({\n accountId: accountIdArg,\n lineItemId: entityIdArg.describe(\"The line item to target.\"),\n targetingType: z\n .string()\n .min(1)\n .describe(\n 'The criterion type, e.g. \"LOCATION\", \"INTEREST\", \"BROAD_KEYWORD\", \"FOLLOWERS_OF_USER\", ' +\n '\"CUSTOM_AUDIENCE\", \"PLATFORM\", \"LANGUAGE\".',\n ),\n targetingValue: z\n .string()\n .min(1)\n .describe(\n \"The value for that type — an id from x_ads_search_targeting_options, or free text \" +\n \"for keyword types.\",\n ),\n operatorType: z\n .enum([\"EQ\", \"NE\", \"GTE\", \"LT\"])\n .default(\"EQ\")\n .describe(\"How to compare. EQ is right for almost everything; NE excludes.\"),\n confirm: adsConfirmArg,\n }),\n annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false },\n },\n async ({ accountId, lineItemId, targetingType, targetingValue, operatorType }) =>\n wrap(async () => {\n const id = await resolveAccount(accountId);\n const created = await client.post(`/12/accounts/${id}/targeting_criteria`, {\n line_item_id: lineItemId,\n targeting_type: targetingType,\n targeting_value: targetingValue,\n operator_type: operatorType,\n });\n return {\n account_id: id,\n line_item_id: lineItemId,\n targeting_criterion: shapeAds(created),\n cost: adsCostNote(),\n };\n }),\n );\n\n server.registerTool(\n \"x_ads_delete_targeting_criterion\",\n {\n title: \"X: Ads Delete Targeting Criterion\",\n description:\n \"Remove one targeting criterion from a line item. Narrowing or widening targeting on an \" +\n \"active line item changes who sees the ads immediately. Removing the last criterion \" +\n \"leaves the line item targeting everyone, which usually spends faster, not slower.\",\n inputSchema: z.object({\n accountId: accountIdArg,\n targetingCriterionId: entityIdArg.describe(\n \"The criterion id from x_ads_get_targeting_criteria.\",\n ),\n confirm: adsConfirmArg,\n }),\n annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true },\n },\n async ({ accountId, targetingCriterionId }) =>\n wrap(async () => {\n const id = await resolveAccount(accountId);\n const res = await client.del(\n `/12/accounts/${id}/targeting_criteria/${targetingCriterionId}`,\n );\n return {\n account_id: id,\n deleted: targetingCriterionId,\n targeting_criterion: shapeAds(res),\n cost: adsCostNote(),\n };\n }),\n );\n};\n","import type { McpServer } from \"@modelcontextprotocol/server\";\n\nimport type { AdsApiClient } from \"#/client/ads\";\nimport { registerAdsAccountTools } from \"#/tools/ads/accounts\";\nimport { registerAdsAnalyticsTools } from \"#/tools/ads/analytics\";\nimport { registerAdsAudienceTools } from \"#/tools/ads/audiences\";\nimport { registerAdsCampaignTools } from \"#/tools/ads/campaigns\";\nimport { registerAdsTargetingTools } from \"#/tools/ads/targeting\";\nimport { createAccountResolver } from \"#/tools/ads/util\";\nimport type { ToolContext } from \"#/tools/index\";\n\n/**\n * Register the X Ads API tools.\n *\n * Reads and the analytics-job tools are registered whenever ads is enabled.\n * The campaign-mutating tools appear only when `X_ADS_ALLOW_WRITES` is on, so\n * with the defaults they are not merely refused — they do not exist and cannot\n * be called. Sandbox-only tools appear only when pointed at the sandbox.\n *\n * The analytics-job tools sit with the reads rather than behind the write gate\n * on purpose: queuing a job changes nothing an advertiser can see and spends\n * nothing, so gating it would make long-range analytics unreachable in exactly\n * the configuration most people should be running.\n */\nexport const registerAdsTools = (\n server: McpServer,\n client: AdsApiClient,\n ctx: ToolContext,\n): void => {\n const ads = ctx.ads;\n if (!ads) return;\n\n // One resolver shared by every tool, so the \"which account?\" lookup happens\n // at most once per process rather than once per call.\n const resolveAccount = createAccountResolver(client, ads);\n\n registerAdsAccountTools(server, client, ads, resolveAccount);\n registerAdsCampaignTools(server, client, ads, resolveAccount);\n registerAdsTargetingTools(server, client, ads, resolveAccount);\n registerAdsAudienceTools(server, client, resolveAccount);\n registerAdsAnalyticsTools(server, client, resolveAccount);\n};\n","import type { McpServer } from \"@modelcontextprotocol/server\";\nimport { z } from \"zod\";\n\nimport { fileMode } from \"#/client/tokens\";\nimport type { ToolContext } from \"#/tools/index\";\nimport { wrap } from \"#/tools/util\";\n\nexport const registerAuthTools = (server: McpServer, ctx: ToolContext): void => {\n server.registerTool(\n \"x_auth_status\",\n {\n title: \"X: Auth Status\",\n description:\n \"Which credentials this server is holding: an app-only Bearer token (enough for public \" +\n \"reads and search), an OAuth2 user session (needed for bookmarks and the home timeline), \" +\n \"or neither. Shows the logged-in handle, granted scopes and token expiry, and whether \" +\n \"the Ads API tools are registered and against which environment. Call this first if the \" +\n \"X API tools seem to be missing — it explains exactly what to configure.\",\n inputSchema: z.object({}),\n annotations: { readOnlyHint: true },\n },\n async () =>\n wrap(async () => {\n const status = ctx.tokenProvider.describe();\n const mode = ctx.tokenFile ? fileMode(ctx.tokenFile) : undefined;\n\n // The state a first-time user lands in. Answer it as a setup guide\n // rather than a status dump, since the server can no longer signal this\n // by refusing to start.\n if (!ctx.hasCredentials) {\n return {\n configured: false,\n app_only_bearer: false,\n user: { authenticated: false, reason: \"no credentials configured\" },\n can_read_public: false,\n can_read_bookmarks: false,\n available_without_credentials: [\n \"x_compose_post\",\n \"x_validate_post\",\n \"x_build_search_query\",\n \"x_auth_status\",\n ],\n setup: ctx.setup ?? [],\n };\n }\n\n return {\n configured: true,\n app_only_bearer: status.app,\n user: status.user.authenticated\n ? {\n ...status.user,\n expires_at: new Date(status.user.expiresAt).toISOString(),\n expires_in_seconds: Math.max(\n 0,\n Math.round((status.user.expiresAt - Date.now()) / 1000),\n ),\n }\n : status.user,\n ...(ctx.tokenFile\n ? {\n token_file: {\n path: ctx.tokenFile,\n mode: mode === undefined ? \"absent\" : `0${mode.toString(8)}`,\n ...(mode !== undefined && (mode & 0o077) !== 0\n ? { warning: `Readable by other users. Run: chmod 600 ${ctx.tokenFile}` }\n : {}),\n },\n }\n : {}),\n can_read_public: status.app || status.user.authenticated,\n can_read_bookmarks: status.user.authenticated,\n ads: ctx.ads\n ? {\n enabled: true,\n environment: ctx.ads.sandbox ? \"sandbox\" : \"production\",\n base_url: ctx.ads.baseUrl,\n writes_enabled: ctx.ads.allowWrites,\n default_account_id: ctx.ads.accountId ?? null,\n note: ctx.ads.sandbox\n ? \"Sandbox — campaigns here spend nothing.\"\n : \"PRODUCTION — these tools read and can change campaigns that spend real money.\",\n }\n : {\n enabled: false,\n reason: ctx.adsSetup\n ? \"X_ADS_ENABLED is not set.\"\n : \"no OAuth2 client id configured\",\n ...(ctx.adsSetup ? { setup: ctx.adsSetup } : {}),\n },\n };\n }),\n );\n\n if (!ctx.login) return;\n\n const login = ctx.login;\n\n server.registerTool(\n \"x_auth_login\",\n {\n title: \"X: Auth Login\",\n description:\n \"Start the OAuth2 login. Prints a URL (and opens your browser) for you to authorize the \" +\n \"app, waits up to two minutes for the callback, then stores a refresh token in the token \" +\n \"file with mode 600. Only needed for bookmarks, the home timeline and API writes — \" +\n \"public reads and search work with the Bearer token alone.\",\n inputSchema: z.object({\n open: z.boolean().default(true).describe(\"Open the authorize URL in your browser.\"),\n }),\n annotations: { readOnlyHint: false, destructiveHint: false },\n },\n async ({ open }) =>\n wrap(async () => {\n const result = await login(open);\n return {\n authenticated: true,\n username: result.username,\n userId: result.userId,\n scopes: result.scopes,\n token_file: result.tokenFile,\n note: \"The refresh token is stored with mode 600 and rotates on every refresh.\",\n };\n }),\n );\n\n server.registerTool(\n \"x_auth_logout\",\n {\n title: \"X: Auth Logout\",\n description:\n \"Delete the stored OAuth2 tokens. The app-only Bearer token is unaffected, so public \" +\n \"reads and search keep working.\",\n inputSchema: z.object({\n confirm: z\n .literal(true)\n .describe(\"Must be true. You will need to run the login flow again to undo this.\"),\n }),\n annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true },\n },\n async () =>\n wrap(async () => {\n ctx.logout?.();\n return {\n logged_out: true,\n note: \"Public reads and search continue to work if a Bearer token is configured.\",\n };\n }),\n );\n};\n","import type { McpServer } from \"@modelcontextprotocol/server\";\nimport { z } from \"zod\";\n\nimport { isRecord } from \"#/client/shape\";\nimport type { XApiClient } from \"#/client/x\";\nimport { validateIntent } from \"#/compose/intent\";\nimport { openInBrowser } from \"#/compose/open\";\nimport type { ToolContext } from \"#/tools/index\";\nimport { compact, confirmArg, postIdArg, wrap } from \"#/tools/util\";\n\nconst textArg = z\n .string()\n .min(1)\n .describe(\"The body of the post. Counted against X's 280 weighted-character limit.\");\n\nconst urlArg = z\n .string()\n .url()\n .optional()\n .describe(\n \"A link to append. X shortens every link to a fixed 23 characters, so its real length does \" +\n \"not matter — but those 23 do count.\",\n );\n\nconst hashtagsArg = z\n .array(z.string())\n .optional()\n .describe('Hashtags, with or without \"#\". More than four tends to suppress reach.');\n\nconst viaArg = z.string().optional().describe('An attribution handle, appended as \"via @handle\".');\n\nexport const registerComposeTools = (\n server: McpServer,\n client: XApiClient,\n ctx: ToolContext,\n): void => {\n server.registerTool(\n \"x_validate_post\",\n {\n title: \"X: Validate Post\",\n description:\n \"Check a draft against X's 280-character limit before doing anything with it. X counts \" +\n \"weighted characters, not plain ones: every URL costs 23 whatever its length, and CJK \" +\n \"characters and emoji cost 2 each — so 140 Japanese characters is already a full post. \" +\n \"Runs locally; no API call, no cost.\",\n inputSchema: z.object({\n text: textArg,\n url: urlArg,\n hashtags: hashtagsArg,\n via: viaArg,\n }),\n annotations: { readOnlyHint: true },\n },\n async ({ text, url, hashtags, via }) =>\n wrap(async () => {\n const { intent_url: _intentUrl, ...validation } = validateIntent({\n text,\n url,\n hashtags,\n via,\n });\n return validation;\n }),\n );\n\n server.registerTool(\n \"x_compose_post\",\n {\n title: \"X: Compose Post\",\n description:\n \"The default way to post. Validates the draft and returns an x.com/intent/tweet URL \" +\n \"that opens X's composer pre-filled — you click Post yourself. This is FREE: no API \" +\n \"quota, no write scope, no credentials, and nothing is published without a human click. \" +\n \"Prefer it over x_create_post, which costs $0.015 per post ($0.200 with a URL). \" +\n \"Web intents cannot attach media, create polls, make native quote posts, or build \" +\n \"threads — those need the paid API. Replying to a post does work.\",\n inputSchema: z.object({\n text: textArg,\n url: urlArg,\n hashtags: hashtagsArg,\n via: viaArg,\n inReplyTo: postIdArg\n .optional()\n .describe(\"Post id to reply to. The composer opens in reply context.\"),\n open: z\n .boolean()\n .optional()\n .describe(\n \"Open the URL in your browser. Defaults to the server's X_API_AUTO_OPEN_BROWSER \" +\n \"setting. The URL is returned either way.\",\n ),\n }),\n // Not readOnly (it may open a browser), but deliberately NOT gated behind\n // allowWrites: it changes nothing without a human click, and gating the\n // free path would push people toward the paid one.\n annotations: { readOnlyHint: false, destructiveHint: false },\n },\n async ({ text, url, hashtags, via, inReplyTo, open }) =>\n wrap(async () => {\n const validation = validateIntent({ text, url, hashtags, via, inReplyTo });\n if (!validation.valid) {\n return {\n ...validation,\n cost: { estimated_usd: 0, note: \"Nothing was sent — the draft is not postable.\" },\n };\n }\n\n const shouldOpen = open ?? ctx.autoOpenBrowser;\n const result = shouldOpen\n ? await openInBrowser(validation.intent_url)\n : { opened: false, reason: \"not requested\" };\n\n return {\n intent_url: validation.intent_url,\n opened: result.opened,\n ...(result.reason ? { open_note: result.reason } : {}),\n composed: validation.composed,\n weighted_length: validation.weighted,\n remaining: validation.remaining,\n valid: true,\n warnings: validation.warnings,\n next_step: result.opened\n ? \"X's composer is open in your browser — review it and click Post.\"\n : \"Open the intent_url above to review and post it.\",\n cost: {\n estimated_usd: 0,\n note: \"Web intent — no API call, no quota consumed, no credentials used.\",\n },\n };\n }),\n );\n\n // Everything below publishes through the paid API. Registered only when both\n // flags are on, so with the defaults these tools do not exist at all.\n if (!ctx.allowWrites || ctx.writeBackend !== \"api\") return;\n\n server.registerTool(\n \"x_create_post\",\n {\n title: \"X: Create Post\",\n description:\n \"Publish a post directly through the API. COSTS MONEY: about $0.015 per post, or $0.200 \" +\n \"if it contains a URL — forty times a post read. x_compose_post does the same thing for \" +\n \"free via a browser click; use this only when you specifically need unattended posting, \" +\n \"a thread, or a native quote post.\",\n inputSchema: z.object({\n text: textArg,\n replyToPostId: postIdArg.optional().describe(\"Post id to reply to.\"),\n quotePostId: postIdArg.optional().describe(\"Post id to quote.\"),\n replySettings: z\n .enum([\"everyone\", \"mentionedUsers\", \"following\"])\n .optional()\n .describe(\"Who may reply. Defaults to everyone.\"),\n confirm: confirmArg,\n }),\n annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false },\n },\n async ({ text, replyToPostId, quotePostId, replySettings }) =>\n wrap(async () => {\n const validation = validateIntent({ text });\n if (!validation.valid) {\n return { error: validation.error, weighted_length: validation.weighted };\n }\n\n const res = await client.post(\n \"/2/tweets\",\n compact({\n text,\n ...(replyToPostId ? { reply: { in_reply_to_tweet_id: replyToPostId } } : {}),\n ...(quotePostId ? { quote_tweet_id: quotePostId } : {}),\n ...(replySettings ? { reply_settings: replySettings } : {}),\n }),\n );\n\n const hasUrl = validation.weighted !== undefined && /https?:\\/\\/|\\w+\\.\\w{2,}/.test(text);\n ctx.ledger.recordCreate(hasUrl);\n\n const data = isRecord(res) && isRecord(res.data) ? res.data : {};\n const id = typeof data.id === \"string\" ? data.id : undefined;\n return {\n posted: true,\n id,\n ...(id ? { url: `https://x.com/i/web/status/${id}` } : {}),\n cost: {\n estimated_usd: hasUrl ? ctx.pricing.postCreateWithUrl : ctx.pricing.postCreate,\n note: hasUrl\n ? \"Billed at the with-URL rate. x_compose_post would have cost nothing.\"\n : \"x_compose_post would have cost nothing.\",\n },\n };\n }),\n );\n\n server.registerTool(\n \"x_delete_post\",\n {\n title: \"X: Delete Post\",\n description:\n \"Delete one of your own posts. Irreversible — X keeps no undo, and the id cannot be \" +\n \"reused.\",\n inputSchema: z.object({ postId: postIdArg, confirm: confirmArg }),\n annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true },\n },\n async ({ postId }) =>\n wrap(async () => {\n await client.del(`/2/tweets/${postId}`);\n return { deleted: postId };\n }),\n );\n};\n","import type { McpServer } from \"@modelcontextprotocol/server\";\nimport { z } from \"zod\";\n\nimport { isRecord, shapePostsResponse, type ShapedPost } from \"#/client/shape\";\nimport type { XApiClient } from \"#/client/x\";\nimport type { ToolContext } from \"#/tools/index\";\nimport {\n cachedByIds,\n compact,\n maxResultsArg,\n paginationTokenArg,\n POST_QUERY,\n postIdArg,\n recordResultCost,\n wrap,\n} from \"#/tools/util\";\n\nexport const registerPostTools = (\n server: McpServer,\n client: XApiClient,\n ctx: ToolContext,\n): void => {\n /** One batched lookup, shared by the single- and multi-id tools. */\n const fetchPosts = async (ids: string[]): Promise<Map<string, ShapedPost>> => {\n const raw = await client.get(\"/2/tweets\", compact({ ids, ...POST_QUERY }));\n const shaped = shapePostsResponse(raw);\n return new Map(shaped.posts.map((post) => [post.id, post]));\n };\n\n server.registerTool(\n \"x_get_post\",\n {\n title: \"X: Get Post\",\n description:\n \"Get one post by id, with its author, metrics, media and any quoted or replied-to post \" +\n \"already inlined. Reading the same post twice in one UTC day is free — X does not bill \" +\n \"a repeat read.\",\n inputSchema: z.object({ postId: postIdArg }),\n annotations: { readOnlyHint: true },\n },\n async ({ postId }) =>\n wrap(async () => {\n const { items, cost, notFound } = await cachedByIds(\n ctx,\n \"post\",\n [postId],\n fetchPosts,\n \"x_get_post\",\n );\n if (items.length === 0) {\n return {\n error:\n `X returned no post for id ${notFound[0]}. It is deleted, protected, or from a ` +\n `suspended account.`,\n cost,\n };\n }\n return { post: items[0], cost };\n }),\n );\n\n server.registerTool(\n \"x_get_posts\",\n {\n title: \"X: Get Posts\",\n description:\n \"Get up to 100 posts by id in a single request. Always prefer this over calling \" +\n \"x_get_post repeatedly — X bills per post either way, but one request is far faster and \" +\n \"spends only one unit of rate limit. Ids that cannot be served come back under \" +\n \"`not_found` rather than failing the call.\",\n inputSchema: z.object({\n postIds: z\n .array(postIdArg)\n .min(1)\n .max(100)\n .describe(\"Post ids to look up, up to 100 in one call.\"),\n }),\n annotations: { readOnlyHint: true },\n },\n async ({ postIds }) =>\n wrap(async () => {\n // De-duplicate up front: asking for the same id twice in one call would\n // otherwise look like two reads to the caller reading the cost note.\n const unique = [...new Set(postIds)];\n const { items, cost, notFound } = await cachedByIds(\n ctx,\n \"post\",\n unique,\n fetchPosts,\n \"x_get_posts\",\n );\n return {\n posts: items,\n ...(notFound.length > 0 ? { not_found: notFound } : {}),\n cost,\n };\n }),\n );\n\n server.registerTool(\n \"x_get_thread\",\n {\n title: \"X: Get Thread\",\n description:\n \"Reconstruct a conversation: every reply sharing the post's conversation_id, oldest \" +\n \"first. Note that this searches the last 7 days only, so an older thread returns just \" +\n \"the root post. Costs one post read per reply returned.\",\n inputSchema: z.object({\n postId: postIdArg,\n maxResults: maxResultsArg,\n }),\n annotations: { readOnlyHint: true },\n },\n async ({ postId, maxResults }) =>\n wrap(async () => {\n // The root post carries the conversation_id, which may differ from its\n // own id when the post is itself a reply.\n const rootRaw = await client.get(`/2/tweets/${postId}`, compact({ ...POST_QUERY }));\n const rootShaped = shapePostsResponse({\n ...(isRecord(rootRaw) ? rootRaw : {}),\n data: isRecord(rootRaw) && isRecord(rootRaw.data) ? [rootRaw.data] : [],\n });\n const root = rootShaped.posts[0];\n if (!root) {\n return {\n error: `X returned no post for id ${postId}.`,\n cost: recordResultCost(ctx, \"post\", []),\n };\n }\n\n const conversationId = root.conversation_id ?? root.id;\n const replies = await client.paginate(\n \"/2/tweets/search/recent\",\n compact({\n query: `conversation_id:${conversationId}`,\n max_results: Math.min(Math.max(maxResults, 10), 100),\n sort_order: \"recency\",\n ...POST_QUERY,\n }),\n { maxItems: maxResults },\n );\n\n const shapedReplies = shapePostsResponse({\n data: replies.data,\n includes: replies.includes[0] ?? {},\n });\n // Oldest first: a thread reads top-down, but search returns newest first.\n const ordered = shapedReplies.posts.toReversed();\n const ids = [root.id, ...ordered.map((p) => p.id)];\n\n return {\n conversation_id: conversationId,\n posts: [root, ...ordered.filter((p) => p.id !== root.id)],\n ...(replies.nextToken ? { next_token: replies.nextToken } : {}),\n note:\n \"Recent search reaches back 7 days. Replies older than that are not returned even \" +\n \"if the thread has more.\",\n cost: recordResultCost(ctx, \"post\", ids),\n };\n }),\n );\n\n server.registerTool(\n \"x_get_quotes\",\n {\n title: \"X: Get Quotes\",\n description: \"List posts quoting a given post, newest first.\",\n inputSchema: z.object({\n postId: postIdArg,\n maxResults: maxResultsArg,\n paginationToken: paginationTokenArg,\n }),\n annotations: { readOnlyHint: true },\n },\n async ({ postId, maxResults, paginationToken }) =>\n wrap(async () => {\n const res = await client.paginate(\n `/2/tweets/${postId}/quote_tweets`,\n compact({\n max_results: Math.min(Math.max(maxResults, 10), 100),\n pagination_token: paginationToken,\n ...POST_QUERY,\n }),\n { maxItems: maxResults },\n );\n const shaped = shapePostsResponse({ data: res.data, includes: res.includes[0] ?? {} });\n return {\n posts: shaped.posts,\n result_count: shaped.posts.length,\n ...(res.nextToken ? { next_token: res.nextToken } : {}),\n cost: recordResultCost(\n ctx,\n \"post\",\n shaped.posts.map((p) => p.id),\n ),\n };\n }),\n );\n};\n","import type { McpServer } from \"@modelcontextprotocol/server\";\nimport { z } from \"zod\";\n\nimport { isRecord, shapePostsResponse } from \"#/client/shape\";\nimport type { XApiClient } from \"#/client/x\";\nimport type { ToolContext } from \"#/tools/index\";\nimport {\n assertWithinBudget,\n compact,\n maxResultsArg,\n paginationTokenArg,\n POST_QUERY,\n recordResultCost,\n stripAt,\n wrap,\n} from \"#/tools/util\";\n\nconst queryArg = z\n .string()\n .min(1)\n .max(1024)\n .describe(\n 'An X search query, e.g. \"rust -is:retweet lang:en\". Build one with x_build_search_query if ' +\n \"you are unsure of the operators.\",\n );\n\nconst timeArgs = {\n startTime: z\n .string()\n .optional()\n .describe('Only posts at or after this ISO-8601 UTC time, e.g. \"2026-07-01T00:00:00Z\".'),\n endTime: z.string().optional().describe(\"Only posts before this ISO-8601 UTC time.\"),\n};\n\n/**\n * Full-archive search is capped at one request per second on top of its 15-minute\n * window. Enforced with a real gate rather than hoped for: a paginated call\n * issues several requests back to back and would trip the limit on its own.\n */\nconst createRateGate = (minIntervalMs: number) => {\n let last = 0;\n return async (): Promise<void> => {\n const wait = last + minIntervalMs - Date.now();\n if (wait > 0) await new Promise((resolve) => setTimeout(resolve, wait));\n last = Date.now();\n };\n};\n\n/**\n * Registered separately from the rest of search because it runs entirely\n * locally — no API call, no credentials, no cost. It stays available on an\n * unconfigured server, where getting a query right for free is the most useful\n * thing left to do.\n */\nexport const registerQueryBuilderTool = (server: McpServer): void => {\n server.registerTool(\n \"x_build_search_query\",\n {\n title: \"X: Build Search Query\",\n description:\n \"Build an X search query from structured parts and explain each operator it used. Runs \" +\n \"entirely locally: no API call, no cost, no credentials. Use this to get the query right \" +\n \"for free, then pass the result to x_count_recent and only then to x_search_recent.\",\n inputSchema: z.object({\n allWords: z.string().optional().describe('Words that must all appear, e.g. \"rust async\".'),\n exactPhrase: z.string().optional().describe(\"A phrase that must appear verbatim.\"),\n anyWords: z.array(z.string()).optional().describe(\"At least one of these must appear.\"),\n noneWords: z.array(z.string()).optional().describe(\"None of these may appear.\"),\n hashtags: z.array(z.string()).optional().describe('Hashtags, with or without \"#\".'),\n from: z.array(usernameLike()).optional().describe(\"Only posts by these handles.\"),\n to: z.array(usernameLike()).optional().describe(\"Only replies to these handles.\"),\n mentioning: z.array(usernameLike()).optional().describe(\"Only posts mentioning these.\"),\n lang: z.string().optional().describe('BCP-47 language code, e.g. \"en\", \"fr\", \"ja\".'),\n hasMedia: z.boolean().optional().describe(\"Only posts with a photo or video.\"),\n hasLinks: z.boolean().optional().describe(\"Only posts containing a link.\"),\n isReply: z.boolean().optional().describe(\"true to require replies, false to exclude them.\"),\n isRetweet: z\n .boolean()\n .optional()\n .describe(\"true to require reposts, false to exclude them. False is the usual choice.\"),\n isQuote: z.boolean().optional().describe(\"true to require quote posts, false to exclude.\"),\n }),\n annotations: { readOnlyHint: true },\n },\n async (args) => wrap(async () => buildSearchQuery(args)),\n );\n};\n\nexport const registerSearchTools = (\n server: McpServer,\n client: XApiClient,\n ctx: ToolContext,\n): void => {\n const runSearch = async (\n path: string,\n args: {\n query: string;\n maxResults: number;\n sortOrder?: string | undefined;\n startTime?: string | undefined;\n endTime?: string | undefined;\n sinceId?: string | undefined;\n untilId?: string | undefined;\n paginationToken?: string | undefined;\n },\n label: string,\n gate?: () => Promise<void>,\n ) => {\n // Estimated by count, not by id: a search cannot know what it will return\n // until it returns it, so the guard uses the worst case it asked for.\n assertWithinBudget(ctx, label, ctx.ledger.estimateCount(\"post\", args.maxResults));\n if (gate) await gate();\n\n const res = await client.paginate(\n path,\n compact({\n query: args.query,\n // X requires max_results between 10 and 100 on search, so a request for\n // 3 becomes a request for 10 that we then truncate. Billing follows what\n // came back, so this is honest about what it costs.\n max_results: Math.min(Math.max(args.maxResults, 10), 100),\n sort_order: args.sortOrder,\n start_time: args.startTime,\n end_time: args.endTime,\n since_id: args.sinceId,\n until_id: args.untilId,\n pagination_token: args.paginationToken,\n ...POST_QUERY,\n }),\n { maxItems: args.maxResults, maxPages: 5 },\n );\n\n const shaped = shapePostsResponse({ data: res.data, includes: res.includes[0] ?? {} });\n return {\n query: args.query,\n posts: shaped.posts,\n result_count: shaped.posts.length,\n ...(res.nextToken ? { next_token: res.nextToken } : {}),\n cost: recordResultCost(\n ctx,\n \"post\",\n shaped.posts.map((p) => p.id),\n ),\n };\n };\n\n server.registerTool(\n \"x_search_recent\",\n {\n title: \"X: Search Recent\",\n description:\n \"Search posts from the last 7 days. Supports X's full query syntax: `from:handle`, \" +\n '`to:handle`, `#tag`, `\"exact phrase\"`, `lang:en`, `has:media`, `has:links`, ' +\n \"`url:example.com`, `conversation_id:`, and negation with `-is:retweet` or `-is:reply`. \" +\n \"Run x_count_recent first to see how big a query is before paying to read it.\",\n inputSchema: z.object({\n query: queryArg,\n maxResults: maxResultsArg,\n sortOrder: z\n .enum([\"recency\", \"relevancy\"])\n .optional()\n .describe(\"`recency` (newest first, the default) or `relevancy`.\"),\n ...timeArgs,\n sinceId: z.string().regex(/^\\d+$/).optional().describe(\"Only posts newer than this id.\"),\n untilId: z.string().regex(/^\\d+$/).optional().describe(\"Only posts older than this id.\"),\n paginationToken: paginationTokenArg,\n }),\n annotations: { readOnlyHint: true },\n },\n async (args) => wrap(() => runSearch(\"/2/tweets/search/recent\", args, \"x_search_recent\")),\n );\n\n server.registerTool(\n \"x_count_recent\",\n {\n title: \"X: Count Recent\",\n description:\n \"Count how many posts match a query over the last 7 days WITHOUT reading any of them. \" +\n \"This endpoint returns only totals, so it costs nothing per post — always run it before \" +\n \"a broad x_search_recent to find out whether you are about to read 10 posts or 10,000.\",\n inputSchema: z.object({\n query: queryArg,\n granularity: z\n .enum([\"minute\", \"hour\", \"day\"])\n .default(\"day\")\n .describe(\"Bucket size for the time series. Defaults to day.\"),\n ...timeArgs,\n }),\n annotations: { readOnlyHint: true },\n },\n async ({ query, granularity, startTime, endTime }) =>\n wrap(async () => {\n const raw = await client.get(\n \"/2/tweets/counts/recent\",\n compact({ query, granularity, start_time: startTime, end_time: endTime }),\n );\n const meta = isRecord(raw) && isRecord(raw.meta) ? raw.meta : {};\n const total = typeof meta.total_tweet_count === \"number\" ? meta.total_tweet_count : 0;\n const estimated = ctx.ledger.estimateCount(\"post\", total);\n return {\n query,\n total_posts: total,\n buckets: isRecord(raw) && Array.isArray(raw.data) ? raw.data : [],\n cost: { estimated_usd: 0, note: \"Counts are not billed per post.\" },\n reading_all_would_cost_usd: Math.round(estimated * 100) / 100,\n ...(total > 100\n ? {\n advice:\n `Reading all ${total} matches would cost about ` +\n `$${(Math.round(estimated * 100) / 100).toFixed(2)}. Narrow the query with ` +\n `-is:retweet, lang:, or a tighter time window before searching.`,\n }\n : {}),\n };\n }),\n );\n\n // Full-archive search is a paid-tier endpoint; registering it when it cannot\n // work would just hand the model a tool that always 403s.\n if (!ctx.enableFullArchive) return;\n\n const archiveGate = createRateGate(1000);\n\n server.registerTool(\n \"x_search_all\",\n {\n title: \"X: Search All\",\n description:\n \"Search the FULL archive, back to X's first post in March 2006 — not just the last 7 \" +\n \"days. Requires a paid access tier and is limited to one request per second, so it is \" +\n \"slower than x_search_recent. Same query syntax. Costs the same per post read.\",\n inputSchema: z.object({\n query: queryArg,\n maxResults: maxResultsArg,\n sortOrder: z.enum([\"recency\", \"relevancy\"]).optional(),\n ...timeArgs,\n sinceId: z.string().regex(/^\\d+$/).optional(),\n untilId: z.string().regex(/^\\d+$/).optional(),\n paginationToken: paginationTokenArg,\n }),\n annotations: { readOnlyHint: true },\n },\n async (args) =>\n wrap(() => runSearch(\"/2/tweets/search/all\", args, \"x_search_all\", archiveGate)),\n );\n};\n\nfunction usernameLike() {\n return z.string().regex(/^@?[A-Za-z0-9_]{1,15}$/);\n}\n\ntype QueryParts = {\n allWords?: string | undefined;\n exactPhrase?: string | undefined;\n anyWords?: string[] | undefined;\n noneWords?: string[] | undefined;\n hashtags?: string[] | undefined;\n from?: string[] | undefined;\n to?: string[] | undefined;\n mentioning?: string[] | undefined;\n lang?: string | undefined;\n hasMedia?: boolean | undefined;\n hasLinks?: boolean | undefined;\n isReply?: boolean | undefined;\n isRetweet?: boolean | undefined;\n isQuote?: boolean | undefined;\n};\n\n/** Grouped with OR and parenthesised, which is what X's `from:a OR from:b` needs. */\nconst orGroup = (operator: string, values: string[]): string =>\n values.length === 1\n ? `${operator}:${values[0]}`\n : `(${values.map((v) => `${operator}:${v}`).join(\" OR \")})`;\n\nexport const buildSearchQuery = (\n parts: QueryParts,\n): { query: string; explanation: string[]; length: number; valid: boolean; warning?: string } => {\n const clauses: string[] = [];\n const explanation: string[] = [];\n\n if (parts.allWords?.trim()) {\n clauses.push(parts.allWords.trim());\n explanation.push(`\\`${parts.allWords.trim()}\\` — all of these words must appear.`);\n }\n if (parts.exactPhrase?.trim()) {\n clauses.push(`\"${parts.exactPhrase.trim()}\"`);\n explanation.push(`\\`\"${parts.exactPhrase.trim()}\"\\` — this exact phrase must appear.`);\n }\n if (parts.anyWords?.length) {\n clauses.push(`(${parts.anyWords.join(\" OR \")})`);\n explanation.push(`\\`(${parts.anyWords.join(\" OR \")})\\` — at least one of these must appear.`);\n }\n for (const word of parts.noneWords ?? []) {\n clauses.push(`-${word}`);\n explanation.push(`\\`-${word}\\` — excludes posts containing \"${word}\".`);\n }\n for (const tag of parts.hashtags ?? []) {\n const clean = tag.startsWith(\"#\") ? tag : `#${tag}`;\n clauses.push(clean);\n explanation.push(`\\`${clean}\\` — must carry this hashtag.`);\n }\n if (parts.from?.length) {\n const handles = parts.from.map(stripAt);\n clauses.push(orGroup(\"from\", handles));\n explanation.push(\n `\\`from:\\` — only posts authored by ${handles.map((h) => `@${h}`).join(\" or \")}.`,\n );\n }\n if (parts.to?.length) {\n const handles = parts.to.map(stripAt);\n clauses.push(orGroup(\"to\", handles));\n explanation.push(\n `\\`to:\\` — only replies addressed to ${handles.map((h) => `@${h}`).join(\" or \")}.`,\n );\n }\n for (const handle of (parts.mentioning ?? []).map(stripAt)) {\n clauses.push(`@${handle}`);\n explanation.push(`\\`@${handle}\\` — must mention this account.`);\n }\n if (parts.lang) {\n clauses.push(`lang:${parts.lang}`);\n explanation.push(`\\`lang:${parts.lang}\\` — only posts X detected as this language.`);\n }\n if (parts.hasMedia !== undefined) {\n clauses.push(`${parts.hasMedia ? \"\" : \"-\"}has:media`);\n explanation.push(\n `\\`${parts.hasMedia ? \"\" : \"-\"}has:media\\` — ${parts.hasMedia ? \"requires\" : \"excludes\"} posts with a photo or video.`,\n );\n }\n if (parts.hasLinks !== undefined) {\n clauses.push(`${parts.hasLinks ? \"\" : \"-\"}has:links`);\n explanation.push(\n `\\`${parts.hasLinks ? \"\" : \"-\"}has:links\\` — ${parts.hasLinks ? \"requires\" : \"excludes\"} posts containing a link.`,\n );\n }\n for (const [flag, name] of [\n [parts.isReply, \"reply\"],\n [parts.isRetweet, \"retweet\"],\n [parts.isQuote, \"quote\"],\n ] as const) {\n if (flag === undefined) continue;\n clauses.push(`${flag ? \"\" : \"-\"}is:${name}`);\n explanation.push(\n `\\`${flag ? \"\" : \"-\"}is:${name}\\` — ${flag ? \"only\" : \"never\"} ${name} posts.` +\n (name === \"retweet\" && !flag\n ? \" This is the single most useful filter: it removes the duplicate noise of reposts.\"\n : \"\"),\n );\n }\n\n const query = clauses.join(\" \");\n return {\n query,\n explanation,\n length: query.length,\n valid: query.length > 0 && query.length <= 1024,\n ...(query.length === 0 ? { warning: \"No criteria given — the query is empty.\" } : {}),\n ...(query.length > 1024\n ? { warning: `Query is ${query.length} characters; X's limit is 1024.` }\n : {}),\n };\n};\n","import type { McpServer } from \"@modelcontextprotocol/server\";\nimport { z } from \"zod\";\n\nimport { UserContextRequiredError } from \"#/client/errors\";\nimport { isRecord, shapePostsResponse } from \"#/client/shape\";\nimport type { XApiClient } from \"#/client/x\";\nimport type { ToolContext } from \"#/tools/index\";\nimport {\n compact,\n maxResultsArg,\n paginationTokenArg,\n POST_QUERY,\n recordResultCost,\n wrap,\n} from \"#/tools/util\";\n\n/**\n * Both endpoints here are self-only: X permits reading *your* home timeline and\n * *your* bookmarks and nobody else's. So the user id comes from the stored\n * token rather than from an argument — accepting one would imply a capability\n * that does not exist.\n */\nconst resolveOwnUserId = async (client: XApiClient, ctx: ToolContext): Promise<string> => {\n const status = ctx.tokenProvider.describe().user;\n if (!status.authenticated) {\n throw new UserContextRequiredError(\"This tool\", status.reason);\n }\n if (status.userId) return status.userId;\n\n // Login records the id from `/2/users/me`, but that endpoint is documented as\n // unreliable and the login flow tolerates it failing. Telling the user to log\n // in again would be a dead end — the retry hits the same endpoint — so ask\n // for the id now instead, and cache it so this happens at most once.\n const raw = await client.get(\"/2/users/me\", {}, \"user\");\n const id = isRecord(raw) && isRecord(raw.data) ? raw.data.id : undefined;\n const username = isRecord(raw) && isRecord(raw.data) ? raw.data.username : undefined;\n if (typeof id !== \"string\" || !id) {\n throw new UserContextRequiredError(\n \"This tool\",\n \"X did not return your account id from /2/users/me, so there is no way to identify whose \" +\n \"timeline to read. Re-run `x-api-mcp login`, and check the app is enrolled in the \" +\n \"Pay-per-use package and Production environment at console.x.com\",\n );\n }\n\n ctx.ledger.record(\"owned\", [id]);\n\n const stored = ctx.tokenStore?.read();\n if (stored) {\n ctx.tokenStore?.write({\n ...stored,\n userId: id,\n ...(typeof username === \"string\" && username ? { username } : {}),\n });\n }\n return id;\n};\n\nexport const registerTimelineTools = (\n server: McpServer,\n client: XApiClient,\n ctx: ToolContext,\n): void => {\n server.registerTool(\n \"x_get_home_timeline\",\n {\n title: \"X: Get Home Timeline\",\n description:\n \"Your own reverse-chronological home timeline — the posts from accounts you follow. \" +\n \"Requires `x-api-mcp login`; X only ever serves this for the authenticated account, so \" +\n \"there is no way to read someone else's. Billed at the cheaper owned-read rate.\",\n inputSchema: z.object({\n maxResults: maxResultsArg,\n excludeReplies: z.boolean().default(false).describe(\"Leave out replies.\"),\n excludeReposts: z.boolean().default(false).describe(\"Leave out reposts (retweets).\"),\n sinceId: z\n .string()\n .regex(/^\\d+$/)\n .optional()\n .describe(\"Only posts newer than this id — useful for polling.\"),\n paginationToken: paginationTokenArg,\n }),\n annotations: { readOnlyHint: true },\n },\n async ({ maxResults, excludeReplies, excludeReposts, sinceId, paginationToken }) =>\n wrap(async () => {\n const userId = await resolveOwnUserId(client, ctx);\n const exclude = [\n ...(excludeReplies ? [\"replies\"] : []),\n ...(excludeReposts ? [\"retweets\"] : []),\n ];\n const res = await client.paginate(\n `/2/users/${userId}/timelines/reverse_chronological`,\n compact({\n max_results: Math.min(Math.max(maxResults, 5), 100),\n exclude: exclude.length > 0 ? exclude : undefined,\n since_id: sinceId,\n pagination_token: paginationToken,\n ...POST_QUERY,\n }),\n { maxItems: maxResults, auth: \"user\" },\n );\n const shaped = shapePostsResponse({ data: res.data, includes: res.includes[0] ?? {} });\n return {\n posts: shaped.posts,\n result_count: shaped.posts.length,\n ...(res.nextToken ? { next_token: res.nextToken } : {}),\n cost: recordResultCost(\n ctx,\n \"owned\",\n shaped.posts.map((p) => p.id),\n ),\n };\n }),\n );\n\n server.registerTool(\n \"x_get_bookmarks\",\n {\n title: \"X: Get Bookmarks\",\n description:\n \"Your saved bookmarks, newest first. Requires `x-api-mcp login` with the \" +\n \"`bookmark.read` scope — an app-only Bearer token cannot reach bookmarks at all.\",\n inputSchema: z.object({\n maxResults: maxResultsArg,\n paginationToken: paginationTokenArg,\n }),\n annotations: { readOnlyHint: true },\n },\n async ({ maxResults, paginationToken }) =>\n wrap(async () => {\n const userId = await resolveOwnUserId(client, ctx);\n const res = await client.paginate(\n `/2/users/${userId}/bookmarks`,\n compact({\n max_results: Math.min(Math.max(maxResults, 1), 100),\n pagination_token: paginationToken,\n ...POST_QUERY,\n }),\n { maxItems: maxResults, auth: \"user\" },\n );\n const shaped = shapePostsResponse({ data: res.data, includes: res.includes[0] ?? {} });\n return {\n posts: shaped.posts,\n result_count: shaped.posts.length,\n ...(res.nextToken ? { next_token: res.nextToken } : {}),\n cost: recordResultCost(\n ctx,\n \"owned\",\n shaped.posts.map((p) => p.id),\n ),\n };\n }),\n );\n};\n","import type { McpServer } from \"@modelcontextprotocol/server\";\nimport { z } from \"zod\";\n\nimport type { XApiClient } from \"#/client/x\";\nimport type { ToolContext } from \"#/tools/index\";\nimport { wrap } from \"#/tools/util\";\n\nexport const registerUsageTools = (\n server: McpServer,\n client: XApiClient,\n ctx: ToolContext,\n): void => {\n server.registerTool(\n \"x_usage_report\",\n {\n title: \"X: Usage Report\",\n description:\n \"What this session has spent against X's pay-per-use rates, how much the dedup cache \" +\n \"saved, and the pricing table used to compute it. Estimates only, counted since this \" +\n \"process started — the X developer console (console.x.com) is the authoritative record.\",\n inputSchema: z.object({}),\n annotations: { readOnlyHint: true },\n },\n async () => wrap(async () => ctx.ledger.report(ctx.cache.stats())),\n );\n\n server.registerTool(\n \"x_rate_limit_status\",\n {\n title: \"X: Rate Limit Status\",\n description:\n \"Rate-limit headroom per endpoint, as of the last response from each. Empty until at \" +\n \"least one request has been made. Useful when a call has just been rate-limited and you \" +\n \"need to know how long to wait. Covers both the X API v2 and the Ads API, which have \" +\n \"separate budgets — the `api` field says which, and `scope` distinguishes the Ads \" +\n \"endpoint, account and cost limits.\",\n inputSchema: z.object({}),\n annotations: { readOnlyHint: true },\n },\n async () =>\n wrap(async () => {\n // Two clients, two independent budgets. Reading only the v2 one would\n // hide the account-level ads limit, which is the one that bites first\n // during a bulk campaign read.\n const limits = [\n ...client.rateLimitStatus().map((s) => ({ api: \"v2\" as const, ...s })),\n ...(ctx.ads\n ? ctx.ads.client.rateLimitStatus().map((s) => ({ api: \"ads\" as const, ...s }))\n : []),\n ];\n const adsSeen = limits.some((l) => l.api === \"ads\");\n return {\n endpoints: limits,\n ...(limits.length === 0\n ? { note: \"No requests issued yet this session, so X has not reported any limits.\" }\n : {}),\n ...(ctx.ads && !adsSeen\n ? {\n ads_note:\n \"Ads is configured but has not been called yet this session, so it reports no \" +\n \"limits. That is not a failure.\",\n }\n : {}),\n };\n }),\n );\n};\n","import type { McpServer } from \"@modelcontextprotocol/server\";\nimport { z } from \"zod\";\n\nimport { PreconditionError } from \"#/client/errors\";\nimport { shapePostsResponse, shapeUsersResponse, type ShapedUser } from \"#/client/shape\";\nimport type { XApiClient } from \"#/client/x\";\nimport type { ToolContext } from \"#/tools/index\";\nimport {\n cachedByIds,\n compact,\n maxResultsArg,\n paginationTokenArg,\n POST_QUERY,\n recordResultCost,\n stripAt,\n USER_QUERY,\n userIdArg,\n usernameArg,\n wrap,\n} from \"#/tools/util\";\n\n/**\n * Resolve a handle to the numeric id X's timeline endpoints require. Cached and\n * billed as a user read, because that is exactly what it is.\n */\nconst resolveUserId = async (\n client: XApiClient,\n ctx: ToolContext,\n opts: { username?: string | undefined; userId?: string | undefined },\n): Promise<{ id: string; user?: ShapedUser }> => {\n if (opts.userId) return { id: opts.userId };\n if (!opts.username) {\n throw new PreconditionError(\"Provide either `username` or `userId`.\", { got: opts });\n }\n const handle = stripAt(opts.username);\n const raw = await client.get(`/2/users/by/username/${handle}`, compact({ ...USER_QUERY }));\n const shaped = shapeUsersResponse(raw);\n const user = shaped.users[0];\n if (!user?.id) {\n throw new PreconditionError(`No X account found for @${handle}.`, { username: handle });\n }\n ctx.ledger.record(\"user\", [user.id]);\n return { id: user.id, user };\n};\n\nexport const registerUserTools = (\n server: McpServer,\n client: XApiClient,\n ctx: ToolContext,\n): void => {\n const fetchUsersByIds = async (ids: string[]): Promise<Map<string, ShapedUser>> => {\n const raw = await client.get(\"/2/users\", compact({ ids, ...USER_QUERY }));\n return new Map(shapeUsersResponse(raw).users.map((u) => [u.id, u]));\n };\n\n server.registerTool(\n \"x_get_user\",\n {\n title: \"X: Get User\",\n description:\n \"Look up one profile by handle or numeric id: bio, follower and post counts, join date. \" +\n \"A user read costs about $0.010, twice a post read.\",\n inputSchema: z.object({\n username: usernameArg.optional(),\n userId: userIdArg.optional(),\n }),\n annotations: { readOnlyHint: true },\n },\n async ({ username, userId }) =>\n wrap(async () => {\n if (!username && !userId) {\n throw new PreconditionError(\n 'Provide either `username` (a handle like \"mgcrea\") or `userId` (digits).',\n );\n }\n if (username && userId) {\n throw new PreconditionError(\n \"Provide only one of `username` or `userId` — they may disagree.\",\n { username, userId },\n );\n }\n\n if (userId) {\n const { items, cost, notFound } = await cachedByIds(\n ctx,\n \"user\",\n [userId],\n fetchUsersByIds,\n \"x_get_user\",\n );\n return items[0]\n ? { user: items[0], cost }\n : { error: `No X account found for id ${notFound[0]}.`, cost };\n }\n\n const handle = stripAt(username as string);\n const raw = await client.get(`/2/users/by/username/${handle}`, compact({ ...USER_QUERY }));\n const shaped = shapeUsersResponse(raw);\n const user = shaped.users[0];\n if (!user) return { error: `No X account found for @${handle}.` };\n ctx.cache.set(\"user\", user.id, user);\n return { user, cost: recordResultCost(ctx, \"user\", [user.id]) };\n }),\n );\n\n server.registerTool(\n \"x_get_users\",\n {\n title: \"X: Get Users\",\n description:\n \"Look up up to 100 profiles at once, by handle or by id. One request instead of many, \" +\n \"billed per profile returned.\",\n inputSchema: z.object({\n usernames: z\n .array(usernameArg)\n .min(1)\n .max(100)\n .optional()\n .describe('Handles to look up, e.g. [\"mgcrea\", \"acme\"].'),\n userIds: z.array(userIdArg).min(1).max(100).optional(),\n }),\n annotations: { readOnlyHint: true },\n },\n async ({ usernames, userIds }) =>\n wrap(async () => {\n if (!usernames && !userIds) {\n throw new PreconditionError(\"Provide either `usernames` or `userIds`.\");\n }\n if (usernames && userIds) {\n throw new PreconditionError(\"Provide only one of `usernames` or `userIds`.\");\n }\n\n if (userIds) {\n const unique = [...new Set(userIds)];\n const { items, cost, notFound } = await cachedByIds(\n ctx,\n \"user\",\n unique,\n fetchUsersByIds,\n \"x_get_users\",\n );\n return {\n users: items,\n ...(notFound.length > 0 ? { not_found: notFound } : {}),\n cost,\n };\n }\n\n const handles = [...new Set((usernames as string[]).map(stripAt))];\n const raw = await client.get(\"/2/users/by\", compact({ usernames: handles, ...USER_QUERY }));\n const shaped = shapeUsersResponse(raw);\n for (const user of shaped.users) ctx.cache.set(\"user\", user.id, user);\n return {\n users: shaped.users,\n ...(shaped.not_found ? { not_found: shaped.not_found } : {}),\n cost: recordResultCost(\n ctx,\n \"user\",\n shaped.users.map((u) => u.id),\n ),\n };\n }),\n );\n\n server.registerTool(\n \"x_get_user_posts\",\n {\n title: \"X: Get User Posts\",\n description:\n \"A user's own recent posts, newest first. Replies and reposts are excluded by default \" +\n \"so you get their original writing; set the flags to include them. Reaches back roughly \" +\n \"3200 posts, X's timeline limit.\",\n inputSchema: z.object({\n username: usernameArg.optional(),\n userId: userIdArg.optional(),\n maxResults: maxResultsArg,\n excludeReplies: z\n .boolean()\n .default(true)\n .describe(\"Leave out replies to other people. Defaults to true.\"),\n excludeReposts: z\n .boolean()\n .default(true)\n .describe(\"Leave out reposts (retweets). Defaults to true.\"),\n startTime: z\n .string()\n .optional()\n .describe('Only posts at or after this ISO-8601 UTC time, e.g. \"2026-07-01T00:00:00Z\".'),\n endTime: z.string().optional().describe(\"Only posts before this ISO-8601 UTC time.\"),\n paginationToken: paginationTokenArg,\n }),\n annotations: { readOnlyHint: true },\n },\n async ({\n username,\n userId,\n maxResults,\n excludeReplies,\n excludeReposts,\n startTime,\n endTime,\n paginationToken,\n }) =>\n wrap(async () => {\n const { id } = await resolveUserId(client, ctx, { username, userId });\n const exclude = [\n ...(excludeReplies ? [\"replies\"] : []),\n ...(excludeReposts ? [\"retweets\"] : []),\n ];\n const res = await client.paginate(\n `/2/users/${id}/tweets`,\n compact({\n max_results: Math.min(Math.max(maxResults, 5), 100),\n exclude: exclude.length > 0 ? exclude : undefined,\n start_time: startTime,\n end_time: endTime,\n pagination_token: paginationToken,\n ...POST_QUERY,\n }),\n { maxItems: maxResults },\n );\n const shaped = shapePostsResponse({ data: res.data, includes: res.includes[0] ?? {} });\n return {\n user_id: id,\n posts: shaped.posts,\n result_count: shaped.posts.length,\n ...(res.nextToken ? { next_token: res.nextToken } : {}),\n cost: recordResultCost(\n ctx,\n \"post\",\n shaped.posts.map((p) => p.id),\n ),\n };\n }),\n );\n\n server.registerTool(\n \"x_get_user_mentions\",\n {\n title: \"X: Get User Mentions\",\n description: \"Posts mentioning a user, newest first — who is talking about them, and what.\",\n inputSchema: z.object({\n username: usernameArg.optional(),\n userId: userIdArg.optional(),\n maxResults: maxResultsArg,\n startTime: z.string().optional().describe(\"Only posts at or after this ISO-8601 UTC time.\"),\n endTime: z.string().optional().describe(\"Only posts before this ISO-8601 UTC time.\"),\n paginationToken: paginationTokenArg,\n }),\n annotations: { readOnlyHint: true },\n },\n async ({ username, userId, maxResults, startTime, endTime, paginationToken }) =>\n wrap(async () => {\n const { id } = await resolveUserId(client, ctx, { username, userId });\n const res = await client.paginate(\n `/2/users/${id}/mentions`,\n compact({\n max_results: Math.min(Math.max(maxResults, 5), 100),\n start_time: startTime,\n end_time: endTime,\n pagination_token: paginationToken,\n ...POST_QUERY,\n }),\n { maxItems: maxResults },\n );\n const shaped = shapePostsResponse({ data: res.data, includes: res.includes[0] ?? {} });\n return {\n user_id: id,\n posts: shaped.posts,\n result_count: shaped.posts.length,\n ...(res.nextToken ? { next_token: res.nextToken } : {}),\n cost: recordResultCost(\n ctx,\n \"post\",\n shaped.posts.map((p) => p.id),\n ),\n };\n }),\n );\n};\n\nexport { resolveUserId };\n","import type { McpServer } from \"@modelcontextprotocol/server\";\n\nimport type { AdsApiClient } from \"#/client/ads\";\nimport type { TokenProvider } from \"#/client/auth\";\nimport type { DayCache } from \"#/client/cache\";\nimport type { Ledger } from \"#/client/cost\";\nimport type { TokenStore } from \"#/client/tokens\";\nimport type { XApiClient } from \"#/client/x\";\nimport type { Pricing } from \"#/config\";\nimport { registerAdsTools } from \"#/tools/ads/index\";\nimport { registerAuthTools } from \"#/tools/auth\";\nimport { registerComposeTools } from \"#/tools/compose\";\nimport { registerPostTools } from \"#/tools/posts\";\nimport { registerQueryBuilderTool, registerSearchTools } from \"#/tools/search\";\nimport { registerTimelineTools } from \"#/tools/timelines\";\nimport { registerUsageTools } from \"#/tools/usage\";\nimport { registerUserTools } from \"#/tools/users\";\n\n/**\n * Everything the ads tools need, as one object rather than four independently\n * optional fields: \"the client exists exactly when ads is configured\" is then\n * an invariant the type enforces rather than one that can drift.\n */\nexport type AdsContext = {\n client: AdsApiClient;\n /** Register the campaign-mutating tools. Off by default — see X_ADS_ALLOW_WRITES. */\n allowWrites: boolean;\n /** Default ads account. Absent means \"resolve it lazily from GET /12/accounts\". */\n accountId?: string | undefined;\n /** True when pointed at the Ads sandbox, where nothing spends real money. */\n sandbox: boolean;\n baseUrl: string;\n};\n\n/**\n * Threaded through every tool rather than a bare `allowWrites` boolean: the\n * cache and ledger are needed by every read tool, so a boolean would have been\n * widened on the first commit anyway.\n */\nexport type ToolContext = {\n /** Register the paid write tools too. Off by default — see X_API_ALLOW_WRITES. */\n allowWrites: boolean;\n /** \"intent\" (free web-intent URLs, the default) or \"api\" (paid POST /2/tweets). */\n writeBackend: \"intent\" | \"api\";\n autoOpenBrowser: boolean;\n /** Register x_search_all. Off by default — full-archive search needs a paid tier. */\n enableFullArchive: boolean;\n defaultMaxResults: number;\n pricing: Pricing;\n budgetUsd?: number | undefined;\n cache: DayCache;\n ledger: Ledger;\n tokenProvider: TokenProvider;\n /**\n * False when neither a Bearer token nor an OAuth client id is configured. The\n * server still starts and still serves the free local tools; the ones that\n * would call the X API are simply not registered.\n */\n hasCredentials: boolean;\n /** Setup guidance surfaced by x_auth_status when nothing is configured. */\n setup?: string[] | undefined;\n /** Where the OAuth tokens live, for x_auth_status. Absent when OAuth is unconfigured. */\n tokenFile?: string | undefined;\n /**\n * The token file, so a user id discovered lazily can be written back and not\n * re-fetched on every call. Absent when OAuth is unconfigured.\n */\n tokenStore?: TokenStore | undefined;\n /** Present only when a client id is configured; its presence registers the login tools. */\n login?: ((open: boolean) => Promise<LoginSummary>) | undefined;\n logout?: (() => void) | undefined;\n /** Present only when X_ADS_ENABLED is on and an OAuth client id is configured. */\n ads?: AdsContext | undefined;\n /** Ads setup guidance surfaced by x_auth_status when ads is not configured. */\n adsSetup?: string[] | undefined;\n};\n\nexport type LoginSummary = {\n username?: string | undefined;\n userId?: string | undefined;\n scopes: string[];\n tokenFile: string;\n};\n\n/**\n * Register the X API tools.\n *\n * Read tools and the free compose tools are always registered. The paid write\n * tools appear only when `allowWrites` *and* `writeBackend === \"api\"`;\n * `x_search_all` only when full-archive access is enabled; and the login tools\n * and user-context timelines only when an OAuth client id is configured — so\n * with the defaults those tools are not merely refused, they are invisible and\n * cannot be called at all.\n */\nexport const registerTools = (server: McpServer, client: XApiClient, ctx: ToolContext): void => {\n // Always available: these run locally and need no credentials at all. They are\n // registered first and unconditionally so that an unconfigured server is still\n // a useful one, rather than a connection that closes.\n registerComposeTools(server, client, ctx);\n registerAuthTools(server, ctx);\n registerQueryBuilderTool(server);\n\n if (!ctx.hasCredentials) return;\n\n registerPostTools(server, client, ctx);\n registerUserTools(server, client, ctx);\n registerSearchTools(server, client, ctx);\n registerUsageTools(server, client, ctx);\n // Bookmarks and the home timeline are unreachable without a user session, so\n // registering them Bearer-only would just hand the model two tools that\n // always fail with the same message.\n if (ctx.login) {\n registerTimelineTools(server, client, ctx);\n // Same reasoning for ads, which is user-context only: an app-only Bearer\n // token cannot reach /12/accounts at all.\n if (ctx.ads) registerAdsTools(server, ctx.ads.client, ctx);\n }\n};\n","import { McpServer } from \"@modelcontextprotocol/server\";\n\nimport { BUILD_INFO } from \"#/build-info\";\nimport { AdsApiClient } from \"#/client/ads\";\nimport {\n bearerTokenProvider,\n compositeTokenProvider,\n userTokenProvider,\n type Logger,\n type TokenProvider,\n} from \"#/client/auth\";\nimport { createDayCache, type DayCache } from \"#/client/cache\";\nimport { createLedger, type Ledger } from \"#/client/cost\";\nimport { createOAuthClient, startLoginFlow } from \"#/client/oauth\";\nimport { createTokenStore, type TokenStore } from \"#/client/tokens\";\nimport { XApiClient } from \"#/client/x\";\nimport { openInBrowser } from \"#/compose/open\";\nimport {\n adsSetupInstructions,\n effectiveScopes,\n hasAdsAccess,\n hasApiCredentials,\n setupInstructions,\n type Config,\n} from \"#/config\";\nimport { registerTools } from \"#/tools/index\";\n\nexport const SERVER_NAME = BUILD_INFO.name;\nexport const SERVER_VERSION = BUILD_INFO.version;\nexport const USER_AGENT = `mcp-x-api-js/${BUILD_INFO.version}`;\n\nexport type CreateServerOptions = {\n config: Config;\n fetch?: typeof fetch;\n logger?: Logger;\n /** Override the token provider (tests, and the OAuth user flow). */\n tokenProvider?: TokenProvider;\n now?: () => number;\n};\n\nexport type CreatedServer = {\n server: McpServer;\n client: XApiClient;\n /** Present only when ads is configured. Exposed for tests and diagnostics. */\n ads?: AdsApiClient | undefined;\n tokenProvider: TokenProvider;\n cache: DayCache;\n ledger: Ledger;\n store: TokenStore;\n};\n\nexport const createServer = (opts: CreateServerOptions): CreatedServer => {\n const { config } = opts;\n const server = new McpServer({ name: SERVER_NAME, version: SERVER_VERSION });\n\n const scopes = effectiveScopes(config);\n const store = createTokenStore(config.tokenFile);\n\n const tokenProvider =\n opts.tokenProvider ??\n compositeTokenProvider({\n ...(config.bearerToken ? { app: bearerTokenProvider(config.bearerToken) } : {}),\n ...(config.clientId\n ? {\n user: userTokenProvider({\n store,\n oauth: createOAuthClient(config, opts.fetch ?? fetch),\n clientId: config.clientId,\n requiredScopes: scopes,\n ...(opts.logger ? { logger: opts.logger } : {}),\n ...(opts.now ? { now: opts.now } : {}),\n }),\n }\n : {}),\n });\n\n const client = new XApiClient({\n baseUrl: config.baseUrl,\n tokenProvider,\n maxRetries: config.maxRetries,\n userAgent: USER_AGENT,\n ...(opts.fetch ? { fetch: opts.fetch } : {}),\n ...(opts.logger ? { logger: opts.logger } : {}),\n });\n\n // Shares the token provider with the v2 client: ads rides the same OAuth 2.0\n // session, distinguished only by the ads.read / ads.write scopes.\n const ads = hasAdsAccess(config)\n ? new AdsApiClient({\n baseUrl: config.adsBaseUrl,\n tokenProvider,\n maxRetries: config.maxRetries,\n maxDownloadBytes: config.adsMaxDownloadBytes,\n userAgent: USER_AGENT,\n ...(opts.fetch ? { fetch: opts.fetch } : {}),\n ...(opts.logger ? { logger: opts.logger } : {}),\n })\n : undefined;\n\n const cache = createDayCache({\n maxEntries: config.cacheMaxEntries,\n enabled: config.cacheEnabled,\n ...(opts.now ? { now: opts.now } : {}),\n });\n const ledger = createLedger({\n pricing: config.pricing,\n budgetUsd: config.monthlyBudgetUsd,\n ...(opts.now ? { now: opts.now } : {}),\n });\n\n registerTools(server, client, {\n allowWrites: config.allowWrites,\n writeBackend: config.writeBackend,\n autoOpenBrowser: config.autoOpenBrowser,\n enableFullArchive: config.enableFullArchive,\n defaultMaxResults: config.defaultMaxResults,\n pricing: config.pricing,\n budgetUsd: config.monthlyBudgetUsd,\n cache,\n ledger,\n tokenProvider,\n hasCredentials: hasApiCredentials(config),\n ...(hasApiCredentials(config) ? {} : { setup: setupInstructions(config) }),\n ...(ads\n ? {\n ads: {\n client: ads,\n allowWrites: config.adsAllowWrites,\n sandbox: ads.sandbox,\n baseUrl: config.adsBaseUrl,\n ...(config.adsAccountId ? { accountId: config.adsAccountId } : {}),\n },\n }\n : { adsSetup: adsSetupInstructions(config) }),\n ...(config.clientId\n ? {\n tokenFile: config.tokenFile,\n tokenStore: store,\n login: async (open: boolean) => {\n const { tokens } = await startLoginFlow({\n config,\n store,\n ...(opts.fetch ? { fetch: opts.fetch } : {}),\n ...(open ? { openBrowser: openInBrowser } : {}),\n ...(opts.logger ? { logger: opts.logger } : {}),\n ...(opts.now ? { now: opts.now } : {}),\n });\n return {\n username: tokens.username,\n userId: tokens.userId,\n scopes: tokens.scopes,\n tokenFile: config.tokenFile,\n };\n },\n logout: () => store.clear(),\n }\n : {}),\n });\n\n return { server, client, tokenProvider, cache, ledger, store, ...(ads ? { ads } : {}) };\n};\n"],"mappings":"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"}