lotux-mcp 0.16.0 → 0.18.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.
- package/README.md +3 -0
- package/index.mjs +169 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -66,6 +66,9 @@ Restart Claude, then try: *"list my LOTUX brands"*.
|
|
|
66
66
|
| `content_options` / `create_flywheel` / `update_flywheel` / `list_flywheels` / `get_flywheel` | Content flywheels (Marketing → Content, agency-only): one long-form video. Start with `content_options`; give flywheels a `key` so re-runs reuse them |
|
|
67
67
|
| `push_assets` / `add_asset` / `get_asset` / `list_assets` / `update_asset` / `set_asset_status` / `add_comment` / `delete_asset` | Content assets, upserted on their `key`. Statuses: scripting → production → ready_for_post → post_production → to_review → revisions → approved → live → testing → scaling → turned_off. Content you write lands in `to_review`; Claude can't set revisions, approve, or set live / testing / scaling / turned_off; deleting signed-off work needs `force`. Each asset carries `raw_footage_url` (raw files folder) and `final_asset_url` (the export — a change sends it to review), plus its content ID (`code`) and `name` = `"<code> - <title>"`; pass `code` instead of `asset_id` anywhere |
|
|
68
68
|
| `get_setup_checks` / `set_setup_check` | A flywheel's setup checklist (Instagram auto-replies, setter replies, reel covers, YouTube, Klaviyo flow) — tick a step by `check_key` after doing it |
|
|
69
|
+
| `list_team` / `get_team_member` / `update_team_member` | HR (agency-only, needs HR access): the team — role, lead, status, dates, KPIs, archive/restore. No pay, no delete, no invites (app only) |
|
|
70
|
+
| `list_job_roles` / `get_job_role` / `update_job_role` / `create_job_role` | HR → Responsibilities: role templates (responsibilities, KPIs, capacity, owned columns). Lists are replaced whole |
|
|
71
|
+
| `list_sops` / `get_sop` / `suggest_sop_change` / `create_sop_draft` | SOPs (Brain → SOPs). Claude only **suggests** changes: the SOP owner or an editor approves them in LOTUX, and the changelog shows the person "(via Claude)". One open suggestion per person per SOP (a new one replaces it). New SOPs start as a draft (needs Brain edit). No approve, edit, publish or delete over MCP |
|
|
69
72
|
|
|
70
73
|
Beats (`add_beat`, `update_beat`, `delete_beat`, `set_beat_translation`, `add_language`) were
|
|
71
74
|
retired in Sep 2026 — scripts are documents now. Calling one returns a message pointing at the
|
package/index.mjs
CHANGED
|
@@ -52,7 +52,7 @@ const ok = (result) => ({
|
|
|
52
52
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
53
53
|
});
|
|
54
54
|
|
|
55
|
-
const server = new McpServer({ name: "lotux", version: "0.
|
|
55
|
+
const server = new McpServer({ name: "lotux", version: "0.18.0" });
|
|
56
56
|
|
|
57
57
|
// ── Reads ─────────────────────────────────────────────────────────────────────
|
|
58
58
|
|
|
@@ -589,8 +589,8 @@ server.tool(
|
|
|
589
589
|
|
|
590
590
|
const PLATFORMS = ["youtube", "youtube_shorts", "instagram", "tiktok", "linkedin", "x", "email", "meta_ads", "opt_in_page", "google_doc", "internal"];
|
|
591
591
|
// Statuses follow production. You may set the first five; revisions,
|
|
592
|
-
// approved, live, testing, scaling and
|
|
593
|
-
const STATUSES = ["scripting", "production", "ready_for_post", "post_production", "to_review", "revisions", "approved", "live", "testing", "scaling", "turned_off"];
|
|
592
|
+
// approved, to_launch, live, testing, scaling, turned_off and archived are a human's call.
|
|
593
|
+
const STATUSES = ["scripting", "production", "ready_for_post", "post_production", "to_review", "revisions", "approved", "to_launch", "live", "testing", "scaling", "turned_off", "archived"];
|
|
594
594
|
const CLAUDE_STATUSES = ["scripting", "production", "ready_for_post", "post_production", "to_review"];
|
|
595
595
|
// video = "To long form".
|
|
596
596
|
const CTAS = ["none", "video", "lead_magnet", "dm_keyword", "booking", "follow"];
|
|
@@ -613,6 +613,7 @@ const assetRef = {
|
|
|
613
613
|
const assetFields = {
|
|
614
614
|
asset_type: z.enum(ASSET_TYPES).optional(),
|
|
615
615
|
title: z.string().optional().describe("Card title. long_form: the YouTube title"),
|
|
616
|
+
ad_format: z.enum(["video", "static"]).optional().describe('VSL + Follower ads: its Dropbox folder (Video/ or Static/). Left out = "static" when the title says static, else video'),
|
|
616
617
|
platform: z.enum(PLATFORMS).optional().describe("Defaults from the type"),
|
|
617
618
|
cross_post: z.array(z.enum(PLATFORMS)).optional().describe("Also posted on (defaults from the type)"),
|
|
618
619
|
account: z.enum(["personal", "company"]).optional().describe("personal = Niels, company = the agency"),
|
|
@@ -773,7 +774,7 @@ server.tool(
|
|
|
773
774
|
|
|
774
775
|
server.tool(
|
|
775
776
|
"set_asset_status",
|
|
776
|
-
"Move an asset through scripting, production, ready_for_post, post_production or to_review (to_review hands it to a human). Revisions, approved, live, testing, scaling and
|
|
777
|
+
"Move an asset through scripting, production, ready_for_post, post_production or to_review (to_review hands it to a human). Revisions, approved, to_launch, live, testing, scaling, turned_off and archived are a human's call in LOTUX.",
|
|
777
778
|
{
|
|
778
779
|
...assetRef,
|
|
779
780
|
status: z.enum(CLAUDE_STATUSES),
|
|
@@ -817,6 +818,170 @@ server.tool(
|
|
|
817
818
|
async (args) => ok(await rpc("set_setup_check", args))
|
|
818
819
|
);
|
|
819
820
|
|
|
821
|
+
// ---------------------------------------------------------------------------
|
|
822
|
+
// HR (agency only): the team and the role templates (Responsibilities).
|
|
823
|
+
// Needs HR access in LOTUX. Pay, contracts, documents, deleting people and
|
|
824
|
+
// invites stay in the app on purpose.
|
|
825
|
+
// ---------------------------------------------------------------------------
|
|
826
|
+
|
|
827
|
+
const kpiShape = z.object({
|
|
828
|
+
label: z.string(),
|
|
829
|
+
target: z.string().nullable().optional(),
|
|
830
|
+
source: z.string().nullable().optional(),
|
|
831
|
+
});
|
|
832
|
+
|
|
833
|
+
server.tool(
|
|
834
|
+
"list_team",
|
|
835
|
+
"Everyone at the agency in HR: name, job role, lead, is_lead, status (onboarding / active / left), employment, start/end date, LOTUX login yes/no. Archived and left people only with include_archived. No pay.",
|
|
836
|
+
{ include_archived: z.boolean().optional(), account_id: z.string().optional().describe("Agency id; omit for your own agency") },
|
|
837
|
+
async (args) => ok(await rpc("list_team", args))
|
|
838
|
+
);
|
|
839
|
+
|
|
840
|
+
server.tool(
|
|
841
|
+
"get_team_member",
|
|
842
|
+
"One person in HR: role, lead, who they lead, contact fields, their own KPIs (else the role's apply), access checklist and their last 20 notes. No pay.",
|
|
843
|
+
{ member: z.string().describe("Person id or (part of) their name"), account_id: z.string().optional() },
|
|
844
|
+
async (args) => ok(await rpc("get_team_member", args))
|
|
845
|
+
);
|
|
846
|
+
|
|
847
|
+
server.tool(
|
|
848
|
+
"update_team_member",
|
|
849
|
+
"Change a person in HR. Only the fields you send change. role and lead take a name or id (null clears). archived true hides them from HR lists, the lead picker and payroll (data kept); false restores. kpis replaces their own KPI list ([] = use the role's KPIs). Can't change pay, delete people or send invites — do that in the app.",
|
|
850
|
+
{
|
|
851
|
+
member: z.string().describe("Person id or name"),
|
|
852
|
+
name: z.string().optional(),
|
|
853
|
+
role: z.string().nullable().optional().describe("Job role name or id (list_job_roles)"),
|
|
854
|
+
lead: z.string().nullable().optional().describe("Lead's name or id"),
|
|
855
|
+
is_lead: z.boolean().optional(),
|
|
856
|
+
status: z.enum(["onboarding", "active", "left"]).optional(),
|
|
857
|
+
employment: z.enum(["employee", "freelance"]).nullable().optional(),
|
|
858
|
+
start_date: z.string().nullable().optional().describe("YYYY-MM-DD"),
|
|
859
|
+
end_date: z.string().nullable().optional().describe("YYYY-MM-DD"),
|
|
860
|
+
lotux_email: z.string().nullable().optional(),
|
|
861
|
+
personal_email: z.string().nullable().optional(),
|
|
862
|
+
phone: z.string().nullable().optional(),
|
|
863
|
+
archived: z.boolean().optional(),
|
|
864
|
+
kpis: z.array(kpiShape).optional(),
|
|
865
|
+
account_id: z.string().optional(),
|
|
866
|
+
},
|
|
867
|
+
async (args) => ok(await rpc("update_team_member", args))
|
|
868
|
+
);
|
|
869
|
+
|
|
870
|
+
server.tool(
|
|
871
|
+
"list_job_roles",
|
|
872
|
+
"The role templates in HR → Responsibilities: id, name, one-line summary and how many people have the role.",
|
|
873
|
+
{ account_id: z.string().optional() },
|
|
874
|
+
async (args) => ok(await rpc("list_job_roles", args))
|
|
875
|
+
);
|
|
876
|
+
|
|
877
|
+
server.tool(
|
|
878
|
+
"get_job_role",
|
|
879
|
+
"One role template in full: title, one-liner, what they obsess over, who they work with, what failure looks like, what they're not responsible for, responsibilities, KPIs, capacity, the LOTUX columns they own, and who has the role.",
|
|
880
|
+
{ role: z.string().describe("Role id or exact name"), account_id: z.string().optional() },
|
|
881
|
+
async (args) => ok(await rpc("get_job_role", args))
|
|
882
|
+
);
|
|
883
|
+
|
|
884
|
+
const roleFields = {
|
|
885
|
+
role_label: z.string().nullable().optional(),
|
|
886
|
+
title: z.string().nullable().optional(),
|
|
887
|
+
one_line: z.string().nullable().optional(),
|
|
888
|
+
obsess_over: z.string().nullable().optional(),
|
|
889
|
+
works_with: z.string().nullable().optional(),
|
|
890
|
+
failure_looks_like: z.string().nullable().optional(),
|
|
891
|
+
not_responsible_for: z.string().nullable().optional(),
|
|
892
|
+
kpi_headline: z.string().nullable().optional(),
|
|
893
|
+
kpi_detail: z.string().nullable().optional(),
|
|
894
|
+
capacity_count: z.number().int().nonnegative().nullable().optional(),
|
|
895
|
+
capacity_unit: z.enum(["clients", "accounts", "brands"]).nullable().optional(),
|
|
896
|
+
software_columns: z.array(z.string()).optional().describe("LOTUX columns this role owns"),
|
|
897
|
+
responsibilities: z.array(z.string()).optional().describe("Replaces the whole list, in order"),
|
|
898
|
+
kpis: z.array(kpiShape).optional().describe("Replaces the whole list, in order"),
|
|
899
|
+
};
|
|
900
|
+
|
|
901
|
+
server.tool(
|
|
902
|
+
"update_job_role",
|
|
903
|
+
"Change a role template (HR → Responsibilities). Only the fields you send change; responsibilities and kpis replace the whole list, so send the full list. new_name renames the role.",
|
|
904
|
+
{
|
|
905
|
+
role: z.string().describe("Role id or exact name"),
|
|
906
|
+
new_name: z.string().optional(),
|
|
907
|
+
...roleFields,
|
|
908
|
+
account_id: z.string().optional(),
|
|
909
|
+
},
|
|
910
|
+
async (args) => ok(await rpc("update_job_role", args))
|
|
911
|
+
);
|
|
912
|
+
|
|
913
|
+
server.tool(
|
|
914
|
+
"create_job_role",
|
|
915
|
+
"Create a role template (starts with the standard Start here flow) and fill it in one go. An existing role with the same name is returned (and updated with the fields you send) instead of a duplicate.",
|
|
916
|
+
{ name: z.string(), ...roleFields, account_id: z.string().optional() },
|
|
917
|
+
async (args) => ok(await rpc("create_job_role", args))
|
|
918
|
+
);
|
|
919
|
+
|
|
920
|
+
// ── SOPs (Brain → SOPs) ────────────────────────────────────────────────────
|
|
921
|
+
// Claude reads SOPs and SUGGESTS changes; a person approves them in LOTUX.
|
|
922
|
+
// There is deliberately no tool to approve, edit or publish an SOP.
|
|
923
|
+
|
|
924
|
+
const sopStep = z.object({
|
|
925
|
+
title: z.string(),
|
|
926
|
+
detail: z.string().optional(),
|
|
927
|
+
sub: z.array(z.string()).optional().describe("Sub-points"),
|
|
928
|
+
when: z.string().optional().describe('Timing chip, e.g. "Day 0, 60 min"'),
|
|
929
|
+
});
|
|
930
|
+
const sopContent = {
|
|
931
|
+
summary: z.string().optional().describe("One line, shown on the card"),
|
|
932
|
+
purpose: z.string().optional(),
|
|
933
|
+
scope: z.string().optional().describe('"At a glance": lines like "When: …" / "Trigger: …"'),
|
|
934
|
+
materials: z.array(z.string()).optional().describe("Before you start"),
|
|
935
|
+
steps: z.array(sopStep).optional().describe("The FULL ordered list of steps. Who does a step is kept from the step at the same position."),
|
|
936
|
+
done_when: z.string().optional(),
|
|
937
|
+
troubleshooting: z.array(z.object({ risk: z.string(), fix: z.string() })).optional().describe("Watch out"),
|
|
938
|
+
tools: z.array(z.string()).optional(),
|
|
939
|
+
video_url: z.string().nullable().optional().describe("Walkthrough link (http/https)"),
|
|
940
|
+
};
|
|
941
|
+
|
|
942
|
+
server.tool(
|
|
943
|
+
"list_sops",
|
|
944
|
+
"The SOPs in LOTUX (Brain → SOPs): code, title, phase, status, owner, number of steps and open suggestions. Archived ones are left out unless you ask for status archived.",
|
|
945
|
+
{
|
|
946
|
+
phase: z.string().optional().describe("Phase key, e.g. lead, sell, contract, shoot, launch, run"),
|
|
947
|
+
status: z.enum(["draft", "review", "published", "archived"]).optional(),
|
|
948
|
+
search: z.string().optional(),
|
|
949
|
+
account_id: z.string().optional().describe("Omit for the agency"),
|
|
950
|
+
},
|
|
951
|
+
async (args) => ok(await rpc("list_sops", args))
|
|
952
|
+
);
|
|
953
|
+
|
|
954
|
+
server.tool(
|
|
955
|
+
"get_sop",
|
|
956
|
+
"One SOP in full: purpose, at a glance, before you start, steps (with who does them), done when, watch out, tools, open suggestions and the changelog.",
|
|
957
|
+
{ sop: z.string().describe("SOP id, code (e.g. OC1) or title"), account_id: z.string().optional() },
|
|
958
|
+
async (args) => ok(await rpc("get_sop", args))
|
|
959
|
+
);
|
|
960
|
+
|
|
961
|
+
server.tool(
|
|
962
|
+
"suggest_sop_change",
|
|
963
|
+
"Suggest a change to an SOP. Nothing changes until the SOP owner or an editor approves it in LOTUX; the changelog then shows you (via Claude) as the author. Send only the fields you change; lists (steps, materials, troubleshooting, tools) replace the whole list. One open suggestion per person per SOP: a new one replaces your earlier one.",
|
|
964
|
+
{
|
|
965
|
+
sop: z.string().describe("SOP id, code or title"),
|
|
966
|
+
changes: z.object(sopContent).extend({ title: z.string().optional() }),
|
|
967
|
+
reason: z.string().describe("Why this change is needed (the approver reads it)"),
|
|
968
|
+
account_id: z.string().optional(),
|
|
969
|
+
},
|
|
970
|
+
async (args) => ok(await rpc("suggest_sop_change", args))
|
|
971
|
+
);
|
|
972
|
+
|
|
973
|
+
server.tool(
|
|
974
|
+
"create_sop_draft",
|
|
975
|
+
"Create a new SOP as a DRAFT (needs Brain edit rights). It is not live: a person sets owner and category and publishes it in LOTUX. The changelog logs it as created by you (via Claude). An existing title is refused: suggest a change to that SOP instead.",
|
|
976
|
+
{
|
|
977
|
+
title: z.string(),
|
|
978
|
+
phase: z.string().optional().describe("Board column: lead, sell, contract, team_onboard, client_onboard, preprod, shoot, production, launch, run, retain, offboard, always, team"),
|
|
979
|
+
...sopContent,
|
|
980
|
+
account_id: z.string().optional(),
|
|
981
|
+
},
|
|
982
|
+
async (args) => ok(await rpc("create_sop_draft", args))
|
|
983
|
+
);
|
|
984
|
+
|
|
820
985
|
async function main() {
|
|
821
986
|
const transport = new StdioServerTransport();
|
|
822
987
|
await server.connect(transport);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lotux-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.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": {
|