From 9c226c471c2ff4b2c8c93da697f2103b31a5954f Mon Sep 17 00:00:00 2001 From: Z User Date: Tue, 27 Oct 2026 20:30:16 +0000 Subject: [PATCH] feat: 24 MCP tools (full accounting write) + Copilot instructions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .github/copilot-instructions.md | 29 ++++++ db/custom.db | Bin 442368 -> 442368 bytes src/app/api/mcp/route.ts | 175 ++++++++++++++++++++++++++++++++ 3 files changed, 204 insertions(+) create mode 100644 .github/copilot-instructions.md diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..1d24f6f --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,29 @@ +# AOS Copilot Instructions + +## Project +AOS (Agency Operating System) — self-hosted platform for German software agencies. + +## Tech Stack +- Next.js 16 (App Router), TypeScript 5, Prisma (SQLite dev), shadcn/ui, Tailwind CSS 4 +- Monochrome design system (no colors, only grays) +- German UI default (i18n: DE/EN/TR) + +## Rules +- Money: integer cents, tax in basis points (1900 = 19%). No floats for money. +- GoBD: immutable after Festschreibung, corrections via Storno +- Audit: hash-chain on all business operations +- No mocks — real implementations only +- All API routes: try/catch error handling +- All icon-only buttons: aria-label (WCAG 2.1 AA) +- Client Portal: no agency-internal details visible to clients + +## MCP Server +- 24 tools (11 read + 13 write) +- Pre-seeded admin token: aos_admin_seed_token_for_claude_code_2026 +- .mcp.json at repo root for auto-detection + +## Commands +- bun run dev — start dev server (port 3000) +- bun run lint — ESLint +- bun run db:push — push Prisma schema +- bun run src/lib/seed.ts — seed database diff --git a/db/custom.db b/db/custom.db index d097d14a23ec73b464d96c56efa437ab038ea268..11411cf319db57f8d2a9638c91f7ec42b37c1f5c 100644 GIT binary patch delta 33089 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 AmH+?% delta 33085 zcmd6Q36vbgnPz1jt!_!JmXKPlrq$|hnnsePmMSZ+N?oNBAqgEIgh3#Qtg5X0zN>7x zRbs#h19rOxau^>VGds3-JO%@9tu1k^F^px8ZDtnl3^s@Do$(Cg!GJy9UCgzA{8d?< z(b3F%^Y*=c%j2aGk^dKe{8#+(M?}`CQv;_?4LtJHV6RvTME!2JTh7Lnl<3Wg5pTZI zcwunun$F3^+}R0u9`2aqQ%43|u0XPsBkybQzC_+H1tXD4K3;8bm8FesE0zGwB}jA0 zb*?!+Wn^fYMgd#RnSTF}bB=H0=%VTOz3{$(@7FRZwHmE7{`-oR z@ctsiUv!-Vcv{&Se!Fj_qr)5IIPXgnC;6QzAlq3W^j+X?_B_cTY-aH(fHz)OW|*W z__x8vf88>;wsUeh{%!e4XV*YqSC`G^a^;JeOgdNK*SXiZv2mdJY|Qm72l7ktZiF8q#{WDv3`AE9cRPi}uEtxb#a+C+Ij+ka8;|#obWUEre4cZ4udAR{*5wOY zDyZdx+cP|95fLtL6ps%%IwwaPM(fXSwf_9saX0?|1t*rnpO-bpPprm2Z#==npCgUC zPmJK7-#D=X{v2-n?TIz;=g_0KbS%8$*S4Pgf?qGVt@|IkPt5 z`L6k$l{r6~(`HsEPTqMPCMM55Jqpno+_=+iE5$I?IqOiTwY1UN@C-t+EC*c)tz!l(!G2k?GKil+&@;E2jnY(eEoV; z@JT|h5b{JOQSn5i0FdvNvbhu=$R*08mSf=H+<|WMT}Ppf|yO#{JAD+ zy|WYe-8j%|Q`)qN5=9YgjS5|TBs6hy!G)D za>iYd1Ho)G$s^@w>vk7VO1)OD<7t@EFtlgnvfm#|YmHByJfNft0acCWVxrg-bELk) z3AEz_z5M?6h~r?}VvY1#^Gl%wOjl@LmwnJ4o5KQUb7QkE%+!{KU*3bZD0^knu>9mp$qFX;av276__Ul14D955rxrH7* z^?Em%-oQ9!gpx}vvkV(37Fk-^FtE_l$~t+^jMfR2^5X^8=9yla?Y6cuUO0bdE9GSK zX0|pRX`E>Bo<1=eV-xdLmJ)?5KwDXX)l3US7+ zTB$-GW`NjCSFgmRA}p{`9+Y`@Qa~oY%n83yo!; zD*$P8+I80hu0=kzca$HJe5?EWMhB&7QUORYKox2!P0?hn@tw~-Vj(R$7x}JPGyUI^U9&IpDT_vhL1D+(K;N3t)ziH~%HhTd#4ly@Wxv)4eeq$C z<~UN0H=(u~3k=|&rtLgiFU<*SW-4}1;YTZ`|lee}V8%uZPqSq%~eOX($wY^A%b z6dH#vC|7k(N+>Omy4#v1aEFYMagIVmETSc9p` z*VntxHZqMxl5vb>Wv`HxssdzJ=v%B18#g^60hWMRLL4S9UtgbUTTEl6(T`HOd{h-v zRxC>5jlX;1ifODeOlH2m0dTHqEE)L1JdankST+=EW3dLj^GVN@ZN7>yv-$e_w%G=0 ztT^GTtY_kUCY~|*nyJmkg(rDn%Y#GooP>!Epf+1YPuPkQsHm0>N#2Cof||wZ#izWW z#}-z-6DB>7*T&WBr}-j5DonrvVNW~|uJ9iY_V`mfz=c|1Z5Lv!igF>wOehy(6i>Mj zX{A(2xe!PpLTiU|AtEG7-joY5BDA^?`U54CN1y-sJai!)R>~M^6QyW2;?ZKJ78!SM zEO@E^#2s%-+l#@lUKkGR@n&Qh4s z4!~UR;CdNM%mi|7A?o%9%T`QkJ~{-e;7~ddO^<(6_vfPp-SxZ7+=Q+Lxi3B5G1BZx zMfTN<3%c6qb8w5Pz5>8MmQ3_aDiV>(g4q`<_usrcdhp(pP0(hx=gm6-RB~{m3?>!` z3K3OIQkxaU24U*lh2 znRoE^GfiLxC7{$Dc0j&yrd5za6XqxxcQ{;$=HezY6F;teRXMnOtci)M*hx9Z0i-(9 zC`fq)GnW7?S9QJ}l4gk`fBouM&&d50=A%D$sQ5rkhEgvT9vDSY;eio06&|MLHm4=H&%SvOleh@<86v?n9Eo|eLLt#O z``g8jmEZih0|Mt3)bEzYyT8LyV=N3WV|th^D>cm>j6SyR-~P=BFK)&cH#gS2-Fz_w zFJc-Z6$0L3q!N7W*xRRiQT7RF!Wjz8!5xT1Yu02u7KhdZVJBNuw2VJ2Q<@>I7$FPc zLdoNnbxJcaXpIqCe>#~gRQw91nJAGx&OyOsB%F!sIZ8Fq%tr`uT+5WwSx=493~9yi zT1t6pUR{q;nqg!}X=YkzV=bvss$oPmwUB#y{x~ty4D^Z)R4sdhs#x-Vm;)KFFXuKK z{iU>>JNxT(gWFxGja|)Wz1!8}^X(ew@8dX!%f(GcZ32(Zq)Hxdwj62uClg_f`XDDC zy*Li|fAYO!+i#}voq)e%uz!%_U`nvy!=jwW#;!)%F?quo~qtpNmvWnY<8c&k#&BG|sNzyi3nq1pNL_-nhNF z%4p#mZ>->!;!H{KFsBppaXnK|lP!MP;9}hUzWq{T{}8u)>G>A{bbrTN+dGln9nGgP zF~s#EC@jTz%n0N_P35I>JA-AW0pmME+^VHr8VLDk=exEyV`~dC1%qcf_&uWwaWcMC4vur${OIPH!nwX&&7jERddpai% zp%4$%JLb>x`3~AfNA11SR!iW}sys@ms8;iBfwFL$k8)cfmpq7Y2R}X!&^7}aBn9ov z1*#67=9)lF2aA85bu2Z0e2nV{4)-93d+MAMFpm&Si@D?h6O+=sH^*1J@wO0IO#V2x z5jbqYRUCk}15{!iFF|V-rqlvdTc*=F#+!`Ck8>Vi?kZ&Ns`?x|z#SuS7M0`x4l_=^ z9F=3?L_0fV;nttvwprku9Kh`-8n%oz3vieirZRetFNPzg!LV@OI>Bw}>YQxG?JK8l z;cji_S&ZvjYkD!pldUzq7~`4NnjW@NoriO~;|f$7!B{qxMld!?#ouY0 zn`jvw6@VG@p#m^t4pabUv`fWbMp;uC?uBlQzeJEY1ChKpS+41(9+~)J{O1Yo@D>Oo zbwOdmW{<^$KOU_F z+N(rHrpO3@?5+2}CN2d+#DF=nriHV4IoDoRF%m!!*mT=Hb&8v8PM(Y?DETvkq~uRp zDefuxGi-H3-Ls?nw5{ild&U+#x!~q=3qQl%h6;%bus4oDM9g|B!E7pFDukWEp7{bd z*7ZxNIfETG-ueQ!m?Y704?tk#h*#u9K@7H|FB8lUC5qu_YWzqhs1@}5@S5Sn$#h&F zzfR8=HjS?0&KAzU*h0U>+3oY$Y&_Rx@9b>PX>nMr!LAV`#=&&nU5!UB4O`iz@<-zf zU*wkUz4v4j@LJu0fU2DZL=BcbrGP);ZKs9o%<;h&xxu~W@UKbQ5pT~7JU1|iaZ1a& zbJb8JWb((xsn7%5*j{tM*Tgu)+ri>_Fze>Uj3!t8?UaDU`^f{`Wo`8wYA0!|hOI$@ zRh~6QF!BytZ4_H6;aP1ImPhWjv@EPfN}B1BO+CVWavoU*#$jJUo8r+Joc#QRB>nYkt3}Y7uy(%HD=?fg72yWsyg}?hf)E(HjD${C;;Z#$y?oN9_ZHjUBN$yg#QfQj7bM^d_+!Yg!Zy;Mi zN%Qj_cR-OsZm92u+=8G$VDkjMvMlqGM^z+GNK|yc#~qRtkq>El&>xVXS&~&R-<0T) z`UWUUslKrpR!ZV|GtrFkl2Xo8Obbx1G>T6(Er8t@Aaab7<1#6|0A|n>q@DZn@OwP6 zEP>8-*pc?Yzka_IRNMittP8Rmh7gbx#Uo3873@G%c~ymhNkM^EMP88v9{!P=Mtj{{ zn1;?dhq-HT0%YBs3QUZXgz?BLTpw&}97CTy7It+(8Q5ky1%@KP6(7VC7*aqs0h!So z*;lzyz`CjF|HqwxHArC*5=^afZl&fa$u%n$B`f1EU*RsD#_~G=YmCAo0>t%MS}0`l zy5CH<$5O#$u@W%H$SBSD>8sozECY@p){*JykkKJ!bwrVrLKv%~M9f$nB{a(FKn!bX zIZ#%|jBM&n?(}?|4xuO~3MA=C2SS=V;w>A-yWIGVvxf3{*KvP#>*?yTO|4i+PF>!D zBDke)@96gJ=fRB6@9dmj2qHX>+>1(0(JC?H?0eksdIHac^EuDsj>a(y^DS`W&G)z^ zC=Do=iN;w!Klc|OFk>$96fn5NxK*W_u@}58_?74Q=xCg2s(G^HiD2T$I4KN#z>F6{*O!qamS_VMS@2tv(gy|?2M-x%zW zyIeMh-O<_E=_AXUiRK{~$cDuX;h<0pRAg^bwQykWfw6g8FsFqI8AyGi&_D5X=>M-q z?muCF@4-0<$ETN>N@nUo_%fbLwNL8C$hf^9AfnFwiW%ECo^Y0G4{$VF2I z79O0xw9QGletgR0Ap@mnYJG-0G*3zf>%9|BBAo#^AZ?XSQ#i1dBI0g{q)Ae(oD(c; zc}#pJmZVF1E?CrC%K64z9)z>+j<-*ACr`~u^!(vLld!qa>jT-Fm3!nY(0m#P*V&ha z!87Bq>jJS6Uh~I1S}mJTShz4Z{n_A?*CDIY1I1hpLqq!BlfcFw&hjU0f3|Q=!uHH2 zs|qrxd>dz zZ=8YWwoThk*!R2%uJWy&)4UDL*rGtTT2oj%0x=#-MIgqTs0hSZITe8zW1}Jv7{OlJ z3aALgsEvw1jPRzm+8=9HVL&BfiOAI}C34Al#$4vdnSmO=*l!;rr8I2H5t?Ap?-eQ; zv(yl0hHrfOpnaKf^#S{0BqZ&4d|OfdnPg1UTf#>|%KweK541WXV!`c9AstO-14*+^ z5@+OP{M<@|ySb%IC91}Q?Zl69B||u9Ux&PrCbXS$(G$sK%cfDq8PSqKtw9>nq@YnI z(_%3wn6>pdBk*(oe9(Tdx%gltPFWBUJ0)Ajf+ztp7DS1PvLKMxUXhXtWkJmNDGOpo zI(3VEYv)F=h6E5wn0zMlAu;cb^lg1p@9xHhMYvmyrvMHG0=a~Q z%MhswYh@v+TH)Fm12a<^y>Yv+>f$(v;;(oA-dt0~W|bm{JyDlT*gs3H!iyA~9G9 zNmv*5#`48nrYdZ^{(3Vha+^A5=R2)bf)|I4zv*x;G2Tzud%NyZn_W$q<1xEBnrw{+ z7OoQ3kJU&tRV(GA)~?!l2Q#Y}k0kAzy1KL`{3j6pMCarLj%1>KvwewgrEm13{cz3z z>JF~`yycKs0@kq-7LwiNN;#zprn{iEnX7Pic*dC&$dC-_%X2pC&7PxX&nL0xll3@< zJ@07t?4x_egVwm*lKoy9UKTNB-MkzT zeVlt4_rTaFVcn@pFn(^`s7f#nYTc+}Ju1vn$c7# z4fn~KF=!I4Vbb`)9du%<8zJ#b=dhRyYXcK-#hop{ECAxz z;dYG2EAf(X=uZ3oi7w3qWEY2?s;=b{Rcf5~AfxwuymP5_Y4tFZ_f*K5^Ga2KNyX-e8cf4)NkX6d2S!8%8@^h!*ySs%8`GPON;haJ0GsY`Gy@Z%9BzO|;RKyDLY)RFPo_p*c-IE?M znPLksrb#oqUX%&lfgRmpJbX`!O-N=WtcNM8rW6Cwgz<}e?3*U;J=yMw@sSm?44{Qu zU(JDbSFYJeY{+Np;%3?q=?)8-d?cyWifYheLxLE}>B(S1kLHcff7&ka=U;3y!AylK zs9}8)hxN&+&)Cm4qa@?{*0TVtA6w4?Bp5fPA}r%`RD=Z|nV_A73bBlFQz4cyVJgCs zR?1MR2s>?VTFc(35IfVJGE|6VjKCUU6O7KL9<=`fxA~GV2P>HCr)v3B&K*k|JzukL zC6x%)K$qFRf(yn5&ToyY^^`VzeLCDc4}KOEc(%UEb%}4~HG_-$VCR^FvwybH_Ret< z4#O)L?E>zwJ6foCuG@E=8Gm^9(PCIjh4W-v+W67e>}w*x1nat;u#a)%C*J@bU~%2p zR!u}x!#nk4w0VW`0p#L=`YtGCtUNNv9Z&AAf3~O_kkCs5a)BvXMKmAjA@{Ms5vH^MY4%1bb#xfU@aet7p0Q% z%-8Mv_LDKqOsLyO+)2udaoRjg+IbZgmKmYmhYZ|j-1KmBICupPQ}1#pkgp}vIpfU3 z_PuyQm5DXZWix~WB7>Q*(cTPTP0;Eztj$=RhSjCjXIQaQJ_9maOAC#18aNXqQ-hp( z+J5sQve->Rx}#t*ESdATbJ3caF|tE3?))4354PTWvQf`0>V$hML#MAX_p%WM7onLN zOfyjPY#G}uDEI!Y{jsf^wl(VO76QuPXnhBTf+ewPEEE@GA#*!y7L+^wn?1YrjWdn< zjvm6xXg%LXAz=WQGo`9h)k_I8w`Ij|s4v)W^AX=GFMyLcgJVb9Ah5flV769E)gs|k z&D>Ia6UTxG*b|WjN!xRdpXwi(AEUKlpSeWQxRDNQAdMm_&x;&qVWvo6e zT=FtzYFarFEXulBu$zUK%73?4w{E|+QC~R^Q2Iye8{53V6&nQ$!lJdgjGhJK#lN#}+4{5JHR`!8B9)QThubKy zj+JpY0q$m0!a0w*UY-TzqknJz=GLp8ZPaV=1kQqT^^5kx z)^8e(dUXz>oW7}zLINlzMm1Fn$h^B?j))0!_x;ppY~Qx^xj%2zC+uWcBd3!T6qaE!86}!5dD;vyG09l_iv2rV zALwtKp3fmer`sY(fNjXC7g|L)+^y`Yr4ubTwdRaT#{H~0 zBeqg*Wlb5;wrKQvIva;?)}g0Vwzet5!>uZN7kdCz|Qc<1C- zv5&9Tk3ks?MsGU?hp}z8nWKlgj)M7YMJx){U|h3wY-T2G9!+kntnXSkXb$Vy_w9R+ zbx!^{cKPRZ9qMPKOTF3UVp`x(n1ru?K=o5k!#T7>ENSV|oSB%!(@1<^7rRF8haRiH zlpZp^`hoob2onAZihk&EM;Yg$r_C zX{V2jPZ|HI&w=-MkOmDd5mRJY3Yg_#RyoGIeU7!rAn7MZET-Vq{Na+?PB7Viu3y%o zffcbAa|AUVtEqv6d6;UJejZuo*o^(aQrC;+i4xqbl2fwjd8}y|1O1N6+cdas<4KiD z9Av73qvvMvT$yLqEK(o+% z8zEd`R7YpvL-aR0I23N zwS*ip&-2g1+x7v6i2V>a-194>By!HRZ3m2_8Gkm=k{fISd2#bPAY>qob+={B(A>A& z!DBasL?EUUtmzO|z2*Xz^=AXVy`{8iFmVqDf@zO846_z)p3g$_-Q^A$Y2fIJf{8ls zhZDHLN;|D(STj})IySUvFfC7IK((>hzthiW1|6H)H0W}Cu^86Hr1?dLS$O-ypu>wa zgaynY(vny!l~V1Pz(iu>mK80P5*jQz1i;xcLi=n0iF9?ROS62)u^xFNH0W}vP(@D&g}f?z0}-p{$eA9BpW?-V7O zC_G^K@(#9A@%GZ0tyEC$x3)6jbD6ajV%G#6B>QGGd#P}^bY?3R_8T{0ac?R~C#{qtQ%Sl$0nL=pQZ*{ZJE@d~@k1(QVLXmXSxnzrnKGS= z%2^mUnsPgq%pE03dJ$37hAexu2!o}rSU@Nh)0NP-9>F{qU?3ZQ%=r2S#|B8^S}?|28yrh7H@nBVPA+Hs zl^`#{AtUZVIC=CK_uy-fj6G(o+33jb=$xFw{-^5BIXynuBp$b|@v#hGl`QKIi=lGJ z{r-b65U2nH2jpqIy3w%{2GRm5b>KioZDSIRQvi9ONlVp?TZ@)8o&=MI36>C zn;eRTpK-V&5B!Yn7~5kT;4Nznf`hBus9zC1p=tuovSC#m__@;mg~yCnHaU{O&o^;k z-#iI-;X$zDwlS6E2Pf%hDV&H_BJ%fO2L@`%07IU}U7HudgZ;N9q46DUW{W?1iYsU_P6SfSC;Nbk9j;3>EIWEH4 zI7-p**{?qapvQ4okJqo|5J)BR846f{VDCl6Q**^src%^AX*l_Y2|sWIX+J7{lX2GP zxE{==g=TJzvqnq#H!*3-7AgNGt(3)4{!LmbGot((lsZ9M2j$<4&ME&Ut&}Dy|7H|K z`S-Ng$69iy{F@Qx)FH>}1!QYH3CnjCXI%j<<0ew@rNAx69pvM+-$D(2%Sat}EXOyY z4y0m{ATO6Z$x_O1JaF7G=Gb%ZIpeR6J62qB-~Oiewg}4@13wHWn>JDz27K&9W?nxvv$#5tij~aggwC<~2=XPBi z-O;KTDB}kXUc2+^Jv*+tZXDLeJomtV`;#Xq%=TqXcF2^VnPF0bW(GpZnPHV&*5rB~ zwZXsm&$)#e$0I#kiQu7HsA7ubtyZd)NZCwx*i(4qC@49s55@9tqkO(Sb;|MU9#jg0 zGW8k9O#`GU4Xg$*a#wS5SmRS!Bl8!I;j7_ZkB5irXo94}ZLg4541_#Nz!M63-F_{k z2XtOm;6@XiXptltcJmco)!dpC(jY1Xyn-LD7oG5gRCmbhzsXqfb;r_)W88y3-F%6S zgyrws2!o%s)z`o!_l@=LQ6Cf<%z9)R#1lZrR4&MTRTBIuZMYtxXdEw$4T&Pn6LAa(E3E&=UO#;qNk6BrRfD^!glh0PtaF#TG6i!#{ zNNDBZZDFvB;Z^&099k{wJntd$kl^F0yr>7^nwEzbyowByl`3nh*Aoipx-5IdkjDFE zugC{NYOf##{XDt5s1n`+N)Rrn1&xk}9h;1yuR9LIwtJJe_iwes{j_cM?x9cPDJAT}a{sFENz<8Djc=?(q9X9^qhmM^SfdD8dBzZKi0#~>~vLuRdlTY*p!Jb5a z5E?~^hf8wsisTjD3VahMeeWkY;-Fypj^?xO17!w|RN>r%3+CI_N+wOZ#4s4}7m5Kf zmX8$RO4f$;>m%uOJijiV&;rJnf8^LPA*&&`U+@cXQ8W+;@h~^*iXh4XxRC7Cy^>p$ z;qIVcgL{W~o0FeUCPkUEeGj|0H=p$bptV32nBc5TK*SGVJS}QMJSjwKCKE|5uCGgh zOD2ryj~&-ssc3LoJ>U(g;CoOr@PO5a+>)TEK|xdnxWA;4OPPwG>)sF#_e0l1zn`%E zY6cfm&pRIOiIRZC2%J(RBO6MQ4AUgCc6MEDTkv-G2lIbA@7;L^=l-XzKXhFUU$+*! zjH_RBEFp`^G_1%1SSRp?WDlHHGIvvE`SJ&RP2|Xpeo={)1hZbOr@#D&@XeZnmJ`HE)T}AQm`!nG)4w>by)h3^GBL#cpUr3eBXFC` zr(|cM!=llTg^FZSi+Q9(rDdil8(i_5-zzdc`)kLM8zLpulMTT|P%njk6S>~69vgIx zj_t6BhM?n0DB{<%Vp29wf6syrUrE{H_@(q)os7KvtIDVBCQ(nD{{rtZ9JbjsC z0mvx3-X)FI7h5`l%AX(CRH7ooVdJd1MTbuWcYvM*yp(F zS?pric>Xtz#Yba-NYNiIh#`0LLRqVe(@qWsw4?4Agb`h184&3bm--1f(+3Ar%%a9D z1A-!~=J?4su(v&ZAt9H`Me@~5QNgkNuK6@SPsuuiHMM1J*_zt2rfAJ<(~K!m@f}(t zv@K8po>3qb7Z?RlaeuW_ul*`Z6) zNqc@xc>o`MQqQ}?ekJ2ELgUV%B|#m|qQLjOLQ+AMBO=_u)pg@@<4(Ef!dEYFKfHVW z{VjOo=i@C-2@(dCXhh7w6}pVEaGi7c(qgR=mUGdlUdVaVLBA-}#Ecv7zKdVu7+>RH2JHrTj8O3$l&G_^YGl?sFYk?}ywRLn59SJx`H67_4kIVa z##h!m_w@Ya8&>=$%q)#nkJX%5;aaT;TPcBAttrKbz-mn}N4po$Lzvp?Om$xY6L?C2 zuSAH%d5UqEqDqiR=($WNp@)m)dT#fHuRdk`@qXuJzRt;Kj(05d!DmguEMU>qxdQ~% zn$S`*fZ#N(c-3+wQj{qWsFOTp{C>Z4WOL`_cfziPKKKT$-NyBvIDEjlmOzx9EeJb-IAVd2fDfF{6B+oB1srU$Kp+L|$EwmA=~x|EBOSIwZsau>HE+A5Hd{4vhs$~kfm^-5zrKHpA*sDKdd?V>x zzM*sSIgDD*)h}`O`c~dJ2zN01yE?l%9gfa^P-)vdRRF@NsuE6w<5DDV%t<-7Tyf^& zGu(-_a}sbg6^`n|f5N|(o7^pMBc5_%fhI*@a0bhYMZHk=c#Y4eoNmWF?iu69@DA|v zEb{Yg-DU@Vqz~oC3qUxT6+)5_kJkdmSlYSvqj#y#*j{_rTG4HMCGA}2=*~W4yqI>bfT{Vr$mMt2VTqA5rF%wrlvO zN~@%m8WN>d;C2Dc?$jgB%jROvNa6?v#&kR=hvBQFhVpIas+FCSzdEp_%h$WPug}(L z3O`L=#>kKk`GpL8EXlb2+s@UnTKvb&i{a(|zJANg5Joi2d=ha5t~q(CyrRfQ4i|=% zv3xuS9}Lj)`Dh6Z{~xwo0^|b}Iiw>RCN>3K%+*r77sv%d&Z{%%f!Y6Y)gk~G?;Ei6 zPXKUwhMzhpiFqMl969eCB`+Ub2rqABUdDtaSxZ$?p*U|maNhY5c=?xC^~{Ql>4!g{ zCv!nwHQsC@|8m;`czK9IhCD#S{pWZ_3RXS&sF2 zFwu#|$qUXEAmG2=H6LDH$-InfR;^Hlx+k9@--5c5TqDtPejUEEo#j3uM~xHKEW47Fud)0Ee*9oefVm)P*W%x?>ymL3?l#jm2-f8jn&RlgbA#P zxrIz3laVw-deS+9FLy96!-k}W)vRE$3co2-Dj6qRFCT!fBY{m>8~}cv%MHI90eM^& zDyBI&l4LylBznvDcfjY5K$#ZT!k4jhT?_G1z3j-N3Hdna1%Yu&%cX3Af! z`}f#NxuNxtEm|q%SJv%4Y^5A&!n(hQ&6JB+SL3V$P&q1N@l=k=n0RYOYaTjajhdR$ z2r*@?)Re}UBQ>RwR?7a!YJASTFUjCVLdAmEDiB&N!d&7ck?M}uN=U2_A z6_xUQYXjZgjW0Q`0Pp>m`nU@cPM6E)v+r9;ZmQy%X?mW96cFx9kQHRC0!O7}iU~;7 zAV~ry$kXhL3B_gu5eCJb@%~Fr9+-HozLKQ)$b@e;CLq)FV{R_+x=;*f5>`n-vIna! zV1hi&zR2uHxq#WgXvFyX%TDqmkgpjpz1$K!=?;@@UI`S`OfGEg4iZC1$)G#(G*SNp z-QDnt^K$6!pN)xETHQgW?8o(WN>$zETsms)4w6V%+d_BbY3+`h+sy`cbnm?4+yLGE zli_`})g4R*eq2UNwQy7riy>=wkZ{6z4(N_NTc~V&{qXJ{f7Q7My8D&6Y-N4M8WOOT zGI482NHex)O>)zWQCK4aY28aJ(HaucjLfYeAP_dm=Drtb+K&OS3b&Cm3B_&f zc+a^NrnWbBLqcI+v~s}F{!nU~yxiRMj|ydPN)e3z{GRhN^73BD9_)+8tuKS$_|Zqb zf~OM6g*D@M?>Wcd<=^at?7_ZhbO1gBZmNX5+}zgBOKC;(mki(g&LQxn-)x2K!M(=KPCC+OC(QxdPOIv|>K2`E^lkeVp?3osd$@=#C&@e65uI z6;+f}KoGBmgh-ELX4k?nUVh)%1suPA5E2UeBE>p92(y073BrMrE(cQqqx%C)i(fwh z350#oM`yl_G1(LH#A_kAO@90X=LSIj*8n6C_MRhyj-j?JV8sHPBoLwmzLHFM{1xM= z51d=T3V&_>vJ5JM(wcQrW7TAx)Y6PzC<|s3L0K@!ZGsj(Wx|YDDHCQSMp-avr9?zo z@U%?kw1hIX(DecyT1msvKtCn`>2xhui}*9fYb#v2-oo^n~-o!WGoEmohuxB%wCK-H+GX|%5%0ssG$md3e zxwSoe>bqfU$8Otg=Qy9!rPg~*qlW{Lehlj6xIYsF0nB+VAJIILyullErz%<@QYt{u z$fb&fO0X;_#)QvBV(o9qPlukn;B&2ChznH)XX=n^?x~KR;(~`3ymoHk^R7QxF!6tT z(8IOMS?}ilV`nieM=>T^XEB@z^M7-Cf*hW3Aj*H_oWqjogAUW@9L|0FRoB(S^KZ8G z+}!hto}c$T-Sfq7jyU%A7&rBF%%7NbAHp@{*uk}wG}}gkbHuURY@dC5z+pj{t>|wb za_s7Y9{PzL%v!deTG-LC)%mKe=hbtIPC5Rz=WJ(TCg4eYbS{Km8@9bH}0` zf7$`=)cQ%csZ7Ub?ZjGRut`V%e$u%w=Q_5K-+}U>=lP7^6g#kn83V(ne@p4yP^sg6 z`^NUGw>w-*edEH=AUAlMaO>v!ngQDo{CrQl){h7n=*{0-I(I|0;|F%=Yzua_#aR3C zj^>X^k;!fot**dixipkB~ z;ykzY&pO}~ZJYRxYvEfEn*)P`_0@Ne9p-F9){R#}O>Kdmd+f^{E6BF(JJ|I*cZ|X} z95&tr)T4Lp);QaUl{yR5xHd3%NY8bCt>X=%fY$!-)FU0I@e3F1F77$we8(+BB+bK4 G@BJSg`sw8W diff --git a/src/app/api/mcp/route.ts b/src/app/api/mcp/route.ts index 13aaeae..f41e712 100755 --- a/src/app/api/mcp/route.ts +++ b/src/app/api/mcp/route.ts @@ -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