@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 +115 -13
- package/dist/bin.js +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.js +1 -1
- package/dist/http.d.ts +92 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +208 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/postgrest.d.ts +15 -0
- package/dist/postgrest.d.ts.map +1 -1
- package/dist/postgrest.js +32 -4
- package/dist/postgrest.js.map +1 -1
- package/dist/schema.d.ts +3 -3
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +3 -3
- package/dist/schema.js.map +1 -1
- package/dist/server.d.ts +2 -2
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +35 -10
- package/dist/server.js.map +1 -1
- package/dist/tools/import-books.d.ts +22 -0
- package/dist/tools/import-books.d.ts.map +1 -1
- package/dist/tools/import-books.js +20 -4
- package/dist/tools/import-books.js.map +1 -1
- package/dist/tools/modules.d.ts +16 -3
- package/dist/tools/modules.d.ts.map +1 -1
- package/dist/tools/modules.js +25 -19
- package/dist/tools/modules.js.map +1 -1
- package/dist/tools/read.d.ts.map +1 -1
- package/dist/tools/read.js +12 -5
- package/dist/tools/read.js.map +1 -1
- package/dist/tools/write.d.ts +24 -5
- package/dist/tools/write.d.ts.map +1 -1
- package/dist/tools/write.js +64 -14
- package/dist/tools/write.js.map +1 -1
- package/package.json +5 -5
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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;
|
|
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
|
|
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`
|
|
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: `
|
|
208
|
-
`
|
|
209
|
-
`
|
|
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 —
|
|
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
|
|
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
|
|
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
|
|
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
|
package/dist/http.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAGH,OAAO,EAAE,wCAAwC,EAAE,MAAM,+DAA+D,CAAC;AACzH,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACrE,OAAO,EAAE,YAAY,EAAgB,MAAM,cAAc,CAAC;AAC1D,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAyBtD,MAAM,OAAO,GAAG,aAAa,CAAC;AAC9B,MAAM,OAAO,GAAG,8DAA8D,CAAC;AAE/E,oFAAoF;AACpF,SAAS,QAAQ,CAAC,MAAc,EAAE,IAAY,EAAE,OAAe,EAAE,KAAc,IAAI;IACjF,OAAO,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE;QACpF,MAAM;QACN,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;KAChD,CAAC,CAAC;AACL,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAAC,UAA0B;IACxD,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,GAAG,UAAU,CAAC;IACjE,IAAI,WAAW,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACnE,MAAM,IAAI,YAAY,CAAC,wFAAwF,CAAC,CAAC;IACnH,CAAC;IACD,IAAI,gBAAgB,CAAC,OAAO,CAAC,EAAE,CAAC;QAC9B,MAAM,IAAI,YAAY,CAAC,kBAAkB,CAAC,uCAAuC,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;IAClH,CAAC;IACD,MAAM,QAAQ,GAAG,WAAW,KAAK,SAAS,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC,CAAC;IACrE,MAAM,MAAM,GAAG,MAAM,KAAK,SAAS,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;IACzD,IAAI,QAAQ,IAAI,gBAAgB,CAAC,WAAW,CAAC,EAAE,CAAC;QAC9C,MAAM,IAAI,YAAY,CAAC,kBAAkB,CAAC,oCAAoC,EAAE,eAAe,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;IACtH,CAAC;IACD,IAAI,MAAM,IAAI,gBAAgB,CAAC,MAAM,CAAC,EAAE,CAAC;QACvC,MAAM,IAAI,YAAY,CAAC,kBAAkB,CAAC,2BAA2B,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;IACtG,CAAC;IACD,IAAI,QAAQ,KAAK,MAAM,EAAE,CAAC;QACxB,MAAM,IAAI,YAAY,CACpB,QAAQ;YACN,CAAC,CAAC,+FAA+F;YACjG,CAAC,CAAC,+HAA+H,CACpI,CAAC;IACJ,CAAC;AACH,CAAC;AAED,gEAAgE;AAChE,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,UAA0B,EAC1B,UAA2D,EAAE;IAE7D,eAAe,CAAC,UAAU,CAAC,CAAC;IAC5B,OAAO,gBAAgB,CAAC;QACtB,WAAW,EAAE,UAAU,CAAC,WAAW;QACnC,OAAO,EAAE,UAAU,CAAC,OAAO;QAC3B,WAAW,EAAE,UAAU,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS;QACjF,MAAM,EAAE,UAAU,CAAC,MAAM;QACzB,KAAK,EAAE,OAAO,CAAC,KAAK;KACrB,CAAC,CAAC;AACL,CAAC;AAED,uEAAuE;AACvE,SAAS,UAAU,CAAC,IAAa;IAC/B,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC;QAAE,OAAO,IAA8B,CAAC;IAC/D,IAAI,IAAI,KAAK,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ;QAAE,OAAO,CAAC,IAA4B,CAAC,CAAC;IACrF,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,6EAA6E;AAC7E,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC,CAAC,YAAY,EAAE,YAAY,CAAC,CAAC,CAAC;AAE5D;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,OAAgB,EAChB,UAAoC,EACpC,UAA8B,EAAE;IAEhC,IAAI,OAAO,CAAC,MAAM,KAAK,KAAK,IAAI,OAAO,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;QAC5D,kEAAkE;QAClE,6CAA6C;QAC7C,OAAO,IAAI,QAAQ,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC;IACzE,CAAC;IACD,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;QAC9B,OAAO,IAAI,QAAQ,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC;IACzE,CAAC;IAED,IAAI,IAAa,CAAC;IAClB,IAAI,CAAC;QACH,IAAI,GAAG,MAAM,OAAO,CAAC,IAAI,EAAE,CAAC;IAC9B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,QAAQ,CAAC,GAAG,EAAE,CAAC,KAAK,EAAE,oCAAoC,CAAC,CAAC;IACrE,CAAC;IACD,MAAM,QAAQ,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;IAClC,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;IACnE,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,EAAE,KAAK,SAAS,CAAC,EAAE,EAAE,IAAI,IAAI,CAAC;IAEjF,IAAI,OAAgB,CAAC;IACrB,MAAM,KAAK,GAAG,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;IACrC,IAAI,CAAC;QACH,OAAO,GAAG,SAAS,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,MAAM,eAAe,CAAC,UAAU,EAAE,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;IAC7G,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,sEAAsE;QACtE,qEAAqE;QACrE,4CAA4C;QAC5C,OAAO,QAAQ,CAAC,GAAG,EAAE,CAAC,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC,CAAC;IAChG,CAAC;IAED,IAAI,CAAC;QACH,IAAI,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,EAAE,CAAC;YAC9B,uEAAuE;YACvE,wEAAwE;YACxE,+CAA+C;YAC/C,IAAI,CAAC;gBACH,MAAM,qBAAqB,CAAC,OAAO,CAAC,CAAC;YACvC,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,OAAO,QAAQ,CAAC,GAAG,EAAE,CAAC,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC,CAAC;YAChG,CAAC;QACH,CAAC;QAED,MAAM,YAAY,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,OAAO,MAAM,KAAK,QAAQ,IAAI,aAAa,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;QAC5G,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,MAAM,gBAAgB,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAEzF,MAAM,MAAM,GAAG,WAAW,CAAC,OAAO,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;QACjD,MAAM,SAAS,GAAG,IAAI,wCAAwC,CAAC;YAC7D,kBAAkB,EAAE,SAAS;YAC7B,kBAAkB,EAAE,IAAI;SACzB,CAAC,CAAC;QACH,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;YAChC,OAAO,MAAM,SAAS,CAAC,aAAa,CAAC,OAAO,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC;QACtE,CAAC;gBAAS,CAAC;YACT,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QACvC,CAAC;IACH,CAAC;YAAS,CAAC;QACT,IAAI,KAAK;YAAE,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IACnD,CAAC;AACH,CAAC;AAED,SAAS,SAAS,CAAC,KAA+B;IAChD,OAAO,OAAQ,KAAiB,CAAC,MAAM,KAAK,UAAU,CAAC;AACzD,CAAC;AAED,8EAA8E;AAC9E,wDAAwD;AACxD,8EAA8E;AAG9E;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,GAAoB,EACpB,GAAmB,EACnB,UAAoC,EACpC,OAAgD;IAEhD,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG;QAAE,MAAM,CAAC,IAAI,CAAC,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAE,KAAgB,CAAC,CAAC;IAC/G,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;IAC9B,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;QACxD,IAAI,KAAK,KAAK,SAAS;YAAE,SAAS;QAClC,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;IACrE,CAAC;IACD,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,IAAI,KAAK,CAAC;IACnC,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,GAAG,EAAE,OAAO,CAAC,MAAM,CAAC,EAAE;QACnE,MAAM;QACN,OAAO;QACP,GAAG,CAAC,MAAM,KAAK,KAAK,IAAI,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;KAClF,CAAC,CAAC;IACH,MAAM,QAAQ,GAAG,MAAM,iBAAiB,CAAC,OAAO,EAAE,UAAU,EAAE,OAAO,CAAC,CAAC;IACvE,GAAG,CAAC,UAAU,GAAG,QAAQ,CAAC,MAAM,CAAC;IACjC,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;IACtE,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;AACrD,CAAC"}
|
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';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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;
|
|
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;
|
|
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"}
|
package/dist/postgrest.d.ts
CHANGED
|
@@ -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
|
*
|
package/dist/postgrest.d.ts.map
CHANGED
|
@@ -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;
|
|
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"}
|