@skytalesh/sdk 0.4.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.
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Message envelope for multi-protocol support.
3
+ *
4
+ * Wraps payloads with protocol identification so receivers can deserialize
5
+ * correctly when different protocols share a channel or bridge.
6
+ *
7
+ * Wire format: `[header_len:4 LE][json header][payload]`
8
+ *
9
+ * The header is a compact JSON object:
10
+ * ```json
11
+ * {"p": "a2a", "ct": "application/json", "md": {"key": "val"}}
12
+ * ```
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * import { Envelope, Protocol } from "@skytalesh/sdk";
17
+ *
18
+ * const env: Envelope = {
19
+ * protocol: Protocol.A2A,
20
+ * contentType: "application/json",
21
+ * payload: Buffer.from('{"parts":[]}'),
22
+ * };
23
+ * const data = serializeEnvelope(env);
24
+ * const env2 = deserializeEnvelope(data);
25
+ * ```
26
+ */
27
+ /** Supported wire protocols. */
28
+ export declare const Protocol: {
29
+ readonly RAW: "raw";
30
+ readonly A2A: "a2a";
31
+ readonly MCP: "mcp";
32
+ readonly SLIM: "slim";
33
+ readonly ACP: "acp";
34
+ readonly ANP: "anp";
35
+ readonly LMOS: "lmos";
36
+ readonly NLIP: "nlip";
37
+ };
38
+ export type Protocol = (typeof Protocol)[keyof typeof Protocol];
39
+ /** Protocol-tagged message envelope. */
40
+ export interface Envelope {
41
+ protocol: Protocol;
42
+ contentType: string;
43
+ payload: Buffer;
44
+ metadata?: Record<string, unknown>;
45
+ }
46
+ /**
47
+ * Serialize an envelope to wire format: `[header_len:4 LE][json header][payload]`.
48
+ *
49
+ * @example
50
+ * ```typescript
51
+ * const data = serializeEnvelope({
52
+ * protocol: Protocol.RAW,
53
+ * contentType: "text/plain",
54
+ * payload: Buffer.from("hello"),
55
+ * });
56
+ * ```
57
+ */
58
+ export declare function serializeEnvelope(env: Envelope): Buffer;
59
+ /**
60
+ * Deserialize from wire format.
61
+ *
62
+ * @throws {Error} If data is too short, header length is invalid, or header
63
+ * JSON is malformed.
64
+ *
65
+ * @example
66
+ * ```typescript
67
+ * const env = deserializeEnvelope(data);
68
+ * console.log(env.protocol, env.payload);
69
+ * ```
70
+ */
71
+ export declare function deserializeEnvelope(data: Buffer): Envelope;
package/js/envelope.js ADDED
@@ -0,0 +1,117 @@
1
+ "use strict";
2
+ /**
3
+ * Message envelope for multi-protocol support.
4
+ *
5
+ * Wraps payloads with protocol identification so receivers can deserialize
6
+ * correctly when different protocols share a channel or bridge.
7
+ *
8
+ * Wire format: `[header_len:4 LE][json header][payload]`
9
+ *
10
+ * The header is a compact JSON object:
11
+ * ```json
12
+ * {"p": "a2a", "ct": "application/json", "md": {"key": "val"}}
13
+ * ```
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * import { Envelope, Protocol } from "@skytalesh/sdk";
18
+ *
19
+ * const env: Envelope = {
20
+ * protocol: Protocol.A2A,
21
+ * contentType: "application/json",
22
+ * payload: Buffer.from('{"parts":[]}'),
23
+ * };
24
+ * const data = serializeEnvelope(env);
25
+ * const env2 = deserializeEnvelope(data);
26
+ * ```
27
+ */
28
+ Object.defineProperty(exports, "__esModule", { value: true });
29
+ exports.Protocol = void 0;
30
+ exports.serializeEnvelope = serializeEnvelope;
31
+ exports.deserializeEnvelope = deserializeEnvelope;
32
+ /** Supported wire protocols. */
33
+ exports.Protocol = {
34
+ RAW: "raw",
35
+ A2A: "a2a",
36
+ MCP: "mcp",
37
+ SLIM: "slim",
38
+ ACP: "acp",
39
+ ANP: "anp",
40
+ LMOS: "lmos",
41
+ NLIP: "nlip",
42
+ };
43
+ // 4-byte little-endian unsigned int prefix
44
+ const HEADER_SIZE = 4;
45
+ // Minimum valid envelope: 4-byte header length + 2-byte JSON "{}" + 0-byte payload
46
+ const MIN_ENVELOPE_SIZE = HEADER_SIZE + 2;
47
+ // Maximum allowed header length to prevent memory exhaustion
48
+ const MAX_HEADER_LEN = 65536;
49
+ const VALID_PROTOCOLS = new Set(Object.values(exports.Protocol));
50
+ /**
51
+ * Serialize an envelope to wire format: `[header_len:4 LE][json header][payload]`.
52
+ *
53
+ * @example
54
+ * ```typescript
55
+ * const data = serializeEnvelope({
56
+ * protocol: Protocol.RAW,
57
+ * contentType: "text/plain",
58
+ * payload: Buffer.from("hello"),
59
+ * });
60
+ * ```
61
+ */
62
+ function serializeEnvelope(env) {
63
+ const header = {
64
+ p: env.protocol,
65
+ ct: env.contentType,
66
+ };
67
+ if (env.metadata && Object.keys(env.metadata).length > 0) {
68
+ header.md = env.metadata;
69
+ }
70
+ const headerBytes = Buffer.from(JSON.stringify(header), "utf-8");
71
+ const buf = Buffer.alloc(HEADER_SIZE + headerBytes.length + env.payload.length);
72
+ buf.writeUInt32LE(headerBytes.length, 0);
73
+ headerBytes.copy(buf, HEADER_SIZE);
74
+ env.payload.copy(buf, HEADER_SIZE + headerBytes.length);
75
+ return buf;
76
+ }
77
+ /**
78
+ * Deserialize from wire format.
79
+ *
80
+ * @throws {Error} If data is too short, header length is invalid, or header
81
+ * JSON is malformed.
82
+ *
83
+ * @example
84
+ * ```typescript
85
+ * const env = deserializeEnvelope(data);
86
+ * console.log(env.protocol, env.payload);
87
+ * ```
88
+ */
89
+ function deserializeEnvelope(data) {
90
+ if (data.length < MIN_ENVELOPE_SIZE) {
91
+ throw new Error(`envelope too short: ${data.length} bytes (minimum ${MIN_ENVELOPE_SIZE})`);
92
+ }
93
+ const headerLen = data.readUInt32LE(0);
94
+ if (headerLen > MAX_HEADER_LEN) {
95
+ throw new Error(`header length ${headerLen} exceeds maximum (${MAX_HEADER_LEN} bytes)`);
96
+ }
97
+ if (headerLen > data.length - HEADER_SIZE) {
98
+ throw new Error(`header length ${headerLen} exceeds available data (${data.length - HEADER_SIZE} bytes)`);
99
+ }
100
+ const headerBytes = data.subarray(HEADER_SIZE, HEADER_SIZE + headerLen);
101
+ let header;
102
+ try {
103
+ header = JSON.parse(headerBytes.toString("utf-8"));
104
+ }
105
+ catch (e) {
106
+ throw new Error(`malformed envelope header: ${e}`);
107
+ }
108
+ const protocolValue = header.p;
109
+ if (!protocolValue || !VALID_PROTOCOLS.has(protocolValue)) {
110
+ throw new Error(`invalid protocol in envelope header: ${protocolValue}`);
111
+ }
112
+ const protocol = protocolValue;
113
+ const contentType = header.ct ?? "application/octet-stream";
114
+ const metadata = header.md;
115
+ const payload = Buffer.from(data.subarray(HEADER_SIZE + headerLen));
116
+ return { protocol, contentType, payload, metadata };
117
+ }
package/js/errors.d.ts ADDED
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Typed exception hierarchy for the Skytale SDK.
3
+ *
4
+ * All exceptions extend `Error` and carry structured attributes for
5
+ * programmatic handling:
6
+ *
7
+ * - `code` — machine-readable error code (e.g. `"auth_failed"`)
8
+ * - `httpStatus` — HTTP status that triggered the error (if applicable)
9
+ * - `docUrl` — link to troubleshooting documentation
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * import { ChannelError, AuthError } from "@skytalesh/sdk";
14
+ *
15
+ * try {
16
+ * mgr.create("bad-name");
17
+ * } catch (e) {
18
+ * if (e instanceof ChannelError) {
19
+ * console.log(`channel problem: ${e.message}`);
20
+ * console.log(`error code: ${e.code}`);
21
+ * console.log(`docs: ${e.docUrl}`);
22
+ * }
23
+ * }
24
+ * ```
25
+ */
26
+ export declare class SkytaleError extends Error {
27
+ readonly code: string;
28
+ readonly httpStatus?: number;
29
+ readonly docUrl: string;
30
+ constructor(message: string, options?: {
31
+ code?: string;
32
+ httpStatus?: number;
33
+ docUrl?: string;
34
+ });
35
+ }
36
+ export declare class AuthError extends SkytaleError {
37
+ constructor(message: string, options?: {
38
+ code?: string;
39
+ httpStatus?: number;
40
+ docUrl?: string;
41
+ });
42
+ }
43
+ export declare class TransportError extends SkytaleError {
44
+ constructor(message: string, options?: {
45
+ code?: string;
46
+ httpStatus?: number;
47
+ docUrl?: string;
48
+ });
49
+ }
50
+ export declare class ChannelError extends SkytaleError {
51
+ constructor(message: string, options?: {
52
+ code?: string;
53
+ httpStatus?: number;
54
+ docUrl?: string;
55
+ });
56
+ }
57
+ export declare class MlsError extends SkytaleError {
58
+ constructor(message: string, options?: {
59
+ code?: string;
60
+ httpStatus?: number;
61
+ docUrl?: string;
62
+ });
63
+ }
64
+ export declare class QuotaExceededError extends SkytaleError {
65
+ constructor(message: string, options?: {
66
+ code?: string;
67
+ httpStatus?: number;
68
+ docUrl?: string;
69
+ });
70
+ }
71
+ /**
72
+ * Parse a native error message with `[CODE]` prefix into a typed SkytaleError.
73
+ *
74
+ * Native Rust errors are formatted as `[error_code] human message`.
75
+ * This function extracts the code and maps to the appropriate error class.
76
+ */
77
+ export declare function parseNativeError(e: Error): SkytaleError;
package/js/errors.js ADDED
@@ -0,0 +1,106 @@
1
+ "use strict";
2
+ /**
3
+ * Typed exception hierarchy for the Skytale SDK.
4
+ *
5
+ * All exceptions extend `Error` and carry structured attributes for
6
+ * programmatic handling:
7
+ *
8
+ * - `code` — machine-readable error code (e.g. `"auth_failed"`)
9
+ * - `httpStatus` — HTTP status that triggered the error (if applicable)
10
+ * - `docUrl` — link to troubleshooting documentation
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * import { ChannelError, AuthError } from "@skytalesh/sdk";
15
+ *
16
+ * try {
17
+ * mgr.create("bad-name");
18
+ * } catch (e) {
19
+ * if (e instanceof ChannelError) {
20
+ * console.log(`channel problem: ${e.message}`);
21
+ * console.log(`error code: ${e.code}`);
22
+ * console.log(`docs: ${e.docUrl}`);
23
+ * }
24
+ * }
25
+ * ```
26
+ */
27
+ Object.defineProperty(exports, "__esModule", { value: true });
28
+ exports.QuotaExceededError = exports.MlsError = exports.ChannelError = exports.TransportError = exports.AuthError = exports.SkytaleError = void 0;
29
+ exports.parseNativeError = parseNativeError;
30
+ class SkytaleError extends Error {
31
+ code;
32
+ httpStatus;
33
+ docUrl;
34
+ constructor(message, options) {
35
+ super(message);
36
+ this.name = "SkytaleError";
37
+ this.code = options?.code ?? "unknown_error";
38
+ this.httpStatus = options?.httpStatus;
39
+ this.docUrl =
40
+ options?.docUrl ?? `https://skytale.sh/docs/sdk/errors#${this.code}`;
41
+ }
42
+ }
43
+ exports.SkytaleError = SkytaleError;
44
+ class AuthError extends SkytaleError {
45
+ constructor(message, options) {
46
+ super(message, { code: "auth_failed", ...options });
47
+ this.name = "AuthError";
48
+ }
49
+ }
50
+ exports.AuthError = AuthError;
51
+ class TransportError extends SkytaleError {
52
+ constructor(message, options) {
53
+ super(message, { code: "transport_error", ...options });
54
+ this.name = "TransportError";
55
+ }
56
+ }
57
+ exports.TransportError = TransportError;
58
+ class ChannelError extends SkytaleError {
59
+ constructor(message, options) {
60
+ super(message, { code: "channel_error", ...options });
61
+ this.name = "ChannelError";
62
+ }
63
+ }
64
+ exports.ChannelError = ChannelError;
65
+ class MlsError extends SkytaleError {
66
+ constructor(message, options) {
67
+ super(message, { code: "mls_error", ...options });
68
+ this.name = "MlsError";
69
+ }
70
+ }
71
+ exports.MlsError = MlsError;
72
+ class QuotaExceededError extends SkytaleError {
73
+ constructor(message, options) {
74
+ super(message, { code: "quota_exceeded", ...options });
75
+ this.name = "QuotaExceededError";
76
+ }
77
+ }
78
+ exports.QuotaExceededError = QuotaExceededError;
79
+ /**
80
+ * Parse a native error message with `[CODE]` prefix into a typed SkytaleError.
81
+ *
82
+ * Native Rust errors are formatted as `[error_code] human message`.
83
+ * This function extracts the code and maps to the appropriate error class.
84
+ */
85
+ function parseNativeError(e) {
86
+ const match = e.message.match(/^\[(\w+)]\s*(.*)/s);
87
+ if (!match) {
88
+ return new SkytaleError(e.message);
89
+ }
90
+ const code = match[1];
91
+ const message = match[2];
92
+ const codeMap = {
93
+ auth_failed: AuthError,
94
+ unauthorized: AuthError,
95
+ transport_error: TransportError,
96
+ connection_error: TransportError,
97
+ listener_died: TransportError,
98
+ channel_error: ChannelError,
99
+ not_found: ChannelError,
100
+ bad_request: ChannelError,
101
+ mls_error: MlsError,
102
+ quota_exceeded: QuotaExceededError,
103
+ };
104
+ const ErrorClass = codeMap[code] ?? SkytaleError;
105
+ return new ErrorClass(message, { code });
106
+ }
package/js/index.d.ts ADDED
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Skytale SDK: Encrypted channels for AI agents.
3
+ *
4
+ * @example
5
+ * ```typescript
6
+ * import { SkytaleChannelManager, Protocol } from "@skytalesh/sdk";
7
+ *
8
+ * const mgr = new SkytaleChannelManager({ identity: "my-agent" });
9
+ * await mgr.create("org/team/research");
10
+ * mgr.send("org/team/research", "Hello, encrypted world!");
11
+ * ```
12
+ *
13
+ * @packageDocumentation
14
+ */
15
+ export { SkytaleChannelManager, type ChannelManagerOptions, } from "./channel-manager";
16
+ export { Protocol, type Envelope, serializeEnvelope, deserializeEnvelope } from "./envelope";
17
+ export { SkytaleAPI } from "./api";
18
+ export { SkytaleError, AuthError, TransportError, ChannelError, MlsError, QuotaExceededError, parseNativeError, } from "./errors";
19
+ export { SkytaleTransport } from "./mcp-transport";
package/js/index.js ADDED
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ /**
3
+ * Skytale SDK: Encrypted channels for AI agents.
4
+ *
5
+ * @example
6
+ * ```typescript
7
+ * import { SkytaleChannelManager, Protocol } from "@skytalesh/sdk";
8
+ *
9
+ * const mgr = new SkytaleChannelManager({ identity: "my-agent" });
10
+ * await mgr.create("org/team/research");
11
+ * mgr.send("org/team/research", "Hello, encrypted world!");
12
+ * ```
13
+ *
14
+ * @packageDocumentation
15
+ */
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.SkytaleTransport = exports.parseNativeError = exports.QuotaExceededError = exports.MlsError = exports.ChannelError = exports.TransportError = exports.AuthError = exports.SkytaleError = exports.SkytaleAPI = exports.deserializeEnvelope = exports.serializeEnvelope = exports.Protocol = exports.SkytaleChannelManager = void 0;
18
+ var channel_manager_1 = require("./channel-manager");
19
+ Object.defineProperty(exports, "SkytaleChannelManager", { enumerable: true, get: function () { return channel_manager_1.SkytaleChannelManager; } });
20
+ var envelope_1 = require("./envelope");
21
+ Object.defineProperty(exports, "Protocol", { enumerable: true, get: function () { return envelope_1.Protocol; } });
22
+ Object.defineProperty(exports, "serializeEnvelope", { enumerable: true, get: function () { return envelope_1.serializeEnvelope; } });
23
+ Object.defineProperty(exports, "deserializeEnvelope", { enumerable: true, get: function () { return envelope_1.deserializeEnvelope; } });
24
+ var api_1 = require("./api");
25
+ Object.defineProperty(exports, "SkytaleAPI", { enumerable: true, get: function () { return api_1.SkytaleAPI; } });
26
+ var errors_1 = require("./errors");
27
+ Object.defineProperty(exports, "SkytaleError", { enumerable: true, get: function () { return errors_1.SkytaleError; } });
28
+ Object.defineProperty(exports, "AuthError", { enumerable: true, get: function () { return errors_1.AuthError; } });
29
+ Object.defineProperty(exports, "TransportError", { enumerable: true, get: function () { return errors_1.TransportError; } });
30
+ Object.defineProperty(exports, "ChannelError", { enumerable: true, get: function () { return errors_1.ChannelError; } });
31
+ Object.defineProperty(exports, "MlsError", { enumerable: true, get: function () { return errors_1.MlsError; } });
32
+ Object.defineProperty(exports, "QuotaExceededError", { enumerable: true, get: function () { return errors_1.QuotaExceededError; } });
33
+ Object.defineProperty(exports, "parseNativeError", { enumerable: true, get: function () { return errors_1.parseNativeError; } });
34
+ var mcp_transport_1 = require("./mcp-transport");
35
+ Object.defineProperty(exports, "SkytaleTransport", { enumerable: true, get: function () { return mcp_transport_1.SkytaleTransport; } });
@@ -0,0 +1,96 @@
1
+ /**
2
+ * MCP JSON-RPC transport over MLS-encrypted Skytale channels.
3
+ *
4
+ * Provides {@link SkytaleTransport} — a drop-in MCP transport that replaces
5
+ * plaintext HTTP/stdio with end-to-end encrypted messaging. Each JSON-RPC
6
+ * message is serialized as JSON and sent through an MLS-encrypted channel.
7
+ *
8
+ * This module is *not* the MCP tool server (`@skytalesh/mcp-server`). This
9
+ * module provides Skytale **as** the transport layer for arbitrary MCP
10
+ * protocol messages.
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * import { SkytaleChannelManager } from "@skytalesh/sdk";
15
+ * import { SkytaleTransport } from "@skytalesh/sdk/mcp-transport";
16
+ * import { Client } from "@modelcontextprotocol/sdk/client/index.js";
17
+ *
18
+ * const mgr = new SkytaleChannelManager({ identity: "agent-1", mock: true });
19
+ * await mgr.create("org/ns/mcp-rpc");
20
+ * const transport = new SkytaleTransport(mgr, "org/ns/mcp-rpc");
21
+ *
22
+ * const client = new Client({ name: "my-client", version: "1.0.0" });
23
+ * await client.connect(transport);
24
+ * ```
25
+ */
26
+ import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
27
+ import type { JSONRPCMessage } from "@modelcontextprotocol/sdk/types.js";
28
+ /**
29
+ * Subset of SkytaleChannelManager used by the transport.
30
+ * Decouples from the concrete class for testability.
31
+ */
32
+ interface ChannelManagerLike {
33
+ create(channelName: string): Promise<void>;
34
+ send(channelName: string, message: string): void;
35
+ receive(channelName: string, timeoutMs?: number): Promise<string[]>;
36
+ }
37
+ /**
38
+ * MCP Transport that routes JSON-RPC messages over a Skytale encrypted channel.
39
+ *
40
+ * Implements the MCP SDK's `Transport` interface so it can be used with both
41
+ * MCP clients and servers as a drop-in replacement for stdio or HTTP transports.
42
+ *
43
+ * @example
44
+ * ```typescript
45
+ * const transport = new SkytaleTransport(manager, "org/ns/mcp");
46
+ * await transport.start();
47
+ * await transport.send({ jsonrpc: "2.0", method: "ping", id: 1 });
48
+ * ```
49
+ */
50
+ export declare class SkytaleTransport implements Transport {
51
+ private manager;
52
+ private channelName;
53
+ private running;
54
+ private pollTimer;
55
+ private pollIntervalMs;
56
+ onclose?: () => void;
57
+ onerror?: (error: Error) => void;
58
+ onmessage?: (message: JSONRPCMessage) => void;
59
+ /**
60
+ * @param manager - A SkytaleChannelManager instance with the channel
61
+ * already created or joined.
62
+ * @param channelName - Channel name in `org/namespace/service` format.
63
+ * @param pollIntervalMs - How often to poll for incoming messages
64
+ * (default 200ms).
65
+ */
66
+ constructor(manager: ChannelManagerLike, channelName: string, pollIntervalMs?: number);
67
+ /**
68
+ * Start listening for messages on the encrypted channel.
69
+ *
70
+ * Creates the channel if it does not already exist (errors are silently
71
+ * ignored since the channel may have been created or joined externally).
72
+ * Starts a polling loop that delivers incoming messages to the
73
+ * {@link onmessage} callback.
74
+ */
75
+ start(): Promise<void>;
76
+ /**
77
+ * Send a JSON-RPC message over the encrypted channel.
78
+ *
79
+ * @param message - The JSON-RPC message to send.
80
+ * @throws {Error} If the transport has been closed.
81
+ */
82
+ send(message: JSONRPCMessage): Promise<void>;
83
+ /**
84
+ * Stop the transport and clean up resources.
85
+ *
86
+ * After closing, any subsequent {@link send} call will throw. The
87
+ * {@link onclose} callback is invoked if set.
88
+ */
89
+ close(): Promise<void>;
90
+ /**
91
+ * Internal polling loop. Uses recursive setTimeout to avoid overlapping
92
+ * polls when receive() takes longer than the interval.
93
+ */
94
+ private poll;
95
+ }
96
+ export {};
@@ -0,0 +1,144 @@
1
+ "use strict";
2
+ /**
3
+ * MCP JSON-RPC transport over MLS-encrypted Skytale channels.
4
+ *
5
+ * Provides {@link SkytaleTransport} — a drop-in MCP transport that replaces
6
+ * plaintext HTTP/stdio with end-to-end encrypted messaging. Each JSON-RPC
7
+ * message is serialized as JSON and sent through an MLS-encrypted channel.
8
+ *
9
+ * This module is *not* the MCP tool server (`@skytalesh/mcp-server`). This
10
+ * module provides Skytale **as** the transport layer for arbitrary MCP
11
+ * protocol messages.
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * import { SkytaleChannelManager } from "@skytalesh/sdk";
16
+ * import { SkytaleTransport } from "@skytalesh/sdk/mcp-transport";
17
+ * import { Client } from "@modelcontextprotocol/sdk/client/index.js";
18
+ *
19
+ * const mgr = new SkytaleChannelManager({ identity: "agent-1", mock: true });
20
+ * await mgr.create("org/ns/mcp-rpc");
21
+ * const transport = new SkytaleTransport(mgr, "org/ns/mcp-rpc");
22
+ *
23
+ * const client = new Client({ name: "my-client", version: "1.0.0" });
24
+ * await client.connect(transport);
25
+ * ```
26
+ */
27
+ Object.defineProperty(exports, "__esModule", { value: true });
28
+ exports.SkytaleTransport = void 0;
29
+ const DEFAULT_POLL_INTERVAL_MS = 200;
30
+ /**
31
+ * MCP Transport that routes JSON-RPC messages over a Skytale encrypted channel.
32
+ *
33
+ * Implements the MCP SDK's `Transport` interface so it can be used with both
34
+ * MCP clients and servers as a drop-in replacement for stdio or HTTP transports.
35
+ *
36
+ * @example
37
+ * ```typescript
38
+ * const transport = new SkytaleTransport(manager, "org/ns/mcp");
39
+ * await transport.start();
40
+ * await transport.send({ jsonrpc: "2.0", method: "ping", id: 1 });
41
+ * ```
42
+ */
43
+ class SkytaleTransport {
44
+ manager;
45
+ channelName;
46
+ running = false;
47
+ pollTimer = null;
48
+ pollIntervalMs;
49
+ onclose;
50
+ onerror;
51
+ onmessage;
52
+ /**
53
+ * @param manager - A SkytaleChannelManager instance with the channel
54
+ * already created or joined.
55
+ * @param channelName - Channel name in `org/namespace/service` format.
56
+ * @param pollIntervalMs - How often to poll for incoming messages
57
+ * (default 200ms).
58
+ */
59
+ constructor(manager, channelName, pollIntervalMs) {
60
+ this.manager = manager;
61
+ this.channelName = channelName;
62
+ this.pollIntervalMs = pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
63
+ }
64
+ /**
65
+ * Start listening for messages on the encrypted channel.
66
+ *
67
+ * Creates the channel if it does not already exist (errors are silently
68
+ * ignored since the channel may have been created or joined externally).
69
+ * Starts a polling loop that delivers incoming messages to the
70
+ * {@link onmessage} callback.
71
+ */
72
+ async start() {
73
+ if (this.running)
74
+ return;
75
+ this.running = true;
76
+ // Attempt to create the channel; ignore errors if it already exists.
77
+ try {
78
+ await this.manager.create(this.channelName);
79
+ }
80
+ catch {
81
+ // Channel may already exist — that's fine.
82
+ }
83
+ void this.poll();
84
+ }
85
+ /**
86
+ * Send a JSON-RPC message over the encrypted channel.
87
+ *
88
+ * @param message - The JSON-RPC message to send.
89
+ * @throws {Error} If the transport has been closed.
90
+ */
91
+ async send(message) {
92
+ if (!this.running) {
93
+ throw new Error("transport is closed");
94
+ }
95
+ const payload = JSON.stringify(message);
96
+ this.manager.send(this.channelName, payload);
97
+ }
98
+ /**
99
+ * Stop the transport and clean up resources.
100
+ *
101
+ * After closing, any subsequent {@link send} call will throw. The
102
+ * {@link onclose} callback is invoked if set.
103
+ */
104
+ async close() {
105
+ if (!this.running)
106
+ return;
107
+ this.running = false;
108
+ if (this.pollTimer !== null) {
109
+ clearTimeout(this.pollTimer);
110
+ this.pollTimer = null;
111
+ }
112
+ this.onclose?.();
113
+ }
114
+ /**
115
+ * Internal polling loop. Uses recursive setTimeout to avoid overlapping
116
+ * polls when receive() takes longer than the interval.
117
+ */
118
+ async poll() {
119
+ if (!this.running)
120
+ return;
121
+ try {
122
+ // Use a short timeout (0) so receive returns immediately with whatever
123
+ // is buffered, avoiding blocking the event loop.
124
+ const messages = await this.manager.receive(this.channelName, 0);
125
+ for (const raw of messages) {
126
+ try {
127
+ const parsed = JSON.parse(raw);
128
+ this.onmessage?.(parsed);
129
+ }
130
+ catch (err) {
131
+ this.onerror?.(new Error(`failed to parse incoming MCP message: ${err instanceof Error ? err.message : String(err)}`));
132
+ }
133
+ }
134
+ }
135
+ catch (err) {
136
+ this.onerror?.(err instanceof Error ? err : new Error(String(err)));
137
+ }
138
+ // Schedule next poll.
139
+ if (this.running) {
140
+ this.pollTimer = setTimeout(() => { void this.poll(); }, this.pollIntervalMs);
141
+ }
142
+ }
143
+ }
144
+ exports.SkytaleTransport = SkytaleTransport;