@velaro/mcp-server 1.0.2 → 2.0.1
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 +3 -1
- package/parity/actions.customer.js +39 -0
- package/parity/activity.customer.js +33 -0
- package/parity/ai-field-extraction.customer.js +78 -0
- package/parity/callback.customer.js +22 -0
- package/parity/check.customer.js +31 -0
- package/parity/deployment.customer.js +45 -0
- package/parity/endpoints.customer.js +87 -0
- package/parity/ingest.customer.js +22 -0
- package/parity/keyword-intelligence.customer.js +33 -0
- package/parity/knowledge-advisor.customer.js +26 -0
- package/parity/loader.js +32 -0
- package/parity/quality.customer.js +122 -0
- package/parity/rule.customer.js +48 -0
- package/parity/sentiment.customer.js +41 -0
- package/parity/site-builder.customer.js +12 -0
- package/parity/skill-routing.customer.js +79 -0
- package/parity/squarespace.customer.js +44 -0
- package/parity/woocommerce.customer.js +94 -0
- package/parity/zoom-phone.customer.js +49 -0
- package/server.js +365 -3974
- package/server.json +2 -2
- package/tool-annotations.js +8 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@velaro/mcp-server",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "Velaro MCP server — connect Claude and other AI agents directly to your Velaro account: KB, workflows, bots, conversations, contacts, routing, and more.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -45,6 +45,8 @@
|
|
|
45
45
|
"tool-annotations.js",
|
|
46
46
|
"log-search.js",
|
|
47
47
|
"messaging-base.js",
|
|
48
|
+
"parity/loader.js",
|
|
49
|
+
"parity/*.customer.js",
|
|
48
50
|
"workflow-schedule.js",
|
|
49
51
|
"server.json",
|
|
50
52
|
"README.md"
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// velaro actions: shared business actions. Site scope comes from the credential only.
|
|
2
|
+
// Parity module (CLI group -> MCP tools).
|
|
3
|
+
|
|
4
|
+
const enc = encodeURIComponent;
|
|
5
|
+
const qs = (o) => { const p = new URLSearchParams(); for (const [k, v] of Object.entries(o)) if (v !== undefined && v !== null && v !== '') p.set(k, String(v)); const s = p.toString(); return s ? '?' + s : ''; };
|
|
6
|
+
const pid = (v, n = 'id') => { const x = Number(v); if (!Number.isInteger(x) || x <= 0) throw new Error(n + ' must be a positive integer'); return x; };
|
|
7
|
+
const need = (a, ...ks) => { for (const k of ks) if (a[k] === undefined || a[k] === null || a[k] === '') throw new Error(k + ' is required'); };
|
|
8
|
+
|
|
9
|
+
export const tools = [
|
|
10
|
+
{
|
|
11
|
+
name: 'actions_list',
|
|
12
|
+
description: 'List registered business actions with kind (read or write), data class and AI skill tool names.',
|
|
13
|
+
inputSchema: {"type":"object","properties":{}},
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
name: 'actions_route',
|
|
17
|
+
description: 'Deterministically route a free-text request to the matching business action(s).',
|
|
18
|
+
inputSchema: {"type":"object","properties":{"text":{"type":"string","description":"The request text"},"max":{"type":"number","description":"Max matches (default 3)"}},"required":["text"]},
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
name: 'actions_run',
|
|
22
|
+
description: 'Execute a business action as the authenticated agent. Writes honour idempotencyKey: the same key returns the same result, never a duplicate.',
|
|
23
|
+
inputSchema: {"type":"object","properties":{"name":{"type":"string","description":"Action name from actions_list"},"request":{"type":"object","additionalProperties":true,"description":"Request body for the action"},"idempotencyKey":{"type":"string","description":"For writes, repeat the same key to get the same result"}},"required":["name"]},
|
|
24
|
+
},
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
export const handlers = {
|
|
28
|
+
actions_list: async (a, ctx) => {
|
|
29
|
+
return ctx.messagingGet('/BusinessActions');
|
|
30
|
+
},
|
|
31
|
+
actions_route: async (a, ctx) => {
|
|
32
|
+
return ctx.messagingPost('/BusinessActions/Route', { text: a.text, max: a.max ?? 3 });
|
|
33
|
+
},
|
|
34
|
+
actions_run: async (a, ctx) => {
|
|
35
|
+
need(a, 'name');
|
|
36
|
+
const h = a.idempotencyKey ? { 'Idempotency-Key': String(a.idempotencyKey) } : {};
|
|
37
|
+
return ctx.messagingRequest('POST', '/BusinessActions/' + enc(a.name) + '/Execute', a.request ?? {}, h);
|
|
38
|
+
},
|
|
39
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// velaro activity: integration activity logs and settings audit trail for the authenticated site. The CLI --site staff mode is the existing staff tool support_integration_logs.
|
|
2
|
+
// Parity module (CLI group -> MCP tools).
|
|
3
|
+
|
|
4
|
+
const enc = encodeURIComponent;
|
|
5
|
+
const qs = (o) => { const p = new URLSearchParams(); for (const [k, v] of Object.entries(o)) if (v !== undefined && v !== null && v !== '') p.set(k, String(v)); const s = p.toString(); return s ? '?' + s : ''; };
|
|
6
|
+
const pid = (v, n = 'id') => { const x = Number(v); if (!Number.isInteger(x) || x <= 0) throw new Error(n + ' must be a positive integer'); return x; };
|
|
7
|
+
const need = (a, ...ks) => { for (const k of ks) if (a[k] === undefined || a[k] === null || a[k] === '') throw new Error(k + ' is required'); };
|
|
8
|
+
|
|
9
|
+
export const tools = [
|
|
10
|
+
{
|
|
11
|
+
name: 'activity_logs',
|
|
12
|
+
description: 'Integration activity log for this site. slug is an integration name (HubSpot, Square), or skills for AI tool logs. Filter by severity, category, search; paged.',
|
|
13
|
+
inputSchema: {"type":"object","properties":{"slug":{"type":"string","description":"Integration name, or skills for AI tool logs"},"severity":{"type":"string","description":"Info, Warning, Error"},"category":{"type":"string","description":"Auth, Config, Request, Response, AiTool, Setup, Error"},"search":{"type":"string","description":"Message search"},"page":{"type":"integer","description":"Page number (default 1)"},"pageSize":{"type":"integer","description":"Results per page (max 100, default 25)"}}},
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
name: 'activity_audit_trail',
|
|
17
|
+
description: 'Who-changed-what settings history for this site, optionally filtered to one entity type or id. Full before/after detail needs a Compliance package.',
|
|
18
|
+
inputSchema: {"type":"object","properties":{"entityType":{"type":"string","description":"Entity type, e.g. Workflow"},"entityId":{"type":"string","description":"One specific entity id"},"take":{"type":"integer","description":"Max results (default 200, max 500)"}}},
|
|
19
|
+
},
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
export const handlers = {
|
|
23
|
+
activity_logs: async (a, ctx) => {
|
|
24
|
+
const skills = a.slug === 'skills';
|
|
25
|
+
const path = skills || !a.slug ? '/Integration/Logs' : '/Integration/Logs/' + enc(a.slug);
|
|
26
|
+
const q = qs({ severity: a.severity && a.severity !== 'All' ? a.severity : undefined, category: skills ? 'AiTool' : (a.category && a.category !== 'All' ? a.category : undefined), search: a.search, page: Math.max(1, a.page ?? 1), pageSize: Math.min(100, a.pageSize ?? 25) });
|
|
27
|
+
return ctx.api('GET', path + q);
|
|
28
|
+
},
|
|
29
|
+
activity_audit_trail: async (a, ctx) => {
|
|
30
|
+
const q = qs({ entityType: a.entityType, entityId: a.entityId, take: Math.min(500, a.take ?? 200) });
|
|
31
|
+
return ctx.api('GET', '/AuditTrail' + q);
|
|
32
|
+
},
|
|
33
|
+
};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// velaro ai-field-extraction: post-resolve AI field extraction settings and corrections. Site comes from the credential.
|
|
2
|
+
// Parity module (CLI group -> MCP tools).
|
|
3
|
+
|
|
4
|
+
const enc = encodeURIComponent;
|
|
5
|
+
const qs = (o) => { const p = new URLSearchParams(); for (const [k, v] of Object.entries(o)) if (v !== undefined && v !== null && v !== '') p.set(k, String(v)); const s = p.toString(); return s ? '?' + s : ''; };
|
|
6
|
+
const pid = (v, n = 'id') => { const x = Number(v); if (!Number.isInteger(x) || x <= 0) throw new Error(n + ' must be a positive integer'); return x; };
|
|
7
|
+
const need = (a, ...ks) => { for (const k of ks) if (a[k] === undefined || a[k] === null || a[k] === '') throw new Error(k + ' is required'); };
|
|
8
|
+
|
|
9
|
+
export const tools = [
|
|
10
|
+
{
|
|
11
|
+
name: 'ai_field_extraction_get',
|
|
12
|
+
description: 'Show the AI field extraction config for this site.',
|
|
13
|
+
inputSchema: {"type":"object","properties":{}},
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
name: 'ai_field_extraction_create',
|
|
17
|
+
description: 'Create the extraction config. Opt-in: extraction stays off until enabled is true.',
|
|
18
|
+
inputSchema: {"type":"object","properties":{"enabled":{"type":"boolean","description":"Opt-in switch (default off)"},"extractLineOfBusiness":{"type":"boolean","description":"Extract line of business"},"extractIntent":{"type":"boolean","description":"Extract intent"},"extractState":{"type":"boolean","description":"Extract state"},"extractIdentifiers":{"type":"boolean","description":"Extract identifiers"},"createThemeTags":{"type":"boolean","description":"Create theme tags"},"monthlyCap":{"type":"integer","description":"1 to 50000"},"minConfidence":{"type":"number","description":"0 to 1"}}},
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
name: 'ai_field_extraction_update',
|
|
22
|
+
description: 'Partial update of the extraction config; only passed fields change.',
|
|
23
|
+
inputSchema: {"type":"object","properties":{"enabled":{"type":"boolean","description":"Opt-in switch (default off)"},"extractLineOfBusiness":{"type":"boolean","description":"Extract line of business"},"extractIntent":{"type":"boolean","description":"Extract intent"},"extractState":{"type":"boolean","description":"Extract state"},"extractIdentifiers":{"type":"boolean","description":"Extract identifiers"},"createThemeTags":{"type":"boolean","description":"Create theme tags"},"monthlyCap":{"type":"integer","description":"1 to 50000"},"minConfidence":{"type":"number","description":"0 to 1"}}},
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
name: 'ai_field_extraction_delete',
|
|
27
|
+
description: 'Delete the extraction config for this site.',
|
|
28
|
+
inputSchema: {"type":"object","properties":{}},
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
name: 'ai_field_extraction_fields',
|
|
32
|
+
description: 'Show extracted fields for one conversation.',
|
|
33
|
+
inputSchema: {"type":"object","properties":{"conversationId":{"type":"integer","description":"Conversation id"}},"required":["conversationId"]},
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: 'ai_field_extraction_correct',
|
|
37
|
+
description: 'Agent correction of one extracted field on a conversation.',
|
|
38
|
+
inputSchema: {"type":"object","properties":{"conversationId":{"type":"integer","description":"Conversation id"},"fieldName":{"type":"string","description":"Field name"},"value":{"type":"string","description":"Corrected value"}},"required":["conversationId","fieldName","value"]},
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
name: 'ai_field_extraction_extract',
|
|
42
|
+
description: 'Run extraction now for one conversation.',
|
|
43
|
+
inputSchema: {"type":"object","properties":{"conversationId":{"type":"integer","description":"Conversation id"}},"required":["conversationId"]},
|
|
44
|
+
},
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
export const handlers = {
|
|
48
|
+
ai_field_extraction_get: async (a, ctx) => {
|
|
49
|
+
return ctx.messagingGet('/AiFieldExtraction/Config');
|
|
50
|
+
},
|
|
51
|
+
ai_field_extraction_create: async (a, ctx) => {
|
|
52
|
+
const b = {};
|
|
53
|
+
const K = { enabled: 'Enabled', extractLineOfBusiness: 'ExtractLineOfBusiness', extractIntent: 'ExtractIntent', extractState: 'ExtractState', extractIdentifiers: 'ExtractIdentifiers', createThemeTags: 'CreateThemeTags', monthlyCap: 'MonthlyCap', minConfidence: 'MinConfidence' };
|
|
54
|
+
for (const [k, p] of Object.entries(K)) if (a[k] !== undefined) b[p] = a[k];
|
|
55
|
+
return ctx.messagingPost('/AiFieldExtraction/Config', b);
|
|
56
|
+
},
|
|
57
|
+
ai_field_extraction_update: async (a, ctx) => {
|
|
58
|
+
const b = {};
|
|
59
|
+
const K = { enabled: 'Enabled', extractLineOfBusiness: 'ExtractLineOfBusiness', extractIntent: 'ExtractIntent', extractState: 'ExtractState', extractIdentifiers: 'ExtractIdentifiers', createThemeTags: 'CreateThemeTags', monthlyCap: 'MonthlyCap', minConfidence: 'MinConfidence' };
|
|
60
|
+
for (const [k, p] of Object.entries(K)) if (a[k] !== undefined) b[p] = a[k];
|
|
61
|
+
return ctx.messagingPut('/AiFieldExtraction/Config', b);
|
|
62
|
+
},
|
|
63
|
+
ai_field_extraction_delete: async (a, ctx) => {
|
|
64
|
+
await ctx.messagingDel('/AiFieldExtraction/Config');
|
|
65
|
+
return 'Deleted';
|
|
66
|
+
},
|
|
67
|
+
ai_field_extraction_fields: async (a, ctx) => {
|
|
68
|
+
return ctx.messagingGet('/AiFieldExtraction/Conversation/' + pid(a.conversationId, 'conversationId') + '/Fields');
|
|
69
|
+
},
|
|
70
|
+
ai_field_extraction_correct: async (a, ctx) => {
|
|
71
|
+
need(a, 'fieldName', 'value');
|
|
72
|
+
await ctx.messagingPut('/AiFieldExtraction/Conversation/' + pid(a.conversationId, 'conversationId') + '/Fields/' + enc(a.fieldName), { Value: a.value });
|
|
73
|
+
return 'Corrected';
|
|
74
|
+
},
|
|
75
|
+
ai_field_extraction_extract: async (a, ctx) => {
|
|
76
|
+
return ctx.messagingPost('/AiFieldExtraction/Conversation/' + pid(a.conversationId, 'conversationId') + '/Extract', {});
|
|
77
|
+
},
|
|
78
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// velaro callback report: Callback Scheduling supervisor report. Site comes from the credential.
|
|
2
|
+
// Parity module (CLI group -> MCP tools).
|
|
3
|
+
|
|
4
|
+
const enc = encodeURIComponent;
|
|
5
|
+
const qs = (o) => { const p = new URLSearchParams(); for (const [k, v] of Object.entries(o)) if (v !== undefined && v !== null && v !== '') p.set(k, String(v)); const s = p.toString(); return s ? '?' + s : ''; };
|
|
6
|
+
const pid = (v, n = 'id') => { const x = Number(v); if (!Number.isInteger(x) || x <= 0) throw new Error(n + ' must be a positive integer'); return x; };
|
|
7
|
+
const need = (a, ...ks) => { for (const k of ks) if (a[k] === undefined || a[k] === null || a[k] === '') throw new Error(k + ' is required'); };
|
|
8
|
+
|
|
9
|
+
export const tools = [
|
|
10
|
+
{
|
|
11
|
+
name: 'callback_report',
|
|
12
|
+
description: 'Callback Scheduling supervisor report: summary counts (requested, completed, missed, overdue, rescheduled) plus paged items.',
|
|
13
|
+
inputSchema: {"type":"object","properties":{"status":{"type":"string","description":"Pending, Scheduled, Fulfilled, Failed"},"mode":{"type":"string","description":"Fulfillment mode, e.g. AiCallback, AgentCallback"},"days":{"type":"integer","description":"Lookback days 1-365 (default 30)"},"page":{"type":"integer","description":"Page (default 1)"},"pageSize":{"type":"integer","description":"Per page, max 200 (default 50)"}}},
|
|
14
|
+
},
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
export const handlers = {
|
|
18
|
+
callback_report: async (a, ctx) => {
|
|
19
|
+
const q = qs({ status: a.status, fulfillmentMode: a.mode, dateRangeDays: a.days ?? 30, page: Math.max(1, a.page ?? 1), pageSize: Math.min(200, a.pageSize ?? 50) });
|
|
20
|
+
return ctx.messagingGet('/CallbackScheduling/report' + q);
|
|
21
|
+
},
|
|
22
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// velaro check: read-only site health and configuration summary (counts per feature area).
|
|
2
|
+
// Parity module (CLI group -> MCP tools).
|
|
3
|
+
|
|
4
|
+
const enc = encodeURIComponent;
|
|
5
|
+
const qs = (o) => { const p = new URLSearchParams(); for (const [k, v] of Object.entries(o)) if (v !== undefined && v !== null && v !== '') p.set(k, String(v)); const s = p.toString(); return s ? '?' + s : ''; };
|
|
6
|
+
const pid = (v, n = 'id') => { const x = Number(v); if (!Number.isInteger(x) || x <= 0) throw new Error(n + ' must be a positive integer'); return x; };
|
|
7
|
+
const need = (a, ...ks) => { for (const k of ks) if (a[k] === undefined || a[k] === null || a[k] === '') throw new Error(k + ' is required'); };
|
|
8
|
+
|
|
9
|
+
export const tools = [
|
|
10
|
+
{
|
|
11
|
+
name: 'check_site_health',
|
|
12
|
+
description: 'Read-only health and configuration summary for this site: API health, websites, teams, AI configs, knowledge entries, workflows, rules, IVR config and agent count. Arrays are returned as counts.',
|
|
13
|
+
inputSchema: {"type":"object","properties":{}},
|
|
14
|
+
},
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
export const handlers = {
|
|
18
|
+
check_site_health: async (a, ctx) => {
|
|
19
|
+
const probes = { health: '/api/health', deployments: '/Deployment', teams: '/Teams/List', aiConfigs: '/AIConfiguration/list', botQnA: '/BotQnA', knowledgeOverrides: '/Ticket/knowledge-overrides', workflows: '/Workflows/List', rules: '/Rules/List', ivrConfig: '/api/ivr/config', agents: '/Users/List' };
|
|
20
|
+
const out = {};
|
|
21
|
+
await Promise.all(Object.entries(probes).map(async ([k, p]) => {
|
|
22
|
+
try {
|
|
23
|
+
const r = await ctx.messagingGet(p);
|
|
24
|
+
if (k === 'workflows') out[k] = { count: (r ?? []).filter((w) => !w.isTemplate).length };
|
|
25
|
+
else if (Array.isArray(r)) out[k] = { count: r.length };
|
|
26
|
+
else out[k] = r;
|
|
27
|
+
} catch (e) { out[k] = { error: String(e.message).slice(0, 200) }; }
|
|
28
|
+
}));
|
|
29
|
+
return out;
|
|
30
|
+
},
|
|
31
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// velaro deployment: websites (deployments) for the authenticated site.
|
|
2
|
+
// Parity module (CLI group -> MCP tools).
|
|
3
|
+
|
|
4
|
+
const enc = encodeURIComponent;
|
|
5
|
+
const qs = (o) => { const p = new URLSearchParams(); for (const [k, v] of Object.entries(o)) if (v !== undefined && v !== null && v !== '') p.set(k, String(v)); const s = p.toString(); return s ? '?' + s : ''; };
|
|
6
|
+
const pid = (v, n = 'id') => { const x = Number(v); if (!Number.isInteger(x) || x <= 0) throw new Error(n + ' must be a positive integer'); return x; };
|
|
7
|
+
const need = (a, ...ks) => { for (const k of ks) if (a[k] === undefined || a[k] === null || a[k] === '') throw new Error(k + ' is required'); };
|
|
8
|
+
|
|
9
|
+
export const tools = [
|
|
10
|
+
{
|
|
11
|
+
name: 'deployment_list',
|
|
12
|
+
description: 'List this site websites (deployments) and teams so a website can be matched to its team.',
|
|
13
|
+
inputSchema: {"type":"object","properties":{}},
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
name: 'deployment_rename',
|
|
17
|
+
description: 'Rename a website (deployment) display name.',
|
|
18
|
+
inputSchema: {"type":"object","properties":{"id":{"type":"integer","description":"Deployment id"},"name":{"type":"string","description":"New display name"}},"required":["id","name"]},
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
name: 'deployment_assign_team',
|
|
22
|
+
description: 'Assign a team to a website (deployment). The team must exist on this site.',
|
|
23
|
+
inputSchema: {"type":"object","properties":{"id":{"type":"integer","description":"Deployment id"},"teamId":{"type":"integer","description":"Team id"}},"required":["id","teamId"]},
|
|
24
|
+
},
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
export const handlers = {
|
|
28
|
+
deployment_list: async (a, ctx) => {
|
|
29
|
+
const [deployments, teams] = await Promise.all([ctx.api('GET', '/Deployment'), ctx.api('GET', '/Teams/List')]);
|
|
30
|
+
return { deployments, teams };
|
|
31
|
+
},
|
|
32
|
+
deployment_rename: async (a, ctx) => {
|
|
33
|
+
need(a, 'name');
|
|
34
|
+
const id = pid(a.id);
|
|
35
|
+
const dep = await ctx.api('GET', '/Deployment/' + id);
|
|
36
|
+
return ctx.api('POST', '/Deployment', { ...dep, id, displayName: a.name });
|
|
37
|
+
},
|
|
38
|
+
deployment_assign_team: async (a, ctx) => {
|
|
39
|
+
const id = pid(a.id);
|
|
40
|
+
const teamId = pid(a.teamId, 'teamId');
|
|
41
|
+
const [dep, teams] = await Promise.all([ctx.api('GET', '/Deployment/' + id), ctx.api('GET', '/Teams/List')]);
|
|
42
|
+
if (!(teams ?? []).some((t) => t.id === teamId)) throw new Error('Team ' + teamId + ' not found on this site');
|
|
43
|
+
return ctx.api('POST', '/Deployment', { ...dep, id, teamId });
|
|
44
|
+
},
|
|
45
|
+
};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// Parity tools for tenant-scoped endpoints. siteId always comes from the credential, never from an argument.
|
|
2
|
+
// Restore tools are tenant-scoped, take explicit numeric ids, and never act in bulk or by filter.
|
|
3
|
+
const id = (d) => ({ type: 'integer', minimum: 1, description: d });
|
|
4
|
+
|
|
5
|
+
export const tools = [
|
|
6
|
+
{
|
|
7
|
+
name: 'ai_usage_get_summary',
|
|
8
|
+
description: 'AI usage summary for your own site (GET /AiUsageReport/summary).',
|
|
9
|
+
inputSchema: { type: 'object', properties: {} },
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
name: 'ai_usage_update_budget_alert',
|
|
13
|
+
description: 'Update your site AI budget alert (enabled, threshold percent, recipient emails). Partial update; Admin/Manager only.',
|
|
14
|
+
inputSchema: { type: 'object', properties: { enabled: { type: 'boolean' }, thresholdPercent: { type: 'integer', minimum: 1, maximum: 1000 }, recipientEmails: { type: 'string', description: 'Comma-separated emails' } } },
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
name: 'ai_usage_delete_budget_alert',
|
|
18
|
+
description: 'Delete your site AI budget alert configuration (tenant-scoped, no arguments).',
|
|
19
|
+
inputSchema: { type: 'object', properties: {} },
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
name: 'contactlist_list_deleted',
|
|
23
|
+
description: 'List soft-deleted contact lists for your site that can be restored.',
|
|
24
|
+
inputSchema: { type: 'object', properties: {} },
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
name: 'contactlist_restore',
|
|
28
|
+
description: 'Restore ONE soft-deleted contact list by explicit numeric id. Tenant-scoped; no bulk or filter-based restore.',
|
|
29
|
+
inputSchema: { type: 'object', properties: { id: id('Contact list id') }, required: ['id'] },
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: 'contactlist_list_deleted_members',
|
|
33
|
+
description: 'List soft-deleted members of one contact list (explicit list id).',
|
|
34
|
+
inputSchema: { type: 'object', properties: { listId: id('Contact list id') }, required: ['listId'] },
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
name: 'contactlist_restore_member',
|
|
38
|
+
description: 'Restore ONE soft-deleted contact list member by explicit list id and member id. Tenant-scoped; no bulk restore.',
|
|
39
|
+
inputSchema: { type: 'object', properties: { listId: id('Contact list id'), memberId: id('Member id') }, required: ['listId', 'memberId'] },
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: 'contact_segment_list_deleted',
|
|
43
|
+
description: 'List soft-deleted contact segments for your site.',
|
|
44
|
+
inputSchema: { type: 'object', properties: {} },
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: 'kb_restore_deleted_article',
|
|
48
|
+
description: 'Restore ONE soft-deleted KB article by explicit numeric id. Tenant-scoped; no bulk or filter-based restore.',
|
|
49
|
+
inputSchema: { type: 'object', properties: { id: id('KB article id') }, required: ['id'] },
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
name: 'kb_restore_deleted_topic',
|
|
53
|
+
description: 'Restore ONE soft-deleted KB topic by explicit numeric id. Tenant-scoped; no bulk or filter-based restore.',
|
|
54
|
+
inputSchema: { type: 'object', properties: { id: id('KB topic id') }, required: ['id'] },
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: 'entitlements_get_mine',
|
|
58
|
+
description: 'Resolved feature entitlements for your own site (site comes from your credential).',
|
|
59
|
+
inputSchema: { type: 'object', properties: {} },
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
name: 'site_settings_get',
|
|
63
|
+
description: 'General settings for your own site (read-only).',
|
|
64
|
+
inputSchema: { type: 'object', properties: {} },
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
name: 'platform_uptime_get',
|
|
68
|
+
description: 'Platform uptime figures (read-only; site admins).',
|
|
69
|
+
inputSchema: { type: 'object', properties: {} },
|
|
70
|
+
},
|
|
71
|
+
];
|
|
72
|
+
|
|
73
|
+
export const handlers = {
|
|
74
|
+
ai_usage_get_summary: (_a, c) => c.api('GET', '/AiUsageReport/summary'),
|
|
75
|
+
ai_usage_update_budget_alert: (a, c) => c.api('PUT', '/AiUsageReport/budget-alert', { enabled: a.enabled, thresholdPercent: a.thresholdPercent, recipientEmails: a.recipientEmails }),
|
|
76
|
+
ai_usage_delete_budget_alert: (_a, c) => c.api('DELETE', '/AiUsageReport/budget-alert'),
|
|
77
|
+
contactlist_list_deleted: (_a, c) => c.api('GET', '/ContactLists/deleted'),
|
|
78
|
+
contactlist_restore: (a, c) => c.api('POST', `/ContactLists/${Number(a.id)}/restore`),
|
|
79
|
+
contactlist_list_deleted_members: (a, c) => c.api('GET', `/ContactLists/${Number(a.listId)}/Members/deleted`),
|
|
80
|
+
contactlist_restore_member: (a, c) => c.api('POST', `/ContactLists/${Number(a.listId)}/Members/${Number(a.memberId)}/restore`),
|
|
81
|
+
contact_segment_list_deleted: (_a, c) => c.api('GET', '/ContactSegments/deleted'),
|
|
82
|
+
kb_restore_deleted_article: (a, c) => c.api('POST', `/api/kb/articles/${Number(a.id)}/restore`),
|
|
83
|
+
kb_restore_deleted_topic: (a, c) => c.api('POST', `/api/kb/topics/${Number(a.id)}/restore`),
|
|
84
|
+
entitlements_get_mine: (_a, c) => c.api('GET', '/Entitlements/my'),
|
|
85
|
+
site_settings_get: (_a, c) => c.api('GET', '/api/SiteSettings'),
|
|
86
|
+
platform_uptime_get: (_a, c) => c.api('GET', '/PlatformUptime'),
|
|
87
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// velaro ingest: trigger KB ingestion for a completed scraper job. Site comes from the credential.
|
|
2
|
+
// Parity module (CLI group -> MCP tools).
|
|
3
|
+
|
|
4
|
+
const enc = encodeURIComponent;
|
|
5
|
+
const qs = (o) => { const p = new URLSearchParams(); for (const [k, v] of Object.entries(o)) if (v !== undefined && v !== null && v !== '') p.set(k, String(v)); const s = p.toString(); return s ? '?' + s : ''; };
|
|
6
|
+
const pid = (v, n = 'id') => { const x = Number(v); if (!Number.isInteger(x) || x <= 0) throw new Error(n + ' must be a positive integer'); return x; };
|
|
7
|
+
const need = (a, ...ks) => { for (const k of ks) if (a[k] === undefined || a[k] === null || a[k] === '') throw new Error(k + ' is required'); };
|
|
8
|
+
|
|
9
|
+
export const tools = [
|
|
10
|
+
{
|
|
11
|
+
name: 'ingest_start',
|
|
12
|
+
description: 'Trigger knowledge base ingestion for a completed scraper job on this site. Embedding runs in the background.',
|
|
13
|
+
inputSchema: {"type":"object","properties":{"jobId":{"type":"string","description":"Scraper job id to ingest"}},"required":["jobId"]},
|
|
14
|
+
},
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
export const handlers = {
|
|
18
|
+
ingest_start: async (a, ctx) => {
|
|
19
|
+
need(a, 'jobId');
|
|
20
|
+
return ctx.messagingPost('/AzureIndexes/IngestJobDirect', { jobId: String(a.jobId) });
|
|
21
|
+
},
|
|
22
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// velaro keyword-intelligence: keyword and phrase analytics over conversations.
|
|
2
|
+
// Parity module (CLI group -> MCP tools).
|
|
3
|
+
|
|
4
|
+
const enc = encodeURIComponent;
|
|
5
|
+
const qs = (o) => { const p = new URLSearchParams(); for (const [k, v] of Object.entries(o)) if (v !== undefined && v !== null && v !== '') p.set(k, String(v)); const s = p.toString(); return s ? '?' + s : ''; };
|
|
6
|
+
const pid = (v, n = 'id') => { const x = Number(v); if (!Number.isInteger(x) || x <= 0) throw new Error(n + ' must be a positive integer'); return x; };
|
|
7
|
+
const need = (a, ...ks) => { for (const k of ks) if (a[k] === undefined || a[k] === null || a[k] === '') throw new Error(k + ' is required'); };
|
|
8
|
+
|
|
9
|
+
export const tools = [
|
|
10
|
+
{
|
|
11
|
+
name: 'keyword_intelligence_top',
|
|
12
|
+
description: 'Top keywords/phrases in a date range, optionally grouped and filtered by team, agent or website.',
|
|
13
|
+
inputSchema: {"type":"object","properties":{"from":{"type":"string","description":"ISO start date"},"to":{"type":"string","description":"ISO end date"},"groupBy":{"type":"string","description":"Grouping (default none)"},"ngram":{"type":"integer","description":"N-gram size (default 3)"},"limit":{"type":"integer","description":"Max rows (default 50)"},"team":{"type":"integer","description":"Team id"},"agent":{"type":"integer","description":"Agent id"},"website":{"type":"string","description":"Website"}},"required":["from","to"]},
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
name: 'keyword_intelligence_conversations',
|
|
17
|
+
description: 'Conversations that contain a keyword in a date range, optionally filtered by team or agent.',
|
|
18
|
+
inputSchema: {"type":"object","properties":{"keyword":{"type":"string","description":"Keyword or phrase"},"from":{"type":"string","description":"ISO start date"},"to":{"type":"string","description":"ISO end date"},"team":{"type":"integer","description":"Team id"},"agent":{"type":"integer","description":"Agent id"}},"required":["keyword","from","to"]},
|
|
19
|
+
},
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
export const handlers = {
|
|
23
|
+
keyword_intelligence_top: async (a, ctx) => {
|
|
24
|
+
need(a, 'from', 'to');
|
|
25
|
+
const q = qs({ from: a.from, to: a.to, groupBy: a.groupBy ?? 'none', ngram: a.ngram ?? 3, limit: a.limit ?? 50, teamId: a.team, agentId: a.agent, website: a.website });
|
|
26
|
+
return ctx.messagingGet('/api/keyword-intelligence/top' + q);
|
|
27
|
+
},
|
|
28
|
+
keyword_intelligence_conversations: async (a, ctx) => {
|
|
29
|
+
need(a, 'keyword', 'from', 'to');
|
|
30
|
+
const q = qs({ keyword: a.keyword, from: a.from, to: a.to, teamId: a.team, agentId: a.agent });
|
|
31
|
+
return ctx.messagingGet('/api/keyword-intelligence/conversations' + q);
|
|
32
|
+
},
|
|
33
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// velaro knowledge-advisor advise: what to upload and how big the index will be.
|
|
2
|
+
// Parity module (CLI group -> MCP tools).
|
|
3
|
+
|
|
4
|
+
const enc = encodeURIComponent;
|
|
5
|
+
const qs = (o) => { const p = new URLSearchParams(); for (const [k, v] of Object.entries(o)) if (v !== undefined && v !== null && v !== '') p.set(k, String(v)); const s = p.toString(); return s ? '?' + s : ''; };
|
|
6
|
+
const pid = (v, n = 'id') => { const x = Number(v); if (!Number.isInteger(x) || x <= 0) throw new Error(n + ' must be a positive integer'); return x; };
|
|
7
|
+
const need = (a, ...ks) => { for (const k of ks) if (a[k] === undefined || a[k] === null || a[k] === '') throw new Error(k + ' is required'); };
|
|
8
|
+
|
|
9
|
+
export const tools = [
|
|
10
|
+
{
|
|
11
|
+
name: 'knowledge_advisor_advise',
|
|
12
|
+
description: 'Advise how to load knowledge sources: per-source recommendation, what to upload, chunk estimate and per-answer retrieval size. Pass sources, or a single source via kind.',
|
|
13
|
+
inputSchema: {"type":"object","properties":{"sources":{"type":"array","items":{"type":"string"},"description":"Array of source objects (name, kind, pages, rows, changeFrequency, hasExactIdentifiers, scanned, hasLiveSystem)"},"kind":{"type":"string","description":"manual_pdf, spec_sheet, policy_document, faq_or_help_pages, product_catalog, price_list, cross_reference_sheet, images_or_drawings, cad_files, other_documents"},"name":{"type":"string","description":"Source name"},"pages":{"type":"integer","description":"Page count"},"rows":{"type":"integer","description":"Row count"},"changeFrequency":{"type":"string","description":"static, monthly, weekly, daily, realtime"},"exactIds":{"type":"boolean","description":"Answers depend on exact part numbers or SKUs"},"scanned":{"type":"boolean","description":"PDF is scanned images"},"liveSystem":{"type":"boolean","description":"A connected store, ERP or CRM can be queried"}}},
|
|
14
|
+
},
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
export const handlers = {
|
|
18
|
+
knowledge_advisor_advise: async (a, ctx) => {
|
|
19
|
+
let sources = a.sources;
|
|
20
|
+
if (!Array.isArray(sources) || sources.length === 0) {
|
|
21
|
+
if (!a.kind) throw new Error('Give sources (array) or kind.');
|
|
22
|
+
sources = [{ name: a.name, kind: a.kind, pages: a.pages, rows: a.rows, changeFrequency: a.changeFrequency, hasExactIdentifiers: !!a.exactIds, scanned: !!a.scanned, hasLiveSystem: !!a.liveSystem }];
|
|
23
|
+
}
|
|
24
|
+
return ctx.messagingPost('/KnowledgeAdvisor/advise', { sources });
|
|
25
|
+
},
|
|
26
|
+
};
|
package/parity/loader.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Loads parity tool modules. Each module in this directory is named
|
|
2
|
+
// <area>.customer.js -> tools exposed by the customer MCP (mcp/server.js), tenant-scoped only
|
|
3
|
+
// <area>.staff.js -> tools exposed ONLY by velaro-staff (mcp/staff-server.js)
|
|
4
|
+
// and exports:
|
|
5
|
+
// export const tools = [ { name, description, inputSchema }, ... ];
|
|
6
|
+
// export const handlers = { tool_name: async (args, ctx) => string|object, ... };
|
|
7
|
+
// ctx carries the host server's authenticated HTTP helpers (api, messagingGet/Post/Put/Del when present).
|
|
8
|
+
// A customer tool MUST NOT take siteId from its arguments: the site always comes from the credential.
|
|
9
|
+
import { readdirSync } from 'node:fs';
|
|
10
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
11
|
+
import { dirname, join } from 'node:path';
|
|
12
|
+
|
|
13
|
+
export async function loadParity(kind /* 'customer' | 'staff' */) {
|
|
14
|
+
const dir = dirname(fileURLToPath(import.meta.url));
|
|
15
|
+
const out = { tools: [], handlers: {} };
|
|
16
|
+
for (const f of readdirSync(dir).filter((x) => x.endsWith(`.${kind}.js`)).sort()) {
|
|
17
|
+
const mod = await import(pathToFileURL(join(dir, f)).href);
|
|
18
|
+
for (const t of mod.tools || []) {
|
|
19
|
+
if (out.handlers[t.name] || !mod.handlers?.[t.name]) throw new Error(`parity module ${f}: tool ${t.name} duplicate or missing handler`);
|
|
20
|
+
out.tools.push(t);
|
|
21
|
+
out.handlers[t.name] = mod.handlers[t.name];
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return out;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function runParity(parity, name, args, ctx) {
|
|
28
|
+
const h = parity.handlers[name];
|
|
29
|
+
if (!h) return undefined;
|
|
30
|
+
const r = await h(args, ctx);
|
|
31
|
+
return typeof r === 'string' ? r : JSON.stringify(r, null, 2);
|
|
32
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
// velaro quality: sentiment, agent performance, QA scoring, rubrics and alert settings.
|
|
2
|
+
// Parity module (CLI group -> MCP tools).
|
|
3
|
+
|
|
4
|
+
const enc = encodeURIComponent;
|
|
5
|
+
const qs = (o) => { const p = new URLSearchParams(); for (const [k, v] of Object.entries(o)) if (v !== undefined && v !== null && v !== '') p.set(k, String(v)); const s = p.toString(); return s ? '?' + s : ''; };
|
|
6
|
+
const pid = (v, n = 'id') => { const x = Number(v); if (!Number.isInteger(x) || x <= 0) throw new Error(n + ' must be a positive integer'); return x; };
|
|
7
|
+
const need = (a, ...ks) => { for (const k of ks) if (a[k] === undefined || a[k] === null || a[k] === '') throw new Error(k + ' is required'); };
|
|
8
|
+
|
|
9
|
+
export const tools = [
|
|
10
|
+
{
|
|
11
|
+
name: 'quality_sentiment',
|
|
12
|
+
description: 'Sentiment report. kind: overview, breakdown, conversation-overview, conversation-breakdown, contact-overview, agent-breakdown. Defaults to the last 30 days.',
|
|
13
|
+
inputSchema: {"type":"object","properties":{"kind":{"type":"string","description":"Report kind (default overview)"},"from":{"type":"string","description":"ISO start"},"to":{"type":"string","description":"ISO end"},"groupBy":{"type":"string","description":"Grouping (default day)"},"deployment":{"type":"integer","description":"Website (deployment row id)"},"team":{"type":"integer","description":"Team id"}}},
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
name: 'quality_agent_performance',
|
|
17
|
+
description: 'Agent performance report for the last N days, optionally per website or team.',
|
|
18
|
+
inputSchema: {"type":"object","properties":{"days":{"type":"integer","description":"Lookback days (default 30)"},"deployment":{"type":"integer","description":"Website (deployment row id)"},"team":{"type":"integer","description":"Team id"}}},
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
name: 'quality_low_scores',
|
|
22
|
+
description: 'Lowest-scoring QA conversations in the last N days.',
|
|
23
|
+
inputSchema: {"type":"object","properties":{"threshold":{"type":"number","description":"Score threshold"},"days":{"type":"integer","description":"Lookback days (default 30)"},"agent":{"type":"integer","description":"Agent id"},"take":{"type":"integer","description":"Max rows (default 50)"}}},
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
name: 'quality_calibration',
|
|
27
|
+
description: 'QA calibration report (AI score versus human review) for the last N days.',
|
|
28
|
+
inputSchema: {"type":"object","properties":{"days":{"type":"integer","description":"Lookback days (default 90)"}}},
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
name: 'quality_rubric_list',
|
|
32
|
+
description: 'List QA rubrics.',
|
|
33
|
+
inputSchema: {"type":"object","properties":{}},
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: 'quality_rubric_get_default',
|
|
37
|
+
description: 'Get the default QA rubric.',
|
|
38
|
+
inputSchema: {"type":"object","properties":{}},
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
name: 'quality_rubric_get',
|
|
42
|
+
description: 'Get one QA rubric.',
|
|
43
|
+
inputSchema: {"type":"object","properties":{"id":{"type":"integer","description":"Rubric id"}},"required":["id"]},
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
name: 'quality_rubric_create',
|
|
47
|
+
description: 'Create a QA rubric from a rubric JSON body.',
|
|
48
|
+
inputSchema: {"type":"object","properties":{"rubric":{"type":"object","additionalProperties":true,"description":"Rubric body"}},"required":["rubric"]},
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
name: 'quality_rubric_update',
|
|
52
|
+
description: 'Update a QA rubric from a rubric JSON body.',
|
|
53
|
+
inputSchema: {"type":"object","properties":{"id":{"type":"integer","description":"Rubric id"},"rubric":{"type":"object","additionalProperties":true,"description":"Rubric body"}},"required":["id","rubric"]},
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
name: 'quality_rubric_delete',
|
|
57
|
+
description: 'Delete a QA rubric.',
|
|
58
|
+
inputSchema: {"type":"object","properties":{"id":{"type":"integer","description":"Rubric id"}},"required":["id"]},
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
name: 'quality_alerts_get',
|
|
62
|
+
description: 'Read the quality alert settings.',
|
|
63
|
+
inputSchema: {"type":"object","properties":{}},
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
name: 'quality_alerts_update',
|
|
67
|
+
description: 'Update the quality alert settings from a settings JSON body.',
|
|
68
|
+
inputSchema: {"type":"object","properties":{"settings":{"type":"object","additionalProperties":true,"description":"Alert settings body"}},"required":["settings"]},
|
|
69
|
+
},
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
export const handlers = {
|
|
73
|
+
quality_sentiment: async (a, ctx) => {
|
|
74
|
+
const R = { overview: 'sentiment-overview', breakdown: 'sentiment-breakdown', 'conversation-overview': 'conversation-sentiment-overview', 'conversation-breakdown': 'conversation-sentiment-breakdown', 'contact-overview': 'contact-sentiment-overview', 'agent-breakdown': 'agent-sentiment-breakdown' };
|
|
75
|
+
const route = R[a.kind ?? 'overview'];
|
|
76
|
+
if (!route) throw new Error('kind must be one of: ' + Object.keys(R).join(', '));
|
|
77
|
+
const end = a.to ? new Date(a.to) : new Date();
|
|
78
|
+
const start = a.from ? new Date(a.from) : new Date(end.getTime() - 30 * 86400000);
|
|
79
|
+
const body = { Start: start.toISOString(), End: end.toISOString(), GroupBy: a.groupBy ?? 'day' };
|
|
80
|
+
if (a.deployment != null) body.DeploymentId = a.deployment;
|
|
81
|
+
if (a.team != null) body.TeamId = a.team;
|
|
82
|
+
return ctx.messagingPost('/Reports/' + route, body);
|
|
83
|
+
},
|
|
84
|
+
quality_agent_performance: async (a, ctx) => {
|
|
85
|
+
const q = qs({ days: a.days ?? 30, deploymentId: a.deployment, teamId: a.team });
|
|
86
|
+
return ctx.messagingGet('/api/admin/Reports/AgentPerformance' + q);
|
|
87
|
+
},
|
|
88
|
+
quality_low_scores: async (a, ctx) => {
|
|
89
|
+
const q = qs({ days: a.days ?? 30, take: a.take ?? 50, threshold: a.threshold, agentId: a.agent });
|
|
90
|
+
return ctx.messagingGet('/api/admin/QualityQa/low-scores' + q);
|
|
91
|
+
},
|
|
92
|
+
quality_calibration: async (a, ctx) => {
|
|
93
|
+
return ctx.messagingGet('/api/admin/QualityQa/calibration' + qs({ days: a.days ?? 90 }));
|
|
94
|
+
},
|
|
95
|
+
quality_rubric_list: async (a, ctx) => {
|
|
96
|
+
return ctx.messagingGet('/api/admin/QualityQa/rubrics');
|
|
97
|
+
},
|
|
98
|
+
quality_rubric_get_default: async (a, ctx) => {
|
|
99
|
+
return ctx.messagingGet('/api/admin/QualityQa/rubrics/default');
|
|
100
|
+
},
|
|
101
|
+
quality_rubric_get: async (a, ctx) => {
|
|
102
|
+
return ctx.messagingGet('/api/admin/QualityQa/rubrics/' + pid(a.id));
|
|
103
|
+
},
|
|
104
|
+
quality_rubric_create: async (a, ctx) => {
|
|
105
|
+
need(a, 'rubric');
|
|
106
|
+
return ctx.messagingPost('/api/admin/QualityQa/rubrics', a.rubric);
|
|
107
|
+
},
|
|
108
|
+
quality_rubric_update: async (a, ctx) => {
|
|
109
|
+
need(a, 'rubric');
|
|
110
|
+
return ctx.messagingPut('/api/admin/QualityQa/rubrics/' + pid(a.id), a.rubric);
|
|
111
|
+
},
|
|
112
|
+
quality_rubric_delete: async (a, ctx) => {
|
|
113
|
+
return ctx.messagingDel('/api/admin/QualityQa/rubrics/' + pid(a.id));
|
|
114
|
+
},
|
|
115
|
+
quality_alerts_get: async (a, ctx) => {
|
|
116
|
+
return ctx.messagingGet('/api/admin/QualityQa/alert-settings');
|
|
117
|
+
},
|
|
118
|
+
quality_alerts_update: async (a, ctx) => {
|
|
119
|
+
need(a, 'settings');
|
|
120
|
+
return ctx.messagingPut('/api/admin/QualityQa/alert-settings', a.settings);
|
|
121
|
+
},
|
|
122
|
+
};
|