@nooticr/mcp 1.26.19
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.
- package/LICENSE +21 -0
- package/README.md +521 -0
- package/dist/auth.js +170 -0
- package/dist/config.js +54 -0
- package/dist/idempotency.js +32 -0
- package/dist/index.js +417 -0
- package/dist/nooticr.js +6 -0
- package/dist/oauth.js +263 -0
- package/dist/shared/brand-watch.js +297 -0
- package/dist/shared/cimd.js +231 -0
- package/dist/shared/collab.js +447 -0
- package/dist/shared/comment-review.js +171 -0
- package/dist/shared/connections.js +80 -0
- package/dist/shared/evidence.js +410 -0
- package/dist/shared/handoff.js +526 -0
- package/dist/shared/jobs.js +1785 -0
- package/dist/shared/nooticr.js +249 -0
- package/dist/shared/oauth.js +132 -0
- package/dist/shared/output-schemas.js +1046 -0
- package/dist/shared/own-account.js +559 -0
- package/dist/shared/performance.js +156 -0
- package/dist/shared/prompts.js +383 -0
- package/dist/shared/spend.js +158 -0
- package/dist/shared/tasks.js +88 -0
- package/dist/shared/tools-def.js +387 -0
- package/dist/shared/tools.js +2027 -0
- package/dist/shared/ui-template.js +2856 -0
- package/dist/shared/video.js +152 -0
- package/dist/shared/view-meta.js +34 -0
- package/dist/shared/watchlist.js +603 -0
- package/dist/video.js +6 -0
- package/package.json +62 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Nooticr
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,521 @@
|
|
|
1
|
+
# @nooticr/mcp
|
|
2
|
+
|
|
3
|
+
MCP (Model Context Protocol) server for [nooticr](https://nooticr.com).
|
|
4
|
+
|
|
5
|
+
Gives an AI assistant three things: it can **read** real social posts across
|
|
6
|
+
ten networks (TikTok, Instagram, YouTube, X, Reddit, LinkedIn, Douyin,
|
|
7
|
+
Xiaohongshu, Weibo, Bilibili), **understand** them — transcript, video frames,
|
|
8
|
+
comments, the numbers — and **make** something from what it learned: hooks,
|
|
9
|
+
variants to film, a scored draft, a repurposed thread.
|
|
10
|
+
|
|
11
|
+
The understanding is your model's, not ours. Every tool here fetches material
|
|
12
|
+
and hands it over with an account of what to do with it; none of them ask a
|
|
13
|
+
model of ours for an opinion first. You pay for the fetch and nothing else.
|
|
14
|
+
|
|
15
|
+
It also **monitors a name**: `search_mentions` sweeps nine of those networks for
|
|
16
|
+
every comment that says your brand, inside a date window you choose, and
|
|
17
|
+
`search_spoken_mentions` reads the words actually said out loud in TikTok and
|
|
18
|
+
YouTube videos for the mentions that were never typed anywhere.
|
|
19
|
+
|
|
20
|
+
Runs over stdio locally or as a hosted connector at `https://mcp.nooticr.com/mcp`.
|
|
21
|
+
Billed against your nooticr credits; new accounts get 20 free.
|
|
22
|
+
|
|
23
|
+
## Install (one link)
|
|
24
|
+
|
|
25
|
+
**Claude Code** — register the marketplace, then install the plugin:
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
/plugin marketplace add Nooticr/nooticr-mcp
|
|
29
|
+
/plugin install nooticr@nooticr
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
**Claude Code / CLI without the plugin**:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
claude mcp add nooticr --user -- npx -y @nooticr/mcp
|
|
36
|
+
npx @nooticr/mcp login # one-time sign-in (Google)
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
**Cursor / any stdio MCP client** (`claude_desktop_config.json`, `.mcp.json`, …):
|
|
40
|
+
|
|
41
|
+
```json
|
|
42
|
+
{
|
|
43
|
+
"mcpServers": {
|
|
44
|
+
"nooticr": {
|
|
45
|
+
"command": "npx",
|
|
46
|
+
"args": ["-y", "@nooticr/mcp"],
|
|
47
|
+
"env": { "NOOTICR_BASE_URL": "https://api.nooticr.com" }
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Tools
|
|
54
|
+
|
|
55
|
+
49 tools, grouped by what you are trying to do. Prices are in nooticr credits and
|
|
56
|
+
match what the server actually charges.
|
|
57
|
+
|
|
58
|
+
Seven of them — the ones under **Answer a question you actually have** — are not
|
|
59
|
+
endpoint wrappers. Each names a job, fans out over the calls that job needs,
|
|
60
|
+
groups the evidence by whatever you are deciding about, gives every item an id
|
|
61
|
+
a follow-up tool can act on, and hands the reading to your model rather than to
|
|
62
|
+
ours. They fan out, so they cost the sum of what they fetched and every one of
|
|
63
|
+
them caps that fan-out with an argument.
|
|
64
|
+
|
|
65
|
+
### Read a post
|
|
66
|
+
|
|
67
|
+
| Tool | Credits | What it is for |
|
|
68
|
+
|------|---------|----------------|
|
|
69
|
+
| `get_social_media` | 1 | The post's facts and media — contentType, title, caption, author, stats, direct media URLs, plus an inline thumbnail. Use when you want the post itself and nothing interpreted. |
|
|
70
|
+
| `get_post_transcript` | 1 | The words actually spoken, read from the post's caption track (TikTok and YouTube). Exact rather than inferred, and far cheaper than watching the video. Use before any analysis when the wording matters. |
|
|
71
|
+
| `get_post_frames` | 2 | Frames sampled evenly across a post's video, returned as **images you can actually look at** — not a description of them. ffmpeg opens the stream directly rather than downloading it, so HLS works and an expired link is re-resolved on the spot. Verified live at 3/3 on TikTok, YouTube, Instagram, Douyin and X; Reddit works on video posts. A carousel or slideshow returns its own images unchanged. Each frame costs roughly 1,200 tokens of your context. |
|
|
72
|
+
| `get_post_comments` | 2 | Top comments plus the themes the platform clusters them into, with which ones the creator pinned or liked. Use when you want to read what people wrote. |
|
|
73
|
+
|
|
74
|
+
### Understand a post
|
|
75
|
+
|
|
76
|
+
| Tool | Credits | What it is for |
|
|
77
|
+
|------|---------|----------------|
|
|
78
|
+
| `analyze_post_fast` | 2 | The post's transcript, caption and stats — everything but the pictures, which is what makes it the cheap read. Two fetches: `get_social_media` (1) and `get_post_transcript` (1). The sensible default. |
|
|
79
|
+
| `analyze_post` | 3 | Frames sampled across the video, as **images your model can actually look at**, plus the transcript. Two fetches: `get_post_frames` (2) and `get_post_transcript` (1). Use when the visuals are the point — framing, editing, on-screen text. |
|
|
80
|
+
| `understand_social_post` | 3 | The same two fetches, asked for a description of what physically happens on screen rather than why it works. Use when you need the events, not the strategy. |
|
|
81
|
+
| `analyze_comments` | 2 | The comment section, every comment with a stable id, and the taxonomy to label them with — sentiment, and whether each is praise, a complaint, a bug report, a question, a request, a comparison or spam. The same `get_post_comments` call, at the same price as reading them directly. |
|
|
82
|
+
| `show_comment_review` | free | Draws the classifications your model produced — every comment with its sentiment and category, filterable and selectable. Makes no requests; it only renders what you pass it. |
|
|
83
|
+
| `show_analysis` | free | Draws the analysis your model wrote from `analyze_post`/`analyze_post_fast`/`understand_social_post` — hook strength, script structure, quotable lines, suggested hashtags and more, whichever fields you produced. Makes no requests; it only renders what you pass it. |
|
|
84
|
+
| `compare_posts` | 1 | The first of two to five posts, fetched with its stats, and the comparison left to you. Fetch the rest with `get_social_media` at 1 credit each. Use when performance differs and you need to know why. |
|
|
85
|
+
| `show_comparison` | free | Draws the comparison your model wrote from `compare_posts` — each post scored, the winner marked, what differed and the next test worth running. Makes no requests; it only renders what you pass it. |
|
|
86
|
+
|
|
87
|
+
### Research a niche or a creator
|
|
88
|
+
|
|
89
|
+
| Tool | Credits | What it is for |
|
|
90
|
+
|------|---------|----------------|
|
|
91
|
+
| `discover_social_posts` | 2 | Recent posts for a niche across nine networks, with inline thumbnails and `limit`/`offset` pagination. Use to find posts to look at. |
|
|
92
|
+
| `get_user_posts` | 2 | One creator's recent posts with stats. Use to scan an account. |
|
|
93
|
+
| `search_creators` | 2 | Creators by niche or keyword. Use when you know the niche but not the names. |
|
|
94
|
+
| `get_similar_creators` | 2 | Lookalikes for a creator that already works. |
|
|
95
|
+
| `discover_sounds` | 2 | Trending audio with playable previews. Sound is a major ranking signal on TikTok. |
|
|
96
|
+
| `discover_hashtags` | 2 | Trending hashtags with volumes and whether each is rising, cooling or steady. |
|
|
97
|
+
| `find_hook_pattern` | 2 | A creator's recent posts, so their opening lines can be read as a set and turned into fill-in-the-blank templates. One `get_user_posts` call. |
|
|
98
|
+
| `search_mentions` | 2 per network (5 for Xiaohongshu) | **Brand monitoring.** Every *comment* that names a term, across nine networks at once, grouped under the post it was left on. A brand is named far more often in the replies than in a caption, so the comment is the unit — not the post. Takes a `since` date to read a past window, and pages with `offset`/`pageSize` so a nine-network sweep does not arrive all at once. Does not read speech inside a video — `search_spoken_mentions` does, on TikTok and YouTube. |
|
|
99
|
+
| `watch_creator` | free | Add a creator to your watchlist. Stores the handle only — nothing is fetched. |
|
|
100
|
+
| `unwatch_creator` | free | Drop a creator from the watchlist. |
|
|
101
|
+
| `catch_up_watchlist` | 2 per creator | What everyone you watch has posted since your last catch-up. Compares against the snapshot taken last time and moves it forward, so it answers "what is new" rather than "what exists". |
|
|
102
|
+
| `create_brand_watch` | free to call | Schedule a recurring sweep and get emailed only what is new. `kind: "mentions"` (default) is a recurring `search_mentions` sweep for `term`/`platforms`; `kind: "competitor"` is a recurring `get_user_posts` check on one creator (`handle`/`platform`), mailing only what beats their own recent median. Two calls by design: the first returns the quote (cost per run, cadence, cost per day) and a `confirmationToken`; nothing is created or charged until you call it again with `confirm: true` and that token. Each run then bills the same as calling the matching tool yourself — 2 credits per network (5 for Xiaohongshu) for mentions, a flat 2 credits for competitor. |
|
|
103
|
+
| `list_brand_watches` | free | Every watch you have scheduled: term, networks, cadence, cost per run, credits spent so far, runs made, and when the next one is due. |
|
|
104
|
+
| `stop_brand_watch` | free | Stop a watch by `watchId` or `term`. Immediate — the next run does not happen and nothing more is charged. |
|
|
105
|
+
| `niche_report` | 2 | Recent posts in a niche with their stats, so the dominant formats, hook patterns and the gaps nobody fills can be read off them. One `discover_social_posts` call. Use when deciding what to make. |
|
|
106
|
+
| `analyze_creator_profile` | 2 | A creator's recent posts with their stats — the material of a teardown: niche, themes, hook formula, what over- and underperforms, who the audience is. One `get_user_posts` call. |
|
|
107
|
+
|
|
108
|
+
### Answer a question you actually have
|
|
109
|
+
|
|
110
|
+
| Tool | Credits | What it is for |
|
|
111
|
+
|------|---------|----------------|
|
|
112
|
+
| `answer_my_audience` | 2 + 2 per post opened (14 by default) | **The mirror of `search_mentions`.** The questions waiting under your *own* posts: recent posts fetched, comments read on each, grouped under the post they were left on, every comment with a stable id, and the ones that read like questions or requests flagged and sorted to the top. It finds and drafts — it cannot post a reply, because no nooticr connection carries comment-write permission on any network. `limit` caps how many posts are opened, which is the price. |
|
|
113
|
+
| `prepare_handoff` | free | **The hand-off to your tracker.** Turns items you classified — a bug report in a comment, a complaint said out loud in a video — into the exact `title`, `body` and `labels` to file through whichever GitHub, Jira or Linear MCP server the host also has connected. nooticr files nothing and holds no tracker credential. The body carries the quote fenced and framed as a third-party report rather than as instructions, because the issue is read next by a coding agent; contact details are redacted, `@handles` and `#numbers` are defanged so filing notifies nobody by accident, and a `searchFirst` string dedupes a second sweep against the first. Makes no requests. |
|
|
114
|
+
| `show_audience_replies` | free | Lays your drafts out for a person to work through, grouped under the post, each with what you decided to do about it. Sends nothing; fetches nothing. |
|
|
115
|
+
| `track_competitor` | 2 | What a creator shipped, and which of it beat **their own** median rather than a raw view count that mostly measures follower count. One post list, whatever the window. If they are on your watchlist it also marks what is new since your last check and moves that marker forward — its own marker, not the one `catch_up_watchlist` keeps. |
|
|
116
|
+
| `who_should_i_work_with` | 2, or 4 with a seed | A collaboration shortlist: a keyword search merged with the lookalikes of a creator who already fits, marked by which search found each one. Every candidate also carries the **links pulled out of their bio**, typed and sorted by how much opening one will tell you — a repository to read the code in, their own site, a link hub that holds the real links — so vetting is reading the work rather than re-reading the follower count. Those links are never fetched here: they came out of a field the person being evaluated controls, so the host opens them, and the result says so. It does **not** measure audience overlap — that costs about nine credits a candidate, so the result says so and shows how to check a finalist rather than faking the signal. |
|
|
117
|
+
| `show_collab_shortlist` | free | Draws the candidates you scored, ranked, and asks the user which to approach. The scores are attributed to your model, not presented as a nooticr rating, and a candidate scored without anything having been opened is marked unverified. Makes no requests. |
|
|
118
|
+
| `why_did_this_underperform` | 3 | One post against the creator's own recent distribution, with the post taken back out of its own baseline. Returns median, quartiles, ratio and percentile, so the answer can be "this is an ordinary result, not a failure". Different question from `compare_posts`, which weighs two URLs you already picked. |
|
|
119
|
+
| `what_should_i_make_next` | 2 + 2 per post read + 2 (12 by default) | Demand against supply: what your commenters explicitly ask for, set beside what a niche sweep shows is already being made. A gap nobody asked for is noise; a request nobody serves is the opportunity. Falls back to your most-used hashtag when you name no niche. |
|
|
120
|
+
| `search_spoken_mentions` | 2 per platform narrowed by niche + 2 per creator handle + 1 per transcript, up to `maxTranscripts` | **The mirror of `search_mentions`, for what was said rather than typed.** Narrows to candidate posts (a niche sweep, named handles, and/or your watchlist), transcribes only the most-viewed survivors up to a hard ceiling, and searches the words for the term — TikTok and YouTube only, and only where the platform actually supplies a caption track. Reports how many candidates were found, transcribed and matched, so the spend is legible. |
|
|
121
|
+
|
|
122
|
+
### Make something
|
|
123
|
+
|
|
124
|
+
| Tool | Credits | What it is for |
|
|
125
|
+
|------|---------|----------------|
|
|
126
|
+
| `write_hooks` | 2, or free | The source post and its transcript, to write openings against. Give a topic instead of a url and it fetches nothing and costs nothing. |
|
|
127
|
+
| `show_hooks` | free | Draws the hooks your model wrote from `write_hooks` — each one with the device it uses and who it stops. Makes no requests; it only renders what you pass it. |
|
|
128
|
+
| `score_draft` | free | **Your** draft back with the rubric to hold it to — hook, clarity, payoff, specificity and fit, each scored 1-10, plus the three fixes worth making and a rewritten opening. Fetches nothing: the text is already yours. The only tool that runs before the content exists. |
|
|
129
|
+
| `repurpose_post` | 2 | The source post and its transcript, to rewrite for other surfaces — X thread, LinkedIn post, carousel slides, YouTube metadata, newsletter. |
|
|
130
|
+
| `show_repurposed_post` | free | Draws the rewritten copy your model produced from `repurpose_post` — one entry per surface. Makes no requests; it only renders what you pass it. |
|
|
131
|
+
| `create_variants` | 2 | The post that worked, with its transcript, to build variants from: hook, the angle that changes, ordered shot beats and a CTA. |
|
|
132
|
+
| `show_variants` | free | Draws the variants your model wrote from `create_variants` — each one's hook, angle, beats and CTA. Makes no requests; it only renders what you pass it. |
|
|
133
|
+
|
|
134
|
+
### Account
|
|
135
|
+
|
|
136
|
+
| Tool | Credits | What it is for |
|
|
137
|
+
|------|---------|----------------|
|
|
138
|
+
| `check_nooticr_credits` | free | Balance and billing URL. |
|
|
139
|
+
| `buy_nooticr_credits` | free | A Stripe Checkout URL for a credit pack. Credits land automatically after payment. |
|
|
140
|
+
| `nooticr_login` | free | Re-link the account when a call fails with an authentication error. |
|
|
141
|
+
|
|
142
|
+
### Your own product
|
|
143
|
+
|
|
144
|
+
Everything above reads someone else's content. These read and generate for **your own** — a different product, a different balance (your workspace's plan AI credits, not the personal MCP credits above), billed exactly like the matching dashboard button.
|
|
145
|
+
|
|
146
|
+
| Tool | Credits | What it is for |
|
|
147
|
+
|------|---------|----------------|
|
|
148
|
+
| `list_own_apps` | free | Every product in your workspace — call this first when you have more than one and another tool below asks for `appId`. |
|
|
149
|
+
| `create_product` | free | Create a new product in your workspace — `name`/`slug` required, plus optional `description`, `website_url`, `niche`, `product_type` and store-listing fields (snake_case — see note below). Takes no workspace id; always creates in your own. Does not generate a brand playbook — call `analyze_product` for that. |
|
|
150
|
+
| `update_product` | free | Patch a product's fields — omitted arguments leave their column unchanged. Same snake_case field names as `create_product`. |
|
|
151
|
+
| `get_scheduled_posts` | free | Your own scheduled and draft posts in the content pipeline — title, status, scheduled time, approval status. What is queued to publish. |
|
|
152
|
+
| `get_post_performance` | free | Your own already-published posts with their engagement counters — views, likes, comments, shares, platform, post date. The raw performance history, not an interpretation of it; pair with `growth_brief` for that. |
|
|
153
|
+
| `get_video_stats` | free | Your own most recently synced video performance stats across every connected creator — views, likes, comments, shares, plus a running total. Reads the last sync; does not trigger a new one. |
|
|
154
|
+
| `get_content_plan` | free | The saved weekly content plan for a product, if one has been generated. `plan: null` when none has. |
|
|
155
|
+
| `get_brand_playbook` | free | Your product's brand playbook — name, description and the playbook text — if one has been configured, in the dashboard or by `analyze_product`. Read-only; `available: false` when none exists. |
|
|
156
|
+
| `review_post` | free | Score a draft before you publish it — hook strength, an A/B hook comparison, aesthetic and storytelling notes, rewritten hooks/captions. Never billed, same as the dashboard's own pre-publish review. |
|
|
157
|
+
| `draft_post` | plan AI credits | A ready-to-use draft (title, caption, hashtags, per-slide script) for a topic, grounded in your product's name. Returns text only — saves nothing. |
|
|
158
|
+
| `growth_brief` | plan AI credits | A plain-language brief — the one insight that matters, wins, risks, next actions — grounded in your real post history and synced analytics. |
|
|
159
|
+
| `generate_content_plan` | plan AI credits | A one-week, day-by-day content plan for your creators, grounded in what already worked. Saved — fetch it later with `get_content_plan`. |
|
|
160
|
+
| `generate_captions` | plan AI credits | Timed on-screen caption cues for a video — a transcript plus start/end-timed lines. Returns cue data only. |
|
|
161
|
+
| `analyze_product` | plan AI credits | Start an AI analysis of your product: fetches an excerpt of its own website (a real outbound fetch, not just nooticr's own data), reads its posts and fleet performance, and writes the result as its brand playbook. Runs in the background — returns a `jobId`, poll it with `analyze_product_status`. 10 plan AI credits, first analysis free per workspace. |
|
|
162
|
+
| `analyze_product_status` | free | Poll a job `analyze_product` started. Returns `state` (pending/thinking/done/error) and, once done, the analysis. Free — the cost was already charged when the job started. |
|
|
163
|
+
| `list_social_connections` | free | The social accounts you've connected and what each is allowed to do — read, publish, manage comments. Also lists which platforms can be connected at all. |
|
|
164
|
+
| `connect_social_account` | free | A link to open to connect one account. You approve at the provider; nothing connects until you do, and no credential ever passes through this tool. |
|
|
165
|
+
|
|
166
|
+
### How billing works
|
|
167
|
+
|
|
168
|
+
- New accounts get **20 free credits**.
|
|
169
|
+
- **Every tool is priced at what it fetches upstream**, and bills from the first
|
|
170
|
+
call. A tool that fans out to two fetches costs both — `analyze_post` is 2 for
|
|
171
|
+
the frames plus 1 for the transcript — and its description says so. There is
|
|
172
|
+
no free first use: that grant belonged to the AI calls, and there are none.
|
|
173
|
+
- A call that fails is **refunded automatically**, and a call interrupted
|
|
174
|
+
mid-flight is billed **once at most** — retries are idempotent.
|
|
175
|
+
- Platform admins bypass credit debiting entirely.
|
|
176
|
+
|
|
177
|
+
## Interactive cards in Claude / ChatGPT chat
|
|
178
|
+
|
|
179
|
+
Every tool that returns posts also renders **inline interactive cards** directly
|
|
180
|
+
in the chat (MCP Apps `ui://nooticr/view` resource rendered in a sandboxed
|
|
181
|
+
iframe):
|
|
182
|
+
|
|
183
|
+
- **Video posts** (TikTok/IG/YouTube/Douyin/LinkedIn) — an inline `<video>`
|
|
184
|
+
player with the thumbnail as poster, playing the re-hosted permanent MP4
|
|
185
|
+
(no expiring CDN tokens).
|
|
186
|
+
- **Carousels / slideshows** — a horizontally scrollable strip of every slide
|
|
187
|
+
with an image count chip.
|
|
188
|
+
- **Single images** — inline thumbnail.
|
|
189
|
+
- **Text-only posts** (LinkedIn / X) — a styled quote block of the post text.
|
|
190
|
+
- **Official brand marks** — each card shows the platform's real logo
|
|
191
|
+
(simple-icons) in its brand color instead of an emoji.
|
|
192
|
+
|
|
193
|
+
`search_mentions` renders a different view, because monitoring is triage rather
|
|
194
|
+
than browsing:
|
|
195
|
+
|
|
196
|
+
- **Each row is a person saying something.** Their picture, with the network's
|
|
197
|
+
mark on it, then the handle and when they wrote it — "today at 7:25 PM", not
|
|
198
|
+
a timestamp — then the comment with the term highlighted everywhere it
|
|
199
|
+
appears, then what it earned in likes and replies. A `×N` badge marks a
|
|
200
|
+
comment that names the brand more than once.
|
|
201
|
+
- **Each post carries its reach.** The same sentence under a 25K-upvote thread
|
|
202
|
+
and under a post nobody saw are not the same problem, and nothing else on
|
|
203
|
+
screen tells you which one you are reading.
|
|
204
|
+
- **The per-network counts are filters.** Click one to narrow to it; a network
|
|
205
|
+
that answered with nothing is shown but is not clickable, because filtering to
|
|
206
|
+
it is a dead end. Filtering and sorting redraw from what you already paid for
|
|
207
|
+
— the view never re-queries.
|
|
208
|
+
- **A burst collapses.** One post with a run of near-identical replies (a
|
|
209
|
+
coordinated fan campaign, say) shows the first few and offers the rest, so it
|
|
210
|
+
cannot push four other networks off the screen.
|
|
211
|
+
- **Comments are selectable, and the selection is agentic.** Tick any comments,
|
|
212
|
+
or select a whole thread at once, and send them — the view hands the host the
|
|
213
|
+
comment **ids the tool issued**, so the model can reply to, escalate or
|
|
214
|
+
analyse exactly those. Selections survive filtering and sorting.
|
|
215
|
+
- **Load more** pages from the `nextOffset` the tool returned.
|
|
216
|
+
|
|
217
|
+
Also **inline thumbnails** render for the model / plain-text clients:
|
|
218
|
+
|
|
219
|
+
- `get_social_media` / `understand_social_post` / `analyze_post` — up to 4 frames inline (poster + carousel slides). The full `mediaItems[].preview_url` + `thumbnailUrl` stay in `structuredContent` for the model to reason over.
|
|
220
|
+
- `discover_social_posts` / `get_user_posts` / `analyze_creator_profile` — each returned post shows its thumbnail inline (up to 4 at once) together with its title/caption + views/likes/comments. Say **"next"** or **"show more"** — Claude will re-call with `offset`/`limit` pagination. Say **"analyze the 2nd one"** — Claude calls `analyze_post` or `understand_social_post` on that URL.
|
|
221
|
+
- **Batch analysis** — ask "analyze all 4" or "understand these 3 in batch" and Claude will call `analyze_post`/`understand_social_post` once per URL in parallel and summarize. For large batches, `discover_social_posts` + a follow-up `analyze_post` per URL is the recommended flow.
|
|
222
|
+
|
|
223
|
+
## Your own model does the thinking
|
|
224
|
+
|
|
225
|
+
There is no other mode. Every tool returns **the material an analysis would have
|
|
226
|
+
been built from**, at the price of the fetch, and asks your model to reason over
|
|
227
|
+
it. Nothing here calls a model of ours, so nothing here sells you a judgement.
|
|
228
|
+
|
|
229
|
+
For the visual tools that means **actual frames** — real image content blocks,
|
|
230
|
+
not a description of them — paired with the transcript. Measured on Claude Code:
|
|
231
|
+
~1,200 tokens per 1280×720 frame, eight frames read back correctly and in order.
|
|
232
|
+
Twenty frames is about 2.4% of a million-token context.
|
|
233
|
+
|
|
234
|
+
| Tool | What comes back | Credits |
|
|
235
|
+
|---|---|---|
|
|
236
|
+
| `analyze_post` | frames as images, plus the transcript and stats | **3** (2 + 1) |
|
|
237
|
+
| `analyze_comments` | the comments, each with an id, and the labels to use | **2** |
|
|
238
|
+
| `analyze_creator_profile` | the creator's recent posts and their numbers | **2** |
|
|
239
|
+
| `score_draft` | your own draft, and the rubric to score it against | **free** |
|
|
240
|
+
|
|
241
|
+
The prices above are derived from the fetches each tool makes rather than
|
|
242
|
+
written down twice — see `EVIDENCE_PLANS` and `planCost` in
|
|
243
|
+
`src/shared/evidence.ts`. `score_draft` has no plan at all: it reviews text you
|
|
244
|
+
already have, so there is nothing to fetch and nothing to charge for.
|
|
245
|
+
|
|
246
|
+
`show_comment_review` closes the loop: hand back your classifications and it
|
|
247
|
+
draws them — every comment with its sentiment and category, filterable and
|
|
248
|
+
selectable. Free, and it makes no requests.
|
|
249
|
+
|
|
250
|
+
## Before an expensive call, it asks
|
|
251
|
+
|
|
252
|
+
Most tools print their price in their own description, so a call costs what you
|
|
253
|
+
already read. Two do not, because their price is set by an argument:
|
|
254
|
+
|
|
255
|
+
- `search_mentions` bills **per network swept**, so a bare "monitor my brand"
|
|
256
|
+
sweeps all nine for 21 credits.
|
|
257
|
+
- `catch_up_watchlist` bills **per creator**, so the price is the length of a
|
|
258
|
+
list the request never mentions.
|
|
259
|
+
|
|
260
|
+
Above 6 credits those two ask first, over MCP `elicitation` — the client shows
|
|
261
|
+
the number and you accept or decline. Declining spends nothing and is not an
|
|
262
|
+
error. A client that does not support elicitation is not blocked; the call runs
|
|
263
|
+
as it always did.
|
|
264
|
+
|
|
265
|
+
## Prerequisites
|
|
266
|
+
|
|
267
|
+
- Node.js >= 18 (tested on Node 22)
|
|
268
|
+
- An nooticr account (created automatically on first sign-in — Google sign-in
|
|
269
|
+
via `npx @nooticr/mcp login`; the dashboard is **not** required: the server
|
|
270
|
+
auto-creates a default workspace + app for new accounts)
|
|
271
|
+
- Access to an nooticr server — defaults to the cloud API
|
|
272
|
+
(`https://api.nooticr.com`); point `NOOTICR_BASE_URL` at your own deployment
|
|
273
|
+
for local development
|
|
274
|
+
|
|
275
|
+
## Quick start
|
|
276
|
+
|
|
277
|
+
```bash
|
|
278
|
+
# 1. Sign in with your nooticr account (Google sign-in opens in your browser)
|
|
279
|
+
npx @nooticr/mcp login
|
|
280
|
+
# or with email/password:
|
|
281
|
+
npx @nooticr/mcp login --email you@example.com --password '...'
|
|
282
|
+
|
|
283
|
+
# 2. Add it to your MCP client (see install section above) — or run it manually:
|
|
284
|
+
npx @nooticr/mcp # stdio (default; for Claude Desktop / Cursor)
|
|
285
|
+
npx @nooticr/mcp --http # remote HTTP with OAuth (for OpenAI Agents SDK)
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
`login` stores your nooticr tokens in `~/.config/nooticr-mcp/credentials.json`
|
|
289
|
+
(mode `0600`). If your browser cannot be opened automatically, copy the URL it
|
|
290
|
+
prints into a browser manually.
|
|
291
|
+
|
|
292
|
+
## Usage in Claude Desktop
|
|
293
|
+
|
|
294
|
+
After `npx @nooticr/mcp login`, add to `claude_desktop_config.json`:
|
|
295
|
+
|
|
296
|
+
```json
|
|
297
|
+
{
|
|
298
|
+
"mcpServers": {
|
|
299
|
+
"nooticr": {
|
|
300
|
+
"command": "npx",
|
|
301
|
+
"args": ["-y", "@nooticr/mcp"]
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
Tokens are resolved from the credentials file written by `login` (or from
|
|
308
|
+
`NOOTICR_ACCESS_TOKEN`). If your client does not inherit your shell environment,
|
|
309
|
+
set the env vars explicitly:
|
|
310
|
+
|
|
311
|
+
```json
|
|
312
|
+
{
|
|
313
|
+
"mcpServers": {
|
|
314
|
+
"nooticr": {
|
|
315
|
+
"command": "npx",
|
|
316
|
+
"args": ["-y", "@nooticr/mcp"],
|
|
317
|
+
"env": {
|
|
318
|
+
"NOOTICR_BASE_URL": "http://localhost:8080"
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
## Usage in Cursor
|
|
326
|
+
|
|
327
|
+
In `.mcp.json` (project root) or `~/.cursor/mcp.json`:
|
|
328
|
+
|
|
329
|
+
```json
|
|
330
|
+
{
|
|
331
|
+
"mcpServers": {
|
|
332
|
+
"nooticr": {
|
|
333
|
+
"command": "npx",
|
|
334
|
+
"args": ["-y", "@nooticr/mcp"]
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
After adding the server, run `npx @nooticr/mcp login` in your terminal — Cursor
|
|
341
|
+
spawns the server with your environment, so it picks up the stored credentials.
|
|
342
|
+
|
|
343
|
+
For remote/HTTP usage, Cursor can connect to the OAuth-enabled HTTP mode with
|
|
344
|
+
`npx @nooticr/mcp --http` running, pointing the server URL at
|
|
345
|
+
`http://localhost:3457/mcp`. Remote clients (including Cursor) discover the
|
|
346
|
+
OAuth endpoints from
|
|
347
|
+
`http://localhost:3457/.well-known/oauth-authorization-server`, open the
|
|
348
|
+
"Sign in with Google" page, and store the resulting access token.
|
|
349
|
+
|
|
350
|
+
## Usage with OpenAI Agents SDK
|
|
351
|
+
|
|
352
|
+
Start the HTTP transport:
|
|
353
|
+
|
|
354
|
+
```bash
|
|
355
|
+
npx @nooticr/mcp --http --port 3457
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
Python:
|
|
359
|
+
|
|
360
|
+
```python
|
|
361
|
+
from agents import Agent, Runner
|
|
362
|
+
from agents.mcp import RemoteMCPClient
|
|
363
|
+
|
|
364
|
+
async def main():
|
|
365
|
+
async with RemoteMCPClient(
|
|
366
|
+
url="http://localhost:3457/mcp",
|
|
367
|
+
auth_provider="oidc", # OAuth flow opens your browser once
|
|
368
|
+
) as client:
|
|
369
|
+
agent = Agent(name="nooticr", mcp_servers=[client])
|
|
370
|
+
result = await Runner.run(
|
|
371
|
+
agent,
|
|
372
|
+
"Analyze this video: https://www.youtube.com/watch?v=dQw4w9WgXcQ",
|
|
373
|
+
)
|
|
374
|
+
print(result.final_output)
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
TypeScript (Agents SDK):
|
|
378
|
+
|
|
379
|
+
```ts
|
|
380
|
+
import { Agent } from "agents";
|
|
381
|
+
import { RemoteMCPClient } from "agents/mcp/client";
|
|
382
|
+
|
|
383
|
+
const client = new RemoteMCPClient({
|
|
384
|
+
url: "http://localhost:3457/mcp",
|
|
385
|
+
authProvider: "oidc", // opens the browser for the OAuth flow
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
const agent = new Agent({
|
|
389
|
+
name: "nooticr",
|
|
390
|
+
mcpServers: [client],
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
const result = await agent.run(
|
|
394
|
+
"Analyze this video: https://vm.tiktok.com/abc123/",
|
|
395
|
+
);
|
|
396
|
+
console.log(result.output);
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
For a local stdio process with the Agents SDK, use `StdioMCPClient` (Python:
|
|
400
|
+
`StdioMCPClient(command="npx", args=["@nooticr/mcp"])`).
|
|
401
|
+
|
|
402
|
+
## Command line
|
|
403
|
+
|
|
404
|
+
```
|
|
405
|
+
nooticr-mcp Start in stdio mode (default transport)
|
|
406
|
+
nooticr-mcp --stdio Same as above
|
|
407
|
+
nooticr-mcp --http [--port N] Start the remote HTTP transport with OAuth (default port 3457)
|
|
408
|
+
nooticr-mcp login Sign in to nooticr via Google in your browser
|
|
409
|
+
nooticr-mcp login --email ... --password ... Password login
|
|
410
|
+
nooticr-mcp --help Show help
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
## Environment variables
|
|
414
|
+
|
|
415
|
+
| Variable | Default | Description |
|
|
416
|
+
|----------|---------|-------------|
|
|
417
|
+
| `NOOTICR_BASE_URL` | `https://api.nooticr.com` | nooticr server base URL (trailing slash stripped) |
|
|
418
|
+
| `NOOTICR_ACCESS_TOKEN` | — | nooticr JWT access token; takes priority over the credentials file |
|
|
419
|
+
| `NOOTICR_CREDENTIALS_FILE` | `~/.config/nooticr-mcp/credentials.json` | token store path |
|
|
420
|
+
| `NOOTICR_PUBLIC_URL` | `http://localhost:3457` | public base URL advertised in OAuth metadata (HTTP mode) |
|
|
421
|
+
| `NOOTICR_PORT` | `3457` | port for `--http` and `login` |
|
|
422
|
+
| `NOOTICR_TRANSPORT` | `stdio` | `stdio` or `http` (same as `--http`) |
|
|
423
|
+
|
|
424
|
+
## How authentication works
|
|
425
|
+
|
|
426
|
+
**stdio mode** (Claude Desktop, Cursor): the server uses the token from
|
|
427
|
+
`NOOTICR_ACCESS_TOKEN` or the credentials file written by `login`. If the token
|
|
428
|
+
is expired it is automatically refreshed with the stored refresh token, and if
|
|
429
|
+
the nooticr API returns `401` the request is retried once after a refresh.
|
|
430
|
+
|
|
431
|
+
**HTTP mode** (OpenAI Agents SDK, remote clients): the server runs its own
|
|
432
|
+
OAuth 2.0 authorization server (Authorization Code + PKCE S256, public client,
|
|
433
|
+
per the MCP 2025-03-26 spec):
|
|
434
|
+
|
|
435
|
+
- `GET /.well-known/oauth-authorization-server` — metadata
|
|
436
|
+
- `GET /authorize` — validates the request (loopback or https redirect URIs)
|
|
437
|
+
and forwards the browser to nooticr's Google sign-in
|
|
438
|
+
- `GET /oauth/callback` — our own loopback callback; exchanges nooticr's
|
|
439
|
+
completion code for nooticr JWTs and redirects back to the MCP client with a
|
|
440
|
+
one-time code
|
|
441
|
+
- `POST /token` — verifies PKCE and issues an opaque Bearer token bound to the
|
|
442
|
+
nooticr session (valid 1 hour)
|
|
443
|
+
- every MCP RPC validates the Bearer token against the session map
|
|
444
|
+
|
|
445
|
+
## Supported URLs
|
|
446
|
+
|
|
447
|
+
- TikTok: `tiktok.com/*`, `vm.tiktok.com/*` (and `www.`/`m.` subdomains)
|
|
448
|
+
- Instagram: `instagram.com/*` (reels, posts, carousels), `instagr.am/*`
|
|
449
|
+
- YouTube: `youtube.com/*` (including `/shorts/`), `youtu.be/*`, `m.youtube.com/*`
|
|
450
|
+
- X: `x.com/*`, `twitter.com/*`
|
|
451
|
+
- Reddit: `reddit.com/*`, `redd.it/*`
|
|
452
|
+
- Weibo: `weibo.com/*`, `weibo.cn/*`
|
|
453
|
+
- Douyin: `douyin.com/*`
|
|
454
|
+
- Xiaohongshu: `xiaohongshu.com/*`, `xhslink.com/*`
|
|
455
|
+
- Bilibili: `bilibili.com/*`, `b23.tv/*`
|
|
456
|
+
- LinkedIn: `linkedin.com/*` (posts, profile URLs)
|
|
457
|
+
|
|
458
|
+
All tools accept these hosts and handle **video, image, carousel, slideshow
|
|
459
|
+
and text** posts.
|
|
460
|
+
|
|
461
|
+
## Troubleshooting
|
|
462
|
+
|
|
463
|
+
- **`Not authenticated with nooticr` / 401**: run `npx @nooticr/mcp login` or set
|
|
464
|
+
`NOOTICR_ACCESS_TOKEN`.
|
|
465
|
+
- **402 paywall / `insufficient MCP credits`**: your nooticr account is out of
|
|
466
|
+
credits. Prices are listed per tool in [Tools](#tools) — 1 credit for a post
|
|
467
|
+
lookup or transcript, 2 for discovery and for a tool that makes one fetch, 3
|
|
468
|
+
for the two that fetch frames *and* transcript. Every tool bills from the
|
|
469
|
+
first call. Top up via
|
|
470
|
+
`buy_nooticr_credits`, `check_nooticr_credits`, or the nooticr dashboard at
|
|
471
|
+
`https://nooticr.com/settings?tab=billing`.
|
|
472
|
+
- **Expired refresh token**: the stored refresh token was rejected by the
|
|
473
|
+
nooticr server. Run `npx @nooticr/mcp login` again to re-authenticate.
|
|
474
|
+
- **`Could not reach the nooticr server`**: `NOOTICR_BASE_URL` is unreachable or
|
|
475
|
+
wrong.
|
|
476
|
+
- **Client shows "Bad Request" or connection errors in HTTP mode**: make sure
|
|
477
|
+
the port matches `NOOTICR_PUBLIC_URL` and that the client fetched a token
|
|
478
|
+
first (the OAuth flow must complete once in your browser).
|
|
479
|
+
|
|
480
|
+
## Platform submissions
|
|
481
|
+
|
|
482
|
+
See [docs/SUBMISSION.md](docs/SUBMISSION.md) for ready-to-paste configs and the submission
|
|
483
|
+
package for **Claude Desktop**, **Cursor**, and **OpenAI Agents SDK**.
|
|
484
|
+
|
|
485
|
+
## Security notes
|
|
486
|
+
|
|
487
|
+
- The credentials file is written with mode `0600` and the directory with
|
|
488
|
+
`0700`.
|
|
489
|
+
- OAuth redirect URIs are restricted to loopback (`http://localhost`,
|
|
490
|
+
`http://127.0.0.1`, `http://[::1]`) or `https://` URLs; the `/authorize`
|
|
491
|
+
endpoint requires PKCE (`S256`).
|
|
492
|
+
- Authorization codes and PKCE challenges are one-time use and short-lived
|
|
493
|
+
(in-memory).
|
|
494
|
+
- Access tokens are opaque, random, and bound to the in-memory session map;
|
|
495
|
+
they expire after 1 hour. Restarting the server invalidates all sessions.
|
|
496
|
+
- Never share your credentials file or `NOOTICR_ACCESS_TOKEN`.
|
|
497
|
+
|
|
498
|
+
## Development
|
|
499
|
+
|
|
500
|
+
```bash
|
|
501
|
+
npm install
|
|
502
|
+
npm run build # tsc
|
|
503
|
+
npm test # vitest (66 unit tests, mocked fetch — no network)
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
Those are unit tests against a stub backend. For real MCP-protocol
|
|
507
|
+
end-to-end tests — no Rust, no Postgres, no API key —
|
|
508
|
+
`npm run test:e2e-smoke:fixture` spawns this repo's real built CLI and
|
|
509
|
+
drives it over stdio against a pure-Node stand-in for nooticr-server. See
|
|
510
|
+
`docs/testing/agentic-e2e-testing.md` for that plus the two tiers above it
|
|
511
|
+
(`npm run test:e2e-smoke` against a real nooticr-server, and
|
|
512
|
+
`npm run test:agentic-e2e`, a real model driving it), and for
|
|
513
|
+
`tests/e2e/agentic-visual*.e2e.ts` — every reachable widget view, driven by
|
|
514
|
+
a real tool-call result, rendered in a real browser with every host-facing
|
|
515
|
+
button actually clicked (`npx playwright test tests/e2e/agentic-visual.e2e.ts
|
|
516
|
+
tests/e2e/agentic-visual-full-app.e2e.ts`). That exercise found six real
|
|
517
|
+
product bugs, documented in the same doc.
|
|
518
|
+
|
|
519
|
+
## License
|
|
520
|
+
|
|
521
|
+
MIT
|