@nooticr/mcp 1.26.66 → 1.26.68
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 +12 -3
- package/dist/shared/connections.js +3 -2
- package/dist/shared/employees.js +2 -2
- package/dist/shared/jobs.js +2 -2
- package/dist/shared/loading-plans.js +16 -0
- package/dist/shared/output-schemas.js +43 -2
- package/dist/shared/replies.js +113 -0
- package/dist/shared/sheets.js +102 -0
- package/dist/shared/tools-def.js +58 -4
- package/dist/shared/tools.js +6 -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
|
|
@@ -111,6 +111,12 @@ them caps that fan-out with an argument.
|
|
|
111
111
|
| `creative_image` | quoted first | 1-4 still images from a `prompt` with GPT Image (low quality), at a `size` (default portrait 1024x1536), optionally `transparent`. The first call returns a quote in credits and charges nothing; call again with `confirm: true` and the `confirmationToken` to generate. Settled to actual usage, never above the quote. Returns the new `assets` and `creativeCredits`. Keep long text out of the prompt and ask for invented people, not real ones. |
|
|
112
112
|
| `creative_image_edit` | quoted first | Edit or combine 1-4 existing image assets (`imageAssetIds`) with a `prompt`, with an optional `maskAssetId`. Same quote-then-confirm handshake as `creative_image`; returns a new asset and `creativeCredits`. |
|
|
113
113
|
| `creative_job_status` | free | Where an asynchronous creative job stands by `jobId`: queued, running, done, failed or canceled, with the finished assets and the credits quoted, charged and refunded. Poll after `pollAfterSeconds`. |
|
|
114
|
+
| `sheets_create` | free | Create a Google spreadsheet in the connected Google account, with named tabs and a header row on each. Returns its id and URL; Nooticr owns the file, so later writes need no approval. Needs the product's Google Sheets connection. |
|
|
115
|
+
| `sheets_list` | free | The spreadsheets the connection can see (created by Nooticr or picked by the customer) and whether each may be edited without approval. |
|
|
116
|
+
| `sheets_read` | free | Read an A1 range (e.g. `Ledger!A1:F200`) of a spreadsheet; at most 10000 cells. |
|
|
117
|
+
| `sheets_append_rows` | free | Append typed rows after the last row of a tab. Free on files Nooticr created or the customer allowed you to edit; any other picked file waits for approval. |
|
|
118
|
+
| `sheets_update_range` | free | Overwrite an A1 range with typed values. Same approval rule as `sheets_append_rows`. |
|
|
119
|
+
| `sheets_add_sheet` | free | Add an empty tab to a spreadsheet. Same approval rule as `sheets_append_rows`. |
|
|
114
120
|
| `mention_trend` | free | **What a watch has seen over time**, which nothing else here answers — every other read is about now. One point per run: when it ran, how many mentions it found, how many were new, and both counts per network, so "which network is growing" and "did it go quiet or did we stop looking" come apart. On a competitor watch each point carries the median that run measured, which turns "they beat their own median" into "and that median has doubled". Plus the mentions more than one run has seen. Free because the sweeps were billed when they ran; charging again to read what was already paid for is the thing it fixes. |
|
|
115
121
|
| `update_watch_portfolio` | free | Add or remove a portfolio watch's terms by `watchId`. No new confirmation, because the per-run budget you agreed to does not move — more terms split it more ways, and the reply names any network a run no longer reaches. |
|
|
116
122
|
| `stop_brand_watch` | free | Stop a watch by `watchId` or `term`. Immediate — the next run does not happen and nothing more is charged. |
|
|
@@ -121,8 +127,11 @@ them caps that fan-out with an argument.
|
|
|
121
127
|
|
|
122
128
|
| Tool | Credits | What it is for |
|
|
123
129
|
|------|---------|----------------|
|
|
124
|
-
| `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. |
|
|
125
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. |
|
|
126
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. |
|
|
127
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. |
|
|
128
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. |
|
|
@@ -188,7 +197,7 @@ a `scanId` for the rest.
|
|
|
188
197
|
| `get_tool_run` | free | One run from that history by id, with its full error. |
|
|
189
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. |
|
|
190
199
|
| `nooticr_login` | free | Re-link the account when a call fails with an authentication error. |
|
|
191
|
-
| `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. |
|
|
192
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. |
|
|
193
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. |
|
|
194
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/employees.js
CHANGED
|
@@ -46,7 +46,7 @@ export function registerEmployeeTools(server, makeClient) {
|
|
|
46
46
|
title: "Hire Employee",
|
|
47
47
|
description: "Hire an AI employee (for example a Social Media Manager) that works on a schedule inside " +
|
|
48
48
|
"this workspace. Pass `template` and optionally name, instructions, products (the string all, or a list of product ids; appId is deprecated), schedule, " +
|
|
49
|
-
"monthlyBudgetCredits, maxCreditsPerRun, approvalPolicy and notify. " +
|
|
49
|
+
"monthlyBudgetCredits, maxCreditsPerRun, approvalPolicy and notify (channels, and recipients: hirer, admins or member user ids). " +
|
|
50
50
|
TWO_STEP_RULE +
|
|
51
51
|
" Public actions (publish, email, reply, listing or price changes) wait for the user's " +
|
|
52
52
|
"approval unless autopilot is turned on. The hiring user is billed for runs, up to the " +
|
|
@@ -69,7 +69,7 @@ export function registerEmployeeTools(server, makeClient) {
|
|
|
69
69
|
monthlyBudgetCredits: z.number().int().min(1).optional(),
|
|
70
70
|
maxCreditsPerRun: z.number().int().min(1).optional(),
|
|
71
71
|
approvalPolicy: z.object({}).passthrough().optional().describe("Action type -> 'approval' | 'auto'."),
|
|
72
|
-
notify: z.object({}).passthrough().optional().describe("{channels:['email'|'slack']}."),
|
|
72
|
+
notify: z.object({}).passthrough().optional().describe("{channels:['email'|'slack'], recipients?:'hirer'|'admins'|[member user ids, max 25]}."),
|
|
73
73
|
model: z.string().optional().describe("Optional model."),
|
|
74
74
|
effort: z.string().optional().describe("low | medium | high."),
|
|
75
75
|
confirm,
|
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,
|
|
@@ -487,6 +487,13 @@ export const LOADING_PLANS = {
|
|
|
487
487
|
// Paid, but the first call only quotes; the charge waits for the confirmed second call.
|
|
488
488
|
creative_image: { label: "Quoting the images", kind: "text", n: 1, steps: [] },
|
|
489
489
|
creative_image_edit: { label: "Quoting the image edit", kind: "text", n: 1, steps: [] },
|
|
490
|
+
// Google Sheets (sheets.ts): reads are free; writes are free too (approval waits on the server side).
|
|
491
|
+
sheets_create: { label: "Creating the spreadsheet", kind: "text", n: 1, steps: [], free: true },
|
|
492
|
+
sheets_list: { label: "Listing your spreadsheets", kind: "list", n: 3, steps: [], free: true },
|
|
493
|
+
sheets_read: { label: "Reading the spreadsheet", kind: "text", n: 1, steps: [], free: true },
|
|
494
|
+
sheets_append_rows: { label: "Adding rows to the spreadsheet", kind: "text", n: 1, steps: [], free: true },
|
|
495
|
+
sheets_update_range: { label: "Updating the spreadsheet", kind: "text", n: 1, steps: [], free: true },
|
|
496
|
+
sheets_add_sheet: { label: "Adding a tab to the spreadsheet", kind: "text", n: 1, steps: [], free: true },
|
|
490
497
|
list_brand_watches: { label: "Reading your watches", kind: "list", n: 3, steps: [], free: true },
|
|
491
498
|
stop_brand_watch: { label: "Stopping the watch", kind: "text", n: 1, steps: [], free: true },
|
|
492
499
|
update_watch_portfolio: { label: "Updating the portfolio", kind: "text", n: 1, steps: [], free: true },
|
|
@@ -530,6 +537,15 @@ export const LOADING_PLANS = {
|
|
|
530
537
|
list_watchlist: { label: "Reading your watchlist", kind: "text", n: 1, steps: [], free: true },
|
|
531
538
|
list_social_connections: { label: "Reading your connections", kind: "list", n: 3, steps: [], free: true },
|
|
532
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 },
|
|
533
549
|
create_product: { label: "Writing the row", kind: "text", n: 1, steps: [], free: true },
|
|
534
550
|
update_product: { label: "Patching the row", kind: "text", n: 1, steps: [], free: true },
|
|
535
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."),
|
|
@@ -1327,6 +1328,13 @@ export const OUTPUT_SCHEMAS = {
|
|
|
1327
1328
|
pollAfterSeconds: scalar(),
|
|
1328
1329
|
error: scalar(),
|
|
1329
1330
|
}),
|
|
1331
|
+
// Google Sheets (nooticr-server BK2, sheets.ts): the server's own shapes, only stable keys named.
|
|
1332
|
+
sheets_create: open({ id: scalar(), url: scalar(), title: scalar(), sheets: listOf(oneType(z.string())), owner: scalar() }),
|
|
1333
|
+
sheets_list: open({ files: listOf(open({ id: scalar(), title: scalar(), url: scalar() })) }),
|
|
1334
|
+
sheets_read: open({}),
|
|
1335
|
+
sheets_append_rows: open({ id: scalar(), sheet: scalar(), rowsAppended: scalar(), cells: scalar(), updatedRange: scalar() }),
|
|
1336
|
+
sheets_update_range: open({ id: scalar(), updatedRange: scalar(), updatedCells: scalar(), cells: scalar() }),
|
|
1337
|
+
sheets_add_sheet: open({ id: scalar(), sheetId: scalar(), name: scalar() }),
|
|
1330
1338
|
list_brand_watches: open({
|
|
1331
1339
|
watches: listOf(open({
|
|
1332
1340
|
watchId: scalar(),
|
|
@@ -1689,6 +1697,39 @@ export const OUTPUT_SCHEMAS = {
|
|
|
1689
1697
|
connections: listOf(anyObject()),
|
|
1690
1698
|
message: scalar(),
|
|
1691
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
|
+
}),
|
|
1692
1733
|
// Marketplace. One shape for all three collection tools: a status poll
|
|
1693
1734
|
// returns exactly what the scan that started it returns, and a single
|
|
1694
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
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* nooticr-server's `sheets_*` family (BK2, Google Sheets): create, list, read
|
|
3
|
+
* and write spreadsheets in the customer's connected Google account. Plain
|
|
4
|
+
* passthroughs to the server's `POST /mcp` dispatch, which owns the Google
|
|
5
|
+
* connection, the approval rule for files the customer picked, and the
|
|
6
|
+
* 10000-cell limit. Argument names match the server's schema exactly.
|
|
7
|
+
*/
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
import { OUTPUT_SCHEMAS } from "./output-schemas.js";
|
|
10
|
+
function toResult(proxy) {
|
|
11
|
+
const textBlock = proxy.contentBlocks.find((c) => c.type === "text");
|
|
12
|
+
const structured = proxy.structured;
|
|
13
|
+
const text = structured ? JSON.stringify(structured) : String(textBlock?.text ?? "");
|
|
14
|
+
return { content: [{ type: "text", text }], structuredContent: structured };
|
|
15
|
+
}
|
|
16
|
+
function failed(prefix, err) {
|
|
17
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
18
|
+
return {
|
|
19
|
+
content: [{ type: "text", text: `${prefix}: ${msg}` }],
|
|
20
|
+
isError: true,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
const appId = z.number().int().optional().describe("Product id; optional when the workspace has one product.");
|
|
24
|
+
const spreadsheetId = z.string().describe("Spreadsheet id from sheets_create or sheets_list.");
|
|
25
|
+
const cell = z
|
|
26
|
+
.union([z.number(), z.boolean(), z.string(), z.null(), z.object({ formula: z.string() }).strict()])
|
|
27
|
+
.describe("A number, boolean, text or an ISO date (2026-01-31) or datetime; null leaves the cell empty. " +
|
|
28
|
+
'Text that looks like a formula or number stays text; use {"formula": "=SUM(A1:A3)"} for a formula.');
|
|
29
|
+
const rows = z.array(z.array(cell));
|
|
30
|
+
const READ = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true };
|
|
31
|
+
const WRITE = { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true };
|
|
32
|
+
export function registerSheetsTools(server, makeClient) {
|
|
33
|
+
const passthrough = (name) => async (args, extra) => {
|
|
34
|
+
const client = await makeClient({ ...extra, arguments: args });
|
|
35
|
+
try {
|
|
36
|
+
return toResult(await client.callTool(name, args));
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
return failed(`${name} failed`, err);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
server.registerTool("sheets_create", {
|
|
43
|
+
title: "Create Spreadsheet",
|
|
44
|
+
description: "Create a Google spreadsheet in the connected Google account, with the named sheets and a header row on each. " +
|
|
45
|
+
"Returns its id and URL; Nooticr owns the file, so later writes need no approval. " +
|
|
46
|
+
"Requires the product's Google Sheets connection. Free.",
|
|
47
|
+
annotations: WRITE,
|
|
48
|
+
inputSchema: z
|
|
49
|
+
.object({
|
|
50
|
+
appId,
|
|
51
|
+
title: z.string(),
|
|
52
|
+
sheets: z
|
|
53
|
+
.array(z.object({ name: z.string(), headers: z.array(z.string()).optional() }))
|
|
54
|
+
.optional()
|
|
55
|
+
.describe("Tabs to create."),
|
|
56
|
+
})
|
|
57
|
+
.strict(),
|
|
58
|
+
outputSchema: OUTPUT_SCHEMAS.sheets_create,
|
|
59
|
+
}, passthrough("sheets_create"));
|
|
60
|
+
server.registerTool("sheets_list", {
|
|
61
|
+
title: "List Spreadsheets",
|
|
62
|
+
description: "List the spreadsheets this connection can see: the ones Nooticr created and the ones the customer picked. " +
|
|
63
|
+
"Each says whether it was created or picked and whether the employee may edit it without approval. Free.",
|
|
64
|
+
annotations: READ,
|
|
65
|
+
inputSchema: z.object({ appId, limit: z.number().int().min(1).max(100).optional() }).strict(),
|
|
66
|
+
outputSchema: OUTPUT_SCHEMAS.sheets_list,
|
|
67
|
+
}, passthrough("sheets_list"));
|
|
68
|
+
server.registerTool("sheets_read", {
|
|
69
|
+
title: "Read Spreadsheet Range",
|
|
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, " +
|
|
72
|
+
'and a range open at the bottom ("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. Free.",
|
|
74
|
+
annotations: READ,
|
|
75
|
+
inputSchema: z.object({ appId, id: spreadsheetId, range: z.string() }).strict(),
|
|
76
|
+
outputSchema: OUTPUT_SCHEMAS.sheets_read,
|
|
77
|
+
}, passthrough("sheets_read"));
|
|
78
|
+
server.registerTool("sheets_append_rows", {
|
|
79
|
+
title: "Append Spreadsheet Rows",
|
|
80
|
+
description: "Append rows after the last row of a sheet (tab). Values are typed: numbers stay numbers and ISO dates become sheet dates. " +
|
|
81
|
+
"At most 10000 cells per call. Free on a file Nooticr created or one the customer allowed you to edit; " +
|
|
82
|
+
"on any other file the customer picked it waits for their approval.",
|
|
83
|
+
annotations: WRITE,
|
|
84
|
+
inputSchema: z.object({ appId, id: spreadsheetId, sheet: z.string().describe("Tab name."), rows }).strict(),
|
|
85
|
+
outputSchema: OUTPUT_SCHEMAS.sheets_append_rows,
|
|
86
|
+
}, passthrough("sheets_append_rows"));
|
|
87
|
+
server.registerTool("sheets_update_range", {
|
|
88
|
+
title: "Update Spreadsheet Range",
|
|
89
|
+
description: 'Overwrite a range (A1 notation, e.g. "Ledger!B2:C3") with values. Typed like sheets_append_rows; ' +
|
|
90
|
+
"at most 10000 cells per call. Same approval rule as sheets_append_rows: free on a file Nooticr created or one the customer allowed you to edit, otherwise it waits for their approval.",
|
|
91
|
+
annotations: WRITE,
|
|
92
|
+
inputSchema: z.object({ appId, id: spreadsheetId, range: z.string(), values: rows }).strict(),
|
|
93
|
+
outputSchema: OUTPUT_SCHEMAS.sheets_update_range,
|
|
94
|
+
}, passthrough("sheets_update_range"));
|
|
95
|
+
server.registerTool("sheets_add_sheet", {
|
|
96
|
+
title: "Add Spreadsheet Tab",
|
|
97
|
+
description: "Add an empty sheet (tab) to a spreadsheet. Same approval rule as sheets_append_rows: free on a file Nooticr created or one the customer allowed you to edit, otherwise it waits for their approval.",
|
|
98
|
+
annotations: WRITE,
|
|
99
|
+
inputSchema: z.object({ appId, id: spreadsheetId, name: z.string() }).strict(),
|
|
100
|
+
outputSchema: OUTPUT_SCHEMAS.sheets_add_sheet,
|
|
101
|
+
}, passthrough("sheets_add_sheet"));
|
|
102
|
+
}
|
package/dist/shared/tools-def.js
CHANGED
|
@@ -312,6 +312,42 @@ export const TOOL_DEFINITIONS = [
|
|
|
312
312
|
description: "Status of an asynchronous creative job by jobId: queued, running, done, failed or canceled, with the finished assets and the credits quoted, charged and refunded. Poll after pollAfterSeconds. No cost to call.",
|
|
313
313
|
inputSchema: z.object({ jobId: z.string().describe("jobId returned when the job was started.") }).strict(),
|
|
314
314
|
},
|
|
315
|
+
{
|
|
316
|
+
name: "sheets_create",
|
|
317
|
+
title: "Create Spreadsheet",
|
|
318
|
+
description: "Create a Google spreadsheet in the connected Google account, with the named sheets and a header row on each. Returns its id and URL; Nooticr owns the file, so later writes need no approval. Requires the product's Google Sheets connection. Free.",
|
|
319
|
+
inputSchema: z.object({ appId: z.number().int().optional().describe("Product id; optional when the workspace has one product."), title: z.string(), sheets: z.array(z.object({ name: z.string(), headers: z.array(z.string()).optional() })).optional().describe("Tabs to create.") }).strict(),
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
name: "sheets_list",
|
|
323
|
+
title: "List Spreadsheets",
|
|
324
|
+
description: "List the spreadsheets this connection can see: the ones Nooticr created and the ones the customer picked. Each says whether it was created or picked and whether the employee may edit it without approval. Free.",
|
|
325
|
+
inputSchema: z.object({ appId: z.number().int().optional().describe("Product id; optional when the workspace has one product."), limit: z.number().int().min(1).max(100).optional() }).strict(),
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
name: "sheets_read",
|
|
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, and a range open at the bottom (\"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. Free.",
|
|
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
|
+
},
|
|
333
|
+
{
|
|
334
|
+
name: "sheets_append_rows",
|
|
335
|
+
title: "Append Spreadsheet Rows",
|
|
336
|
+
description: "Append rows after the last row of a sheet (tab). Values are typed: numbers stay numbers and ISO dates become sheet dates. At most 10000 cells per call. Free on a file Nooticr created or one the customer allowed you to edit; on any other file the customer picked it waits for their approval.",
|
|
337
|
+
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."), sheet: z.string().describe("Tab name."), rows: z.array(z.array(z.union([z.number(), z.boolean(), z.string(), z.null(), z.object({ formula: z.string() }).strict()]).describe('A number, boolean, text or an ISO date (2026-01-31) or datetime; null leaves the cell empty. Text that looks like a formula or number stays text; use {"formula": "=SUM(A1:A3)"} for a formula.'))) }).strict(),
|
|
338
|
+
},
|
|
339
|
+
{
|
|
340
|
+
name: "sheets_update_range",
|
|
341
|
+
title: "Update Spreadsheet Range",
|
|
342
|
+
description: "Overwrite a range (A1 notation, e.g. \"Ledger!B2:C3\") with values. Typed like sheets_append_rows; at most 10000 cells per call. Same approval rule as sheets_append_rows: free on a file Nooticr created or one the customer allowed you to edit, otherwise it waits for their approval.",
|
|
343
|
+
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(), values: z.array(z.array(z.union([z.number(), z.boolean(), z.string(), z.null(), z.object({ formula: z.string() }).strict()]).describe('A number, boolean, text or an ISO date (2026-01-31) or datetime; null leaves the cell empty. Text that looks like a formula or number stays text; use {"formula": "=SUM(A1:A3)"} for a formula.'))) }).strict(),
|
|
344
|
+
},
|
|
345
|
+
{
|
|
346
|
+
name: "sheets_add_sheet",
|
|
347
|
+
title: "Add Spreadsheet Tab",
|
|
348
|
+
description: "Add an empty sheet (tab) to a spreadsheet. Same approval rule as sheets_append_rows: free on a file Nooticr created or one the customer allowed you to edit, otherwise it waits for their approval.",
|
|
349
|
+
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."), name: z.string() }).strict(),
|
|
350
|
+
},
|
|
315
351
|
{
|
|
316
352
|
name: "list_brand_watches",
|
|
317
353
|
title: "List Brand Watches",
|
|
@@ -441,7 +477,7 @@ export const TOOL_DEFINITIONS = [
|
|
|
441
477
|
{
|
|
442
478
|
name: "list_social_connections",
|
|
443
479
|
title: "List Social Connections",
|
|
444
|
-
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.",
|
|
445
481
|
inputSchema: z.object({}).strict(),
|
|
446
482
|
},
|
|
447
483
|
{
|
|
@@ -450,6 +486,24 @@ export const TOOL_DEFINITIONS = [
|
|
|
450
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.",
|
|
451
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(),
|
|
452
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
|
+
},
|
|
453
507
|
{
|
|
454
508
|
name: "connect_store",
|
|
455
509
|
title: "Connect Store",
|
|
@@ -465,7 +519,7 @@ export const TOOL_DEFINITIONS = [
|
|
|
465
519
|
{
|
|
466
520
|
name: "answer_my_audience",
|
|
467
521
|
title: "Answer My Audience",
|
|
468
|
-
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.",
|
|
469
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(),
|
|
470
524
|
},
|
|
471
525
|
{
|
|
@@ -573,8 +627,8 @@ export const TOOL_DEFINITIONS = [
|
|
|
573
627
|
{
|
|
574
628
|
name: "hire_employee",
|
|
575
629
|
title: "Hire Employee",
|
|
576
|
-
description: "Hire an AI employee (for example a Social Media Manager) that works on a schedule inside this workspace. Pass `template` and optionally name, instructions, products (the string all, or a list of product ids; appId is deprecated), schedule, monthlyBudgetCredits, maxCreditsPerRun, approvalPolicy and notify. The first call changes nothing and returns a preview and a confirmationToken; show it to the user, and call again with confirm: true and the token only after the user explicitly agrees — never confirm on the user's behalf. Public actions (publish, email, reply, listing or price changes) wait for the user's approval unless autopilot is turned on. The hiring user is billed for runs, up to the monthly budget. No cost to call.",
|
|
577
|
-
inputSchema: z.object({ template: z.string().describe("Template slug, e.g. social-media-manager."), name: z.string().optional().describe("What to call the employee."), instructions: z.string().optional().describe("Standing instructions from the customer (tone, topics to avoid, ...)."), appId: z.number().int().optional().describe("Deprecated: same as products:[appId]. Do not send together with products."), products: z.union([z.literal("all"), z.array(z.number().int()).min(1)]).optional().describe('"all" (every product, including ones added later) or a list of product ids the employee covers.'), schedule: z.object({}).passthrough().optional().describe("{kind:'weekly',days:['mon'],times:['09:00'],tz:'Europe/Paris'} | {kind:'interval',minutes:N>=60} | {kind:'manual'}."), monthlyBudgetCredits: z.number().int().min(1).optional(), maxCreditsPerRun: z.number().int().min(1).optional(), approvalPolicy: z.object({}).passthrough().optional().describe("Action type -> 'approval' | 'auto'."), notify: z.object({}).passthrough().optional().describe("{channels:['email'|'slack']}."), model: z.string().optional().describe("Optional model."), effort: z.string().optional().describe("low | medium | high."), confirm: z.boolean().optional(), confirmationToken: z.string().optional().describe("Token returned in the quote.") }).strict(),
|
|
630
|
+
description: "Hire an AI employee (for example a Social Media Manager) that works on a schedule inside this workspace. Pass `template` and optionally name, instructions, products (the string all, or a list of product ids; appId is deprecated), schedule, monthlyBudgetCredits, maxCreditsPerRun, approvalPolicy and notify (channels, and recipients: hirer, admins or member user ids). The first call changes nothing and returns a preview and a confirmationToken; show it to the user, and call again with confirm: true and the token only after the user explicitly agrees — never confirm on the user's behalf. Public actions (publish, email, reply, listing or price changes) wait for the user's approval unless autopilot is turned on. The hiring user is billed for runs, up to the monthly budget. No cost to call.",
|
|
631
|
+
inputSchema: z.object({ template: z.string().describe("Template slug, e.g. social-media-manager."), name: z.string().optional().describe("What to call the employee."), instructions: z.string().optional().describe("Standing instructions from the customer (tone, topics to avoid, ...)."), appId: z.number().int().optional().describe("Deprecated: same as products:[appId]. Do not send together with products."), products: z.union([z.literal("all"), z.array(z.number().int()).min(1)]).optional().describe('"all" (every product, including ones added later) or a list of product ids the employee covers.'), schedule: z.object({}).passthrough().optional().describe("{kind:'weekly',days:['mon'],times:['09:00'],tz:'Europe/Paris'} | {kind:'interval',minutes:N>=60} | {kind:'manual'}."), monthlyBudgetCredits: z.number().int().min(1).optional(), maxCreditsPerRun: z.number().int().min(1).optional(), approvalPolicy: z.object({}).passthrough().optional().describe("Action type -> 'approval' | 'auto'."), notify: z.object({}).passthrough().optional().describe("{channels:['email'|'slack'], recipients?:'hirer'|'admins'|[member user ids, max 25]}."), model: z.string().optional().describe("Optional model."), effort: z.string().optional().describe("low | medium | high."), confirm: z.boolean().optional(), confirmationToken: z.string().optional().describe("Token returned in the quote.") }).strict(),
|
|
578
632
|
},
|
|
579
633
|
{
|
|
580
634
|
name: "list_employees",
|
package/dist/shared/tools.js
CHANGED
|
@@ -22,8 +22,10 @@ import { BackendWatchStore, MemoryWatchStore, registerWatchlist, } from "./watch
|
|
|
22
22
|
import { registerJobTools } from "./jobs.js";
|
|
23
23
|
import { registerBrandWatch } from "./brand-watch.js";
|
|
24
24
|
import { registerCreativeTools } from "./creative.js";
|
|
25
|
+
import { registerSheetsTools } from "./sheets.js";
|
|
25
26
|
import { registerOwnAccountTools } from "./own-account.js";
|
|
26
27
|
import { registerConnectionTools } from "./connections.js";
|
|
28
|
+
import { registerReplyTools } from "./replies.js";
|
|
27
29
|
import { registerEmployeeTools } from "./employees.js";
|
|
28
30
|
import { registerStoreReadTools } from "./store-read.js";
|
|
29
31
|
import { registerGettingStarted, SERVER_INSTRUCTIONS } from "./getting-started.js";
|
|
@@ -34,7 +36,7 @@ import { registerAmazonTools } from "./amazon.js";
|
|
|
34
36
|
import { registerMarketplaceTools } from "./marketplace.js";
|
|
35
37
|
import { DEFAULT_NOOTICR_SETTINGS, DEFAULT_SOCIAL_PLATFORMS, MemorySettingsStore, SETTINGS_READ_TOOL, SETTINGS_UPDATE_TOOL, settingsOwnerFromAuth, } from "./settings.js";
|
|
36
38
|
/** Current MCP server version — bumped on every deploy for traceability. */
|
|
37
|
-
export const MCP_SERVER_VERSION = "1.26.
|
|
39
|
+
export const MCP_SERVER_VERSION = "1.26.68";
|
|
38
40
|
/** MCP Apps extension identifier */
|
|
39
41
|
const UI_EXTENSION = "io.modelcontextprotocol/ui";
|
|
40
42
|
/** MIME type for MCP Apps HTML resources */
|
|
@@ -2890,7 +2892,10 @@ export function createMcpServer(rawMakeClient, opts) {
|
|
|
2890
2892
|
registerOwnAccountTools(server, makeClient);
|
|
2891
2893
|
// Creative assets (creative.ts): the free reads of nooticr-server's creative_* family.
|
|
2892
2894
|
registerCreativeTools(server, makeClient);
|
|
2895
|
+
// Google Sheets (sheets.ts): the sheets_* family of nooticr-server BK2.
|
|
2896
|
+
registerSheetsTools(server, makeClient);
|
|
2893
2897
|
registerConnectionTools(server, makeClient);
|
|
2898
|
+
registerReplyTools(server, makeClient);
|
|
2894
2899
|
// AI employees (employees.ts): hire, list, pause, fire, and approve or reject what they queue.
|
|
2895
2900
|
registerEmployeeTools(server, makeClient);
|
|
2896
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.68",
|
|
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": {
|