@opennous/mcp 0.18.0 → 0.19.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/package.json +1 -1
- package/src/server.js +131 -1
package/package.json
CHANGED
package/src/server.js
CHANGED
|
@@ -24,13 +24,17 @@
|
|
|
24
24
|
* get_workspace_status — what's set up in this workspace + a ranked next_steps list (call first)
|
|
25
25
|
* set_workspace_profile— agent-driven onboarding: set the workspace's name, site, type, ICP
|
|
26
26
|
* build_scoring_model — build/rebuild the ICP scoring model from the recorded GTM context
|
|
27
|
+
* connect_integration — connect a key-based integration (Apollo, Prospeo, HubSpot, …)
|
|
28
|
+
* configure_crm_sync — set CRM sync rules (auto-sync, create policy, hygiene cadence)
|
|
29
|
+
* set_trigger — create an outbound event trigger (webhook); list_triggers reads them
|
|
30
|
+
* list_triggers — list the workspace's event triggers + available events
|
|
27
31
|
*/
|
|
28
32
|
|
|
29
33
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
30
34
|
import { z } from "zod";
|
|
31
35
|
import { get, post } from "./client.js";
|
|
32
36
|
|
|
33
|
-
export const SERVER_VERSION = "0.
|
|
37
|
+
export const SERVER_VERSION = "0.19.0";
|
|
34
38
|
|
|
35
39
|
// ─── helpers ──────────────────────────────────────────────────────────────────
|
|
36
40
|
|
|
@@ -630,5 +634,131 @@ export function createServer() {
|
|
|
630
634
|
}
|
|
631
635
|
);
|
|
632
636
|
|
|
637
|
+
// ===========================================================================
|
|
638
|
+
// TOOL: connect_integration — POST /v2/workspace/integrations
|
|
639
|
+
// The agent connects a KEY-BASED integration for the user (no clicking through
|
|
640
|
+
// the Integrations page). OAuth providers still need a browser, so this is
|
|
641
|
+
// limited to providers that authenticate with an API key/token.
|
|
642
|
+
// ===========================================================================
|
|
643
|
+
server.tool(
|
|
644
|
+
"connect_integration",
|
|
645
|
+
"Connect a key-based integration for the user — an enrichment, CRM, or sequencer provider that " +
|
|
646
|
+
"authenticates with an API key or token (e.g. Apollo, Prospeo, Instantly, HubSpot private-app " +
|
|
647
|
+
"token, Pipedrive, Attio, Smartlead, HeyReach). Ask the user for the provider's API key, then " +
|
|
648
|
+
"call this; it verifies the credentials before saving. Providers that use a browser sign-in " +
|
|
649
|
+
"(OAuth, e.g. Gmail) can't be connected this way — for those, point the user to the Integrations " +
|
|
650
|
+
"page. After connecting an enrichment provider, the account record starts filling in.",
|
|
651
|
+
{
|
|
652
|
+
provider: z.string().describe("Provider name, lowercase — e.g. 'apollo', 'prospeo', 'instantly', 'hubspot', 'pipedrive', 'attio'."),
|
|
653
|
+
credentials: z.record(z.string()).describe("The provider's credentials as key/value, e.g. { api_key: '...' } or { access_token: '...' }."),
|
|
654
|
+
name: z.string().optional().describe("Optional label for the connection."),
|
|
655
|
+
},
|
|
656
|
+
async ({ provider, credentials, name }) => {
|
|
657
|
+
try {
|
|
658
|
+
const r = await post("/v2/workspace/integrations", { provider, credentials, name });
|
|
659
|
+
return { content: [{ type: "text", text: `Connected ${r.connection?.provider ?? provider}.${r.message ? ` ${r.message}` : ""}` }] };
|
|
660
|
+
} catch (e) {
|
|
661
|
+
const msg = String(e?.message ?? e);
|
|
662
|
+
if (msg.includes("oauth_provider")) {
|
|
663
|
+
return { content: [{ type: "text", text: `${provider} uses a browser sign-in, so it can't be connected with a key. Tell the user to connect it on the Integrations page.` }] };
|
|
664
|
+
}
|
|
665
|
+
if (msg.includes("invalid_credentials")) {
|
|
666
|
+
return { content: [{ type: "text", text: `Those credentials didn't verify for ${provider}. Ask the user to double-check the key and try again.` }] };
|
|
667
|
+
}
|
|
668
|
+
if (msg.includes("unknown_provider")) {
|
|
669
|
+
return { content: [{ type: "text", text: `No provider named "${provider}". Ask the user which tool they mean.` }] };
|
|
670
|
+
}
|
|
671
|
+
throw e;
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
);
|
|
675
|
+
|
|
676
|
+
// ===========================================================================
|
|
677
|
+
// TOOL: configure_crm_sync — POST /v2/workspace/crm-sync
|
|
678
|
+
// The agent sets the CRM sync rules — the same options as the CRM Sync page.
|
|
679
|
+
// The CRM must already be connected (OAuth connect stays a human step).
|
|
680
|
+
// ===========================================================================
|
|
681
|
+
server.tool(
|
|
682
|
+
"configure_crm_sync",
|
|
683
|
+
"Configure how Nous keeps a connected CRM in sync — the same settings as the CRM Sync page. The " +
|
|
684
|
+
"CRM must already be connected (HubSpot/Pipedrive/Attio). Set any of: auto-sync (daily pull), " +
|
|
685
|
+
"push of touchpoints, the create policy (when a new record is auto-created and the ICP-fit " +
|
|
686
|
+
"threshold), and the hygiene cadence. Only send the fields you want to change. If it reports the " +
|
|
687
|
+
"CRM isn't connected, tell the user to connect it on the Integrations page first.",
|
|
688
|
+
{
|
|
689
|
+
provider: z.enum(["hubspot", "pipedrive", "attio"]).describe("Which connected CRM to configure."),
|
|
690
|
+
autoSync: z.boolean().optional().describe("Pull contacts/companies/deals daily."),
|
|
691
|
+
pushActivities: z.boolean().optional().describe("Push touchpoints (meetings, replies, proposals) back to the CRM."),
|
|
692
|
+
createInCrm: z.boolean().optional().describe("Auto-create new records in the CRM when they earn it."),
|
|
693
|
+
createTrigger: z.enum(["any_reply_or_meeting", "positive_reply_or_meeting", "meeting_only", "interested_stage"]).optional()
|
|
694
|
+
.describe("What earns a new record."),
|
|
695
|
+
createRequireIcpFit: z.boolean().optional().describe("Require an ICP-fit score before creating a record."),
|
|
696
|
+
createIcpThreshold: z.number().optional().describe("Minimum ICP-fit score to create (0-100)."),
|
|
697
|
+
hygieneEnabled: z.boolean().optional().describe("Run scheduled hygiene reconciliation."),
|
|
698
|
+
hygieneCadence: z.enum(["weekly", "monthly"]).optional().describe("How often hygiene runs."),
|
|
699
|
+
},
|
|
700
|
+
async (args) => {
|
|
701
|
+
try {
|
|
702
|
+
const r = await post("/v2/workspace/crm-sync", args);
|
|
703
|
+
const c = r.config ?? {};
|
|
704
|
+
return { content: [{ type: "text", text:
|
|
705
|
+
`CRM sync configured for ${args.provider}. auto-sync ${c.auto_sync ? "on" : "off"}, ` +
|
|
706
|
+
`create ${c.create_in_crm ? `on (${c.create_trigger}${c.create_require_icp_fit ? `, ICP ≥ ${c.create_icp_threshold}` : ""})` : "off"}, ` +
|
|
707
|
+
`hygiene ${c.hygiene_enabled ? c.hygiene_cadence : "off"}.` }] };
|
|
708
|
+
} catch (e) {
|
|
709
|
+
const msg = String(e?.message ?? e);
|
|
710
|
+
if (msg.includes("crm_not_connected")) {
|
|
711
|
+
return { content: [{ type: "text", text: `${args.provider} isn't connected yet. Tell the user to connect it on the Integrations page, then configure sync.` }] };
|
|
712
|
+
}
|
|
713
|
+
throw e;
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
);
|
|
717
|
+
|
|
718
|
+
// ===========================================================================
|
|
719
|
+
// TOOL: set_trigger / list_triggers — /v2/workspace/triggers
|
|
720
|
+
// Outbound event triggers (webhooks) — wire the user's stack to fire when the
|
|
721
|
+
// record changes.
|
|
722
|
+
// ===========================================================================
|
|
723
|
+
server.tool(
|
|
724
|
+
"set_trigger",
|
|
725
|
+
"Create an outbound event trigger (a webhook) so an external tool is notified when something " +
|
|
726
|
+
"happens in the workspace — e.g. a new contact, a reply, a meeting booked. Pass the destination " +
|
|
727
|
+
"URL and which events to fire on. Call list_triggers first to see the available event names.",
|
|
728
|
+
{
|
|
729
|
+
url: z.string().describe("The destination URL the event is POSTed to."),
|
|
730
|
+
events: z.array(z.string()).describe("Event names to fire on (see list_triggers for the catalog)."),
|
|
731
|
+
name: z.string().optional().describe("Optional label for the trigger."),
|
|
732
|
+
},
|
|
733
|
+
async ({ url, events, name }) => {
|
|
734
|
+
try {
|
|
735
|
+
const r = await post("/v2/workspace/triggers", { url, events, name });
|
|
736
|
+
return { content: [{ type: "text", text: `Trigger created for ${events.join(", ")} → ${url}.` }] };
|
|
737
|
+
} catch (e) {
|
|
738
|
+
const msg = String(e?.message ?? e);
|
|
739
|
+
return { content: [{ type: "text", text: `Couldn't create the trigger: ${msg}. Call list_triggers to see valid event names.` }] };
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
);
|
|
743
|
+
server.tool(
|
|
744
|
+
"list_triggers",
|
|
745
|
+
"List the workspace's outbound event triggers (webhooks) and the catalog of available event names.",
|
|
746
|
+
{},
|
|
747
|
+
async () => {
|
|
748
|
+
const r = await get("/v2/workspace/triggers");
|
|
749
|
+
const lines = [];
|
|
750
|
+
if (r.triggers?.length) {
|
|
751
|
+
lines.push(`TRIGGERS (${r.triggers.length}):`);
|
|
752
|
+
for (const t of r.triggers) lines.push(` ${t.name || "(unnamed)"} → ${t.url} [${(t.events || []).join(", ")}]`);
|
|
753
|
+
} else {
|
|
754
|
+
lines.push("No triggers set up yet.");
|
|
755
|
+
}
|
|
756
|
+
if (r.available_events?.length) {
|
|
757
|
+
lines.push("", `AVAILABLE EVENTS: ${r.available_events.join(", ")}`);
|
|
758
|
+
}
|
|
759
|
+
return { content: [{ type: "text", text: lines.join("\n").trim() }] };
|
|
760
|
+
}
|
|
761
|
+
);
|
|
762
|
+
|
|
633
763
|
return server;
|
|
634
764
|
}
|