{"_id":"@ekwo-ai/mcp","_rev":"7-eb76d5e6ce57e9cfdfa6665f6c3cb24d","name":"@ekwo-ai/mcp","dist-tags":{"latest":"0.9.0"},"versions":{"0.4.0":{"name":"@ekwo-ai/mcp","version":"0.4.0","keywords":["mcp","model-context-protocol","accounting","supabase","postgres","bookkeeping","ekwo"],"author":{"name":"Ekwo AI"},"license":"AGPL-3.0-only","_id":"@ekwo-ai/mcp@0.4.0","maintainers":[{"name":"ekwo","email":"contact@ekwo.ai"}],"homepage":"https://github.com/Ekwo-ai/ekwo-os#readme","bugs":{"url":"https://github.com/Ekwo-ai/ekwo-os/issues"},"bin":{"ekwo-mcp":"dist/bin.js"},"dist":{"shasum":"36dfc7486f998f258b600791551f890c4c739795","tarball":"https://registry.npmjs.org/@ekwo-ai/mcp/-/mcp-0.4.0.tgz","fileCount":58,"integrity":"sha512-Hd1Pz6WHwXWydvivIZxddsYRWW46BdIU/kC4N6LkybHZh29iBtlWtd6yGNAph1vM72euRGSJKnDDBEB0oPXJjA==","signatures":[{"sig":"MEUCIQDWg711TaPgz+5jrKfMSDVmKc1KSNyeZ/KTERRY9XHvbgIgJq1OnFz0i6v/TZU/Gy9M+nAg+avxr2PmhwLgVpPvLN0=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":412058},"ekwo":{"schemaMin":"0.4.0"},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":">=20"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"0e5696dbd19f28aa4daa8ccc34427f0e41ab7843","scripts":{"build":"tsc -p tsconfig.build.json && node scripts/chmod-bin.mjs","typecheck":"tsc -p tsconfig.json --noEmit"},"_npmUser":{"name":"ekwo","email":"contact@ekwo.ai"},"repository":{"url":"git+https://github.com/Ekwo-ai/ekwo-os.git","type":"git","directory":"packages/mcp"},"_npmVersion":"11.13.0","description":"MCP server for Ekwo OS: let an AI assistant keep the books, under the user's own row level security.","directories":{},"_nodeVersion":"24.17.0","dependencies":{"zod":"^4.6.2","@ekwo-ai/fec":"^0.4.0","@ekwo-ai/coda":"^0.4.0","@ekwo-ai/core":"^0.4.0","@ekwo-ai/camt053":"^0.4.0","@ekwo-ai/cfonb120":"^0.4.0","@supabase/supabase-js":"^2.58.0","@modelcontextprotocol/sdk":"^1.30.0"},"_hasShrinkwrap":false,"peerDependencies":{"postgres":"^3.4.5"},"peerDependenciesMeta":{"postgres":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/mcp_0.4.0_1789747681045_0.9117076197192047","host":"s3://npm-registry-packages-npm-production"}},"0.4.1":{"name":"@ekwo-ai/mcp","version":"0.4.1","keywords":["mcp","model-context-protocol","accounting","supabase","postgres","bookkeeping","ekwo"],"author":{"name":"Ekwo AI"},"license":"AGPL-3.0-only","_id":"@ekwo-ai/mcp@0.4.1","maintainers":[{"name":"ekwo","email":"contact@ekwo.ai"}],"homepage":"https://github.com/Ekwo-ai/ekwo-os#readme","bugs":{"url":"https://github.com/Ekwo-ai/ekwo-os/issues"},"bin":{"ekwo-mcp":"dist/bin.js"},"dist":{"shasum":"47e5cdb1e2a56fe3326d18635e0479acc506f9d3","tarball":"https://registry.npmjs.org/@ekwo-ai/mcp/-/mcp-0.4.1.tgz","fileCount":58,"integrity":"sha512-moML9sMveSc5dGNnZj6cgW61ntMj7Csa86bWwT8XyKzFJLggdIdceoLTpLbBFONhOv4HIxfk+inPP/t85T168g==","signatures":[{"sig":"MEQCIATOBhFTGyKZy2BSyl3pZIxG0VqQnmoi2BZjWqHmO7AlAiARxDtau1qe3tCkvxHHAH1cmMLoA1WzzpR8LUl53YnEaQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEUCIQCFSpRBnj9Izg08sNrmqoCzBBM81Db9K69vJ7xQgqLi5gIgRRBpti1A5tfQt0bjMpda4mOupzEpisDGYpOh7//qNqM=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":412067},"ekwo":{"schemaMin":"0.4.0"},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":">=20"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"d373e0e8fc47232edf2070d82bca42454edeeb68","scripts":{"build":"tsc -p tsconfig.build.json && node scripts/chmod-bin.mjs","typecheck":"tsc -p tsconfig.json --noEmit"},"_npmUser":{"name":"ekwo","email":"contact@ekwo.ai"},"repository":{"url":"git+https://github.com/Ekwo-ai/ekwo-os.git","type":"git","directory":"packages/mcp"},"_npmVersion":"11.13.0","description":"MCP server for Ekwo OS: let an AI assistant keep the books, under the user's own row level security.","directories":{},"_nodeVersion":"24.17.0","dependencies":{"zod":"^4.6.2","@ekwo-ai/fec":"^0.4.0","@ekwo-ai/coda":"^0.4.0","@ekwo-ai/core":"^0.4.0","@ekwo-ai/camt053":"^0.4.0","@ekwo-ai/cfonb120":"^0.4.0","@supabase/supabase-js":"^2.58.0","@modelcontextprotocol/sdk":"^1.30.0"},"_hasShrinkwrap":false,"peerDependencies":{"postgres":"^3.4.5"},"peerDependenciesMeta":{"postgres":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/mcp_0.4.1_1789754048456_0.16524002653550074","host":"s3://npm-registry-packages-npm-production"}},"0.5.0":{"name":"@ekwo-ai/mcp","version":"0.5.0","keywords":["mcp","model-context-protocol","accounting","supabase","postgres","bookkeeping","ekwo"],"author":{"name":"Ekwo AI"},"license":"AGPL-3.0-only","_id":"@ekwo-ai/mcp@0.5.0","maintainers":[{"name":"ekwo","email":"contact@ekwo.ai"}],"homepage":"https://github.com/Ekwo-ai/ekwo-os#readme","bugs":{"url":"https://github.com/Ekwo-ai/ekwo-os/issues"},"bin":{"ekwo-mcp":"dist/bin.js"},"dist":{"shasum":"f6e4510e3606947edbdcd1edfae3a51952ccae92","tarball":"https://registry.npmjs.org/@ekwo-ai/mcp/-/mcp-0.5.0.tgz","fileCount":54,"integrity":"sha512-wujQGgK7TCow7bWnbH8B5an69Q0cWIB1j6gaYNR8PGCu0Mc6lvolHtE8UqEaMome4UceduLtiAQ60XIKPEL5qQ==","signatures":[{"sig":"MEUCIQDIzkCZksJCi40PxhSZ6n9Fq/KmLDnoP3LoJLVqccRNGQIgeveotkULXyc25uTRRoZgfdgiMiwuMUTSqSl5hv9KGyg=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEUCIQCWdL+e3KRh4Ix7MuDURX36KVw6Lot4OpVwjbiix5O5BwIgVX5rtmhT4Af/Ta2DjSlH85hKEVjEbX18ELHxmrzFo7E=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":406552},"ekwo":{"schemaMin":"0.5.0"},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":">=20"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"f314e1b9eb2c267915094fe858d4281908302df7","mcpName":"ai.ekwo/mcp","scripts":{"build":"tsc -p tsconfig.build.json && node scripts/chmod-bin.mjs","typecheck":"tsc -p tsconfig.json --noEmit"},"_npmUser":{"name":"ekwo","email":"contact@ekwo.ai"},"repository":{"url":"git+https://github.com/Ekwo-ai/ekwo-os.git","type":"git","directory":"packages/mcp"},"_npmVersion":"10.9.2","description":"MCP server for Ekwo OS: let an AI assistant work on the books, under the user's own row level security.","directories":{},"_nodeVersion":"22.17.1","dependencies":{"zod":"^4.6.2","@ekwo-ai/fec":"^0.5.0","@ekwo-ai/coda":"^0.5.0","@ekwo-ai/core":"^0.5.0","@ekwo-ai/camt053":"^0.5.0","@ekwo-ai/cfonb120":"^0.5.0","@supabase/supabase-js":"^2.58.0","@modelcontextprotocol/sdk":"^1.30.0"},"_hasShrinkwrap":false,"peerDependencies":{"postgres":"^3.4.5"},"peerDependenciesMeta":{"postgres":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/mcp_0.5.0_1790063787523_0.5387463663318599","host":"s3://npm-registry-packages-npm-production"}},"0.6.0":{"name":"@ekwo-ai/mcp","version":"0.6.0","keywords":["mcp","model-context-protocol","accounting","supabase","postgres","bookkeeping","ekwo"],"author":{"name":"Ekwo AI"},"license":"AGPL-3.0-only","_id":"@ekwo-ai/mcp@0.6.0","maintainers":[{"name":"ekwo","email":"contact@ekwo.ai"}],"homepage":"https://github.com/Ekwo-ai/ekwo-os#readme","bugs":{"url":"https://github.com/Ekwo-ai/ekwo-os/issues"},"bin":{"ekwo-mcp":"dist/bin.js"},"dist":{"shasum":"79554bd1ad8d449d6735603ecea301de6230c9bf","tarball":"https://registry.npmjs.org/@ekwo-ai/mcp/-/mcp-0.6.0.tgz","fileCount":58,"integrity":"sha512-eIK0yrw4a5wRttkxRVjbYDo2/PHsRKrzQkxvmw4RHo/TxGK4c1tW8KOWeZy/egjQ2ypgQcnnbckuQf5k+ikMwQ==","signatures":[{"sig":"MEUCIQD9h0GbpWlHuKia2V7z2EYSRsQpk6xCVcYN2KTNuQIrEgIgbqtSgysZFSi0w7ZUc7F9Mw7qfgivHE+ZLjqK1AOU+PM=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEQCIALirVuVvCp9pbQkx9ivyS0yT2+qz0WGSyPnIOtSqJ/bAiANJgYDy4BApFHnXVPlw/T2rKstPPNpuR3xbBTW8l6oEQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":416464},"ekwo":{"schemaMin":"0.6.0"},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":">=20"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"23962e3e2f574bdce7bcb81231fa577e17bc36aa","mcpName":"ai.ekwo/mcp","scripts":{"build":"tsc -p tsconfig.build.json && node scripts/chmod-bin.mjs","typecheck":"tsc -p tsconfig.json --noEmit"},"_npmUser":{"name":"ekwo","email":"contact@ekwo.ai"},"repository":{"url":"git+https://github.com/Ekwo-ai/ekwo-os.git","type":"git","directory":"packages/mcp"},"_npmVersion":"10.9.2","description":"MCP server for Ekwo OS: let an AI assistant work on the books, under the user's own row level security.","directories":{},"_nodeVersion":"22.17.1","dependencies":{"zod":"^4.6.2","@ekwo-ai/fec":"^0.6.0","@ekwo-ai/core":"^0.6.0","@supabase/supabase-js":"^2.58.0","@modelcontextprotocol/sdk":"^1.30.0"},"_hasShrinkwrap":false,"peerDependencies":{"postgres":"^3.4.5"},"peerDependenciesMeta":{"postgres":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/mcp_0.6.0_1790070129397_0.1211350884137401","host":"s3://npm-registry-packages-npm-production"}},"0.7.0":{"name":"@ekwo-ai/mcp","version":"0.7.0","keywords":["mcp","model-context-protocol","accounting","supabase","postgres","bookkeeping","ekwo"],"author":{"name":"Ekwo AI"},"license":"AGPL-3.0-only","_id":"@ekwo-ai/mcp@0.7.0","maintainers":[{"name":"ekwo","email":"contact@ekwo.ai"}],"homepage":"https://github.com/Ekwo-ai/ekwo-os#readme","bugs":{"url":"https://github.com/Ekwo-ai/ekwo-os/issues"},"bin":{"ekwo-mcp":"dist/bin.js"},"dist":{"shasum":"fdc4dba2216f412519494b96d0f93cfc31f301f2","tarball":"https://registry.npmjs.org/@ekwo-ai/mcp/-/mcp-0.7.0.tgz","fileCount":58,"integrity":"sha512-uGlraf+nMV/+dYc9zlr7U/ZavK4y0XxCpp2ZUbHqV32BmAvSfoLchtbo1Rxb2+KUBDznr/EJQMHc6U9KvgBPTg==","signatures":[{"sig":"MEUCIQCLd056b1GCVfNk5HvVLch0HkI2/BZn7ofFV/bkaAbsRAIgGBYGfYcjmRVCkIVnHk0bZWsg72iGKBIQRVz0+M5KqiI=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEYCIQDnFwIz5ujvE0ErmaTRpl9TmXiKfl5Tiy4OTVsO+k8chwIhAMuBwdqrpET1jcKrzCS9Kw8t9kgUKT3UV7nVw0XKfNqT","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":422940},"ekwo":{"schemaMin":"0.7.0"},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":">=20"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"f414580d00d744407440cf37d6a2c9abe3413a53","mcpName":"ai.ekwo/mcp","scripts":{"build":"tsc -p tsconfig.build.json && node scripts/chmod-bin.mjs","typecheck":"tsc -p tsconfig.json --noEmit"},"_npmUser":{"name":"ekwo","email":"contact@ekwo.ai"},"repository":{"url":"git+https://github.com/Ekwo-ai/ekwo-os.git","type":"git","directory":"packages/mcp"},"_npmVersion":"10.9.2","description":"MCP server for Ekwo OS: let an AI assistant work on the books, under the user's own row level security.","directories":{},"_nodeVersion":"22.17.1","dependencies":{"zod":"^4.6.2","@ekwo-ai/fec":"^0.7.0","@ekwo-ai/core":"^0.7.0","@supabase/supabase-js":"^2.58.0","@modelcontextprotocol/sdk":"^1.30.0"},"_hasShrinkwrap":false,"peerDependencies":{"postgres":"^3.4.5"},"peerDependenciesMeta":{"postgres":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/mcp_0.7.0_1790074795554_0.4715599909137058","host":"s3://npm-registry-packages-npm-production"}},"0.8.0":{"name":"@ekwo-ai/mcp","version":"0.8.0","keywords":["mcp","model-context-protocol","accounting","supabase","postgres","bookkeeping","ekwo"],"author":{"name":"Ekwo AI"},"license":"AGPL-3.0-only","_id":"@ekwo-ai/mcp@0.8.0","maintainers":[{"name":"ekwo","email":"contact@ekwo.ai"}],"homepage":"https://github.com/Ekwo-ai/ekwo-os#readme","bugs":{"url":"https://github.com/Ekwo-ai/ekwo-os/issues"},"bin":{"ekwo-mcp":"dist/bin.js"},"dist":{"shasum":"2d40406aa7aa86b7a164b3f86b7bcc51bb2f7f62","tarball":"https://registry.npmjs.org/@ekwo-ai/mcp/-/mcp-0.8.0.tgz","fileCount":58,"integrity":"sha512-2RxkQJJnpfv+5Devm7aJ8oc7XgA52n7cJePd5HJzdFCQM2Em4/cGnMNS1BIbv0CikwRlXJkEtKVZC41Jjp9OuA==","signatures":[{"sig":"MEUCICQgZxdKNK44kpHr9MSz9nDfV155+zRoi9XydboeGAElAiEAr/zopXLWisQ8g6OUxUv1ismdcIuy439rf4uJgOj3q80=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEUCIQDlbvKzOQ/Ol3cCcdKWVz/33O80cXXe2wOEFJW1/KPZrAIgKB1BE078Tnxs3zspp0OmTLRADn/9MW2wAt6qBt38Vb4=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":422940},"ekwo":{"schemaMin":"0.8.0"},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":">=20"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"832a40323322506b183968d557c116356835220c","mcpName":"ai.ekwo/mcp","scripts":{"build":"tsc -p tsconfig.build.json && node scripts/chmod-bin.mjs","typecheck":"tsc -p tsconfig.json --noEmit"},"_npmUser":{"name":"ekwo","email":"contact@ekwo.ai"},"repository":{"url":"git+https://github.com/Ekwo-ai/ekwo-os.git","type":"git","directory":"packages/mcp"},"_npmVersion":"10.9.2","description":"MCP server for Ekwo OS: let an AI assistant work on the books, under the user's own row level security.","directories":{},"_nodeVersion":"22.17.1","dependencies":{"zod":"^4.6.2","@ekwo-ai/fec":"^0.8.0","@ekwo-ai/core":"^0.8.0","@supabase/supabase-js":"^2.58.0","@modelcontextprotocol/sdk":"^1.30.0"},"_hasShrinkwrap":false,"peerDependencies":{"postgres":"^3.4.5"},"peerDependenciesMeta":{"postgres":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/mcp_0.8.0_1790138838205_0.07981106085936829","host":"s3://npm-registry-packages-npm-production"}},"0.9.0":{"_id":"@ekwo-ai/mcp@0.9.0","bin":{"ekwo-mcp":"dist/bin.js"},"bugs":{"url":"https://github.com/Ekwo-ai/ekwo-os/issues"},"dist":{"shasum":"fdec3da22c83ae06ad5b7bd1f8b121b64a566cc2","tarball":"https://registry.npmjs.org/@ekwo-ai/mcp/-/mcp-0.9.0.tgz","fileCount":74,"integrity":"sha512-9FF2mpaGYJvb84Wi/ozW8Th6AZ3TS5qgzVVovCHjPCKsS8KbTnKZpkPZLAyV7HewpDLXy31pagXOtoQl25VGKA==","signatures":[{"sig":"MEUCICcshhBqNrE6VM1tjbvhYkk64c+iLgbyl4y1Ty3eBXEsAiEAr0pFzOE1bphu37kYZUMG+sfjbpGBqgxb32UBeieCBso=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQD1wg+MlN2S+me9wZmz4s/3QS67ok9MmHMKfEbdVhn1ZwIhALgJ3cThN0gT+nwxnkvZu+UHEyl0FHMvS7wHfFJ5xcw7"}],"unpackedSize":483484},"ekwo":{"schemaMin":"0.9.0"},"main":"./dist/index.js","name":"@ekwo-ai/mcp","type":"module","types":"./dist/index.d.ts","author":{"name":"Ekwo AI"},"engines":{"node":">=20"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"f06385971a5d2418b66a9f8fce72b5afea52dcad","license":"AGPL-3.0-only","mcpName":"ai.ekwo/mcp","scripts":{"build":"tsc -p tsconfig.build.json && node scripts/chmod-bin.mjs","typecheck":"tsc -p tsconfig.json --noEmit"},"version":"0.9.0","_npmUser":{"name":"ekwo","email":"contact@ekwo.ai"},"homepage":"https://github.com/Ekwo-ai/ekwo-os#readme","keywords":["mcp","model-context-protocol","accounting","supabase","postgres","bookkeeping","ekwo"],"repository":{"url":"git+https://github.com/Ekwo-ai/ekwo-os.git","type":"git","directory":"packages/mcp"},"_npmVersion":"11.13.0","description":"MCP server for Ekwo OS: let an AI agent work on the books, under the user's own row level security.","directories":{},"maintainers":[{"name":"ekwo","email":"contact@ekwo.ai"}],"_nodeVersion":"24.17.0","dependencies":{"zod":"^4.6.2","@ekwo-ai/fec":"^0.9.0","@ekwo-ai/core":"^0.9.0","@supabase/supabase-js":"^2.58.0","@modelcontextprotocol/sdk":"^1.30.0"},"_hasShrinkwrap":false,"peerDependencies":{"postgres":"^3.4.5"},"peerDependenciesMeta":{"postgres":{"optional":true}},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/mcp_0.9.0_1790585643234_0.6082819849353551"}}},"time":{"created":"2026-09-18T16:08:00.689Z","modified":"2026-09-28T08:54:03.494Z","0.4.0":"2026-09-18T16:08:01.320Z","0.4.1":"2026-09-18T17:54:08.573Z","0.5.0":"2026-09-22T07:56:27.617Z","0.6.0":"2026-09-22T09:42:09.619Z","0.7.0":"2026-09-22T10:59:55.628Z","0.8.0":"2026-09-23T04:47:18.292Z","0.9.0":"2026-09-28T08:54:03.331Z"},"bugs":{"url":"https://github.com/Ekwo-ai/ekwo-os/issues"},"author":{"name":"Ekwo AI"},"license":"AGPL-3.0-only","homepage":"https://github.com/Ekwo-ai/ekwo-os#readme","keywords":["mcp","model-context-protocol","accounting","supabase","postgres","bookkeeping","ekwo"],"repository":{"url":"git+https://github.com/Ekwo-ai/ekwo-os.git","type":"git","directory":"packages/mcp"},"description":"MCP server for Ekwo OS: let an AI agent work on the books, under the user's own row level security.","maintainers":[{"name":"ekwo","email":"contact@ekwo.ai"}],"readme":"# @ekwo-ai/mcp\n\nThe [Model Context Protocol](https://modelcontextprotocol.io) server for\n[Ekwo OS](https://github.com/Ekwo-ai/ekwo-os). It lets an AI agent work on\nthe books in your own Postgres: read the ledger, raise an invoice, post it, match\na payment, pull the VAT return or the French FEC — **as you**, under the row\nlevel security of your own installation.\n\n```sh\nnpx -y @ekwo-ai/mcp@latest\n```\n\nIt speaks MCP over stdio and is started by a client, never by hand. Keep the\nversion in the command: run from inside a clone of the Ekwo repository, a bare\n`npx @ekwo-ai/mcp` finds the workspace package of the same name, which has no\nbuilt command, and answers `ekwo-mcp: command not found`. With `@latest`, `npx`\nfetches the published server wherever it is started. To run the server of the\nclone itself, build it (`npm run build`) and start\n`node packages/mcp/dist/bin.js`.\n\n## What it is, and what it is not\n\nThe server holds no privileges of its own. It signs in as the person using it,\nor is handed their access token, and everything it can do afterwards is\nexactly what that person can do: a viewer reads and cannot write, a member of\none company cannot see another, a locked period refuses a posting. None of\nthat is checked in this package — the policies and the triggers in the schema\ndecide, and this server reports what they answered.\n\nThree things it will never do:\n\n- **Write a ledger line.** Every entry comes out of `post_document`,\n  `post_payment`, `post_entry` or `reconcile`, which carry the accounting\n  rules. Direct inserts are for the objects a person types: contacts, draft\n  documents and their lines, payments, bank transactions.\n- **Delete or edit a posted entry.** There is no unpost, and no tool that\n  removes one. A mistake is corrected with a credit note, which is how\n  accounting has always worked. `unreconcile` is the only undo here, and\n  matching changes no account.\n- **Use a `service_role` key.** It would work, and that is the objection: it\n  bypasses every policy, so the agent would answer for companies its user\n  was never invited to. The server refuses to start with one.\n\n## Configuration\n\n### The recommended route: PostgREST, as the signed-in user\n\n```json\n{\n  \"mcpServers\": {\n    \"ekwo\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"@ekwo-ai/mcp@latest\"],\n      \"env\": {\n        \"SUPABASE_URL\": \"https://YOURREF.supabase.co\",\n        \"SUPABASE_ANON_KEY\": \"your anon (publishable) key\",\n        \"EKWO_EMAIL\": \"you@example.com\",\n        \"EKWO_PASSWORD\": \"your password\"\n      }\n    }\n  }\n}\n```\n\nThat block goes in `claude_desktop_config.json` for Claude Desktop, or in\n`.mcp.json` at the root of a project for Claude Code. `EKWO_ACCESS_TOKEN`\nreplaces the address and the password when you already hold a session; with\nthe password, the session is kept in memory and refreshed, and nothing is\nwritten to disk.\n\n### The fallback: a direct Postgres connection\n\nFor a self-hosted installation with no PostgREST in front of the database, or\nfor tests.\n\n```json\n{\n  \"env\": {\n    \"EKWO_DB_URL\": \"postgresql://…\",\n    \"EKWO_ACT_AS_USER_ID\": \"the auth.users id this server acts for\"\n  }\n}\n```\n\n`EKWO_ACT_AS_USER_ID` is **required**, and that is the whole point of this\nmode. A database connection is nobody: `auth.uid()` is null, row level\nsecurity is bypassed rather than satisfied, and a server running that way\nwould be a way round the policies rather than a client of them. So every\nquery runs inside a transaction that sets `request.jwt.claims` to that user\nand switches to the `authenticated` role, and the policies bind exactly as\nthey do over the API. This mode needs the `postgres` package installed\nalongside the server; the recommended route needs no driver at all.\n\n| Variable | Meaning |\n|---|---|\n| `SUPABASE_URL` | `https://<ref>.supabase.co` |\n| `SUPABASE_ANON_KEY` | The anon (publishable) key. A `service_role` key is refused. |\n| `EKWO_EMAIL` / `EKWO_PASSWORD` | The user this agent acts as |\n| `EKWO_ACCESS_TOKEN` | A session already in hand, instead of the two above |\n| `EKWO_DB_URL` | A direct Postgres connection, for a self-hosted installation |\n| `EKWO_ACT_AS_USER_ID` | Required with `EKWO_DB_URL`: the `auth.users` id to act for |\n\n### Started with nothing set\n\nA client lists a server's tools as soon as it is added, and so do the\ndirectories that index MCP servers. So with **none** of the variables above —\nor with half of them — the server still starts, still answers the list of\ntools, and every tool call returns a `not_configured` error that names the\nvariables to set and where. It connects nowhere until then, and it offers the\ntools of the socle only: which modules an installation carries is read from\nits database.\n\nOnly absence is forgiven. A `service_role` key, in either slot, and an\n`EKWO_ACT_AS_USER_ID` that is not a uuid are values somebody wrote, and the\nserver still refuses to start with them.\n\n```sh\nnpm run build -w @ekwo-ai/mcp\nnode packages/mcp/scripts/introspect.mjs     # starts it with no environment and lists the tools\n```\n\nThe `Dockerfile` at the root of the repository builds the same server from the\ncheckout; `docker run -i --rm <image>` speaks MCP over stdio, with the variables\npassed as `-e`.\n\n## Over HTTP: hosting it for others\n\nThe same server, reached by URL instead of started by a client:\n`handleHttpRequest` answers the **Streamable HTTP** transport of MCP. It is\n**stateless** — each request builds a server, answers in JSON and closes it,\nwith no session id and nothing kept — so it runs on a function platform,\nbehind a load balancer, or in one process. A `GET` is answered `405`: this\nserver never speaks first, so there is no stream to open.\n\nWhat it does not do is decide who is calling. That is the host's job, and the\nwhole of it: the host authenticates the request its own way, then hands in\n**the connection that request may use**.\n\n```ts\nimport { handleHttpRequest } from '@ekwo-ai/mcp';\n\nexport default async function (request: Request): Promise<Response> {\n  const who = await yourOwnAuthentication(request);      // a token you issued\n  if (who === undefined) return new Response(null, { status: 401 });\n  return handleHttpRequest(request, {\n    supabaseUrl: who.supabaseUrl,                         // https://<ref>.supabase.co\n    anonKey: who.publishableKey,                          // never the service_role key\n    apiKey: who.ekwoKey,                                  // or: accessToken: who.session\n  });\n}\n```\n\nA connection is one of two things, never both:\n\n- **A person's access token** on the instance. Row level security decides,\n  exactly as for that person in a browser.\n- **A key of Ekwo OS**, as `create_api_key()` issues it. It travels in the\n  `X-Ekwo-Api-Key` header with no `Authorization` beside it, the schema's\n  pre-request hook presents it (decision\n  [0062](../../docs/decisions/0062-a-key-reaches-the-api.md)), and the caller\n  is on that key's one company with that key's capabilities. A key issued\n  without a capability that writes gives an agent that reads.\n\nA `service_role` key is refused in all three slots, as on stdio. For\n`node:http` (or anything built on it), `handleNodeRequest(req, res,\nconnection, { origin })` is the same handler.\n\n### Putting it in front of people: authorization\n\nA remote MCP client — Claude and the others — expects the server to follow\nthe [authorization part of the MCP\nspecification](https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization):\nOAuth 2.1 with PKCE. That belongs to the host, not to this package, and a\nhost that wants the clients to connect on their own serves:\n\n1. **`401` with `WWW-Authenticate: Bearer resource_metadata=\"…\"`** on the MCP\n   endpoint when there is no valid token.\n2. **Protected resource metadata** (RFC 9728) at\n   `/.well-known/oauth-protected-resource`, naming the endpoint as the\n   `resource` and the authorization server.\n3. **Authorization server metadata** (RFC 8414) at\n   `/.well-known/oauth-authorization-server`, with `S256` in\n   `code_challenge_methods_supported`.\n4. **Dynamic client registration** (RFC 7591), or client metadata documents,\n   so a client registers itself.\n5. **An authorization page** where the person signs in the host's way and\n   chooses what the client may reach; then a token endpoint that issues short\n   access tokens and rotating refresh tokens.\n\nEach access token the host issues maps to a connection — for instance to a\nkey of Ekwo OS the person issued on their own installation for this purpose,\nheld by the host encrypted and opened in memory for the one request. The\ndatabase stays the only place that decides what may be read or written.\n\n## The tools\n\nEvery write names its company explicitly.\n\n| Tool | What it does |\n|---|---|\n| `list_companies` | The companies you are a member of, with your role on each |\n| `get_company` | Financial years, lock dates, journals, default accounts |\n| `list_accounts` | The accounts a company works with, by code prefix, type or name. `include_all` for the whole chart |\n| `search_contacts` | Customers and suppliers, by name, type or VAT number |\n| `search_products` | The catalogue: code, unit, price, account and tax of what is sold and bought |\n| `list_documents` | Invoices, credit notes and quotes, filtered |\n| `get_document` | One document with its lines and the entry it produced |\n| `list_bank_accounts` | The bank accounts of a company, with the journal and ledger account behind each |\n| `list_bank_transactions` | Statement lines, pending by default |\n| `trial_balance` | Opening, movements and closing per account |\n| `general_ledger` | Every posted line of an account, with a running balance |\n| `aged_balance` | What is still owed, bucketed by age, read from the ledger |\n| `vat_return` | The boxes for a period, summed from the ledger |\n| `ec_sales_list` | The recapitulative statement of intra-Community supplies: one line per customer VAT number and per nature |\n| `portfolio_upcoming_filings` | *Portfolio* = the companies you may read: for an accounting firm, its clients ([`docs/firms.md`](../../docs/firms.md)). What falls due between two dates in every company you hold `filings.read` on. One row per company at least: a pack that names no deadline is listed without a date, and says so |\n| `portfolio_filings_touched_since` | Declarations that have gone and whose period received entries afterwards, across the same companies, with the company named |\n| `list_statements` / `financial_statement` | The schemes a company can be presented on, and one statement |\n| `generate_fec` | The French FEC as text, with its checks and its filename |\n| `read_audit_log` | Who changed what and when: the configuration of a company, and the acts that change a state. Append-only; nothing writes it |\n| `get_preferences` | What you prefer, and the language chain to read labels with |\n| `list_invitations` | Who has been invited into a company and not yet joined |\n| `list_api_keys` | The machine keys of a company, and what each may do |\n| `describe_pack` | Which country packs this installation holds: their version, how much anyone has read them, and the register of texts each was built from — title, official publisher, link and the day it was opened |\n| `status` | Schema version, instance, connection, companies |\n| `create_contact` | A customer, supplier or other third party |\n| `create_product` | A catalogue row: code, name, unit, price, account, tax |\n| `update_product` | Changes one, or retires it with `active: false` |\n| `pin_accounts` | Adds accounts to the working chart a company sees first, or takes one back out with `pinned: false` |\n| `create_document` | A draft invoice, credit note or quote, with its lines. With `client_ref`, calling twice creates once |\n| `update_document_lines` | Replaces the lines of a **draft** |\n| `post_document` | Books it. There is no unpost. `dry_run: true` returns the entry the database would write, and writes nothing |\n| `cancel_document` | Undoes a posted invoice, and says how in `undone_by`: back to `draft` where its country's `posted_edit_policy` allows it and nothing about it has left (`unpost_document()`), otherwise a `credit_note` that names it, posted and matched against it, and the invoice cancelled (`cancel_document()`), with `why` the draft was ruled out. A credit note is dated on the invoice's day while that period is open; otherwise the caller gives a date. A date, or `credit_note: true`, asks for the credit note |\n| `reverse_entry` | Undoes a posted entry keyed by hand: its mirror, posted under the next number and matched against it. Same rule for the date |\n| `record_payment` | Books money in or out and matches it against open invoices — or, with `document_id`, against that document alone, which then names the contact and the direction. With `client_ref`, recording twice records once |\n| `reconcile` / `unreconcile` | Matches two ledger lines, or undoes one matching |\n| `create_bank_account` | Registers an account from its IBAN and wires it to the bank journal. Running it twice with the same IBAN creates nothing |\n| `create_bank_transaction` | One statement line by hand, for an installation with no feed |\n| `import_bank_statement` | A statement file (`camt.053`, `coda`, `cfonb120`) into statements and pending lines. Books nothing; the same file twice creates nothing; an unknown account or a statement that does not add up is refused by name, a missing statement is signalled |\n| `lock_period` | Moves the accounting and VAT lock dates. Needs `company.write`. |\n| `opening_balance` | The trial balance of whatever kept the books before, as the opening entry |\n| `import_books` | Books kept elsewhere — a FEC, an export of journal items, a journal report, a trial balance — whole or not at all. `dry_run: true` first: the correspondence proposed for every account and journal — `exact` only for the same code the files do not contradict, otherwise `suggested` with its reason, or `none` — what has no answer, and the import rehearsed by the database and taken back. Then again with the completed `mapping`, or `accept_suggestions` once the user has read every suggestion; nothing is posted while one is unconfirmed. Every entry through `post_entry()`; no tax; the same files twice refused, saying when and what. The files travel as text, 256 KiB at most: beyond, the tool answers with the `ekwo import` command that reads them from the disk. `ekwo import` is the same function |\n| `close_fiscal_year` / `reopen_fiscal_year` | Closes a year the way the country pack says, or reverses a close run too early |\n| `create_company` | A company on a country pack, with its chart and its first financial year. An instance-level act |\n| `update_company_profile` | What a company says about itself on its documents |\n| `set_preferences` | Your own language, timezone, formats and default company |\n| `invite_member` / `revoke_invitation` | Invites an address into a company, or withdraws the invitation. The token is shown once |\n| `create_api_key` / `revoke_api_key` | A key for a machine, scoped to one company and a list of capabilities |\n\n**`list_accounts` answers with the working chart, not the whole one.** A\ncountry pack transcribes the regulation — hundreds of accounts, and more than\na thousand in the Luxembourg PCN or the SYSCOHADA — and a company works with a few dozen of them, so the default is\nwhat `accounts_in_use()` returns: the accounts carrying posted entries, those\nthe company's own settings or an enabled module point at, and those somebody\npinned, minus the deprecated ones. Every answer carries a `scope` field saying\nwhich it used. `in_use_from` and `in_use_to` narrow the movements to a period;\n`include_all` returns the whole chart; `include_deprecated` returns it with the\nretired accounts too; and `ekwo://companies/{id}/chart` was already the\nresource that carries everything. None of this restricts anything: a document\nline may name any account of the chart that is not deprecated, and every write\ntool still accepts one.\n\n`post_document`, `cancel_document`, `reverse_entry`, `record_payment`,\n`update_document_lines`, `unreconcile`, `lock_period`, `opening_balance`, `import_books`,\n`close_fiscal_year`, `reopen_fiscal_year`, `revoke_invitation` and\n`revoke_api_key` are annotated destructive in the protocol, so a client can ask\nbefore calling them.\n\n**What a tool may do is the capability the user holds**, not the tool's own\nright: the server acts as the person it signed in as, so `post_document` works\nfor an accountant and is refused to a viewer, by the database, with the\ndatabase's own words. `get_company` returns `your_capabilities` for exactly\nthat reason.\n\n**The modules.** A module of this installation gets its own tools, under the\nprefix its `module.json` declares, and the server reads `public.modules` at\nstartup to know which: `assets_list`, `assets_create`, `assets_schedule`,\n`assets_run_depreciation`, `assets_dispose`, `budgets_list`,\n`budgets_upsert_lines`, `budgets_variance`. A module that is not installed is\nnot offered, because a tool a model cannot use is worse than a tool it cannot\nsee. PostgREST serves a module's schema only once the project exposes it, and\nthe refusal it answers with is a profile error that says nothing useful — so\nevery module tool turns it into the sentence that names the setting.\n\n**Resources.** `ekwo://companies/{id}/chart` is the whole chart of accounts;\n`ekwo://companies/{id}/taxes` is every tax with the ledger account and the\ndeclaration box each of its postings feeds.\n\n**Prompts.** `close_month` walks the month-end checklist — drafts, unmatched\nbank lines, the balance, the VAT, what is still open. `prepare_vat_return`\npulls the boxes and ties them back to the ledger before anything is filed.\n\n## Conventions\n\n- **Amounts are decimal strings.** `\"1210.00\"`, never a float. They go in that\n  way and come back that way, because `numeric` is exact and a float is not.\n- **Dates are ISO**, `2026-06-15`. Identifiers are uuids.\n- **Totals are computed by the database.** `create_document` returns the draft\n  with the totals the schema derived, not with anything the caller supplied.\n- **Refusals travel unchanged.** `period_locked:`, `entry_unbalanced:`,\n  `document_total_mismatch:` and the rest arrive with the message the database\n  raised, plus one sentence saying what it means. They are answers, not\n  obstacles to route around.\n- **A product fills a line in and never constrains it.** A line naming\n  `product_code` takes the catalogue's text, description, unit, price, account\n  and tax; anything the line carries wins over that. What is already posted is\n  never touched when the catalogue changes, and a product referenced by a line\n  is retired with `active: false` rather than deleted.\n- **A missing tax is a missing tax.** A line with no tax books a base with no\n  VAT box, which is not the same as 0 %. A missing *account* is different: it\n  can only mean \"resolve it\", because a product line with no account is\n  refused by a check constraint. So a line may leave `account_code` out, and\n  the database fills it — the company default, then the country model.\n\n## Testing it by hand\n\nThe automated tests run every tool against the real schema in Postgres\ncompiled to WebAssembly (`tests/mcp/`), including the refusals. Two things\nthey cannot run: PostgREST and GoTrue. To exercise those, on a project you can\nthrow away:\n\n```sh\nnpx -y ekwo-os@latest init --country BE --org \"Scratch\" --company \"Scratch BV\" …   # a real project\n```\n\nThen point a client at it — in Claude Desktop, the JSON block above — and:\n\n1. **\"List my companies.\"** The company you created, with `your_role: owner`.\n2. **\"What are the journals and the lock dates?\"** `get_company`.\n3. **\"Create a customer called Dumont, then invoice them 1 000 € plus 21 %\n   VAT for consulting.\"** `create_contact`, then `create_document`; the answer\n   carries `amount_total: \"1210.00\"` computed by the database.\n   Or with a catalogue: **\"add a product CONS-JOUR, a consulting day at 500 €\n   on 704000 at 21 %, then invoice Dumont two of them\"** — `create_product`,\n   then `create_document` with `product_code` and nothing else on the line.\n4. **\"Post it.\"** `post_document`. The entry books 704 / 451 / 400 and takes a\n   number like `SAL/2026/0001`.\n5. **\"They paid 500 € on the 10th.\"** `record_payment`, which books the bank\n   line and matches it; the invoice becomes partially paid.\n6. **\"Show me the trial balance and the VAT for the quarter.\"**\n   `trial_balance` and `vat_return`.\n7. **\"Lock June.\"** `lock_period`, then try to post something dated in June:\n   the refusal comes back as `period_locked:`.\n\nA payment needs somewhere to book the bank side. On a company installed from a\ncountry model the bank and cash journals already point at their account\n(`550000` and `570000` in Belgium, `512000` and `530000` in France), so\n`record_payment` works with nothing else set up. `create_bank_account` names\nthe real account — the IBAN is the one thing nobody can derive — and wires it\nto the journal; `bank_account_id` on the payment then says which one the money\nmoved on, which is what you need with several accounts in one journal. Until a\ncompany has one, `ekwo doctor` says so.\n\n## Licence\n\n[AGPL-3.0-only](../../LICENSE) © Ekwo AI.\n","readmeFilename":"README.md"}