@nooticr/mcp 1.26.65 → 1.26.67

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
- 108 tools, grouped by what you are trying to do. Prices are in nooticr credits and
55
+ 114 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. |
@@ -45,8 +45,8 @@ export function registerEmployeeTools(server, makeClient) {
45
45
  server.registerTool("hire_employee", {
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
- "this workspace. Pass `template` and optionally name, instructions, appId, schedule, " +
49
- "monthlyBudgetCredits, maxCreditsPerRun, approvalPolicy and notify. " +
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 (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 " +
@@ -57,7 +57,11 @@ export function registerEmployeeTools(server, makeClient) {
57
57
  template: z.string().describe("Template slug, e.g. social-media-manager."),
58
58
  name: z.string().optional().describe("What to call the employee."),
59
59
  instructions: z.string().optional().describe("Standing instructions from the customer (tone, topics to avoid, ...)."),
60
- appId: z.number().int().optional().describe("Focus product id."),
60
+ appId: z.number().int().optional().describe("Deprecated: same as products:[appId]. Do not send together with products."),
61
+ products: z
62
+ .union([z.literal("all"), z.array(z.number().int()).min(1)])
63
+ .optional()
64
+ .describe('"all" (every product, including ones added later) or a list of product ids the employee covers.'),
61
65
  schedule: z
62
66
  .object({}).passthrough()
63
67
  .optional()
@@ -65,7 +69,7 @@ export function registerEmployeeTools(server, makeClient) {
65
69
  monthlyBudgetCredits: z.number().int().min(1).optional(),
66
70
  maxCreditsPerRun: z.number().int().min(1).optional(),
67
71
  approvalPolicy: z.object({}).passthrough().optional().describe("Action type -> 'approval' | 'auto'."),
68
- 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]}."),
69
73
  model: z.string().optional().describe("Optional model."),
70
74
  effort: z.string().optional().describe("low | medium | high."),
71
75
  confirm,
@@ -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 },
@@ -1327,6 +1327,13 @@ export const OUTPUT_SCHEMAS = {
1327
1327
  pollAfterSeconds: scalar(),
1328
1328
  error: scalar(),
1329
1329
  }),
1330
+ // Google Sheets (nooticr-server BK2, sheets.ts): the server's own shapes, only stable keys named.
1331
+ sheets_create: open({ id: scalar(), url: scalar(), title: scalar(), sheets: listOf(oneType(z.string())), owner: scalar() }),
1332
+ sheets_list: open({ files: listOf(open({ id: scalar(), title: scalar(), url: scalar() })) }),
1333
+ sheets_read: open({}),
1334
+ sheets_append_rows: open({ id: scalar(), sheet: scalar(), rowsAppended: scalar(), cells: scalar(), updatedRange: scalar() }),
1335
+ sheets_update_range: open({ id: scalar(), updatedRange: scalar(), updatedCells: scalar(), cells: scalar() }),
1336
+ sheets_add_sheet: open({ id: scalar(), sheetId: scalar(), name: scalar() }),
1330
1337
  list_brand_watches: open({
1331
1338
  watches: listOf(open({
1332
1339
  watchId: scalar(),
@@ -0,0 +1,100 @@
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. Free.",
72
+ annotations: READ,
73
+ inputSchema: z.object({ appId, id: spreadsheetId, range: z.string() }).strict(),
74
+ outputSchema: OUTPUT_SCHEMAS.sheets_read,
75
+ }, passthrough("sheets_read"));
76
+ server.registerTool("sheets_append_rows", {
77
+ title: "Append Spreadsheet Rows",
78
+ description: "Append rows after the last row of a sheet (tab). Values are typed: numbers stay numbers and ISO dates become sheet dates. " +
79
+ "At most 10000 cells per call. Free on a file Nooticr created or one the customer allowed you to edit; " +
80
+ "on any other file the customer picked it waits for their approval.",
81
+ annotations: WRITE,
82
+ inputSchema: z.object({ appId, id: spreadsheetId, sheet: z.string().describe("Tab name."), rows }).strict(),
83
+ outputSchema: OUTPUT_SCHEMAS.sheets_append_rows,
84
+ }, passthrough("sheets_append_rows"));
85
+ server.registerTool("sheets_update_range", {
86
+ title: "Update Spreadsheet Range",
87
+ description: 'Overwrite a range (A1 notation, e.g. "Ledger!B2:C3") with values. Typed like sheets_append_rows; ' +
88
+ "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.",
89
+ annotations: WRITE,
90
+ inputSchema: z.object({ appId, id: spreadsheetId, range: z.string(), values: rows }).strict(),
91
+ outputSchema: OUTPUT_SCHEMAS.sheets_update_range,
92
+ }, passthrough("sheets_update_range"));
93
+ server.registerTool("sheets_add_sheet", {
94
+ title: "Add Spreadsheet Tab",
95
+ 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.",
96
+ annotations: WRITE,
97
+ inputSchema: z.object({ appId, id: spreadsheetId, name: z.string() }).strict(),
98
+ outputSchema: OUTPUT_SCHEMAS.sheets_add_sheet,
99
+ }, passthrough("sheets_add_sheet"));
100
+ }
@@ -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. 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",
@@ -573,8 +609,8 @@ export const TOOL_DEFINITIONS = [
573
609
  {
574
610
  name: "hire_employee",
575
611
  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, appId, 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("Focus product id."), 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(),
612
+ 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.",
613
+ 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
614
  },
579
615
  {
580
616
  name: "list_employees",
@@ -22,6 +22,7 @@ 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";
27
28
  import { registerEmployeeTools } from "./employees.js";
@@ -34,7 +35,7 @@ import { registerAmazonTools } from "./amazon.js";
34
35
  import { registerMarketplaceTools } from "./marketplace.js";
35
36
  import { DEFAULT_NOOTICR_SETTINGS, DEFAULT_SOCIAL_PLATFORMS, MemorySettingsStore, SETTINGS_READ_TOOL, SETTINGS_UPDATE_TOOL, settingsOwnerFromAuth, } from "./settings.js";
36
37
  /** Current MCP server version — bumped on every deploy for traceability. */
37
- export const MCP_SERVER_VERSION = "1.26.65";
38
+ export const MCP_SERVER_VERSION = "1.26.67";
38
39
  /** MCP Apps extension identifier */
39
40
  const UI_EXTENSION = "io.modelcontextprotocol/ui";
40
41
  /** MIME type for MCP Apps HTML resources */
@@ -2890,6 +2891,8 @@ export function createMcpServer(rawMakeClient, opts) {
2890
2891
  registerOwnAccountTools(server, makeClient);
2891
2892
  // Creative assets (creative.ts): the free reads of nooticr-server's creative_* family.
2892
2893
  registerCreativeTools(server, makeClient);
2894
+ // Google Sheets (sheets.ts): the sheets_* family of nooticr-server BK2.
2895
+ registerSheetsTools(server, makeClient);
2893
2896
  registerConnectionTools(server, makeClient);
2894
2897
  // AI employees (employees.ts): hire, list, pause, fire, and approve or reject what they queue.
2895
2898
  registerEmployeeTools(server, makeClient);
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.65",
4
+ "version": "1.26.67",
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": {