From ab9fd10b5e2c710eddfd7f3345f9b2db5483044d Mon Sep 17 00:00:00 2001 From: Z User Date: Tue, 27 Oct 2026 20:23:50 +0000 Subject: [PATCH] feat: Claude Code auto-detect (.mcp.json) + 20 MCP tools + health endpoint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- .mcp.json | 10 +++ CLAUDE.md | 59 ++++++++++++++++++ db/custom.db | Bin 442368 -> 442368 bytes src/app/api/health/route.ts | 25 ++++++++ src/app/api/mcp/route.ts | 121 ++++++++++++++++++++++++++++++++++++ src/lib/api-auth.ts | 2 +- src/lib/seed.ts | 15 +++++ 7 files changed, 231 insertions(+), 1 deletion(-) create mode 100644 .mcp.json create mode 100644 CLAUDE.md create mode 100644 src/app/api/health/route.ts diff --git a/.mcp.json b/.mcp.json new file mode 100644 index 0000000..b557296 --- /dev/null +++ b/.mcp.json @@ -0,0 +1,10 @@ +{ + "mcpServers": { + "aos": { + "url": "http://localhost:3000/api/mcp", + "headers": { + "Authorization": "Bearer aos_admin_seed_token_for_claude_code_2026" + } + } + } +} diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..aafa749 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,59 @@ +# AOS — Agency Operating System + +## Project Overview +Self-hosted platform for German software agencies. CRM, Leads Finder, Projects, Licenses, Contracts, Expenses, Finance, full Accounting (SKR04, GoBD, ELSTER, DATEV), Client Portal, MCP Server, PWA. + +## Tech Stack +- **Framework**: Next.js 16 (App Router, Turbopack) +- **Language**: TypeScript 5 (strict) +- **Database**: Prisma ORM (SQLite for dev) +- **UI**: shadcn/ui + Tailwind CSS 4 (monochrome design) +- **State**: Zustand + TanStack Query patterns +- **AI**: z-ai-web-dev-sdk (server-side only) + +## Commands +```bash +bun run dev # Start dev server (port 3000) +bun run lint # ESLint check +bun run db:push # Push Prisma schema to DB +bun run db:generate # Generate Prisma Client +bun run src/lib/seed.ts # Seed database +``` + +## Architecture +- **Single deployable** (modular monolith per PRD D1) +- **Money**: integer cents, tax in basis points. No floats. +- **GoBD**: immutable after Festschreibung, corrections via Storno (new row) +- **Audit**: hash-chain on all business operations +- **Auth**: Bearer token (SHA-256 hashed at rest) with scoped permissions + +## Key Directories +``` +src/app/api/aos/ # Internal API routes +src/app/api/v1/ # Public REST API (scoped tokens) +src/app/api/mcp/ # MCP Server (JSON-RPC for Claude Code) +src/components/aos/ # All UI components +src/lib/ # Shared logic (db, format, i18n, api-auth, webhook) +prisma/ # Schema + migrations +``` + +## MCP Server +- Endpoint: `POST /api/mcp` (JSON-RPC 2.0) +- 15 tools: 7 read + 8 write +- Auth: Bearer token with `mcp:read` or `mcp:write` scope +- All write actions audit-logged with actor `mcp-agent` + +## Compliance +- GoBD: Festschreibung (SHA-256 hash), immutable Buchungssätze, 8/6 year retention +- DSGVO: Art. 15/17/20/30, consent log, data export with checksum +- E-Rechnung: ZUGFeRD/XRechnung (EN 16931) +- SKR04 Kontenrahmen, EÜR, UStVA, DATEV EXTF export +- ELSTER: UStVA XML (ERiC requires cert in production) + +## Important Rules +- No mocks in production code — real implementations only +- No `any` types — all interfaces explicitly typed +- All API routes have try/catch error handling +- All icon-only buttons have `aria-label` (WCAG 2.1 AA) +- Client Portal: no agency-internal details visible to clients +- German UI by default (i18n: DE/EN/TR) diff --git a/db/custom.db b/db/custom.db index e1922731f52372e49664966e0eaa50c9049d69fc..d097d14a23ec73b464d96c56efa437ab038ea268 100644 GIT binary patch delta 33718 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 delta 33353 zcmd7533MFQnJ?Z|)siLKvTTej3t5(IA#7vI?&{jQuuWTD0tURW7>30*)z#H|clEvz zBDXV>!Gtx+qb8XEc9=OKJAsURGGWV*yet#8glw!K6JBsa2qbUjO%}udyI*&;s&wT! z@BQC7|MTZ?3Y%Ns?|%2&?sva?Z}pKQgGY`G-ZjX4=f|*YXKA%s{ zC3BICnUI8X!Z-E8(E1G@`t`Q&GbhON%FcN~b=6=;hn_Cy34tF7Btj4fWeT~BPncq= z%cdq)E+s_&ibVfc=b3BLv?;g6~|#0b6VO+XSgP5cmm!l1W5`bVQw6JB&TF2==o; zFzg_x0Rc}4LbXCQmJg(-mP{_2`uOl-(#yB8mv8T9$T%EC6~Y%GiYsxglrp8M=Z4Rl z4+P&tf^R-NqV!JADb{&IT#z}V3_G2zl(0ISt%L)YWW0`6B8E%ca^RfJ)S%fFT(G;v zR*D+k!@iF%BBT2;j_$`(_gy$Z{`w>Q>yM_sap8(_`0ES!*B4fGbr1G;cY8b?9feXh zYvzmmM&CvsHvXXboSGjTBmmFjU!R}a6g=lj`0I1{*XKq%x`$f;x|u2FHN9Avfp`{Q zdUmz9d#LqNDy|!uf1uMPvc*oo_aml=lJWx+kQAS3axJp z^?KL$b?9a~n~G~0-Pjm4x9=?GO9qnta_T=04imC}8|m>1OFO3C%`7!QQ`q?GISajO zmUa}i>c+egF6l;idsb<+y|Q9~cU>R06^dFWtmUDRSMAo3`Ch)SBc3UnFq(~-QaWVh z_ZrnA_=_L0UzxHFt@J|2uS|Wd`G=RMUOKdi{GXSn79AcWe|TwX{P5c5AFe&@BY${t z>h{AU4jSsd}V=Wu{wU#0yn0UJ}x@3M(86WEN+{CxZB_K}`a;X@XljXRH+ zsn)7hC6K5jVo3kn#>nFNL6u+M=h52q`JB(E76^SNnW>6NxcV12WCi3 zAk7g{zg7xM^+GcMezkg$QZOr~9?$81O-K5VH+p)2ew3!q68dN|uIEB#1q0~CwH-=L zH|upVQqUsJfq%TwzmW6~g41XE*P;Iap*Q7fLQ)iY>iYWd)a$jKvL20Pq+Hw=NHzOE zcGChNUjyWu+~f)&Pen_*zZfup{35xY4W)ANQr<5Iko8+WG=F|@l`zof*)>BNf}U}% zDAbDiTBxW_{iJ@Wl$0V>KAVWiA*B4<4|dNFZXN?lX@*h*N}M#5YE;b0dT8qEn=V$$ zk$f?iN{MnH*CcIpbpgYh!O)a8ZBB#Wvq`#qYR%c6K2WR>x^eUmx zmi%d-7FDL+{oo-bKdU=g_ChXSEfGuAzqHxv}8OUs^yxF$v3<#^t*nL9~hXSgjqnW63S2{ zA>|{*Fieb>%Xtx^Vjv%`mTE!7;d@8KiA?IANI9Y)S_Ojso>};icyw}+ZZanGs<3+QYRhO6c zI9n+nTsXVca=#&SuJ0x*e^wGjusyDN}c59q0%yL!NY|9ki`F(+=7i zooNTQI;~B2W*sJFQLU__A2UkYY9798Wj9GqGb9r-h&|OKNhXAl3D0o*d_k@q6zgw5PD&Qw@z>5>%Fq3PZw4dVJeitG1{`7lET*9=_b;oHi{K#=oc zDic*IWh0VSY&7`)vn=}AM|WNfXzwE0yLFhZg8hA$dREPV!F1`v^duxGs^Bk}_M(F} z)9W7-9bieAzk>b!s%NPi%p~KC}N~F?Sp=^UuDxG@l z@l6h}n_o(1>Mxs`f zpef-}HBc@^&W!feC$=@+C_DPG8E9R+mDZzLWtC#oQ`#`VwjO zxn%D2r3@uRr!S#nxPf8Q|3i)$`?OGTNUc)iN9b_Teg35%U4-B(Kvub99Xx?t1W|*muye#c| zFx7O|KOyL!D)Rty2geLim>5D!>14hfD;I4{kkTx#o&U=DlPmT&G5?5|e~fnm<|dBm zb74YW!DEJ93ssD2xgn+4(6$3p1a7OZp`(ADN3$1HXA1$hXMHJMsDE=4m^tkoACZ>QdVWprOYyMpQk%NCc(udMdK@-kYF*$CQGfL8#p=XlNij@mr)~97t ze#NDm1hit1tR{;g$uDPIx*@FzG8jYm#dO6mUAl=A>ElF{j`4|5E>m+U2b%d9A&yu0 zS~l$~xpYHXF~X*!K7YMdt+{kVNztX78L5r6#O6{CC93Jg%#+PbC=b1&4;gh)uP3s0 zWTjIp>+KbcdfiiRFwBYH^ghjW5Oqw28WgZ^=3DmFT zLIvBO=#0{83^DR`pL+%H|L$`=%ngX#*?cy7ynw%BXkdt8U|MkCX8|8GxllUqFJ{|O zkj~_+B`X1c{Vs<2^wXcYm-zr9H%HOY=;a9h(t&mCEPPmI@|fGD3{^E!;dY)feDF#AMGrw z$dH5U_AHa3hGIQ>Eu&llwtE(i`m9Fh!Ue(LKF{bV+t;>R#6!D+f?@!wFm@U~2j14} zm>}eo6H5?p$p;qz+*W{t+`vv?VAMW8nvohW`Zc4{P7EknTHm^kksNT7^O3v0Rm^}J z4y`Cq$X6vf3YA>U>oYXmj_a8Kfm@8+EzWcU+_-SNY&2__s#2> zH3Y5~;d*cC0=SDjF77hm8h#9+=?WhUGo#JG zbneH<7|td$vRTjBc$6_&cbwu@SkK(R3=`0W$m7C>rxQguu*#v}4Ai>(n2`ypPtcRK z_7ac=RSq&L8S?_fTTto%I2Aly6`P-nf2^JW;x-p zh1p=d0D6Fc&R7kCHdpzwVJ5Yly?mPklt08=KzN*wJkGCj0Jj75;v8}jc*N{9n@dUc zsvfrGNcolZ)k6$V*lWQx=COcxkl;D=LhvvHjrwX;*l`J+8E?~JW*folM)tZJUIy^? z5IsAF3$E(NOfeJ4`x8c@erCL{9cH%92fSuL?3%uj`7kD4bR2Nj{b;Xt*8ONFb=Li` z)#Y=pqyk)M((Oa8tb%e0S5`q8xGSrmEY%f^XAEz$WqGb>OqyLL>44&^2dbl;Jx4vg zAMYJo^!TD1PAEG2n0 zLSO(X#`U^btGAOmDzNWQm7?*?_%0;Q`Bg0<3!#XgH^Skt#*1Our~3t;F3CK2s-cH9K^H<{c#99|VM&6NB#hJ0s>XIG zt*Ef4Fu-&);Osy%i85iiBw(bE)qDlH5=u?%yK;R075m2d>#Spw9Sa9+(}AqcT`97x zN12hyzy8H4WK#1*{Jv1wkQGHRq_FCjv04F9Q&5gm~h5}U}B{(@aIN;(Jsu}`@^BAmpa^X^)@O$M|v{rjhlC91?*IiI-(9WJnQ(zkwGa_vAwX zpJM2SB=|&L&?BG#U&PSB1{5$0UJpqTe<%= zenkx!J{2l6VI>^mbyFoUKbS>`h%ufC2Phrsp(Az+AL2)>B!mnFX*4g|s>LzDu7 zChNKy3Hg1J9tiu?h{p2<*a{yoWF72*k7(#v%jd&1bkaM*T#geU?dGn)M7f(QFj02y z3QUw$xdIa?Zi8EMuE0bIaQZpst384pm~c5+kqSv89J5nfdMN3q9!;gJhhAltT?DiK zY8?OSXh%0xhdqv?XC{^MjX*XntV86sdS5o~3Y zrG`m-1=ng~M#+HH*t-EH?L_u4hY)N?V*qT_#aRHraE+FP7+(|$b~)OlG-ti|8nc2d z2$mz*@|j7IOffD^x@?cqip%yWF}rM!k{fM%E=jm-j~d$aJIv9AI4Qz}FAAhg7^+3# zjFK!`)?b+MYv&B;3$9}R;ltr|tGBcwBB{x(K@qHJu$?`@9RmYFwxgSwIRI)Bcua(3 zIW6$1f^H3+X4VsUx+cf`PfXQT`;BTg4oBk<`Ml<{)X@`M*S8AbySio;g(lQqMV@dU z6=We2D%PwMrpG5{Wt)J%t;4TBJ&cTtN6=)zg4@H9Snxev=-0o zHZx{>tCs4I8(}^zto_kyIP->6Dlwo|5VH@%qQ$rj(z!gP?aAd}lsUK@j8d-4!JzdL zw_;o#Mv2wsV3bUz7qhEY;4%i*RAP~cs~W2Z1fNm6V%NAIN+aX&|LwvYUH{g}q0&?e zGM#uvOEJf9PVELygHq}m=7lHcrP%T3P)VDb&qiP8S-~;bLhtDCa4gr=)fFU*o5|*p z8CaEzS;B!*75OS}RvjFOt?)}{Z{a*d!7L;|ap=G0>EGsduVGIgpO@n9TyCeORLKcn z-bkM8#71k?I6HuHfc3qI8zE^a9L*Xf(ZNM~c|_&7$VF2I4j#O}@XSkfJbT3Efm)>@ z597^eqi@nnq%#Odvz^ju3J2DsB5souvV2z3N>26oZQ)R{#4H>6aLH&X=b_)c4$8SS z@!2&!=_B(}y?;7xGuB-0^@Hr~iazqRq4~7-ZDf~6!81)5fQZ{m(U2L5MblY_aN4y( zD)$l+R;mFe~E_E&p`%_47Rcuy|g|^VAmX=KSW(UoY%GW#(AmkXV%&yrWTOc`?}_{F}a)}s^|k%bbz6D zcXy+qHdO(ZBua#@WX()U$+9uS7gUK`XGy4-lGWn2ll(GS1Fq!fRd`Mey?Y+J`yFtV zukD=SZE)7s1+oRYBNJIW0#P38ia?Y%xgro{<*o=s8JjBtfe~EdwgOiKqSWS!K$P&N zx3P~jYcXUzWg^#@re;g|kR5gKES)PfS+DP5$8d*(j51GX67g&*s^#0s5H)=3&VB51 z>$1JuvXEQz?!8POAABL^$I*4mhom-Ta-hBVGX~4wyw<(p0X;O zOva?Wm{$~2469e~x`Iq%5=>&M9iQeXqTCc?1IzM%YWSJs-~0R*{pe>~n@{VzF?JBa zAvu+Btsc$SO*2tZ_wJmq!J#J5@_Vp5_t8RYIL?mto?r4a4?g6 zw|luW-s%OCcnK5aGGEt91ufnh0-1+lZ|01>yb))GW#x38&uV@{|qLx%KE%PpJZT|%eoLv#> zOR~b+&%FZD`2Mmec-Q!}vX2j~cUk$WWLWIS)^-ax+ zmN&(2=}p!^C0kbfV^bw^yO-wfuVUSvVh6VqtAhDV!Wwa?67%yVF*kAb)ply+vlUOL z+*-K|FOFExcY4pZ-b=B4-RV=!zNB_vGLA^Lu1mK@MEb&FvM!n7S~?$g_GPCZ^rUA! zlx8>2hqSBNfruU42pW^H1F3NXdvktH<0P30la+*ZWxf_p*F_`i zw2OAl6?mIf);X2|Ig(L*X5MgLvumNtAnS@vIU8BBMsKlJzN4 z(mH($yL09GU0c&|Q!bXOX;~=7m5nC(bx3}lb@*6|9D0+{Xv1YWok{C)>+WOhLEnAX z+WgRK1xP~8JeGICHLgX#Yps>HHUYCB8W~NoYE;81Jy){!-^yM#2}agNq_R4H&Cv3x znroofAn-LG>{=FF(?2@SvMjh9taaewIP9ARQOmf7FY{_d@Yke?16SH%#jk`)M&8_4 zESlEfN7<3x%6GTAq;WO&ado3_9&|Fi>Hs9Eb9Pb%8*Of&Xlkw;&sD6y{wTYz|8uX* z31|KKqwLVxB<4XGN5TS8sFu)@g{(Dr8+%#*(7SH3?a0M;>qED-Lo9^ z{&2}*LV_5{8|iS$h!?C+-N_34y~k`O=$g|?*RU?c?k}9ai#>r+l6HOP2?5%VohJlx zl$*LDEah{q2n%YNbo+=a#8SrX3bC_I*yoC{l%={N?2Nr>ErWA~Skmk=C})VxQbyp6 zuvtoH)5qCA;l^JMCNBl^{!}beQ(}D1>iq({4L7YQ16}TU5EqXJae_N|nUT>(t~R61 zJDw&g&}^*jI4ij3@}Z^uu$Roh`9RNTdoLMoRQeQ*c4nm>F(A@jb?H@h_~CummEam+ zv_Lkht!KW#u8%=bpyO#5oaZ}mQUe}fp?z?omWpRab{gq;9{W!=pN(Bm)>w03sBaLq zw2_OMiyd%x-Y4VIP6^jzQaF{hv@fz3UqO1KVy*3y)Ew`H`Zny>0UL?^&mX+XOpk0g z^rX?`qFIZKT7Ui`i+iX!(7cSwQ%sGAK<~5GeTfauU3l#B?KSh-$fe*#85~Wd`E2Z9 z$(RNQhMQyZI3){NgN(~WwH~d({luj8)R)*x_mDAFAcev?)V`r)j|o;p?h~h77h{o` z5^8fVNwcp1a%(t{NywOjM`DFqG?U3#$G*&7G7i1bvBtZ6mT(T?a1OOM251v>It^_z zPN$)D>GT;|>`tG_fy_3zh30gc95u-4C)pd8kcDp!BD8{qG1XT}sPRaqB~p4Q)~(O8 z|GKR*ez1{U(ghb>hL2uu?`|UsF4IlPSMybM`y9v|DF5vT>?7NT-aXjZxEN4|MjJcY zD6rB|n&mORln=|5=$U!B<%eu;+wcY^ zAGr`*ud)cv<_wJ;aABaK<;7UIR8Z^$c@B)zFR+`pU2^Ba#?3v*$ay>eR3Owf`+`1NM)=sBU?Bgu3)03rZQ3@Qq9{^nFHm=Y;D`Q!okLx z1%NWJs?y1v$Oc0u!##H64d7tsjNEe#E@}M!^lVxUrDqV zRC8eb>?iDn+a7!1U?bm6q_XPhm2DK*9+Rsi5w7w+2n;oQH*XG91q4IWC} zz)Is%7fP9+l(Q9HD1_T~ONYeEe#REJSr;8_)aD_|(d*kNFv-c77%6o)1R7V{dZwb> zJukA41`j=Pu+a@4KLG{R+88jK$yj$Uh4T5lKhl;g9mxLtBD-hXy~hq7h5apJwMTPp z6qwg!tOtn&qgso?QIlq^g1ENZv#ncRV!yR*{`Cisc6iBn#*Uh86ct{^>U%Al4d=os zd+|93%Iz<+-$8FanDj%Xya^zGO2iAUW@n=cqvAyh~16Clyx=ao#1*QL({! zrZh+Syeng*eA$&TLTk%QCtGgnOc`^O`#DoaY<0PnGh@tA?&8cCbCe^v?vm#y3wO}@A;?a~xg ze`~n|U2r4UP0qGH^JjKtcjB`-=J)x72@B>AW3uq!qi1{1hP@F_(1hLG zj?u;fSbNUgO3Vwu5ypY6R?U@SVZ*_ZT@j=gAT~$Tdh1F}yK-dD)v(*oO1+=!fp2O+ zstHf1qUNlZ{=!~G>XJv0vm=&znjO4=Bn6Nz2hemQwNlnp>RAU9c1W3{4h&cA#NK<4 z0sLy^yYPghGG{%1nmu?JI=Kluxv6mwN@*~B&p|k(?eWYSey#)_hY4+{Y6_WBxa{b; zUCx4+W)+FyTFa*H-ly$>ed|4TH|ev2eO4L<)X+$uMzhbQE|KT5fYuCmnyv@(KGV_X ze^J#$C5(dx$WbX>X2apCFRK*34R1O#kL&}|*X6U!v za_iH}xB(=D2~5R8O)?GlAL}K%usla+r4H+XPgu`Ev*mZ(pcU@t za9=79z8=8%B?zTtDV?zA8(PrTKlXEY%_k3&NT9ih5}^!U$hAA8e9`(#KequH6oDpB z(vQ3mfK%mV+bibi=j!DS8cY;Y#Y#3+OxnrD95fFt=eA-$gas^7=&6Lt^Qs+`Xcx5x z2e@;Q1}cpK+{TeoAyuy!cG#GMCOyF6o>?Bs3<2~3p{`3|U(B}qIcUB+zzNt7p~0|g z7-hYXiP={_=Ab$M9Bv$Gz;gpQlh@!5PswMt(>BV~t-m>klL$=?R&7yLDSr`g5%rW;K`R%tRUuV!kI~|!z(xiX)4eSX78nJ%7D{{_SqY{ z8|#}ZIN7FAFk6quVzOLM7thF=wRVWR0BOn)3NbgY)Kpay{mC=a+%?2)Y15#~`Lj_m zAL85jJQbg;KMipKq#;e1cW29$NL(-3x4Gz8Y~8rBrP2Zk`o!gGsZ2mq%I&O_ir>~h zuWYH5&|pDPtt;RRT3gmM&5B`e6Y>VN1f^LOj2l`h8EcyxO;a0g=`;s4=yG8>77`0^ z&bpPoQ#2=kI?T<(uN=`)c+m0Po;($A&vCX=L3O~{N`=qm&Q^$#lkOnd?`)>R;WB3{ z75MsQx4J^y+0NFPU^dx`W=oyTRNQiA>U4y1X6o4Libc*$9W**C>P)&)bt=HPGIi4G z3MH;goeCPROq~i3txUanWDBE*D-^ggb;{{onL4z(-P@I^Q;zJ))XS4V?DAPxor>~K zXUdYJ{Lq!MP#)(>S!~~06DhQu%ayZGZZz%V&YnL?QWjVdsW?FuO;IwcrN|pEV48WX z`LvV^IB7eiUw2~4cW14;7Y;Ob40^^ldxjl*wRtF-s95jH)T=_wAJD({5N5-G13B?A z>q{4KDoNw+K%6_QcQ4?Uol6*iWtEB+mM&_(Lf9XJV@J$!IE!?QIezNyA04ySZ|1T) zXDA!qdA&i{E*|%+54u?(NLVZ@6e2MpS_{8-9L55LVBmo~t=BelJIGjWM;>l3cj8z^ zJ!6s^2$pgxPNqe@5UuC=_}kCnngck3mB%qFyoHkqM=hMjI9#X)j>dM3?e+}vjztFn z!onM$k7wgyNrMA!SRMzCKCpAoG3(VWT#9gX8*+5p&2V!bq&w~zQ{5av@kPa{Iu3U# zbYEKd&MjC~1{|m@AG2=T%58PvOu`L$z*+BsEy<1V7f2ywlV3lr*RJ zbL$q7jq*I~PpCNmhK-7%!*NaP!b2SSpzg%SP)8rLGKaVo_$t&`R4jKX2|r&+ih1k) zL);j*e%GDWw-0eE&#IhmhJ(A2@NW3zif53i;s63W5U0<(E}OCpV$wupkI>2yPUag~LJ! zE<@?U_14NSamy|^$Q*xh3IMrAWcicAvM?t7f*hNg34t+X?ehDhXl9=&X z8@|9Tzl0Yf0ZG+GNzlTI7!C`HUo`vy)erZ-dU_9~p}9FkH>L;|tu1$$O8z+2Z0nxEU`r<>KZ79bKr_N_D+1?3}o2*VQAts^qiE z<5wmjbQdL6kwqgA2t^FPKLA&)1YT6(24jeqg>WdMghWx0bqy{a2Q*#rN8lzS+_MTr zw16+NSO+;0ro;%`Yt;CrlqO+Y5JGN~vp0{ffLmh{aCUUeA)q3pNIFhLTA~!G7ek@z z;Pl0glvWv;h=QSvtlhI?|2kM!8n811cfSOk4~ejpl>{ST2#O?#T1eDGvZDDT2HXi# zK(d+w+Y1p*?TZ9-0X}XUk-{i2BcKGr5v%jd+!ky2OWc*+_a6HdO6|?IGtn00bS7Gx zPG`bamz15(L=TiW6W8;or+3q$H2lYNa`BVg6FtNYN{}h*7+|t;G8z`+G3%F4b2}$x z(Wi-m20KHl5Hdm%9|`mDUmvf)hjhV<4s-TZa*USkS{@Iq)?ZNK8%0`bif4KT5Xgd!Pe2&4Zon4d?_JTEM^NAY}ynv&17L?A*JcoXP)79Op1X3 zFDU`=jt~zwNQomUhNSp(4L-c9>T(!vY)0Tx34G8!AR0n=A+|Kh*dT>Kh&`StX3+?RXfa|+z0SV}fd#g-_h ziDJ9DFYzq;W6%2wUtI8)1$*Z|(EXp?mq6t5cUza8;?5?E#UiA;I|MSLLxV9DCYRra*u&!fSp%%z$u}syj;LLH~ z{8tEHgpX|n>i(h{N8s0*&&GPVwiQ%zv*96~4`Br&oyq3vvS=4n=J?de-M<0hNV*Pl zjz0yzIpBi7o?Pcw4-R#Vj_q&=2iDdhELK3Nyv~OMcClg(c=$ZZZtkf!UbJp~i&L+Y z;0Rt!kR!fi4EuSt`8>LsTLe%ix`g_w#+}r#x842qbT*fX$&@hC?dKM<%dNzCV_D=8P-T=J&3w-w% z$JWBlnc(H)Lk;1Mi35yhw0q{eWJS1Jq059k7mkMDYe?ynA70?S2S1}xg!-wDOSlMM zjKKNBkW28S71w16HC75l6xAhos8Z<73lnl&sY-R%QLiEt8uVuUBPoP3aHP^Dcxau} zaa|OP*8*|BZn^|diO3~*N**r3Q^O@Oxnp6;)4O%iM|xiD;o*G56sdT#v~3onwU_SBe%u2c*bMnui?m14^J+y?KteR@{#!@sb};H&XbJ}gv(V%qxU z2Jg<`y~plhUfxnTaSv+dla2FW-)_z5u1)K?bsicYm{oBnF%XIyb**4n(Q)r^Uxi%I zSE6}8uay$vN*07vNn4*B_sU!EyY?RD>D!m=ZsLEUv5o`$q1{_oapQB~!$hN^1q)El zP1UWQjoy|0iH+V1g8QF$j}N=D-~QQP(%5o_3wMUBNqb7ebJ7^LHMLC>tXuVc)An^3OKZedv%$5q#Cb=l+ zOdIf#ixwwW?)9!G8247ZEew`m9B^QOV{4cjq%yH&J(l~?{Wh2sRQhpKp=#um_^8` zI(z{)?{|X0(!>eE{8u2jp_rGlMJ-;@oFGuXX@YoxlfSnw2+|sL22EO1&e;lVe3k}W z7D~z1Wuee|iRKcU%S0)8xJ;C^x&}IZqgTTPNx=_1H7i(hL<`r9npIAF1Kftb`>n5} zy@Q)!@DE_Dd!TWaw=cNn+99~SIndqJ-NkWT1E5yhN)ZVszIr+#$x=nP=4HGWo|mlM z&m7(`F9qjV;aETX5&m^t0sl0Df7(iByjZ>|kfkq9hgmse7PU&q`c%dXe*e~2?zf)J zG&wqfL?;>^7C4g5$Pp~jHJlnFrEI}pPWr7e)4SoC^r`zjr~YDd1b>8o9UR%;1fY>I)*M~*)9*)Ry}mDVV(hBErnkS)5A^-5xx2WYwA9)4>4|l@AjNW+Si_v94j-v0L~1!F zh93**4_Jrq_X>oMug74e3S-&j#t;AlGaDo3tLQmB@5JEos`mrdbN72UaDZ|0{rA}- z9_gUv=+Z4EA(w7Rt845o-BP1+>b3yreaYPw|oCCf(pcM-7=92BZ7oG*=gD!Fr$T2m7w8)w7k2sy91 z=?S;ry?6-#jQ0;Z`X>OG`_$pn0%lb%TL-@GT}|Hp&c4O)_GZ`HM5~y|_@qo8&Lz~X z`@im8N8bMS#k~|Z35qzQ`;C@8XU?NC_0Hav*L=k9$X&Z+Fi5HkRo88Q88eRLQ!z z_4YUR!q=9-upDksAhR}B*OK*AG#X9Et#3WvbeeDMfX^v`Ivp;CZ(|t@l3J}6FGrzX z?Yk5_#mI!0c6PrWst7f4aDU(K(gGQw_wcE8(1{i z^YY0>&v}IfHzizK;nf<7S@TE zya8hMKd@eT$-AQ6ALbec92xKx#DKFu8FQYen@<~-gbJ7Q+YNMo*S_o>BO *q(ooqk?2rDvRgFIWk(QaU~FQMw`Xf)fKPr1WbAMlb&3iHSXRm5$=W)gcY;~EsGd2_{6LcoNManXq zp@Fns;?|=xHo%#hSyFe#h9V`S>36&vaaCJ{gFzw880xuL6t-Sd){fKOAerTUauMVg z?2CSuML7NgKWYitQAm>bqM=CE=T3W9;oFx$Ho-0#pZzvQhlpmV(UK@ze>m+u zcMV|wcqe2N?2^$z_|~|s7C6@v!u+5Z&t~DL7_1B5^9~cA`tdeMCfFszgI(^IF+!9R z89)4{Pi@|>{nB=$63-W*U<98oiNZ%mMOF4;Zt$a>kYLQ}4$f|bFgHjA{C>k%F9L#i z1!PD@5;MIfhV}A$-fl9!AMJzmf?c9G$44MALlBeETo~kM^}OFqm0vgv`2@S<8t2<^ z93+GpLN;1d)M{C=4!!SH3HA?l$S2s|14GtOhL7tc;CB~*y0cE zU$mih=bYncT{-7CY<20zWyiC1QFhrev`&Ux(svm$C0UmtQ$lmuF(o9I9nT15UP~&| zi#uMxb1y|Ws1d?sTZf-)Qshk7I<>MRx9sJAKk-xM?|-%Z+Ml8&{H*!3vcnztEkh*3 zARjK}6RMgiTAvv1I7mtoO@g0Jk90iUyOqqFR1YrRNUIBo%)d;p;^|fX32M^47dJit zyGg8P7t1g~Z-?6Gvkf1Pr-U%iQByVeT~akZ?^GeC`6c;+QaMqH)J^_F_S-n;3MRmXp}XmXdg z(Zl@T9tiOwO6dQ^DUM0!F%Ax4{%;OmkaHL8P8d@6|K<^ifmvrHW{yZq|GcAfWZ^ZQ z-cR&i)B9-etG!iF-LBrr5pEaLQpub<9vzMs=G?OIuI6^z2xr)J;8t-L*{{snHaNev zZ@{kNc6YbTqA!g`rgb#0RtRM6Y!;cBI_X`jLCjtBCpHBW&t@C>P8-BDp-EG_fxHj-*udGa`}nQcUkzg)rtRsE zIjd8Im4JKG_2jmH=-kYaz2Fx0Emy$zC${QCLydK}k6p=lhMjxCkixrnWluixna&~H zN`3=7f8&->_;AGL>w$dqwu>~zvuZXuE+*_<+ml^i=zJUX@Jl5B<@7_HNAbHF^w#o8 Q>+79|Q7+A+SHJpy07VfFumAu6 diff --git a/src/app/api/health/route.ts b/src/app/api/health/route.ts new file mode 100644 index 0000000..a55b845 --- /dev/null +++ b/src/app/api/health/route.ts @@ -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 }) + } +} diff --git a/src/app/api/mcp/route.ts b/src/app/api/mcp/route.ts index c4a4403..13aaeae 100755 --- a/src/app/api/mcp/route.ts +++ b/src/app/api/mcp/route.ts @@ -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', diff --git a/src/lib/api-auth.ts b/src/lib/api-auth.ts index b153277..4214767 100755 --- a/src/lib/api-auth.ts +++ b/src/lib/api-auth.ts @@ -17,7 +17,7 @@ export async function verifyToken(authHeader: string | null): Promise