@sellable/mcp 0.1.619-wip.121.2 → 0.1.619

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/dist/server.js CHANGED
@@ -1,8 +1,12 @@
1
1
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
2
2
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
3
  import { CallToolRequestSchema, GetPromptRequestSchema, ListPromptsRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
4
- import { getSkillByName, listSkills } from "./skills.js";
4
+ import { createMcpCredentialBoundary, resolveMcpCredentialIntent, } from "./agent-service-auth.js";
5
+ import { getConfig, getConfiguredCredentialIntent } from "./auth.js";
6
+ import { compileAgentToolPolicy, createAgentMcpAuthorizationHandlers, } from "./agent-tool-policy.js";
7
+ import { HttpAgentApprovalEffectPort } from "./agent-approval-effect-port.js";
5
8
  import { MCP_RUNTIME_IDENTITY } from "./runtime-identity.js";
9
+ import { getSkillByName, listSkills } from "./skills.js";
6
10
  import { getAuthStatus } from "./tools/auth.js";
7
11
  import { handleAddColumn, handleCommitBlueprint, } from "./tools/blueprint-commit.js";
8
12
  import { bootstrapCreateCampaign } from "./tools/bootstrap.js";
@@ -29,9 +33,8 @@ import { copySenderConfigTool, getEngageMemoryTool, migrateFlatConfigsTool, reco
29
33
  import { getEngageStateTool, setEngageStateTool, } from "./tools/engage-state.js";
30
34
  import { bulkEnrichWithProspeo, enrichWithProspeo, getProspeoCredits, } from "./tools/enrichment.js";
31
35
  import { getRefillPlanV2 } from "./tools/evergreen-refill-plan.js";
32
- import { getRuntimeIdentityTool } from "./tools/runtime-identity.js";
33
- import { getCampaignFramework } from "./tools/framework.js";
34
36
  import { bootstrapFindLeads, cancelFindLeads, getFindLeadsRun, reissueFindLeadsWatchLink, updateFindLeadsRun, waitForFindLeadsRun, } from "./tools/find-leads-runs.js";
37
+ import { getCampaignFramework } from "./tools/framework.js";
35
38
  import { confirmHarvestJobCompanies, searchHarvestJobs, } from "./tools/harvest-jobs.js";
36
39
  import { checkInboxReplyEligibility, getInboxThread, searchInboxThreads, sendInboxDraft, sendInboxManualReply, updateInboxDraft, } from "./tools/inbox.js";
37
40
  import { cancelLeadImport, confirmLeadList, confirmProspeoCompanyAccounts, getProviderPrompt, importLeads, listDncEntriesTool, loadCsvDncEntriesTool, loadCsvDomains, loadCsvLinkedinLeads, lookupSalesNavFilter, saveDomainFilters, searchApollo, searchProspeo, searchProspeoCompanies, searchSalesNav, searchSignals, selectPromisingPosts, setHeadlineICPCriteria, } from "./tools/leads.js";
@@ -39,16 +42,17 @@ import { fetchCompany, fetchCompanyPosts, fetchLinkedInPosts, fetchLinkedInProfi
39
42
  import { getCampaignNavigationState } from "./tools/navigation.js";
40
43
  import { addOnDemandLeads, createOnDemandCampaign, createOnDemandTable, initOnDemandSequence, pauseOnDemandCampaign, startOnDemandCampaign, } from "./tools/one-off.js";
41
44
  import { upsertRubric } from "./tools/processing.js";
42
- import { preflightFindLeadsProvider } from "./tools/provider-preflight.js";
43
45
  import { completeSenderResearch, getPostFindLeadsScoutRegistry, getSourceScoutRegistry, getSubskillAsset, getSubskillPrompt, listSubskillPrompts, searchSubskillPrompts, } from "./tools/prompts.js";
46
+ import { preflightFindLeadsProvider } from "./tools/provider-preflight.js";
44
47
  import { waitForCampaignTableReady, waitForLeadListReady, } from "./tools/readiness.js";
45
- import { refreshSenderEngagementCommand } from "./tools/refresh-sender-engagement.js";
46
48
  import { refillSendsV2Command } from "./tools/refill-sends-v2.js";
47
49
  import { executeRefillSendsCommand } from "./tools/refill-sends.js";
48
50
  import { getRefillTargetPlan } from "./tools/refill-target-plan.js";
51
+ import { refreshSenderEngagementCommand } from "./tools/refresh-sender-engagement.js";
49
52
  import { allTools } from "./tools/registry.js";
50
53
  import { getRows, getTableRows, getTableRowsMinimal } from "./tools/rows.js";
51
54
  import { addRubricItem, checkRubric, deleteRubricItem, draftRubrics, saveRubrics, selectNecessaryRubrics, updateRubricItem, waitForRubricResults, } from "./tools/rubrics.js";
55
+ import { getRuntimeIdentityTool } from "./tools/runtime-identity.js";
52
56
  import { getSchedulerFillCapacity } from "./tools/scheduler-fill-capacity.js";
53
57
  import { runSchedulerSweep } from "./tools/scheduler-run.js";
54
58
  import { getSenderRoutingTool, setSenderRoutingTool, } from "./tools/sender-routing.js";
@@ -133,12 +137,79 @@ function isStructuredToolResult(result) {
133
137
  typeof result === "object" &&
134
138
  Array.isArray(result.content));
135
139
  }
136
- server.setRequestHandler(ListToolsRequestSchema, async () => ({
137
- tools: allTools,
138
- }));
140
+ const agentApprovalPort = new HttpAgentApprovalEffectPort();
141
+ const agentMcpAuthorization = createAgentMcpAuthorizationHandlers({
142
+ allTools,
143
+ policy: compileAgentToolPolicy(),
144
+ approvalPort: agentApprovalPort,
145
+ });
146
+ const credentialBoundary = createMcpCredentialBoundary({
147
+ allTools,
148
+ agentAuthorization: agentMcpAuthorization,
149
+ resolve: () => resolveMcpCredentialIntent({
150
+ env: process.env,
151
+ getConfig,
152
+ getCredentialIntent: getConfiguredCredentialIntent,
153
+ }),
154
+ });
155
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
156
+ return { tools: credentialBoundary.listTools() };
157
+ });
139
158
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
140
159
  const { name, arguments: args } = request.params;
141
160
  try {
161
+ const meta = request.params._meta?.sellableAgent;
162
+ const argumentApprovalId = args &&
163
+ typeof args === "object" &&
164
+ typeof args.approvalId === "string"
165
+ ? args.approvalId
166
+ : undefined;
167
+ const boundary = await credentialBoundary.authorizeCall({
168
+ name,
169
+ args,
170
+ requesterId: meta?.requesterId,
171
+ channelId: meta?.channelId,
172
+ providerRequestId: meta?.providerRequestId,
173
+ approvalId: meta?.approvalId ?? argumentApprovalId,
174
+ });
175
+ if (boundary.mode === "invalid") {
176
+ return {
177
+ isError: true,
178
+ content: [
179
+ {
180
+ type: "text",
181
+ text: JSON.stringify({
182
+ ok: false,
183
+ error: "invalid_agent_service_context",
184
+ decision: "DENY",
185
+ }),
186
+ },
187
+ ],
188
+ };
189
+ }
190
+ let agentEffectId;
191
+ if (boundary.mode === "agent_service") {
192
+ const authorization = boundary.authorization;
193
+ if (!authorization.allowed) {
194
+ return {
195
+ isError: true,
196
+ content: [
197
+ {
198
+ type: "text",
199
+ text: JSON.stringify({
200
+ ok: false,
201
+ error: authorization.code,
202
+ decision: authorization.decision,
203
+ ...(authorization.approval
204
+ ? { approval: authorization.approval }
205
+ : {}),
206
+ }),
207
+ },
208
+ ],
209
+ };
210
+ }
211
+ agentEffectId = boundary.authorization.effectId;
212
+ }
142
213
  let result;
143
214
  switch (name) {
144
215
  case "get_auth_status":
@@ -567,9 +638,25 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
567
638
  break;
568
639
  case "get_company_info":
569
640
  result = await getCompanyInfoTool();
641
+ if (boundary.mode === "agent_service" &&
642
+ boundary.context &&
643
+ result &&
644
+ typeof result === "object") {
645
+ result = {
646
+ ...result,
647
+ agentContext: {
648
+ workspaceId: boundary.context.principal.workspaceId,
649
+ agentId: boundary.context.principal.agentId,
650
+ credentialGeneration: boundary.context.principal.generation,
651
+ requesterId: boundary.context.requesterId,
652
+ channelId: boundary.context.channelId,
653
+ providerRequestId: boundary.context.providerRequestId,
654
+ },
655
+ };
656
+ }
570
657
  break;
571
658
  case "set_company_info":
572
- result = await setCompanyInfoTool(args);
659
+ result = await setCompanyInfoTool(args, agentEffectId);
573
660
  break;
574
661
  case "list_senders":
575
662
  result = await listSenders(args);
@@ -5,6 +5,7 @@ type CompanyInfoResponse = {
5
5
  maxLength: number;
6
6
  };
7
7
  type CompanyInfoInput = {
8
+ approvalId?: string;
8
9
  companyInfoMarkdown?: string | null;
9
10
  expectedUpdatedAt?: string | null;
10
11
  forceOverwrite?: boolean;
@@ -16,6 +17,7 @@ export declare const companyInfoToolDefinitions: ({
16
17
  type: string;
17
18
  properties: {
18
19
  companyInfoMarkdown?: undefined;
20
+ approvalId?: undefined;
19
21
  expectedUpdatedAt?: undefined;
20
22
  forceOverwrite?: undefined;
21
23
  };
@@ -32,6 +34,10 @@ export declare const companyInfoToolDefinitions: ({
32
34
  type: string[];
33
35
  description: string;
34
36
  };
37
+ approvalId: {
38
+ type: string;
39
+ description: string;
40
+ };
35
41
  expectedUpdatedAt: {
36
42
  type: string[];
37
43
  description: string;
@@ -49,7 +55,7 @@ export declare function getCompanyInfoTool(): Promise<CompanyInfoResponse | {
49
55
  ok: false;
50
56
  error: string;
51
57
  }>;
52
- export declare function setCompanyInfoTool(input?: CompanyInfoInput): Promise<{
58
+ export declare function setCompanyInfoTool(input?: CompanyInfoInput, agentEffectId?: string): Promise<{
53
59
  ok: false;
54
60
  saved?: false;
55
61
  error: string;
@@ -21,6 +21,10 @@ export const companyInfoToolDefinitions = [
21
21
  type: ["string", "null"],
22
22
  description: 'Complete Company Info markdown to store verbatim. Use real newlines, not literal "\\n" sequences. Empty, null, or omitted markdown clears Company Info.',
23
23
  },
24
+ approvalId: {
25
+ type: "string",
26
+ description: "The APPROVED approvalId returned by the first confirmation-required set_company_info attempt for these exact arguments. It authorizes the Agent request and is never written into Company Info.",
27
+ },
24
28
  expectedUpdatedAt: {
25
29
  type: ["string", "null"],
26
30
  description: "The updatedAt returned by get_company_info. Use null only when get_company_info returned updatedAt: null for an unconfigured document.",
@@ -98,7 +102,7 @@ export async function getCompanyInfoTool() {
98
102
  return toolError("read", error);
99
103
  }
100
104
  }
101
- export async function setCompanyInfoTool(input = {}) {
105
+ export async function setCompanyInfoTool(input = {}, agentEffectId) {
102
106
  const markdown = typeof input.companyInfoMarkdown === "string"
103
107
  ? input.companyInfoMarkdown
104
108
  : null;
@@ -129,7 +133,9 @@ export async function setCompanyInfoTool(input = {}) {
129
133
  }
130
134
  try {
131
135
  const api = getApi();
132
- const response = await api.put("/api/v3/reply-management/company-info", payload);
136
+ const response = agentEffectId
137
+ ? await api.put("/api/v3/reply-management/company-info", payload, { agentEffectId })
138
+ : await api.put("/api/v3/reply-management/company-info", payload);
133
139
  const normalizedResponse = normalizeCompanyInfoResponse(response);
134
140
  if ("ok" in normalizedResponse) {
135
141
  return {
@@ -8103,45 +8103,6 @@ export declare const allTools: ({
8103
8103
  };
8104
8104
  required: string[];
8105
8105
  };
8106
- } | {
8107
- name: string;
8108
- description: string;
8109
- inputSchema: {
8110
- type: string;
8111
- properties: {
8112
- workspaceId: {
8113
- type: string;
8114
- description: string;
8115
- };
8116
- mode: {
8117
- type: string;
8118
- enum: string[];
8119
- description: string;
8120
- };
8121
- senderId: {
8122
- type: string;
8123
- description: string;
8124
- };
8125
- tableId: {
8126
- type: string;
8127
- description: string;
8128
- };
8129
- maxPosts: {
8130
- type: string;
8131
- description: string;
8132
- };
8133
- maxEngagerPages: {
8134
- type: string;
8135
- description: string;
8136
- };
8137
- dryRunFingerprint: {
8138
- type: string;
8139
- description: string;
8140
- };
8141
- };
8142
- required: string[];
8143
- additionalProperties: boolean;
8144
- };
8145
8106
  } | {
8146
8107
  name: string;
8147
8108
  description: string;
@@ -8278,6 +8239,45 @@ export declare const allTools: ({
8278
8239
  required: string[];
8279
8240
  additionalProperties: boolean;
8280
8241
  };
8242
+ } | {
8243
+ name: string;
8244
+ description: string;
8245
+ inputSchema: {
8246
+ type: string;
8247
+ properties: {
8248
+ workspaceId: {
8249
+ type: string;
8250
+ description: string;
8251
+ };
8252
+ mode: {
8253
+ type: string;
8254
+ enum: string[];
8255
+ description: string;
8256
+ };
8257
+ senderId: {
8258
+ type: string;
8259
+ description: string;
8260
+ };
8261
+ tableId: {
8262
+ type: string;
8263
+ description: string;
8264
+ };
8265
+ maxPosts: {
8266
+ type: string;
8267
+ description: string;
8268
+ };
8269
+ maxEngagerPages: {
8270
+ type: string;
8271
+ description: string;
8272
+ };
8273
+ dryRunFingerprint: {
8274
+ type: string;
8275
+ description: string;
8276
+ };
8277
+ };
8278
+ required: string[];
8279
+ additionalProperties: boolean;
8280
+ };
8281
8281
  } | {
8282
8282
  name: string;
8283
8283
  description: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sellable/mcp",
3
- "version": "0.1.619-wip.121.2",
3
+ "version": "0.1.619",
4
4
  "type": "module",
5
5
  "description": "Sellable MCP server for Claude Code, Codex, and Hermes campaign workflows",
6
6
  "main": "dist/index.js",
@@ -10,8 +10,7 @@
10
10
  "sellable-mcp-dev": "dist/index-dev.js"
11
11
  },
12
12
  "scripts": {
13
- "clean": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"",
14
- "build": "npm run clean && tsc",
13
+ "build": "tsc",
15
14
  "prepack": "npm run build",
16
15
  "start": "node dist/index.js",
17
16
  "start:dev": "node dist/index-dev.js",