@connect-xlab/mcp-server 0.3.4 → 0.4.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/dist/compact.js +301 -0
- package/dist/config.js +3 -0
- package/dist/index.js +37 -9
- package/dist/tools/checkPartnerAgentLinks.js +40 -0
- package/dist/tools/createPartnerAgent.js +45 -0
- package/dist/tools/deletePartnerAgent.js +33 -0
- package/dist/tools/getPartnerAgent.js +33 -0
- package/dist/tools/getPartnerAgentStats.js +39 -0
- package/dist/tools/listPartnerAgents.js +31 -0
- package/dist/tools/partnerAgentFields.js +29 -0
- package/dist/tools/setPartnerAgentPublished.js +35 -0
- package/dist/tools/updatePartnerAgent.js +45 -0
- package/package.json +1 -1
package/dist/compact.js
ADDED
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Economie de tokens (0.4.0).
|
|
3
|
+
*
|
|
4
|
+
* - Reponses legeres : JSON sans indentation, `fields` pour ne garder que
|
|
5
|
+
* certains champs, `limit` a 20 par defaut, `next_offset` quand il reste
|
|
6
|
+
* des elements.
|
|
7
|
+
* - Descriptions courtes : une phrase par tool et par parametre dans la
|
|
8
|
+
* liste envoyee au client ; la doc complete passe par `describe_tool`.
|
|
9
|
+
* - Mode compact (defaut) : le client ne voit que search_tools /
|
|
10
|
+
* describe_tool / execute ; les 192 tools restent appelables derriere
|
|
11
|
+
* `execute`. XLAB_MCP_MODE=full reexpose tous les tools.
|
|
12
|
+
*/
|
|
13
|
+
const DEFAULT_LIMIT = 20;
|
|
14
|
+
/** Actions qui suppriment, retirent ou envoient quelque chose : accord explicite de l'utilisateur. */
|
|
15
|
+
const SENSITIVE_PREFIXES = ['delete_', 'ban_', 'kick_', 'reject_', 'send_', 'revoke_', 'clear_', 'demote_', 'stop_', 'unpublish_'];
|
|
16
|
+
const SENSITIVE_TOOLS = new Set(['update_slug', 'schedule_newsletter_edition', 'set_current_subscription_price', 'publish_podcast_episode']);
|
|
17
|
+
export function isSensitive(name) {
|
|
18
|
+
return SENSITIVE_TOOLS.has(name) || SENSITIVE_PREFIXES.some(prefix => name.startsWith(prefix));
|
|
19
|
+
}
|
|
20
|
+
/** Tools de lecture : ceux qui acceptent `fields`. */
|
|
21
|
+
export function isReadTool(name) {
|
|
22
|
+
return /^(list_|get_|search_|preview_|check_)/.test(name) || name === 'newsletter_dashboard';
|
|
23
|
+
}
|
|
24
|
+
/** Premiere phrase, coupee proprement au-dela de `max` caracteres. */
|
|
25
|
+
export function firstSentence(text, max) {
|
|
26
|
+
if (!text)
|
|
27
|
+
return '';
|
|
28
|
+
const flat = text.replace(/\s+/g, ' ').trim();
|
|
29
|
+
const match = flat.match(/^(.+?[.!?])(\s|$)/);
|
|
30
|
+
let sentence = match ? match[1] : flat;
|
|
31
|
+
if (sentence.length > max) {
|
|
32
|
+
const cut = sentence.slice(0, max - 1);
|
|
33
|
+
sentence = cut.slice(0, cut.lastIndexOf(' ') > max / 2 ? cut.lastIndexOf(' ') : cut.length) + '…';
|
|
34
|
+
}
|
|
35
|
+
return sentence;
|
|
36
|
+
}
|
|
37
|
+
export function shortDescription(tool) {
|
|
38
|
+
const summary = firstSentence(tool.description, 110);
|
|
39
|
+
return isSensitive(tool.name) ? `${summary} Action sensible : accord de l'utilisateur avant.` : summary;
|
|
40
|
+
}
|
|
41
|
+
/** Bornes retirees de la liste : la validation du tool (zod) les applique toujours, avec un message clair. */
|
|
42
|
+
const BOUND_KEYS = new Set(['minimum', 'maximum', 'minLength', 'maxLength', 'minItems', 'maxItems', 'additionalProperties']);
|
|
43
|
+
function stripBounds(value) {
|
|
44
|
+
if (Array.isArray(value))
|
|
45
|
+
return value.map(stripBounds);
|
|
46
|
+
if (typeof value !== 'object' || value === null)
|
|
47
|
+
return value;
|
|
48
|
+
const copy = {};
|
|
49
|
+
for (const [key, inner] of Object.entries(value)) {
|
|
50
|
+
if (!BOUND_KEYS.has(key))
|
|
51
|
+
copy[key] = stripBounds(inner);
|
|
52
|
+
}
|
|
53
|
+
return copy;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Schema allege : descriptions de parametres raccourcies (celle de `lab`,
|
|
57
|
+
* commune a tous les tools, est dans les instructions du serveur), bornes
|
|
58
|
+
* retirees, limit a 20, `fields` sur les lectures.
|
|
59
|
+
*/
|
|
60
|
+
export function lightSchema(tool) {
|
|
61
|
+
const properties = {};
|
|
62
|
+
for (const [key, prop] of Object.entries(tool.inputSchema.properties ?? {})) {
|
|
63
|
+
const copy = stripBounds(prop);
|
|
64
|
+
if (key === 'lab') {
|
|
65
|
+
delete copy.description;
|
|
66
|
+
}
|
|
67
|
+
else if (typeof copy.description === 'string') {
|
|
68
|
+
copy.description = firstSentence(copy.description, 50);
|
|
69
|
+
}
|
|
70
|
+
if (key === 'limit') {
|
|
71
|
+
copy.default = defaultLimit(prop);
|
|
72
|
+
}
|
|
73
|
+
properties[key] = copy;
|
|
74
|
+
}
|
|
75
|
+
if (isReadTool(tool.name)) {
|
|
76
|
+
properties.fields = { type: 'array', items: { type: 'string' } };
|
|
77
|
+
}
|
|
78
|
+
const { additionalProperties: _strict, ...schema } = tool.inputSchema;
|
|
79
|
+
return { ...schema, properties };
|
|
80
|
+
}
|
|
81
|
+
function defaultLimit(limitProp) {
|
|
82
|
+
const max = typeof limitProp?.maximum === 'number' ? limitProp.maximum : DEFAULT_LIMIT;
|
|
83
|
+
return Math.min(DEFAULT_LIMIT, max);
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Lance un tool avec les reglages economes : `fields` retire des arguments,
|
|
87
|
+
* limit a 20 si le tool en a une et que rien n'est demande, reponse compacte.
|
|
88
|
+
*/
|
|
89
|
+
export async function runTool(tool, rawArgs) {
|
|
90
|
+
const args = { ...rawArgs };
|
|
91
|
+
const fields = Array.isArray(args.fields) ? args.fields.filter((f) => typeof f === 'string') : [];
|
|
92
|
+
delete args.fields;
|
|
93
|
+
const limitProp = tool.inputSchema.properties?.limit;
|
|
94
|
+
if (limitProp && args.limit === undefined) {
|
|
95
|
+
args.limit = defaultLimit(limitProp);
|
|
96
|
+
}
|
|
97
|
+
const paged = !!limitProp && !!tool.inputSchema.properties?.offset;
|
|
98
|
+
let result;
|
|
99
|
+
try {
|
|
100
|
+
result = await tool.handler(args);
|
|
101
|
+
}
|
|
102
|
+
catch (err) {
|
|
103
|
+
// Arguments refuses par la validation du tool : message court et relisible.
|
|
104
|
+
const issues = err.issues;
|
|
105
|
+
const detail = issues?.length
|
|
106
|
+
? issues.map(issue => `${issue.path.join('.') || 'arguments'} : ${issue.message}`).join(' ; ')
|
|
107
|
+
: err.message ?? String(err);
|
|
108
|
+
return { isError: true, content: [{ type: 'text', text: `[arguments] ${detail}` }] };
|
|
109
|
+
}
|
|
110
|
+
if (result.isError)
|
|
111
|
+
return result;
|
|
112
|
+
return {
|
|
113
|
+
...result,
|
|
114
|
+
content: result.content.map(part => ({
|
|
115
|
+
...part,
|
|
116
|
+
text: compactText(part.text, {
|
|
117
|
+
fields,
|
|
118
|
+
limit: typeof args.limit === 'number' ? args.limit : undefined,
|
|
119
|
+
offset: typeof args.offset === 'number' ? args.offset : 0,
|
|
120
|
+
paged,
|
|
121
|
+
}),
|
|
122
|
+
})),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
function compactText(text, options) {
|
|
126
|
+
let value;
|
|
127
|
+
try {
|
|
128
|
+
value = JSON.parse(text);
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
return text;
|
|
132
|
+
}
|
|
133
|
+
// Enveloppe { status: true, data } de l'API : seul le contenu compte.
|
|
134
|
+
if (isObject(value) && value.status === true && 'data' in value) {
|
|
135
|
+
const { status: _status, ...rest } = value;
|
|
136
|
+
value = Object.keys(rest).length === 1 ? rest.data : rest;
|
|
137
|
+
}
|
|
138
|
+
if (options.fields.length) {
|
|
139
|
+
value = pickFields(value, options.fields);
|
|
140
|
+
}
|
|
141
|
+
// Page pleine : il en reste peut-etre, on donne l'offset suivant.
|
|
142
|
+
if (options.paged && options.limit) {
|
|
143
|
+
const list = Array.isArray(value) ? value : isObject(value) && Array.isArray(value.data) ? value.data : null;
|
|
144
|
+
const nextOffset = options.offset + options.limit;
|
|
145
|
+
const total = isObject(value) && isObject(value.meta) && typeof value.meta.total === 'number' ? value.meta.total : null;
|
|
146
|
+
if (list && list.length >= options.limit && (total === null || nextOffset < total)) {
|
|
147
|
+
value = Array.isArray(value) ? { items: value, next_offset: nextOffset } : { ...value, next_offset: nextOffset };
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return JSON.stringify(value);
|
|
151
|
+
}
|
|
152
|
+
function pickFields(value, fields) {
|
|
153
|
+
if (Array.isArray(value))
|
|
154
|
+
return value.map(item => pickFields(item, fields));
|
|
155
|
+
if (!isObject(value))
|
|
156
|
+
return value;
|
|
157
|
+
// Liste enveloppee ({ data: [...], meta }) : le filtre s'applique aux elements.
|
|
158
|
+
if (Array.isArray(value.data))
|
|
159
|
+
return { ...value, data: pickFields(value.data, fields) };
|
|
160
|
+
const picked = {};
|
|
161
|
+
for (const field of fields) {
|
|
162
|
+
if (field in value)
|
|
163
|
+
picked[field] = value[field];
|
|
164
|
+
}
|
|
165
|
+
return Object.keys(picked).length ? picked : value;
|
|
166
|
+
}
|
|
167
|
+
function isObject(value) {
|
|
168
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
169
|
+
}
|
|
170
|
+
function normalize(text) {
|
|
171
|
+
return text.toLowerCase().normalize('NFD').replace(/[̀-ͯ]/g, '').replace(/_/g, ' ');
|
|
172
|
+
}
|
|
173
|
+
/** Verbes francais -> mot des noms de tools (en anglais). */
|
|
174
|
+
const VERB_HINTS = [
|
|
175
|
+
[/^envo/, 'send'], [/^supprim|^efface/, 'delete'], [/^cre/, 'create'], [/^modifi|^change|^mettre/, 'update'],
|
|
176
|
+
[/^list|^voir|^affich/, 'list'], [/^banni/, 'ban'], [/^exclu|^vire|^retir/, 'kick'], [/^publi/, 'publish'],
|
|
177
|
+
[/^program|^planifi/, 'schedule'], [/^invit/, 'invitation'], [/^approuv|^valid|^accept/, 'approve'],
|
|
178
|
+
[/^refus|^rejet/, 'reject'], [/^cherch|^trouv/, 'search'], [/^epingl/, 'pin'], [/^stat|^chiffr/, 'stats'],
|
|
179
|
+
[/^revenu|^gain|^argent/, 'earnings'], [/^vente/, 'sales'], [/^membre/, 'member'], [/^cours|^formation|^classe/, 'class'],
|
|
180
|
+
[/^evenement/, 'event'], [/^boutique|^produit/, 'shop'], [/^tunnel/, 'funnel'], [/^lien/, 'link'],
|
|
181
|
+
[/^attente/, 'pending'], [/^abonne/, 'subscription'], [/^prix|^tarif/, 'price'],
|
|
182
|
+
];
|
|
183
|
+
/** Mots trop courants pour departager les actions. */
|
|
184
|
+
const STOP_WORDS = new Set(['le', 'la', 'les', 'un', 'une', 'des', 'du', 'de', 'mes', 'mon', 'ma', 'ses', 'son', 'sa', 'au', 'aux', 'en', 'et', 'pour', 'sur', 'avec', 'dans', 'lab', 'qui', 'que', 'quelqu']);
|
|
185
|
+
/** Recherche simple par mots dans le nom et la description des tools. */
|
|
186
|
+
export function searchTools(tools, query, limit) {
|
|
187
|
+
const base = normalize(query).split(/[^a-z0-9]+/).filter(word => word.length > 1 && !STOP_WORDS.has(word));
|
|
188
|
+
const hints = base.flatMap(word => VERB_HINTS.filter(([pattern]) => pattern.test(word)).map(([, hint]) => hint));
|
|
189
|
+
const ranked = tools
|
|
190
|
+
.map(tool => {
|
|
191
|
+
const name = normalize(tool.name);
|
|
192
|
+
const parts = name.split(' ');
|
|
193
|
+
const description = normalize(tool.description);
|
|
194
|
+
let score = base.reduce((sum, word) => {
|
|
195
|
+
const stem = word.length > 4 ? word.slice(0, -1) : word; // membres ~ membre
|
|
196
|
+
return sum + (name.includes(stem) ? 3 : 0) + (description.includes(stem) ? 1 : 0);
|
|
197
|
+
}, 0);
|
|
198
|
+
// Mot traduit present tel quel dans le nom (« ban » oui, « unban » non) ; verbe en tete : bonus.
|
|
199
|
+
for (const hint of hints) {
|
|
200
|
+
if (parts.includes(hint) || parts.includes(hint + 's'))
|
|
201
|
+
score += parts[0] === hint ? 5 : 4;
|
|
202
|
+
else if (description.includes(hint))
|
|
203
|
+
score += 1;
|
|
204
|
+
}
|
|
205
|
+
return { tool, score };
|
|
206
|
+
})
|
|
207
|
+
.filter(entry => base.length === 0 || entry.score > 0)
|
|
208
|
+
// A egalite, l'action la plus generale (nom le plus court) d'abord : get_stats avant get_quiz_stats.
|
|
209
|
+
.sort((a, b) => b.score - a.score || a.tool.name.length - b.tool.name.length || a.tool.name.localeCompare(b.tool.name));
|
|
210
|
+
if (base.length === 0) {
|
|
211
|
+
// Sans mots-cles : tous les noms, sans description (catalogue).
|
|
212
|
+
return { tools: ranked.map(entry => entry.tool.name) };
|
|
213
|
+
}
|
|
214
|
+
return {
|
|
215
|
+
tools: ranked.slice(0, limit).map(entry => ({
|
|
216
|
+
name: entry.tool.name,
|
|
217
|
+
summary: firstSentence(entry.tool.description, 140),
|
|
218
|
+
...(isSensitive(entry.tool.name) ? { sensitive: true } : {}),
|
|
219
|
+
})),
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
export const SERVER_INSTRUCTIONS = "Pilote un Lab ConnectXLab. Parametre `lab` : id numerique ou url du Lab, donnes par list_labs. Reponses compactes : limit 20 par defaut, `fields` (lectures) pour ne garder que certains champs, `next_offset` pour la page suivante. Avant une action sensible (suppression, bannissement, envoi), demande l'accord explicite de l'utilisateur.";
|
|
223
|
+
export const COMPACT_INSTRUCTIONS = " Mode compact : trouve l'action avec search_tools, lis ses parametres avec describe_tool, puis lance-la avec execute.";
|
|
224
|
+
/**
|
|
225
|
+
* Les 3 tools du mode compact, construits sur le catalogue complet. En mode
|
|
226
|
+
* complet, seul describe_tool est ajoute (doc detaillee a la demande).
|
|
227
|
+
*/
|
|
228
|
+
export function compactTools(catalog) {
|
|
229
|
+
const byName = new Map(catalog.map(tool => [tool.name, tool]));
|
|
230
|
+
const text = (value, isError = false) => ({
|
|
231
|
+
...(isError ? { isError: true } : {}),
|
|
232
|
+
content: [{ type: 'text', text: typeof value === 'string' ? value : JSON.stringify(value) }],
|
|
233
|
+
});
|
|
234
|
+
const search = {
|
|
235
|
+
name: 'search_tools',
|
|
236
|
+
description: `Cherche parmi les ${catalog.length} actions ConnectXLab (membres, posts, cours, evenements, boutique, newsletter, podcast, tunnels, quiz…). Sans query : liste de tous les noms.`,
|
|
237
|
+
inputSchema: {
|
|
238
|
+
type: 'object',
|
|
239
|
+
properties: {
|
|
240
|
+
query: { type: 'string', description: 'Mots-cles, ex. "bannir membre" ou "newsletter envoyer"' },
|
|
241
|
+
limit: { type: 'number', minimum: 1, maximum: 30, default: 8 },
|
|
242
|
+
},
|
|
243
|
+
additionalProperties: false,
|
|
244
|
+
},
|
|
245
|
+
async handler(args) {
|
|
246
|
+
const limit = typeof args.limit === 'number' ? Math.min(30, Math.max(1, args.limit)) : 8;
|
|
247
|
+
return text(searchTools(catalog, typeof args.query === 'string' ? args.query : '', limit));
|
|
248
|
+
},
|
|
249
|
+
};
|
|
250
|
+
const describe = {
|
|
251
|
+
name: 'describe_tool',
|
|
252
|
+
description: "Description complete et parametres d'une action, avant de l'appeler avec execute.",
|
|
253
|
+
inputSchema: {
|
|
254
|
+
type: 'object',
|
|
255
|
+
properties: { name: { type: 'string', description: 'Nom exact, ex. list_members' } },
|
|
256
|
+
required: ['name'],
|
|
257
|
+
additionalProperties: false,
|
|
258
|
+
},
|
|
259
|
+
async handler(args) {
|
|
260
|
+
const tool = byName.get(String(args.name ?? ''));
|
|
261
|
+
if (!tool)
|
|
262
|
+
return text(`Action inconnue : ${String(args.name)}. Utilise search_tools.`, true);
|
|
263
|
+
return text({
|
|
264
|
+
name: tool.name,
|
|
265
|
+
description: tool.description,
|
|
266
|
+
...(isSensitive(tool.name) ? { sensitive: "oui : accord explicite de l'utilisateur, puis execute avec confirm: true" } : {}),
|
|
267
|
+
...(isReadTool(tool.name) ? { fields: 'accepte (via execute)' } : {}),
|
|
268
|
+
input_schema: tool.inputSchema,
|
|
269
|
+
});
|
|
270
|
+
},
|
|
271
|
+
};
|
|
272
|
+
const execute = {
|
|
273
|
+
name: 'execute',
|
|
274
|
+
description: "Lance une action ConnectXLab par son nom avec ses arguments. Actions sensibles : confirm: true, seulement apres accord de l'utilisateur.",
|
|
275
|
+
inputSchema: {
|
|
276
|
+
type: 'object',
|
|
277
|
+
properties: {
|
|
278
|
+
name: { type: 'string', description: "Nom exact de l'action" },
|
|
279
|
+
arguments: { type: 'object', description: "Arguments de l'action (voir describe_tool)" },
|
|
280
|
+
fields: { type: 'array', items: { type: 'string' }, description: 'Champs a garder dans la reponse' },
|
|
281
|
+
confirm: { type: 'boolean', description: 'true pour une action sensible approuvee' },
|
|
282
|
+
},
|
|
283
|
+
required: ['name'],
|
|
284
|
+
additionalProperties: false,
|
|
285
|
+
},
|
|
286
|
+
async handler(args) {
|
|
287
|
+
const name = String(args.name ?? '');
|
|
288
|
+
const tool = byName.get(name);
|
|
289
|
+
if (!tool)
|
|
290
|
+
return text(`Action inconnue : ${name}. Utilise search_tools.`, true);
|
|
291
|
+
if (isSensitive(name) && args.confirm !== true) {
|
|
292
|
+
return text(`${name} est une action sensible. Decris-la a l'utilisateur, obtiens son accord explicite, puis relance execute avec confirm: true.`, true);
|
|
293
|
+
}
|
|
294
|
+
const toolArgs = isObject(args.arguments) ? { ...args.arguments } : {};
|
|
295
|
+
if (Array.isArray(args.fields))
|
|
296
|
+
toolArgs.fields = args.fields;
|
|
297
|
+
return runTool(tool, toolArgs);
|
|
298
|
+
},
|
|
299
|
+
};
|
|
300
|
+
return [search, describe, execute];
|
|
301
|
+
}
|
package/dist/config.js
CHANGED
|
@@ -15,4 +15,7 @@ export const config = {
|
|
|
15
15
|
apiBaseUrl: process.env.XLAB_API_URL || 'https://v1.connectxlab.io/api/v1',
|
|
16
16
|
mcpToken: requireEnv('XLAB_MCP_TOKEN'),
|
|
17
17
|
timeoutMs: parseInt(process.env.XLAB_TIMEOUT_MS || '15000', 10),
|
|
18
|
+
// compact (defaut) : 3 tools search_tools / describe_tool / execute ;
|
|
19
|
+
// full : les ~190 tools exposes directement (descriptions courtes).
|
|
20
|
+
mode: process.env.XLAB_MCP_MODE === 'full' ? 'full' : 'compact',
|
|
18
21
|
};
|
package/dist/index.js
CHANGED
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
16
16
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
17
17
|
import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
18
|
+
import { config } from './config.js';
|
|
19
|
+
import { COMPACT_INSTRUCTIONS, SERVER_INSTRUCTIONS, compactTools, lightSchema, runTool, shortDescription } from './compact.js';
|
|
18
20
|
import { listLabsTool } from './tools/listLabs.js';
|
|
19
21
|
import { listMembersTool } from './tools/listMembers.js';
|
|
20
22
|
import { listPostsTool } from './tools/listPosts.js';
|
|
@@ -199,6 +201,14 @@ import { rejectAssistantActionTool } from './tools/rejectAssistantAction.js';
|
|
|
199
201
|
import { correctAssistantActionTool } from './tools/correctAssistantAction.js';
|
|
200
202
|
import { requestAssistantPostDraftTool } from './tools/requestAssistantPostDraft.js';
|
|
201
203
|
import { getAssistantSummaryTool } from './tools/getAssistantSummary.js';
|
|
204
|
+
import { listPartnerAgentsTool } from './tools/listPartnerAgents.js';
|
|
205
|
+
import { getPartnerAgentTool } from './tools/getPartnerAgent.js';
|
|
206
|
+
import { createPartnerAgentTool } from './tools/createPartnerAgent.js';
|
|
207
|
+
import { updatePartnerAgentTool } from './tools/updatePartnerAgent.js';
|
|
208
|
+
import { setPartnerAgentPublishedTool } from './tools/setPartnerAgentPublished.js';
|
|
209
|
+
import { deletePartnerAgentTool } from './tools/deletePartnerAgent.js';
|
|
210
|
+
import { checkPartnerAgentLinksTool } from './tools/checkPartnerAgentLinks.js';
|
|
211
|
+
import { getPartnerAgentStatsTool } from './tools/getPartnerAgentStats.js';
|
|
202
212
|
const tools = [
|
|
203
213
|
listLabsTool,
|
|
204
214
|
listMembersTool,
|
|
@@ -397,27 +407,45 @@ const tools = [
|
|
|
397
407
|
correctAssistantActionTool,
|
|
398
408
|
requestAssistantPostDraftTool,
|
|
399
409
|
getAssistantSummaryTool,
|
|
410
|
+
listPartnerAgentsTool,
|
|
411
|
+
getPartnerAgentTool,
|
|
412
|
+
createPartnerAgentTool,
|
|
413
|
+
updatePartnerAgentTool,
|
|
414
|
+
setPartnerAgentPublishedTool,
|
|
415
|
+
deletePartnerAgentTool,
|
|
416
|
+
checkPartnerAgentLinksTool,
|
|
417
|
+
getPartnerAgentStatsTool,
|
|
400
418
|
];
|
|
401
|
-
const
|
|
419
|
+
const catalog = tools;
|
|
420
|
+
const [searchTool, describeTool, executeTool] = compactTools(catalog);
|
|
421
|
+
const compact = config.mode === 'compact';
|
|
422
|
+
const server = new Server({ name: 'connectxlab', version: '0.4.0' }, {
|
|
423
|
+
capabilities: { tools: {} },
|
|
424
|
+
instructions: SERVER_INSTRUCTIONS + (compact ? COMPACT_INSTRUCTIONS : ''),
|
|
425
|
+
});
|
|
426
|
+
// Mode compact : 3 tools. Mode complet : tous les tools (descriptions courtes) + describe_tool.
|
|
427
|
+
const exposed = compact ? [searchTool, describeTool, executeTool] : [...catalog, describeTool];
|
|
402
428
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
403
|
-
tools:
|
|
404
|
-
name: t.name,
|
|
405
|
-
|
|
406
|
-
inputSchema: t.inputSchema,
|
|
407
|
-
})),
|
|
429
|
+
tools: exposed.map(t => compact || t === describeTool
|
|
430
|
+
? { name: t.name, description: t.description, inputSchema: t.inputSchema }
|
|
431
|
+
: { name: t.name, description: shortDescription(t), inputSchema: lightSchema(t) }),
|
|
408
432
|
}));
|
|
409
433
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
410
434
|
const { name, arguments: args } = request.params;
|
|
411
|
-
const tool =
|
|
435
|
+
const tool = exposed.find(t => t.name === name);
|
|
412
436
|
if (!tool) {
|
|
413
437
|
return {
|
|
414
438
|
isError: true,
|
|
415
439
|
content: [{ type: 'text', text: `Tool inconnu : ${name}` }],
|
|
416
440
|
};
|
|
417
441
|
}
|
|
418
|
-
|
|
442
|
+
// Les tools du mode compact gerent eux-memes l'appel ; les autres passent par runTool.
|
|
443
|
+
if (tool === searchTool || tool === describeTool || tool === executeTool) {
|
|
444
|
+
return await tool.handler(args ?? {});
|
|
445
|
+
}
|
|
446
|
+
return await runTool(tool, args ?? {});
|
|
419
447
|
});
|
|
420
448
|
const transport = new StdioServerTransport();
|
|
421
449
|
await server.connect(transport);
|
|
422
450
|
// Log stderr pour debug (Claude Desktop l'affiche dans les logs).
|
|
423
|
-
console.error(`[connectxlab-mcp] ready — ${
|
|
451
|
+
console.error(`[connectxlab-mcp] ready — mode ${config.mode}, ${catalog.length} actions, ${exposed.length} tools exposes`);
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool `check_partner_agent_links` — verifie que des liens repondent.
|
|
3
|
+
* Scope : partner_agents.read. Super admin, Lab officiel uniquement.
|
|
4
|
+
*/
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import { http, formatApiError } from '../http.js';
|
|
7
|
+
export const checkPartnerAgentLinksTool = {
|
|
8
|
+
name: 'check_partner_agent_links',
|
|
9
|
+
description: "Verifie que le lien d'affiliation et/ou le site de l'editeur repondent avant de les enregistrer. Simple avertissement : certains sites bloquent les verifications.",
|
|
10
|
+
inputSchema: {
|
|
11
|
+
type: 'object',
|
|
12
|
+
properties: {
|
|
13
|
+
lab: { type: 'string', description: 'group_url ou id du Lab officiel XLab' },
|
|
14
|
+
affiliate: { type: 'string', description: "Lien d'affiliation (https://)" },
|
|
15
|
+
landing: { type: 'string', description: "Site de l'editeur (https://)" },
|
|
16
|
+
},
|
|
17
|
+
required: ['lab'],
|
|
18
|
+
additionalProperties: false,
|
|
19
|
+
},
|
|
20
|
+
zodSchema: z.object({
|
|
21
|
+
lab: z.string(),
|
|
22
|
+
affiliate: z.string().max(2000).optional(),
|
|
23
|
+
landing: z.string().max(2000).optional(),
|
|
24
|
+
}),
|
|
25
|
+
async handler(args) {
|
|
26
|
+
const { lab, affiliate, landing } = this.zodSchema.parse(args);
|
|
27
|
+
const urls = {};
|
|
28
|
+
if (affiliate)
|
|
29
|
+
urls.affiliate = affiliate;
|
|
30
|
+
if (landing)
|
|
31
|
+
urls.landing = landing;
|
|
32
|
+
try {
|
|
33
|
+
const { data } = await http.post(`/mcp/labs/${encodeURIComponent(lab)}/partner-agents/check-links`, { urls });
|
|
34
|
+
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
return { isError: true, content: [{ type: 'text', text: formatApiError(err) }] };
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool `create_partner_agent` — cree un agent partenaire et sa page de vente.
|
|
3
|
+
* Scope : partner_agents.write. Super admin, Lab officiel uniquement.
|
|
4
|
+
*/
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import { http, formatApiError } from '../http.js';
|
|
7
|
+
import { partnerAgentProperties, partnerAgentZod } from './partnerAgentFields.js';
|
|
8
|
+
export const createPartnerAgentTool = {
|
|
9
|
+
name: 'create_partner_agent',
|
|
10
|
+
description: "Cree un agent partenaire dans le Lab officiel XLab : carte marketplace + page de vente dont les boutons menent au lien d'affiliation (suivi des vues et clics). Jamais propose en tunnel de vente, sans abonnement XLab. Publie par defaut (published=false pour le garder masque). Action visible publiquement : la faire confirmer.",
|
|
11
|
+
inputSchema: {
|
|
12
|
+
type: 'object',
|
|
13
|
+
properties: {
|
|
14
|
+
lab: { type: 'string', description: 'group_url ou id du Lab officiel XLab' },
|
|
15
|
+
...partnerAgentProperties,
|
|
16
|
+
published: { type: 'boolean', description: 'Visible sur la marketplace (defaut true)' },
|
|
17
|
+
},
|
|
18
|
+
required: ['lab', 'title', 'short_description', 'call_to_action_text', 'shop_image', 'affiliate_url'],
|
|
19
|
+
additionalProperties: false,
|
|
20
|
+
},
|
|
21
|
+
zodSchema: z.object({
|
|
22
|
+
lab: z.string(),
|
|
23
|
+
title: partnerAgentZod.title,
|
|
24
|
+
short_description: partnerAgentZod.short_description,
|
|
25
|
+
description: partnerAgentZod.description.optional(),
|
|
26
|
+
call_to_action_text: partnerAgentZod.call_to_action_text,
|
|
27
|
+
shop_image: partnerAgentZod.shop_image,
|
|
28
|
+
icon: partnerAgentZod.icon.optional(),
|
|
29
|
+
affiliate_url: partnerAgentZod.affiliate_url,
|
|
30
|
+
pricing_label: partnerAgentZod.pricing_label.optional(),
|
|
31
|
+
landing_template: partnerAgentZod.landing_template.optional(),
|
|
32
|
+
landing_external_url: partnerAgentZod.landing_external_url.optional(),
|
|
33
|
+
published: z.boolean().optional(),
|
|
34
|
+
}),
|
|
35
|
+
async handler(args) {
|
|
36
|
+
const { lab, ...body } = this.zodSchema.parse(args);
|
|
37
|
+
try {
|
|
38
|
+
const { data } = await http.post(`/mcp/labs/${encodeURIComponent(lab)}/partner-agents`, body);
|
|
39
|
+
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
return { isError: true, content: [{ type: 'text', text: formatApiError(err) }] };
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool `delete_partner_agent` — supprime un agent partenaire et ses statistiques.
|
|
3
|
+
* Scope : partner_agents.write. Super admin, Lab officiel uniquement.
|
|
4
|
+
*/
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import { http, formatApiError } from '../http.js';
|
|
7
|
+
export const deletePartnerAgentTool = {
|
|
8
|
+
name: 'delete_partner_agent',
|
|
9
|
+
description: "Supprime definitivement un agent partenaire (carte, page de vente, vues et clics). Irreversible : la faire confirmer. Pour le retirer temporairement : set_partner_agent_published false.",
|
|
10
|
+
inputSchema: {
|
|
11
|
+
type: 'object',
|
|
12
|
+
properties: {
|
|
13
|
+
lab: { type: 'string', description: 'group_url ou id du Lab officiel XLab' },
|
|
14
|
+
agent_id: { type: 'number', description: 'id de l agent partenaire (list_partner_agents)' },
|
|
15
|
+
},
|
|
16
|
+
required: ['lab', 'agent_id'],
|
|
17
|
+
additionalProperties: false,
|
|
18
|
+
},
|
|
19
|
+
zodSchema: z.object({
|
|
20
|
+
lab: z.string(),
|
|
21
|
+
agent_id: z.number().int().min(1),
|
|
22
|
+
}),
|
|
23
|
+
async handler(args) {
|
|
24
|
+
const p = this.zodSchema.parse(args);
|
|
25
|
+
try {
|
|
26
|
+
const { data } = await http.delete(`/mcp/labs/${encodeURIComponent(p.lab)}/partner-agents/${p.agent_id}`);
|
|
27
|
+
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
28
|
+
}
|
|
29
|
+
catch (err) {
|
|
30
|
+
return { isError: true, content: [{ type: 'text', text: formatApiError(err) }] };
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool `get_partner_agent` — detail d'un agent partenaire (page de vente complete).
|
|
3
|
+
* Scope : partner_agents.read. Super admin, Lab officiel uniquement.
|
|
4
|
+
*/
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import { http, formatApiError } from '../http.js';
|
|
7
|
+
export const getPartnerAgentTool = {
|
|
8
|
+
name: 'get_partner_agent',
|
|
9
|
+
description: "Detail d'un agent partenaire du Lab officiel : textes de la page de vente (description HTML comprise), image, lien d'affiliation, texte du bouton, prix affiche, publication et liens suivis (page de vente, affiliation).",
|
|
10
|
+
inputSchema: {
|
|
11
|
+
type: 'object',
|
|
12
|
+
properties: {
|
|
13
|
+
lab: { type: 'string', description: 'group_url ou id du Lab officiel XLab' },
|
|
14
|
+
agent_id: { type: 'number', description: 'id de l agent partenaire (list_partner_agents)' },
|
|
15
|
+
},
|
|
16
|
+
required: ['lab', 'agent_id'],
|
|
17
|
+
additionalProperties: false,
|
|
18
|
+
},
|
|
19
|
+
zodSchema: z.object({
|
|
20
|
+
lab: z.string(),
|
|
21
|
+
agent_id: z.number().int().min(1),
|
|
22
|
+
}),
|
|
23
|
+
async handler(args) {
|
|
24
|
+
const p = this.zodSchema.parse(args);
|
|
25
|
+
try {
|
|
26
|
+
const { data } = await http.get(`/mcp/labs/${encodeURIComponent(p.lab)}/partner-agents/${p.agent_id}`);
|
|
27
|
+
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
28
|
+
}
|
|
29
|
+
catch (err) {
|
|
30
|
+
return { isError: true, content: [{ type: 'text', text: formatApiError(err) }] };
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool `get_partner_agent_stats` — vues et clics d'un agent partenaire, jour par jour.
|
|
3
|
+
* Scope : partner_agents.read. Super admin, Lab officiel uniquement.
|
|
4
|
+
*/
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import { http, formatApiError } from '../http.js';
|
|
7
|
+
export const getPartnerAgentStatsTool = {
|
|
8
|
+
name: 'get_partner_agent_stats',
|
|
9
|
+
description: "Vues de la page de vente et clics vers le lien d'affiliation d'un agent partenaire, jour par jour. period : today, 7d, 30d (defaut) ou custom avec from / to (YYYY-MM-DD, 366 jours max).",
|
|
10
|
+
inputSchema: {
|
|
11
|
+
type: 'object',
|
|
12
|
+
properties: {
|
|
13
|
+
lab: { type: 'string', description: 'group_url ou id du Lab officiel XLab' },
|
|
14
|
+
agent_id: { type: 'number', description: 'id de l agent partenaire (list_partner_agents)' },
|
|
15
|
+
period: { type: 'string', enum: ['today', '7d', '30d', 'custom'] },
|
|
16
|
+
from: { type: 'string', description: 'YYYY-MM-DD (period=custom)' },
|
|
17
|
+
to: { type: 'string', description: 'YYYY-MM-DD (period=custom)' },
|
|
18
|
+
},
|
|
19
|
+
required: ['lab', 'agent_id'],
|
|
20
|
+
additionalProperties: false,
|
|
21
|
+
},
|
|
22
|
+
zodSchema: z.object({
|
|
23
|
+
lab: z.string(),
|
|
24
|
+
agent_id: z.number().int().min(1),
|
|
25
|
+
period: z.enum(['today', '7d', '30d', 'custom']).optional(),
|
|
26
|
+
from: z.string().optional(),
|
|
27
|
+
to: z.string().optional(),
|
|
28
|
+
}),
|
|
29
|
+
async handler(args) {
|
|
30
|
+
const { lab, agent_id, ...params } = this.zodSchema.parse(args);
|
|
31
|
+
try {
|
|
32
|
+
const { data } = await http.get(`/mcp/labs/${encodeURIComponent(lab)}/partner-agents/${agent_id}/stats`, { params });
|
|
33
|
+
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
return { isError: true, content: [{ type: 'text', text: formatApiError(err) }] };
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool `list_partner_agents` — agents partenaires du Lab officiel XLab.
|
|
3
|
+
* Scope : partner_agents.read. Super admin, Lab officiel uniquement.
|
|
4
|
+
*/
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import { http, formatApiError } from '../http.js';
|
|
7
|
+
export const listPartnerAgentsTool = {
|
|
8
|
+
name: 'list_partner_agents',
|
|
9
|
+
description: "Liste les agents partenaires (agents IA d'autres editeurs vendus via un lien d'affiliation) du Lab officiel XLab : titre, image, lien d'affiliation, prix affiche, publication, vues et clics sur 30 jours. Reserve au super admin, uniquement dans le Lab officiel.",
|
|
10
|
+
inputSchema: {
|
|
11
|
+
type: 'object',
|
|
12
|
+
properties: {
|
|
13
|
+
lab: { type: 'string', description: 'group_url ou id du Lab officiel XLab' },
|
|
14
|
+
},
|
|
15
|
+
required: ['lab'],
|
|
16
|
+
additionalProperties: false,
|
|
17
|
+
},
|
|
18
|
+
zodSchema: z.object({
|
|
19
|
+
lab: z.string(),
|
|
20
|
+
}),
|
|
21
|
+
async handler(args) {
|
|
22
|
+
const p = this.zodSchema.parse(args);
|
|
23
|
+
try {
|
|
24
|
+
const { data } = await http.get(`/mcp/labs/${encodeURIComponent(p.lab)}/partner-agents`);
|
|
25
|
+
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
26
|
+
}
|
|
27
|
+
catch (err) {
|
|
28
|
+
return { isError: true, content: [{ type: 'text', text: formatApiError(err) }] };
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Champs de la page de vente d'un agent partenaire, partages par
|
|
3
|
+
* create_partner_agent et update_partner_agent.
|
|
4
|
+
*/
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
export const partnerAgentProperties = {
|
|
7
|
+
title: { type: 'string', maxLength: 255, description: "Nom de l'agent" },
|
|
8
|
+
short_description: { type: 'string', maxLength: 500, description: 'Phrase courte de la carte marketplace' },
|
|
9
|
+
description: { type: 'string', description: 'Description de la page de vente (HTML simple accepte)' },
|
|
10
|
+
call_to_action_text: { type: 'string', maxLength: 120, description: 'Texte du bouton de la page de vente (ex. "Essayer l\'agent")' },
|
|
11
|
+
shop_image: { type: 'string', description: "Image de la carte : URL https ou image encodee (data:image/...;base64,...)" },
|
|
12
|
+
icon: { type: 'string', description: 'Logo (URL https ou data URI), optionnel' },
|
|
13
|
+
affiliate_url: { type: 'string', maxLength: 2000, description: "Lien d'affiliation chez l'editeur, en https:// (les boutons de la page y menent)" },
|
|
14
|
+
pricing_label: { type: 'string', maxLength: 80, description: 'Prix affiche, texte libre (ex. "Des 29 euros/mois")' },
|
|
15
|
+
landing_template: { type: 'string', enum: ['', 'external_url'], description: '"" = page de vente XLab ; "external_url" = la carte mene au site de l editeur' },
|
|
16
|
+
landing_external_url: { type: 'string', maxLength: 2000, description: "Site de l'editeur en https:// (obligatoire si landing_template = external_url)" },
|
|
17
|
+
};
|
|
18
|
+
export const partnerAgentZod = {
|
|
19
|
+
title: z.string().max(255),
|
|
20
|
+
short_description: z.string().max(500),
|
|
21
|
+
description: z.string(),
|
|
22
|
+
call_to_action_text: z.string().max(120),
|
|
23
|
+
shop_image: z.string(),
|
|
24
|
+
icon: z.string().nullable(),
|
|
25
|
+
affiliate_url: z.string().max(2000),
|
|
26
|
+
pricing_label: z.string().max(80).nullable(),
|
|
27
|
+
landing_template: z.enum(['', 'external_url']),
|
|
28
|
+
landing_external_url: z.string().max(2000).nullable(),
|
|
29
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool `set_partner_agent_published` — publie ou masque un agent partenaire.
|
|
3
|
+
* Scope : partner_agents.write. Super admin, Lab officiel uniquement.
|
|
4
|
+
*/
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import { http, formatApiError } from '../http.js';
|
|
7
|
+
export const setPartnerAgentPublishedTool = {
|
|
8
|
+
name: 'set_partner_agent_published',
|
|
9
|
+
description: "Publie (published=true) ou masque (false) un agent partenaire sur la marketplace. Action visible publiquement : la faire confirmer.",
|
|
10
|
+
inputSchema: {
|
|
11
|
+
type: 'object',
|
|
12
|
+
properties: {
|
|
13
|
+
lab: { type: 'string', description: 'group_url ou id du Lab officiel XLab' },
|
|
14
|
+
agent_id: { type: 'number', description: 'id de l agent partenaire (list_partner_agents)' },
|
|
15
|
+
published: { type: 'boolean' },
|
|
16
|
+
},
|
|
17
|
+
required: ['lab', 'agent_id', 'published'],
|
|
18
|
+
additionalProperties: false,
|
|
19
|
+
},
|
|
20
|
+
zodSchema: z.object({
|
|
21
|
+
lab: z.string(),
|
|
22
|
+
agent_id: z.number().int().min(1),
|
|
23
|
+
published: z.boolean(),
|
|
24
|
+
}),
|
|
25
|
+
async handler(args) {
|
|
26
|
+
const p = this.zodSchema.parse(args);
|
|
27
|
+
try {
|
|
28
|
+
const { data } = await http.put(`/mcp/labs/${encodeURIComponent(p.lab)}/partner-agents/${p.agent_id}/published`, { published: p.published });
|
|
29
|
+
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
return { isError: true, content: [{ type: 'text', text: formatApiError(err) }] };
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool `update_partner_agent` — modifie la page de vente d'un agent partenaire.
|
|
3
|
+
* Scope : partner_agents.write. Super admin, Lab officiel uniquement.
|
|
4
|
+
*/
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import { http, formatApiError } from '../http.js';
|
|
7
|
+
import { partnerAgentProperties, partnerAgentZod } from './partnerAgentFields.js';
|
|
8
|
+
export const updatePartnerAgentTool = {
|
|
9
|
+
name: 'update_partner_agent',
|
|
10
|
+
description: "Modifie un agent partenaire du Lab officiel : n'envoyer que les champs a changer, les autres gardent leur valeur (textes, image, lien d'affiliation, texte du bouton, prix affiche, type de page). Pour publier ou masquer : set_partner_agent_published.",
|
|
11
|
+
inputSchema: {
|
|
12
|
+
type: 'object',
|
|
13
|
+
properties: {
|
|
14
|
+
lab: { type: 'string', description: 'group_url ou id du Lab officiel XLab' },
|
|
15
|
+
agent_id: { type: 'number', description: 'id de l agent partenaire (list_partner_agents)' },
|
|
16
|
+
...partnerAgentProperties,
|
|
17
|
+
},
|
|
18
|
+
required: ['lab', 'agent_id'],
|
|
19
|
+
additionalProperties: false,
|
|
20
|
+
},
|
|
21
|
+
zodSchema: z.object({
|
|
22
|
+
lab: z.string(),
|
|
23
|
+
agent_id: z.number().int().min(1),
|
|
24
|
+
title: partnerAgentZod.title.optional(),
|
|
25
|
+
short_description: partnerAgentZod.short_description.optional(),
|
|
26
|
+
description: partnerAgentZod.description.optional(),
|
|
27
|
+
call_to_action_text: partnerAgentZod.call_to_action_text.optional(),
|
|
28
|
+
shop_image: partnerAgentZod.shop_image.optional(),
|
|
29
|
+
icon: partnerAgentZod.icon.optional(),
|
|
30
|
+
affiliate_url: partnerAgentZod.affiliate_url.optional(),
|
|
31
|
+
pricing_label: partnerAgentZod.pricing_label.optional(),
|
|
32
|
+
landing_template: partnerAgentZod.landing_template.optional(),
|
|
33
|
+
landing_external_url: partnerAgentZod.landing_external_url.optional(),
|
|
34
|
+
}),
|
|
35
|
+
async handler(args) {
|
|
36
|
+
const { lab, agent_id, ...body } = this.zodSchema.parse(args);
|
|
37
|
+
try {
|
|
38
|
+
const { data } = await http.put(`/mcp/labs/${encodeURIComponent(lab)}/partner-agents/${agent_id}`, body);
|
|
39
|
+
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
return { isError: true, content: [{ type: 'text', text: formatApiError(err) }] };
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
};
|
package/package.json
CHANGED