PageSourceSearch

https://postcopilot.ai/threads-mcp-connector

html postcopilot.ai collected 2026-10-02 00:41:50 UTC 57,549 bytes, 903 lines download raw bytes

1<!doctype html>
2<html lang="en">
3<head>
4  <meta charset="utf-8" />
5  <meta name="viewport" content="width=device-width, initial-scale=1" />
6  <!-- Google tag (gtag.js) -->
vendor: 67 bytes, lines 6-7
6
7  <script async src="https://www.googletagmanager.com/gtag/js?id=
7G-M9ES79J8K9
vendor: 15 bytes, lines 7-8
7"></script>
8  
8<script>
9    
vendor: 142 bytes, lines 9-12
9window.dataLayer = window.dataLayer || [];
10    function gtag(){dataLayer.push(arguments);}
11    gtag('js', new Date());
12    gtag('config', '
12G-M9ES79J8K9
vendor: 7 bytes, lines 12-13
12');
13  
13</script>
13
14
15  <title>Threads MCP Connector for Claude — Export Posts &amp; Followers Free</title>
16  <meta name="description" content="Ask Claude for a Threads profile's posts, followers or following. The export runs on our servers and the rows come back in the chat. Free, no card." />
17  <meta name="keywords" content="threads mcp server, threads mcp connector, claude threads export, mcp connector claude, export threads with ai, threads data in claude, model context protocol threads" />
18  <meta name="author" content="PostCopilot" />
19  <meta name="application-name" content="PostCopilot MCP Connector" />
20  <meta name="theme-color" content="#000000" />
21
22  <!-- Favicons -->
23  <link rel="icon" type="image/x-icon" href="/favicon.ico" />
24  <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
25  <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
26  <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
27
28  <link rel="canonical" href="https://postcopilot.ai/threads-mcp-connector" />
29  <meta name="robots" content="index,follow" />
30  <meta name="language" content="English" />
31  <meta name="geo.region" content="US" />
32
33  <!-- Social preview -->
34  <meta property="og:type" content="website" />
35  <meta property="og:site_name" content="PostCopilot" />
36  <meta property="og:locale" content="en_US" />
37  <meta property="og:title" content="Threads MCP Connector for Claude — Export Posts &amp; Followers Free" />
38  <meta property="og:description" content="Ask Claude for a Threads profile's posts or followers. We run the export. Claude shows you the rows. Free to start, nothing to install." />
39  <meta property="og:url" content="https://postcopilot.ai/threads-mcp-connector" />
40  <meta property="og:image" content="https://postcopilot.ai/og-cover.png" />
41  <meta property="og:image:width" content="1200" />
42  <meta property="og:image:height" content="630" />
43  <meta name="twitter:card" content="summary_large_image" />
44  <meta name="twitter:title" content="Threads MCP Connector for Claude — Export Posts &amp; Followers Free" />
45  <meta name="twitter:description" content="Ask Claude for a Threads profile's posts or followers. We run the export. Claude shows you the rows. Free to start, nothing to install." />
46  <meta name="twitter:image" content="https://postcopilot.ai/og-cover.png" />
47
48  <!-- ─── Schema.org ──────────────────────────────────────────────────────────
49       STATIC structured data. Every number below is a hand-written LITERAL that
50       must match the visible copy AND the server. The free numbers resolve from
51       lib/connector-tier.js freeLimits(), which itself READS config/limits.js
52       FREE (postsPerExport 20, followersPerExport 100, followingPerExport 100,
53       exportsPerDay 3, exportFileDownloadsPerDay 1 — the last one is the
54       "rows of 1 export a day" read allowance, via freeRead()).
55
56       SCOPE OF THIS PAGE (narrowed 2026-09-12): the WEBSITE connector ONLY — the
57       path where Claude asks PostCopilot's own servers to run the export, and the
58       tools that read the result. The two BROWSER-side paths (an export the
59       Chrome extension uploads into the buffer, and request_browser_export
60       queueing a job for the user's own Chrome) are deliberately not described
61       here, because the extension half of them is not shipped. They stay live on
62       the backend; the page simply stops advertising them. For the same reason
63       the Chrome extension's separate $7.99/month plan is not mentioned anywhere
64       on this page: it unlocks nothing a visitor can reach from here. Do not add
65       either back until the extension build that implements them is public.
66
67       WHAT LIFTS THE LIMITS: a WEBSITE plan — the $2 24-hour day pass or Queue
68       Pro $39.99/mo, which are the SAME entitlement with different clocks.
69       Resolved by lib/connector-entitlement.js through lib/billing.js
70       getPlan(googleSub) via isPro(), keyed on the Google account, and reached by
71       creating or linking a connector while signed in on this page. The paid
72       numbers are lib/connector-tier.js proLimits(). If any of those change, fix
73       the JSON, the plans table and the FAQ together.
74
75       The FAQPage answers below are the visible <details> answers VERBATIM (text
76       content, whitespace collapsed). Edit both or neither. -->
77  
77<script type="application/ld+json">
78  {
79    "@context": "https://schema.org",
80    "@type": "SoftwareApplication",
81    "name": "PostCopilot MCP Connector",
82    "alternateName": "Threads MCP Connector for Claude",
83    "url": "https://postcopilot.ai/threads-mcp-connector",
84    "applicationCategory": "UtilityApplication",
85    "operatingSystem": "Any MCP client (Claude and others)",
86    "description": "Get Threads posts and followers inside Claude. Ask Claude for any public Threads profile; PostCopilot's servers run the export, the rows go into a private buffer, and Claude reads and filters them in the chat. Free: 3 exports a day, up to 20 posts or 100 followers or following each, and the rows of 1 export a day (re-read it as often as you like). No credit card, nothing to install. A $2 24-hour day pass or Queue Pro at $39.99/month lifts every limit; they are the same plan with a different duration.",
87    "offers": {
88      "@type": "AggregateOffer",
89      "lowPrice": "0",
90      "highPrice": "39.99",
91      "priceCurrency": "USD",
92      "offerCount": "3",
93      "offers": [
94        { "@type": "Offer", "name": "Free connector", "price": "0", "priceCurrency": "USD", "description": "Sign in with Google: no credit card, nothing to install. 3 exports a day per connector (5 per network), up to 20 posts or 100 followers or following each, 10 video downloads a day, and 3 exports kept for 6 hours. Claude can open the rows of 1 export a day and re-read that export as often as you like. The same allowance as the free web tools." },
95        { "@type": "Offer", "name": "PostCopilot Queue Pro day pass", "price": "2", "priceCurrency": "USD", "description": "24 hours of the same plan as Queue Pro. Lifts every connector limit: 10 exports an hour with no daily cap, up to 2,000 rows per export, unlimited video downloads and reads, 25 exports kept for 24 hours. One payment, nothing recurring." },
96        { "@type": "Offer", "name": "PostCopilot Queue Pro", "price": "39.99", "priceCurrency": "USD", "description": "The same plan as the day pass, by the month. Lifts every connector limit: 10 exports an hour with no daily cap, up to 2,000 rows per export, unlimited video downloads and reads, 25 exports kept for 24 hours." }
97      ]
98    },
99    "author": { "@type": "Organization", "name": "PostCopilot", "url": "https://postcopilot.ai" }
100  }
101  </script>
101
102  
102<script type="application/ld+json">
103  {
104    "@context": "https://schema.org",
105    "@type": "FAQPage",
106    "mainEntity": [
107      {
108        "@type": "Question",
109        "name": "How does it work?",
110        "acceptedAnswer": {
111          "@type": "Answer",
112          "text": "You ask Claude for a Threads profile. Claude calls our export tool, and the export runs on our servers. You do not need a Threads login, and nothing runs on your computer. An export takes about 20 seconds to 3 minutes. If it takes longer than about 45 seconds, Claude checks back and gets the result when it is ready. The rows go into a private buffer. Claude reads them in pages and can filter them by keyword, date, likes, follower count or verified status."
113        }
114      },
115      {
116        "@type": "Question",
117        "name": "What do I get for free?",
118        "acceptedAnswer": {
119          "@type": "Answer",
120          "text": "3 exports a day per connector, and 5 a day per network. Each export gives up to 20 posts, or up to 100 followers or following. Claude can open the rows of 1 export a day, and re-read that export as often as you like. You also get 10 video downloads a day. This is the same allowance as our free web tools. No credit card, nothing to install."
121        }
122      },
123      {
124        "@type": "Question",
125        "name": "Why can Claude read only one free export a day?",
126        "acceptedAnswer": {
127          "@type": "Answer",
128          "text": "Reading the rows is like downloading the file on our website, where free users get 1 file a day. Opening the same export again is free, including every page and every filter. The limit only applies to a second, different export on the same day. Listing your buffer is always free."
129        }
130      },
131      {
132        "@type": "Question",
133        "name": "What do the $2 day pass and Queue Pro unlock?",
134        "acceptedAnswer": {
135          "@type": "Answer",
136          "text": "Every limit. You get 10 exports an hour with no daily cap, up to 2,000 rows each, unlimited video downloads and reads, and 25 exports kept for 24 hours. The $2 day pass and Queue Pro ($39.99/month) are the same plan. Only the duration is different. Buying the pass again adds time. The URL already in Claude keeps working."
137        }
138      },
139      {
140        "@type": "Question",
141        "name": "I already have a connector token. Do I need a new one?",
142        "acceptedAnswer": {
143          "@type": "Answer",
144          "text": "No. Sign in, press Link an existing connector and paste the token (it starts with pcb_). Your plan applies within a minute. The URL in Claude and your buffer do not change."
145        }
146      },
147      {
148        "@type": "Question",
149        "name": "Can I try it without signing in?",
150        "acceptedAnswer": {
151          "@type": "Answer",
152          "text": "Yes. In Claude, open Customize → Connectors → Add custom connector. Paste https://postcopilot.ai/mcp, leave the OAuth fields empty (if Claude asks about sign-in, choose No sign-in) and click Add. Then turn it on in a chat from the + menu → Connectors. Other MCP clients take the same URL. No token, no account. You can export a profile's posts, followers or following, download a video, and generate a Threads post. It allows 2 exports a day per IP address. Results come straight into the chat, with no private buffer and no filtering. Read the setup guide for Claude, Cursor and VS Code."
153        }
154      },
155      {
156        "@type": "Question",
157        "name": "Which AI apps can use it?",
158        "acceptedAnswer": {
159          "@type": "Answer",
160          "text": "Any MCP client that can add a remote Streamable HTTP server. The steps above are for Claude. If your client can send an Authorization: Bearer header, use that instead of the token in the URL."
161        }
162      },
163      {
164        "@type": "Question",
165        "name": "What happens to my data?",
166        "acceptedAnswer": {
167          "@type": "Answer",
168          "text": "Nothing is exported until you ask. There is no background sync. Exports are deleted after 6 hours on free, or 24 hours on a plan. An expired export is never shown, even before cleanup runs. You can ask Claude to delete one export or empty the whole buffer at any time. A connector can only see its own buffer. More in the privacy policy."
169        }
170      },
171      {
172        "@type": "Question",
173        "name": "Is the connector URL a secret?",
174        "acceptedAnswer": {
175          "@type": "Answer",
176          "text": "Yes. Treat it like a password. Anyone with the URL can read that buffer, but never another account's. We store only a SHA-256 hash of it, so we show it once. Press Regenerate my connector to make the old URL stop working. Your exports stay."
177        }
178      },
179      {
180        "@type": "Question",
181        "name": "Why is the free plan limited?",
182        "acceptedAnswer": {
183          "@type": "Answer",
184          "text": "Each export uses a real browser on our servers for 20 seconds to 3 minutes, and a shared Threads session. That is our main cost. So the free connector gets the same allowance as the free website, and a plan lifts it."
185        }
186      },
187      {
188        "@type": "Question",
189        "name": "Which tools does Claude get?",
190        "acceptedAnswer": {
191          "@type": "Answer",
192          "text": "You never type these. Claude picks them from your request. export_posts, export_followers, export_following: run a new export on our servers. get_job: checks an export that is still running. download_video: returns direct video and photo links for one post. Works with /share/ links. list_exports: shows what is in your buffer and when it expires. get_export: reads an export in pages (50 by default, up to 500). query_export: filters and sorts rows without loading all of them. delete_export: deletes one export. purge_buffer: deletes everything, after you confirm."
193        }
194      }
195    ]
196  }
197  </script>
197
198  
198<script type="application/ld+json">
199  {
200    "@context": "https://schema.org",
201    "@type": "BreadcrumbList",
202    "itemListElement": [
203      { "@type": "ListItem", "position": 1, "name": "Home", "item": "https://postcopilot.ai/" },
204      { "@type": "ListItem", "position": 2, "name": "Threads MCP Connector", "item": "https://postcopilot.ai/threads-mcp-connector" }
205    ]
206  }
207  </script>
207
208
209  <link rel="stylesheet" href="/styles.css?v=20260914">
210  <style>
211    /* Calm layout: plain sections separated by whitespace. The connector card
212       (#connect-plan) is the ONLY raised surface, because it holds the one
213       action the page exists for. */
214    #connect-plan, #plans, #faq, #faq-no-signin { scroll-margin-top: 84px; }
215    .sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
216
217    .cta:focus-visible, .cta-secondary:focus-visible, .text-link:focus-visible,
218    details summary:focus-visible, .tools-grid a:focus-visible {
219      outline: 2px solid var(--primary); outline-offset: 3px;
220    }
221
222    /* ── Hero ── */
223    .c-hero { margin-bottom: 0; padding: 24px 0 8px; }
224    .c-hero h1 { max-width: 640px; margin-left: auto; margin-right: auto; }
225    .c-hero .lead { max-width: 560px; }
226    .c-hero__cta { margin-top: 28px; }
227    .c-hero__cta .cta { width: auto; min-width: 300px; text-decoration: none; }
228    /* --text-secondary, not --text-muted: #a3a3a3 on #ffffff is ~2.5:1 (fails AA). */
229    .micro { margin: 12px 0 0; font-size: 14px; color: var(--text-secondary); }
230    .text-link {
231      display: inline-flex; align-items: center; min-height: 44px; margin-top: 4px;
232      font-size: 15px; font-weight: 600; color: var(--text);
233      text-decoration: underline; text-underline-offset: 3px; text-decoration-color: var(--border);
234    }
235    .text-link:hover { text-decoration-color: currentColor; }
236
237    /* ── Section rhythm ── */
238    .sec { margin-top: 72px; }
239    .sec h2 { margin: 0 0 8px; font-size: clamp(22px, 4vw, 28px); font-weight: 700; letter-spacing: -0.02em; color: var(--text); }
240    .sec__intro { margin: 0; font-size: 16px; line-height: 1.6; color: var(--text-secondary); max-width: 640px; }
241    .fine { margin: 14px 0 0; font-size: 14px; line-height: 1.6; color: var(--text-secondary); }
242    .fine strong { color: var(--text); font-weight: 600; }
243
244    /* ── Examples as a chat ── */
245    .chat { display: grid; gap: 16px; margin-top: 24px; }
246    .ex {
247      background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius-lg);
248      padding: 20px;
249    }
250    .ex__who {
251      display: block; margin-bottom: 6px; font-size: 12px; font-weight: 700;
252      letter-spacing: 0.05em; text-transform: uppercase; color: var(--text-secondary);
253    }
254    .ex__ask {
255      margin: 0 0 16px; padding: 12px 16px;
256      background: var(--surface); border: 1px solid var(--border);
257      border-radius: var(--radius-lg) var(--radius-lg) 4px var(--radius-lg);
258      font-size: 15px; line-height: 1.5; color: var(--text);
259    }
260    .ex__ask:last-child { margin-bottom: 0; }
261    .ex__answer { font-size: 14px; color: var(--text-secondary); }
262    .ex__answer p { margin: 0; line-height: 1.55; }
263    .mini-wrap { overflow-x: auto; }
264    .mini-table { width: 100%; border-collapse: collapse; font-size: 14px; font-variant-numeric: tabular-nums; }
265    .mini-table caption { text-align: left; padding-bottom: 8px; font-size: 13px; font-style: italic; color: var(--text-secondary); }
266    .mini-table th, .mini-table td { padding: 9px 10px; text-align: left; border-bottom: 1px solid var(--border); vertical-align: top; }
267    .mini-table thead th { font-size: 12px; font-weight: 600; color: var(--text-secondary); }
268    .mini-table tbody td { color: var(--text); }
269    .mini-table tbody tr:last-child td { border-bottom: 0; }
270    .mini-table .num { text-align: right; white-space: nowrap; }
271    .mini-table .nowrap { white-space: nowrap; }
272
273    code {
274      font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
275      font-size: 0.92em; padding: 1px 5px; border-radius: 5px;
276      background: var(--surface-2); border: 1px solid var(--border);
277    }
278
279    /* ── The one raised surface: create a connector ── */
280    .card {
281      background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg);
282      padding: 32px; box-shadow: var(--shadow-lg);
283    }
284    .steps { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; margin: 24px 0 0; padding: 0; list-style: none; }
285    .step { background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius); padding: 20px; }
286    .step__num {
287      display: inline-flex; align-items: center; justify-content: center;
288      width: 28px; height: 28px; border-radius: 50%; margin-bottom: 10px;
289      background: linear-gradient(135deg, var(--primary), var(--secondary));
290      color: var(--on-primary); font-size: 14px; font-weight: 700;
291    }
292    .step h3 { margin: 0 0 6px; font-size: 16px; font-weight: 700; color: var(--text); }
293    .step p { margin: 0; font-size: 14px; color: var(--text-secondary); line-height: 1.55; }
294
295    .url-block {
296      margin: 16px 0 0; padding: 14px 16px; overflow-x: auto;
297      background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius);
298      font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
299      font-size: 14px; color: var(--text); white-space: nowrap;
300    }
301    .url-block .tok { color: var(--text-secondary); }
302
303    /* Shared by the authored markup AND the strings #connect-plan's script
304       renders (hero-ctas, plans-fine, facts) — keep these class names. */
305    .hero-ctas { display: flex; gap: 12px; justify-content: center; flex-wrap: wrap; }
306    .hero-ctas .cta { width: auto; min-width: 260px; text-decoration: none; }
307    .hero-ctas .cta-secondary { text-decoration: none; }
308    .plans-fine { margin-top: 14px; font-size: 14px; line-height: 1.6; color: var(--text-secondary); }
309    .plans-fine strong { color: var(--text); font-weight: 600; }
310    .facts { margin: 16px 0 0; padding-left: 20px; color: var(--text-secondary); line-height: 1.8; font-size: 15px; }
311    .connect-panel { margin-top: 24px; }
312    .connect-panel > .hero-ctas { justify-content: flex-start; }
313
314    /* ── Plans table ── */
315    .plans-table { width: 100%; border-collapse: collapse; font-size: 15px; margin-top: 24px; }
316    .plans-table th, .plans-table td { padding: 14px 12px; text-align: left; border-bottom: 1px solid var(--border); }
317    .plans-table thead th {
318      font-size: 13px; font-weight: 600; color: var(--text-secondary); border-bottom: 2px solid var(--border);
319    }
320    /* TWO value columns. The label column takes the spare width rather than the
321       values stretching: a comparison reads wrong the moment one side is visibly
322       roomier than the other. */
323    .plans-table thead th:first-child { width: 34%; }
324    .plans-table tbody th { font-weight: 600; color: var(--text); }
325    .plans-table td { color: var(--text-secondary); }
326    .plans-table .pro-col { background: var(--surface-2); color: var(--text); font-weight: 600; }
327    /* Column caption rendered INSIDE each cell for the linearised mobile layout.
328       A real element, not a ::before — generated content is not reliably exposed
329       to screen readers, and once the table is display:block the native header
330       association is gone, so this is the only thing left saying which plan a
331       value belongs to. Hidden on desktop, where the real <th> does the job. */
332    .cell-plan { display: none; }
333    .buy-row { display: flex; gap: 12px; flex-wrap: wrap; margin-top: 20px; }
334    .buy-row .cta { width: auto; min-width: 240px; }
335    .buy-row .cta-secondary { text-decoration: none; }
336
337    /* ── FAQ ── */
338    .faq-list { margin-top: 24px; }
339    details > .faq-a { padding: 0 16px 16px; color: var(--text-secondary); font-size: 15px; line-height: 1.65; }
340    .faq-a p { margin: 0 0 10px; }
341    .faq-a p:last-child { margin-bottom: 0; }
342    .faq-a ul { margin: 0; padding-left: 20px; }
343    .faq-a li { margin-bottom: 6px; }
344
345    /* ── Closing CTA ── */
346    .final-cta { text-align: center; padding: 48px 24px; border-radius: var(--radius-lg); background: var(--surface-2); border: 1px solid var(--border); }
347    .final-cta .sec__intro { margin: 0 auto; }
348    .final-cta .cta { width: auto; min-width: 300px; margin-top: 24px; text-decoration: none; }
349
350    /* ── More tools: kept for internal linking, deliberately quiet ── */
351    .more-tools { margin-top: 64px; padding-top: 32px; border-top: 1px solid var(--border); }
352    .more-tools h2 { margin: 0; font-size: 17px; font-weight: 600; color: var(--text-secondary); }
353    .more-tools .tools-grid { margin-top: 16px; gap: 10px; }
354    .more-tools .tools-grid a { background: transparent; }
355    .more-tools .tools-grid a:hover { transform: none; box-shadow: none; }
356
357    /* ── Responsive ── */
358    @media (max-width: 800px) {
359      .steps { grid-template-columns: 1fr; }
360    }
361    @media (max-width: 640px) {
362      .sec { margin-top: 56px; }
363      .card { padding: 24px 18px; }
364      .ex { padding: 14px; }
365      .ex__ask { font-size: 14.5px; }
366      .mini-table { font-size: 13px; }
367      .mini-table th, .mini-table td { padding: 8px 6px; }
368      /* Example data only: at 360px four columns squeeze the post text to a
369         sliver, so the least useful column drops out. */
370      .mini-table .col-opt { display: none; }
371      .c-hero__cta .cta, .final-cta .cta, .buy-row .cta, .buy-row .cta-secondary,
372      .hero-ctas .cta, .hero-ctas .cta-secondary { width: 100%; min-width: 0; }
373      .final-cta { padding: 32px 18px; }
374      /* At 360px the connector URL is wider than the viewport. Wrap it rather
375         than scroll it: a URL people are told to COPY must be fully visible. */
376      .url-block { font-size: 12.5px; white-space: normal; overflow-wrap: anywhere; word-break: break-all; }
377
378      /* A 360px table leaves ~110px per value column, not enough for "10 an
379         hour, no daily cap". Linearise row-major instead: each row becomes a
380         block headed by its label, values stacked beneath with their own
381         caption. Horizontal scroll was rejected — you cannot compare a column
382         that is off-screen. */
383      .plans-table, .plans-table tbody, .plans-table tr,
384      .plans-table th, .plans-table td { display: block; width: auto; }
385      .plans-table thead { display: none; }
386      .plans-table { font-size: 14px; margin-top: 16px; }
387      .plans-table tbody tr { padding: 14px 0 4px; border-bottom: 1px solid var(--border); }
388      .plans-table tbody tr:last-child { border-bottom: 0; }
389      .plans-table tbody th[scope="row"] { padding: 0 0 8px; border-bottom: 0; }
390      .plans-table tbody td {
391        display: grid; grid-template-columns: 84px minmax(0, 1fr); gap: 10px;
392        align-items: baseline; padding: 5px 10px 5px 13px; border-bottom: 0;
393      }
394      .plans-table tbody td.pro-col { border-left: 3px solid var(--success); border-radius: 8px; }
395      .cell-plan { display: block; font-size: 12px; font-weight: 600; color: var(--text-secondary); }
396    }
397
398    @media (prefers-reduced-motion: reduce) {
399      .cta, .cta-secondary, .tools-grid a { transition: none; }
400      .cta:hover { transform: none; }
401    }
402  </style>
403</head>
404<body>
405  <div id="header-placeholder"></div>
406  <div class="wrap">
407
408    <!-- ── HERO ─────────────────────────────────────────────────────────────
409         One headline, one sentence, one button. The button goes STRAIGHT to
410         Google sign-in (not to a section that shows the same button). Sign-in
411         cannot return to a #hash — /auth/google/callback appends "?auth=ok" to
412         returnTo, which would land after the fragment — so the page script
413         scrolls to #connect-plan when it sees auth=ok instead. When the status
414         call says the visitor is already signed in, the script relabels both
415         .js-connector-cta buttons "Create my connector" and points them at
416         #connect-plan;
416 their gtag event name comes from data-event. -->
417    <header class="c-hero">
418      <h1>Get any Threads profile's posts, followers and following inside Claude</h1>
419      <p class="lead">Ask Claude for any public Threads profile. Our servers run the export, and the rows come back in the chat.</p>
420      <div class="c-hero__cta">
421        <a class="cta js-connector-cta" href="/auth/google?return=/threads-mcp-connector" data-event="click_connector_signin" data-placement="hero" onclick="if(typeof gtag==='function')gtag('event',this.getAttribute('data-event'),{placement:'hero'})">Sign in with Google &mdash; it's free</a>
422        <p class="micro">No credit card. Nothing to install. 3 free exports a day.</p>
423        <a class="text-link" href="#faq-no-signin" onclick="if(typeof gtag==='function')gtag('event','click_connector_try_public',{placement:'hero'})">Or try it without signing in</a>
424      </div>
425    </header>
426
427    <!-- ── EXAMPLES ─────────────────────────────────────────────────────────
428         Every handle and number here is MADE UP, and says so. Column names match
429         the real fields (posts: text, likes, replies, reposts, postedAt; people:
430         username, full_name, follower_count, is_verified). Example 1 asks for 20
431         posts and example 2 fits inside 100 followers ON PURPOSE, so both run on
432         a free connector — do not raise them. -->
433    <section class="sec" aria-labelledby="examples-h">
434      <h2 id="examples-h">Paste this into Claude</h2>
435      <p class="sec__intro">You write a normal request. Claude picks the tools, runs the export and answers.</p>
436
437      <div class="chat">
438        <div class="ex">
439          <p class="ex__ask"><span class="ex__who">You</span>Export the last 20 posts from @coffee.lab and show me the 3 with the most replies.</p>
440          <div class="ex__answer mini-wrap">
441            <table class="mini-table">
442              <caption>What Claude shows you (example data)</caption>
443              <thead><tr><th scope="col" class="col-opt">Posted</th><th scope="col">Post</th><th scope="col" class="num">Likes</th><th scope="col" class="num">Replies</th></tr></thead>
444              <tbody>
445                <tr><td class="col-opt nowrap">Sep 9</td><td>&ldquo;Oat milk or whole milk for a flat white?&rdquo;</td><td class="num">412</td><td class="num">188</td></tr>
446                <tr><td class="col-opt nowrap">Sep 4</td><td>&ldquo;We are closing early on Friday. Here's why&hellip;&rdquo;</td><td class="num">530</td><td class="num">97</td></tr>
447                <tr><td class="col-opt nowrap">Aug 30</td><td>&ldquo;New beans from Kenya this week.&rdquo;</td><td class="num">301</td><td class="num">64</td></tr>
448              </tbody>
449            </table>
450          </div>
451        </div>
452
453        <div class="ex">
454          <p class="ex__ask"><span class="ex__who">You</span>Export @coffee.lab's followers. List the verified ones with more than 10,000 followers.</p>
455          <div class="ex__answer mini-wrap">
456            <table class="mini-table">
457              <caption>What Claude shows you (example data)</caption>
458              <thead><tr><th scope="col">Username</th><th scope="col" class="col-opt">Name</th><th scope="col" class="num">Followers</th><th scope="col">Verified</th></tr></thead>
459              <tbody>
460                <tr><td>@citybites</td><td class="col-opt">City Bites</td><td class="num">48,200</td><td>Yes</td></tr>
461                <tr><td>@brewdaily</td><td class="col-opt">Brew Daily</td><td class="num">21,050</td><td>Yes</td></tr>
462              </tbody>
463            </table>
464          </div>
465        </div>
466
467        <div class="ex">
468          <p class="ex__ask"><span class="ex__who">You</span>Download the video from this Threads link: threads.com/@coffee.lab/post/&hellip;</p>
469          <div class="ex__answer"><p>Claude returns the direct video and photo links, with no watermark.</p></div>
470        </div>
471      </div>
472
473      <p class="fine">The rows stay in a private buffer. Claude can filter them again without a new exp
473ort. <strong>Free: open the rows of 1 export a day</strong> (re-read it as often as you like).</p>
474    </section>
475
476    <!-- ── THE DOOR ──────────────────────────────────────────────────────────
477         The page's one entry point, and its primary conversion action: sign in
478         here, mint a connector, paste it into Claude. It is also where a WEBSITE
479         plan gets tied to a connector, because a plan is keyed on a Google
480         account (lib/billing.js billing_plans/{googleSub}) and the token has to
481         be attached to one — which can only happen while the user is signed in
482         HERE, where the sub is proven by the OAuth session rather than typed.
483
484         Everything below is progressive enhancement: with JS off, or before the
485         status call returns, it is still a readable, correctly-priced
486         explanation plus a working Sign in with Google link. -->
487    <section class="sec card" id="connect-plan" aria-labelledby="connect-h">
488      <h2 id="connect-h">Set it up in about a minute</h2>
489      <ol class="steps">
490        <li class="step">
491          <div class="step__num" aria-hidden="true">1</div>
492          <h3>Sign in with Google</h3>
493          <p>Free. No card, no email to confirm.</p>
494        </li>
495        <li class="step">
496          <div class="step__num" aria-hidden="true">2</div>
497          <h3>Press Create my connector</h3>
498          <p>You get a private URL. It is shown once, so copy it.</p>
499        </li>
500        <li class="step">
501          <div class="step__num" aria-hidden="true">3</div>
502          <h3>Paste it into Claude</h3>
503          <p>Customize &rarr; Connectors &rarr; Add custom connector. Paste the URL. Done.</p>
504        </li>
505      </ol>
506      <p class="url-block">https://postcopilot.ai/mcp/connector/<span class="tok">&lt;your-token&gt;</span></p>
507      <div id="connectPlanState" class="connect-panel" aria-live="polite">
508        <div class="hero-ctas" id="connectPlanSignedOut">
509          <a class="cta" href="/auth/google?return=/threads-mcp-connector" onclick="if(typeof gtag==='function')gtag('event','click_connector_signin',{placement:'connect_plan'})">Sign in with Google</a>
510        </div>
511      </div>
512      <p class="fine">Keep this URL private, like a password. Lost it? Press <em>Regenerate my connector</em> and the old one stops working.</p>
513    </section>
514
515    <section class="sec" id="plans" aria-labelledby="plans-h">
516      <h2 id="plans-h">Free to start. $2 when you need more.</h2>
517      <p class="sec__intro">The free connector gives the same allowance as our free web tools.</p>
518      <!-- Every literal in this table is a number the SERVER enforces.
519           FREE: lib/connector-tier.js freeLimits(), which reads config/limits.js
520           FREE (20 / 100 / 100 / 3 / 1 — the 1 is freeRead(): the rows of one
521           server-scraped export per UTC day, re-reads of it free); buffer depth
522           and TTL → CONNECTOR_FREE_MAX_EXPORTS_PER_ACCOUNT 3,
523           CONNECTOR_FREE_TTL_HOURS 6; per-network cap →
524           CONNECTOR_FREE_EXPORTS_PER_DAY_PER_IP 5; video →
525           CONNECTOR_FREE_VIDEOS_PER_DAY 10.
526
527           ON A PLAN ($2 day pass or Queue Pro $39.99/mo — the SAME entitlement,
528           only the duration differs): proLimits() — exportsPerHour 10,
529           exportMaxItems 2000, exportReadsPerDay 0 and videosPerDay 0 (both
530           meaning "not metered"), maxExportsPerAccount 25, ttlHours 24. -->
531      <table class="plans-table">
532        <thead>
533          <tr>
534            <th scope="col"><span class="sr-only">Limit</span></th>
535            <th scope="col">Free</th>
536            <th scope="col">$2 day pass or Queue Pro</th>
537          </tr>
538        </thead>
539        <tbody>
540          <tr>
541            <th scope="row">Exports</th>
542            <td><span class="cell-plan">Free</span>3 a day (5 per network)</td>
543            <td class="pro-col"><span class="cell-plan">On a plan</span>10 an hour, no daily cap</td>
544          </tr>
545          <tr>
546            <th scope="row">Rows per export</th>
547            <td><span class="cell-plan">Free</span>20 posts, or 100 followers or following</td>
548            <td class="pro-col"><span class="cell-plan">On a plan</span>Up to 2,000</td>
549          </tr>
550          <tr>
551            <th scope="row">Video downloads</th>
552            <td><span class="cell-plan">Free</span>10 a day</td>
553            <td class="pro-col"><span class="cell-plan">On a plan</span>No daily cap</td>
554          </tr>
555          <tr>
556            <th scope="row">Reading rows back</th>
557            <td><span class="cell-plan">Free</span>1 export a day, re-reads free</td>
558            <td class="pro-col"><span class="cell-plan">On a plan</span>Unlimited</td>
559          </tr>
560          <tr>
561            <th scope="row">Kept in buffer</th>
562            <td><span class="cell-plan">Free</span>3 exports, 6 hours</td>
563            <td class="pro-col"><span class="cell-plan">On a plan</span>25 exports, 24 hours</td>
564          </tr>
565        </tbody>
566      </table>
567      <p class="fine">The $2 day pass and Queue Pro are the <strong>same plan</strong>. The pass lasts 24 hours. Queue Pro is $39.99/month. The connector URL in Claude keeps working after you buy.</p>
568      <div class="buy-row">
569        <button class="cta" type="button" data-pc-buy-day-pass data-
569pc-surface="connector_plans">Get 24 hours for $2</button>
570        <a class="cta-secondary" href="/bulk-threads-downloader" data-pc-buy data-pc-surface="connector_plans_monthly">Queue Pro &mdash; $39.99/month</a>
571      </div>
572    </section>
573
574    <section class="sec" id="faq" aria-labelledby="faq-h">
575      <h2 id="faq-h">Questions</h2>
576      <!-- Deliberately NO tool COUNT anywhere in the tools answer. A count is the
577           one claim on this page that goes stale on a deploy rather than on a
578           decision — the tool list in mcp/connector-server.mjs changes — and a
579           page that says "nine" beside a connector offering ten is wrong for no
580           benefit. -->
581      <div class="faq-list">
582        <details>
583          <summary>How does it work?</summary>
584          <div class="faq-a"><p>You ask Claude for a Threads profile. Claude calls our export tool, and the export runs on our servers. You do not need a Threads login, and nothing runs on your computer. An export takes about 20 seconds to 3 minutes. If it takes longer than about 45 seconds, Claude checks back and gets the result when it is ready. The rows go into a private buffer. Claude reads them in pages and can filter them by keyword, date, likes, follower count or verified status.</p></div>
585        </details>
586        <details>
587          <summary>What do I get for free?</summary>
588          <div class="faq-a"><p>3 exports a day per connector, and 5 a day per network. Each export gives up to 20 posts, or up to 100 followers or following. Claude can open the rows of 1 export a day, and re-read that export as often as you like. You also get 10 video downloads a day. This is the same allowance as our free web tools. No credit card, nothing to install.</p></div>
589        </details>
590        <details>
591          <summary>Why can Claude read only one free export a day?</summary>
592          <div class="faq-a"><p>Reading the rows is like downloading the file on our website, where free users get 1 file a day. Opening the same export again is free, including every page and every filter. The limit only applies to a second, different export on the same day. Listing your buffer is always free.</p></div>
593        </details>
594        <details>
595          <summary>What do the $2 day pass and Queue Pro unlock?</summary>
596          <div class="faq-a"><p>Every limit. You get 10 exports an hour with no daily cap, up to 2,000 rows each, unlimited video downloads and reads, and 25 exports kept for 24 hours. The $2 day pass and <a href="/bulk-threads-downloader">Queue Pro</a> ($39.99/month) are the same plan. Only the duration is different. Buying the pass again adds time. The URL already in Claude keeps working.</p></div>
597        </details>
598        <details>
599          <summary>I already have a connector token. Do I need a new one?</summary>
600          <div class="faq-a"><p>No. Sign in, press <em>Link an existing connector</em> and paste the token (it starts with <code>pcb_</code>). Your plan applies within a minute. The URL in Claude and your buffer do not change.</p></div>
601        </details>
602        <details id="faq-no-signin">
603          <summary>Can I try it without signing in?</summary>
604          <div class="faq-a"><p>Yes. In Claude, open Customize &rarr; Connectors &rarr; Add custom connector. Paste <code>https://postcopilot.ai/mcp</code>, leave the OAuth fields empty (if Claude asks about sign-in, choose <em>No sign-in</em>) and click Add. Then turn it on in a chat from the + menu &rarr; Connectors. Other MCP clients take the same URL. No token, no account. You can export a profile's posts, followers or following, download a video, and generate a Threads post. It allows 2 exports a day per IP address. Results come straight into the chat, with no private buffer and no filtering. Read the <a href="/blog/postcopilot-mcp-server-threads-ai-tools">setup guide for Claude, Cursor and VS Code</a>.</p></div>
605        </details>
606        <details>
607          <summary>Which AI apps can use it?</summary>
608          <div class="faq-a"><p>Any MCP client that can add a remote Streamable HTTP server. The steps above are for Claude. If your client can send an <code>Authorization: Bearer</code> header, use that instead of the token in the URL.</p></div>
609        </details>
610        <details>
611          <summary>What happens to my data?</summary>
612          <div class="faq-a"><p>Nothing is exported until you ask. There is no background sync. E
612xports are deleted after 6 hours on free, or 24 hours on a plan. An expired export is never shown, even before cleanup runs. You can ask Claude to delete one export or empty the whole buffer at any time. A connector can only see its own buffer. More in the <a href="/privacy">privacy policy</a>.</p></div>
613        </details>
614        <details>
615          <summary>Is the connector URL a secret?</summary>
616          <div class="faq-a"><p>Yes. Treat it like a password. Anyone with the URL can read that buffer, but never another account's. We store only a SHA-256 hash of it, so we show it once. Press <em>Regenerate my connector</em> to make the old URL stop working. Your exports stay.</p></div>
617        </details>
618        <details>
619          <summary>Why is the free plan limited?</summary>
620          <div class="faq-a"><p>Each export uses a real browser on our servers for 20 seconds to 3 minutes, and a shared Threads session. That is our main cost. So the free connector gets the same allowance as the free website, and a plan lifts it.</p></div>
621        </details>
622        <details>
623          <summary>Which tools does Claude get?</summary>
624          <div class="faq-a">
625            <p>You never type these. Claude picks them from your request.</p>
626            <ul>
627              <li><code>export_posts</code>, <code>export_followers</code>, <code>export_following</code>: run a new export on our servers.</li>
628              <li><code>get_job</code>: checks an export that is still running.</li>
629              <li><code>download_video</code>: returns direct video and photo links for one post. Works with <code>/share/</code> links.</li>
630              <li><code>list_exports</code>: shows what is in your buffer and when it expires.</li>
631              <li><code>get_export</code>: reads an export in pages (50 by default, up to 500).</li>
632              <li><code>query_export</code>: filters and sorts rows without loading all of them.</li>
633              <li><code>delete_export</code>: deletes one export.</li>
634              <li><code>purge_buffer</code>: deletes everything, after you confirm.</li>
635            </ul>
636          </div>
637        </details>
638      </div>
639    </section>
640
641    <section class="sec final-cta" aria-labelledby="final-h">
642      <h2 id="final-h">Try it with your first profile</h2>
643      <p class="sec__intro">Sign in, copy one URL, paste it into Claude. 3 free exports a day, no card.</p>
644      <a class="cta js-connector-cta" href="/auth/google?return=/threads-mcp-connector" data-event="click_connector_signin" data-placement="footer" onclick="if(typeof gtag==='function')gtag('event',this.getAttribute('data-event'),{placement:'footer'})">Sign in with Google &mdash; it's free</a>
645      <div><a class="text-link" href="#plans">See plans</a></div>
646    </section>
647
648    <section class="more-tools" aria-labelledby="more-h">
649      <h2 id="more-h">More Free Threads Tools</h2>
650      <div class="tools-grid">
651        <a href="/post-downloader"><h3>Threads Post Downloader</h3><p>Download any profile's posts as CSV or JSON — free, no signup</p></a>
652        <a href="/threads-follower-export"><h3>Threads Follower Export</h3><p>Export followers and following lists from any Threads profile to CSV</p></a>
653        <a href="/download"><h3>Threads Video Downloader</h3><p>Save Threads videos and photos in MP4 or JPG — no watermark, no signup</p></a>
654        <a href="/bulk-threads-downloader"><h3>Bulk Threads Downloader</h3><p>Queue a list of @handles and get every profile's files by email</p></a>
655        <a href="/threads-exporter-extension"><h3>Threads Exporter Extension</h3><p>Chrome extension to export followers &amp; posts directly from Threads</p></a>
656      </div>
657    </section>
658
659    <div id="footer-placeholder"></div>
660  </div>
661
662  
662<script src="/components/loader.js?v=20260711"></script>
662
663
664  <!-- ── #connect-plan behaviour ────────────────────────────────────────────
665       Mints a connector for the signed-in PostCopilot account, and ties an
666       existing one to it so a WEBSITE plan ($2 day pass / Queue Pro) applies.
667       Nothing here computes a limit or a price: every number and every sentence
668       it renders comes from /api/connector/web/status and
669       /api/connector/web/token, i.e. from the same entitlement the server
670       enforces one function call later. That rule exists because of the
671       /post-downloader incident, where the page advertised the 10/day shared
672       shield while the server enforced a 3/day quota.
673
674       Progressive enhancement and fail-silent throughout: signed out, or with
675       this script blocked, the section is still a readable explanation plus a
676       working Sign in with Google link. A network error never leaves the panel
677       in a spinner — it says what happened and offers the action again. -->
678  
678<script>
679  (function () {
680    'use strict';
681    var box = document.getElementById('connectPlanState');
682    if (!box) return;
683
684    function esc(v) {
685      return String(v == null ? '' : v).replace(/[&<>"']/g, function (c) {
686        return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c];
687      });
688    }
689    function track(name, params) {
690      try { if (typeof gtag === 'function') gtag('event', name, params || {}); } catch (_) {}
691    }
692    async function api(method, url, body) {
693      var res = await fetch(url, {
694        method: method,
695        // Same-origin only, and credentials are required: these are the ONE
696        // family of connector routes authenticated by the pc_session cookie.
697        credentials: 'same-origin',
698        headers: body ? { 'Content-Type': 'application/json' } : undefined,
699        body: body ? JSON.stringify(body) : undefined
700      });
701      var json = null;
702      try { json = await res.json(); } catch (_) {}
703      return { status: res.status, json: json || {} };
704    }
705
706    // The plan line. Server-authored where a limit is involved; the labels here
707    // are only names for a plan the server already told us we hold.
708    function planLine(st) {
709      if (!st.webPro) {
710        return '<strong>No plan on this account.</strong> This connector runs on the free allowance ' +
711          '&mdash; 3 exports and 10 video downloads a day. A $2 24-hour day pass ' +
712          'or Queue Pro ($39.99/month) lifts them.';
713      }
714      if (st.dayPass && st.planEndsAt) {
715        return '<strong>Day pass active</strong> until ' + esc(new Date(st.planEndsAt).toLocaleString()) +
716          '. Exports and video downloads are unmetered on this connector.';
717      }
718      if (st.planStatus === 'canceling' && st.planEndsAt) {
719        return '<strong>Queue Pro</strong>, ending ' + esc(new Date(st.planEndsAt).toLocaleDateString()) +
720          '. Exports and video downloads are unmetered on this connector.';
721      }
722      return '<strong>Queue Pro active.</strong> Exports and video downloads are unmetered on this connector.';
723    }
724
725    function render(st) {
726      // `origin` is the server's own provenance field. Anything not minted here
727      // is labelled neutrally as "Linked" — this page does not describe where
728      // else a token could have come from, and inventing a source in a label
729      // would be the page advertising a path again.
730      var connectors = st.connectors || [];
731      var rows = connectors.map(function (c) {
732        return '<li>' + esc(c.origin === 'web' ? 'Created here' : 'Linked') +
733          (c.label ? ' &middot; ' + esc(c.label) : '') +
734          (c.lastUsedAt ? ' &middot; last used ' + esc(new Date(c.lastUsedAt).toLocaleDateString()) : '') +
735          '</li>';
736      }).join('');
737
738      // Only a web-origin connector is replaced by POST /api/connector/web/token —
739      // mintWebToken() skips any doc whose origin !== 'web'. Gating on `rows` (ANY
740      // connector, including linked extension ones) told people with only a linked
741      // connector that it would "stop working immediately", which is false: nothing
742      // was revoked, and they silently ended up with a SECOND connector on a second,
743      // empty-looking buffer. Gate on the thing the server actually rotates.
744      var hasWebConnector = connectors.some(function (c) { return c.origin === 'web'; });
745
746      box.innerHTML =
747        '<p class="plans-fine">Signed in as <strong>' + esc(st.email) + '</strong>. ' + planLine(st) + '</p>' +
748        (rows ? '<ul class="facts" style="margin-top:12px;">' + rows + '</ul>' : '') +
749        '<div class="hero-ctas" style="margin-top:16px;">' +
750          '<button class="cta" type="button" id="cpCreate">' +
751            (hasWebConnector ? 'Regenerate my connector' : 'Create my connector') + '</button>' +
752          '<button class="cta-secondary" type="button" id="cpLinkOpen">Link an existing connector</button>' +
753        '</div>' +
754        '<div id="cpOut"></div>';
755
756      var create = document.getElementById('cpCreate');
757      if (create) {
758        create.addEventListener('click', function () {
759          var regenerate = hasWebConnector;
760          if (regenerate && !window.confirm(
761            'This creates a new connector URL and makes the one you created here stop working ' +
762            'immediately. Any connector you linked from the extension is not affected. ' +
763            'Your buffered exports are kept. Continue?')) return;
764          create.disabled = true;
765          create.textContent = 'Creating…';
766          track('connector_web_token', { regenerate: regenerate });
767          api('POST', '/api/connector/web/token', {}).then(function (r) {
768            create.disabled = false;
769            create.textContent = regenerate ? 'Regenerate my connector' : 'Create my connector';
770            var out = document.getElementById('cpOut');
771            if (r.status !== 200 || !r.json.token) {
772              out.innerHTML = '<p class="plans-fine">
772' +
773                esc(r.json.error || 'Could not create a connector. Try again in a moment.') + '</p>';
774              return;
775            }
776            // Shown ONCE. The server stores only a SHA-256 of it, so there is no
777            // way to show it again later — said out loud rather than discovered.
778            out.innerHTML =
779              '<p class="plans-fine" style="margin-top:18px;"><strong>Paste this into Claude</strong> ' +
780              '(Customize &rarr; Connectors &rarr; Add custom connector). It is shown once and cannot be ' +
781              'recovered &mdash; we only ever store a hash of it. Treat it like a password.</p>' +
782              '<p class="url-block">' + esc(r.json.mcpUrlWithToken) + '</p>' +
783              // Deliberately NOT the server's full upgradeHint here. That
784              // sentence ends "buying one at postcopilot.ai/threads-mcp-connector"
785              // — which is this page, with the buttons a section below, so quoting
786              // it reads like being sent somewhere you already are. The plan NAMES
787              // are safe to author client-side; the LIMITS are not, and none are
788              // quoted below.
789              (r.json.entitlement && !r.json.entitlement.webPro
790                ? '<p class="plans-fine">Exports and video downloads are on the free allowance ' +
791                  'for this account. The $2 day pass or Queue Pro buttons below lift them, on this same ' +
792                  'connector, with nothing to reconnect.</p>'
793                : '<p class="plans-fine">Your plan applies to it already: exports and video ' +
794                  'downloads are unmetered.</p>');
795          }).catch(function () {
796            create.disabled = false;
797            create.textContent = regenerate ? 'Regenerate my connector' : 'Create my connector';
798            document.getElementById('cpOut').innerHTML =
799              '<p class="plans-fine">Network error. Nothing was changed &mdash; try again.</p>';
800          });
801        });
802      }
803
804      var linkOpen = document.getElementById('cpLinkOpen');
805      if (linkOpen) {
806        linkOpen.addEventListener('click', function () {
807          document.getElementById('cpOut').innerHTML =
808            '<p class="plans-fine" style="margin-top:18px;"><strong>Paste a connector token you already ' +
809            'hold.</strong> It starts with <code>pcb_</code> &mdash; either the token on its own ' +
810            'or the whole URL, we will take the token out of it. The token itself does not change: whatever ' +
811            'is already in Claude keeps working.</p>' +
812            '<p><input id="cpToken" type="text" inputmode="latin" autocomplete="off" spellcheck="false" ' +
813            'placeholder="pcb_…" aria-label="Connector token" ' +
814            'style="width:100%;box-sizing:border-box;padding:12px 14px;border-radius:10px;' +
815            'border:1px solid var(--border);background:var(--surface-2);color:var(--text);' +
816            'font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:14px;"></p>' +
817            '<div class="hero-ctas"><button class="cta" type="button" id="cpLinkGo">Link it</button></div>' +
818            '<p class="plans-fine" id="cpLinkMsg"></p>';
819
820          document.getElementById('cpLinkGo').addEventListener('click', function () {
821            var msg = document.getElementById('cpLinkMsg');
822            var raw = (document.getElementById('cpToken').value || '').trim();
823            // Accept the whole URL as well as the bare token: what people have to
824            // hand is usually a URL, so demanding the token alone would fail for
825            // the most likely paste.
826            var m = /pcb_[A-Za-z0-9_-]{20,}/.exec(raw);
827            if (!m) { msg.textContent = 'That does not contain a connector token. It starts with "pcb_".'; return; }
828            msg.textContent = 'Linking…';
829            track('connector_web_link', {});
830            api('POST', '/api/connector/web/link', { token: m[0] }).then(function (r) {
831              if (r.status !== 200) { msg.textContent = r.json.error || 'Could not link that connector.'; return; }
832              var e = r.json.entitlement || {};
833              msg.innerHTML = r.json.alreadyLinked
834                ? 'That connector was already linked to this account.'
835                : '<strong>Linked.</strong>
835 Nothing to reconnect &mdash; the URL in Claude is unchanged.' +
836                  (e.webPro
837                    ? ' Your plan now applies: exports and video downloads are unmetered.'
838                    : ' ' + esc(e.upgradeHint || ''));
839            }).catch(function () { msg.textContent = 'Network error. Nothing was changed — try again.'; });
840          });
841        });
842      }
843    }
844
845    // Signed in: the hero and closing buttons stop pointing at Google and point
846    // at the Create button instead, with the matching gtag event name.
847    function markSignedIn() {
848      var ctas = document.querySelectorAll('.js-connector-cta');
849      for (var i = 0; i < ctas.length; i++) {
850        ctas[i].setAttribute('href', '#connect-plan');
851        ctas[i].setAttribute('data-event', 'click_connector_create');
852        ctas[i].textContent = 'Create my connector';
853      }
854    }
855
856    // Back from Google sign-in. The OAuth callback appends "?auth=ok" to the
857    // return path, so it cannot carry a #fragment — land the user on the
858    // Create button here instead, then drop the flag so a reload doesn't jump.
859    function landAfterSignIn() {
860      if (!/[?&]auth=ok(&|$)/.test(location.search)) return;
861      var target = document.getElementById('connect-plan');
862      if (!target) return;
863      var reduce = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
864      target.scrollIntoView({ behavior: reduce ? 'auto' : 'smooth', block: 'start' });
865      var btn = document.getElementById('cpCreate');
866      if (btn) { try { btn.focus({ preventScroll: true }); } catch (_) {} }
867      try {
868        var q = location.search.replace(/([?&])auth=ok(&|$)/, function (_, a, b) { return b ? a : ''; }).replace(/[?&]$/, '');
869        history.replaceState(null, '', location.pathname + q + location.hash);
870      } catch (_) {}
871    }
872
873    api('GET', '/api/connector/web/status').then(function (r) {
874      // 401 is the ordinary signed-out case, not an error: leave the authored
875      // sign-in markup exactly as it is.
876      if (r.status === 401) return;
877      if (r.status !== 200) return;
878      render(r.json);
879      markSignedIn();
880    }).catch(function () { /* keep the signed-out markup */ })
881      .then(landAfterSignIn);
882  })();
883  </script>
883
884  
884<script>
885  // "Or try it without signing in" points at a closed <details>; open it.
886  (function () {
887    function openTarget() {
888      var id = location.hash && location.hash.slice(1);
889      var el = id && document.getElementById(id);
890      if (el && el.tagName === 'DETAILS') el.open = true;
891    }
892    window.addEventListener('hashchange', openTarget);
893    document.addEventListener('click', function (e) {
894      var a = e.target.closest && e.target.closest('a[href^="#faq-"]');
895      if (!a) return;
896      var el = document.getElementById(a.getAttribute('href').slice(1));
897      if (el && el.tagName === 'DETAILS') el.open = true;
898    });
899    openTarget();
900  })();
901  </script>
901
902</body>
903</html>

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.