@nooticr/mcp 1.26.67 → 1.26.69

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 CHANGED
@@ -52,7 +52,7 @@ npx @nooticr/mcp login # one-time sign-in (Google)
52
52
 
53
53
  ## Tools
54
54
 
55
- 114 tools, grouped by what you are trying to do. Prices are in nooticr credits and
55
+ 117 tools, grouped by what you are trying to do. Prices are in nooticr credits and
56
56
  match what the server actually charges.
57
57
 
58
58
  The job tools under **Answer a question you actually have** are not
@@ -127,8 +127,11 @@ them caps that fan-out with an argument.
127
127
 
128
128
  | Tool | Credits | What it is for |
129
129
  |------|---------|----------------|
130
- | `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. |
130
+ | `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 itself: the drafts are for a person to paste in, or to send through `draft_comment_reply` and `post_comment_reply` once the text is confirmed. `limit` caps how many posts are opened, which is the price. |
131
131
  | `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. |
132
+ | `list_comments_to_answer` | free | Recent comments on your *own* connected account that nobody has answered yet, newest first (YouTube once connected with `capabilities: ["manage_comments"]`; other networks come back under `unsupported` with the reason). Read-only. |
133
+ | `draft_comment_reply` | free with `text`, 1 when nooticr writes it | Stores a reply to one comment; nothing is posted. Returns a `draftId` and a `confirmationToken` to show the user first. |
134
+ | `post_comment_reply` | free | Posts a stored draft as your own account. Needs the `draftId`, `confirm: true` and the `confirmationToken`, and only after the user has agreed to the exact text. Each comment is answered at most once. |
132
135
  | `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. |
133
136
  | `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. |
134
137
  | `show_trend` | free | Draws the trend your model read out of `mention_trend`. `tooShort` marks a series with too few points to call a direction, and `edgeIsRecordStart` marks a left edge that is where the record begins rather than where the conversation did — a chart hiding either is the mistake this view could otherwise make on your behalf. Makes no requests. |
@@ -194,7 +197,7 @@ a `scanId` for the rest.
194
197
  | `get_tool_run` | free | One run from that history by id, with its full error. |
195
198
  | `get_usage_report` | free | The run ledger added up over a window: total calls, failures and credits actually taken, per tool, per day and the most recent failures. `scope: "workspace"` adds one row per seat, for the workspace's owners and admins. |
196
199
  | `nooticr_login` | free | Re-link the account when a call fails with an authentication error. |
197
- | `list_social_connections` | free | What you have connected and what each connection is actually allowed to do — read the account, publish a post, manage comments — as yes, no or **unknown**. "Manage comments" reports the platform's grant; no nooticr tool posts or replies to a comment. Unknown means the grant predates scope recording: treat it as "try it", not as a refusal. Also says which platforms can be linked at all, which is a smaller set than the networks nooticr reads. |
200
+ | `list_social_connections` | free | What you have connected and what each connection is actually allowed to do — read the account, publish a post, manage comments — as yes, no or **unknown**. "Manage comments" reports the platform's grant; this tool posts nothing; a reply goes out only through `post_comment_reply`, on a draft the user confirmed. Unknown means the grant predates scope recording: treat it as "try it", not as a refusal. Also says which platforms can be linked at all, which is a smaller set than the networks nooticr reads. |
198
201
  | `connect_social_account` | free | A link the user opens to connect one account. They approve at the provider in their own browser; nothing is connected until they do, and no credential passes through the tool or the model's context. Each call mints a fresh link — an old one does not work twice. |
199
202
  | `hire_employee` | free to call | Hire an AI employee (for example a Social Media Manager) that works on a schedule inside your workspace. Two calls by design: the first creates nothing and returns a quote and a `confirmationToken`; call again with `confirm: true` and that token only after you say yes. Public actions wait for your approval unless autopilot is on. Its runs bill the hiring user, up to the monthly budget. |
200
203
  | `list_employees` | free | Your AI employees with status, schedule, next run and budget left; `includeFired: true` adds the ones let go. |
@@ -45,8 +45,9 @@ export function registerConnectionTools(server, makeClient) {
45
45
  "no, or unknown; unknown means the grant predates scope recording, so treat it as \"try " +
46
46
  "it\", not as a refusal. Also returns which platforms can be connected at all, which is a " +
47
47
  "smaller set than the networks nooticr can read. \"Manage comments\" reports what the " +
48
- "platform granted, not something nooticr does: no nooticr tool posts, replies to or " +
49
- "moderates a comment, so a yes there never means a drafted reply will be sent. Call this " +
48
+ "platform granted, not something this tool does: this tool posts nothing, and a yes there " +
49
+ "never means a drafted reply will be sent by itself. A reply goes out only through " +
50
+ "post_comment_reply, on a draft the user confirmed. Call this " +
50
51
  "before promising that something can be posted or replied to. No cost to call.",
51
52
  annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
52
53
  inputSchema: z.object({}).strict(),
@@ -799,8 +799,8 @@ export function registerJobTools(server, makeClient, store) {
799
799
  postsChecked: threads.length,
800
800
  // Stated in the payload as well as the prose, because a model that
801
801
  // believes it can send will promise the user something that cannot
802
- // happen. Some grants do allow managing comments (#29); nooticr has no
803
- // send path whatever the grant says.
802
+ // happen. This payload comes from the drafting tools, which never post;
803
+ // only post_comment_reply sends, and only a confirmed draft.
804
804
  repliesCanBeSent: false,
805
805
  totalMentions: totalComments,
806
806
  totalThreads: threads.length,
@@ -537,6 +537,15 @@ export const LOADING_PLANS = {
537
537
  list_watchlist: { label: "Reading your watchlist", kind: "text", n: 1, steps: [], free: true },
538
538
  list_social_connections: { label: "Reading your connections", kind: "list", n: 3, steps: [], free: true },
539
539
  connect_social_account: { label: "Minting a fresh link", kind: "text", n: 1, steps: [], free: true },
540
+ list_comments_to_answer: { label: "Reading the comments waiting for a reply", kind: "list", n: 5, steps: [], free: true },
541
+ // Free when the caller passes `text`; 1 credit when the server writes the reply.
542
+ draft_comment_reply: {
543
+ label: "Drafting the reply",
544
+ kind: "text",
545
+ n: 1,
546
+ steps: [{ via: "draft_comment_reply", label: "Writing the reply", detail: "Free when you pass the text yourself", credits: 1 }],
547
+ },
548
+ post_comment_reply: { label: "Posting the reply", kind: "text", n: 1, steps: [], free: true },
540
549
  create_product: { label: "Writing the row", kind: "text", n: 1, steps: [], free: true },
541
550
  update_product: { label: "Patching the row", kind: "text", n: 1, steps: [], free: true },
542
551
  };
@@ -640,8 +640,9 @@ 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. 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."),
643
+ repliesCanBeSent: scalar().describe("Always false: answer_my_audience posts nothing, so the drafts this produces are for a person to " +
644
+ "paste in themselves, or to send through draft_comment_reply and post_comment_reply after " +
645
+ "confirming the text."),
645
646
  totalMentions: scalar().describe("Comments returned across every group."),
646
647
  totalThreads: scalar(),
647
648
  wantsReplyCount: scalar().describe("How many carry a reply signal. A sort, not a verdict."),
@@ -1696,6 +1697,39 @@ export const OUTPUT_SCHEMAS = {
1696
1697
  connections: listOf(anyObject()),
1697
1698
  message: scalar(),
1698
1699
  }),
1700
+ list_comments_to_answer: open({
1701
+ account: open({
1702
+ platform: scalar(),
1703
+ username: scalar(),
1704
+ influencerId: scalar(),
1705
+ canManageComments: scalar(),
1706
+ manageCommentsStatus: scalar(),
1707
+ }).nullish(),
1708
+ comments: listOf(open({})),
1709
+ unsupported: listOf(open({ platform: scalar(), reason: scalar() })),
1710
+ note: scalar(),
1711
+ message: scalar(),
1712
+ }),
1713
+ draft_comment_reply: open({
1714
+ draftId: scalar(),
1715
+ reply: scalar().describe("The stored reply text. Show it to the user before posting."),
1716
+ comment: open({ author: scalar(), snippet: scalar() }).nullish(),
1717
+ postUrl: scalar(),
1718
+ status: scalar(),
1719
+ expiresInSeconds: scalar(),
1720
+ confirmationToken: scalar().describe("Pass to post_comment_reply with confirm: true after the user agrees."),
1721
+ rejected: scalar(),
1722
+ instructions: scalar(),
1723
+ mcpCredits: open({ cost: scalar(), balance: scalar() }).nullish(),
1724
+ }),
1725
+ post_comment_reply: open({
1726
+ posted: scalar(),
1727
+ alreadyPosted: scalar(),
1728
+ draftId: scalar(),
1729
+ platformReplyId: scalar(),
1730
+ url: scalar(),
1731
+ postedAt: scalar(),
1732
+ }),
1699
1733
  // Marketplace. One shape for all three collection tools: a status poll
1700
1734
  // returns exactly what the scan that started it returns, and a single
1701
1735
  // product is a scan of one — describing them differently would be three
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Replying to comments on the user's own posts (nooticr-server R1).
3
+ *
4
+ * Three tools, in the order a reply travels: list what is waiting, draft one
5
+ * reply (stored, nothing sent), post it. The first two never write to a
6
+ * network. `post_comment_reply` does, as the user's own account, so it needs
7
+ * the exact draft the user saw: the draftId, `confirm: true` and the
8
+ * confirmationToken that `draft_comment_reply` returned. The server enforces
9
+ * that; the schema here requires all three so a model cannot omit one.
10
+ *
11
+ * Thin passthroughs, like connections.ts: the server owns the rules.
12
+ */
13
+ import { z } from "zod";
14
+ import { OUTPUT_SCHEMAS } from "./output-schemas.js";
15
+ function toResult(proxy) {
16
+ const textBlock = proxy.contentBlocks.find((c) => c.type === "text");
17
+ return {
18
+ content: textBlock ? [{ type: "text", text: String(textBlock.text ?? "") }] : [],
19
+ structuredContent: proxy.structured,
20
+ };
21
+ }
22
+ function failed(prefix, err) {
23
+ const msg = err instanceof Error ? err.message : String(err);
24
+ return {
25
+ content: [{ type: "text", text: `${prefix}: ${msg}` }],
26
+ isError: true,
27
+ };
28
+ }
29
+ const platform = z.enum(["youtube", "instagram"]);
30
+ export function registerReplyTools(server, makeClient) {
31
+ server.registerTool("list_comments_to_answer", {
32
+ title: "List Comments To Answer",
33
+ description: "List recent comments on the user's own connected account that nobody has answered yet, " +
34
+ "newest first. Only the user's own posts, through their own connection: YouTube works once " +
35
+ "connected with capabilities: [\"manage_comments\"] (see connect_social_account); networks " +
36
+ "that cannot be answered come back under `unsupported` with the reason. Optional platform, " +
37
+ "influencerId, postId, sinceDays (1-30, default 7), limit (1-100, default 30), " +
38
+ "includeAnswered. Read-only: it sends nothing. No cost to call.",
39
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
40
+ outputSchema: OUTPUT_SCHEMAS.list_comments_to_answer,
41
+ inputSchema: z
42
+ .object({
43
+ platform: platform.optional().describe("Only this network."),
44
+ influencerId: z.number().int().optional().describe("Creator profile id when more than one account is connected."),
45
+ postId: z.string().optional().describe("Only comments on this video/media id."),
46
+ sinceDays: z.number().int().min(1).max(30).optional().describe("How far back to look (default 7)."),
47
+ limit: z.number().int().min(1).max(100).optional().describe("Most comments to return (default 30)."),
48
+ includeAnswered: z.boolean().optional().describe("Also list comments that already have a reply."),
49
+ })
50
+ .strict(),
51
+ }, async (args, extra) => {
52
+ const client = await makeClient({ ...extra, arguments: args });
53
+ try {
54
+ return toResult(await client.callTool("list_comments_to_answer", args));
55
+ }
56
+ catch (err) {
57
+ return failed("list_comments_to_answer failed", err);
58
+ }
59
+ });
60
+ server.registerTool("draft_comment_reply", {
61
+ title: "Draft Comment Reply",
62
+ description: "Draft a reply to one comment from list_comments_to_answer and store it; nothing is " +
63
+ "posted. Pass `text` to use the user's own wording (no cost), or omit it and nooticr " +
64
+ "writes one (costs 1 nooticr credit); `guidance` steers the writing. Replies may not contain links, " +
65
+ "emails, phone numbers or @-mentions of anyone but the commenter. Returns a draftId and a " +
66
+ "confirmationToken: show the reply to the user first, then post it with post_comment_reply.",
67
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
68
+ outputSchema: OUTPUT_SCHEMAS.draft_comment_reply,
69
+ inputSchema: z
70
+ .object({
71
+ platform: platform.describe("The network the comment is on."),
72
+ commentId: z.string().describe("Comment id from list_comments_to_answer."),
73
+ postId: z.string().describe("Post id the comment is on."),
74
+ text: z.string().optional().describe("The reply to store, in the user's words. Omit to have one written (1 credit)."),
75
+ influencerId: z.number().int().optional().describe("Creator profile id when more than one account is connected."),
76
+ guidance: z.string().optional().describe("What the reply should say or avoid; a URL here is the only URL the reply may contain."),
77
+ })
78
+ .strict(),
79
+ }, async (args, extra) => {
80
+ const client = await makeClient({ ...extra, arguments: args });
81
+ try {
82
+ return toResult(await client.callTool("draft_comment_reply", args));
83
+ }
84
+ catch (err) {
85
+ return failed("draft_comment_reply failed", err);
86
+ }
87
+ });
88
+ server.registerTool("post_comment_reply", {
89
+ title: "Post Comment Reply",
90
+ description: "Post a stored reply draft to the comment, as the user's own account. Only after the user " +
91
+ "has seen and agreed to the exact text: pass the draftId, confirm: true and the " +
92
+ "confirmationToken from draft_comment_reply. Each comment is answered at most once and " +
93
+ "daily limits apply. It writes to a third-party network and cannot be undone from here. " +
94
+ "No cost to call.",
95
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
96
+ outputSchema: OUTPUT_SCHEMAS.post_comment_reply,
97
+ inputSchema: z
98
+ .object({
99
+ draftId: z.string().describe("Draft id from draft_comment_reply."),
100
+ confirm: z.boolean().describe("true only once the user has agreed to the exact text."),
101
+ confirmationToken: z.string().describe("Token returned with the draft."),
102
+ })
103
+ .strict(),
104
+ }, async (args, extra) => {
105
+ const client = await makeClient({ ...extra, arguments: args });
106
+ try {
107
+ return toResult(await client.callTool("post_comment_reply", args));
108
+ }
109
+ catch (err) {
110
+ return failed("post_comment_reply failed", err);
111
+ }
112
+ });
113
+ }
@@ -68,7 +68,11 @@ export function registerSheetsTools(server, makeClient) {
68
68
  server.registerTool("sheets_read", {
69
69
  title: "Read Spreadsheet Range",
70
70
  description: 'Read a range (A1 notation, e.g. "Ledger!A1:F200") of a spreadsheet. ' +
71
- "Numbers come back as numbers and dates as formatted text; at most 10000 cells. Free.",
71
+ "Numbers come back as numbers and dates as formatted text; at most 10000 cells, " +
72
+ 'and a range open at the bottom ("A:F", "Ledger!$A:$F") or longer than the cap is cut to the rows that fit, ' +
73
+ "with truncated=true when more may remain: read further down with a range that starts after the last row. " +
74
+ 'A bare name without quotes ("Expenses") is passed to Google as written, since it may be a named range, ' +
75
+ 'so name a tab as "Ledger!A:F" or "\'My Tab\'" to have it bounded. Free.',
72
76
  annotations: READ,
73
77
  inputSchema: z.object({ appId, id: spreadsheetId, range: z.string() }).strict(),
74
78
  outputSchema: OUTPUT_SCHEMAS.sheets_read,
@@ -327,7 +327,7 @@ export const TOOL_DEFINITIONS = [
327
327
  {
328
328
  name: "sheets_read",
329
329
  title: "Read Spreadsheet Range",
330
- description: "Read a range (A1 notation, e.g. \"Ledger!A1:F200\") of a spreadsheet. Numbers come back as numbers and dates as formatted text; at most 10000 cells. Free.",
330
+ description: "Read a range (A1 notation, e.g. \"Ledger!A1:F200\") of a spreadsheet. Numbers come back as numbers and dates as formatted text; at most 10000 cells, and a range open at the bottom (\"A:F\", \"Ledger!$A:$F\") or longer than the cap is cut to the rows that fit, with truncated=true when more may remain: read further down with a range that starts after the last row. A bare name without quotes (\"Expenses\") is passed to Google as written, since it may be a named range, so name a tab as \"Ledger!A:F\" or \"'My Tab'\" to have it bounded. Free.",
331
331
  inputSchema: z.object({ appId: z.number().int().optional().describe("Product id; optional when the workspace has one product."), id: z.string().describe("Spreadsheet id from sheets_create or sheets_list."), range: z.string() }).strict(),
332
332
  },
333
333
  {
@@ -477,7 +477,7 @@ export const TOOL_DEFINITIONS = [
477
477
  {
478
478
  name: "list_social_connections",
479
479
  title: "List Social Connections",
480
- description: "List the social accounts your workspace has connected and what each connection is allowed to do — read the account, publish a post, manage comments. Manage comments reports the grant; no nooticr tool posts or replies to a comment. Also returns which platforms can be connected at all. No cost to call.",
480
+ description: "List the social accounts your workspace has connected and what each connection is allowed to do — read the account, publish a post, manage comments. Manage comments reports the grant; this tool posts nothing, and a reply goes out only through post_comment_reply on a draft the user confirmed. Also returns which platforms can be connected at all. No cost to call.",
481
481
  inputSchema: z.object({}).strict(),
482
482
  },
483
483
  {
@@ -486,6 +486,24 @@ export const TOOL_DEFINITIONS = [
486
486
  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.",
487
487
  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(),
488
488
  },
489
+ {
490
+ name: "list_comments_to_answer",
491
+ title: "List Comments To Answer",
492
+ description: "List recent comments on the user's own connected account that nobody has answered yet, newest first. Only the user's own posts, through their own connection: YouTube works once connected with capabilities: [\"manage_comments\"] (see connect_social_account); networks that cannot be answered come back under `unsupported` with the reason. Optional platform, influencerId, postId, sinceDays (1-30, default 7), limit (1-100, default 30), includeAnswered. Read-only: it sends nothing. No cost to call.",
493
+ inputSchema: z.object({ platform: z.enum(["youtube", "instagram"]).optional().describe("Only this network."), influencerId: z.number().int().optional().describe("Creator profile id when more than one account is connected."), postId: z.string().optional().describe("Only comments on this video/media id."), sinceDays: z.number().int().min(1).max(30).optional().describe("How far back to look (default 7)."), limit: z.number().int().min(1).max(100).optional().describe("Most comments to return (default 30)."), includeAnswered: z.boolean().optional().describe("Also list comments that already have a reply.") }).strict(),
494
+ },
495
+ {
496
+ name: "draft_comment_reply",
497
+ title: "Draft Comment Reply",
498
+ description: "Draft a reply to one comment from list_comments_to_answer and store it; nothing is posted. Pass `text` to use the user's own wording (no cost), or omit it and nooticr writes one (costs 1 nooticr credit); `guidance` steers the writing. Replies may not contain links, emails, phone numbers or @-mentions of anyone but the commenter. Returns a draftId and a confirmationToken: show the reply to the user first, then post it with post_comment_reply.",
499
+ inputSchema: z.object({ platform: z.enum(["youtube", "instagram"]).describe("The network the comment is on."), commentId: z.string().describe("Comment id from list_comments_to_answer."), postId: z.string().describe("Post id the comment is on."), text: z.string().optional().describe("The reply to store, in the user's words. Omit to have one written (1 credit)."), influencerId: z.number().int().optional().describe("Creator profile id when more than one account is connected."), guidance: z.string().optional().describe("What the reply should say or avoid; a URL here is the only URL the reply may contain.") }).strict(),
500
+ },
501
+ {
502
+ name: "post_comment_reply",
503
+ title: "Post Comment Reply",
504
+ description: "Post a stored reply draft to the comment, as the user's own account. Only after the user has seen and agreed to the exact text: pass the draftId, confirm: true and the confirmationToken from draft_comment_reply. Each comment is answered at most once and daily limits apply. It writes to a third-party network and cannot be undone from here. No cost to call.",
505
+ inputSchema: z.object({ draftId: z.string().describe("Draft id from draft_comment_reply."), confirm: z.boolean().describe("true only once the user has agreed to the exact text."), confirmationToken: z.string().describe("Token returned with the draft.") }).strict(),
506
+ },
489
507
  {
490
508
  name: "connect_store",
491
509
  title: "Connect Store",
@@ -501,7 +519,7 @@ export const TOOL_DEFINITIONS = [
501
519
  {
502
520
  name: "answer_my_audience",
503
521
  title: "Answer My Audience",
504
- 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.",
522
+ 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; it cannot post a reply \u2014 the drafts are for a person to paste in themselves, or to send through draft_comment_reply and post_comment_reply once the user has confirmed the 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.",
505
523
  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(),
506
524
  },
507
525
  {
@@ -25,6 +25,7 @@ import { registerCreativeTools } from "./creative.js";
25
25
  import { registerSheetsTools } from "./sheets.js";
26
26
  import { registerOwnAccountTools } from "./own-account.js";
27
27
  import { registerConnectionTools } from "./connections.js";
28
+ import { registerReplyTools } from "./replies.js";
28
29
  import { registerEmployeeTools } from "./employees.js";
29
30
  import { registerStoreReadTools } from "./store-read.js";
30
31
  import { registerGettingStarted, SERVER_INSTRUCTIONS } from "./getting-started.js";
@@ -35,7 +36,7 @@ import { registerAmazonTools } from "./amazon.js";
35
36
  import { registerMarketplaceTools } from "./marketplace.js";
36
37
  import { DEFAULT_NOOTICR_SETTINGS, DEFAULT_SOCIAL_PLATFORMS, MemorySettingsStore, SETTINGS_READ_TOOL, SETTINGS_UPDATE_TOOL, settingsOwnerFromAuth, } from "./settings.js";
37
38
  /** Current MCP server version — bumped on every deploy for traceability. */
38
- export const MCP_SERVER_VERSION = "1.26.67";
39
+ export const MCP_SERVER_VERSION = "1.26.69";
39
40
  /** MCP Apps extension identifier */
40
41
  const UI_EXTENSION = "io.modelcontextprotocol/ui";
41
42
  /** MIME type for MCP Apps HTML resources */
@@ -2894,6 +2895,7 @@ export function createMcpServer(rawMakeClient, opts) {
2894
2895
  // Google Sheets (sheets.ts): the sheets_* family of nooticr-server BK2.
2895
2896
  registerSheetsTools(server, makeClient);
2896
2897
  registerConnectionTools(server, makeClient);
2898
+ registerReplyTools(server, makeClient);
2897
2899
  // AI employees (employees.ts): hire, list, pause, fire, and approve or reject what they queue.
2898
2900
  registerEmployeeTools(server, makeClient);
2899
2901
  // Store connections (store-read.ts): the connect link and the catalogue read.
@@ -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
- +"replies are never sent on their own: one is posted only after you confirm its exact text.</p></div>":""),
4414
+ +"replies go out only through post_comment_reply, on a draft you confirmed, and only where a connection allows managing comments.</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.67",
4
+ "version": "1.26.69",
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": {