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/README.md +44 -29
- package/capability-proxy.js +199 -0
- package/catalog.js +322 -6
- package/crm/helpers.js +9 -6
- package/crm/index.js +3 -2
- package/engine/validator.js +13 -1
- package/index.js +8 -5
- package/lib/stats.json +1 -1
- package/orchestrator.js +6 -35
- package/package.json +3 -2
- package/tools.js +3 -36
- package/workflow.js +336 -12
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
|
-
//
|
|
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, ...
|
|
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" &&
|
|
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(
|
|
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(
|
|
573
|
+
options.body = JSON.stringify(enrichedParams);
|
|
563
574
|
}
|
|
564
575
|
}
|
|
565
576
|
|
|
566
577
|
// Query string for GET
|
|
567
|
-
if (ep.method === "GET" &&
|
|
578
|
+
if (ep.method === "GET" && enrichedParams) {
|
|
568
579
|
const flat = {};
|
|
569
|
-
for (const [k, v] of Object.entries(
|
|
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
|
-
|
|
577
|
-
|
|
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
|
-
*
|
|
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
|
+
|