okengine 0.23.0 → 0.23.1
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/package.json +1 -1
- package/site/content/docs/ai/mcp.mdx +1 -1
- package/site/content/docs/elements/channel/index.mdx +4 -4
- package/site/content/docs/elements/channel/receipts.mdx +7 -5
- package/site/content/docs/elements/channel/sms.mdx +2 -1
- package/site/content/docs/elements/flow/index.mdx +22 -18
- package/site/content/docs/elements/signal/index.mdx +15 -14
- package/site/content/docs/plugins/otp.mdx +3 -3
- package/site/content/docs/reference/fx.mdx +12 -4
- package/src/auth/tenants.ts +17 -0
- package/src/cli/doctor-diff.test.ts +29 -1
- package/src/cli/doctor-diff.ts +35 -0
- package/src/cli/manifest-pr-diff.ts +16 -0
- package/src/compiler/effects-fetch.test.ts +77 -0
- package/src/compiler/effects-infer.ts +87 -16
- package/src/compiler/effects-join.test.ts +48 -0
- package/src/compiler/extract.test.ts +28 -0
- package/src/compiler/extract.ts +11 -1
- package/src/compiler/fx-index.ts +58 -4
- package/src/console/index.ts +1 -1
- package/src/console/ui-next/dist/assets/{access-page-DDKkhT9v.js → access-page-C_N4qvai.js} +1 -1
- package/src/console/ui-next/dist/assets/{agent-disclosure-62Xts8Xi.js → agent-disclosure-Cq1TxniW.js} +1 -1
- package/src/console/ui-next/dist/assets/{cache-glyph-DyeoKHKA.js → cache-glyph-BaI4uxZz.js} +1 -1
- package/src/console/ui-next/dist/assets/{call-pii-button-DvfpLXsU.js → call-pii-button-DNyK7IIY.js} +1 -1
- package/src/console/ui-next/dist/assets/{collapsible-BY03SeCg.js → collapsible-CRhBJMZR.js} +1 -1
- package/src/console/ui-next/dist/assets/{decisions-page-CgkKlA56.js → decisions-page-D9twj8Xy.js} +1 -1
- package/src/console/ui-next/dist/assets/{duration-tone-Y6HLjyJ5.js → duration-tone-CsHWlVPU.js} +1 -1
- package/src/console/ui-next/dist/assets/{flows-page-DLPfNy-_.js → flows-page-ZaTJAhR9.js} +1 -1
- package/src/console/ui-next/dist/assets/{highlighted-json-D7nNzpgB.js → highlighted-json-DQ-EMAfz.js} +1 -1
- package/src/console/ui-next/dist/assets/{http-method-DdL19zzh.js → http-method-Cn_Fl79s.js} +1 -1
- package/src/console/ui-next/dist/assets/{index-C0lc_s9d.js → index-CmRFeWdT.js} +3 -3
- package/src/console/ui-next/dist/assets/{observability-page-CRlcyLCC.js → observability-page-Cn6R_dXz.js} +1 -1
- package/src/console/ui-next/dist/assets/{replica-lag-Bb3FWUu9.js → replica-lag-Bx6EZhSe.js} +1 -1
- package/src/console/ui-next/dist/assets/{request-meta-DYBMWhX8.js → request-meta-DmdiUrfh.js} +1 -1
- package/src/console/ui-next/dist/assets/{store-page-CcE-SXC8.js → store-page-8P6vGXnn.js} +1 -1
- package/src/console/ui-next/dist/assets/{trace-detail-sheet-CKMSEQ4s.js → trace-detail-sheet-BfGvpFSv.js} +1 -1
- package/src/console/ui-next/dist/assets/{tree-expand-toggle-QOfk0M0H.js → tree-expand-toggle-CWdUHbc0.js} +1 -1
- package/src/console/ui-next/dist/assets/{units-page-DKC1CDDd.js → units-page-CZOFZzay.js} +1 -1
- package/src/console/ui-next/dist/assets/{vault-page-B54v4Yit.js → vault-page-ki_ishmX.js} +1 -1
- package/src/console/ui-next/dist/index.html +1 -1
- package/src/drivers/journal-postgres.test.ts +22 -1
- package/src/drivers/journal-postgres.ts +223 -12
- package/src/drivers/signal-compete.test.ts +234 -0
- package/src/drivers/signal-postgres.ts +39 -19
- package/src/drivers/signal-redis.ts +167 -11
- package/src/drivers/signal-types.ts +12 -0
- package/src/elements/ai/mcp-protocol.ts +5 -6
- package/src/elements/ai/runtime.ts +20 -19
- package/src/elements/channel/runtime.ts +11 -0
- package/src/elements/channel/sql-ledger.ts +251 -0
- package/src/elements/signal/runtime.ts +9 -0
- package/src/elements/signal.test.ts +1 -0
- package/src/elements/store/cache.test.ts +120 -15
- package/src/elements/store/cache.ts +200 -39
- package/src/elements/store/prepare-row.test.ts +6 -2
- package/src/elements/store/sql-session.test.ts +2 -0
- package/src/elements/store/sql-session.ts +95 -9
- package/src/elements/store.ts +1 -0
- package/src/kernel/app.ts +32 -16
- package/src/kernel/auto-cache.test.ts +228 -0
- package/src/kernel/boot-bind/channel.ts +63 -2
- package/src/kernel/boot-bind/honor-config.test.ts +40 -10
- package/src/kernel/boot-bind/signal.ts +61 -25
- package/src/kernel/boot.ts +16 -2
- package/src/kernel/errors-compiler.ts +11 -0
- package/src/kernel/errors-text.ts +16 -0
- package/src/kernel/errors.ts +8 -0
- package/src/kernel/flow.ts +33 -3
- package/src/kernel/fx-call-types.test.ts +32 -0
- package/src/kernel/fx-decide.ts +11 -7
- package/src/kernel/fx-sql-handle.ts +46 -7
- package/src/kernel/fx.test.ts +1 -3
- package/src/kernel/fx.ts +27 -16
- package/src/kernel/idempotency.test.ts +2 -1
- package/src/kernel/journal.test.ts +114 -0
- package/src/kernel/journal.ts +369 -53
- package/src/kernel/pipeline-tenant.ts +5 -1
- package/src/kernel/pipeline.ts +5 -0
- package/src/kernel/signal-tx.ts +28 -0
- package/src/kernel/store-transaction.test.ts +95 -0
- package/src/mcp/docs-server.ts +19 -2
- package/src/mcp/mcp.test.ts +83 -0
- package/src/mcp/protocol.ts +8 -2
- package/src/mcp/server.ts +23 -2
- package/src/mcp/versions.ts +60 -0
- package/src/plugins/index.ts +1 -0
- package/src/plugins/mena.ts +56 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `fx.store().transaction` publishes signal emits only after commit.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, expect, test } from "bun:test";
|
|
6
|
+
import type { SqlConnection } from "../drivers/types.ts";
|
|
7
|
+
import { memorySignalDriver } from "../drivers/signal-memory.ts";
|
|
8
|
+
import { signal } from "../elements/signal.ts";
|
|
9
|
+
import { store } from "../elements/store.ts";
|
|
10
|
+
import { createSqlStoreHandle } from "../elements/store/sql-session.ts";
|
|
11
|
+
import type { StoreRuntime } from "../elements/store/runtime.ts";
|
|
12
|
+
import { createFxContext } from "./fx.ts";
|
|
13
|
+
import type { SignalRuntime } from "../elements/signal/runtime.ts";
|
|
14
|
+
|
|
15
|
+
function fakeSql(): SqlConnection {
|
|
16
|
+
return {
|
|
17
|
+
driverId: "memory",
|
|
18
|
+
role: "primary",
|
|
19
|
+
async query() {
|
|
20
|
+
return [];
|
|
21
|
+
},
|
|
22
|
+
async exec() {
|
|
23
|
+
return { changes: 1 };
|
|
24
|
+
},
|
|
25
|
+
async transaction(fn) {
|
|
26
|
+
return fn(this);
|
|
27
|
+
},
|
|
28
|
+
async close() {},
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
describe("fx.store().transaction", () => {
|
|
33
|
+
test("commit publishes the staged emit; rollback drops it", async () => {
|
|
34
|
+
const ping = signal.once("ping");
|
|
35
|
+
const signals = new Map([[ping.name, ping]]);
|
|
36
|
+
const bus = await memorySignalDriver.open({ signals });
|
|
37
|
+
const got: unknown[] = [];
|
|
38
|
+
await bus.subscribe("ping", "c", async (message) => {
|
|
39
|
+
got.push(message.payload);
|
|
40
|
+
});
|
|
41
|
+
const signalRuntime: SignalRuntime = {
|
|
42
|
+
driverId: "memory",
|
|
43
|
+
get bus() {
|
|
44
|
+
return bus;
|
|
45
|
+
},
|
|
46
|
+
declarations: signals,
|
|
47
|
+
register(decl) {
|
|
48
|
+
signals.set(decl.name, decl);
|
|
49
|
+
},
|
|
50
|
+
async start() {
|
|
51
|
+
return bus;
|
|
52
|
+
},
|
|
53
|
+
emit: (name, payload, options) => bus.emit(name, payload, options),
|
|
54
|
+
deadLetters: (name) => bus.deadLetters(name),
|
|
55
|
+
live: (name, opts) => bus.live(name, opts),
|
|
56
|
+
checkLiveResume: (name, afterId) => bus.checkLiveResume(name, afterId),
|
|
57
|
+
async close() {
|
|
58
|
+
await bus.close();
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
const notes = { name: "notes" };
|
|
62
|
+
const storeRuntime = {
|
|
63
|
+
async open() {
|
|
64
|
+
return createSqlStoreHandle("sql:db", {
|
|
65
|
+
connection: fakeSql(),
|
|
66
|
+
classifications: new Map(),
|
|
67
|
+
routedRole: "primary",
|
|
68
|
+
});
|
|
69
|
+
},
|
|
70
|
+
} as unknown as StoreRuntime;
|
|
71
|
+
const db = store.sql("db");
|
|
72
|
+
const { fx } = createFxContext({
|
|
73
|
+
flow: "notes.write",
|
|
74
|
+
effects: { writes: ["sql:notes", "sql:db"], emits: ["ping"] },
|
|
75
|
+
storeRuntime,
|
|
76
|
+
signalRuntime,
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
await fx.store(db).transaction(async (tx) => {
|
|
80
|
+
await tx.insert(notes).values({ id: "1" });
|
|
81
|
+
await fx.emit(ping, { id: "1" });
|
|
82
|
+
});
|
|
83
|
+
await bus.drain();
|
|
84
|
+
expect(got).toEqual([{ id: "1" }]);
|
|
85
|
+
|
|
86
|
+
await expect(
|
|
87
|
+
fx.store(db).transaction(async () => {
|
|
88
|
+
await fx.emit(ping, { id: "no" });
|
|
89
|
+
throw new Error("nope");
|
|
90
|
+
}),
|
|
91
|
+
).rejects.toThrow("nope");
|
|
92
|
+
await bus.drain();
|
|
93
|
+
expect(got).toEqual([{ id: "1" }]);
|
|
94
|
+
});
|
|
95
|
+
});
|
package/src/mcp/docs-server.ts
CHANGED
|
@@ -16,7 +16,9 @@ import { asData } from "./data.ts";
|
|
|
16
16
|
import { defaultDocsContentDir, loadDocsIndex, type DocsIndex } from "./docs-index.ts";
|
|
17
17
|
import { createDocsToolRuntime } from "./docs-tools.ts";
|
|
18
18
|
import {
|
|
19
|
-
|
|
19
|
+
acceptsMcpProtocolVersion,
|
|
20
|
+
negotiateMcpProtocolVersion,
|
|
21
|
+
protocolVersionOf,
|
|
20
22
|
parseJsonRpcRequest,
|
|
21
23
|
parseToolsCallParams,
|
|
22
24
|
rpcError,
|
|
@@ -94,10 +96,18 @@ export async function createDocsMcpServer(
|
|
|
94
96
|
|
|
95
97
|
switch (rpc.method) {
|
|
96
98
|
case "initialize": {
|
|
99
|
+
const requested = protocolVersionOf(rpc.params);
|
|
100
|
+
const protocolVersion = negotiateMcpProtocolVersion(requested);
|
|
101
|
+
if (protocolVersion === undefined) {
|
|
102
|
+
return jsonRpcHttp(
|
|
103
|
+
rpcError(id, RpcErrorCode.invalidParams, "unsupported protocol version"),
|
|
104
|
+
400,
|
|
105
|
+
);
|
|
106
|
+
}
|
|
97
107
|
const sessionId = newMcpTransportSessionId();
|
|
98
108
|
transportSessions.add(sessionId);
|
|
99
109
|
const result: McpInitializeResult = {
|
|
100
|
-
protocolVersion
|
|
110
|
+
protocolVersion,
|
|
101
111
|
capabilities: { tools: { listChanged: false } },
|
|
102
112
|
serverInfo: { name: "okengine-docs-mcp", version },
|
|
103
113
|
sessionId,
|
|
@@ -124,6 +134,13 @@ export async function createDocsMcpServer(
|
|
|
124
134
|
);
|
|
125
135
|
}
|
|
126
136
|
case "tools/call": {
|
|
137
|
+
const requested = protocolVersionOf(rpc.params);
|
|
138
|
+
if (requested !== undefined && !acceptsMcpProtocolVersion(requested)) {
|
|
139
|
+
return jsonRpcHttp(
|
|
140
|
+
rpcError(id, RpcErrorCode.invalidParams, "unsupported protocol version"),
|
|
141
|
+
400,
|
|
142
|
+
);
|
|
143
|
+
}
|
|
127
144
|
const call = parseToolsCallParams(rpc.params);
|
|
128
145
|
if (!call.ok) {
|
|
129
146
|
return jsonRpcHttp(rpcError(id, RpcErrorCode.invalidParams, call.message), 400);
|
package/src/mcp/mcp.test.ts
CHANGED
|
@@ -15,6 +15,7 @@ import { asData, isDataEnvelope, MCP_DATA_KIND } from "./data.ts";
|
|
|
15
15
|
import { authenticateMcpRequest, mintMcpSession, MCP_AUDIENCE } from "./session.ts";
|
|
16
16
|
import { createMcpServer } from "./server.ts";
|
|
17
17
|
import { createToolRuntime } from "./tools.ts";
|
|
18
|
+
import { MCP_CLIENT_PROTOCOL_VERSION, MCP_PROTOCOL_VERSION } from "./versions.ts";
|
|
18
19
|
|
|
19
20
|
const SECRET = "mcp-test-secret";
|
|
20
21
|
|
|
@@ -314,4 +315,86 @@ describe("MCP HTTP server", () => {
|
|
|
314
315
|
expect(callBody.result.structuredContent.kind).toBe("data");
|
|
315
316
|
expect(callBody.result.structuredContent.content.manifest.app).toBe("skyport");
|
|
316
317
|
});
|
|
318
|
+
|
|
319
|
+
test("initialize advertises 2024-11-05 and rejects a version the server does not implement", async () => {
|
|
320
|
+
const store = createSessionStore();
|
|
321
|
+
const issued = await mintMcpSession({
|
|
322
|
+
store,
|
|
323
|
+
secret: SECRET,
|
|
324
|
+
principalId: "op1",
|
|
325
|
+
scopes: ["console:*"],
|
|
326
|
+
});
|
|
327
|
+
const server = createMcpServer({
|
|
328
|
+
sessions: store,
|
|
329
|
+
secret: SECRET,
|
|
330
|
+
context: {
|
|
331
|
+
getManifest: () => SAMPLE_MANIFEST,
|
|
332
|
+
listRuns: async () => [],
|
|
333
|
+
},
|
|
334
|
+
});
|
|
335
|
+
const headers = {
|
|
336
|
+
host: "127.0.0.1:6535",
|
|
337
|
+
authorization: `Bearer ${issued.accessToken}`,
|
|
338
|
+
"content-type": "application/json",
|
|
339
|
+
};
|
|
340
|
+
const init = await server.fetch(
|
|
341
|
+
new Request("http://127.0.0.1:6535/mcp", {
|
|
342
|
+
method: "POST",
|
|
343
|
+
headers,
|
|
344
|
+
body: JSON.stringify({
|
|
345
|
+
jsonrpc: "2.0",
|
|
346
|
+
id: 1,
|
|
347
|
+
method: "initialize",
|
|
348
|
+
params: { protocolVersion: MCP_PROTOCOL_VERSION },
|
|
349
|
+
}),
|
|
350
|
+
}),
|
|
351
|
+
);
|
|
352
|
+
expect(init.status).toBe(200);
|
|
353
|
+
const initBody = (await init.json()) as { result: { protocolVersion: string } };
|
|
354
|
+
expect(initBody.result.protocolVersion).toBe("2024-11-05");
|
|
355
|
+
const omitted = await server.fetch(
|
|
356
|
+
new Request("http://127.0.0.1:6535/mcp", {
|
|
357
|
+
method: "POST",
|
|
358
|
+
headers,
|
|
359
|
+
body: JSON.stringify({
|
|
360
|
+
jsonrpc: "2.0",
|
|
361
|
+
id: 2,
|
|
362
|
+
method: "initialize",
|
|
363
|
+
params: {},
|
|
364
|
+
}),
|
|
365
|
+
}),
|
|
366
|
+
);
|
|
367
|
+
const omittedBody = (await omitted.json()) as { result: { protocolVersion: string } };
|
|
368
|
+
expect(omittedBody.result.protocolVersion).toBe("2024-11-05");
|
|
369
|
+
const rejected = await server.fetch(
|
|
370
|
+
new Request("http://127.0.0.1:6535/mcp", {
|
|
371
|
+
method: "POST",
|
|
372
|
+
headers,
|
|
373
|
+
body: JSON.stringify({
|
|
374
|
+
jsonrpc: "2.0",
|
|
375
|
+
id: 3,
|
|
376
|
+
method: "initialize",
|
|
377
|
+
params: { protocolVersion: MCP_CLIENT_PROTOCOL_VERSION },
|
|
378
|
+
}),
|
|
379
|
+
}),
|
|
380
|
+
);
|
|
381
|
+
expect(rejected.status).toBe(400);
|
|
382
|
+
const call = await server.fetch(
|
|
383
|
+
new Request("http://127.0.0.1:6535/mcp", {
|
|
384
|
+
method: "POST",
|
|
385
|
+
headers,
|
|
386
|
+
body: JSON.stringify({
|
|
387
|
+
jsonrpc: "2.0",
|
|
388
|
+
id: 4,
|
|
389
|
+
method: "tools/call",
|
|
390
|
+
params: {
|
|
391
|
+
name: "oke.manifest.get",
|
|
392
|
+
arguments: {},
|
|
393
|
+
protocolVersion: MCP_PROTOCOL_VERSION,
|
|
394
|
+
},
|
|
395
|
+
}),
|
|
396
|
+
}),
|
|
397
|
+
);
|
|
398
|
+
expect(call.status).toBe(200);
|
|
399
|
+
});
|
|
317
400
|
});
|
package/src/mcp/protocol.ts
CHANGED
|
@@ -129,8 +129,14 @@ export interface McpInitializeResult {
|
|
|
129
129
|
readonly sessionId: string;
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
-
/**
|
|
133
|
-
export
|
|
132
|
+
/** Highest protocol version the servers implement (`2024-11-05`). */
|
|
133
|
+
export {
|
|
134
|
+
MCP_PROTOCOL_VERSION,
|
|
135
|
+
MCP_SUPPORTED_PROTOCOL_VERSIONS,
|
|
136
|
+
acceptsMcpProtocolVersion,
|
|
137
|
+
negotiateMcpProtocolVersion,
|
|
138
|
+
protocolVersionOf,
|
|
139
|
+
} from "./versions.ts";
|
|
134
140
|
|
|
135
141
|
/**
|
|
136
142
|
* Parse tools/call params.
|
package/src/mcp/server.ts
CHANGED
|
@@ -18,7 +18,9 @@ import { MCP_PORT, type ServerHandle } from "../runtime/types.ts";
|
|
|
18
18
|
import { SessionError, type SessionStore } from "../auth/sessions.ts";
|
|
19
19
|
import { asData } from "./data.ts";
|
|
20
20
|
import {
|
|
21
|
-
|
|
21
|
+
acceptsMcpProtocolVersion,
|
|
22
|
+
negotiateMcpProtocolVersion,
|
|
23
|
+
protocolVersionOf,
|
|
22
24
|
parseJsonRpcRequest,
|
|
23
25
|
parseToolsCallParams,
|
|
24
26
|
rpcError,
|
|
@@ -149,10 +151,20 @@ export function createMcpServer(options: CreateMcpServerOptions): McpServer {
|
|
|
149
151
|
|
|
150
152
|
switch (rpc.method) {
|
|
151
153
|
case "initialize": {
|
|
154
|
+
const requested = protocolVersionOf(rpc.params);
|
|
155
|
+
const protocolVersion = negotiateMcpProtocolVersion(requested);
|
|
156
|
+
if (protocolVersion === undefined) {
|
|
157
|
+
return respond(
|
|
158
|
+
jsonRpcHttp(
|
|
159
|
+
rpcError(id, RpcErrorCode.invalidParams, "unsupported protocol version"),
|
|
160
|
+
400,
|
|
161
|
+
),
|
|
162
|
+
);
|
|
163
|
+
}
|
|
152
164
|
const sessionId = newMcpTransportSessionId();
|
|
153
165
|
transportSessions.add(sessionId);
|
|
154
166
|
const result: McpInitializeResult = {
|
|
155
|
-
protocolVersion
|
|
167
|
+
protocolVersion,
|
|
156
168
|
capabilities: { tools: { listChanged: false } },
|
|
157
169
|
serverInfo: { name: "okengine-mcp", version },
|
|
158
170
|
sessionId,
|
|
@@ -182,6 +194,15 @@ export function createMcpServer(options: CreateMcpServerOptions): McpServer {
|
|
|
182
194
|
);
|
|
183
195
|
}
|
|
184
196
|
case "tools/call": {
|
|
197
|
+
const requested = protocolVersionOf(rpc.params);
|
|
198
|
+
if (requested !== undefined && !acceptsMcpProtocolVersion(requested)) {
|
|
199
|
+
return respond(
|
|
200
|
+
jsonRpcHttp(
|
|
201
|
+
rpcError(id, RpcErrorCode.invalidParams, "unsupported protocol version"),
|
|
202
|
+
400,
|
|
203
|
+
),
|
|
204
|
+
);
|
|
205
|
+
}
|
|
185
206
|
const call = parseToolsCallParams(rpc.params);
|
|
186
207
|
if (!call.ok) {
|
|
187
208
|
return respond(jsonRpcHttp(rpcError(id, RpcErrorCode.invalidParams, call.message), 400));
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP protocol versions.
|
|
3
|
+
*
|
|
4
|
+
* The app and docs servers implement `initialize`, `ping`, `tools/list`, and
|
|
5
|
+
* `tools/call`. That is the 2024-11-05 tools surface, so they advertise
|
|
6
|
+
* {@link MCP_PROTOCOL_VERSION} and reject any other client version.
|
|
7
|
+
*
|
|
8
|
+
* The outbound client is separate. It probes external servers with
|
|
9
|
+
* {@link MCP_CLIENT_PROTOCOL_VERSION} (`_meta` on each request) and falls
|
|
10
|
+
* back to a 2024-11-05 `initialize`. That constant is not the server
|
|
11
|
+
* advertisement.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/** Versions the app and docs servers implement, newest last. */
|
|
15
|
+
export const MCP_SUPPORTED_PROTOCOL_VERSIONS = ["2024-11-05"] as const;
|
|
16
|
+
|
|
17
|
+
/** Highest protocol version the servers implement and advertise. */
|
|
18
|
+
export const MCP_PROTOCOL_VERSION: (typeof MCP_SUPPORTED_PROTOCOL_VERSIONS)[number] = "2024-11-05";
|
|
19
|
+
|
|
20
|
+
/** Version the outbound MCP client probes first. Not the server advertisement. */
|
|
21
|
+
export const MCP_CLIENT_PROTOCOL_VERSION = "2026-07-28";
|
|
22
|
+
|
|
23
|
+
/** `initialize` dialect the outbound client falls back to. */
|
|
24
|
+
export const MCP_LEGACY_PROTOCOL_VERSION = "2024-11-05";
|
|
25
|
+
|
|
26
|
+
const ACCEPTED: readonly string[] = MCP_SUPPORTED_PROTOCOL_VERSIONS;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Whether a client-offered protocol version is one this server implements.
|
|
30
|
+
*
|
|
31
|
+
* @param version - `initialize` or `tools/call` protocol version
|
|
32
|
+
*/
|
|
33
|
+
export function acceptsMcpProtocolVersion(version: string): boolean {
|
|
34
|
+
return ACCEPTED.includes(version);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Pick the protocol version for an `initialize` result.
|
|
39
|
+
*
|
|
40
|
+
* An omitted client version gets the highest version this server implements.
|
|
41
|
+
* A supported client version is echoed. Anything else has no overlap.
|
|
42
|
+
*
|
|
43
|
+
* @param requested - Client `protocolVersion`, when present
|
|
44
|
+
*/
|
|
45
|
+
export function negotiateMcpProtocolVersion(requested: string | undefined): string | undefined {
|
|
46
|
+
if (requested === undefined) return MCP_PROTOCOL_VERSION;
|
|
47
|
+
if (acceptsMcpProtocolVersion(requested)) return requested;
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Read `params.protocolVersion` when the client sent one.
|
|
53
|
+
*
|
|
54
|
+
* @param params - JSON-RPC params
|
|
55
|
+
*/
|
|
56
|
+
export function protocolVersionOf(params: unknown): string | undefined {
|
|
57
|
+
if (params === null || typeof params !== "object" || Array.isArray(params)) return undefined;
|
|
58
|
+
const version = (params as { protocolVersion?: unknown }).protocolVersion;
|
|
59
|
+
return typeof version === "string" && version.length > 0 ? version : undefined;
|
|
60
|
+
}
|
package/src/plugins/index.ts
CHANGED
|
@@ -24,6 +24,7 @@ export {
|
|
|
24
24
|
type MagicLinkOptions,
|
|
25
25
|
} from "./magic-link.ts";
|
|
26
26
|
export { maintenanceMode, type MaintenanceModeOptions } from "./maintenance-mode.ts";
|
|
27
|
+
export { mena, menaChannels, type MenaChannelOptions } from "./mena.ts";
|
|
27
28
|
export {
|
|
28
29
|
createPhoneStore,
|
|
29
30
|
otp,
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MENA channel plugin — Taqnyat, Unifonic, and Msegat behind one plugin.
|
|
3
|
+
*
|
|
4
|
+
* Drivers stay the protocol adapters. `fx.sendOtp`, `fx.verifyOtp`, and
|
|
5
|
+
* `fx.deliverOtp` remain supported Channel methods.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { openMsegatChannel } from "../drivers/channel-msegat.ts";
|
|
9
|
+
import { openTaqnyatChannel } from "../drivers/channel-taqnyat.ts";
|
|
10
|
+
import { openTaqnyatMailChannel } from "../drivers/channel-taqnyat-mail.ts";
|
|
11
|
+
import { openTaqnyatWhatsAppChannel } from "../drivers/channel-taqnyat-whatsapp.ts";
|
|
12
|
+
import { openUnifonicChannel } from "../drivers/channel-unifonic.ts";
|
|
13
|
+
import type { ChannelDriver, ChannelOpenOptions } from "../drivers/channel-types.ts";
|
|
14
|
+
import { plugin, type PluginDef } from "../kernel/plugin.ts";
|
|
15
|
+
|
|
16
|
+
export {
|
|
17
|
+
openMsegatChannel,
|
|
18
|
+
openTaqnyatChannel,
|
|
19
|
+
openTaqnyatMailChannel,
|
|
20
|
+
openTaqnyatWhatsAppChannel,
|
|
21
|
+
openUnifonicChannel,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/** Which MENA providers to open. Omitted providers are skipped. */
|
|
25
|
+
export interface MenaChannelOptions {
|
|
26
|
+
readonly sms?: ChannelOpenOptions;
|
|
27
|
+
readonly unifonic?: ChannelOpenOptions;
|
|
28
|
+
readonly msegat?: ChannelOpenOptions;
|
|
29
|
+
readonly mail?: ChannelOpenOptions;
|
|
30
|
+
readonly whatsapp?: ChannelOpenOptions;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Open the MENA drivers that have options.
|
|
35
|
+
*
|
|
36
|
+
* @param options - Per-provider credentials (`bearerToken` / `sender` / …)
|
|
37
|
+
*/
|
|
38
|
+
export function menaChannels(options: MenaChannelOptions = {}): ChannelDriver[] {
|
|
39
|
+
const drivers: ChannelDriver[] = [];
|
|
40
|
+
if (options.sms) drivers.push(openTaqnyatChannel(options.sms));
|
|
41
|
+
if (options.unifonic) drivers.push(openUnifonicChannel(options.unifonic));
|
|
42
|
+
if (options.msegat) drivers.push(openMsegatChannel(options.msegat));
|
|
43
|
+
if (options.mail) drivers.push(openTaqnyatMailChannel(options.mail));
|
|
44
|
+
if (options.whatsapp) drivers.push(openTaqnyatWhatsAppChannel(options.whatsapp));
|
|
45
|
+
return drivers;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Headline plugin. Attach with `.plug(mena())`. Drivers come from
|
|
50
|
+
* {@link menaChannels}; this registration is the product name.
|
|
51
|
+
*
|
|
52
|
+
* @param options - Unused today; reserved for plugin config
|
|
53
|
+
*/
|
|
54
|
+
export function mena(options: MenaChannelOptions = {}): PluginDef {
|
|
55
|
+
return plugin("mena", { version: "1", config: options });
|
|
56
|
+
}
|