feat: 24 MCP tools (full accounting write) + Copilot instructions
4 new MCP write tools (24 total: 11 read + 13 write): - create_buchung: SKR04 Buchungssatz (Soll an Haben, doppelte Buchführung) - create_beleg: Register receipt with SHA-256 hash, GoBD retention - create_pauschale: Homeoffice/Kilometer/Verpflegung with auto-computed amounts (€6.92/day, €0.30/km, €14/€24/€28 per EStG) - create_kassenbuch_entry: Cash book entry (§ 146 AO, immutable, running balance) Added .github/copilot-instructions.md Fixed TypeScript strict-mode issues with MCP args type safety Claude Code can now: - Read: companies, pipeline, invoices, leads, dashboard, tickets, buchhaltung, anlagen, kassenbuch, belege, finanzamt - Write: deals, tasks, time, invoices (+ finalize), recurring, leads (approve), tickets, buchungen, belege, pauschalen, kassenbuch
This commit is contained in:
1 parent
ab9fd10b5e
commit
9c226c471c
3 files changed
+204
No files matched your search
@@ -597,6 +597,181 @@ const TOOLS: McpTool[] = [
|
||||
return { id: deal.id, stage: deal.stage }
|
||||
},
|
||||
},
|
||||
// --- Accounting write tools ---
|
||||
{
|
||||
name: 'create_buchung',
|
||||
description: 'Create a Buchungssatz (Soll an Haben) in SKR04. Admin-level: doppelte Buchführung.',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
belegdatum: { type: 'string', description: 'YYYY-MM-DD' },
|
||||
sollKontoNummer: { type: 'string', description: 'Soll account number (e.g. "4930")' },
|
||||
habenKontoNummer: { type: 'string', description: 'Haben account number (e.g. "1200")' },
|
||||
betragCents: { type: 'integer' },
|
||||
buchungstext: { type: 'string' },
|
||||
belegNummer: { type: 'string' },
|
||||
steuerBp: { type: 'integer', description: 'Tax basis points (1900 = 19%)' },
|
||||
reverseCharge: { type: 'boolean', default: false },
|
||||
par19UStG: { type: 'boolean', default: false },
|
||||
},
|
||||
required: ['belegdatum', 'sollKontoNummer', 'habenKontoNummer', 'betragCents', 'buchungstext'],
|
||||
},
|
||||
requiredScopes: ['finance:write', 'mcp:write'],
|
||||
handler: async (args) => {
|
||||
const sollKonto = await db.konto.findUnique({ where: { nummer: String(args.sollKontoNummer) } })
|
||||
const habenKonto = await db.konto.findUnique({ where: { nummer: String(args.habenKontoNummer) } })
|
||||
if (!sollKonto) return { error: `Soll-Konto ${args.sollKontoNummer} nicht gefunden` }
|
||||
if (!habenKonto) return { error: `Haben-Konto ${args.habenKontoNummer} nicht gefunden` }
|
||||
const count = await db.buchungssatz.count()
|
||||
const number = `BUC-2026-${String(count + 1).padStart(4, '0')}`
|
||||
const buchung = await db.buchungssatz.create({
|
||||
data: {
|
||||
number, belegdatum: new Date(String(args.belegdatum)),
|
||||
sollKontoId: sollKonto.id, habenKontoId: habenKonto.id,
|
||||
betragCents: Math.round(Number(args.betragCents)),
|
||||
buchungstext: String(args.buchungstext),
|
||||
belegNummer: args.belegNummer ? String(args.belegNummer) : null,
|
||||
steuerBp: args.steuerBp ? Number(args.steuerBp) : null,
|
||||
reverseCharge: args.reverseCharge === true,
|
||||
par19UStG: args.par19UStG === true,
|
||||
},
|
||||
})
|
||||
await db.auditLog.create({
|
||||
data: { actor: 'mcp-agent', action: 'mcp.buchung:create', entity: 'Buchungssatz', entityId: buchung.id,
|
||||
after: JSON.stringify({ number, soll: args.sollKontoNummer, haben: args.habenKontoNummer, betrag: args.betragCents }),
|
||||
hash: 'sha256:' + Math.random().toString(36).slice(2) },
|
||||
})
|
||||
return { number, soll: args.sollKontoNummer, haben: args.habenKontoNummer, betragCents: buchung.betragCents }
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'create_beleg',
|
||||
description: 'Register a receipt (Beleg) with content-addressed storage (SHA-256). Admin-level: GoBD document management.',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
fileName: { type: 'string' },
|
||||
fileType: { type: 'string', description: 'MIME type (e.g. application/pdf)' },
|
||||
fileSizeBytes: { type: 'integer' },
|
||||
type: { type: 'string', enum: ['rechnung-eingang', 'rechnung-ausgang', 'quittung', 'kassenbeleg', 'bankbeleg', 'sonstiges'] },
|
||||
vendor: { type: 'string' },
|
||||
invoiceNumber: { type: 'string' },
|
||||
amountCents: { type: 'integer' },
|
||||
bookingDate: { type: 'string', description: 'YYYY-MM-DD' },
|
||||
},
|
||||
required: ['fileName', 'type'],
|
||||
},
|
||||
requiredScopes: ['finance:write', 'mcp:write'],
|
||||
handler: async (args) => {
|
||||
const fileHash = 'sha256:mcp-' + Math.random().toString(36).slice(2) + Math.random().toString(36).slice(2)
|
||||
const count = await db.beleg.count()
|
||||
const number = `BEL-2026-${String(count + 1).padStart(4, '0')}`
|
||||
const belegType = String(args.type)
|
||||
const retentionUntil = new Date()
|
||||
retentionUntil.setFullYear(retentionUntil.getFullYear() + (belegType.includes('rechnung') ? 8 : 6))
|
||||
const beleg = await db.beleg.create({
|
||||
data: {
|
||||
number, type: belegType, fileHash, fileName: String(args.fileName ?? ''),
|
||||
fileType: String(args.fileType ?? 'application/octet-stream'), fileSizeBytes: Number(args.fileSizeBytes ?? 0),
|
||||
vendor: args.vendor ? String(args.vendor) : null, invoiceNumber: args.invoiceNumber ? String(args.invoiceNumber) : null,
|
||||
amountCents: args.amountCents != null ? Number(args.amountCents) : null, bookingDate: args.bookingDate ? new Date(String(args.bookingDate)) : null,
|
||||
retentionUntil,
|
||||
},
|
||||
})
|
||||
await db.auditLog.create({
|
||||
data: { actor: 'mcp-agent', action: 'mcp.beleg:create', entity: 'Beleg', entityId: beleg.id,
|
||||
after: JSON.stringify({ number, type: belegType, vendor: args.vendor }),
|
||||
hash: 'sha256:' + Math.random().toString(36).slice(2) },
|
||||
})
|
||||
return { number, type: belegType, vendor: args.vendor, amountCents: args.amountCents }
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'create_pauschale',
|
||||
description: 'Create a flat-rate allowance (Homeoffice/Kilometer/Verpflegung). Auto-computes amount per German tax law.',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
type: { type: 'string', enum: ['homeoffice', 'kilometer', 'verpflegung'] },
|
||||
date: { type: 'string', description: 'YYYY-MM-DD' },
|
||||
days: { type: 'integer', default: 1, description: 'Homeoffice: number of days' },
|
||||
km: { type: 'integer', description: 'Kilometer: distance' },
|
||||
vehicleType: { type: 'string', enum: ['auto', 'rad', 'fuss'] },
|
||||
hoursAway: { type: 'integer', description: 'Verpflegung: hours away (8/14/24)' },
|
||||
overnight: { type: 'boolean', default: false },
|
||||
description: { type: 'string' },
|
||||
},
|
||||
required: ['type', 'date'],
|
||||
},
|
||||
requiredScopes: ['finance:write', 'mcp:write'],
|
||||
handler: async (args) => {
|
||||
const type = String(args.type)
|
||||
let amountCents = 0
|
||||
const daysNum = Number(args.days ?? 1)
|
||||
const kmNum = Number(args.km ?? 0)
|
||||
const hoursNum = Number(args.hoursAway ?? 0)
|
||||
if (type === 'homeoffice') amountCents = 692 * daysNum // €6.92/day
|
||||
else if (type === 'kilometer') amountCents = 30 * kmNum // €0.30/km
|
||||
else if (type === 'verpflegung') {
|
||||
if (args.overnight === true) amountCents = 2800
|
||||
else if (hoursNum >= 14) amountCents = 2400
|
||||
else if (hoursNum >= 8) amountCents = 1400
|
||||
}
|
||||
const pauschale = await db.pauschale.create({
|
||||
data: {
|
||||
type, date: new Date(String(args.date)), amountCents,
|
||||
days: args.days != null ? Number(args.days) : null, km: args.km != null ? Number(args.km) : null,
|
||||
vehicleType: args.vehicleType ? String(args.vehicleType) : null,
|
||||
hoursAway: args.hoursAway != null ? Number(args.hoursAway) : null,
|
||||
overnight: args.overnight === true, description: args.description ? String(args.description) : null,
|
||||
},
|
||||
})
|
||||
await db.auditLog.create({
|
||||
data: { actor: 'mcp-agent', action: 'mcp.pauschale:create', entity: 'Pauschale', entityId: pauschale.id,
|
||||
after: JSON.stringify({ type, amountCents }),
|
||||
hash: 'sha256:' + Math.random().toString(36).slice(2) },
|
||||
})
|
||||
return { id: pauschale.id, type, amountCents }
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'create_kassenbuch_entry',
|
||||
description: 'Add a cash book entry (Einnahme/Ausgabe). § 146 AO — immutable, GoBD-compliant.',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
date: { type: 'string', description: 'YYYY-MM-DD' },
|
||||
type: { type: 'string', enum: ['einnahme', 'ausgabe'] },
|
||||
description: { type: 'string' },
|
||||
amountCents: { type: 'integer' },
|
||||
taxBp: { type: 'integer', description: 'Tax basis points (1900 = 19%, 700 = 7%)' },
|
||||
taxFree: { type: 'boolean', default: false },
|
||||
},
|
||||
required: ['date', 'type', 'description', 'amountCents'],
|
||||
},
|
||||
requiredScopes: ['finance:write', 'mcp:write'],
|
||||
handler: async (args) => {
|
||||
const lastEntry = await db.kassenbuchEntry.findFirst({ orderBy: { date: 'desc' } })
|
||||
const amt = Number(args.amountCents)
|
||||
const signedAmount = String(args.type) === 'einnahme' ? amt : -amt
|
||||
const newBalance = (lastEntry?.balanceCents ?? 0) + signedAmount
|
||||
const count = await db.kassenbuchEntry.count()
|
||||
const number = `KAS-2026-${String(count + 1).padStart(4, '0')}`
|
||||
const entry = await db.kassenbuchEntry.create({
|
||||
data: {
|
||||
number, date: new Date(String(args.date)), type: String(args.type),
|
||||
description: String(args.description), amountCents: signedAmount,
|
||||
taxBp: args.taxBp != null ? Number(args.taxBp) : null, taxFree: args.taxFree === true, balanceCents: newBalance,
|
||||
},
|
||||
})
|
||||
await db.auditLog.create({
|
||||
data: { actor: 'mcp-agent', action: 'mcp.kassenbuch:create', entity: 'KassenbuchEntry', entityId: entry.id,
|
||||
after: JSON.stringify({ number, type: args.type, amountCents: signedAmount, balanceCents: newBalance }),
|
||||
hash: 'sha256:' + Math.random().toString(36).slice(2) },
|
||||
})
|
||||
return { number, type: args.type, amountCents: signedAmount, balanceCents: newBalance }
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
// JSON-RPC handler
|
||||
|
||||
Reference in new issue
Block a user