@opencx/mcp 1.15.15 → 1.15.21
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/campaign-tools.spec.d.ts +2 -0
- package/dist/campaign-tools.spec.d.ts.map +1 -0
- package/dist/campaign-tools.spec.js +508 -0
- package/dist/campaign-tools.spec.js.map +1 -0
- package/dist/companies.spec.d.ts +2 -0
- package/dist/companies.spec.d.ts.map +1 -0
- package/dist/companies.spec.js +191 -0
- package/dist/companies.spec.js.map +1 -0
- package/dist/phone-caller-number-tool.spec.d.ts +2 -0
- package/dist/phone-caller-number-tool.spec.d.ts.map +1 -0
- package/dist/phone-caller-number-tool.spec.js +97 -0
- package/dist/phone-caller-number-tool.spec.js.map +1 -0
- package/dist/schema.d.ts +10756 -7565
- package/dist/schema.d.ts.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +12 -2
- package/dist/server.js.map +1 -1
- package/dist/slack-tools.spec.d.ts +2 -0
- package/dist/slack-tools.spec.d.ts.map +1 -0
- package/dist/slack-tools.spec.js +148 -0
- package/dist/slack-tools.spec.js.map +1 -0
- package/dist/tools/campaign-channel.d.ts +4 -0
- package/dist/tools/campaign-channel.d.ts.map +1 -0
- package/dist/tools/campaign-channel.js +250 -0
- package/dist/tools/campaign-channel.js.map +1 -0
- package/dist/tools/campaign-discovery.d.ts +4 -0
- package/dist/tools/campaign-discovery.d.ts.map +1 -0
- package/dist/tools/campaign-discovery.js +70 -0
- package/dist/tools/campaign-discovery.js.map +1 -0
- package/dist/tools/campaign-qualification.d.ts +4 -0
- package/dist/tools/campaign-qualification.d.ts.map +1 -0
- package/dist/tools/campaign-qualification.js +289 -0
- package/dist/tools/campaign-qualification.js.map +1 -0
- package/dist/tools/campaign-spine.d.ts +4 -0
- package/dist/tools/campaign-spine.d.ts.map +1 -0
- package/dist/tools/campaign-spine.js +630 -0
- package/dist/tools/campaign-spine.js.map +1 -0
- package/dist/tools/companies.d.ts +4 -0
- package/dist/tools/companies.d.ts.map +1 -0
- package/dist/tools/companies.js +127 -0
- package/dist/tools/companies.js.map +1 -0
- package/dist/tools/macros.js +1 -1
- package/dist/tools/macros.js.map +1 -1
- package/dist/tools/phone.d.ts.map +1 -1
- package/dist/tools/phone.js +54 -2
- package/dist/tools/phone.js.map +1 -1
- package/dist/tools/reports.d.ts.map +1 -1
- package/dist/tools/reports.js +1 -0
- package/dist/tools/reports.js.map +1 -1
- package/dist/tools/sla.d.ts.map +1 -1
- package/dist/tools/sla.js +3 -3
- package/dist/tools/sla.js.map +1 -1
- package/dist/tools/slack.d.ts +4 -0
- package/dist/tools/slack.d.ts.map +1 -0
- package/dist/tools/slack.js +76 -0
- package/dist/tools/slack.js.map +1 -0
- package/dist/utils/session-channel.d.ts +2 -2
- package/dist/utils/session-channel.d.ts.map +1 -1
- package/dist/utils/session-channel.js +1 -0
- package/dist/utils/session-channel.js.map +1 -1
- package/package.json +1 -1
- package/dist/tools/sequences.d.ts +0 -4
- package/dist/tools/sequences.d.ts.map +0 -1
- package/dist/tools/sequences.js +0 -182
- package/dist/tools/sequences.js.map +0 -1
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
3
|
+
import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js';
|
|
4
|
+
import { createServer } from './server.js';
|
|
5
|
+
function mockResponse(body, status = 200, statusText = 'OK') {
|
|
6
|
+
return new Response(JSON.stringify(body), { status, statusText });
|
|
7
|
+
}
|
|
8
|
+
function getRequestUrl(call) {
|
|
9
|
+
const arg = call[0];
|
|
10
|
+
if (arg instanceof Request)
|
|
11
|
+
return arg.url;
|
|
12
|
+
return String(arg);
|
|
13
|
+
}
|
|
14
|
+
function getRequestMethod(call) {
|
|
15
|
+
const arg = call[0];
|
|
16
|
+
if (arg instanceof Request)
|
|
17
|
+
return arg.method;
|
|
18
|
+
const opts = call[1];
|
|
19
|
+
return opts.method ?? 'GET';
|
|
20
|
+
}
|
|
21
|
+
async function getRequestBody(call) {
|
|
22
|
+
const arg = call[0];
|
|
23
|
+
if (arg instanceof Request) {
|
|
24
|
+
const text = await arg.clone().text();
|
|
25
|
+
return text ? JSON.parse(text) : undefined;
|
|
26
|
+
}
|
|
27
|
+
const opts = call[1];
|
|
28
|
+
return opts.body ? JSON.parse(opts.body) : undefined;
|
|
29
|
+
}
|
|
30
|
+
function contentJson(content) {
|
|
31
|
+
const parsed = content;
|
|
32
|
+
return JSON.parse(parsed[0].text);
|
|
33
|
+
}
|
|
34
|
+
const COMPANY = {
|
|
35
|
+
id: 'c1',
|
|
36
|
+
external_id: 'acct-1',
|
|
37
|
+
name: 'Acme Retail',
|
|
38
|
+
custom_data: { plan: 'growth' },
|
|
39
|
+
agent_owner: { id: 7, name: 'Dana', email: 'dana@open.cx' },
|
|
40
|
+
created_at: '2026-08-22T00:00:00.000Z',
|
|
41
|
+
updated_at: '2026-08-22T00:00:00.000Z',
|
|
42
|
+
};
|
|
43
|
+
describe('company tools', () => {
|
|
44
|
+
let client;
|
|
45
|
+
let cleanup;
|
|
46
|
+
let fetchSpy;
|
|
47
|
+
beforeEach(async () => {
|
|
48
|
+
fetchSpy = vi.fn();
|
|
49
|
+
vi.stubGlobal('fetch', fetchSpy);
|
|
50
|
+
const server = createServer('test-api-key', 'https://api.test.com');
|
|
51
|
+
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
|
|
52
|
+
await server.connect(serverTransport);
|
|
53
|
+
client = new Client({ name: 'test-client', version: '1.0.0' });
|
|
54
|
+
await client.connect(clientTransport);
|
|
55
|
+
cleanup = async () => {
|
|
56
|
+
await client.close();
|
|
57
|
+
await server.close();
|
|
58
|
+
};
|
|
59
|
+
});
|
|
60
|
+
afterEach(async () => {
|
|
61
|
+
await cleanup();
|
|
62
|
+
vi.restoreAllMocks();
|
|
63
|
+
});
|
|
64
|
+
it('registers all eight company tools', async () => {
|
|
65
|
+
const { tools } = await client.listTools();
|
|
66
|
+
const toolNames = tools.map((t) => t.name);
|
|
67
|
+
expect(toolNames).toContain('list_companies');
|
|
68
|
+
expect(toolNames).toContain('get_company');
|
|
69
|
+
expect(toolNames).toContain('upsert_company');
|
|
70
|
+
expect(toolNames).toContain('set_company_owner');
|
|
71
|
+
expect(toolNames).toContain('list_contact_companies');
|
|
72
|
+
expect(toolNames).toContain('link_contact_to_company');
|
|
73
|
+
expect(toolNames).toContain('link_contacts_to_companies');
|
|
74
|
+
expect(toolNames).toContain('unlink_contact_from_company');
|
|
75
|
+
});
|
|
76
|
+
it('list_companies GETs /companies and returns the owner', async () => {
|
|
77
|
+
fetchSpy.mockResolvedValueOnce(mockResponse({ data: [COMPANY], total: 1, page: 1, limit: 50 }));
|
|
78
|
+
const result = await client.callTool({ name: 'list_companies', arguments: {} });
|
|
79
|
+
const call = fetchSpy.mock.calls[0];
|
|
80
|
+
expect(getRequestUrl(call)).toBe('https://api.test.com/companies');
|
|
81
|
+
expect(getRequestMethod(call)).toBe('GET');
|
|
82
|
+
// The owner has to survive the wrapper — it is the whole point of the tool
|
|
83
|
+
// for an operator asking "who owns this account?".
|
|
84
|
+
const body = contentJson(result.content);
|
|
85
|
+
expect(body.data[0]?.agent_owner?.email).toBe('dana@open.cx');
|
|
86
|
+
});
|
|
87
|
+
it('get_company GETs the per-company route', async () => {
|
|
88
|
+
fetchSpy.mockResolvedValueOnce(mockResponse(COMPANY));
|
|
89
|
+
await client.callTool({ name: 'get_company', arguments: { company_id: 'c1' } });
|
|
90
|
+
const call = fetchSpy.mock.calls[0];
|
|
91
|
+
expect(getRequestUrl(call)).toBe('https://api.test.com/companies/c1');
|
|
92
|
+
expect(getRequestMethod(call)).toBe('GET');
|
|
93
|
+
});
|
|
94
|
+
it('upsert_company POSTs /companies with the external key', async () => {
|
|
95
|
+
fetchSpy.mockResolvedValueOnce(mockResponse(COMPANY, 201, 'Created'));
|
|
96
|
+
await client.callTool({
|
|
97
|
+
name: 'upsert_company',
|
|
98
|
+
arguments: { external_id: 'acct-1', name: 'Acme Retail', custom_data: { plan: 'growth' } },
|
|
99
|
+
});
|
|
100
|
+
const call = fetchSpy.mock.calls[0];
|
|
101
|
+
expect(getRequestUrl(call)).toBe('https://api.test.com/companies');
|
|
102
|
+
expect(getRequestMethod(call)).toBe('POST');
|
|
103
|
+
expect(await getRequestBody(call)).toEqual({
|
|
104
|
+
external_id: 'acct-1',
|
|
105
|
+
name: 'Acme Retail',
|
|
106
|
+
custom_data: { plan: 'growth' },
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
it('set_company_owner PUTs the owner route with the email', async () => {
|
|
110
|
+
fetchSpy.mockResolvedValueOnce(mockResponse(COMPANY));
|
|
111
|
+
await client.callTool({
|
|
112
|
+
name: 'set_company_owner',
|
|
113
|
+
arguments: { external_id: 'acct-1', owner: { email: 'dana@open.cx' } },
|
|
114
|
+
});
|
|
115
|
+
const call = fetchSpy.mock.calls[0];
|
|
116
|
+
expect(getRequestUrl(call)).toBe('https://api.test.com/companies/acct-1/owner');
|
|
117
|
+
expect(getRequestMethod(call)).toBe('PUT');
|
|
118
|
+
// external_id belongs in the path, never the body.
|
|
119
|
+
expect(await getRequestBody(call)).toEqual({ owner: { email: 'dana@open.cx' } });
|
|
120
|
+
});
|
|
121
|
+
it('set_company_owner passes an explicit null through to clear the owner', async () => {
|
|
122
|
+
fetchSpy.mockResolvedValueOnce(mockResponse({ ...COMPANY, agent_owner: null }));
|
|
123
|
+
await client.callTool({
|
|
124
|
+
name: 'set_company_owner',
|
|
125
|
+
arguments: { external_id: 'acct-1', owner: null },
|
|
126
|
+
});
|
|
127
|
+
const call = fetchSpy.mock.calls[0];
|
|
128
|
+
// A dropped null would read as "no owner given" and be rejected instead of
|
|
129
|
+
// clearing, so the null has to survive serialization.
|
|
130
|
+
expect(await getRequestBody(call)).toEqual({ owner: null });
|
|
131
|
+
});
|
|
132
|
+
it('upsert_company carries an owner through in one call', async () => {
|
|
133
|
+
fetchSpy.mockResolvedValueOnce(mockResponse(COMPANY, 201, 'Created'));
|
|
134
|
+
await client.callTool({
|
|
135
|
+
name: 'upsert_company',
|
|
136
|
+
arguments: {
|
|
137
|
+
external_id: 'acct-1',
|
|
138
|
+
name: 'Acme Retail',
|
|
139
|
+
owner: { email: 'dana@open.cx' },
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
const call = fetchSpy.mock.calls[0];
|
|
143
|
+
expect(await getRequestBody(call)).toEqual({
|
|
144
|
+
external_id: 'acct-1',
|
|
145
|
+
name: 'Acme Retail',
|
|
146
|
+
owner: { email: 'dana@open.cx' },
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
it('list_contact_companies GETs the contact route', async () => {
|
|
150
|
+
fetchSpy.mockResolvedValueOnce(mockResponse({ companies: [COMPANY] }));
|
|
151
|
+
await client.callTool({ name: 'list_contact_companies', arguments: { contact_id: 'ct1' } });
|
|
152
|
+
const call = fetchSpy.mock.calls[0];
|
|
153
|
+
expect(getRequestUrl(call)).toBe('https://api.test.com/contacts/ct1/companies');
|
|
154
|
+
expect(getRequestMethod(call)).toBe('GET');
|
|
155
|
+
});
|
|
156
|
+
it('link_contact_to_company POSTs the company key to the contact route', async () => {
|
|
157
|
+
fetchSpy.mockResolvedValueOnce(mockResponse({ outcome: 'linked', company: COMPANY }, 201, 'Created'));
|
|
158
|
+
await client.callTool({
|
|
159
|
+
name: 'link_contact_to_company',
|
|
160
|
+
arguments: { contact_id: 'ct1', external_id: 'acct-1' },
|
|
161
|
+
});
|
|
162
|
+
const call = fetchSpy.mock.calls[0];
|
|
163
|
+
expect(getRequestUrl(call)).toBe('https://api.test.com/contacts/ct1/companies');
|
|
164
|
+
expect(getRequestMethod(call)).toBe('POST');
|
|
165
|
+
expect(await getRequestBody(call)).toEqual({ external_id: 'acct-1' });
|
|
166
|
+
});
|
|
167
|
+
it('link_contacts_to_companies POSTs the batch to /companies/links', async () => {
|
|
168
|
+
fetchSpy.mockResolvedValueOnce(mockResponse({ results: [], summary: { total: 0, linked: 0, unchanged: 0, failed: 0 } }, 201, 'Created'));
|
|
169
|
+
await client.callTool({
|
|
170
|
+
name: 'link_contacts_to_companies',
|
|
171
|
+
arguments: { links: [{ contact_email: 'a@open.cx', external_id: 'acct-1' }] },
|
|
172
|
+
});
|
|
173
|
+
const call = fetchSpy.mock.calls[0];
|
|
174
|
+
expect(getRequestUrl(call)).toBe('https://api.test.com/companies/links');
|
|
175
|
+
expect(getRequestMethod(call)).toBe('POST');
|
|
176
|
+
expect(await getRequestBody(call)).toEqual({
|
|
177
|
+
links: [{ contact_email: 'a@open.cx', external_id: 'acct-1' }],
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
it('unlink_contact_from_company DELETEs one named company', async () => {
|
|
181
|
+
fetchSpy.mockResolvedValueOnce(mockResponse({ outcome: 'unlinked' }));
|
|
182
|
+
await client.callTool({
|
|
183
|
+
name: 'unlink_contact_from_company',
|
|
184
|
+
arguments: { contact_id: 'ct1', external_id: 'acct-1' },
|
|
185
|
+
});
|
|
186
|
+
const call = fetchSpy.mock.calls[0];
|
|
187
|
+
expect(getRequestUrl(call)).toBe('https://api.test.com/contacts/ct1/companies/acct-1');
|
|
188
|
+
expect(getRequestMethod(call)).toBe('DELETE');
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
//# sourceMappingURL=companies.spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"companies.spec.js","sourceRoot":"","sources":["../src/companies.spec.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"phone-caller-number-tool.spec.d.ts","sourceRoot":"","sources":["../src/phone-caller-number-tool.spec.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
2
|
+
import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js';
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import { createServer } from './server.js';
|
|
6
|
+
// The caller-number attach tool, from the agent's side of the wire. The
|
|
7
|
+
// error-path test exists because of a specific failure: the first cut
|
|
8
|
+
// destructured `{ error }` from the client — unreachable, since the client
|
|
9
|
+
// middleware THROWS on any non-ok response — so a 400 surfaced as a thrown
|
|
10
|
+
// exception ("the tool is broken") instead of explainable data.
|
|
11
|
+
function mockResponse(body, status = 200) {
|
|
12
|
+
return new Response(JSON.stringify(body), { status, statusText: 'X' });
|
|
13
|
+
}
|
|
14
|
+
function requestUrl(call) {
|
|
15
|
+
const arg = call[0];
|
|
16
|
+
return arg instanceof Request ? arg.url : String(arg);
|
|
17
|
+
}
|
|
18
|
+
async function requestBody(call) {
|
|
19
|
+
const arg = call[0];
|
|
20
|
+
if (arg instanceof Request) {
|
|
21
|
+
const text = await arg.clone().text();
|
|
22
|
+
return text ? JSON.parse(text) : undefined;
|
|
23
|
+
}
|
|
24
|
+
const opts = call[1];
|
|
25
|
+
return opts?.body ? JSON.parse(opts.body) : undefined;
|
|
26
|
+
}
|
|
27
|
+
/** The tool's single text-content JSON payload, parsed — never a cast. */
|
|
28
|
+
function parseToolJson(content) {
|
|
29
|
+
const [first] = z.array(z.object({ type: z.string(), text: z.string() })).parse(content);
|
|
30
|
+
if (!first)
|
|
31
|
+
throw new Error('tool returned no content');
|
|
32
|
+
return z.record(z.string(), z.unknown()).parse(JSON.parse(first.text));
|
|
33
|
+
}
|
|
34
|
+
describe('set_phone_agent_caller_number tool', () => {
|
|
35
|
+
let client;
|
|
36
|
+
let cleanup;
|
|
37
|
+
let fetchSpy;
|
|
38
|
+
beforeEach(async () => {
|
|
39
|
+
fetchSpy = vi.fn();
|
|
40
|
+
vi.stubGlobal('fetch', fetchSpy);
|
|
41
|
+
const server = createServer('test-api-key', 'https://api.test.com');
|
|
42
|
+
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
|
|
43
|
+
await server.connect(serverTransport);
|
|
44
|
+
client = new Client({ name: 'test-client', version: '1.0.0' });
|
|
45
|
+
await client.connect(clientTransport);
|
|
46
|
+
cleanup = async () => {
|
|
47
|
+
await client.close();
|
|
48
|
+
await server.close();
|
|
49
|
+
};
|
|
50
|
+
});
|
|
51
|
+
afterEach(async () => {
|
|
52
|
+
await cleanup();
|
|
53
|
+
vi.restoreAllMocks();
|
|
54
|
+
});
|
|
55
|
+
it('is declared, and PUTs the caller number to the right path', async () => {
|
|
56
|
+
fetchSpy.mockResolvedValue(mockResponse({ phone_agent_id: 'agent-1', caller_number: '+15005551234' }));
|
|
57
|
+
const result = await client.callTool({
|
|
58
|
+
name: 'set_phone_agent_caller_number',
|
|
59
|
+
arguments: { phone_agent_id: 'agent-1', caller_number: '+15005551234' },
|
|
60
|
+
});
|
|
61
|
+
expect(result.isError ?? false).toBe(false);
|
|
62
|
+
const call = fetchSpy.mock.calls[0];
|
|
63
|
+
expect(call).toBeDefined();
|
|
64
|
+
expect(requestUrl(call)).toContain('/phone/agent-1/caller-number');
|
|
65
|
+
expect(await requestBody(call)).toEqual({ caller_number: '+15005551234' });
|
|
66
|
+
});
|
|
67
|
+
it('detaches with null', async () => {
|
|
68
|
+
fetchSpy.mockResolvedValue(mockResponse({ phone_agent_id: 'agent-1', caller_number: null }));
|
|
69
|
+
const result = await client.callTool({
|
|
70
|
+
name: 'set_phone_agent_caller_number',
|
|
71
|
+
arguments: { phone_agent_id: 'agent-1', caller_number: null },
|
|
72
|
+
});
|
|
73
|
+
expect(result.isError ?? false).toBe(false);
|
|
74
|
+
expect(await requestBody(fetchSpy.mock.calls[0])).toEqual({ caller_number: null });
|
|
75
|
+
});
|
|
76
|
+
it('a 400 (unverified number) comes back as DATA the agent can explain — not a thrown error', async () => {
|
|
77
|
+
fetchSpy.mockResolvedValue(mockResponse({ message: 'caller_number is not a verified phone number' }, 400));
|
|
78
|
+
const result = await client.callTool({
|
|
79
|
+
name: 'set_phone_agent_caller_number',
|
|
80
|
+
arguments: { phone_agent_id: 'agent-1', caller_number: '+19998887777' },
|
|
81
|
+
});
|
|
82
|
+
// Not the SDK's exception envelope: our own structured refusal.
|
|
83
|
+
const payload = parseToolJson(result.content);
|
|
84
|
+
expect(payload).toMatchObject({ ok: false, status: 400 });
|
|
85
|
+
expect(JSON.stringify(payload)).toContain('not a verified phone number');
|
|
86
|
+
});
|
|
87
|
+
it('a 404 (foreign/missing agent) comes back as data too', async () => {
|
|
88
|
+
fetchSpy.mockResolvedValue(mockResponse({ message: 'Agent not found' }, 404));
|
|
89
|
+
const result = await client.callTool({
|
|
90
|
+
name: 'set_phone_agent_caller_number',
|
|
91
|
+
arguments: { phone_agent_id: 'nope', caller_number: null },
|
|
92
|
+
});
|
|
93
|
+
const payload = parseToolJson(result.content);
|
|
94
|
+
expect(payload).toMatchObject({ ok: false, status: 404 });
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
//# sourceMappingURL=phone-caller-number-tool.spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"phone-caller-number-tool.spec.js","sourceRoot":"","sources":["../src/phone-caller-number-tool.spec.ts"],"names":[],"mappings":"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"}
|