This commit is contained in:
seb
2026-10-14 00:08:11 +02:00
parent 11f6a8fc0f
commit 74b24686bc
14 changed files with 962 additions and 12 deletions

View File

@@ -9,6 +9,9 @@ import env from '../../config/env.js';
import * as mcpDb from '../../db/mcp.js';
import * as googleAuthDb from '../../db/googleAuth.js';
import { TOOL_DESCRIPTIONS, TOOL_PARAMETER_DESCRIPTIONS } from '../../tools/prompts.js';
import { createSearchArticlesTool } from '../../tools/search-articles.js';
import { createSubagentTool } from '../../tools/subagent.js';
import { resolveDefaultModel } from '../../tools/llm-key.js';
const MAX_RESPONSE_CHARS = 20_000;
const REQUEST_TIMEOUT_MS = 30_000;
@@ -177,6 +180,89 @@ function createDashbackMcpServer({ user }) {
}
);
server.registerTool(
'dash-subagent',
{
title: 'Dashback Subagent',
description: TOOL_DESCRIPTIONS.subagent || 'Delegate a task to an isolated subagent with access to Dashback tools and VFS.',
inputSchema: {
task: z.string().describe(TOOL_PARAMETER_DESCRIPTIONS.subagent?.task || 'Specific instructions and goal for the subagent.'),
skills: z.array(z.string()).nullable().optional().describe(TOOL_PARAMETER_DESCRIPTIONS.subagent?.skills || 'Array of SKILL names or slugs to inject into the subagent prompt.'),
conversation_id: z.number().int().positive().nullable().optional().describe(TOOL_PARAMETER_DESCRIPTIONS.subagent?.conversation_id || 'Existing subagent conversation ID to follow up on, or null to create a new one.'),
max_iterations: z.number().int().min(1).max(30).nullable().optional().describe(TOOL_PARAMETER_DESCRIPTIONS.subagent?.max_iterations || 'Safety bound on tool loops (default 15, max 30).'),
model: z.string().nullable().optional().describe('Optional OpenRouter model override. Defaults to company default model.'),
note: z.string().nullable().optional().describe(TOOL_PARAMETER_DESCRIPTIONS.subagent?.note || 'Status message shown during execution.'),
},
},
async ({ task, skills = null, conversation_id = null, max_iterations = null, model = null, note = null }, extra = {}) => {
const freshUser = await googleAuthDb.getById(user.id);
const companyId = freshUser?.selectedCompanyId || user.selectedCompanyId;
if (!companyId) throw new Error('No company selected');
const parentModel = model || (await resolveDefaultModel(user.id));
const tool = createSubagentTool({
googleAuthId: user.id,
companyId,
conversationId: null,
parentModel,
depth: 0,
});
console.log(`[mcp] Tool invoke dash-subagent: task="${task?.slice(0, 100)}"`);
const signals = [AbortSignal.timeout(300_000)];
if (extra?.signal) signals.push(extra.signal);
const signal = AbortSignal.any(signals);
const result = await tool.invoke(
{ task, skills, conversation_id, max_iterations, note },
{ signal }
);
let text = JSON.stringify(result, null, 2);
if (text.length > MAX_RESPONSE_CHARS) {
text = `${text.slice(0, MAX_RESPONSE_CHARS)}\n…[truncated, ${text.length} chars total]`;
}
return {
content: [{ type: 'text', text }],
};
}
);
server.registerTool(
'search-articles',
{
title: 'Search Articles',
description: TOOL_DESCRIPTIONS.search_articles || 'Search articles by name, brand, EAN, SKU, or supplier SKU (AND logic, ordered by name).',
inputSchema: {
searchquery: z.string().nullable().optional().describe(TOOL_PARAMETER_DESCRIPTIONS.search_articles?.searchquery || 'Keywords matching name, brand, EAN, SKU, or supplier SKU (AND logic).'),
excludewords: z.string().nullable().optional().describe(TOOL_PARAMETER_DESCRIPTIONS.search_articles?.excludewords || 'Keywords to exclude (AND logic).'),
offset: z.number().int().min(0).nullable().optional().describe(TOOL_PARAMETER_DESCRIPTIONS.search_articles?.offset || 'Pagination offset (default 0).'),
limit: z.number().int().min(1).max(20).nullable().optional().describe(TOOL_PARAMETER_DESCRIPTIONS.search_articles?.limit || 'Window size 1–20 (default 3).'),
},
},
async ({ searchquery = '', excludewords = '', offset = 0, limit = 3 } = {}) => {
const freshUser = await googleAuthDb.getById(user.id);
const companyId = freshUser?.selectedCompanyId || user.selectedCompanyId;
if (!companyId) throw new Error('No company selected');
console.log(`[mcp] Tool invoke search-articles: query="${searchquery ?? ''}" exclude="${excludewords ?? ''}" offset=${offset} limit=${limit}`);
const tool = createSearchArticlesTool({ googleAuthId: user.id, companyId });
const result = await tool.invoke({
searchquery: searchquery ?? '',
excludewords: excludewords ?? '',
offset: offset ?? 0,
limit: limit ?? 3,
});
let text = JSON.stringify(result, null, 2);
if (text.length > MAX_RESPONSE_CHARS) {
text = `${text.slice(0, MAX_RESPONSE_CHARS)}\n…[truncated, ${text.length} chars total]`;
}
return {
content: [{ type: 'text', text }],
};
}
);
return server;
}