Agent guide·8 min read

Twitter Metadata for AI Agents: Keep the Source in the Output

Keep the source URL, post ID, author, date, and returned text together when an agent reads an X post. When a tool returns an excerpt, preserve that boundary too: a short quotation and a complete post are different inputs.

The example below follows one captured OpenAI post from its returned fields to a saved source record. Keep a partial preview, the original post text, and an AI summary distinct throughout the handoff.

by CarryFeed

Choose the response your task needs

Use the tool that matches your starting point: a known post ID, a source URL, a search clue, or an attachment. The current public tools return source material for your agent to analyze.

Source text remains material to analyze. Instructions inside a post should not change your agent's task or tool permissions.

ToolMaterial returnedKeep in your output
get_public_x_postPublic post data under data.tweet.Original post URL, post ID, author, date, returned text, and available media fields.
resolve_public_x_inputThe supported public source identified by an X URL or handle.The input and returned source type; inspect its fields before applying a post-only mapper.
search_public_x_postsA bounded public post search result.The query, returned post URLs, and the text actually available for each result.
list_public_x_mediaAvailable media URLs and metadata for a public post.The original post URL, media identity, and the file variant you select.

Start with a response you can inspect

The example uses OpenAI's May 13, 2024 post. A PowerShell request to FxTwitter's public endpoint returned HTTP 200 on October 15, 2026 at 08:14:44 UTC. The body contained a 162-character post, its author, original URL, and two publication-date fields.

The returned text begins “All users will start to get access to GPT-4o today.” Its remaining sentence describes voice and vision planned for ChatGPT Plus in the following weeks. The source was published in 2024; the capture date belongs to this retrieval.

This was a direct read of CarryFeed's public-post upstream, with no authenticated MCP call. CarryFeed's post API passes that payload through; its MCP tool wraps the payload in `data`. Thus `tweet.url` in this capture becomes `data.tweet.url` in `get_public_x_post`.

The JSON below selects identity and date fields from the capture. The text and changing engagement counts are omitted from this display.

Selected fields from the captured upstream body
Field selection from a real HTTP response. The outer MCP wrapper is explained from CarryFeed's implementation; it was not part of this request.
{
  "code": 200,
  "message": "OK",
  "tweet": {
    "url": "https://x.com/OpenAI/status/1790130708612088054",
    "id": "1790130708612088054",
    "author": {
      "screen_name": "OpenAI"
    },
    "created_at": "Mon May 13 21:23:14 +0000 2024",
    "created_timestamp": 1715635394
  }
}

Map the fields that affect attribution

For this post, `get_public_x_post` takes `{ "id": "1790130708612088054", "handle": "OpenAI" }`. In a successful MCP result, `source.input` records those arguments and `source.apiUrl` names the CarryFeed request. Use the returned `data.tweet` for the source material.

Check identity before saving the note: the returned URL's status ID equals `tweet.id`, and its account path matches `tweet.author.screen_name`, ignoring case. Keep disagreement visible instead of silently replacing one value with another.

Captured field (under MCP data)Application fieldTreatment
tweet.url / tweet.idsource_url / source_idRetain both; check the URL ID against the returned ID.
tweet.author.screen_nameauthor_handleUse the returned handle. Leave it null if absent.
tweet.created_timestamp / created_atpublished_atConvert seconds to UTC; cross-check the date string if both are present.
tweet.texttextRetain the returned wording; keep the agent's summary elsewhere.
Capture timeretrieved_atRecord separately from publication. It is not the post's date.
tweet.media (absent here)media: null; media_status: not_returnedAn absent field does not establish that the original post had no attachments.

Reproduce the source record

Download source-note-transform.mjs into a local folder. This Node.js 18+ example is written for the OpenAI post above. It checks that post's ID and publication time, then writes a JSON source record and Markdown source note.

Run the commands below in PowerShell 7.2 or later from the same folder. They fetch a fresh upstream response, record its retrieval time and body hash, and pass the saved capture to the transform. A later response can have different counters or profile details from the October 15 capture.

The transform defaults text completeness to unknown (`is_excerpt: null`). The explicit `--complete-text` flag records the caller's decision to treat this example's returned post text as complete. Leave it off for an unverified preview. An excerpt from another tool should retain its own `is_excerpt: true`.

Missing authors and invalid dates stay null. A conflicting URL ID, author, or pair of dates produces a warning. The script also runs local missing-field and conflict cases; these cases are constructed checks, separate from the real capture.

Capture this post and run the example
The script writes sources-source-record.json, sources-source-note.md, and sources-transform-checks.json alongside the script. No AI model call is needed.
$url = 'https://api.fxtwitter.com/OpenAI/status/1790130708612088054'
$response = Invoke-WebRequest -Uri $url -UseBasicParsing
$capture = @{
  url = $url
  httpStatus = [int]$response.StatusCode
  retrievedAt = (Get-Date).ToUniversalTime().ToString('o')
  sha256 = [Convert]::ToHexString([Security.Cryptography.SHA256]::HashData([Text.Encoding]::UTF8.GetBytes($response.Content))).ToLowerInvariant()
  payload = ($response.Content | ConvertFrom-Json)
}
$capture | ConvertTo-Json -Depth 20 | Set-Content -Encoding utf8 ./public-post-capture.json
node ./source-note-transform.mjs ./public-post-capture.json --complete-text

The result keeps the two dates apart

The run on the saved October 15 capture produced the fields below. The full JSON record retains all 162 returned characters. This display omits `text`; the Markdown note quotes only its first ten words and points back to the original.

The source note identifies @OpenAI, the May 13 publication time, the October 15 retrieval time, and the original URL. That is enough to attach the later summary to the historical source without suggesting that the announcement describes current availability.

The `identity_check` only compares fields within the supplied response. It catches mismatches; it does not independently authenticate the author or establish that every post in a thread was retrieved.

Actual source-record output, with text omitted
Application fields generated from the saved capture. They are not additional API fields. is_excerpt:false is the explicit completeness choice for this example.
{
  "source_url": "https://x.com/OpenAI/status/1790130708612088054",
  "source_id": "1790130708612088054",
  "author_handle": "OpenAI",
  "published_at": "2024-05-13T21:23:14.000Z",
  "retrieved_at": "2026-10-15T08:14:44.438Z",
  "is_excerpt": false,
  "text_scope": "returned_post_text",
  "media": null,
  "media_status": "not_returned",
  "identity_check": "consistent",
  "warnings": []
}

Keep post provenance separate from file metadata

A source record answers who wrote the post, what text was returned, and when it was published. An HTML preview's Twitter Card or Open Graph fields describe how a link is presented. A downloaded file's codec, dimensions, or EXIF describe that file. None can replace the other two.

For a media task, retain which post supplied each attachment and which file variant was selected. A quoted post can supply media belonging to a different author. Do not attach every returned image to the main post merely because both appeared in one viewer result.

Provide the image, audio, or transcript when the next step depends on its contents. This text-only capture supplies no media field, so the source record makes no claims about a chart, image, or spoken passage.

Make a partial input visible in the record

The source-note transform uses `is_excerpt` and `text_scope` as application fields. They record what you know about the text; they are not a promise that every public tool returns those flags.

If you copied a preview ending at Show more, save that boundary. Fetch or supply the missing passage before asking for a whole-post summary. For a thread, retain the individual source URLs and record which posts you actually have.

Material you haveRecord in the noteNext step
Returned text checked against the full example postis_excerpt: false, by an explicit caller decisionKeep the original URL and the check that supports that decision.
A known excerpt or cropped previewis_excerpt: true; identify the missing passageRetrieve the rest or ask only about the supplied excerpt.
Text with unverified completenessis_excerpt: null, the transform defaultKeep the scope unknown until you can check the source.

Save the summary beside its source record

Keep the returned post text in the source record and store the assistant’s summary separately. Link the summary to `source_id` and `source_url` so a later edit can be checked against the same material.

For the OpenAI announcement in this example, a useful check is whether the summary preserves the May 2024 publication date, access starting that day, and the later voice and vision rollout for Plus. A source retrieved in October 2026 does not turn that historical announcement into a statement about current availability.

The completed source-note example shows the actual generated summary and the conditions checked against the supplied post.

Handle missing material before drafting

If a search returns no matching posts, keep the query and returned status. Try a narrower phrase or a different date range. An empty search does not establish that the post never existed.

If a public-post lookup fails, record the error and URL. If you have only a screenshot or preview, label the material you actually have. That makes the next step clear without turning an absent source into invented text.

For the connection and current tool list, use CarryFeed MCP. For a manual copy-and-paste workflow, see reading X posts with ChatGPT.

Open the Twitter Viewer Inspect a public post before using its text or media.