@ekwo-ai/mcp 0.8.0 → 0.9.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 +72 -3
- package/dist/bin.js +1 -1
- package/dist/columns.d.ts +59 -0
- package/dist/columns.d.ts.map +1 -0
- package/dist/columns.js +422 -0
- package/dist/columns.js.map +1 -0
- package/dist/config.d.ts +1 -1
- package/dist/config.js +1 -1
- package/dist/format.d.ts +21 -0
- package/dist/format.d.ts.map +1 -0
- package/dist/format.js +49 -0
- package/dist/format.js.map +1 -0
- 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/rounding.d.ts +25 -0
- package/dist/rounding.d.ts.map +1 -0
- package/dist/rounding.js +35 -0
- package/dist/rounding.js.map +1 -0
- package/dist/schema.d.ts +3 -3
- package/dist/schema.js +3 -3
- package/dist/server.d.ts +1 -1
- package/dist/server.js +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/read.d.ts.map +1 -1
- package/dist/tools/read.js +11 -4
- package/dist/tools/read.js.map +1 -1
- package/dist/tools/write.d.ts +1 -1
- package/dist/tools/write.js +1 -1
- package/package.json +5 -5
package/dist/format.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Amounts as decimal strings, everywhere, in both directions.
|
|
3
|
+
*
|
|
4
|
+
* Postgres holds `numeric` exactly and hands it over as text; a table read
|
|
5
|
+
* asks for `amount::text` so that is what arrives. The one place a float can
|
|
6
|
+
* appear is a function result crossing PostgREST as JSON, where `1210.00`
|
|
7
|
+
* becomes a number before any code of ours sees it. `money()` puts it back to
|
|
8
|
+
* two decimals, which is exact for every amount a ledger holds, and a model
|
|
9
|
+
* reading `"1210.00"` never has to wonder whether `1210.0000000001` was the
|
|
10
|
+
* VAT rounding or the transport.
|
|
11
|
+
*/
|
|
12
|
+
import { EkwoMcpError } from './backend.js';
|
|
13
|
+
import { roundCurrency } from './rounding.js';
|
|
14
|
+
/** A numeric, as the decimal string the ledger holds. */
|
|
15
|
+
export function money(value) {
|
|
16
|
+
if (value === null || value === undefined)
|
|
17
|
+
return null;
|
|
18
|
+
if (typeof value === 'number')
|
|
19
|
+
return roundCurrency(value, 2).toFixed(2);
|
|
20
|
+
const text = String(value);
|
|
21
|
+
return text.length === 0 ? null : text;
|
|
22
|
+
}
|
|
23
|
+
/** The same, for a quantity or a rate, where two decimals would be a lie. */
|
|
24
|
+
export function decimal(value) {
|
|
25
|
+
if (value === null || value === undefined)
|
|
26
|
+
return null;
|
|
27
|
+
const text = String(value);
|
|
28
|
+
return text.length === 0 ? null : text;
|
|
29
|
+
}
|
|
30
|
+
/** Applies `money()` to the named keys of every row. */
|
|
31
|
+
export function moneyFields(rows, keys) {
|
|
32
|
+
return rows.map((row) => {
|
|
33
|
+
const out = { ...row };
|
|
34
|
+
for (const key of keys) {
|
|
35
|
+
if (key in out)
|
|
36
|
+
out[key] = money(out[key]);
|
|
37
|
+
}
|
|
38
|
+
return out;
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
/** An amount on its way into the database: `"1210.00"`, never 1210.0000001. */
|
|
42
|
+
export function amountIn(value) {
|
|
43
|
+
const text = typeof value === 'number' ? roundCurrency(value, 2).toFixed(2) : value.trim();
|
|
44
|
+
if (!/^-?\d+(\.\d+)?$/.test(text)) {
|
|
45
|
+
throw new EkwoMcpError(`bad_amount: ${String(value)} is not a decimal amount`);
|
|
46
|
+
}
|
|
47
|
+
return text;
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=format.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.js","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAI9C,yDAAyD;AACzD,MAAM,UAAU,KAAK,CAAC,KAAc;IAClC,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IACvD,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,aAAa,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IACzE,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAC3B,OAAO,IAAI,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;AACzC,CAAC;AAED,6EAA6E;AAC7E,MAAM,UAAU,OAAO,CAAC,KAAc;IACpC,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IACvD,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAC3B,OAAO,IAAI,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;AACzC,CAAC;AAED,wDAAwD;AACxD,MAAM,UAAU,WAAW,CAAoC,IAAS,EAAE,IAAc;IACtF,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE;QACtB,MAAM,GAAG,GAA4B,EAAE,GAAG,GAAG,EAAE,CAAC;QAChD,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;YACvB,IAAI,GAAG,IAAI,GAAG;gBAAE,GAAG,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;QAC7C,CAAC;QACD,OAAO,GAAQ,CAAC;IAClB,CAAC,CAAC,CAAC;AACL,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,QAAQ,CAAC,KAAsB;IAC7C,MAAM,IAAI,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,aAAa,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;IAC3F,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAClC,MAAM,IAAI,YAAY,CAAC,eAAe,MAAM,CAAC,KAAK,CAAC,0BAA0B,CAAC,CAAC;IACjF,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
|
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":"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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';
|
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"}
|
package/dist/postgrest.js
CHANGED
|
@@ -50,6 +50,23 @@ function unwrap(answer, what) {
|
|
|
50
50
|
}
|
|
51
51
|
return answer.data;
|
|
52
52
|
}
|
|
53
|
+
/** The header the core's pre-request hook reads a key out of. */
|
|
54
|
+
export const API_KEY_HEADER = 'X-Ekwo-Api-Key';
|
|
55
|
+
/**
|
|
56
|
+
* A `fetch` that never sends `Authorization`.
|
|
57
|
+
*
|
|
58
|
+
* `supabase-js` puts the publishable key in `Authorization` when nothing else
|
|
59
|
+
* is there. With a key of Ekwo OS on the request that is the one header that
|
|
60
|
+
* must be absent, so it is taken off on the way out rather than trusted not to
|
|
61
|
+
* be added.
|
|
62
|
+
*/
|
|
63
|
+
function withoutAuthorization(inner) {
|
|
64
|
+
return (input, init) => {
|
|
65
|
+
const headers = new Headers(init?.headers ?? (input instanceof Request ? input.headers : undefined));
|
|
66
|
+
headers.delete('authorization');
|
|
67
|
+
return inner(input, { ...init, headers });
|
|
68
|
+
};
|
|
69
|
+
}
|
|
53
70
|
/**
|
|
54
71
|
* Signs in, or takes the token it was given, and returns the backend.
|
|
55
72
|
*
|
|
@@ -58,20 +75,31 @@ function unwrap(answer, what) {
|
|
|
58
75
|
* server outlives the first hour without a credential cache existing.
|
|
59
76
|
*/
|
|
60
77
|
export async function postgrestBackend(options) {
|
|
78
|
+
const apiKey = options.apiKey !== undefined && options.apiKey.length > 0 ? options.apiKey : undefined;
|
|
61
79
|
const headers = {};
|
|
62
|
-
if (
|
|
80
|
+
if (apiKey !== undefined) {
|
|
81
|
+
headers[API_KEY_HEADER] = apiKey;
|
|
82
|
+
}
|
|
83
|
+
else if (options.accessToken !== undefined && options.accessToken.length > 0) {
|
|
63
84
|
headers['Authorization'] = `Bearer ${options.accessToken}`;
|
|
64
85
|
}
|
|
86
|
+
const baseFetch = options.fetch ?? globalThis.fetch;
|
|
65
87
|
const client = createClient(options.supabaseUrl, options.anonKey, {
|
|
66
88
|
auth: {
|
|
67
89
|
persistSession: false,
|
|
68
|
-
autoRefreshToken: options.accessToken === undefined,
|
|
90
|
+
autoRefreshToken: apiKey === undefined && options.accessToken === undefined,
|
|
69
91
|
detectSessionInUrl: false,
|
|
70
92
|
},
|
|
71
|
-
global: { headers },
|
|
93
|
+
global: { headers, fetch: apiKey === undefined ? baseFetch : withoutAuthorization(baseFetch) },
|
|
72
94
|
});
|
|
73
95
|
let actingAs;
|
|
74
|
-
if (
|
|
96
|
+
if (apiKey !== undefined) {
|
|
97
|
+
// A key is not a person: there is nobody to sign in and no user id to
|
|
98
|
+
// report. What it may do is decided by the capabilities it carries, on
|
|
99
|
+
// the one company it was issued for.
|
|
100
|
+
actingAs = undefined;
|
|
101
|
+
}
|
|
102
|
+
else if (options.accessToken === undefined || options.accessToken.length === 0) {
|
|
75
103
|
if (options.email === undefined || options.password === undefined) {
|
|
76
104
|
throw new EkwoMcpError('no_credentials: set EKWO_EMAIL and EKWO_PASSWORD, or EKWO_ACCESS_TOKEN');
|
|
77
105
|
}
|
package/dist/postgrest.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"postgrest.js","sourceRoot":"","sources":["../src/postgrest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,YAAY,EAAuB,MAAM,uBAAuB,CAAC;AAC1E,OAAO,EACL,YAAY,EACZ,OAAO,EACP,UAAU,GAKX,MAAM,cAAc,CAAC;AAetB,SAAS,YAAY,CAA0B,OAAU,EAAE,OAA6B;IACtF,IAAI,GAAG,GAAG,OAAO,CAAC;IAClB,KAAK,MAAM,MAAM,IAAI,OAAO,IAAI,EAAE,EAAE,CAAC;QACnC,MAAM,MAAM,GAAG,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QACzC,QAAQ,MAAM,CAAC,EAAE,EAAE,CAAC;YAClB,KAAK,IAAI;gBACP,GAAG,GAAG,GAAG,CAAC,EAAE,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;gBAC3B,MAAM;YACR,KAAK,IAAI;gBACP,GAAG,GAAG,GAAG,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,KAAc,CAAC,CAAC;gBAC5C,MAAM;YACR,KAAK,OAAO;gBACV,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;gBACtC,MAAM;YACR;gBACE,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,KAAc,CAAC,CAAC;QACxD,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAOD,4EAA4E;AAC5E,SAAS,MAAM,CAAI,MAAiB,EAAE,IAAY;IAChD,IAAI,MAAM,CAAC,KAAK,KAAK,IAAI,EAAE,CAAC;QAC1B,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,MAAM,CAAC,KAAK,CAAC;QAChD,iEAAiE;QACjE,MAAM,IAAI,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAChH,MAAM,OAAO,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,SAAS,CAAC,CAAC;IAC3D,CAAC;IACD,IAAI,MAAM,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;QACzB,MAAM,IAAI,YAAY,CAAC,GAAG,IAAI,qBAAqB,CAAC,CAAC;IACvD,CAAC;IACD,OAAO,MAAM,CAAC,IAAI,CAAC;AACrB,CAAC;
|
|
1
|
+
{"version":3,"file":"postgrest.js","sourceRoot":"","sources":["../src/postgrest.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one rounding rule of Ekwo: **half up, on the absolute value, at the
|
|
3
|
+
* currency's decimals**.
|
|
4
|
+
*
|
|
5
|
+
* Three packages used to answer this differently — `Math.round((v + EPSILON) *
|
|
6
|
+
* 100) / 100`, `Math.round(v * 100) / 100` and `Number.prototype.toFixed(2)` —
|
|
7
|
+
* and the three disagreed on the two cases that matter: a negative half, where
|
|
8
|
+
* `Math.round` goes towards positive infinity and turns -0.005 into -0.00, and
|
|
9
|
+
* a value a binary float cannot hold, where 2.675 becomes 2.67 because
|
|
10
|
+
* `2.675 * 100` is really 267.49999999999994. A credit note is a sale with the
|
|
11
|
+
* sign flipped; a rule that treats the two differently puts a cent between an
|
|
12
|
+
* invoice and its credit note, and the cent is found by the person filing the
|
|
13
|
+
* return.
|
|
14
|
+
*
|
|
15
|
+
* Half up on the absolute value is what an invoice, a VAT return and a set of
|
|
16
|
+
* annual accounts are written with, and it is symmetric by construction: the
|
|
17
|
+
* rounding of -x is the rounding of x with the sign put back.
|
|
18
|
+
*
|
|
19
|
+
* The file is deliberately identical in every package that holds a copy. A
|
|
20
|
+
* format brick may not import the core or another brick, so the rule is
|
|
21
|
+
* duplicated rather than shared — and `tests/rounding.test.ts` reads the
|
|
22
|
+
* copies and refuses one that has drifted. See `docs/decisions.md`.
|
|
23
|
+
*/
|
|
24
|
+
export declare function roundCurrency(value: number, decimals?: number): number;
|
|
25
|
+
//# sourceMappingURL=rounding.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rounding.d.ts","sourceRoot":"","sources":["../src/rounding.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,SAAI,GAAG,MAAM,CAUjE"}
|
package/dist/rounding.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one rounding rule of Ekwo: **half up, on the absolute value, at the
|
|
3
|
+
* currency's decimals**.
|
|
4
|
+
*
|
|
5
|
+
* Three packages used to answer this differently — `Math.round((v + EPSILON) *
|
|
6
|
+
* 100) / 100`, `Math.round(v * 100) / 100` and `Number.prototype.toFixed(2)` —
|
|
7
|
+
* and the three disagreed on the two cases that matter: a negative half, where
|
|
8
|
+
* `Math.round` goes towards positive infinity and turns -0.005 into -0.00, and
|
|
9
|
+
* a value a binary float cannot hold, where 2.675 becomes 2.67 because
|
|
10
|
+
* `2.675 * 100` is really 267.49999999999994. A credit note is a sale with the
|
|
11
|
+
* sign flipped; a rule that treats the two differently puts a cent between an
|
|
12
|
+
* invoice and its credit note, and the cent is found by the person filing the
|
|
13
|
+
* return.
|
|
14
|
+
*
|
|
15
|
+
* Half up on the absolute value is what an invoice, a VAT return and a set of
|
|
16
|
+
* annual accounts are written with, and it is symmetric by construction: the
|
|
17
|
+
* rounding of -x is the rounding of x with the sign put back.
|
|
18
|
+
*
|
|
19
|
+
* The file is deliberately identical in every package that holds a copy. A
|
|
20
|
+
* format brick may not import the core or another brick, so the rule is
|
|
21
|
+
* duplicated rather than shared — and `tests/rounding.test.ts` reads the
|
|
22
|
+
* copies and refuses one that has drifted. See `docs/decisions.md`.
|
|
23
|
+
*/
|
|
24
|
+
export function roundCurrency(value, decimals = 2) {
|
|
25
|
+
const factor = 10 ** decimals;
|
|
26
|
+
const scaled = Math.abs(value) * factor;
|
|
27
|
+
// The nudge is proportional to the number, which is what makes it work at
|
|
28
|
+
// 1.005 and at 1000000.005 alike, and far too small to move a value that is
|
|
29
|
+
// not already within a bit of the halfway point.
|
|
30
|
+
const rounded = Math.round(scaled + Number.EPSILON * scaled);
|
|
31
|
+
// `-0` is a value of its own in JavaScript and it prints as "-0.00".
|
|
32
|
+
// Adding zero turns it back into `0`.
|
|
33
|
+
return (value < 0 ? -rounded : rounded) / factor + 0;
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=rounding.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rounding.js","sourceRoot":"","sources":["../src/rounding.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,UAAU,aAAa,CAAC,KAAa,EAAE,QAAQ,GAAG,CAAC;IACvD,MAAM,MAAM,GAAG,EAAE,IAAI,QAAQ,CAAC;IAC9B,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,MAAM,CAAC;IACxC,0EAA0E;IAC1E,4EAA4E;IAC5E,iDAAiD;IACjD,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,OAAO,GAAG,MAAM,CAAC,CAAC;IAC7D,qEAAqE;IACrE,sCAAsC;IACtC,OAAO,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,MAAM,GAAG,CAAC,CAAC;AACvD,CAAC"}
|
package/dist/schema.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* What this server needs of the database it is pointed at.
|
|
3
3
|
*
|
|
4
|
-
* An MCP client is installed by whoever wants an
|
|
4
|
+
* An MCP client is installed by whoever wants an agent, and the database
|
|
5
5
|
* it reaches was installed by whoever runs the books — two decisions, two
|
|
6
6
|
* dates. So the server asks the schema its version before it answers anything,
|
|
7
7
|
* and refuses one it is too new for, by name. The alternative is what an
|
|
8
|
-
*
|
|
8
|
+
* agent does with a missing column: it improvises, and the improvisation
|
|
9
9
|
* is an accounting entry.
|
|
10
10
|
*
|
|
11
11
|
* Declared in `package.json` under `ekwo.schemaMin`, as a country pack
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import { type Backend } from './backend.js';
|
|
16
16
|
/** The oldest schema this server speaks to. */
|
|
17
|
-
export declare const SCHEMA_MIN = "0.
|
|
17
|
+
export declare const SCHEMA_MIN = "0.9.0";
|
|
18
18
|
/** The version the migrations of that database define. */
|
|
19
19
|
export declare function installedSchemaVersion(backend: Backend): Promise<string | undefined>;
|
|
20
20
|
/**
|