@mindstone/mcp-server-salesforce 0.1.2 → 0.2.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 +142 -4
- package/dist/client.js +11 -2
- package/dist/server.js +7 -1
- package/dist/tools/accounts.js +4 -4
- package/dist/tools/campaigns.d.ts +3 -0
- package/dist/tools/campaigns.js +78 -0
- package/dist/tools/cases.d.ts +3 -0
- package/dist/tools/cases.js +121 -0
- package/dist/tools/contacts.js +4 -4
- package/dist/tools/events.d.ts +3 -0
- package/dist/tools/events.js +90 -0
- package/dist/tools/index.d.ts +6 -0
- package/dist/tools/index.js +6 -0
- package/dist/tools/leads.js +16 -8
- package/dist/tools/notes.d.ts +3 -0
- package/dist/tools/notes.js +111 -0
- package/dist/tools/opportunities.js +4 -4
- package/dist/tools/query.js +13 -10
- package/dist/tools/reports.d.ts +3 -0
- package/dist/tools/reports.js +39 -0
- package/dist/tools/search.d.ts +3 -0
- package/dist/tools/search.js +50 -0
- package/dist/tools/tasks.js +4 -4
- package/dist/tools/users.js +2 -2
- package/dist/types.d.ts +8 -0
- package/dist/types.js +8 -0
- package/dist/untrusted-content.d.ts +37 -0
- package/dist/untrusted-content.js +70 -0
- package/dist/utils.d.ts +34 -0
- package/dist/utils.js +130 -3
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,13 +1,117 @@
|
|
|
1
1
|
# @mindstone/mcp-server-salesforce
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/@mindstone/mcp-server-salesforce)
|
|
4
|
+
[](./LICENSE)
|
|
5
|
+
|
|
3
6
|
Salesforce CRM MCP server — accounts, contacts, opportunities, leads, tasks, users, and custom objects via the Salesforce API.
|
|
4
7
|
|
|
5
|
-
|
|
8
|
+
*Best when an MCP host needs a local, install-and-run CRM connector for everyday sales work rather than a Salesforce-hosted endpoint.*
|
|
9
|
+
|
|
10
|
+
## Status
|
|
11
|
+
|
|
12
|
+
- **Version:** [0.2.0](./CHANGELOG.md) · [npm](https://www.npmjs.com/package/@mindstone/mcp-server-salesforce)
|
|
13
|
+
- **Auth:** OAuth (local 127.0.0.1 callback) or static access token ([`SALESFORCE_CLIENT_SECRET`](./server.json), [`SALESFORCE_ACCESS_TOKEN`](./server.json))
|
|
14
|
+
- **Tools:** [37](./src/tools/) (accounts, contacts, opportunities, leads, tasks, cases, events, search, notes, campaigns, reports, query)
|
|
15
|
+
- **Surface:** cloud-api
|
|
16
|
+
- **Machine-readable:** [`STATUS.json`](./STATUS.json)
|
|
17
|
+
|
|
18
|
+
## Why this exists
|
|
19
|
+
|
|
20
|
+
Salesforce's own MCP options are the right starting point for many teams. This package is for teams that want a normal npm MCP server they can run locally in any stdio host.
|
|
21
|
+
|
|
22
|
+
It gives an assistant focused access to the CRM work people actually ask for: finding accounts and contacts, updating leads, creating opportunities and tasks, running CRM searches, and working with custom objects. The benefit is a short path from a natural-language sales request to the right Salesforce records, with write actions kept visible to the host.
|
|
23
|
+
|
|
24
|
+
## Example interaction
|
|
25
|
+
|
|
26
|
+
> "Find Acme Corp in Salesforce, create a Q3 expansion opportunity for $75,000, and add a follow-up task for next Friday."
|
|
27
|
+
|
|
28
|
+
Tools the host calls:
|
|
29
|
+
1. `salesforce_get_accounts` — searches accounts by name and returns the matching account ID.
|
|
30
|
+
2. `salesforce_create_opportunity` — creates the opportunity against that account.
|
|
31
|
+
3. `salesforce_create_task` — adds a follow-up task related to the new opportunity.
|
|
32
|
+
|
|
33
|
+
Response (trimmed):
|
|
34
|
+
|
|
35
|
+
```json
|
|
36
|
+
{
|
|
37
|
+
"account": { "id": "001xx000003DGbYAAW", "name": "Acme Corp" },
|
|
38
|
+
"opportunity": {
|
|
39
|
+
"id": "006xx000004TmiYAAS",
|
|
40
|
+
"name": "Q3 expansion",
|
|
41
|
+
"amount": 75000,
|
|
42
|
+
"stage": "Prospecting"
|
|
43
|
+
},
|
|
44
|
+
"task": {
|
|
45
|
+
"id": "00Txx000006rYxDEAU",
|
|
46
|
+
"subject": "Follow up on Q3 expansion"
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Requirements
|
|
52
|
+
|
|
53
|
+
- Node.js 20+
|
|
54
|
+
- npm
|
|
55
|
+
- A Salesforce Connected App for the OAuth path, or a valid access token plus instance URL for manual-token mode.
|
|
56
|
+
|
|
57
|
+
<!-- BEGIN INSTALL_LINKS: do not edit by hand; regenerated by scripts/gen-install-links.mjs -->
|
|
58
|
+
## One-click install
|
|
59
|
+
|
|
60
|
+
[](cursor://anysphere.cursor-deeplink/mcp/install?name=Salesforce&config=eyJ0eXBlIjoic3RkaW8iLCJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkBtaW5kc3RvbmUvbWNwLXNlcnZlci1zYWxlc2ZvcmNlIl0sImVudiI6eyJTQUxFU0ZPUkNFX0NMSUVOVF9JRCI6IiIsIlNBTEVTRk9SQ0VfQ0xJRU5UX1NFQ1JFVCI6IiIsIlNBTEVTRk9SQ0VfQUNDRVNTX1RPS0VOIjoiIiwiU0FMRVNGT1JDRV9DT05GSUdfRElSIjoifi8ubWNwL3NhbGVzZm9yY2UiLCJTQUxFU0ZPUkNFX09BVVRIX1BPUlQiOiIwIn19)
|
|
61
|
+
[](vscode:mcp/install?%7B%22name%22%3A%22Salesforce%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40mindstone%2Fmcp-server-salesforce%22%5D%2C%22env%22%3A%7B%22SALESFORCE_CLIENT_ID%22%3A%22%22%2C%22SALESFORCE_CLIENT_SECRET%22%3A%22%22%2C%22SALESFORCE_ACCESS_TOKEN%22%3A%22%22%2C%22SALESFORCE_CONFIG_DIR%22%3A%22%7E%2F.mcp%2Fsalesforce%22%2C%22SALESFORCE_OAUTH_PORT%22%3A%220%22%7D%7D)
|
|
62
|
+
[](vscode-insiders:mcp/install?%7B%22name%22%3A%22Salesforce%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40mindstone%2Fmcp-server-salesforce%22%5D%2C%22env%22%3A%7B%22SALESFORCE_CLIENT_ID%22%3A%22%22%2C%22SALESFORCE_CLIENT_SECRET%22%3A%22%22%2C%22SALESFORCE_ACCESS_TOKEN%22%3A%22%22%2C%22SALESFORCE_CONFIG_DIR%22%3A%22%7E%2F.mcp%2Fsalesforce%22%2C%22SALESFORCE_OAUTH_PORT%22%3A%220%22%7D%7D)
|
|
63
|
+
|
|
64
|
+
After clicking the button, your host will prompt you to fill: `SALESFORCE_CLIENT_ID`, `SALESFORCE_CLIENT_SECRET`, `SALESFORCE_ACCESS_TOKEN`, `SALESFORCE_CONFIG_DIR`, `SALESFORCE_OAUTH_PORT`.
|
|
65
|
+
|
|
66
|
+
<details>
|
|
67
|
+
<summary>Manual config for Claude Desktop / Claude Code / Goose / Continue.dev (Salesforce)</summary>
|
|
68
|
+
|
|
69
|
+
```json
|
|
70
|
+
{
|
|
71
|
+
"mcpServers": {
|
|
72
|
+
"Salesforce": {
|
|
73
|
+
"command": "npx",
|
|
74
|
+
"args": [
|
|
75
|
+
"-y",
|
|
76
|
+
"@mindstone/mcp-server-salesforce"
|
|
77
|
+
],
|
|
78
|
+
"env": {
|
|
79
|
+
"SALESFORCE_CLIENT_ID": "",
|
|
80
|
+
"SALESFORCE_CLIENT_SECRET": "",
|
|
81
|
+
"SALESFORCE_ACCESS_TOKEN": "",
|
|
82
|
+
"SALESFORCE_CONFIG_DIR": "~/.mcp/salesforce",
|
|
83
|
+
"SALESFORCE_OAUTH_PORT": "0"
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
</details>
|
|
91
|
+
<!-- END INSTALL_LINKS -->
|
|
92
|
+
|
|
93
|
+
## Quick Start
|
|
94
|
+
|
|
95
|
+
### Install & build
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
cd <path-to-repo>/connectors/salesforce
|
|
99
|
+
npm install
|
|
100
|
+
npm run build
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### npx
|
|
6
104
|
|
|
7
105
|
```bash
|
|
8
106
|
npx -y @mindstone/mcp-server-salesforce
|
|
9
107
|
```
|
|
10
108
|
|
|
109
|
+
### Local
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
node dist/index.js
|
|
113
|
+
```
|
|
114
|
+
|
|
11
115
|
## Configuration
|
|
12
116
|
|
|
13
117
|
### OAuth (Recommended)
|
|
@@ -28,8 +132,10 @@ Then call `salesforce_connect_account` to start the OAuth flow.
|
|
|
28
132
|
### Additional Options
|
|
29
133
|
|
|
30
134
|
- `SALESFORCE_CONFIG_DIR` — Custom config directory (default: `~/.mcp/salesforce`)
|
|
135
|
+
- `SALESFORCE_OAUTH_PORT` — OAuth callback port (`0` = OS-assigned; default: `0`)
|
|
136
|
+
- `SALESFORCE_OAUTH_SCOPES` — Space-separated OAuth scopes. Leave unset to use the connector default.
|
|
31
137
|
|
|
32
|
-
## Available Tools (
|
|
138
|
+
## Available Tools (37)
|
|
33
139
|
|
|
34
140
|
### Account Management
|
|
35
141
|
- `salesforce_connect_account` — Connect a Salesforce account via OAuth
|
|
@@ -62,6 +168,29 @@ Then call `salesforce_connect_account` to start the OAuth flow.
|
|
|
62
168
|
- `salesforce_create_task` — Create a task
|
|
63
169
|
- `salesforce_update_task` — Update a task
|
|
64
170
|
|
|
171
|
+
### Cases
|
|
172
|
+
- `salesforce_get_cases` — Get support cases with filters
|
|
173
|
+
- `salesforce_create_case` — Create a support case
|
|
174
|
+
- `salesforce_update_case` — Update a support case
|
|
175
|
+
|
|
176
|
+
### Events
|
|
177
|
+
- `salesforce_get_events` — Get calendar events with filters
|
|
178
|
+
- `salesforce_create_event` — Create a calendar event
|
|
179
|
+
|
|
180
|
+
### Search
|
|
181
|
+
- `salesforce_search` — Cross-object full-text search (SOSL); the response's `truncated` flag tells you whether more matches exist beyond the limit
|
|
182
|
+
|
|
183
|
+
### Notes
|
|
184
|
+
- `salesforce_get_notes` — Get notes attached to a record
|
|
185
|
+
- `salesforce_create_note` — Create a note, optionally attached to a record (a failed attach rolls the note back, or reports the orphaned note ID)
|
|
186
|
+
|
|
187
|
+
### Campaigns
|
|
188
|
+
- `salesforce_get_campaigns` — Get marketing campaigns with filters
|
|
189
|
+
- `salesforce_get_campaign_members` — Get leads/contacts in a campaign
|
|
190
|
+
|
|
191
|
+
### Reports
|
|
192
|
+
- `salesforce_run_report` — Run an existing Salesforce report (Analytics REST API)
|
|
193
|
+
|
|
65
194
|
### Users
|
|
66
195
|
- `salesforce_get_users` — Get Salesforce users
|
|
67
196
|
|
|
@@ -75,6 +204,15 @@ Then call `salesforce_connect_account` to start the OAuth flow.
|
|
|
75
204
|
- `salesforce_update_record` — Update any Salesforce record
|
|
76
205
|
- `salesforce_get_records` — Query any Salesforce object
|
|
77
206
|
|
|
78
|
-
##
|
|
207
|
+
## Security notes
|
|
208
|
+
|
|
209
|
+
- The standalone OAuth callback server binds to `127.0.0.1`; it does not honour environment overrides that would expose the callback beyond loopback.
|
|
210
|
+
- OAuth credentials are stored under `SALESFORCE_CONFIG_DIR` (default `~/.mcp/salesforce`) with restrictive directory and file permissions.
|
|
211
|
+
- Write and disconnect tools are marked so capable hosts can ask for confirmation before changing Salesforce data.
|
|
212
|
+
- SOQL helper paths escape string and `LIKE` values, strip comments quote-safely before applying the query limit cap, and enforce a maximum of 200 records for raw SOQL queries.
|
|
213
|
+
- Record text returned by read tools is wrapped in `<untrusted-content>` envelopes so Salesforce-authored field values are presented to the model as data, not instructions.
|
|
214
|
+
- `salesforce_convert_lead` reports a conversion as failed when Salesforce rejects it at the record level (e.g. a validation rule), instead of claiming success; the org-authored error detail is enveloped the same way.
|
|
215
|
+
|
|
216
|
+
## Licence
|
|
79
217
|
|
|
80
|
-
FSL-1.1-MIT
|
|
218
|
+
[FSL-1.1-MIT](./LICENSE) — Functional Source License, Version 1.1, with MIT future licence. The software converts to MIT licence on 2030-04-08.
|
package/dist/client.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import jsforce from 'jsforce';
|
|
2
|
-
import { ConnectorError, } from './types.js';
|
|
2
|
+
import { ConnectorError, SALESFORCE_API_VERSION, } from './types.js';
|
|
3
3
|
import { getAuthMode, getActiveToken, loadAccounts, loadToken, saveToken, deleteToken, saveAccounts, } from './auth.js';
|
|
4
4
|
// Connection cache per account
|
|
5
5
|
const connections = new Map();
|
|
@@ -44,10 +44,19 @@ export async function getConnection(accountId) {
|
|
|
44
44
|
const connectionConfig = {
|
|
45
45
|
instanceUrl: tokenData.instance_url,
|
|
46
46
|
accessToken: tokenData.access_token,
|
|
47
|
-
|
|
47
|
+
version: SALESFORCE_API_VERSION,
|
|
48
48
|
};
|
|
49
|
+
// Only hand jsforce a refresh token when we can also give it a way to USE
|
|
50
|
+
// that token — i.e. the OAuth2 client info to build a refresh delegate.
|
|
51
|
+
// jsforce throws synchronously at construction otherwise:
|
|
52
|
+
// "Refresh token is specified without oauth2 client information or refresh function".
|
|
53
|
+
// This is the bridge-mode case: the host app owns OAuth and does not pass
|
|
54
|
+
// SALESFORCE_CLIENT_ID/SECRET into the connector, so we operate on the access
|
|
55
|
+
// token alone and surface SESSION_EXPIRED (-> reconnect) on expiry rather than
|
|
56
|
+
// crashing every tool call. (See docs/plans/260612_fix-salesforce-bridge-refresh-token/.)
|
|
49
57
|
if (clientId && clientSecret && tokenData.refresh_token) {
|
|
50
58
|
connectionConfig.oauth2 = { clientId, clientSecret, loginUrl };
|
|
59
|
+
connectionConfig.refreshToken = tokenData.refresh_token;
|
|
51
60
|
}
|
|
52
61
|
const conn = new jsforce.Connection(connectionConfig);
|
|
53
62
|
conn.on('refresh', async (accessToken) => {
|
package/dist/server.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
2
|
import { SERVER_NAME, SERVER_VERSION } from './types.js';
|
|
3
3
|
import { resolveAuthMode } from './auth.js';
|
|
4
|
-
import { registerAuthTools, registerAccountTools, registerContactTools, registerOpportunityTools, registerLeadTools, registerTaskTools, registerUserTools, registerQueryTools, } from './tools/index.js';
|
|
4
|
+
import { registerAuthTools, registerAccountTools, registerContactTools, registerOpportunityTools, registerLeadTools, registerTaskTools, registerCaseTools, registerEventTools, registerSearchTools, registerNoteTools, registerCampaignTools, registerReportTools, registerUserTools, registerQueryTools, } from './tools/index.js';
|
|
5
5
|
export function createServer() {
|
|
6
6
|
resolveAuthMode();
|
|
7
7
|
const server = new McpServer({
|
|
@@ -14,6 +14,12 @@ export function createServer() {
|
|
|
14
14
|
registerOpportunityTools(server);
|
|
15
15
|
registerLeadTools(server);
|
|
16
16
|
registerTaskTools(server);
|
|
17
|
+
registerCaseTools(server);
|
|
18
|
+
registerEventTools(server);
|
|
19
|
+
registerSearchTools(server);
|
|
20
|
+
registerNoteTools(server);
|
|
21
|
+
registerCampaignTools(server);
|
|
22
|
+
registerReportTools(server);
|
|
17
23
|
registerUserTools(server);
|
|
18
24
|
registerQueryTools(server);
|
|
19
25
|
return server;
|
package/dist/tools/accounts.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { withErrorHandling, escapeSOQL, escapeSOQLLike, validateFields, validateAndMergeCustomFields, checkSaveResult } from '../utils.js';
|
|
2
|
+
import { withErrorHandling, escapeSOQL, escapeSOQLLike, validateFields, validateAndMergeCustomFields, checkSaveResult, formatVendorErrors, sanitizeRecords } from '../utils.js';
|
|
3
3
|
import { withConnection } from '../client.js';
|
|
4
4
|
export function registerAccountTools(server) {
|
|
5
5
|
server.registerTool('salesforce_get_accounts', {
|
|
@@ -29,7 +29,7 @@ export function registerAccountTools(server) {
|
|
|
29
29
|
const limit = Math.min(Math.max(1, args.limit ?? 50), 200);
|
|
30
30
|
query += ` LIMIT ${limit}`;
|
|
31
31
|
const result = await conn.query(query);
|
|
32
|
-
return JSON.stringify({ ok: true, records: result.records, totalSize: result.totalSize, done: result.done });
|
|
32
|
+
return JSON.stringify({ ok: true, records: sanitizeRecords(result.records, 'salesforce:get_accounts:records'), totalSize: result.totalSize, done: result.done });
|
|
33
33
|
});
|
|
34
34
|
}));
|
|
35
35
|
server.registerTool('salesforce_create_account', {
|
|
@@ -43,7 +43,7 @@ export function registerAccountTools(server) {
|
|
|
43
43
|
description: z.string().optional().describe('Account description'),
|
|
44
44
|
fields: z.record(z.unknown()).optional().describe('Additional/custom fields as key-value pairs'),
|
|
45
45
|
}),
|
|
46
|
-
annotations: { readOnlyHint: false, destructiveHint:
|
|
46
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true },
|
|
47
47
|
}, withErrorHandling(async (args) => {
|
|
48
48
|
return withConnection(undefined, async (conn) => {
|
|
49
49
|
const data = { Name: args.name };
|
|
@@ -62,7 +62,7 @@ export function registerAccountTools(server) {
|
|
|
62
62
|
const result = await conn.sobject('Account').create(data);
|
|
63
63
|
if (!result.success) {
|
|
64
64
|
const { ConnectorError } = await import('../types.js');
|
|
65
|
-
throw new ConnectorError('Failed to create account', 'CREATE_ERROR',
|
|
65
|
+
throw new ConnectorError('Failed to create account', 'CREATE_ERROR', formatVendorErrors(result.errors));
|
|
66
66
|
}
|
|
67
67
|
return JSON.stringify({ ok: true, status: 'success', object: 'Account', id: result.id, name: args.name });
|
|
68
68
|
});
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { withErrorHandling, escapeSOQL, escapeSOQLLike, validateFields, formatSOQLDate, sanitizeRecords } from '../utils.js';
|
|
3
|
+
import { withConnection } from '../client.js';
|
|
4
|
+
export function registerCampaignTools(server) {
|
|
5
|
+
server.registerTool('salesforce_get_campaigns', {
|
|
6
|
+
description: `Get marketing campaigns. Filters: name_contains, campaign_type, status, is_active, start_date_from, start_date_to. Returns: Id, Name, Type, Status, IsActive, StartDate, EndDate, Description. Max 200 (default: 50).`,
|
|
7
|
+
inputSchema: z.object({
|
|
8
|
+
limit: z.number().int().min(1).max(200).optional().describe('Max results 1-200 (default: 50)'),
|
|
9
|
+
name_contains: z.string().optional().describe('Filter by name (case-insensitive)'),
|
|
10
|
+
campaign_type: z.string().optional().describe('Filter by type (Email, Webinar, Advertisement)'),
|
|
11
|
+
status: z.string().optional().describe('Filter by status (Planned, In Progress, Completed)'),
|
|
12
|
+
is_active: z.boolean().optional().describe('Filter by active status'),
|
|
13
|
+
start_date_from: z.string().optional().describe('Campaigns starting on/after date (YYYY-MM-DD)'),
|
|
14
|
+
start_date_to: z.string().optional().describe('Campaigns starting on/before date (YYYY-MM-DD)'),
|
|
15
|
+
fields: z.array(z.string()).optional().describe('Custom fields (must be valid API names)'),
|
|
16
|
+
}),
|
|
17
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
18
|
+
}, withErrorHandling(async (args) => {
|
|
19
|
+
return withConnection(undefined, async (conn) => {
|
|
20
|
+
const defaultFields = ['Id', 'Name', 'Type', 'Status', 'IsActive', 'StartDate', 'EndDate', 'Description'];
|
|
21
|
+
const fields = validateFields(args.fields || [], defaultFields);
|
|
22
|
+
let query = `SELECT ${fields.join(', ')} FROM Campaign`;
|
|
23
|
+
const conditions = [];
|
|
24
|
+
if (args.name_contains)
|
|
25
|
+
conditions.push(`Name LIKE '%${escapeSOQLLike(args.name_contains)}%'`);
|
|
26
|
+
if (args.campaign_type)
|
|
27
|
+
conditions.push(`Type = '${escapeSOQL(args.campaign_type)}'`);
|
|
28
|
+
if (args.status)
|
|
29
|
+
conditions.push(`Status = '${escapeSOQL(args.status)}'`);
|
|
30
|
+
if (args.is_active !== undefined)
|
|
31
|
+
conditions.push(`IsActive = ${args.is_active}`);
|
|
32
|
+
if (args.start_date_from)
|
|
33
|
+
conditions.push(`StartDate >= ${formatSOQLDate(args.start_date_from, 'start_date_from')}`);
|
|
34
|
+
if (args.start_date_to)
|
|
35
|
+
conditions.push(`StartDate <= ${formatSOQLDate(args.start_date_to, 'start_date_to')}`);
|
|
36
|
+
if (conditions.length > 0)
|
|
37
|
+
query += ` WHERE ${conditions.join(' AND ')}`;
|
|
38
|
+
const limit = Math.min(Math.max(1, args.limit ?? 50), 200);
|
|
39
|
+
query += ` LIMIT ${limit}`;
|
|
40
|
+
const result = await conn.query(query);
|
|
41
|
+
return JSON.stringify({ ok: true, records: sanitizeRecords(result.records, 'salesforce:get_campaigns:records'), totalSize: result.totalSize });
|
|
42
|
+
});
|
|
43
|
+
}));
|
|
44
|
+
server.registerTool('salesforce_get_campaign_members', {
|
|
45
|
+
description: `Get campaign members (leads/contacts in a campaign). Filters: campaign_id, lead_id, contact_id, status. Returns: Id, CampaignId, LeadId, ContactId, Status, CreatedDate. Max 200 (default: 50).`,
|
|
46
|
+
inputSchema: z.object({
|
|
47
|
+
limit: z.number().int().min(1).max(200).optional().describe('Max results 1-200 (default: 50)'),
|
|
48
|
+
campaign_id: z.string().optional().describe('Filter by Campaign ID'),
|
|
49
|
+
lead_id: z.string().optional().describe('Filter by Lead ID'),
|
|
50
|
+
contact_id: z.string().optional().describe('Filter by Contact ID'),
|
|
51
|
+
status: z.string().optional().describe('Filter by member status (Sent, Responded)'),
|
|
52
|
+
fields: z.array(z.string()).optional().describe('Custom fields (must be valid API names)'),
|
|
53
|
+
}),
|
|
54
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
55
|
+
}, withErrorHandling(async (args) => {
|
|
56
|
+
return withConnection(undefined, async (conn) => {
|
|
57
|
+
const defaultFields = ['Id', 'CampaignId', 'LeadId', 'ContactId', 'Status', 'CreatedDate'];
|
|
58
|
+
const fields = validateFields(args.fields || [], defaultFields);
|
|
59
|
+
let query = `SELECT ${fields.join(', ')} FROM CampaignMember`;
|
|
60
|
+
const conditions = [];
|
|
61
|
+
if (args.campaign_id)
|
|
62
|
+
conditions.push(`CampaignId = '${escapeSOQL(args.campaign_id)}'`);
|
|
63
|
+
if (args.lead_id)
|
|
64
|
+
conditions.push(`LeadId = '${escapeSOQL(args.lead_id)}'`);
|
|
65
|
+
if (args.contact_id)
|
|
66
|
+
conditions.push(`ContactId = '${escapeSOQL(args.contact_id)}'`);
|
|
67
|
+
if (args.status)
|
|
68
|
+
conditions.push(`Status = '${escapeSOQL(args.status)}'`);
|
|
69
|
+
if (conditions.length > 0)
|
|
70
|
+
query += ` WHERE ${conditions.join(' AND ')}`;
|
|
71
|
+
const limit = Math.min(Math.max(1, args.limit ?? 50), 200);
|
|
72
|
+
query += ` LIMIT ${limit}`;
|
|
73
|
+
const result = await conn.query(query);
|
|
74
|
+
return JSON.stringify({ ok: true, records: sanitizeRecords(result.records, 'salesforce:get_campaign_members:records'), totalSize: result.totalSize });
|
|
75
|
+
});
|
|
76
|
+
}));
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=campaigns.js.map
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { withErrorHandling, escapeSOQL, escapeSOQLLike, validateFields, validateAndMergeCustomFields, checkSaveResult, formatVendorErrors, sanitizeRecords } from '../utils.js';
|
|
3
|
+
import { withConnection } from '../client.js';
|
|
4
|
+
import { ConnectorError } from '../types.js';
|
|
5
|
+
export function registerCaseTools(server) {
|
|
6
|
+
server.registerTool('salesforce_get_cases', {
|
|
7
|
+
description: `Get support cases. Filters: subject_contains, status, priority, related_account_id, related_contact_id, is_closed. Returns: Id, CaseNumber, Subject, Status, Priority, Origin, Description, AccountId, ContactId, OwnerId, CreatedDate, ClosedDate. Max 200 (default: 50).`,
|
|
8
|
+
inputSchema: z.object({
|
|
9
|
+
limit: z.number().int().min(1).max(200).optional().describe('Max results 1-200 (default: 50)'),
|
|
10
|
+
subject_contains: z.string().optional().describe('Filter by subject (case-insensitive)'),
|
|
11
|
+
status: z.string().optional().describe('Filter by status (New, Working, Escalated, Closed)'),
|
|
12
|
+
priority: z.string().optional().describe('Filter by priority (High, Medium, Low)'),
|
|
13
|
+
related_account_id: z.string().optional().describe('Filter by Account ID'),
|
|
14
|
+
related_contact_id: z.string().optional().describe('Filter by Contact ID'),
|
|
15
|
+
is_closed: z.boolean().optional().describe('Filter by closed status'),
|
|
16
|
+
fields: z.array(z.string()).optional().describe('Custom fields (must be valid API names)'),
|
|
17
|
+
}),
|
|
18
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
19
|
+
}, withErrorHandling(async (args) => {
|
|
20
|
+
return withConnection(undefined, async (conn) => {
|
|
21
|
+
const defaultFields = ['Id', 'CaseNumber', 'Subject', 'Status', 'Priority', 'Origin', 'Description', 'AccountId', 'ContactId', 'OwnerId', 'CreatedDate', 'ClosedDate'];
|
|
22
|
+
const fields = validateFields(args.fields || [], defaultFields);
|
|
23
|
+
let query = `SELECT ${fields.join(', ')} FROM Case`;
|
|
24
|
+
const conditions = [];
|
|
25
|
+
if (args.subject_contains)
|
|
26
|
+
conditions.push(`Subject LIKE '%${escapeSOQLLike(args.subject_contains)}%'`);
|
|
27
|
+
if (args.status)
|
|
28
|
+
conditions.push(`Status = '${escapeSOQL(args.status)}'`);
|
|
29
|
+
if (args.priority)
|
|
30
|
+
conditions.push(`Priority = '${escapeSOQL(args.priority)}'`);
|
|
31
|
+
if (args.related_account_id)
|
|
32
|
+
conditions.push(`AccountId = '${escapeSOQL(args.related_account_id)}'`);
|
|
33
|
+
if (args.related_contact_id)
|
|
34
|
+
conditions.push(`ContactId = '${escapeSOQL(args.related_contact_id)}'`);
|
|
35
|
+
if (args.is_closed !== undefined)
|
|
36
|
+
conditions.push(`IsClosed = ${args.is_closed}`);
|
|
37
|
+
if (conditions.length > 0)
|
|
38
|
+
query += ` WHERE ${conditions.join(' AND ')}`;
|
|
39
|
+
const limit = Math.min(Math.max(1, args.limit ?? 50), 200);
|
|
40
|
+
query += ` LIMIT ${limit}`;
|
|
41
|
+
const result = await conn.query(query);
|
|
42
|
+
return JSON.stringify({ ok: true, records: sanitizeRecords(result.records, 'salesforce:get_cases:records'), totalSize: result.totalSize });
|
|
43
|
+
});
|
|
44
|
+
}));
|
|
45
|
+
server.registerTool('salesforce_create_case', {
|
|
46
|
+
description: `Create a support case. Required: subject. Optional: status, priority, origin, description, related_account_id, related_contact_id, fields.`,
|
|
47
|
+
inputSchema: z.object({
|
|
48
|
+
subject: z.string().min(1).describe('Case subject (required)'),
|
|
49
|
+
status: z.string().optional().describe('Case status (default: New)'),
|
|
50
|
+
priority: z.string().optional().describe('Priority (High, Medium, Low)'),
|
|
51
|
+
origin: z.string().optional().describe('Case origin (Phone, Email, Web)'),
|
|
52
|
+
description: z.string().optional().describe('Case description'),
|
|
53
|
+
related_account_id: z.string().optional().describe('Account ID to associate with'),
|
|
54
|
+
related_contact_id: z.string().optional().describe('Contact ID to associate with'),
|
|
55
|
+
fields: z.record(z.unknown()).optional().describe('Additional/custom fields as key-value pairs'),
|
|
56
|
+
}),
|
|
57
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true },
|
|
58
|
+
}, withErrorHandling(async (args) => {
|
|
59
|
+
return withConnection(undefined, async (conn) => {
|
|
60
|
+
const data = { Subject: args.subject };
|
|
61
|
+
if (args.status)
|
|
62
|
+
data.Status = args.status;
|
|
63
|
+
if (args.priority)
|
|
64
|
+
data.Priority = args.priority;
|
|
65
|
+
if (args.origin)
|
|
66
|
+
data.Origin = args.origin;
|
|
67
|
+
if (args.description)
|
|
68
|
+
data.Description = args.description;
|
|
69
|
+
if (args.related_account_id)
|
|
70
|
+
data.AccountId = args.related_account_id;
|
|
71
|
+
if (args.related_contact_id)
|
|
72
|
+
data.ContactId = args.related_contact_id;
|
|
73
|
+
if (args.fields)
|
|
74
|
+
validateAndMergeCustomFields(data, args.fields);
|
|
75
|
+
const result = await conn.sobject('Case').create(data);
|
|
76
|
+
if (!result.success)
|
|
77
|
+
throw new ConnectorError('Failed to create case', 'CREATE_ERROR', formatVendorErrors(result.errors));
|
|
78
|
+
return JSON.stringify({ ok: true, status: 'success', object: 'Case', id: result.id, subject: args.subject });
|
|
79
|
+
});
|
|
80
|
+
}));
|
|
81
|
+
server.registerTool('salesforce_update_case', {
|
|
82
|
+
description: `Update a support case. Required: id. Updatable: subject, status, priority, origin, description, related_account_id, related_contact_id, fields.`,
|
|
83
|
+
inputSchema: z.object({
|
|
84
|
+
id: z.string().min(1).describe('Salesforce Case ID (required)'),
|
|
85
|
+
subject: z.string().optional().describe('Case subject'),
|
|
86
|
+
status: z.string().optional().describe('Case status (e.g., Working, Escalated, Closed)'),
|
|
87
|
+
priority: z.string().optional().describe('Priority (High, Medium, Low)'),
|
|
88
|
+
origin: z.string().optional().describe('Case origin'),
|
|
89
|
+
description: z.string().optional().describe('Description'),
|
|
90
|
+
related_account_id: z.string().optional().describe('Account ID to associate with'),
|
|
91
|
+
related_contact_id: z.string().optional().describe('Contact ID to associate with'),
|
|
92
|
+
fields: z.record(z.unknown()).optional().describe('Additional/custom fields as key-value pairs'),
|
|
93
|
+
}),
|
|
94
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: true },
|
|
95
|
+
}, withErrorHandling(async (args) => {
|
|
96
|
+
return withConnection(undefined, async (conn) => {
|
|
97
|
+
const updateData = { Id: args.id };
|
|
98
|
+
if (args.subject)
|
|
99
|
+
updateData.Subject = args.subject;
|
|
100
|
+
if (args.status)
|
|
101
|
+
updateData.Status = args.status;
|
|
102
|
+
if (args.priority)
|
|
103
|
+
updateData.Priority = args.priority;
|
|
104
|
+
if (args.origin)
|
|
105
|
+
updateData.Origin = args.origin;
|
|
106
|
+
if (args.description)
|
|
107
|
+
updateData.Description = args.description;
|
|
108
|
+
if (args.related_account_id)
|
|
109
|
+
updateData.AccountId = args.related_account_id;
|
|
110
|
+
if (args.related_contact_id)
|
|
111
|
+
updateData.ContactId = args.related_contact_id;
|
|
112
|
+
if (args.fields)
|
|
113
|
+
validateAndMergeCustomFields(updateData, args.fields);
|
|
114
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
115
|
+
const result = await conn.sobject('Case').update(updateData);
|
|
116
|
+
checkSaveResult(result, 'Failed to update case');
|
|
117
|
+
return JSON.stringify({ ok: true, status: 'success', object: 'Case', id: args.id });
|
|
118
|
+
});
|
|
119
|
+
}));
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=cases.js.map
|
package/dist/tools/contacts.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { withErrorHandling, escapeSOQL, escapeSOQLLike, validateFields, validateAndMergeCustomFields, checkSaveResult } from '../utils.js';
|
|
2
|
+
import { withErrorHandling, escapeSOQL, escapeSOQLLike, validateFields, validateAndMergeCustomFields, checkSaveResult, formatVendorErrors, sanitizeRecords } from '../utils.js';
|
|
3
3
|
import { withConnection } from '../client.js';
|
|
4
4
|
import { ConnectorError } from '../types.js';
|
|
5
5
|
export function registerContactTools(server) {
|
|
@@ -32,7 +32,7 @@ export function registerContactTools(server) {
|
|
|
32
32
|
const limit = Math.min(Math.max(1, args.limit ?? 50), 200);
|
|
33
33
|
query += ` LIMIT ${limit}`;
|
|
34
34
|
const result = await conn.query(query);
|
|
35
|
-
return JSON.stringify({ ok: true, records: result.records, totalSize: result.totalSize });
|
|
35
|
+
return JSON.stringify({ ok: true, records: sanitizeRecords(result.records, 'salesforce:get_contacts:records'), totalSize: result.totalSize });
|
|
36
36
|
});
|
|
37
37
|
}));
|
|
38
38
|
server.registerTool('salesforce_create_contact', {
|
|
@@ -46,7 +46,7 @@ export function registerContactTools(server) {
|
|
|
46
46
|
related_account_id: z.string().optional().describe('Account ID to associate with'),
|
|
47
47
|
fields: z.record(z.unknown()).optional().describe('Additional/custom fields as key-value pairs'),
|
|
48
48
|
}),
|
|
49
|
-
annotations: { readOnlyHint: false, destructiveHint:
|
|
49
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true },
|
|
50
50
|
}, withErrorHandling(async (args) => {
|
|
51
51
|
return withConnection(undefined, async (conn) => {
|
|
52
52
|
const data = { LastName: args.last_name };
|
|
@@ -64,7 +64,7 @@ export function registerContactTools(server) {
|
|
|
64
64
|
validateAndMergeCustomFields(data, args.fields);
|
|
65
65
|
const result = await conn.sobject('Contact').create(data);
|
|
66
66
|
if (!result.success)
|
|
67
|
-
throw new ConnectorError('Failed to create contact', 'CREATE_ERROR',
|
|
67
|
+
throw new ConnectorError('Failed to create contact', 'CREATE_ERROR', formatVendorErrors(result.errors));
|
|
68
68
|
return JSON.stringify({ ok: true, status: 'success', object: 'Contact', id: result.id, name: `${args.first_name || ''} ${args.last_name}`.trim() });
|
|
69
69
|
});
|
|
70
70
|
}));
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { withErrorHandling, escapeSOQL, escapeSOQLLike, validateFields, validateAndMergeCustomFields, formatSOQLDateTime, formatVendorErrors, sanitizeRecords } from '../utils.js';
|
|
3
|
+
import { withConnection } from '../client.js';
|
|
4
|
+
import { ConnectorError } from '../types.js';
|
|
5
|
+
export function registerEventTools(server) {
|
|
6
|
+
server.registerTool('salesforce_get_events', {
|
|
7
|
+
description: `Get calendar events. Filters: subject_contains, who_id, what_id, start_from, start_to. Dates accept YYYY-MM-DD (midnight UTC) or ISO 8601 datetimes. Returns: Id, Subject, StartDateTime, EndDateTime, Location, Description, WhoId, WhatId, OwnerId, IsAllDayEvent. Max 200 (default: 50).`,
|
|
8
|
+
inputSchema: z.object({
|
|
9
|
+
limit: z.number().int().min(1).max(200).optional().describe('Max results 1-200 (default: 50)'),
|
|
10
|
+
subject_contains: z.string().optional().describe('Filter by subject (case-insensitive)'),
|
|
11
|
+
who_id: z.string().optional().describe('Filter by WhoId (Contact or Lead ID)'),
|
|
12
|
+
what_id: z.string().optional().describe('Filter by WhatId (Account or Opportunity ID)'),
|
|
13
|
+
start_from: z.string().optional().describe('Events starting on/after this date or datetime (YYYY-MM-DD or ISO 8601; see tool description for an example)'),
|
|
14
|
+
start_to: z.string().optional().describe('Events starting on/before this date or datetime (YYYY-MM-DD or ISO 8601)'),
|
|
15
|
+
fields: z.array(z.string()).optional().describe('Custom fields (must be valid API names)'),
|
|
16
|
+
}),
|
|
17
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
18
|
+
}, withErrorHandling(async (args) => {
|
|
19
|
+
return withConnection(undefined, async (conn) => {
|
|
20
|
+
const defaultFields = ['Id', 'Subject', 'StartDateTime', 'EndDateTime', 'Location', 'Description', 'WhoId', 'WhatId', 'OwnerId', 'IsAllDayEvent'];
|
|
21
|
+
const fields = validateFields(args.fields || [], defaultFields);
|
|
22
|
+
let query = `SELECT ${fields.join(', ')} FROM Event`;
|
|
23
|
+
const conditions = [];
|
|
24
|
+
if (args.subject_contains)
|
|
25
|
+
conditions.push(`Subject LIKE '%${escapeSOQLLike(args.subject_contains)}%'`);
|
|
26
|
+
if (args.who_id)
|
|
27
|
+
conditions.push(`WhoId = '${escapeSOQL(args.who_id)}'`);
|
|
28
|
+
if (args.what_id)
|
|
29
|
+
conditions.push(`WhatId = '${escapeSOQL(args.what_id)}'`);
|
|
30
|
+
if (args.start_from)
|
|
31
|
+
conditions.push(`StartDateTime >= ${formatSOQLDateTime(args.start_from, 'start_from')}`);
|
|
32
|
+
if (args.start_to)
|
|
33
|
+
conditions.push(`StartDateTime <= ${formatSOQLDateTime(args.start_to, 'start_to')}`);
|
|
34
|
+
if (conditions.length > 0)
|
|
35
|
+
query += ` WHERE ${conditions.join(' AND ')}`;
|
|
36
|
+
query += ' ORDER BY StartDateTime ASC';
|
|
37
|
+
const limit = Math.min(Math.max(1, args.limit ?? 50), 200);
|
|
38
|
+
query += ` LIMIT ${limit}`;
|
|
39
|
+
const result = await conn.query(query);
|
|
40
|
+
return JSON.stringify({ ok: true, records: sanitizeRecords(result.records, 'salesforce:get_events:records'), totalSize: result.totalSize });
|
|
41
|
+
});
|
|
42
|
+
}));
|
|
43
|
+
server.registerTool('salesforce_create_event', {
|
|
44
|
+
description: `Create a calendar event. Required: subject, start_date_time, end_date_time. Optional: location, description, who_id, what_id, is_all_day_event, owner_id, fields. Datetimes must be ISO 8601 (e.g. "2026-01-09T14:30:00Z"); all-day events accept plain dates (YYYY-MM-DD).`,
|
|
45
|
+
inputSchema: z.object({
|
|
46
|
+
subject: z.string().min(1).describe('Event subject (required)'),
|
|
47
|
+
start_date_time: z.string().min(1).describe('Start (required) — ISO 8601 datetime (see tool description for an example), or YYYY-MM-DD for all-day events'),
|
|
48
|
+
end_date_time: z.string().min(1).describe('End (required) — ISO 8601 datetime, or YYYY-MM-DD for all-day events'),
|
|
49
|
+
location: z.string().optional().describe('Event location'),
|
|
50
|
+
description: z.string().optional().describe('Event description'),
|
|
51
|
+
who_id: z.string().optional().describe('Contact or Lead ID to invite/relate'),
|
|
52
|
+
what_id: z.string().optional().describe('Account or Opportunity ID to relate'),
|
|
53
|
+
is_all_day_event: z.boolean().optional().describe('All-day event (default: false)'),
|
|
54
|
+
owner_id: z.string().optional().describe('Owner User ID'),
|
|
55
|
+
fields: z.record(z.unknown()).optional().describe('Additional/custom fields as key-value pairs'),
|
|
56
|
+
}),
|
|
57
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true },
|
|
58
|
+
}, withErrorHandling(async (args) => {
|
|
59
|
+
// Validate formats up front for actionable errors; the validated values
|
|
60
|
+
// go into the create body verbatim (JSON body — no SOQL injection surface).
|
|
61
|
+
formatSOQLDateTime(args.start_date_time, 'start_date_time');
|
|
62
|
+
formatSOQLDateTime(args.end_date_time, 'end_date_time');
|
|
63
|
+
return withConnection(undefined, async (conn) => {
|
|
64
|
+
const data = {
|
|
65
|
+
Subject: args.subject,
|
|
66
|
+
StartDateTime: args.start_date_time,
|
|
67
|
+
EndDateTime: args.end_date_time,
|
|
68
|
+
};
|
|
69
|
+
if (args.location)
|
|
70
|
+
data.Location = args.location;
|
|
71
|
+
if (args.description)
|
|
72
|
+
data.Description = args.description;
|
|
73
|
+
if (args.who_id)
|
|
74
|
+
data.WhoId = args.who_id;
|
|
75
|
+
if (args.what_id)
|
|
76
|
+
data.WhatId = args.what_id;
|
|
77
|
+
if (args.is_all_day_event !== undefined)
|
|
78
|
+
data.IsAllDayEvent = args.is_all_day_event;
|
|
79
|
+
if (args.owner_id)
|
|
80
|
+
data.OwnerId = args.owner_id;
|
|
81
|
+
if (args.fields)
|
|
82
|
+
validateAndMergeCustomFields(data, args.fields);
|
|
83
|
+
const result = await conn.sobject('Event').create(data);
|
|
84
|
+
if (!result.success)
|
|
85
|
+
throw new ConnectorError('Failed to create event', 'CREATE_ERROR', formatVendorErrors(result.errors));
|
|
86
|
+
return JSON.stringify({ ok: true, status: 'success', object: 'Event', id: result.id, subject: args.subject });
|
|
87
|
+
});
|
|
88
|
+
}));
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=events.js.map
|
package/dist/tools/index.d.ts
CHANGED
|
@@ -4,6 +4,12 @@ export { registerContactTools } from './contacts.js';
|
|
|
4
4
|
export { registerOpportunityTools } from './opportunities.js';
|
|
5
5
|
export { registerLeadTools } from './leads.js';
|
|
6
6
|
export { registerTaskTools } from './tasks.js';
|
|
7
|
+
export { registerCaseTools } from './cases.js';
|
|
8
|
+
export { registerEventTools } from './events.js';
|
|
9
|
+
export { registerSearchTools } from './search.js';
|
|
10
|
+
export { registerNoteTools } from './notes.js';
|
|
11
|
+
export { registerCampaignTools } from './campaigns.js';
|
|
12
|
+
export { registerReportTools } from './reports.js';
|
|
7
13
|
export { registerUserTools } from './users.js';
|
|
8
14
|
export { registerQueryTools } from './query.js';
|
|
9
15
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/tools/index.js
CHANGED
|
@@ -4,6 +4,12 @@ export { registerContactTools } from './contacts.js';
|
|
|
4
4
|
export { registerOpportunityTools } from './opportunities.js';
|
|
5
5
|
export { registerLeadTools } from './leads.js';
|
|
6
6
|
export { registerTaskTools } from './tasks.js';
|
|
7
|
+
export { registerCaseTools } from './cases.js';
|
|
8
|
+
export { registerEventTools } from './events.js';
|
|
9
|
+
export { registerSearchTools } from './search.js';
|
|
10
|
+
export { registerNoteTools } from './notes.js';
|
|
11
|
+
export { registerCampaignTools } from './campaigns.js';
|
|
12
|
+
export { registerReportTools } from './reports.js';
|
|
7
13
|
export { registerUserTools } from './users.js';
|
|
8
14
|
export { registerQueryTools } from './query.js';
|
|
9
15
|
//# sourceMappingURL=index.js.map
|