@nooticr/mcp 1.26.56 → 1.26.57
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/README.md +5 -1
- package/dist/index.js +22 -4
- package/dist/shared/amazon.js +13 -1
- package/dist/shared/backend-only.js +4 -0
- package/dist/shared/connections.js +9 -2
- package/dist/shared/jobs.js +7 -9
- package/dist/shared/marketplace.js +20 -1
- package/dist/shared/output-schemas.js +2 -2
- package/dist/shared/tools-def.js +3 -3
- package/dist/shared/tools.js +1 -1
- package/dist/shared/ui-template.js +1 -1
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -113,7 +113,7 @@ them caps that fan-out with an argument.
|
|
|
113
113
|
|
|
114
114
|
| Tool | Credits | What it is for |
|
|
115
115
|
|------|---------|----------------|
|
|
116
|
-
| `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
|
|
116
|
+
| `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. This tool never sends anything; a reply is posted only if you then ask for it, on your connected account, after you confirm the exact text. `limit` caps how many posts are opened, which is the price. |
|
|
117
117
|
| `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. |
|
|
118
118
|
| `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. |
|
|
119
119
|
| `show_standings` | free | Draws the standings your model read out of `compare_creators` or `watchlist_standings` — one row per creator, with the window and the median it was scored against. `ranking` records which axis you ordered on, and `tooThin` draws a creator with too short a window as unranked rather than last: missing from a comparison is not the same as losing it. Makes no requests. |
|
|
@@ -638,6 +638,10 @@ and text** posts.
|
|
|
638
638
|
See [docs/SUBMISSION.md](docs/SUBMISSION.md) for ready-to-paste configs and the submission
|
|
639
639
|
package for **Claude Desktop**, **Cursor**, and **OpenAI Agents SDK**.
|
|
640
640
|
|
|
641
|
+
The official MCP registry entry `com.nooticr/mcp` must equal `server.json`'s version after each
|
|
642
|
+
release; see [docs/registry-publishing.md](docs/registry-publishing.md) for the drift check
|
|
643
|
+
(`npm run registry:check`) and the publish script (`npm run registry:publish`).
|
|
644
|
+
|
|
641
645
|
## Security notes
|
|
642
646
|
|
|
643
647
|
- The credentials file is written with mode `0600` and the directory with
|
package/dist/index.js
CHANGED
|
@@ -414,6 +414,23 @@ export async function runApiKey(opts) {
|
|
|
414
414
|
"the key it returns is what every run afterwards uses.");
|
|
415
415
|
}
|
|
416
416
|
const client = new NooticrClient(baseUrl, createManagementTokenProvider(auth));
|
|
417
|
+
// Who, and where. A key is scoped to one account on one server, and both
|
|
418
|
+
// are easy to get wrong without noticing: the credentials file holds
|
|
419
|
+
// whoever last ran `login` on this machine, and NOOTICR_BASE_URL defaults
|
|
420
|
+
// to production. A key minted against one pair and looked for in another
|
|
421
|
+
// is simply absent, with nothing anywhere saying why — which is exactly how
|
|
422
|
+
// this was first reported.
|
|
423
|
+
let account = "an unknown account";
|
|
424
|
+
try {
|
|
425
|
+
const me = await client.me();
|
|
426
|
+
if (me?.email)
|
|
427
|
+
account = me.email;
|
|
428
|
+
}
|
|
429
|
+
catch {
|
|
430
|
+
// Not worth failing the command over; the operation below will fail with
|
|
431
|
+
// a better message if the credential is genuinely bad.
|
|
432
|
+
}
|
|
433
|
+
const scope = `${account} on ${baseUrl}`;
|
|
417
434
|
if (opts.action === "create") {
|
|
418
435
|
const created = await client.createApiKey({
|
|
419
436
|
...(opts.name !== undefined ? { name: opts.name } : {}),
|
|
@@ -424,7 +441,7 @@ export async function runApiKey(opts) {
|
|
|
424
441
|
process.stdout.write(`${JSON.stringify(created, null, 2)}\n`);
|
|
425
442
|
return;
|
|
426
443
|
}
|
|
427
|
-
process.stdout.write(`Created API key "${created.name}" (${created.id}).\n\n` +
|
|
444
|
+
process.stdout.write(`Created API key "${created.name}" (${created.id}) for ${scope}.\n\n` +
|
|
428
445
|
` ${created.key}\n\n` +
|
|
429
446
|
"This is the only time the key is shown — nooticr stores a hash of it and\n" +
|
|
430
447
|
"cannot show it again. Store it wherever this deployment keeps its secrets,\n" +
|
|
@@ -441,10 +458,11 @@ export async function runApiKey(opts) {
|
|
|
441
458
|
return;
|
|
442
459
|
}
|
|
443
460
|
if (keys.length === 0) {
|
|
444
|
-
process.stdout.write(
|
|
461
|
+
process.stdout.write(`No API keys for ${scope}.\n` +
|
|
462
|
+
"Create one with `npx nooticr-mcp api-key create --name <name>`.\n");
|
|
445
463
|
return;
|
|
446
464
|
}
|
|
447
|
-
process.stdout.write(
|
|
465
|
+
process.stdout.write(`Keys for ${scope}:\n\n${keys.map(describeKey).join("\n\n")}\n`);
|
|
448
466
|
return;
|
|
449
467
|
}
|
|
450
468
|
if (!opts.id) {
|
|
@@ -455,7 +473,7 @@ export async function runApiKey(opts) {
|
|
|
455
473
|
process.stdout.write(`${JSON.stringify({ id: opts.id, revoked: true }, null, 2)}\n`);
|
|
456
474
|
return;
|
|
457
475
|
}
|
|
458
|
-
process.stdout.write(`Revoked ${opts.id}. Any server still presenting it now gets a 401.\n`);
|
|
476
|
+
process.stdout.write(`Revoked ${opts.id} for ${scope}. Any server still presenting it now gets a 401.\n`);
|
|
459
477
|
}
|
|
460
478
|
// ---------------------------------------------------------------------------
|
|
461
479
|
// CLI
|
package/dist/shared/amazon.js
CHANGED
|
@@ -177,7 +177,15 @@ export function categoryGuidance(opts) {
|
|
|
177
177
|
// commonest reason is a query in a language that storefront does not sell
|
|
178
178
|
// in. Said here rather than only in the tool description because by the time
|
|
179
179
|
// this is read the description is long out of the model's attention.
|
|
180
|
-
if (opts.
|
|
180
|
+
if (opts.status === "degraded") {
|
|
181
|
+
lines.push(`DEGRADED SCAN: ${site} could not be read properly right now, so this is not a finding ` +
|
|
182
|
+
`about the category and not evidence that nothing sells there. ` +
|
|
183
|
+
(opts.notice
|
|
184
|
+
? `Tell the user this, verbatim: "${opts.notice}". `
|
|
185
|
+
: `Tell the user the site is currently unavailable. `) +
|
|
186
|
+
`Do not retry with other queries and do not call the category empty.`);
|
|
187
|
+
}
|
|
188
|
+
else if (opts.products === 0 && opts.complete) {
|
|
181
189
|
lines.push(`No listings came back. This is not a finding about the category — it is a search that ` +
|
|
182
190
|
`matched nothing. Before reporting it: check the query is in the language that ` +
|
|
183
191
|
`storefront sells in, try the common shopper's word for the thing rather than a ` +
|
|
@@ -190,6 +198,10 @@ export function categoryGuidance(opts) {
|
|
|
190
198
|
// caveat changes what it writes.
|
|
191
199
|
if (opts.freshness)
|
|
192
200
|
lines.push(opts.freshness);
|
|
201
|
+
if (opts.siteStatus && opts.status !== "degraded") {
|
|
202
|
+
lines.push(`Caveat: the ${site} collector is currently ${opts.siteStatus}, so this data may be ` +
|
|
203
|
+
`partial. Say so when you report it.`);
|
|
204
|
+
}
|
|
193
205
|
if (polling) {
|
|
194
206
|
lines.push(`Stop polling only if ${statusTool} reports status "failed", or returns the same number of ` +
|
|
195
207
|
`listings three calls in a row. Then write the read from what was collected and say plainly ` +
|
|
@@ -20,5 +20,9 @@ export const BACKEND_ONLY_TOOLS = {
|
|
|
20
20
|
schedule_post: "Rendering and publishing stay in the dashboard (#92).",
|
|
21
21
|
get_batch_history: "History of the dashboard's compose batches, which only exist once rendering does; stays with #92.",
|
|
22
22
|
get_latest_analysis: "Returns the dashboard's stored AI fleet-analysis verdict; the public surface sells the evidence, not a judgement of ours.",
|
|
23
|
+
propose_listing_update: "Commerce writes (nooticr-server C1 3/4) stay in the dashboard until exposing store writes to hosts is a product decision. connect_store and list_store_products, the C1 read pair, are undecided and want settling together with these.",
|
|
24
|
+
apply_listing_update: "Commerce writes (nooticr-server C1 3/4) stay in the dashboard until exposing store writes to hosts is a product decision; applying a change edits the merchant's live store.",
|
|
25
|
+
propose_price_change: "Commerce writes (nooticr-server C1 3/4) stay in the dashboard until exposing store writes to hosts is a product decision.",
|
|
26
|
+
apply_price_change: "Commerce writes (nooticr-server C1 3/4) stay in the dashboard until exposing store writes to hosts is a product decision; applying a change edits the merchant's live prices.",
|
|
23
27
|
brand_watch_history: "Not a deliberate exclusion: a candidate for the public surface once it has a trend view to draw. Listed so it reads as pending, not forgotten.",
|
|
24
28
|
};
|
|
@@ -68,20 +68,27 @@ export function registerConnectionTools(server, makeClient) {
|
|
|
68
68
|
"or product — omit both and this picks the only one if there is just one, or lists the " +
|
|
69
69
|
"choices if there is more than one. The user approves at the provider; nothing is " +
|
|
70
70
|
"connected until they do, and no credential ever passes through this tool. Each call " +
|
|
71
|
-
"mints a fresh link, so do not reuse an old one.
|
|
71
|
+
"mints a fresh link, so do not reuse an old one. For YouTube, pass capabilities " +
|
|
72
|
+
"[\"manage_comments\"] to also ask for the comment-management grant. No cost to call.",
|
|
72
73
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
73
74
|
inputSchema: z
|
|
74
75
|
.object({
|
|
75
76
|
platform: z.string().describe("Which network to connect, e.g. tiktok, youtube."),
|
|
76
77
|
influencerId: z.number().int().optional().describe("Which creator profile — call list_own_apps or list_social_connections first if unsure."),
|
|
77
78
|
appId: z.number().int().optional().describe("Which product this connection is for."),
|
|
79
|
+
capabilities: z
|
|
80
|
+
.array(z.enum(["manage_comments"]))
|
|
81
|
+
.optional()
|
|
82
|
+
.describe("Extra access to ask for. YouTube only: [\"manage_comments\"] asks for the comment-management grant. Omit for the default."),
|
|
78
83
|
})
|
|
79
84
|
.strict(),
|
|
80
85
|
outputSchema: OUTPUT_SCHEMAS.connect_social_account,
|
|
81
86
|
}, async (args, extra) => {
|
|
82
87
|
const client = await makeClient({ ...extra, arguments: args });
|
|
83
88
|
try {
|
|
84
|
-
|
|
89
|
+
// Forward capabilities only when supplied, so a plain call is unchanged.
|
|
90
|
+
const { capabilities, ...rest } = args;
|
|
91
|
+
return toResult(await client.callTool("connect_social_account", capabilities ? { ...rest, capabilities } : rest));
|
|
85
92
|
}
|
|
86
93
|
catch (err) {
|
|
87
94
|
return failed("connect_social_account failed", err);
|
package/dist/shared/jobs.js
CHANGED
|
@@ -320,9 +320,8 @@ function audienceGuidance(a) {
|
|
|
320
320
|
"ones asking the same thing: a single pinned reply beats twenty individual ones, and a " +
|
|
321
321
|
"question that recurs across posts is a video rather than a reply.",
|
|
322
322
|
"",
|
|
323
|
-
"You are drafting, not sending.
|
|
324
|
-
"
|
|
325
|
-
"for the creator to paste in themselves. Say that rather than implying the replies will go out.",
|
|
323
|
+
"You are drafting, not sending. this tool cannot post: nothing is sent from here, and a reply is posted only " +
|
|
324
|
+
"after the creator confirms its exact text. Say that rather than implying the replies will go out.",
|
|
326
325
|
"",
|
|
327
326
|
"Do not invent facts about the product, the price or the creator's plans to fill a gap. If a " +
|
|
328
327
|
"comment cannot be answered from what is here, say what you would need.",
|
|
@@ -633,9 +632,8 @@ export function registerJobTools(server, makeClient, store) {
|
|
|
633
632
|
"reads the comments on each, and returns them grouped under the post they were left on — " +
|
|
634
633
|
"every comment with a stable id, and the ones that look like questions or requests " +
|
|
635
634
|
"flagged and sorted to the top. This FINDS and helps you DRAFT answers; it cannot post " +
|
|
636
|
-
"them:
|
|
637
|
-
"
|
|
638
|
-
"in themselves — never promise the user they will be sent. `since` filters on the POST's date, not the comments'. `limit` caps how many " +
|
|
635
|
+
"them: this tool sends nothing, and a reply is only ever posted after the user confirms its " +
|
|
636
|
+
"exact text — never promise the user a reply will go out on its own. `since` filters on the POST's date, not the comments'. `limit` caps how many " +
|
|
639
637
|
"posts are opened. Pair it with show_audience_replies to lay the drafts out for triage. " +
|
|
640
638
|
"Costs 2 nooticr credits for the post list plus 2 per post opened — 14 credits at the " +
|
|
641
639
|
"default of 6 posts. Use when the job is to answer people; search_mentions is for what " +
|
|
@@ -1631,8 +1629,8 @@ export function registerJobTools(server, makeClient, store) {
|
|
|
1631
1629
|
_meta: viewMeta("show_audience_replies"),
|
|
1632
1630
|
description: "Lay out the replies you drafted from answer_my_audience so a person can read them. " +
|
|
1633
1631
|
"Free, and makes no requests — it only draws what you pass it. It does NOT send " +
|
|
1634
|
-
"anything:
|
|
1635
|
-
"
|
|
1632
|
+
"anything: each row is a draft, and a reply is posted only after the creator confirms its " +
|
|
1633
|
+
"exact text. Each comment shows with what you decided to do " +
|
|
1636
1634
|
"about it and your draft underneath, grouped under its post, so they can work through " +
|
|
1637
1635
|
"them one at a time and skip the rest. Call this after you have drafted the replies, not " +
|
|
1638
1636
|
"instead of drafting them.",
|
|
@@ -1660,7 +1658,7 @@ export function registerJobTools(server, makeClient, store) {
|
|
|
1660
1658
|
draft: z
|
|
1661
1659
|
.string()
|
|
1662
1660
|
.optional()
|
|
1663
|
-
.describe("The reply, in the creator's voice
|
|
1661
|
+
.describe("The reply, in the creator's voice. It is only a draft: nothing is posted until the creator confirms its exact text."),
|
|
1664
1662
|
kind: z
|
|
1665
1663
|
.enum(["answer", "pin", "make_a_video", "escalate", "ignore"])
|
|
1666
1664
|
.optional()
|
|
@@ -164,6 +164,17 @@ function toolError(what, err) {
|
|
|
164
164
|
return { isError: true, content: [{ type: "text", text: `${what}: ${message}` }] };
|
|
165
165
|
}
|
|
166
166
|
const num = (v) => (typeof v === "number" && Number.isFinite(v) ? v : null);
|
|
167
|
+
/** `siteStatus` is `{status, since, note}` from the server; a bare string is accepted too. */
|
|
168
|
+
function siteStatusName(v) {
|
|
169
|
+
if (typeof v === "string")
|
|
170
|
+
return v;
|
|
171
|
+
if (v && typeof v === "object") {
|
|
172
|
+
const status = v.status;
|
|
173
|
+
if (typeof status === "string")
|
|
174
|
+
return status;
|
|
175
|
+
}
|
|
176
|
+
return undefined;
|
|
177
|
+
}
|
|
167
178
|
/**
|
|
168
179
|
* The scan, in the shape the view and the digest read.
|
|
169
180
|
*
|
|
@@ -172,7 +183,7 @@ const num = (v) => (typeof v === "number" && Number.isFinite(v) ? v : null);
|
|
|
172
183
|
* `market`, because each collector calls it something different and a caller
|
|
173
184
|
* should not have to know which.
|
|
174
185
|
*/
|
|
175
|
-
function marketplaceResult(payload, m, focus) {
|
|
186
|
+
export function marketplaceResult(payload, m, focus) {
|
|
176
187
|
const products = normaliseProducts(payload.products);
|
|
177
188
|
const rollup = (payload.rollup ?? {});
|
|
178
189
|
const reviews = allReviews(products);
|
|
@@ -198,6 +209,9 @@ function marketplaceResult(payload, m, focus) {
|
|
|
198
209
|
statusTool: "marketplace_scan_status",
|
|
199
210
|
statusArgs: { marketplace: m.slug },
|
|
200
211
|
status: typeof payload.status === "string" ? payload.status : undefined,
|
|
212
|
+
notice: typeof payload.notice === "string" ? payload.notice : undefined,
|
|
213
|
+
// The server sends `{status, since, note}`; a bare string is accepted too.
|
|
214
|
+
siteStatus: siteStatusName(payload.siteStatus),
|
|
201
215
|
total: num(progress.total) ?? undefined,
|
|
202
216
|
// Its own view, labelled with this site (#95): pointing a model at
|
|
203
217
|
// show_amazon_category_insights would have it draw a Lazada scan as an
|
|
@@ -226,6 +240,11 @@ function marketplaceResult(payload, m, focus) {
|
|
|
226
240
|
reviews,
|
|
227
241
|
errors: payload.errors ?? [],
|
|
228
242
|
mcpCredits: payload.mcpCredits ?? null,
|
|
243
|
+
// Degraded-scan fields (nooticr-server#239); absent on an older server.
|
|
244
|
+
notice: payload.notice ?? null,
|
|
245
|
+
quality: payload.quality ?? null,
|
|
246
|
+
collectorStatus: payload.collectorStatus ?? null,
|
|
247
|
+
siteStatus: payload.siteStatus ?? null,
|
|
229
248
|
};
|
|
230
249
|
return {
|
|
231
250
|
content: [{ type: "text", text: pollTail(withEvidence(guidance, structuredPayload), structuredPayload.nextCall) }],
|
|
@@ -640,8 +640,8 @@ export const OUTPUT_SCHEMAS = {
|
|
|
640
640
|
since: scalar().describe("The date window asked for, if any."),
|
|
641
641
|
sinceApplied: scalar().describe("False when the platform returned no dates, so the window could not be honoured."),
|
|
642
642
|
postsChecked: scalar().describe("Posts whose comments were actually read."),
|
|
643
|
-
repliesCanBeSent: scalar().describe("Always false.
|
|
644
|
-
"
|
|
643
|
+
repliesCanBeSent: scalar().describe("Always false. This tool only drafts and never sends; a reply is posted only by a separate step, " +
|
|
644
|
+
"on the user's connected account, after the user confirms the exact text."),
|
|
645
645
|
totalMentions: scalar().describe("Comments returned across every group."),
|
|
646
646
|
totalThreads: scalar(),
|
|
647
647
|
wantsReplyCount: scalar().describe("How many carry a reply signal. A sort, not a verdict."),
|
package/dist/shared/tools-def.js
CHANGED
|
@@ -399,13 +399,13 @@ export const TOOL_DEFINITIONS = [
|
|
|
399
399
|
{
|
|
400
400
|
name: "connect_social_account",
|
|
401
401
|
title: "Connect Social Account",
|
|
402
|
-
description: "Get a link to open so you can connect one social account. The user approves at the provider; nothing is connected until they do, and no credential ever passes through this tool. No cost to call.",
|
|
403
|
-
inputSchema: z.object({ platform: z.string().describe("Which network to connect."), influencerId: z.number().int().optional(), appId: z.number().int().optional() }).strict(),
|
|
402
|
+
description: "Get a link to open so you can connect one social account. The user approves at the provider; nothing is connected until they do, and no credential ever passes through this tool. For YouTube, pass capabilities [\"manage_comments\"] to also ask for the comment-management grant. No cost to call.",
|
|
403
|
+
inputSchema: z.object({ platform: z.string().describe("Which network to connect."), influencerId: z.number().int().optional(), appId: z.number().int().optional(), capabilities: z.array(z.enum(["manage_comments"])).optional().describe("YouTube only: [\"manage_comments\"] asks for the comment-management grant.") }).strict(),
|
|
404
404
|
},
|
|
405
405
|
{
|
|
406
406
|
name: "answer_my_audience",
|
|
407
407
|
title: "Answer My Audience",
|
|
408
|
-
description: "The questions waiting for you under your own posts. Fetches a creator's recent posts, reads the comments on each, and returns them grouped under the post they were left on \u2014 every comment with a stable id, and the ones that read like questions or requests flagged and sorted to the top. Finds and drafts;
|
|
408
|
+
description: "The questions waiting for you under your own posts. Fetches a creator's recent posts, reads the comments on each, and returns them grouped under the post they were left on \u2014 every comment with a stable id, and the ones that read like questions or requests flagged and sorted to the top. Finds and drafts; this tool cannot post a reply \u2014 a reply is posted only after the user confirms its exact text. Use when the job is to answer your own audience rather than to read about strangers. Consumes 2 nooticr credits for the post list plus 2 per post opened \u2014 14 credits at the default of 6 posts.",
|
|
409
409
|
inputSchema: z.object({ username: z.string().describe("Your handle, with or without @."), platform: z.string().optional().describe("Platform (default tiktok)."), limit: z.number().int().optional().describe("Posts to open (default 6, max 12). Each one is a comment fetch, so this is the price."), commentsPerPost: z.number().int().optional().describe("Comments to read per post (default 20, max 50)."), since: z.string().optional().describe("Only posts published on or after this date, as YYYY-MM-DD. Windows the posts, not the comments.") }).strict(),
|
|
410
410
|
},
|
|
411
411
|
{
|
package/dist/shared/tools.js
CHANGED
|
@@ -31,7 +31,7 @@ import { registerAmazonTools } from "./amazon.js";
|
|
|
31
31
|
import { registerMarketplaceTools } from "./marketplace.js";
|
|
32
32
|
import { DEFAULT_NOOTICR_SETTINGS, DEFAULT_SOCIAL_PLATFORMS, MemorySettingsStore, SETTINGS_READ_TOOL, SETTINGS_UPDATE_TOOL, settingsOwnerFromAuth, } from "./settings.js";
|
|
33
33
|
/** Current MCP server version — bumped on every deploy for traceability. */
|
|
34
|
-
export const MCP_SERVER_VERSION = "1.26.
|
|
34
|
+
export const MCP_SERVER_VERSION = "1.26.57";
|
|
35
35
|
/** MCP Apps extension identifier */
|
|
36
36
|
const UI_EXTENSION = "io.modelcontextprotocol/ui";
|
|
37
37
|
/** MIME type for MCP Apps HTML resources */
|
|
@@ -4411,7 +4411,7 @@ export const NOOTICR_UI_TEMPLATE = `<!DOCTYPE html>
|
|
|
4411
4411
|
+'<div class="nt-commerce-chiprow">'+readOnly.map(function(k){
|
|
4412
4412
|
return '<span class="nt-tag nt-commerce-nettag tag">'+glyph(k,12)+esc(pTitle(k))+"</span>";}).join("")+"</div>"
|
|
4413
4413
|
+'<p class="nt-commerce-conn-sub" style="margin-top:8px">So "monitor my brand on Weibo" works and "reply to that Weibo comment" cannot — '
|
|
4414
|
-
+"
|
|
4414
|
+
+"replies are never sent on their own: one is posted only after you confirm its exact text.</p></div>":""),
|
|
4415
4415
|
footer:'<span class="ai-note">Each press mints a fresh link — an old one does not work twice. Approve in your own browser; nothing is connected until you do.</span>'});
|
|
4416
4416
|
}
|
|
4417
4417
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nooticr/mcp",
|
|
3
3
|
"mcpName": "com.nooticr/mcp",
|
|
4
|
-
"version": "1.26.
|
|
4
|
+
"version": "1.26.57",
|
|
5
5
|
"description": "MCP server for nooticr - fetch, discover and understand TikTok/Instagram/YouTube/X/Reddit/LinkedIn/Weibo posts with AI (media metadata, niche discovery, brand monitoring, hook & viral analysis)",
|
|
6
6
|
"homepage": "https://mcp.nooticr.com",
|
|
7
7
|
"repository": {
|
|
@@ -46,7 +46,9 @@
|
|
|
46
46
|
"test:mutation": "node scripts/mutation-guard.mjs",
|
|
47
47
|
"test:invariants": "node scripts/invariant-guard.mjs",
|
|
48
48
|
"verify:deep": "npm run verify && npm run test:mutation && npm run test:invariants",
|
|
49
|
-
"check:submission": "node scripts/check-submission-schema.mjs"
|
|
49
|
+
"check:submission": "node scripts/check-submission-schema.mjs",
|
|
50
|
+
"registry:check": "node scripts/registry-drift.mjs",
|
|
51
|
+
"registry:publish": "bash scripts/publish-registry.sh"
|
|
50
52
|
},
|
|
51
53
|
"keywords": [
|
|
52
54
|
"mcp",
|