@ekwo-ai/mcp 0.8.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,16 +1,45 @@
1
1
  # @ekwo-ai/mcp
2
2
 
3
3
  The [Model Context Protocol](https://modelcontextprotocol.io) server for
4
- [Ekwo OS](https://github.com/Ekwo-ai/ekwo-os). It lets an AI assistant work on
4
+ [Ekwo OS](https://github.com/Ekwo-ai/ekwo-os). It lets an AI agent work on
5
5
  the books in your own Postgres: read the ledger, raise an invoice, post it, match
6
6
  a payment, pull the VAT return or the French FEC — **as you**, under the row
7
7
  level security of your own installation.
8
8
 
9
+ There are two ways to reach it, and they are the same server.
10
+
11
+ ## Hosted, at `https://mcp.ekwo.ai/mcp`
12
+
13
+ For a client that adds remote servers rather than launching them — claude.ai,
14
+ or any other that speaks **Streamable HTTP**. Nothing is installed, and no
15
+ password or key is written into a configuration file.
16
+
17
+ ```
18
+ https://mcp.ekwo.ai/mcp
19
+ ```
20
+
21
+ Point a client at that address and it discovers the rest. An unauthenticated
22
+ call is answered with `401` and a `WWW-Authenticate` header naming the
23
+ server's protected-resource metadata (RFC 9728); the client follows it to the
24
+ authorization server (RFC 8414), registers itself (RFC 7591, so it needs no
25
+ credentials of its own) and sends the person to their browser to approve the
26
+ connection. The scope asked for is `books`.
27
+
28
+ An agent reading this without a browser can start from
29
+ [`/.well-known/oauth-protected-resource`](https://mcp.ekwo.ai/.well-known/oauth-protected-resource).
30
+
31
+ The books stay where they are: the hosted server holds none, and connects to
32
+ the instance the person names when they approve it.
33
+
34
+ ## Locally, over stdio
35
+
9
36
  ```sh
10
37
  npx -y @ekwo-ai/mcp@latest
11
38
  ```
12
39
 
13
- It speaks MCP over stdio and is started by a client, never by hand. Keep the
40
+ For a client that launches its own servers — Claude Desktop, Claude Code, any
41
+ editor that reads `.mcp.json`. It speaks MCP over stdio and is started by a
42
+ client, never by hand. Keep the
14
43
  version in the command: run from inside a clone of the Ekwo repository, a bare
15
44
  `npx @ekwo-ai/mcp` finds the workspace package of the same name, which has no
16
45
  built command, and answers `ekwo-mcp: command not found`. With `@latest`, `npx`
@@ -38,7 +67,7 @@ Three things it will never do:
38
67
  accounting has always worked. `unreconcile` is the only undo here, and
39
68
  matching changes no account.
40
69
  - **Use a `service_role` key.** It would work, and that is the objection: it
41
- bypasses every policy, so the assistant would answer for companies its user
70
+ bypasses every policy, so the agent would answer for companies its user
42
71
  was never invited to. The server refuses to start with one.
43
72
 
44
73
  ## Configuration
@@ -95,7 +124,7 @@ alongside the server; the recommended route needs no driver at all.
95
124
  |---|---|
96
125
  | `SUPABASE_URL` | `https://<ref>.supabase.co` |
97
126
  | `SUPABASE_ANON_KEY` | The anon (publishable) key. A `service_role` key is refused. |
98
- | `EKWO_EMAIL` / `EKWO_PASSWORD` | The user this assistant acts as |
127
+ | `EKWO_EMAIL` / `EKWO_PASSWORD` | The user this agent acts as |
99
128
  | `EKWO_ACCESS_TOKEN` | A session already in hand, instead of the two above |
100
129
  | `EKWO_DB_URL` | A direct Postgres connection, for a self-hosted installation |
101
130
  | `EKWO_ACT_AS_USER_ID` | Required with `EKWO_DB_URL`: the `auth.users` id to act for |
@@ -123,6 +152,75 @@ The `Dockerfile` at the root of the repository builds the same server from the
123
152
  checkout; `docker run -i --rm <image>` speaks MCP over stdio, with the variables
124
153
  passed as `-e`.
125
154
 
155
+ ## Over HTTP: hosting it for others
156
+
157
+ The same server, reached by URL instead of started by a client:
158
+ `handleHttpRequest` answers the **Streamable HTTP** transport of MCP. It is
159
+ **stateless** — each request builds a server, answers in JSON and closes it,
160
+ with no session id and nothing kept — so it runs on a function platform,
161
+ behind a load balancer, or in one process. A `GET` is answered `405`: this
162
+ server never speaks first, so there is no stream to open.
163
+
164
+ What it does not do is decide who is calling. That is the host's job, and the
165
+ whole of it: the host authenticates the request its own way, then hands in
166
+ **the connection that request may use**.
167
+
168
+ ```ts
169
+ import { handleHttpRequest } from '@ekwo-ai/mcp';
170
+
171
+ export default async function (request: Request): Promise<Response> {
172
+ const who = await yourOwnAuthentication(request); // a token you issued
173
+ if (who === undefined) return new Response(null, { status: 401 });
174
+ return handleHttpRequest(request, {
175
+ supabaseUrl: who.supabaseUrl, // https://<ref>.supabase.co
176
+ anonKey: who.publishableKey, // never the service_role key
177
+ apiKey: who.ekwoKey, // or: accessToken: who.session
178
+ });
179
+ }
180
+ ```
181
+
182
+ A connection is one of two things, never both:
183
+
184
+ - **A person's access token** on the instance. Row level security decides,
185
+ exactly as for that person in a browser.
186
+ - **A key of Ekwo OS**, as `create_api_key()` issues it. It travels in the
187
+ `X-Ekwo-Api-Key` header with no `Authorization` beside it, the schema's
188
+ pre-request hook presents it (decision
189
+ [0062](../../docs/decisions/0062-a-key-reaches-the-api.md)), and the caller
190
+ is on that key's one company with that key's capabilities. A key issued
191
+ without a capability that writes gives an agent that reads.
192
+
193
+ A `service_role` key is refused in all three slots, as on stdio. For
194
+ `node:http` (or anything built on it), `handleNodeRequest(req, res,
195
+ connection, { origin })` is the same handler.
196
+
197
+ ### Putting it in front of people: authorization
198
+
199
+ A remote MCP client — Claude and the others — expects the server to follow
200
+ the [authorization part of the MCP
201
+ specification](https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization):
202
+ OAuth 2.1 with PKCE. That belongs to the host, not to this package, and a
203
+ host that wants the clients to connect on their own serves:
204
+
205
+ 1. **`401` with `WWW-Authenticate: Bearer resource_metadata="…"`** on the MCP
206
+ endpoint when there is no valid token.
207
+ 2. **Protected resource metadata** (RFC 9728) at
208
+ `/.well-known/oauth-protected-resource`, naming the endpoint as the
209
+ `resource` and the authorization server.
210
+ 3. **Authorization server metadata** (RFC 8414) at
211
+ `/.well-known/oauth-authorization-server`, with `S256` in
212
+ `code_challenge_methods_supported`.
213
+ 4. **Dynamic client registration** (RFC 7591), or client metadata documents,
214
+ so a client registers itself.
215
+ 5. **An authorization page** where the person signs in the host's way and
216
+ chooses what the client may reach; then a token endpoint that issues short
217
+ access tokens and rotating refresh tokens.
218
+
219
+ Each access token the host issues maps to a connection — for instance to a
220
+ key of Ekwo OS the person issued on their own installation for this purpose,
221
+ held by the host encrypted and opened in memory for the one request. The
222
+ database stays the only place that decides what may be read or written.
223
+
126
224
  ## The tools
127
225
 
128
226
  Every write names its company explicitly.
@@ -142,12 +240,12 @@ Every write names its company explicitly.
142
240
  | `general_ledger` | Every posted line of an account, with a running balance |
143
241
  | `aged_balance` | What is still owed, bucketed by age, read from the ledger |
144
242
  | `vat_return` | The boxes for a period, summed from the ledger |
145
- | `ec_sales_list` | The recapitulative statement of intra-Community supplies: one line per customer VAT number and per nature |
243
+ | `ec_sales_list` | The recapitulative statement of European Union supplies: one line per customer VAT number and per nature |
146
244
  | `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 |
147
245
  | `portfolio_filings_touched_since` | Declarations that have gone and whose period received entries afterwards, across the same companies, with the company named |
148
246
  | `list_statements` / `financial_statement` | The schemes a company can be presented on, and one statement |
149
247
  | `generate_fec` | The French FEC as text, with its checks and its filename |
150
- | `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 |
248
+ | `read_audit_log` | Who changed what and when: the configuration of a company, and the acts that change a state. Append-only for every client; only the database writes it |
151
249
  | `get_preferences` | What you prefer, and the language chain to read labels with |
152
250
  | `list_invitations` | Who has been invited into a company and not yet joined |
153
251
  | `list_api_keys` | The machine keys of a company, and what each may do |
@@ -164,7 +262,7 @@ Every write names its company explicitly.
164
262
  | `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 |
165
263
  | `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 |
166
264
  | `reconcile` / `unreconcile` | Matches two ledger lines, or undoes one matching |
167
- | `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 |
265
+ | `create_bank_account` | Registers an account from its identifier, in the scheme the company's country uses (an IBAN, a routing number and an account number, a sort code and an account number…), and wires it to the bank journal. Running it twice with the same identifier creates nothing |
168
266
  | `create_bank_transaction` | One statement line by hand, for an installation with no feed |
169
267
  | `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 |
170
268
  | `lock_period` | Moves the accounting and VAT lock dates. Needs `company.write`. |
@@ -175,6 +273,7 @@ Every write names its company explicitly.
175
273
  | `update_company_profile` | What a company says about itself on its documents |
176
274
  | `set_preferences` | Your own language, timezone, formats and default company |
177
275
  | `invite_member` / `revoke_invitation` | Invites an address into a company, or withdraws the invitation. The token is shown once |
276
+ | `remove_member` / `set_member_role` | Takes a member out of a company — leaving oneself needs no `members.manage` — or moves them to another preset, clearing their per-member adjustments. The last owner is neither removed nor demoted |
178
277
  | `create_api_key` / `revoke_api_key` | A key for a machine, scoped to one company and a list of capabilities |
179
278
 
180
279
  **`list_accounts` answers with the working chart, not the whole one.** A
@@ -192,8 +291,8 @@ tool still accepts one.
192
291
 
193
292
  `post_document`, `cancel_document`, `reverse_entry`, `record_payment`,
194
293
  `update_document_lines`, `unreconcile`, `lock_period`, `opening_balance`, `import_books`,
195
- `close_fiscal_year`, `reopen_fiscal_year`, `revoke_invitation` and
196
- `revoke_api_key` are annotated destructive in the protocol, so a client can ask
294
+ `close_fiscal_year`, `reopen_fiscal_year`, `revoke_invitation`, `remove_member`,
295
+ `set_member_role` and `revoke_api_key` are annotated destructive in the protocol, so a client can ask
197
296
  before calling them.
198
297
 
199
298
  **What a tool may do is the capability the user holds**, not the tool's own
@@ -204,9 +303,12 @@ that reason.
204
303
 
205
304
  **The modules.** A module of this installation gets its own tools, under the
206
305
  prefix its `module.json` declares, and the server reads `public.modules` at
207
- startup to know which: `assets_list`, `assets_create`, `assets_schedule`,
208
- `assets_run_depreciation`, `assets_dispose`, `budgets_list`,
209
- `budgets_upsert_lines`, `budgets_variance`. A module that is not installed is
306
+ startup to know which: `fixed_assets_list`, `fixed_assets_create`,
307
+ `fixed_assets_schedule`, `fixed_assets_run_depreciation`,
308
+ `fixed_assets_dispose`, `budgets_list`, `budgets_upsert_lines`,
309
+ `budgets_variance`. The fixed assets tools were called `assets_*` until
310
+ 0.10.0; those names are still registered, as deprecated aliases that say so in
311
+ their description, and are removed in 0.11.0. A module that is not installed is
210
312
  not offered, because a tool a model cannot use is worse than a tool it cannot
211
313
  see. PostgREST serves a module's schema only once the project exposes it, and
212
314
  the refusal it answers with is a profile error that says nothing useful — so
@@ -276,7 +378,7 @@ A payment needs somewhere to book the bank side. On a company installed from a
276
378
  country model the bank and cash journals already point at their account
277
379
  (`550000` and `570000` in Belgium, `512000` and `530000` in France), so
278
380
  `record_payment` works with nothing else set up. `create_bank_account` names
279
- the real account — the IBAN is the one thing nobody can derive — and wires it
381
+ the real account — its identifier is the one thing nobody can derive — and wires it
280
382
  to the journal; `bank_account_id` on the payment then says which one the money
281
383
  moved on, which is what you need with several accounts in one journal. Until a
282
384
  company has one, `ekwo doctor` says so.
package/dist/bin.js CHANGED
@@ -45,7 +45,7 @@ in claude_desktop_config.json or .mcp.json with:
45
45
 
46
46
  SUPABASE_URL https://<ref>.supabase.co
47
47
  SUPABASE_ANON_KEY the anon (publishable) key — never the service_role key
48
- EKWO_EMAIL the user this assistant acts as
48
+ EKWO_EMAIL the user this agent acts as
49
49
  EKWO_PASSWORD their password
50
50
  EKWO_ACCESS_TOKEN an access token, instead of the two above
51
51
 
package/dist/config.d.ts CHANGED
@@ -11,7 +11,7 @@
11
11
  *
12
12
  * The one thing this file exists to refuse is a `service_role` key. It would
13
13
  * work — that is the problem. Every policy in the schema would be bypassed,
14
- * and an assistant that can read every company of an installation is not the
14
+ * and an agent that can read every company of an installation is not the
15
15
  * thing anybody asked for. It is refused wherever it can arrive, and there are
16
16
  * two doors, not one: `SUPABASE_ANON_KEY` is the obvious paste, and
17
17
  * `EKWO_ACCESS_TOKEN` is the quiet one — it goes into the `Authorization`
package/dist/config.js CHANGED
@@ -11,7 +11,7 @@
11
11
  *
12
12
  * The one thing this file exists to refuse is a `service_role` key. It would
13
13
  * work — that is the problem. Every policy in the schema would be bypassed,
14
- * and an assistant that can read every company of an installation is not the
14
+ * and an agent that can read every company of an installation is not the
15
15
  * thing anybody asked for. It is refused wherever it can arrive, and there are
16
16
  * two doors, not one: `SUPABASE_ANON_KEY` is the obvious paste, and
17
17
  * `EKWO_ACCESS_TOKEN` is the quiet one — it goes into the `Authorization`
package/dist/http.d.ts ADDED
@@ -0,0 +1,92 @@
1
+ /**
2
+ * The same server over HTTP: the Streamable HTTP transport of MCP, stateless.
3
+ *
4
+ * `bin.ts` is a process a client starts on its own machine, and it reads who
5
+ * it acts as from its environment. A server somebody reaches by URL is the
6
+ * other shape: one process answers many people, so **who a request acts as
7
+ * is decided per request**, by whatever put the server on the network — its
8
+ * own sign-in, its own tokens — and handed in here as a connection. This
9
+ * file knows nothing about how that host authenticates anybody. It takes a
10
+ * request and the connection that request is allowed to use, and answers.
11
+ *
12
+ * ## Stateless, in JSON
13
+ *
14
+ * Each request builds a server, answers, and closes it. Nothing is kept
15
+ * between two requests — no session id, no stream, no cache of whose books
16
+ * were open — so a host can run it on a function platform, behind a load
17
+ * balancer, or on one machine, and two people never share a thing. The
18
+ * answers are plain JSON (`enableJsonResponse`): every tool of this server
19
+ * answers once, and an open event stream would hold a connection for nothing.
20
+ * A `GET` — the stream a client may open for messages the server sends on its
21
+ * own — is answered `405`, which the protocol allows and which is the truth:
22
+ * this server never speaks first.
23
+ *
24
+ * ## What a connection may be
25
+ *
26
+ * - **A person's session on the instance**: the project address, its
27
+ * publishable key and an access token. Row level security decides, as it
28
+ * does for that person in a browser.
29
+ * - **A key of Ekwo OS**: the same two, and a key `create_api_key()` issued.
30
+ * It travels in `X-Ekwo-Api-Key`, the core's pre-request hook presents it
31
+ * (decision 0062), and the caller is on the key's one company with the
32
+ * key's capabilities.
33
+ * - **A backend already built**, for a host that reaches the database some
34
+ * other way — and for the tests, which run the real schema in PGlite.
35
+ *
36
+ * A `service_role` key is refused in every slot it could arrive in, as it is
37
+ * on stdio: this server has no privilege of its own, over any transport.
38
+ */
39
+ import type { IncomingMessage, ServerResponse } from 'node:http';
40
+ import { type Backend } from './backend.js';
41
+ /** Who one request acts as, over PostgREST. Exactly one of the two credentials. */
42
+ export interface HttpConnection {
43
+ /** The project, `https://<ref>.supabase.co`. */
44
+ supabaseUrl: string;
45
+ /** Its publishable (anon) key. Never the `service_role` key. */
46
+ anonKey: string;
47
+ /** A person's access token on that project. */
48
+ accessToken?: string | undefined;
49
+ /** A key of Ekwo OS, issued on one company of that project. */
50
+ apiKey?: string | undefined;
51
+ }
52
+ export interface HttpHandlerOptions {
53
+ /**
54
+ * The modules whose tools are offered. Left out, the database is asked on
55
+ * every request that needs to know — a host that knows already, or caches
56
+ * the answer, passes it and saves the round trip.
57
+ */
58
+ modules?: readonly string[] | undefined;
59
+ /** The `fetch` the PostgREST backend uses. A host or a test may hand its own. */
60
+ fetch?: typeof globalThis.fetch | undefined;
61
+ }
62
+ /**
63
+ * Checks a connection and says what is wrong with it, without a network.
64
+ *
65
+ * The three refusals of `readConfig`, for a connection that did not come from
66
+ * an environment: nothing to act as, two things to act as, and a
67
+ * `service_role` key in any of the three slots.
68
+ */
69
+ export declare function checkConnection(connection: HttpConnection): void;
70
+ /** The backend a connection describes. The caller closes it. */
71
+ export declare function openHttpBackend(connection: HttpConnection, options?: {
72
+ fetch?: typeof globalThis.fetch | undefined;
73
+ }): Promise<Backend>;
74
+ /**
75
+ * Answers one HTTP request of the MCP Streamable HTTP transport.
76
+ *
77
+ * `connection` is who this request acts as, decided by the host before it
78
+ * calls this. Nothing about it is remembered once the response is built.
79
+ */
80
+ export declare function handleHttpRequest(request: Request, connection: HttpConnection | Backend, options?: HttpHandlerOptions): Promise<Response>;
81
+ /**
82
+ * The same handler for `node:http` (and anything built on it).
83
+ *
84
+ * The request is read into a web `Request`, answered by `handleHttpRequest`,
85
+ * and the `Response` written back — one path, whichever server a host runs.
86
+ * `origin` is the address this server is reached at, which `node:http` does
87
+ * not know: `https://mcp.example.org`.
88
+ */
89
+ export declare function handleNodeRequest(req: IncomingMessage, res: ServerResponse, connection: HttpConnection | Backend, options: HttpHandlerOptions & {
90
+ origin: string;
91
+ }): Promise<void>;
92
+ //# sourceMappingURL=http.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAGjE,OAAO,EAAgB,KAAK,OAAO,EAAE,MAAM,cAAc,CAAC;AAM1D,mFAAmF;AACnF,MAAM,WAAW,cAAc;IAC7B,gDAAgD;IAChD,WAAW,EAAE,MAAM,CAAC;IACpB,gEAAgE;IAChE,OAAO,EAAE,MAAM,CAAC;IAChB,+CAA+C;IAC/C,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,+DAA+D;IAC/D,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC7B;AAED,MAAM,WAAW,kBAAkB;IACjC;;;;OAIG;IACH,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;IACxC,iFAAiF;IACjF,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,GAAG,SAAS,CAAC;CAC7C;AAaD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,UAAU,EAAE,cAAc,GAAG,IAAI,CAuBhE;AAED,gEAAgE;AAChE,wBAAsB,eAAe,CACnC,UAAU,EAAE,cAAc,EAC1B,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,GAAG,SAAS,CAAA;CAAO,GAC5D,OAAO,CAAC,OAAO,CAAC,CASlB;AAYD;;;;;GAKG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,OAAO,EAChB,UAAU,EAAE,cAAc,GAAG,OAAO,EACpC,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,QAAQ,CAAC,CA4DnB;AAWD;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,cAAc,EACnB,UAAU,EAAE,cAAc,GAAG,OAAO,EACpC,OAAO,EAAE,kBAAkB,GAAG;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,GAC/C,OAAO,CAAC,IAAI,CAAC,CAkBf"}
package/dist/http.js ADDED
@@ -0,0 +1,208 @@
1
+ /**
2
+ * The same server over HTTP: the Streamable HTTP transport of MCP, stateless.
3
+ *
4
+ * `bin.ts` is a process a client starts on its own machine, and it reads who
5
+ * it acts as from its environment. A server somebody reaches by URL is the
6
+ * other shape: one process answers many people, so **who a request acts as
7
+ * is decided per request**, by whatever put the server on the network — its
8
+ * own sign-in, its own tokens — and handed in here as a connection. This
9
+ * file knows nothing about how that host authenticates anybody. It takes a
10
+ * request and the connection that request is allowed to use, and answers.
11
+ *
12
+ * ## Stateless, in JSON
13
+ *
14
+ * Each request builds a server, answers, and closes it. Nothing is kept
15
+ * between two requests — no session id, no stream, no cache of whose books
16
+ * were open — so a host can run it on a function platform, behind a load
17
+ * balancer, or on one machine, and two people never share a thing. The
18
+ * answers are plain JSON (`enableJsonResponse`): every tool of this server
19
+ * answers once, and an open event stream would hold a connection for nothing.
20
+ * A `GET` — the stream a client may open for messages the server sends on its
21
+ * own — is answered `405`, which the protocol allows and which is the truth:
22
+ * this server never speaks first.
23
+ *
24
+ * ## What a connection may be
25
+ *
26
+ * - **A person's session on the instance**: the project address, its
27
+ * publishable key and an access token. Row level security decides, as it
28
+ * does for that person in a browser.
29
+ * - **A key of Ekwo OS**: the same two, and a key `create_api_key()` issued.
30
+ * It travels in `X-Ekwo-Api-Key`, the core's pre-request hook presents it
31
+ * (decision 0062), and the caller is on the key's one company with the
32
+ * key's capabilities.
33
+ * - **A backend already built**, for a host that reaches the database some
34
+ * other way — and for the tests, which run the real schema in PGlite.
35
+ *
36
+ * A `service_role` key is refused in every slot it could arrive in, as it is
37
+ * on stdio: this server has no privilege of its own, over any transport.
38
+ */
39
+ import { WebStandardStreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js';
40
+ import { isServiceRoleKey, serviceRoleRefusal } from '@ekwo-ai/core';
41
+ import { EkwoMcpError } from './backend.js';
42
+ import { postgrestBackend } from './postgrest.js';
43
+ import { assertSchemaSupported } from './schema.js';
44
+ import { buildServer } from './server.js';
45
+ import { installedModules } from './tools/modules.js';
46
+ const SURFACE = 'this server';
47
+ const SIGN_IN = 'connect it with a person\'s session or with a key of Ekwo OS';
48
+ /** A JSON-RPC error on the wire, for the refusals that come before the protocol. */
49
+ function rpcError(status, code, message, id = null) {
50
+ return new Response(JSON.stringify({ jsonrpc: '2.0', error: { code, message }, id }), {
51
+ status,
52
+ headers: { 'Content-Type': 'application/json' },
53
+ });
54
+ }
55
+ /**
56
+ * Checks a connection and says what is wrong with it, without a network.
57
+ *
58
+ * The three refusals of `readConfig`, for a connection that did not come from
59
+ * an environment: nothing to act as, two things to act as, and a
60
+ * `service_role` key in any of the three slots.
61
+ */
62
+ export function checkConnection(connection) {
63
+ const { supabaseUrl, anonKey, accessToken, apiKey } = connection;
64
+ if (supabaseUrl.trim().length === 0 || anonKey.trim().length === 0) {
65
+ throw new EkwoMcpError('missing_configuration: a connection needs the project address and its publishable key.');
66
+ }
67
+ if (isServiceRoleKey(anonKey)) {
68
+ throw new EkwoMcpError(serviceRoleRefusal('the publishable key of the connection', 'apikey', SURFACE, SIGN_IN));
69
+ }
70
+ const hasToken = accessToken !== undefined && accessToken.length > 0;
71
+ const hasKey = apiKey !== undefined && apiKey.length > 0;
72
+ if (hasToken && isServiceRoleKey(accessToken)) {
73
+ throw new EkwoMcpError(serviceRoleRefusal('the access token of the connection', 'authorization', SURFACE, SIGN_IN));
74
+ }
75
+ if (hasKey && isServiceRoleKey(apiKey)) {
76
+ throw new EkwoMcpError(serviceRoleRefusal('the key of the connection', 'apikey', SURFACE, SIGN_IN));
77
+ }
78
+ if (hasToken === hasKey) {
79
+ throw new EkwoMcpError(hasToken
80
+ ? 'two_credentials: a connection acts as a person or as a key of Ekwo OS, never as both at once.'
81
+ : 'missing_credentials: a connection needs a person\'s access token or a key of Ekwo OS. This server has no identity of its own.');
82
+ }
83
+ }
84
+ /** The backend a connection describes. The caller closes it. */
85
+ export async function openHttpBackend(connection, options = {}) {
86
+ checkConnection(connection);
87
+ return postgrestBackend({
88
+ supabaseUrl: connection.supabaseUrl,
89
+ anonKey: connection.anonKey,
90
+ accessToken: connection.apiKey === undefined ? connection.accessToken : undefined,
91
+ apiKey: connection.apiKey,
92
+ fetch: options.fetch,
93
+ });
94
+ }
95
+ /** The JSON-RPC messages of a body, whether it held one or a batch. */
96
+ function messagesOf(body) {
97
+ if (Array.isArray(body))
98
+ return body;
99
+ if (body !== null && typeof body === 'object')
100
+ return [body];
101
+ return [];
102
+ }
103
+ /** Methods whose answer depends on which tools this installation carries. */
104
+ const NEEDS_MODULES = new Set(['tools/list', 'tools/call']);
105
+ /**
106
+ * Answers one HTTP request of the MCP Streamable HTTP transport.
107
+ *
108
+ * `connection` is who this request acts as, decided by the host before it
109
+ * calls this. Nothing about it is remembered once the response is built.
110
+ */
111
+ export async function handleHttpRequest(request, connection, options = {}) {
112
+ if (request.method === 'GET' || request.method === 'DELETE') {
113
+ // No stream to open and no session to end: every answer is in the
114
+ // response to the request that asked for it.
115
+ return new Response(null, { status: 405, headers: { Allow: 'POST' } });
116
+ }
117
+ if (request.method !== 'POST') {
118
+ return new Response(null, { status: 405, headers: { Allow: 'POST' } });
119
+ }
120
+ let body;
121
+ try {
122
+ body = await request.json();
123
+ }
124
+ catch {
125
+ return rpcError(400, -32700, 'Parse error: the body is not JSON.');
126
+ }
127
+ const messages = messagesOf(body);
128
+ const methods = new Set(messages.map((message) => message.method));
129
+ const firstId = messages.find((message) => message.id !== undefined)?.id ?? null;
130
+ let backend;
131
+ const owned = !isBackend(connection);
132
+ try {
133
+ backend = isBackend(connection) ? connection : await openHttpBackend(connection, { fetch: options.fetch });
134
+ }
135
+ catch (error) {
136
+ // A connection the host built wrong, or a token the instance refused.
137
+ // The host decided this request may act; the database disagreeing is
138
+ // still an answer, and it goes back as one.
139
+ return rpcError(200, -32001, error instanceof Error ? error.message : String(error), firstId);
140
+ }
141
+ try {
142
+ if (methods.has('initialize')) {
143
+ // The one moment a client is told what this server is, so the moment a
144
+ // database older than it is refused by name. On stdio this is the start
145
+ // of the process; here there is no such thing.
146
+ try {
147
+ await assertSchemaSupported(backend);
148
+ }
149
+ catch (error) {
150
+ return rpcError(200, -32002, error instanceof Error ? error.message : String(error), firstId);
151
+ }
152
+ }
153
+ const needsModules = [...methods].some((method) => typeof method === 'string' && NEEDS_MODULES.has(method));
154
+ const modules = options.modules ?? (needsModules ? await installedModules(backend) : []);
155
+ const server = buildServer(backend, { modules });
156
+ const transport = new WebStandardStreamableHTTPServerTransport({
157
+ sessionIdGenerator: undefined,
158
+ enableJsonResponse: true,
159
+ });
160
+ try {
161
+ await server.connect(transport);
162
+ return await transport.handleRequest(request, { parsedBody: body });
163
+ }
164
+ finally {
165
+ await server.close().catch(() => { });
166
+ }
167
+ }
168
+ finally {
169
+ if (owned)
170
+ await backend.close().catch(() => { });
171
+ }
172
+ }
173
+ function isBackend(value) {
174
+ return typeof value.select === 'function';
175
+ }
176
+ // ---------------------------------------------------------------------------
177
+ // node:http, for a host that is not a function platform
178
+ // ---------------------------------------------------------------------------
179
+ /**
180
+ * The same handler for `node:http` (and anything built on it).
181
+ *
182
+ * The request is read into a web `Request`, answered by `handleHttpRequest`,
183
+ * and the `Response` written back — one path, whichever server a host runs.
184
+ * `origin` is the address this server is reached at, which `node:http` does
185
+ * not know: `https://mcp.example.org`.
186
+ */
187
+ export async function handleNodeRequest(req, res, connection, options) {
188
+ const chunks = [];
189
+ for await (const chunk of req)
190
+ chunks.push(typeof chunk === 'string' ? Buffer.from(chunk) : chunk);
191
+ const headers = new Headers();
192
+ for (const [name, value] of Object.entries(req.headers)) {
193
+ if (value === undefined)
194
+ continue;
195
+ headers.set(name, Array.isArray(value) ? value.join(', ') : value);
196
+ }
197
+ const method = req.method ?? 'GET';
198
+ const request = new Request(new URL(req.url ?? '/', options.origin), {
199
+ method,
200
+ headers,
201
+ ...(method === 'GET' || method === 'HEAD' ? {} : { body: Buffer.concat(chunks) }),
202
+ });
203
+ const response = await handleHttpRequest(request, connection, options);
204
+ res.statusCode = response.status;
205
+ response.headers.forEach((value, name) => res.setHeader(name, value));
206
+ res.end(Buffer.from(await response.arrayBuffer()));
207
+ }
208
+ //# sourceMappingURL=http.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -10,7 +10,8 @@ export { EkwoMcpError, explain, socleCode, type Backend, type Filter, type Order
10
10
  export { ENV, isServiceRoleKey, openBackend, readConfig, type Config } from './config.js';
11
11
  export { amountIn, decimal, money, moneyFields, type Decimal } from '@ekwo-ai/core';
12
12
  export { connect, sqlBackend, type SqlBackendOptions, type SqlClient } from './sql.js';
13
- export { postgrestBackend, type PostgrestBackendOptions } from './postgrest.js';
13
+ export { API_KEY_HEADER, postgrestBackend, type PostgrestBackendOptions } from './postgrest.js';
14
+ export { checkConnection, handleHttpRequest, handleNodeRequest, openHttpBackend, type HttpConnection, type HttpHandlerOptions, } from './http.js';
14
15
  export { SERVER_NAME, SERVER_VERSION, buildServer, type ServerOptions } from './server.js';
15
16
  export { SCHEMA_MIN, assertSchemaSupported, installedSchemaVersion } from './schema.js';
16
17
  export { serverFromEnvironment, type Started } from './start.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EACL,YAAY,EACZ,OAAO,EACP,SAAS,EACT,KAAK,OAAO,EACZ,KAAK,MAAM,EACX,KAAK,KAAK,EACV,KAAK,GAAG,EACR,KAAK,WAAW,GACjB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,GAAG,EAAE,gBAAgB,EAAE,WAAW,EAAE,UAAU,EAAE,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AAC1F,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,KAAK,OAAO,EAAE,MAAM,eAAe,CAAC;AACpF,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,KAAK,iBAAiB,EAAE,KAAK,SAAS,EAAE,MAAM,UAAU,CAAC;AACvF,OAAO,EAAE,gBAAgB,EAAE,KAAK,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AAChF,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,WAAW,EAAE,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAC3F,OAAO,EAAE,UAAU,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AACxF,OAAO,EAAE,qBAAqB,EAAE,KAAK,OAAO,EAAE,MAAM,YAAY,CAAC;AACjE,OAAO,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAChF,OAAO,KAAK,SAAS,MAAM,iBAAiB,CAAC;AAC7C,OAAO,KAAK,UAAU,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EACL,eAAe,EACf,gBAAgB,EAChB,WAAW,EACX,KAAK,UAAU,EACf,KAAK,aAAa,GACnB,MAAM,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EACL,YAAY,EACZ,OAAO,EACP,SAAS,EACT,KAAK,OAAO,EACZ,KAAK,MAAM,EACX,KAAK,KAAK,EACV,KAAK,GAAG,EACR,KAAK,WAAW,GACjB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,GAAG,EAAE,gBAAgB,EAAE,WAAW,EAAE,UAAU,EAAE,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AAC1F,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,KAAK,OAAO,EAAE,MAAM,eAAe,CAAC;AACpF,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,KAAK,iBAAiB,EAAE,KAAK,SAAS,EAAE,MAAM,UAAU,CAAC;AACvF,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,KAAK,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AAChG,OAAO,EACL,eAAe,EACf,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,EACf,KAAK,cAAc,EACnB,KAAK,kBAAkB,GACxB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,WAAW,EAAE,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAC3F,OAAO,EAAE,UAAU,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AACxF,OAAO,EAAE,qBAAqB,EAAE,KAAK,OAAO,EAAE,MAAM,YAAY,CAAC;AACjE,OAAO,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAChF,OAAO,KAAK,SAAS,MAAM,iBAAiB,CAAC;AAC7C,OAAO,KAAK,UAAU,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EACL,eAAe,EACf,gBAAgB,EAChB,WAAW,EACX,KAAK,UAAU,EACf,KAAK,aAAa,GACnB,MAAM,oBAAoB,CAAC"}
package/dist/index.js CHANGED
@@ -10,7 +10,8 @@ export { EkwoMcpError, explain, socleCode, } from './backend.js';
10
10
  export { ENV, isServiceRoleKey, openBackend, readConfig } from './config.js';
11
11
  export { amountIn, decimal, money, moneyFields } from '@ekwo-ai/core';
12
12
  export { connect, sqlBackend } from './sql.js';
13
- export { postgrestBackend } from './postgrest.js';
13
+ export { API_KEY_HEADER, postgrestBackend } from './postgrest.js';
14
+ export { checkConnection, handleHttpRequest, handleNodeRequest, openHttpBackend, } from './http.js';
14
15
  export { SERVER_NAME, SERVER_VERSION, buildServer } from './server.js';
15
16
  export { SCHEMA_MIN, assertSchemaSupported, installedSchemaVersion } from './schema.js';
16
17
  export { serverFromEnvironment } from './start.js';
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EACL,YAAY,EACZ,OAAO,EACP,SAAS,GAMV,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,GAAG,EAAE,gBAAgB,EAAE,WAAW,EAAE,UAAU,EAAe,MAAM,aAAa,CAAC;AAC1F,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,WAAW,EAAgB,MAAM,eAAe,CAAC;AACpF,OAAO,EAAE,OAAO,EAAE,UAAU,EAA0C,MAAM,UAAU,CAAC;AACvF,OAAO,EAAE,gBAAgB,EAAgC,MAAM,gBAAgB,CAAC;AAChF,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,WAAW,EAAsB,MAAM,aAAa,CAAC;AAC3F,OAAO,EAAE,UAAU,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AACxF,OAAO,EAAE,qBAAqB,EAAgB,MAAM,YAAY,CAAC;AACjE,OAAO,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAChF,OAAO,KAAK,SAAS,MAAM,iBAAiB,CAAC;AAC7C,OAAO,KAAK,UAAU,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EACL,eAAe,EACf,gBAAgB,EAChB,WAAW,GAGZ,MAAM,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EACL,YAAY,EACZ,OAAO,EACP,SAAS,GAMV,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,GAAG,EAAE,gBAAgB,EAAE,WAAW,EAAE,UAAU,EAAe,MAAM,aAAa,CAAC;AAC1F,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,WAAW,EAAgB,MAAM,eAAe,CAAC;AACpF,OAAO,EAAE,OAAO,EAAE,UAAU,EAA0C,MAAM,UAAU,CAAC;AACvF,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAgC,MAAM,gBAAgB,CAAC;AAChG,OAAO,EACL,eAAe,EACf,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,GAGhB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,WAAW,EAAsB,MAAM,aAAa,CAAC;AAC3F,OAAO,EAAE,UAAU,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AACxF,OAAO,EAAE,qBAAqB,EAAgB,MAAM,YAAY,CAAC;AACjE,OAAO,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAChF,OAAO,KAAK,SAAS,MAAM,iBAAiB,CAAC;AAC7C,OAAO,KAAK,UAAU,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EACL,eAAe,EACf,gBAAgB,EAChB,WAAW,GAGZ,MAAM,oBAAoB,CAAC"}
@@ -23,7 +23,22 @@ export interface PostgrestBackendOptions {
23
23
  accessToken?: string | undefined;
24
24
  email?: string | undefined;
25
25
  password?: string | undefined;
26
+ /**
27
+ * A key of Ekwo OS — what `create_api_key()` issues — instead of a person.
28
+ *
29
+ * It travels in `X-Ekwo-Api-Key` and the core's `db_pre_request` hook
30
+ * presents it at the top of every request's transaction (decision 0062), so
31
+ * the caller is on the key's company with the key's capabilities and
32
+ * nothing else. Such a request carries **no `Authorization`**: PostgREST
33
+ * decides the role from that header before any function of the core runs,
34
+ * and the hook is what moves the request off `anon`.
35
+ */
36
+ apiKey?: string | undefined;
37
+ /** The `fetch` every request goes through. A host or a test may hand its own. */
38
+ fetch?: typeof globalThis.fetch | undefined;
26
39
  }
40
+ /** The header the core's pre-request hook reads a key out of. */
41
+ export declare const API_KEY_HEADER = "X-Ekwo-Api-Key";
27
42
  /**
28
43
  * Signs in, or takes the token it was given, and returns the backend.
29
44
  *
@@ -1 +1 @@
1
- {"version":3,"file":"postgrest.d.ts","sourceRoot":"","sources":["../src/postgrest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAGH,OAAO,EAIL,KAAK,OAAO,EAIb,MAAM,cAAc,CAAC;AAuDtB,MAAM,WAAW,uBAAuB;IACtC,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,uEAAuE;IACvE,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC/B;AAED;;;;;;GAMG;AACH,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,uBAAuB,GAAG,OAAO,CAAC,OAAO,CAAC,CAkHzF"}
1
+ {"version":3,"file":"postgrest.d.ts","sourceRoot":"","sources":["../src/postgrest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAGH,OAAO,EAIL,KAAK,OAAO,EAIb,MAAM,cAAc,CAAC;AAuDtB,MAAM,WAAW,uBAAuB;IACtC,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,uEAAuE;IACvE,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B;;;;;;;;;OASG;IACH,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,iFAAiF;IACjF,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,GAAG,SAAS,CAAC;CAC7C;AAED,iEAAiE;AACjE,eAAO,MAAM,cAAc,mBAAmB,CAAC;AAkB/C;;;;;;GAMG;AACH,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,uBAAuB,GAAG,OAAO,CAAC,OAAO,CAAC,CA2HzF"}