lotux-mcp 0.24.0 → 0.26.0

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/tools.mjs +27 -8
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lotux-mcp",
3
- "version": "0.24.0",
3
+ "version": "0.26.0",
4
4
  "type": "module",
5
5
  "description": "Local MCP server that lets Claude create LOTUX concepts (fetch brand-DB entities, create concepts, fill scripts + copy). Talks to the LOTUX app with a personal token; no business logic or secrets live here.",
6
6
  "bin": {
package/tools.mjs CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  import { z } from "zod";
8
8
 
9
- export const SERVER_INFO = { name: "lotux", version: "0.22.0" };
9
+ export const SERVER_INFO = { name: "lotux", version: "0.25.0" };
10
10
 
11
11
  export function registerTools(server, rpc) {
12
12
  const ok = (result) => ({
@@ -75,7 +75,7 @@ export function registerTools(server, rpc) {
75
75
 
76
76
  server.tool(
77
77
  "plan_items",
78
- "Plan 1–60 items on ONE person's calendar on a date. With start_time they run back to back, each as long as that person's HR output allows (40 h ÷ weekly output; tasks 1 h); without start_time they land all-day. The person becomes the editor (post) or media buyer (go_live); on a task they are added to the assignees. Planning concept blocks needs Creative edit rights. More than 10 items → returns a PREVIEW only and changes nothing: show the list, ask for a yes, then call apply_planning_preview. Never deletes anything.",
78
+ "Plan 1–60 items on ONE person's calendar on a date. With start_time they run back to back, each as long as that person's HR output allows (40 h ÷ weekly output; tasks 1 h); without start_time they land all-day. The person becomes the maker (post: video editor on video concepts, static designer on statics) or media buyer (go_live); on a task they are added to the assignees. Planning concept blocks needs Creative edit rights. More than 10 items → returns a PREVIEW only and changes nothing: show the list, ask for a yes, then call apply_planning_preview. Never deletes anything.",
79
79
  {
80
80
  person: z.string().describe("id, name or 'me'"),
81
81
  items: PLAN_ITEMS,
@@ -168,14 +168,14 @@ export function registerTools(server, rpc) {
168
168
 
169
169
  server.tool(
170
170
  "get_brand",
171
- "One client's workspace settings: slug, client_status, second brain (Kennisbank id + name, shared with the client or not), workspace_folder, dropbox_client_path, raw_footage_paths, Claude project name, the Claude project kit (instructions text) and the active work claims.",
171
+ "One client's workspace settings: ad_languages (main + translations), slug, client_status, second brain (Kennisbank id + name, shared with the client or not), workspace_folder, dropbox_client_path, raw_footage_paths, Claude project name, the Claude project kit (instructions text) and the active work claims.",
172
172
  { account_id: z.string().describe("Brand id from list_brands") },
173
173
  async (args) => ok(await rpc("get_brand", args))
174
174
  );
175
175
 
176
176
  server.tool(
177
177
  "update_brand_settings",
178
- "Change a client's workspace settings. Omitted fields stay as they are. slug is refused once client setup has run (it names the workspace folder). dropbox_client_path follows the Dropbox connection and workspace_folder follows the slug, so neither is set here.",
178
+ "Change a client's workspace settings, incl. the brand's ad languages (main_language + up to 2 translation_languages). Omitted fields stay as they are. slug is refused once client setup has run (it names the workspace folder). dropbox_client_path follows the Dropbox connection and workspace_folder follows the slug, so neither is set here.",
179
179
  {
180
180
  account_id: z.string().describe("Brand id from list_brands"),
181
181
  client_status: z.enum(["active", "archived", "internal"]).optional(),
@@ -183,6 +183,11 @@ export function registerTools(server, rpc) {
183
183
  kb_workspace_id: z.string().nullable().optional().describe("Kennisbank workspace id (list_kb_workspaces) to link as the second brain"),
184
184
  raw_footage_paths: z.array(z.string()).optional().describe("Dropbox paths, e.g. /CLIENTS/Active/Dr. Kegel/01_video/01_raw footage"),
185
185
  claude_project_name: z.string().optional(),
186
+ main_language: z.string().optional().describe("Ad languages: the main (script base) language for new concepts, full-written, e.g. Dutch"),
187
+ translation_languages: z
188
+ .array(z.string())
189
+ .optional()
190
+ .describe("Ad languages: up to 2 translation languages, full-written (e.g. [\"French\", \"English\"]). New concepts get a script tab for each. [] clears them."),
186
191
  },
187
192
  async (args) => ok(await rpc("update_brand_settings", args))
188
193
  );
@@ -222,7 +227,7 @@ export function registerTools(server, rpc) {
222
227
 
223
228
  server.tool(
224
229
  "claim_work",
225
- "Claim a piece of work before writing files for it. kind: concept (ref = concept id; prep / edit / revise / re-render one concept; 6 h), inventory (the brand's inventory run; 6 h), brand_shared (files several concepts of the brand use: logos, shared media, shared code; 15 min), house_rules (the shared house rules or style spec, agency-wide; 15 min). Returns {ok: true, claim_id, expires_at} or {ok: false, held_by, since, expires_at} when someone else holds it: then don't write, ask or wait. A concept claim warns when you're not its editor and sets you as editor when it has none.",
230
+ "Claim a piece of work before writing files for it. kind: concept (ref = concept id; prep / edit / revise / re-render one concept; 6 h), inventory (the brand's inventory run; 6 h), brand_shared (files several concepts of the brand use: logos, shared media, shared code; 15 min), house_rules (the shared house rules or style spec, agency-wide; 15 min). Returns {ok: true, claim_id, expires_at} or {ok: false, held_by, since, expires_at} when someone else holds it: then don't write, ask or wait. A concept claim warns when you're not its maker (video editor on video, static designer otherwise) and sets you as maker when it has none.",
226
231
  {
227
232
  account_id: z.string().describe("Brand id from list_brands (house_rules: any brand of the agency, or the agency)"),
228
233
  kind: z.enum(["concept", "inventory", "brand_shared", "house_rules"]),
@@ -368,7 +373,7 @@ export function registerTools(server, rpc) {
368
373
 
369
374
  server.tool(
370
375
  "list_members",
371
- "List the agency team members (id + name) assignable to a concept's roles. Use the ids with set_concept_fields (strategist_id, copy_checker_id, editor_id, media_buyer_id, final_check_id).",
376
+ "List the agency team members (id + name) assignable to a concept's roles. Use the ids with set_concept_fields (strategist_id, copy_checker_id, editor_id = video editor, designer_id = static designer, media_buyer_id, final_check_id).",
372
377
  { account_id: z.string().describe("Brand id from list_brands") },
373
378
  async ({ account_id }) => ok(await rpc("list_members", { account_id }))
374
379
  );
@@ -398,7 +403,7 @@ export function registerTools(server, rpc) {
398
403
 
399
404
  server.tool(
400
405
  "set_concept_fields",
401
- "Update fields on an existing concept — including renaming it via title. Any omitted field is left unchanged; the id arrays, when given, replace the full set of links. Assign team roles by passing a person id from list_members into strategist_id / copy_checker_id / editor_id / media_buyer_id / final_check_id.",
406
+ "Update fields on an existing concept — including renaming it via title. Any omitted field is left unchanged; the id arrays, when given, replace the full set of links. Assign team roles by passing a person id from list_members into strategist_id / copy_checker_id / editor_id (video editor) / designer_id (static designer) / media_buyer_id / final_check_id.",
402
407
  {
403
408
  concept_id: z.string(),
404
409
  title: z.string().optional().describe("Rename the concept (blank is ignored — title is required)"),
@@ -438,7 +443,8 @@ export function registerTools(server, rpc) {
438
443
  // Team roles — pass a member id from list_members.
439
444
  strategist_id: z.string().optional().describe("Member id from list_members"),
440
445
  copy_checker_id: z.string().optional().describe("Member id from list_members"),
441
- editor_id: z.string().optional().describe("Member id from list_members"),
446
+ editor_id: z.string().optional().describe("Video editor: member id from list_members"),
447
+ designer_id: z.string().optional().describe("Static designer: member id from list_members"),
442
448
  media_buyer_id: z.string().optional().describe("Member id from list_members"),
443
449
  final_check_id: z.string().optional().describe("Member id from list_members"),
444
450
  },
@@ -537,6 +543,19 @@ export function registerTools(server, rpc) {
537
543
  async ({ concept_id, confirm_code }) => ok(await rpc("delete_concept", { concept_id, confirm_code }))
538
544
  );
539
545
 
546
+ server.tool(
547
+ "apply_brand_languages",
548
+ "Bring existing concepts in line with the brand's ad languages (get_brand → ad_languages; change them with update_brand_settings). Pick concepts by concept_ids and/or stage (stage key like copy_check or the column name like \"Copy check\"); max 100 per call. Per concept: the base language becomes the brand's main language (the base text is re-labelled, not translated) and every script gets a tab for each missing brand language, holding a copy of the base text. Existing tabs are never changed or removed. dry_run: true shows the plan only. More than 10 concepts to change returns a preview first — show it to the user, then call again with confirm: true. Returns per concept what changed, the new tabs (script_id + language) and the linked ad copy with its language. LOTUX does not translate: afterwards write each translation with save_script and fix ad copy languages with update_copy / add_copy_translation.",
549
+ {
550
+ account_id: z.string().describe("Brand id from list_brands"),
551
+ concept_ids: z.array(z.string()).optional(),
552
+ stage: z.string().optional().describe("Stage key (copy_check) or column name (Copy check)"),
553
+ dry_run: z.boolean().optional(),
554
+ confirm: z.boolean().optional().describe("Required when more than 10 concepts would change"),
555
+ },
556
+ async (args) => ok(await rpc("apply_brand_languages", args))
557
+ );
558
+
540
559
  server.tool(
541
560
  "set_base_language",
542
561
  "Set the concept's base script language, e.g. English. Existing base-language script texts are re-labelled to the new language.",