feat: 29 MCP tools — full agency read+write coverage

5 new MCP read tools (29 total: 16 read + 13 write):
- get_contracts: Verträge (AVV, NDA, SLA, Werkvertrag) with status + value
- get_projects: Projects with progress, budget, task/time counts
- get_timesheet: Weekly timesheet grid with lock status
- get_notifications: All alerts (overdue invoices, expiring licenses, SLA breaches)
- get_calendar: Upcoming deadlines (30-day look-ahead across all modules)

Claude Code can now read:
  companies, pipeline, invoices, leads, dashboard, tickets, buchhaltung,
  anlagen, kassenbuch, belege, finanzamt, contracts, projects, timesheet,
  notifications, calendar (16 read tools)

And write:
  approve lead, create ticket/deal/task/invoice/buchung/beleg/pauschale/kassenbuch,
  log time, finalize invoice, run recurring, update deal stage (13 write tools)

All 29 tools tested and verified working.
This commit is contained in:
Z User committed 2026-10-27 20:46:31 +00:00
1 parent 9c226c471c
commit 97426e0ca8
1 file changed
+128
+128
View File
@@ -285,6 +285,134 @@ const TOOLS: McpTool[] = [
}
},
},
// --- Additional read tools ---
{
name: 'get_contracts',
description: 'List contracts (Verträge) with type, status, company, value. Includes AVV, NDA, SLA, Werkvertrag.',
inputSchema: {
type: 'object',
properties: {
status: { type: 'string', enum: ['draft', 'sent', 'signed', 'active', 'expired', 'terminated'] },
},
},
requiredScopes: ['mcp:read'],
handler: async (args) => {
const where = args.status ? { status: String(args.status) } : {}
const contracts = await db.contract.findMany({ where, orderBy: { updatedAt: 'desc' }, take: 50, include: { company: { select: { name: true } } } })
return contracts.map((c) => ({
number: c.number, type: c.type, title: c.title, status: c.status,
company: c.company.name, valueCents: c.valueCents,
startDate: c.startDate, endDate: c.endDate, signedAt: c.signedAt,
}))
},
},
{
name: 'get_projects',
description: 'List projects with status, progress, budget, and task counts.',
inputSchema: {
type: 'object',
properties: {
status: { type: 'string', enum: ['planning', 'active', 'review', 'done', 'on-hold', 'cancelled'] },
},
},
requiredScopes: ['mcp:read'],
handler: async (args) => {
const where = args.status ? { status: String(args.status) } : {}
const projects = await db.project.findMany({ where, orderBy: { updatedAt: 'desc' }, take: 50, include: { company: { select: { name: true } }, _count: { select: { tasks: true, timeEntries: true } } } })
return projects.map((p) => ({
id: p.id, name: p.name, type: p.type, status: p.status, progress: p.progress,
company: p.company.name, budgetType: p.budgetType, spentHours: p.spentHours, budgetHours: p.budgetHours,
dueDate: p.dueDate, taskCount: p._count.tasks, timeEntryCount: p._count.timeEntries,
}))
},
},
{
name: 'get_timesheet',
description: 'Get weekly timesheet data: projects × days grid with minutes per cell and lock status.',
inputSchema: {
type: 'object',
properties: {
week: { type: 'string', description: 'A date within the target week (YYYY-MM-DD). Default: current week.' },
},
},
requiredScopes: ['mcp:read'],
handler: async (args) => {
const now = new Date()
let refDate = now
if (args.week) { const d = new Date(String(args.week)); if (!isNaN(d.getTime())) refDate = d }
const day = refDate.getDay()
const offset = day === 0 ? -6 : 1 - day
const monday = new Date(refDate); monday.setDate(refDate.getDate() + offset); monday.setHours(0, 0, 0, 0)
const sunday = new Date(monday); sunday.setDate(monday.getDate() + 6); sunday.setHours(23, 59, 59, 999)
const entries = await db.timeEntry.findMany({ where: { date: { gte: monday, lte: sunday } }, include: { project: { select: { name: true } } }, orderBy: { date: 'asc' } })
const totalMinutes = entries.reduce((s, e) => s + e.minutes, 0)
const billableMinutes = entries.filter((e) => e.billable).reduce((s, e) => s + e.minutes, 0)
const lockedCount = entries.filter((e) => e.locked).length
return {
weekStart: monday.toISOString().slice(0, 10),
weekEnd: sunday.toISOString().slice(0, 10),
totalEntries: entries.length,
totalMinutes,
billableMinutes,
lockedCount,
allLocked: entries.length > 0 && entries.every((e) => e.locked),
entries: entries.map((e) => ({ description: e.description, minutes: e.minutes, billable: e.billable, locked: e.locked, project: e.project.name, date: e.date })),
}
},
},
{
name: 'get_notifications',
description: 'Get all active notifications: overdue invoices, expiring licenses/assets, SLA breaches, pending approvals, pending VAT.',
inputSchema: { type: 'object', properties: {} },
requiredScopes: ['mcp:read'],
handler: async () => {
const now = new Date()
const in7Days = new Date(now.getTime() + 7 * 86400000)
const [overdueInv, expiringLic, expiringAssets, urgentTickets, pendingVat] = await Promise.all([
db.invoice.findMany({ where: { status: 'overdue' }, include: { company: { select: { name: true } } }, take: 10 }),
db.license.findMany({ where: { status: 'active', validUntil: { lt: in7Days } }, include: { company: { select: { name: true } } }, take: 10 }),
db.asset.findMany({ where: { status: { in: ['active', 'expiring'] }, renewalDate: { lt: in7Days } }, take: 10 }),
db.ticket.findMany({ where: { status: 'open', priority: 'urgent' }, take: 10 }),
db.vatReturn.findFirst({ where: { status: { in: ['computed', 'prepared'] } }, orderBy: { periodYear: 'desc' } }),
])
return {
overdueInvoices: overdueInv.map((i) => ({ number: i.number, company: i.company.name, grossCents: i.grossCents, dueDate: i.dueDate })),
expiringLicenses: expiringLic.map((l) => ({ product: l.product, company: l.company.name, validUntil: l.validUntil })),
expiringAssets: expiringAssets.map((a) => ({ name: a.name, type: a.type, renewalDate: a.renewalDate })),
urgentTickets: urgentTickets.map((t) => ({ number: t.number, subject: t.subject, createdAt: t.createdAt })),
pendingVat: pendingVat ? { period: `${pendingVat.periodMonth}/${pendingVat.periodYear}`, payableCents: pendingVat.payableCents, status: pendingVat.status } : null,
total: overdueInv.length + expiringLic.length + expiringAssets.length + urgentTickets.length + (pendingVat ? 1 : 0),
}
},
},
{
name: 'get_calendar',
description: 'Get upcoming deadlines (next 30 days): VAT, contracts, licenses, assets, projects, invoices, tickets.',
inputSchema: {
type: 'object',
properties: { days: { type: 'integer', default: 30, description: 'Look-ahead in days' } },
},
requiredScopes: ['mcp:read'],
handler: async (args) => {
const now = new Date()
const limit = new Date(now.getTime() + (Number(args.days ?? 30)) * 86400000)
const [contracts, licenses, assets, projects, invoices] = await Promise.all([
db.contract.findMany({ where: { status: 'active', endDate: { gte: now, lte: limit } }, include: { company: { select: { name: true } } } }),
db.license.findMany({ where: { status: 'active', validUntil: { gte: now, lte: limit } }, include: { company: { select: { name: true } } } }),
db.asset.findMany({ where: { status: { in: ['active', 'expiring'] }, renewalDate: { gte: now, lte: limit } } }),
db.project.findMany({ where: { dueDate: { gte: now, lte: limit }, status: { in: ['planning', 'active', 'review'] } } }),
db.invoice.findMany({ where: { status: 'sent', dueDate: { gte: now, lte: limit } }, include: { company: { select: { name: true } } } }),
])
const deadlines = [
...contracts.map((c) => ({ date: c.endDate, type: 'Vertrag', title: `${c.number} (${c.company.name})` })),
...licenses.map((l) => ({ date: l.validUntil, type: 'Lizenz', title: `${l.product} (${l.company.name})` })),
...assets.map((a) => ({ date: a.renewalDate, type: 'Asset', title: a.name })),
...projects.map((p) => ({ date: p.dueDate, type: 'Projekt', title: p.name })),
...invoices.map((i) => ({ date: i.dueDate, type: 'Rechnung', title: `${i.number} (${i.company.name})` })),
].filter((d) => d.date).sort((a, b) => new Date(a.date!).getTime() - new Date(b.date!).getTime())
return { total: deadlines.length, days: Number(args.days ?? 30), deadlines }
},
},
// --- Write tools (gated by write scopes) ---
{
name: 'approve_lead',