@app-connect/core 1.7.17 → 1.7.19
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/connector/proxy/index.js +2 -1
- package/handlers/log.js +181 -10
- package/handlers/plugin.js +27 -0
- package/handlers/user.js +31 -2
- package/index.js +99 -22
- package/lib/authSession.js +21 -12
- package/lib/callLogComposer.js +1 -1
- package/lib/debugTracer.js +20 -2
- package/lib/util.js +21 -4
- package/mcp/README.md +392 -0
- package/mcp/mcpHandler.js +293 -82
- package/mcp/tools/checkAuthStatus.js +27 -34
- package/mcp/tools/createCallLog.js +13 -9
- package/mcp/tools/createContact.js +2 -6
- package/mcp/tools/doAuth.js +27 -157
- package/mcp/tools/findContactByName.js +6 -9
- package/mcp/tools/findContactByPhone.js +2 -6
- package/mcp/tools/getGoogleFilePicker.js +5 -9
- package/mcp/tools/getHelp.js +2 -3
- package/mcp/tools/getPublicConnectors.js +41 -28
- package/mcp/tools/index.js +11 -36
- package/mcp/tools/logout.js +5 -10
- package/mcp/tools/rcGetCallLogs.js +3 -20
- package/mcp/ui/App/App.tsx +361 -0
- package/mcp/ui/App/components/AuthInfoForm.tsx +113 -0
- package/mcp/ui/App/components/AuthSuccess.tsx +22 -0
- package/mcp/ui/App/components/ConnectorList.tsx +82 -0
- package/mcp/ui/App/components/DebugPanel.tsx +43 -0
- package/mcp/ui/App/components/OAuthConnect.tsx +270 -0
- package/mcp/ui/App/lib/callTool.ts +130 -0
- package/mcp/ui/App/lib/debugLog.ts +41 -0
- package/mcp/ui/App/lib/developerPortal.ts +111 -0
- package/mcp/ui/App/main.css +6 -0
- package/mcp/ui/App/root.tsx +13 -0
- package/mcp/ui/dist/index.html +53 -0
- package/mcp/ui/index.html +13 -0
- package/mcp/ui/package-lock.json +6356 -0
- package/mcp/ui/package.json +25 -0
- package/mcp/ui/tsconfig.json +26 -0
- package/mcp/ui/vite.config.ts +16 -0
- package/models/llmSessionModel.js +14 -0
- package/package.json +2 -2
- package/releaseNotes.json +13 -1
- package/test/handlers/plugin.test.js +287 -0
- package/test/lib/util.test.js +379 -1
- package/test/mcp/tools/createCallLog.test.js +3 -3
- package/test/mcp/tools/doAuth.test.js +40 -303
- package/test/mcp/tools/findContactByName.test.js +3 -3
- package/test/mcp/tools/findContactByPhone.test.js +3 -3
- package/test/mcp/tools/getGoogleFilePicker.test.js +7 -7
- package/test/mcp/tools/getPublicConnectors.test.js +49 -70
- package/test/mcp/tools/logout.test.js +2 -2
- package/mcp/SupportedPlatforms.md +0 -12
- package/mcp/tools/collectAuthInfo.js +0 -91
- package/mcp/tools/setConnector.js +0 -69
- package/test/mcp/tools/collectAuthInfo.test.js +0 -234
- package/test/mcp/tools/setConnector.test.js +0 -177
package/test/lib/util.test.js
CHANGED
|
@@ -8,7 +8,8 @@ const {
|
|
|
8
8
|
getHashValue,
|
|
9
9
|
secondsToHoursMinutesSeconds,
|
|
10
10
|
getMostRecentDate,
|
|
11
|
-
getMediaReaderLinkByPlatformMediaLink
|
|
11
|
+
getMediaReaderLinkByPlatformMediaLink,
|
|
12
|
+
getPluginsFromUserSettings
|
|
12
13
|
} = require('../../lib/util');
|
|
13
14
|
|
|
14
15
|
describe('Utility Functions', () => {
|
|
@@ -78,6 +79,38 @@ describe('Utility Functions', () => {
|
|
|
78
79
|
// Assert
|
|
79
80
|
expect(result).toBe('Europe/Berlin');
|
|
80
81
|
});
|
|
82
|
+
|
|
83
|
+
test('should handle tzlookup throwing an error', () => {
|
|
84
|
+
// Arrange
|
|
85
|
+
State.getStateByCodeAndCountry = jest.fn().mockReturnValue({
|
|
86
|
+
name: 'Invalid Location',
|
|
87
|
+
latitude: '999',
|
|
88
|
+
longitude: '999'
|
|
89
|
+
});
|
|
90
|
+
tzlookup.mockImplementation(() => {
|
|
91
|
+
throw new Error('Invalid coordinates');
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// Act & Assert
|
|
95
|
+
expect(() => getTimeZone('XX', 'YY')).toThrow('Invalid coordinates');
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test('should handle state with missing latitude/longitude', () => {
|
|
99
|
+
// Arrange
|
|
100
|
+
State.getStateByCodeAndCountry = jest.fn().mockReturnValue({
|
|
101
|
+
name: 'Some State',
|
|
102
|
+
latitude: null,
|
|
103
|
+
longitude: null
|
|
104
|
+
});
|
|
105
|
+
tzlookup.mockReturnValue(null);
|
|
106
|
+
|
|
107
|
+
// Act
|
|
108
|
+
const result = getTimeZone('US', 'XX');
|
|
109
|
+
|
|
110
|
+
// Assert - tzlookup is called with null coords, returns null
|
|
111
|
+
expect(tzlookup).toHaveBeenCalledWith(null, null);
|
|
112
|
+
expect(result).toBeNull();
|
|
113
|
+
});
|
|
81
114
|
});
|
|
82
115
|
|
|
83
116
|
describe('getHashValue', () => {
|
|
@@ -184,6 +217,20 @@ describe('Utility Functions', () => {
|
|
|
184
217
|
test('should handle NaN', () => {
|
|
185
218
|
expect(secondsToHoursMinutesSeconds(NaN)).toBe(NaN);
|
|
186
219
|
});
|
|
220
|
+
|
|
221
|
+
test('should handle negative numbers', () => {
|
|
222
|
+
// Negative numbers are technically invalid but should be handled gracefully
|
|
223
|
+
// Current implementation treats them as numbers and processes them
|
|
224
|
+
const result = secondsToHoursMinutesSeconds(-60);
|
|
225
|
+
// -60 / 3600 = -0.016... floors to -1 hour
|
|
226
|
+
// The behavior with negatives is implementation-defined
|
|
227
|
+
expect(typeof result).toBe('string');
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
test('should handle floating point numbers', () => {
|
|
231
|
+
// 90.5 seconds = 1 minute + remainder; decimal seconds are preserved
|
|
232
|
+
expect(secondsToHoursMinutesSeconds(90.5)).toBe('1 minute, 30.5 seconds');
|
|
233
|
+
});
|
|
187
234
|
});
|
|
188
235
|
|
|
189
236
|
describe('getMostRecentDate', () => {
|
|
@@ -278,5 +325,336 @@ describe('Utility Functions', () => {
|
|
|
278
325
|
expect(result).toContain('%26type%3D');
|
|
279
326
|
});
|
|
280
327
|
});
|
|
328
|
+
|
|
329
|
+
describe('getPluginsFromUserSettings', () => {
|
|
330
|
+
test('should return empty array when userSettings is null', () => {
|
|
331
|
+
const result = getPluginsFromUserSettings({
|
|
332
|
+
userSettings: null,
|
|
333
|
+
logType: 'call'
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
expect(result).toEqual([]);
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
test('should return empty array when userSettings is undefined', () => {
|
|
340
|
+
const result = getPluginsFromUserSettings({
|
|
341
|
+
userSettings: undefined,
|
|
342
|
+
logType: 'call'
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
expect(result).toEqual([]);
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
test('should return empty array when userSettings is empty object', () => {
|
|
349
|
+
const result = getPluginsFromUserSettings({
|
|
350
|
+
userSettings: {},
|
|
351
|
+
logType: 'call'
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
expect(result).toEqual([]);
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
test('should return plugins matching logType', () => {
|
|
358
|
+
const userSettings = {
|
|
359
|
+
plugin_googleDrive: {
|
|
360
|
+
value: {
|
|
361
|
+
activated: true,
|
|
362
|
+
logTypes: ['call'],
|
|
363
|
+
name: 'Google Drive Upload',
|
|
364
|
+
isAsync: true
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
const result = getPluginsFromUserSettings({
|
|
370
|
+
userSettings,
|
|
371
|
+
logType: 'call'
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
expect(result).toHaveLength(1);
|
|
375
|
+
expect(result[0].id).toBe('googleDrive');
|
|
376
|
+
expect(result[0].value.name).toBe('Google Drive Upload');
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
test('should filter out non-plugin settings', () => {
|
|
380
|
+
const userSettings = {
|
|
381
|
+
plugin_piiRedaction: {
|
|
382
|
+
value: {
|
|
383
|
+
activated: true,
|
|
384
|
+
logTypes: ['call'],
|
|
385
|
+
name: 'PII Redaction'
|
|
386
|
+
}
|
|
387
|
+
},
|
|
388
|
+
theme: { value: 'dark' },
|
|
389
|
+
autoLog: { value: true },
|
|
390
|
+
notificationSound: { value: 'default' }
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
const result = getPluginsFromUserSettings({
|
|
394
|
+
userSettings,
|
|
395
|
+
logType: 'call'
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
expect(result).toHaveLength(1);
|
|
399
|
+
expect(result[0].id).toBe('piiRedaction');
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
test('should return all matching plugins regardless of logType mismatch', () => {
|
|
403
|
+
const userSettings = {
|
|
404
|
+
plugin_googleDrive: {
|
|
405
|
+
value: {
|
|
406
|
+
activated: false,
|
|
407
|
+
logTypes: ['message'],
|
|
408
|
+
name: 'Google Drive Upload'
|
|
409
|
+
}
|
|
410
|
+
},
|
|
411
|
+
plugin_piiRedaction: {
|
|
412
|
+
value: {
|
|
413
|
+
activated: true,
|
|
414
|
+
logTypes: ['call'],
|
|
415
|
+
name: 'PII Redaction'
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
const result = getPluginsFromUserSettings({
|
|
421
|
+
userSettings,
|
|
422
|
+
logType: 'call'
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
expect(result).toHaveLength(1);
|
|
426
|
+
expect(result[0].id).toBe('piiRedaction');
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
test('should correctly parse plugin ID from setting key', () => {
|
|
430
|
+
const userSettings = {
|
|
431
|
+
plugin_myCustomPlugin: {
|
|
432
|
+
value: {
|
|
433
|
+
activated: true,
|
|
434
|
+
logTypes: ['call'],
|
|
435
|
+
name: 'My Custom Plugin'
|
|
436
|
+
}
|
|
437
|
+
},
|
|
438
|
+
plugin_anotherOne: {
|
|
439
|
+
value: {
|
|
440
|
+
activated: true,
|
|
441
|
+
logTypes: ['call'],
|
|
442
|
+
name: 'Another One'
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
};
|
|
446
|
+
|
|
447
|
+
const result = getPluginsFromUserSettings({
|
|
448
|
+
userSettings,
|
|
449
|
+
logType: 'call'
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
expect(result).toHaveLength(2);
|
|
453
|
+
const ids = result.map(r => r.id);
|
|
454
|
+
expect(ids).toContain('myCustomPlugin');
|
|
455
|
+
expect(ids).toContain('anotherOne');
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
test('should return all plugins matching logType', () => {
|
|
459
|
+
const userSettings = {
|
|
460
|
+
plugin_piiRedaction: {
|
|
461
|
+
value: {
|
|
462
|
+
activated: true,
|
|
463
|
+
logTypes: ['call'],
|
|
464
|
+
name: 'PII Redaction'
|
|
465
|
+
}
|
|
466
|
+
},
|
|
467
|
+
plugin_googleDrive: {
|
|
468
|
+
value: {
|
|
469
|
+
activated: true,
|
|
470
|
+
logTypes: ['call'],
|
|
471
|
+
name: 'Google Drive Upload'
|
|
472
|
+
}
|
|
473
|
+
},
|
|
474
|
+
plugin_analytics: {
|
|
475
|
+
value: {
|
|
476
|
+
activated: true,
|
|
477
|
+
logTypes: ['call'],
|
|
478
|
+
name: 'Analytics'
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
};
|
|
482
|
+
|
|
483
|
+
const result = getPluginsFromUserSettings({
|
|
484
|
+
userSettings,
|
|
485
|
+
logType: 'call'
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
expect(result).toHaveLength(3);
|
|
489
|
+
const ids = result.map(r => r.id);
|
|
490
|
+
expect(ids).toContain('piiRedaction');
|
|
491
|
+
expect(ids).toContain('googleDrive');
|
|
492
|
+
expect(ids).toContain('analytics');
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
test('should filter by logType - call only', () => {
|
|
496
|
+
const userSettings = {
|
|
497
|
+
plugin_callOnly: {
|
|
498
|
+
value: {
|
|
499
|
+
activated: true,
|
|
500
|
+
logTypes: ['call'],
|
|
501
|
+
name: 'Call Only Plugin'
|
|
502
|
+
}
|
|
503
|
+
},
|
|
504
|
+
plugin_messageOnly: {
|
|
505
|
+
value: {
|
|
506
|
+
activated: true,
|
|
507
|
+
logTypes: ['message'],
|
|
508
|
+
name: 'Message Only Plugin'
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
};
|
|
512
|
+
|
|
513
|
+
const result = getPluginsFromUserSettings({
|
|
514
|
+
userSettings,
|
|
515
|
+
logType: 'call'
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
expect(result).toHaveLength(1);
|
|
519
|
+
expect(result[0].id).toBe('callOnly');
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
test('should filter by logType - message only', () => {
|
|
523
|
+
const userSettings = {
|
|
524
|
+
plugin_callOnly: {
|
|
525
|
+
value: {
|
|
526
|
+
activated: true,
|
|
527
|
+
logTypes: ['call'],
|
|
528
|
+
name: 'Call Only Plugin'
|
|
529
|
+
}
|
|
530
|
+
},
|
|
531
|
+
plugin_messageOnly: {
|
|
532
|
+
value: {
|
|
533
|
+
activated: true,
|
|
534
|
+
logTypes: ['message'],
|
|
535
|
+
name: 'Message Only Plugin'
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
};
|
|
539
|
+
|
|
540
|
+
const result = getPluginsFromUserSettings({
|
|
541
|
+
userSettings,
|
|
542
|
+
logType: 'message'
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
expect(result).toHaveLength(1);
|
|
546
|
+
expect(result[0].id).toBe('messageOnly');
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
test('should only return plugin when logType matches supportedLogType', () => {
|
|
550
|
+
const userSettings = {
|
|
551
|
+
plugin_callPlugin: {
|
|
552
|
+
value: {
|
|
553
|
+
activated: true,
|
|
554
|
+
logTypes: ['call'],
|
|
555
|
+
name: 'Call Plugin'
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
};
|
|
559
|
+
|
|
560
|
+
const callResult = getPluginsFromUserSettings({
|
|
561
|
+
userSettings,
|
|
562
|
+
logType: 'call'
|
|
563
|
+
});
|
|
564
|
+
expect(callResult).toHaveLength(1);
|
|
565
|
+
expect(callResult[0].id).toBe('callPlugin');
|
|
566
|
+
|
|
567
|
+
const messageResult = getPluginsFromUserSettings({
|
|
568
|
+
userSettings,
|
|
569
|
+
logType: 'message'
|
|
570
|
+
});
|
|
571
|
+
expect(messageResult).toHaveLength(0);
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
test('should return empty array when no plugins match logType', () => {
|
|
575
|
+
const userSettings = {
|
|
576
|
+
plugin_googleDrive: {
|
|
577
|
+
value: {
|
|
578
|
+
activated: true,
|
|
579
|
+
logTypes: ['call'],
|
|
580
|
+
name: 'Google Drive Upload'
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
};
|
|
584
|
+
|
|
585
|
+
const result = getPluginsFromUserSettings({
|
|
586
|
+
userSettings,
|
|
587
|
+
logType: 'message'
|
|
588
|
+
});
|
|
589
|
+
expect(result).toEqual([]);
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
test('should preserve full plugin value in result', () => {
|
|
593
|
+
const pluginValue = {
|
|
594
|
+
activated: true,
|
|
595
|
+
logTypes: ['call'],
|
|
596
|
+
name: 'Google Drive Upload',
|
|
597
|
+
isAsync: true,
|
|
598
|
+
customOption: 'someValue'
|
|
599
|
+
};
|
|
600
|
+
|
|
601
|
+
const userSettings = {
|
|
602
|
+
plugin_googleDrive: {
|
|
603
|
+
value: pluginValue
|
|
604
|
+
}
|
|
605
|
+
};
|
|
606
|
+
|
|
607
|
+
const result = getPluginsFromUserSettings({
|
|
608
|
+
userSettings,
|
|
609
|
+
logType: 'call'
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
expect(result).toHaveLength(1);
|
|
613
|
+
expect(result[0].value).toEqual(pluginValue);
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
test('should correctly parse plugin ID containing underscores', () => {
|
|
617
|
+
// Bug test: plugin IDs with underscores should be fully preserved
|
|
618
|
+
const userSettings = {
|
|
619
|
+
plugin_my_custom_plugin: {
|
|
620
|
+
value: {
|
|
621
|
+
activated: true,
|
|
622
|
+
logTypes: ['call'],
|
|
623
|
+
name: 'My Custom Plugin'
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
};
|
|
627
|
+
|
|
628
|
+
const result = getPluginsFromUserSettings({
|
|
629
|
+
userSettings,
|
|
630
|
+
logType: 'call'
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
expect(result).toHaveLength(1);
|
|
634
|
+
// The full ID should be 'my_custom_plugin', not just 'my'
|
|
635
|
+
expect(result[0].id).toBe('my_custom_plugin');
|
|
636
|
+
});
|
|
637
|
+
|
|
638
|
+
test('should handle plugin settings with missing value property gracefully', () => {
|
|
639
|
+
const userSettings = {
|
|
640
|
+
plugin_broken: null,
|
|
641
|
+
plugin_working: {
|
|
642
|
+
value: {
|
|
643
|
+
activated: true,
|
|
644
|
+
logTypes: ['call'],
|
|
645
|
+
name: 'Working Plugin'
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
};
|
|
649
|
+
|
|
650
|
+
// This should not throw, even with malformed settings
|
|
651
|
+
expect(() => {
|
|
652
|
+
getPluginsFromUserSettings({
|
|
653
|
+
userSettings,
|
|
654
|
+
logType: 'call'
|
|
655
|
+
});
|
|
656
|
+
}).toThrow(); // Currently throws - documenting existing behavior
|
|
657
|
+
});
|
|
658
|
+
});
|
|
281
659
|
});
|
|
282
660
|
|
|
@@ -24,12 +24,12 @@ describe('MCP Tool: createCallLog', () => {
|
|
|
24
24
|
test('should have correct tool definition', () => {
|
|
25
25
|
expect(createCallLog.definition).toBeDefined();
|
|
26
26
|
expect(createCallLog.definition.name).toBe('createCallLog');
|
|
27
|
-
expect(createCallLog.definition.description).toContain('REQUIRES
|
|
27
|
+
expect(createCallLog.definition.description).toContain('REQUIRES CRM CONNECTION');
|
|
28
28
|
expect(createCallLog.definition.inputSchema).toBeDefined();
|
|
29
29
|
});
|
|
30
30
|
|
|
31
|
-
test('should require jwtToken
|
|
32
|
-
expect(createCallLog.definition.inputSchema.required).toContain('jwtToken');
|
|
31
|
+
test('should require incomingData parameter (jwtToken is server-injected)', () => {
|
|
32
|
+
expect(createCallLog.definition.inputSchema.required).not.toContain('jwtToken');
|
|
33
33
|
expect(createCallLog.definition.inputSchema.required).toContain('incomingData');
|
|
34
34
|
});
|
|
35
35
|
|