agencyhandy-mcp 1.7.1 → 1.7.2

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/PRODUCTION.md CHANGED
@@ -28,5 +28,6 @@ Repo: https://github.com/AgencyHandy/agency-backend-mcp
28
28
 
29
29
  ## Version
30
30
 
31
+ `1.7.2` — API builder context resource (`ah://api-context`): leads/orders/tasks/comments/labels/clients/invoices/vouchers/forms gotchas + unit/mutation tests.
31
32
  `1.7.1` — Security harden (SSRF path/url guards, axios/deps audit clean) + mutation/npx entry tests; owner insights tolerate null/sparse API rows.
32
33
  `1.7.0` — Owner insights: cash risk, proposal pipeline aging, lead segments, client churn heuristics; plus advanced context packs.
package/README.md CHANGED
@@ -209,11 +209,11 @@ Order create is intentionally omitted (multipart / service-package heavy). Use `
209
209
  - **Lists / gets:** proposals, projects, tickets, tasks, invoices, members, client companies, services, forms, dashboard
210
210
  - **Platform:** chat, email-to-ticket settings, webhooks, order & platform subscriptions
211
211
 
212
- Prefer `*_context` tools and the `ah://full-context` resource before inventing ObjectIds.
212
+ Prefer `*_context` tools and the `ah://full-context` resource before inventing ObjectIds. For custom API tooling, also read `ah://api-context` (leads/orders/tasks/comments/labels/clients/invoices/vouchers/forms gotchas).
213
213
 
214
214
  ### Resources & prompts
215
215
 
216
- Resources: `ah://full-context`, `ah://glossary`, `ah://models`, `ah://fields`, `ah://workflows`, `ah://workflows-platform`, `ah://usage`, …
216
+ Resources: `ah://full-context`, `ah://glossary`, `ah://models`, `ah://fields`, `ah://workflows`, `ah://workflows-platform`, `ah://api-context`, `ah://usage`, …
217
217
 
218
218
  Prompts: `agency_handy_orientation`, `investigate_ticket`, `investigate_project`, `investigate_proposal`, `investigate_task`
219
219
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "agencyhandy-mcp",
3
- "version": "1.7.1",
4
- "description": "Official Agency Handy MCP server — owner insights + advanced context packs",
3
+ "version": "1.7.2",
4
+ "description": "Official Agency Handy MCP server — owner insights, context packs, API builder context",
5
5
  "type": "module",
6
6
  "main": "src/server.js",
7
7
  "bin": {
@@ -19,8 +19,9 @@
19
19
  "scripts": {
20
20
  "start": "node src/server.js",
21
21
  "dev": "node --watch src/server.js",
22
- "test": "node scripts/smoke.mjs && node scripts/unit-custom-fields.mjs && node scripts/unit-work-context.mjs && node scripts/unit-owner-insights.mjs && node scripts/unit-security-mutate.mjs",
22
+ "test": "node scripts/smoke.mjs && node scripts/unit-api-context.mjs && node scripts/unit-custom-fields.mjs && node scripts/unit-work-context.mjs && node scripts/unit-owner-insights.mjs && node scripts/unit-security-mutate.mjs",
23
23
  "test:security": "node scripts/unit-security-mutate.mjs",
24
+ "test:api-context": "node scripts/unit-api-context.mjs",
24
25
  "test:live": "SMOKE_LIVE=1 node scripts/smoke.mjs",
25
26
  "test:live:custom-fields": "node scripts/live-custom-fields.mjs",
26
27
  "prepublishOnly": "npm test && npm audit --audit-level=high"
package/src/lib.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import axios from "axios";
2
2
  import { z } from "zod";
3
3
 
4
- export const VERSION = "1.7.1";
4
+ export const VERSION = "1.7.2";
5
5
 
6
6
  export const READ_ONLY = false;
7
7
 
package/src/prompts.js CHANGED
@@ -76,12 +76,13 @@ export function getPrompt(name, args = {}) {
76
76
  type: "text",
77
77
  text: `Read MCP resource ah://full-context (preferred single read).
78
78
  If the host cannot load the bundle, read in order:
79
- ah://glossary → ah://models → ah://fields → ah://workflows → ah://workflows-platform → ah://usage → ah://tool-guide → ah://response-shape
79
+ ah://glossary → ah://models → ah://fields → ah://workflows → ah://workflows-platform → ah://api-context → ah://usage → ah://tool-guide → ah://response-shape
80
80
 
81
81
  Then confirm:
82
82
  - proposal=quotation, project=order(pid), ticket=task type=issue
83
+ - leads live under /members (bulk-lead, isConvertedClient=false); never PUT /accounts/update without accountId
83
84
  - Core delivery workflows (A–O) plus platform/ops (P–CC): auth, email-to-ticket, chat, webhooks, subscriptions
84
- - Field meanings from ah://fields
85
+ - Field meanings from ah://fields; path/permission gotchas from ah://api-context
85
86
  - Prefer *_context tools; never invent ObjectIds
86
87
  - People by name: ah_member_resolve first (unique|ambiguous|none). If ambiguous, ask user. Never guess.
87
88
  - Status by label: ah_status_resolve first. Same rule.
package/src/resources.js CHANGED
@@ -9,7 +9,7 @@ export const RESOURCES = [
9
9
  name: "Full Agency Handy MCP context (bundle)",
10
10
  mimeType: "text/markdown",
11
11
  description:
12
- "Single bundle: glossary, models, fields, product + platform workflows, usage, tools.",
12
+ "Single bundle: glossary, models, fields, product + platform workflows, API builder context, usage, tools.",
13
13
  },
14
14
  {
15
15
  uri: "ah://glossary",
@@ -68,6 +68,13 @@ export const RESOURCES = [
68
68
  mimeType: "text/markdown",
69
69
  description: "How to interpret ok/status/data from ah_* tools.",
70
70
  },
71
+ {
72
+ uri: "ah://api-context",
73
+ name: "API builder context (gotchas)",
74
+ mimeType: "text/markdown",
75
+ description:
76
+ "Missing/ambiguous API context for MCP builders: leads, orders, tasks, comments, invoices, forms, webhooks, permissions.",
77
+ },
71
78
  ];
72
79
 
73
80
  function md(uri, text) {
@@ -88,6 +95,7 @@ const GLOSSARY = `# Agency Handy glossary
88
95
  | Client company | ClientCompany | \`/api/v1/client-companies\` | B2B org |
89
96
  | Form | Form / PublicForm | \`/forms\`, \`/public-forms\` | Intake → lead/order |
90
97
  | Comment | Comment / QuotationComment | \`/comments\`, \`/quotation-comments\` | Different namespaces |
98
+ | Lead | Member (client role, not converted) | \`/members\` + \`bulk-lead\` | **No** \`/leads\` mount — see \`ah://api-context\` |
91
99
  | Time entry | TimeTracker | \`/time-trackers\` | \`taskId\` / \`projectId\` |
92
100
  | Task status | TaskStatus | \`/taskStatus\` | Board lane ObjectIds |
93
101
  | Company | Company | \`/company\` | Workspace tenant |
@@ -96,6 +104,7 @@ const GLOSSARY = `# Agency Handy glossary
96
104
  | Dashboard | Dashboard | \`/dashboards\` | Aggregates |
97
105
 
98
106
  Auth: \`x-api-key\` (member API token). Optional \`AGENCY_WORKSPACE_ID\`.
107
+ Read **\`ah://api-context\`** for path renames, permission names, and write-side gotchas.
99
108
  `;
100
109
 
101
110
  const MODELS = `# Domain models & relationships
@@ -134,6 +143,7 @@ Company (workspace)
134
143
  - **Ticket vs Task**: same collection; filter/list with \`type=issue\` vs \`type=task\`.
135
144
  - **Project vs Order**: UI "Project" = API \`orders\` = tool \`pid\`.
136
145
  - **Proposal vs Quotation**: UI "Proposal" = API \`quotations\`.
146
+ - **Lead vs Client vs Team**: same \`Member\` collection — lead = client role + \`isConvertedClient=false\`; client = converted; team = non-client role.
137
147
  - **Human ids**: \`taskId\`, order \`id\`/\`customId\`/\`prefix\` are display ids — MCP tools need Mongo \`_id\`.
138
148
 
139
149
  ## ID args for tools
@@ -679,7 +689,7 @@ If only tools: \`ah_ping\` → use \`forAnyLlm\`; still call \`ah_*_context\`.
679
689
 
680
690
  ## Startup (every model)
681
691
  1. \`ah_ping\`
682
- 2. Read \`ah://full-context\` OR (\`ah://models\` + \`ah://fields\` + \`ah://workflows\` + \`ah://workflows-platform\` + \`ah://usage\`)
692
+ 2. Read \`ah://full-context\` OR (\`ah://models\` + \`ah://fields\` + \`ah://workflows\` + \`ah://workflows-platform\` + \`ah://api-context\` + \`ah://usage\`)
683
693
  3. One context pack / investigate prompt
684
694
  4. Filtered lists before more gets
685
695
 
@@ -691,6 +701,7 @@ If only tools: \`ah_ping\` → use \`forAnyLlm\`; still call \`ah_*_context\`.
691
701
  - Billing → \`ah_invoice_list\` → \`ah_invoice_context\`
692
702
  - Board → \`ah_task_statuses\` then filtered \`ah_task_list\` / \`ah_ticket_list\`
693
703
  - Email-created ticket / chat / webhooks → \`ah://workflows-platform\` then ticket/project tools
704
+ - Path / permission / lead confusion → \`ah://api-context\`
694
705
 
695
706
  ## Token hygiene
696
707
  - Prefer \`*_context\` (1 call) over 3–5 gets
@@ -701,9 +712,10 @@ If only tools: \`ah_ping\` → use \`forAnyLlm\`; still call \`ah_*_context\`.
701
712
  ## Anti-patterns
702
713
  - Inventing ObjectIds
703
714
  - Ticket id as \`pid\`
704
- - Writes (MCP is read-oriented)
705
- - Company-wide task list when \`pid\` known → use \`ah_project_tasks\`
715
+ - Calling stale OpenAPI paths (\`/projects\`, \`/company/.../members\`)
716
+ - \`PUT /accounts/update\` without \`accountId\` (updates API-key owner)
706
717
  - Mixing \`type=issue\` / \`type=task\`
718
+ - Treating \`/subscriptions\` as client order-subscriptions
707
719
  `;
708
720
 
709
721
  const TOOL_GUIDE = `# Tool selection
@@ -760,7 +772,7 @@ const MODULES = `# Modules
760
772
  | Platform SaaS sub | \`ah_platform_subscription_list\` | subscriptions/list |
761
773
  | Meta | \`ah_ping\` | local |
762
774
 
763
- Resources: full-context, glossary, models, fields, workflows, workflows-platform, usage, tool-guide, modules, response-shape.
775
+ Resources: full-context, glossary, models, fields, workflows, workflows-platform, usage, tool-guide, modules, response-shape, api-context.
764
776
  Prompts: orientation + investigate_*.
765
777
  `;
766
778
 
@@ -803,6 +815,126 @@ Zod argument errors: \`{ ok:false, error:"Invalid tool arguments", details:[...]
803
815
  Config errors: \`Invalid MCP server environment variables\`.
804
816
  `;
805
817
 
818
+ const API_CONTEXT = `# API builder context (missing / easy-to-miss)
819
+
820
+ Use this when mapping product tools to live Agency Handy HTTP APIs, or when OpenAPI and runtime disagree. Prefer \`ah_*\` tools when available — this doc is the ground truth for paths and gotchas.
821
+
822
+ ## Cross-cutting
823
+ - Live base: \`/api/v1/*\`. OpenAPI often still shows stale \`/projects/*\` and \`/company/{id}/members/*\` — **do not call those**.
824
+ - Auth: \`x-api-key\` **or** bearer — never both. API key JWT sets company from claims.
825
+ - Many deletes are **PUT + id array**, not HTTP DELETE (\`/tasks\`, \`/invoices\`, \`/quotations\`, \`/vouchers\`, \`/webhooks\`, \`/orders/delete\`, \`/members/bulk-delete\`).
826
+ - Pagination: orders/invoices/tasks often \`skip\`+\`limit\`; quotations use \`page\`+\`limit\`.
827
+ - 403 \`PermissionError\` ("Restricted - No permission…") = missing **permission name** on the API-key member role — not a broken token.
828
+ - Subscription expired: key still authenticates; routes with \`subscriptionExpireCheck\` return 403 \`SubscriptionExpirationError\` after grace.
829
+
830
+ ## Leads (no \`/leads\` API)
831
+ - Leads are \`Member\` rows: client role + \`isConvertedClient=false\` (same collection as team/clients).
832
+ - Create: \`POST /members/bulk-lead\` (not \`/members\` or \`/members/bulk\`).
833
+ - List: \`GET /members?isConvertedClient=false\` (+ optional pipeline \`status\`).
834
+ - Update / convert: \`PUT /accounts/update\` with **required \`accountId\`**.
835
+ - **Without \`accountId\`**, update targets the **API-key owner** (can rename the token user).
836
+ - Delete: \`PUT /members/bulk-delete\`.
837
+ - Custom fields: \`/custom-fields?type=lead\` (separate from \`client\` / \`task\`).
838
+ - Pipeline statuses: \`New | Contacted | Qualified | Working | Proposal Sent | Negotiation\`.
839
+ - Permissions: \`addNewMemberToCompany\`, \`getEmployeeDirectory\`. Plan may return \`Lead limit reached\`.
840
+ - MCP: \`ah_lead_create\`, \`ah_lead_convert\`, \`ah_member_update\` (always pass account/member id).
841
+
842
+ ## Orders / Projects
843
+ - UI Project = \`/orders\` only (not \`/projects\`).
844
+ - Get: \`GET /orders/:pid\`. Update: \`PUT /orders?pid=\` (query \`pid\` required).
845
+ - Status enum (title case): \`Pending | Ongoing | Review | Completed | Cancelled | Payment Failed\`.
846
+ - Client accept/reject: \`PUT /orders/:pid/actions\` with \`orderStatus: accept|reject\` — not the same as agency status PUT.
847
+ - Create needs \`service\`, \`packageId\`, \`packageName\`, \`clientTargetType\`, \`paymentType\` (\`oneTime|subscription\`), plus \`clientId\` **or** (\`clientCompanyId\` + \`sentToClientIds\`).
848
+ - Soft delete: \`PUT /orders/delete\`.
849
+
850
+ ## Tasks / tickets / statuses
851
+ - Board tickets = \`Task\` with \`type=issue\` via \`/company/all-tasks?type=issue|task\` — **not** the legacy \`/tickets\` collection.
852
+ - List/filter \`status\` = TaskStatus **ObjectId(s)**. Update \`PUT /tasks/:taskId\` \`status\` = **status name string** (controller resolves name → id). Passing ObjectId on update fails.
853
+ - Complete: \`PUT /tasks/mark-as-completed\` (needs \`_id\`, \`pid\`, \`memberId\`, \`assingedPersons\` — typo is real). Not the same as status \`done\`.
854
+ - OpenAPI \`POST /tasks/{id}/subtask\` and \`PATCH /tasks/{id}/reopen\` are **not mounted**. Reopen ≈ \`PUT\` with \`status: open\` on closed issues (limited). Subtasks = model \`type=subTask\` only — no external create route.
855
+ - Create requires \`name\`, \`priority\` (0–4), \`labels[]\` (required, may be \`[]\`), \`type\`; \`client\` required unless visibility \`internal|order\`.
856
+ - Permissions: \`updateTask\`, \`deleteTask\`; tickets delete via \`deleteTicket\` / \`PUT /tasks/delete-ticket\`.
857
+ - \`GET /taskStatus\` returns all types — filter \`type\` (\`task\` vs \`issue\`) client-side.
858
+
859
+ ## Assignment
860
+ - Add: \`POST /tasks/member/:taskId\` body \`{ assignmembers: [memberIds] }\`.
861
+ - Remove: \`PUT /tasks/member/:taskId\` body \`{ assignedMemberIds: [...] }\` — **different field name**.
862
+ - IDs are workspace **member** ObjectIds, not emails. Resolve with \`ah_member_resolve\`.
863
+ - Order PMs: \`assignedProjectManagers\` on \`PUT /orders?pid=\`, not task member endpoints.
864
+
865
+ ## Comments
866
+ - Task/ticket: \`/comments\` — create needs \`text\`, \`type\` (\`agencyComment|clientComment|…\`), \`taskId\`, and query **\`memberId\` required**.
867
+ - Update: \`POST /comments/:commentId/update\` (not PUT).
868
+ - Proposal comments: separate \`/quotation-comments\` — do not reuse \`/comments\`.
869
+ - \`agencyComment\` vs \`clientComment\` controls portal visibility.
870
+
871
+ ## Labels
872
+ - Company catalog create: \`POST /labels/create-for-company\`; suggest: \`GET /labels/suggest-by-company\`.
873
+ - \`POST /labels/\` is **task-scoped** (requires task id) — not global create.
874
+ - Attach/detach: \`/labels/task/:task\`, bulk \`/labels/bulk/:taskId\`.
875
+
876
+ ## Clients / members / groups
877
+ - Live path: \`/members\` (not OpenAPI \`/company/{COMPANY_ID}/members\`).
878
+ - List permission: \`getEmployeeDirectory\`. \`type\` is \`individual|company|leadCompanyMember\` — not \`client\`/\`lead\`. Role filter is a role ObjectId. \`tab=client|team\` only on \`/members/basic\`.
879
+ - Invite: \`POST /members/send-client-invite\`. Bulk team \`/members/bulk\`, bulk client \`/members/bulk-client\`, bulk lead \`/members/bulk-lead\`.
880
+ - Soft delete: \`PUT /members/bulk-delete\` (+ optional async poll).
881
+ - Client companies: \`/client-companies\` (B2B org ids) ≠ individual client members — orders/invoices need the right \`clientTargetType\`.
882
+ - Groups: \`/client-groups\` (hard DELETE on group id).
883
+
884
+ ## Invoices
885
+ - Mount: \`/invoices\`. Permissions: \`getInvoice\`, \`createInvoice\`, \`updateInvoice\`, \`deleteInvoice\`.
886
+ - Status: \`draft | open | paid | void | uncollectable\` (+ processing in places).
887
+ - Resend: \`PUT /invoices/resend\`. Public: \`/invoices/public/:id\`. Delete: \`PUT /invoices\` + ids.
888
+ - Create: \`currency\` + one of \`client\` | \`externalEmail\` | \`clientCompanyId\`; line items via \`additionalFields\` when required.
889
+
890
+ ## Proposals / quotations
891
+ - UI Proposal = \`/quotations\` always.
892
+ - Permissions: \`createQuotation\`, \`updateQuotation\`, \`deleteQuotation\`.
893
+ - Recipient: \`clientId\` **or** \`leadId\` **or** external email object.
894
+ - Send: \`PUT /quotations/send/:id\`. Accept/reject: \`/accept/:id\`, \`/reject/:id\`. Public: \`/quotations/public/:id\`.
895
+ - \`acceptAction\`: \`doNothing | createOrder | createOrderAndInvoice\` — accept can spawn order/invoice.
896
+ - Pagination: \`page\` (min 1) + \`limit\`.
897
+
898
+ ## Services & packages
899
+ - No \`/packages\` resource — packages nested on \`/services\`.
900
+ - Order create needs both \`packageId\` and \`packageName\` + parent \`service\` id.
901
+ - Permissions: \`createService\`, \`updateService\`, \`deleteService\`. Catalog: \`/services/catalog\`.
902
+
903
+ ## Vouchers
904
+ - Admin CRUD permissions: \`createVoucher\`, \`getVouchers\`, \`getVoucher\`, \`updateVoucher\`, \`deleteVoucher\`.
905
+ - \`POST /vouchers/apply\` is **unauthenticated checkout** (\`code\`, \`email\`, \`serviceId\`, \`packageId\`, \`quantity\`) — not admin CRUD.
906
+ - Delete: \`PUT /vouchers\` + \`voucherIds[]\`.
907
+
908
+ ## Webhooks
909
+ - Event ids on create come from \`GET /webhooks/events/list\` (ObjectIds), not free-form strings.
910
+ - Delete: \`PUT /webhooks\` + \`webhookIds[]\`. Logs: \`GET /webhooks/logs\`; attempts under \`/logs/:id/attempts\`.
911
+ - Auth configs (\`/webhook-auths\`) are separate from webhook URL rows.
912
+
913
+ ## Forms
914
+ - Three surfaces: agency \`/forms\`, share \`/forms/share/:formId\` + \`/forms/public/:shareToken/respond\`, and \`/public-forms\`.
915
+ - Agency submit: \`POST /forms/respond\` (and \`respond-update\`).
916
+ - \`GET /forms/:formId\` auth may be open — treat payload as potentially public-facing.
917
+
918
+ ## System
919
+ - Notifications \`GET /notifications\` = **caller inbox only**, not company-wide.
920
+ - Custom fields types: \`task|client|lead\`; mutate often admin/superAdmin only; values via \`/custom-fields/respond\` with \`resourceId\`.
921
+ - Roles: permission **camelCase names** from \`GET /roles/permissions\` (\`createOrder\`, \`getInvoice\`, \`addNewMemberToCompany\`, …).
922
+ - Email templates \`/email-templates\` ≠ builder \`/email-template-builder\`.
923
+ - \`/order-subscriptions\` = client recurring orders. \`/subscriptions\` = Agency Handy **SaaS** plan for the workspace — never mix them.
924
+
925
+ ## Highest-risk misunderstandings
926
+ 1. \`/projects\` vs \`/orders\`
927
+ 2. Proposal vs \`/quotations\`
928
+ 3. Board ticket vs legacy \`/tickets\`
929
+ 4. Status **name on write** / **ObjectId on list**
930
+ 5. OpenAPI subtask/reopen routes not mounted
931
+ 6. \`PUT /accounts/update\` without \`accountId\`
932
+ 7. Permission camelCase names → 403 "security"
933
+ 8. PUT-as-delete
934
+ 9. \`/subscriptions\` vs \`/order-subscriptions\`
935
+ 10. Leads sharing \`/members\` without \`isConvertedClient=false\`
936
+ `;
937
+
806
938
  const CONTENT = {
807
939
  "ah://glossary": GLOSSARY,
808
940
  "ah://models": MODELS,
@@ -813,6 +945,7 @@ const CONTENT = {
813
945
  "ah://tool-guide": TOOL_GUIDE,
814
946
  "ah://modules": MODULES,
815
947
  "ah://response-shape": RESPONSE_SHAPE,
948
+ "ah://api-context": API_CONTEXT,
816
949
  };
817
950
 
818
951
  export function readResource(uri) {
@@ -837,6 +970,8 @@ export function readResource(uri) {
837
970
  MODULES,
838
971
  "---",
839
972
  RESPONSE_SHAPE,
973
+ "---",
974
+ API_CONTEXT,
840
975
  ].join("\n");
841
976
  return md(uri, text);
842
977
  }
package/src/tools.js CHANGED
@@ -761,6 +761,7 @@ export async function handleTool(name, args, ctx) {
761
761
  "ah://fields",
762
762
  "ah://workflows",
763
763
  "ah://workflows-platform",
764
+ "ah://api-context",
764
765
  "ah://usage",
765
766
  "ah://tool-guide",
766
767
  "ah://modules",