0nmcp 2.3.0 → 2.4.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/workflow.js CHANGED
@@ -236,9 +236,11 @@ export const INTERNAL_ACTIONS = {
236
236
  export class WorkflowRunner {
237
237
  /**
238
238
  * @param {import("./connections.js").ConnectionManager} connections
239
+ * @param {import("./capability-proxy.js").CapabilityProxy} [proxy]
239
240
  */
240
- constructor(connections) {
241
+ constructor(connections, proxy) {
241
242
  this.connections = connections;
243
+ this.proxy = proxy;
242
244
  this._resolverReady = loadResolver();
243
245
  }
244
246
 
@@ -519,6 +521,9 @@ export class WorkflowRunner {
519
521
 
520
522
  /**
521
523
  * Execute a service API call.
524
+ * The conversion layer auto-enriches params from connection metadata.
525
+ * If a CRM call needs locationId and the workflow didn't provide it,
526
+ * we pull it from the .0n connection file. The connection IS the context.
522
527
  */
523
528
  async _executeService(service, action, params) {
524
529
  const catalog = SERVICE_CATALOG[service];
@@ -526,7 +531,7 @@ export class WorkflowRunner {
526
531
  throw new Error(`Unknown service: ${service}`);
527
532
  }
528
533
 
529
- // Resolve endpoint name
534
+ // Resolve endpoint name via conversion layer
530
535
  const endpointKey = this._resolveEndpoint(catalog, action);
531
536
  const ep = catalog.endpoints[endpointKey];
532
537
  if (!ep) {
@@ -538,9 +543,15 @@ export class WorkflowRunner {
538
543
  throw new Error(`Service ${service} not connected. Use connect_service first.`);
539
544
  }
540
545
 
541
- // Build URL
546
+ // ── Auto-enrich: inject connection metadata into params ──
547
+ // The .0n connection file has meta (locationId, pipelineId, etc.)
548
+ // If the workflow didn't provide them, we fill them in automatically.
549
+ // This is the universal connector — 0nMCP knows your context.
550
+ const enrichedParams = this._enrichFromConnection(service, params, endpointKey);
551
+
552
+ // Build URL with enriched params (connection metadata auto-injected)
542
553
  let url = catalog.baseUrl + ep.path;
543
- const allParams = { ...creds, ...params };
554
+ const allParams = { ...creds, ...enrichedParams };
544
555
  url = url.replace(/\{(\w+)\}/g, (_, key) => allParams[key] || `{${key}}`);
545
556
 
546
557
  // Build headers
@@ -548,33 +559,34 @@ export class WorkflowRunner {
548
559
  const options = { method: ep.method, headers };
549
560
 
550
561
  // Build body
551
- if (ep.method !== "GET" && params) {
562
+ if (ep.method !== "GET" && enrichedParams) {
552
563
  const contentType = ep.contentType || "application/json";
553
564
  if (contentType === "application/x-www-form-urlencoded") {
554
565
  headers["Content-Type"] = "application/x-www-form-urlencoded";
555
566
  const flat = {};
556
- for (const [k, v] of Object.entries(params)) {
567
+ for (const [k, v] of Object.entries(enrichedParams)) {
557
568
  if (typeof v !== "object") flat[k] = String(v);
558
569
  }
559
570
  options.body = new URLSearchParams(flat).toString();
560
571
  } else {
561
572
  headers["Content-Type"] = "application/json";
562
- options.body = JSON.stringify(params);
573
+ options.body = JSON.stringify(enrichedParams);
563
574
  }
564
575
  }
565
576
 
566
577
  // Query string for GET
567
- if (ep.method === "GET" && params) {
578
+ if (ep.method === "GET" && enrichedParams) {
568
579
  const flat = {};
569
- for (const [k, v] of Object.entries(params)) {
580
+ for (const [k, v] of Object.entries(enrichedParams)) {
570
581
  if (typeof v !== "object") flat[k] = String(v);
571
582
  }
572
583
  const qs = new URLSearchParams(flat).toString();
573
584
  if (qs) url += (url.includes("?") ? "&" : "?") + qs;
574
585
  }
575
586
 
576
- const response = await fetch(url, options);
577
- const data = await response.json().catch(() => ({ status: response.status, statusText: response.statusText }));
587
+ // Execute through capability proxy (rate-limited, audited)
588
+ // Workflow builds its own URL/headers due to auto-enrichment from connection metadata
589
+ const { response, data } = await this.proxy.callWithCredentials(service, endpointKey, url, options);
578
590
 
579
591
  if (!response.ok) {
580
592
  throw new Error(`${service}.${action} failed (${response.status}): ${JSON.stringify(data)}`);
@@ -583,13 +595,74 @@ export class WorkflowRunner {
583
595
  return { data, status: response.status };
584
596
  }
585
597
 
598
+ /**
599
+ * Read the raw .0n connection file to get the full meta block.
600
+ * The ConnectionManager strips meta during load — we need it back.
601
+ */
602
+ _getConnectionMeta(service) {
603
+ try {
604
+ const conn = this.connections.get(service);
605
+ if (!conn?._filePath) return {};
606
+ const raw = JSON.parse(readFileSync(conn._filePath, 'utf8'));
607
+ return raw.meta || raw.metadata || {};
608
+ } catch {
609
+ return {};
610
+ }
611
+ }
612
+
613
+ /**
614
+ * Auto-enrich params from connection metadata.
615
+ * The .0n connection file IS the context. If a workflow step needs
616
+ * locationId, pipelineId, projectRef, etc. and didn't provide them,
617
+ * we inject them from the connection. This is the universal connector.
618
+ *
619
+ * Smart injection: only adds fields the endpoint actually needs.
620
+ * locationId goes everywhere for CRM. pipelineId/stageId only for opportunities.
621
+ */
622
+ _enrichFromConnection(service, params, endpointKey) {
623
+ const meta = this._getConnectionMeta(service);
624
+ if (!meta || Object.keys(meta).length === 0) return params;
625
+
626
+ const enriched = { ...params };
627
+
628
+ // ── CRM auto-injection ──
629
+ if (service === 'crm') {
630
+ // locationId is universal — every CRM endpoint needs it
631
+ if (!enriched.locationId && meta.location_id) enriched.locationId = meta.location_id;
632
+
633
+ // pipelineId ONLY for opportunity endpoints, stageId mapped to pipelineStageId
634
+ const isOpportunity = endpointKey && (endpointKey.includes('opportunity') || endpointKey.includes('pipeline'));
635
+ if (isOpportunity) {
636
+ if (!enriched.pipelineId && meta.pipeline_id) enriched.pipelineId = meta.pipeline_id;
637
+ if (!enriched.pipelineStageId && !enriched.stageId && meta.stages?.free) enriched.pipelineStageId = meta.stages.free;
638
+ // CRM API uses pipelineStageId, not stageId — convert if workflow used stageId
639
+ if (enriched.stageId && !enriched.pipelineStageId) {
640
+ enriched.pipelineStageId = enriched.stageId;
641
+ delete enriched.stageId;
642
+ }
643
+ }
644
+ }
645
+
646
+ // ── Supabase auto-injection ──
647
+ if (service === 'supabase') {
648
+ if (!enriched.projectRef && meta.project_ref) enriched.projectRef = meta.project_ref;
649
+ }
650
+
651
+ return enriched;
652
+ }
653
+
586
654
  /**
587
655
  * Resolve a dot-notation action (e.g., "customers.search") to a catalog endpoint key.
588
- * Tries multiple fallback strategies.
656
+ * Uses the ACTION_ALIASES conversion layer first, then falls back to fuzzy matching.
657
+ * The .0n file format is the standard — our system converts to match.
589
658
  */
590
659
  _resolveEndpoint(catalog, action) {
591
660
  const endpoints = catalog.endpoints;
592
661
 
662
+ // 0. Conversion layer: check ACTION_ALIASES first
663
+ const alias = ACTION_ALIASES[action];
664
+ if (alias && endpoints[alias]) return alias;
665
+
593
666
  // 1. Direct match: action === endpoint key
594
667
  if (endpoints[action]) return action;
595
668
 
@@ -619,3 +692,254 @@ export class WorkflowRunner {
619
692
  return action;
620
693
  }
621
694
  }
695
+
696
+ // ============================================================
697
+ // ACTION_ALIASES — Conversion Layer
698
+ // ============================================================
699
+ // Maps standard .0n action names to catalog endpoint keys.
700
+ // The .0n format is the standard — our catalog adapts to it.
701
+ // Partners write intuitive action names; this layer translates.
702
+ // ============================================================
703
+
704
+ const ACTION_ALIASES = {
705
+
706
+ // ── CRM ─────────────────────────────────────────────────
707
+ // Contacts
708
+ "contacts.create": "create_contact",
709
+ "contacts.upsert": "create_contact",
710
+ "contacts.search": "search_contacts",
711
+ "contacts.get": "get_contact",
712
+ "contacts.update": "update_contact",
713
+ "contacts.delete": "delete_contact",
714
+ "contacts.find": "search_contacts",
715
+ "contacts.lookup": "search_contacts",
716
+ "contacts.createTask": "create_contact", // TODO: add create_task endpoint to catalog
717
+ "contacts.addToWorkflow": "list_workflows", // TODO: add add_to_workflow endpoint to catalog
718
+ "contacts.addTag": "create_tag",
719
+ "contacts.removeTag": "delete_contact", // TODO: add remove_tag endpoint
720
+
721
+ // Opportunities / Deals
722
+ "opportunities.create": "create_opportunity",
723
+ "opportunities.update": "create_opportunity", // TODO: add update_opportunity endpoint
724
+ "opportunities.list": "list_pipelines",
725
+ "opportunities.get": "list_pipelines",
726
+ "deals.create": "create_opportunity",
727
+ "deals.update": "create_opportunity",
728
+
729
+ // Pipelines
730
+ "pipelines.create": "create_pipeline",
731
+ "pipelines.list": "list_pipelines",
732
+
733
+ // Tags
734
+ "tags.create": "create_tag",
735
+ "tags.list": "list_tags",
736
+ "tags.add": "create_tag",
737
+
738
+ // Custom Values / Fields
739
+ "customValues.create": "create_custom_value",
740
+ "customValues.list": "list_custom_values",
741
+ "customFields.create": "create_custom_value",
742
+ "customFields.list": "list_custom_values",
743
+
744
+ // Workflows
745
+ "workflows.list": "list_workflows",
746
+ "workflows.get": "list_workflows",
747
+
748
+ // Calendars
749
+ "calendars.list": "list_calendars",
750
+ "calendars.get": "list_calendars",
751
+
752
+ // ── Stripe ──────────────────────────────────────────────
753
+ "customers.create": "create_customer",
754
+ "customers.list": "list_customers",
755
+ "customers.get": "get_customer",
756
+ "charges.create": "create_charge",
757
+ "invoices.create": "create_invoice",
758
+ "invoices.send": "send_invoice",
759
+ "invoices.list": "list_invoices",
760
+ "subscriptions.create": "create_subscription",
761
+ "subscriptions.cancel": "cancel_subscription",
762
+ "subscriptions.list": "list_subscriptions",
763
+ "balance.get": "get_balance",
764
+ "payments.list": "list_payments",
765
+ "products.create": "create_product",
766
+ "products.list": "list_products",
767
+ "prices.create": "create_price",
768
+ "prices.list": "list_prices",
769
+ "checkout.create": "create_checkout_session",
770
+ "payment_intents.create": "create_payment_intent",
771
+
772
+ // ── Supabase ────────────────────────────────────────────
773
+ "insert": "insert_row",
774
+ "select": "query_table",
775
+ "update": "update_row",
776
+ "delete": "delete_row",
777
+ "query": "query_table",
778
+ "table.insert": "insert_row",
779
+ "table.query": "query_table",
780
+ "table.select": "query_table",
781
+ "table.update": "update_row",
782
+ "table.delete": "delete_row",
783
+ "rows.insert": "insert_row",
784
+ "rows.query": "query_table",
785
+ "rows.update": "update_row",
786
+ "rows.delete": "delete_row",
787
+ "auth.listUsers": "list_users",
788
+ "storage.listBuckets": "list_buckets",
789
+
790
+ // ── Slack ───────────────────────────────────────────────
791
+ "chat.postMessage": "send_message",
792
+ "chat.send": "send_message",
793
+ "post": "send_message",
794
+ "postMessage": "send_message",
795
+ "channels.list": "list_channels",
796
+ "users.list": "list_users",
797
+ "channels.create": "create_channel",
798
+
799
+ // ── SendGrid ────────────────────────────────────────────
800
+ "mail.send": "send_email",
801
+ "email.send": "send_email",
802
+ "contacts.add": "add_contact",
803
+ "lists.list": "list_contacts",
804
+
805
+ // ── Discord ─────────────────────────────────────────────
806
+ "messages.send": "send_message",
807
+ "channels.get": "list_channels",
808
+
809
+ // ── Twilio ──────────────────────────────────────────────
810
+ "sms.send": "send_sms",
811
+ "messages.create": "send_sms",
812
+ "calls.create": "make_call",
813
+
814
+ // ── GitHub ──────────────────────────────────────────────
815
+ "repos.list": "list_repos",
816
+ "repos.get": "get_repo",
817
+ "repos.create": "create_repo",
818
+ "issues.create": "create_issue",
819
+ "issues.list": "list_issues",
820
+ "pulls.create": "create_pull",
821
+ "pulls.list": "list_pulls",
822
+
823
+ // ── Shopify ─────────────────────────────────────────────
824
+ "products.list": "list_products",
825
+ "products.get": "get_product",
826
+ "products.create": "create_product",
827
+ "orders.list": "list_orders",
828
+ "orders.get": "get_order",
829
+ "customers.list": "list_customers",
830
+
831
+ // ── OpenAI ──────────────────────────────────────────────
832
+ "chat.completions": "create_completion",
833
+ "completions.create": "create_completion",
834
+ "embeddings.create": "create_embedding",
835
+ "images.generate": "create_image",
836
+
837
+ // ── Anthropic ───────────────────────────────────────────
838
+ "messages.create": "create_message",
839
+ "chat.create": "create_message",
840
+
841
+ // ── Gmail ───────────────────────────────────────────────
842
+ "messages.send": "send_email",
843
+ "messages.list": "list_emails",
844
+ "messages.get": "get_email",
845
+
846
+ // ── Google Sheets ───────────────────────────────────────
847
+ "sheets.get": "get_sheet",
848
+ "sheets.update": "update_cells",
849
+ "rows.append": "append_row",
850
+ "values.get": "get_values",
851
+ "values.update": "update_cells",
852
+
853
+ // ── Google Drive ────────────────────────────────────────
854
+ "files.list": "list_files",
855
+ "files.get": "get_file",
856
+ "files.create": "upload_file",
857
+ "files.upload": "upload_file",
858
+
859
+ // ── Airtable ────────────────────────────────────────────
860
+ "records.list": "list_records",
861
+ "records.create": "create_record",
862
+ "records.update": "update_record",
863
+ "records.delete": "delete_record",
864
+
865
+ // ── Notion ──────────────────────────────────────────────
866
+ "pages.create": "create_page",
867
+ "pages.get": "get_page",
868
+ "databases.query": "query_database",
869
+ "blocks.get": "get_block",
870
+
871
+ // ── MongoDB ─────────────────────────────────────────────
872
+ "documents.find": "find_documents",
873
+ "documents.insert": "insert_document",
874
+ "documents.update": "update_document",
875
+ "documents.delete": "delete_document",
876
+ "collections.list": "list_collections",
877
+
878
+ // ── HubSpot ─────────────────────────────────────────────
879
+ "contacts.create": "create_contact",
880
+ "contacts.list": "list_contacts",
881
+ "contacts.get": "get_contact",
882
+ "contacts.search": "search_contacts",
883
+ "deals.create": "create_deal",
884
+ "companies.create": "create_company",
885
+
886
+ // ── Mailchimp ───────────────────────────────────────────
887
+ "lists.list": "list_audiences",
888
+ "members.add": "add_member",
889
+ "members.list": "list_members",
890
+ "campaigns.create": "create_campaign",
891
+ "campaigns.send": "send_campaign",
892
+
893
+ // ── Google Calendar ─────────────────────────────────────
894
+ "events.list": "list_events",
895
+ "events.create": "create_event",
896
+ "events.get": "get_event",
897
+ "calendars.list": "list_calendars",
898
+
899
+ // ── Calendly ────────────────────────────────────────────
900
+ "events.list": "list_events",
901
+ "event_types.list": "list_event_types",
902
+ "invitees.list": "list_invitees",
903
+
904
+ // ── Zoom ────────────────────────────────────────────────
905
+ "meetings.create": "create_meeting",
906
+ "meetings.list": "list_meetings",
907
+ "meetings.get": "get_meeting",
908
+ "users.list": "list_users",
909
+
910
+ // ── Zendesk ─────────────────────────────────────────────
911
+ "tickets.create": "create_ticket",
912
+ "tickets.list": "list_tickets",
913
+ "tickets.update": "update_ticket",
914
+ "users.create": "create_user",
915
+ "users.list": "list_users",
916
+
917
+ // ── Jira ────────────────────────────────────────────────
918
+ "issues.create": "create_issue",
919
+ "issues.get": "get_issue",
920
+ "issues.search": "search_issues",
921
+ "projects.list": "list_projects",
922
+
923
+ // ── Linear ──────────────────────────────────────────────
924
+ "issues.create": "create_issue",
925
+ "issues.list": "list_issues",
926
+ "teams.list": "list_teams",
927
+ "projects.list": "list_projects",
928
+
929
+ // ── Microsoft ───────────────────────────────────────────
930
+ "mail.send": "send_email",
931
+ "mail.list": "list_emails",
932
+ "calendar.events": "list_events",
933
+ "files.list": "list_files",
934
+
935
+ // ── ListKit (Partner) ───────────────────────────────────
936
+ // ListKit has no API — these aliases map ListKit field
937
+ // conventions to our CRM/Supabase actions so .0n files
938
+ // written for ListKit data work seamlessly.
939
+ "leads.import": "create_contact",
940
+ "leads.enrich": "update_contact",
941
+ "leads.score": "create_contact",
942
+ "leads.export": "query_table",
943
+ "list.complete": "insert_row",
944
+ };
945
+