feat: Claude Code auto-detect (.mcp.json) + 20 MCP tools + health endpoint

Claude Code integration:
- .mcp.json at repo root for auto-detection (uses seed admin token)
- CLAUDE.md project instructions (commands, architecture, rules)
- Pre-seeded admin token: aos_admin_seed_token_for_claude_code_2026
  - Scopes: all read + all write + mcp:read + mcp:write
  - Works out of the box after bun run src/lib/seed.ts

5 new MCP read tools (20 total: 11 read + 9 write):
- get_buchhaltung: SKR04 Konten, Journal, Saldenliste, EÜR summary
- get_anlagen: Anlagenverzeichnis with AfA, book values, GWG
- get_kassenbuch: Cash book entries, running balance
- get_belege: Receipts with SHA-256 content hashes
- get_finanzamt: VAT returns, Jahreserklärungen, Jahresarchive

Fix: api-auth regex updated to allow underscores in tokens (aos_[a-z0-9_]+)

Health endpoint: GET /api/health (DB connection + version + uptime)
This commit is contained in:
Z User committed 2026-10-27 20:23:50 +00:00
1 parent 1a3241971c
commit ab9fd10b5e
7 files changed
+231 -1

No files matched your search

+25
View File
@@ -0,0 +1,25 @@
import { NextResponse } from 'next/server'
import { db } from '@/lib/db'
export const dynamic = 'force-dynamic'
// GET /api/health — system health check
export async function GET() {
try {
const companyCount = await db.company.count()
return NextResponse.json({
status: 'ok',
timestamp: new Date().toISOString(),
version: '1.0.0',
database: { connected: true, companyCount },
uptime: process.uptime ? `${Math.round(process.uptime())}s` : 'n/a',
})
} catch {
return NextResponse.json({
status: 'error',
timestamp: new Date().toISOString(),
database: { connected: false },
}, { status: 503 })
}
}
+121
View File
@@ -164,6 +164,127 @@ const TOOLS: McpTool[] = [
}))
},
},
// --- Accounting read tools ---
{
name: 'get_buchhaltung',
description: 'Get SKR04 accounting data: Konten, Journal (Buchungssätze), Saldenliste, EÜR summary. Full doppelte Buchführung overview.',
inputSchema: { type: 'object', properties: {} },
requiredScopes: ['mcp:read'],
handler: async () => {
const [konten, buchungen, euerEntries] = await Promise.all([
db.konto.findMany({ orderBy: { nummer: 'asc' } }),
db.buchungssatz.findMany({ take: 50, orderBy: { belegdatum: 'desc' }, include: { sollKonto: true, habenKonto: true } }),
db.euerEntry.findMany({ where: { jahr: 2026 }, orderBy: { zeile: 'asc' } }),
])
const einnahmen = euerEntries.filter((e) => e.art === 'einnahme').reduce((s, e) => s + e.betragCents, 0)
const ausgaben = euerEntries.filter((e) => e.art === 'ausgabe').reduce((s, e) => s + e.betragCents, 0)
return {
kontenCount: konten.length,
buchungenCount: buchungen.length,
festgeschrieben: buchungen.filter((b) => b.festgeschrieben).length,
euer: { einnahmen, ausgaben, gewinn: einnahmen - ausgaben },
journal: buchungen.slice(0, 10).map((b) => ({
number: b.number, belegdatum: b.belegdatum, betragCents: b.betragCents,
soll: b.sollKonto.nummer, haben: b.habenKonto.nummer, text: b.buchungstext,
festgeschrieben: b.festgeschrieben,
})),
}
},
},
{
name: 'get_anlagen',
description: 'Get asset register (Anlagenverzeichnis) with AfA (depreciation). Returns all assets with book values.',
inputSchema: { type: 'object', properties: {} },
requiredScopes: ['mcp:read'],
handler: async () => {
const anlagen = await db.anlage.findMany({ orderBy: { purchaseDate: 'desc' } })
return {
total: anlagen.length,
totalPurchaseCents: anlagen.reduce((s, a) => s + a.purchaseCostCents, 0),
totalBookValueCents: anlagen.reduce((s, a) => s + a.bookValueCents, 0),
totalAnnualAfaCents: anlagen.reduce((s, a) => s + a.annualAfaCents, 0),
gwgCount: anlagen.filter((a) => a.isGWG).length,
assets: anlagen.map((a) => ({
number: a.number, name: a.name, category: a.category,
purchaseCostCents: a.purchaseCostCents, bookValueCents: a.bookValueCents,
annualAfaCents: a.annualAfaCents, isGWG: a.isGWG, status: a.status,
})),
}
},
},
{
name: 'get_kassenbuch',
description: 'Get cash book (Kassenbuch) entries. § 146 AO compliant — immutable.',
inputSchema: {
type: 'object',
properties: { limit: { type: 'integer', default: 20 } },
},
requiredScopes: ['mcp:read'],
handler: async (args) => {
const entries = await db.kassenbuchEntry.findMany({
take: Math.min(Number(args.limit ?? 20), 100),
orderBy: { date: 'desc' },
})
const balance = entries[0]?.balanceCents ?? 0
return {
entries: entries.map((e) => ({
number: e.number, date: e.date, type: e.type, description: e.description,
amountCents: e.amountCents, balanceCents: e.balanceCents,
})),
currentBalanceCents: balance,
}
},
},
{
name: 'get_belege',
description: 'Get receipts/documents (Belege). Content-addressed (SHA-256 hash).',
inputSchema: {
type: 'object',
properties: { limit: { type: 'integer', default: 20 } },
},
requiredScopes: ['mcp:read'],
handler: async (args) => {
const belege = await db.beleg.findMany({
take: Math.min(Number(args.limit ?? 20), 100),
orderBy: { createdAt: 'desc' },
})
return {
total: belege.length,
totalAmountCents: belege.reduce((s, b) => s + (b.amountCents ?? 0), 0),
belege: belege.map((b) => ({
number: b.number, type: b.type, vendor: b.vendor,
invoiceNumber: b.invoiceNumber, amountCents: b.amountCents,
fileHash: b.fileHash?.slice(0, 16) + '…', bookingDate: b.bookingDate,
})),
}
},
},
{
name: 'get_finanzamt',
description: 'Get Finanzamt data: VAT returns, Jahreserklärungen, Jahresarchive. GoBD compliance overview.',
inputSchema: { type: 'object', properties: {} },
requiredScopes: ['mcp:read'],
handler: async () => {
const [vatReturns, jahreserkl, archiv] = await Promise.all([
db.vatReturn.findMany({ orderBy: [{ periodYear: 'desc' }, { periodMonth: 'desc' }] }),
db.jahreserklaerung.findMany({ orderBy: { jahr: 'desc' } }),
db.jahresarchiv.findMany({ orderBy: { jahr: 'desc' } }),
])
return {
vatReturns: vatReturns.map((v) => ({
period: `${v.periodMonth}/${v.periodYear}`, status: v.status,
payableCents: v.payableCents, elsterRef: v.elsterRef,
})),
jahreserklarungen: jahreserkl.map((e) => ({
jahr: e.jahr, art: e.art, status: e.status, elsterRef: e.elsterRef,
})),
jahresarchive: archiv.map((a) => ({
jahr: a.jahr, archivHash: a.archivHash?.slice(0, 24) + '…',
buchungenCount: a.buchungenCount, belegeCount: a.belegeCount,
})),
}
},
},
// --- Write tools (gated by write scopes) ---
{
name: 'approve_lead',