@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 +6 -3
- package/dist/shared/connections.js +3 -2
- package/dist/shared/jobs.js +2 -2
- package/dist/shared/loading-plans.js +9 -0
- package/dist/shared/output-schemas.js +36 -2
- package/dist/shared/replies.js +113 -0
- package/dist/shared/sheets.js +5 -1
- package/dist/shared/tools-def.js +21 -3
- package/dist/shared/tools.js +3 -1
- package/dist/shared/ui-template.js +1 -1
- package/package.json +1 -1
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
|
-
|
|
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
|
|
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;
|
|
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
|
|
49
|
-
"
|
|
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(),
|
package/dist/shared/jobs.js
CHANGED
|
@@ -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.
|
|
803
|
-
//
|
|
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
|
|
644
|
-
"
|
|
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
|
+
}
|
package/dist/shared/sheets.js
CHANGED
|
@@ -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
|
|
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,
|
package/dist/shared/tools-def.js
CHANGED
|
@@ -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;
|
|
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;
|
|
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
|
{
|
package/dist/shared/tools.js
CHANGED
|
@@ -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.
|
|
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
|
|
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.
|
|
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": {
|