@kalera/munin-sdk 0.1.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,4 @@
1
+
2
+ > @kalera/munin-sdk@0.1.0 build /home/runner/work/munin-for-agents/munin-for-agents/packages/ts-sdk
3
+ > tsc -p tsconfig.json
4
+
@@ -0,0 +1,3 @@
1
+ import type { MuninCapabilities } from "./types.js";
2
+ export declare function fetchCapabilities(baseUrl: string, apiKey?: string, fetchImpl?: typeof fetch): Promise<MuninCapabilities>;
3
+ export declare function isActionSupported(capabilities: MuninCapabilities, action: string): boolean;
@@ -0,0 +1,27 @@
1
+ import { MuninSdkError, MuninTransportError } from "./errors.js";
2
+ export async function fetchCapabilities(baseUrl, apiKey, fetchImpl = fetch) {
3
+ const response = await fetchImpl(`${baseUrl}/api/mcp/capabilities`, {
4
+ method: "GET",
5
+ headers: {
6
+ "Content-Type": "application/json",
7
+ ...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {}),
8
+ },
9
+ }).catch((error) => {
10
+ throw new MuninTransportError(`Failed to call capabilities endpoint: ${String(error)}`);
11
+ });
12
+ if (!response.ok) {
13
+ throw new MuninTransportError(`Capabilities request failed with status ${response.status}`);
14
+ }
15
+ const body = (await response.json());
16
+ if (!body.ok || !body.data) {
17
+ throw new MuninSdkError(body.error ?? {
18
+ code: "INTERNAL_ERROR",
19
+ message: "Capabilities response missing data",
20
+ });
21
+ }
22
+ return body.data;
23
+ }
24
+ export function isActionSupported(capabilities, action) {
25
+ return (capabilities.actions.core.includes(action) ||
26
+ capabilities.actions.optional.includes(action));
27
+ }
@@ -0,0 +1,20 @@
1
+ import type { MuninAction, MuninCapabilities, MuninClientConfig, MuninResponse } from "./types.js";
2
+ export declare class MuninClient {
3
+ private readonly baseUrl;
4
+ private readonly apiKey?;
5
+ private readonly project;
6
+ private readonly timeoutMs;
7
+ private readonly fetchImpl;
8
+ private capabilitiesCache?;
9
+ constructor(config: MuninClientConfig);
10
+ capabilities(forceRefresh?: boolean): Promise<MuninCapabilities>;
11
+ invoke<TPayload extends Record<string, unknown>, TData = unknown>(action: MuninAction, payload: TPayload, options?: {
12
+ requestId?: string;
13
+ ensureCapability?: boolean;
14
+ }): Promise<MuninResponse<TData>>;
15
+ store(payload: Record<string, unknown>): Promise<MuninResponse<unknown>>;
16
+ retrieve(payload: Record<string, unknown>): Promise<MuninResponse<unknown>>;
17
+ search(payload: Record<string, unknown>): Promise<MuninResponse<unknown>>;
18
+ list(payload?: Record<string, unknown>): Promise<MuninResponse<unknown>>;
19
+ recent(payload?: Record<string, unknown>): Promise<MuninResponse<unknown>>;
20
+ }
package/dist/client.js ADDED
@@ -0,0 +1,84 @@
1
+ import { MuninSdkError, MuninTransportError } from "./errors.js";
2
+ import { fetchCapabilities, isActionSupported, } from "./capabilities.js";
3
+ const DEFAULT_TIMEOUT_MS = 15_000;
4
+ export class MuninClient {
5
+ baseUrl;
6
+ apiKey;
7
+ project;
8
+ timeoutMs;
9
+ fetchImpl;
10
+ capabilitiesCache;
11
+ constructor(config) {
12
+ this.baseUrl = config.baseUrl.replace(/\/$/, "");
13
+ this.apiKey = config.apiKey;
14
+ this.project = config.project;
15
+ this.timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;
16
+ this.fetchImpl = config.fetchImpl ?? fetch;
17
+ }
18
+ async capabilities(forceRefresh = false) {
19
+ if (this.capabilitiesCache && !forceRefresh) {
20
+ return this.capabilitiesCache;
21
+ }
22
+ const caps = await fetchCapabilities(this.baseUrl, this.apiKey, this.fetchImpl);
23
+ this.capabilitiesCache = caps;
24
+ return caps;
25
+ }
26
+ async invoke(action, payload, options) {
27
+ if (options?.ensureCapability) {
28
+ const caps = await this.capabilities();
29
+ if (!isActionSupported(caps, action)) {
30
+ throw new MuninSdkError({
31
+ code: "FEATURE_DISABLED",
32
+ message: `Action '${action}' is not supported by current server capabilities`,
33
+ });
34
+ }
35
+ }
36
+ const request = {
37
+ apiKey: this.apiKey,
38
+ projectId: this.project,
39
+ action,
40
+ payload,
41
+ requestId: options?.requestId,
42
+ client: {
43
+ name: "@kalera/munin-sdk",
44
+ version: "0.1.0",
45
+ },
46
+ };
47
+ const controller = new AbortController();
48
+ const timeout = setTimeout(() => controller.abort(), this.timeoutMs);
49
+ const response = await this.fetchImpl(`${this.baseUrl}/api/mcp/action`, {
50
+ method: "POST",
51
+ headers: {
52
+ "Content-Type": "application/json",
53
+ },
54
+ body: JSON.stringify(request),
55
+ signal: controller.signal,
56
+ }).catch((error) => {
57
+ throw new MuninTransportError(`Request failed for action '${action}': ${String(error)}`);
58
+ });
59
+ clearTimeout(timeout);
60
+ const body = (await response.json());
61
+ if (!response.ok || (body.ok === false) || (body.success === false)) {
62
+ throw new MuninSdkError(body.error ?? {
63
+ code: "INTERNAL_ERROR",
64
+ message: `Unexpected failure invoking action '${action}'`,
65
+ });
66
+ }
67
+ return body;
68
+ }
69
+ async store(payload) {
70
+ return this.invoke("store", payload, { ensureCapability: true });
71
+ }
72
+ async retrieve(payload) {
73
+ return this.invoke("retrieve", payload, { ensureCapability: true });
74
+ }
75
+ async search(payload) {
76
+ return this.invoke("search", payload, { ensureCapability: true });
77
+ }
78
+ async list(payload = {}) {
79
+ return this.invoke("list", payload, { ensureCapability: true });
80
+ }
81
+ async recent(payload = {}) {
82
+ return this.invoke("recent", payload, { ensureCapability: true });
83
+ }
84
+ }
@@ -0,0 +1,9 @@
1
+ import type { MuninError } from "./types.js";
2
+ export declare class MuninSdkError extends Error {
3
+ readonly code: MuninError["code"];
4
+ readonly details?: Record<string, unknown>;
5
+ constructor(error: MuninError);
6
+ }
7
+ export declare class MuninTransportError extends Error {
8
+ constructor(message: string);
9
+ }
package/dist/errors.js ADDED
@@ -0,0 +1,16 @@
1
+ export class MuninSdkError extends Error {
2
+ code;
3
+ details;
4
+ constructor(error) {
5
+ super(error.message);
6
+ this.name = "MuninSdkError";
7
+ this.code = error.code;
8
+ this.details = error.details;
9
+ }
10
+ }
11
+ export class MuninTransportError extends Error {
12
+ constructor(message) {
13
+ super(message);
14
+ this.name = "MuninTransportError";
15
+ }
16
+ }
@@ -0,0 +1,4 @@
1
+ export * from "./types.js";
2
+ export * from "./errors.js";
3
+ export * from "./capabilities.js";
4
+ export * from "./client.js";
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export * from "./types.js";
2
+ export * from "./errors.js";
3
+ export * from "./capabilities.js";
4
+ export * from "./client.js";
@@ -0,0 +1,45 @@
1
+ export type MuninAction = "store" | "retrieve" | "search" | "list" | "recent" | "share" | "versions" | "rollback" | "encrypt" | "decrypt";
2
+ export interface MuninActionEnvelope<TPayload = Record<string, unknown>> {
3
+ action: MuninAction;
4
+ project: string;
5
+ payload: TPayload;
6
+ requestId?: string;
7
+ client?: {
8
+ name: string;
9
+ version: string;
10
+ };
11
+ }
12
+ export interface MuninCapabilities {
13
+ specVersion: string;
14
+ actions: {
15
+ core: string[];
16
+ optional: string[];
17
+ };
18
+ features: Record<string, {
19
+ supported: boolean;
20
+ reason?: string;
21
+ }>;
22
+ metadata: {
23
+ serverVersion: string;
24
+ timestamp: string;
25
+ [key: string]: unknown;
26
+ };
27
+ }
28
+ export interface MuninError {
29
+ code: "AUTH_INVALID" | "FEATURE_DISABLED" | "NOT_FOUND" | "RATE_LIMITED" | "VALIDATION_ERROR" | "INTERNAL_ERROR";
30
+ message: string;
31
+ details?: Record<string, unknown>;
32
+ }
33
+ export interface MuninResponse<TData = unknown> {
34
+ ok: boolean;
35
+ data?: TData;
36
+ error?: MuninError;
37
+ requestId?: string;
38
+ }
39
+ export interface MuninClientConfig {
40
+ baseUrl: string;
41
+ apiKey?: string;
42
+ project: string;
43
+ timeoutMs?: number;
44
+ fetchImpl?: typeof fetch;
45
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "@kalera/munin-sdk",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "exports": {
8
+ ".": "./dist/index.js"
9
+ },
10
+ "dependencies": {},
11
+ "devDependencies": {
12
+ "typescript": "^5.9.2",
13
+ "tsx": "^4.20.5"
14
+ },
15
+ "scripts": {
16
+ "build": "tsc -p tsconfig.json",
17
+ "lint": "tsc -p tsconfig.json --noEmit",
18
+ "test": "tsx test/client.test.ts"
19
+ }
20
+ }
@@ -0,0 +1,49 @@
1
+ import type { MuninCapabilities, MuninResponse } from "./types.js";
2
+ import { MuninSdkError, MuninTransportError } from "./errors.js";
3
+
4
+ export async function fetchCapabilities(
5
+ baseUrl: string,
6
+ apiKey?: string,
7
+ fetchImpl: typeof fetch = fetch,
8
+ ): Promise<MuninCapabilities> {
9
+ const response = await fetchImpl(`${baseUrl}/api/mcp/capabilities`, {
10
+ method: "GET",
11
+ headers: {
12
+ "Content-Type": "application/json",
13
+ ...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {}),
14
+ },
15
+ }).catch((error: unknown) => {
16
+ throw new MuninTransportError(
17
+ `Failed to call capabilities endpoint: ${String(error)}`,
18
+ );
19
+ });
20
+
21
+ if (!response.ok) {
22
+ throw new MuninTransportError(
23
+ `Capabilities request failed with status ${response.status}`,
24
+ );
25
+ }
26
+
27
+ const body = (await response.json()) as MuninResponse<MuninCapabilities>;
28
+
29
+ if (!body.ok || !body.data) {
30
+ throw new MuninSdkError(
31
+ body.error ?? {
32
+ code: "INTERNAL_ERROR",
33
+ message: "Capabilities response missing data",
34
+ },
35
+ );
36
+ }
37
+
38
+ return body.data;
39
+ }
40
+
41
+ export function isActionSupported(
42
+ capabilities: MuninCapabilities,
43
+ action: string,
44
+ ): boolean {
45
+ return (
46
+ capabilities.actions.core.includes(action) ||
47
+ capabilities.actions.optional.includes(action)
48
+ );
49
+ }
package/src/client.ts ADDED
@@ -0,0 +1,124 @@
1
+ import { MuninSdkError, MuninTransportError } from "./errors.js";
2
+ import {
3
+ fetchCapabilities,
4
+ isActionSupported,
5
+ } from "./capabilities.js";
6
+ import type {
7
+ MuninAction,
8
+ MuninActionEnvelope,
9
+ MuninCapabilities,
10
+ MuninClientConfig,
11
+ MuninResponse,
12
+ } from "./types.js";
13
+
14
+ const DEFAULT_TIMEOUT_MS = 15_000;
15
+
16
+ export class MuninClient {
17
+ private readonly baseUrl: string;
18
+ private readonly apiKey?: string;
19
+ private readonly project: string;
20
+ private readonly timeoutMs: number;
21
+ private readonly fetchImpl: typeof fetch;
22
+ private capabilitiesCache?: MuninCapabilities;
23
+
24
+ constructor(config: MuninClientConfig) {
25
+ this.baseUrl = config.baseUrl.replace(/\/$/, "");
26
+ this.apiKey = config.apiKey;
27
+ this.project = config.project;
28
+ this.timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;
29
+ this.fetchImpl = config.fetchImpl ?? fetch;
30
+ }
31
+
32
+ async capabilities(forceRefresh = false): Promise<MuninCapabilities> {
33
+ if (this.capabilitiesCache && !forceRefresh) {
34
+ return this.capabilitiesCache;
35
+ }
36
+
37
+ const caps = await fetchCapabilities(
38
+ this.baseUrl,
39
+ this.apiKey,
40
+ this.fetchImpl,
41
+ );
42
+ this.capabilitiesCache = caps;
43
+ return caps;
44
+ }
45
+
46
+ async invoke<TPayload extends Record<string, unknown>, TData = unknown>(
47
+ action: MuninAction,
48
+ payload: TPayload,
49
+ options?: { requestId?: string; ensureCapability?: boolean },
50
+ ): Promise<MuninResponse<TData>> {
51
+ if (options?.ensureCapability) {
52
+ const caps = await this.capabilities();
53
+ if (!isActionSupported(caps, action)) {
54
+ throw new MuninSdkError({
55
+ code: "FEATURE_DISABLED",
56
+ message: `Action '${action}' is not supported by current server capabilities`,
57
+ });
58
+ }
59
+ }
60
+
61
+ const request = {
62
+ apiKey: this.apiKey,
63
+ projectId: this.project,
64
+ action,
65
+ payload,
66
+ requestId: options?.requestId,
67
+ client: {
68
+ name: "@kalera/munin-sdk",
69
+ version: "0.1.0",
70
+ },
71
+ };
72
+
73
+ const controller = new AbortController();
74
+ const timeout = setTimeout(() => controller.abort(), this.timeoutMs);
75
+
76
+ const response = await this.fetchImpl(`${this.baseUrl}/api/mcp/action`, {
77
+ method: "POST",
78
+ headers: {
79
+ "Content-Type": "application/json",
80
+ },
81
+ body: JSON.stringify(request),
82
+ signal: controller.signal,
83
+ }).catch((error: unknown) => {
84
+ throw new MuninTransportError(
85
+ `Request failed for action '${action}': ${String(error)}`,
86
+ );
87
+ });
88
+
89
+ clearTimeout(timeout);
90
+
91
+ const body = (await response.json()) as any;
92
+
93
+ if (!response.ok || (body.ok === false) || (body.success === false)) {
94
+ throw new MuninSdkError(
95
+ body.error ?? {
96
+ code: "INTERNAL_ERROR",
97
+ message: `Unexpected failure invoking action '${action}'`,
98
+ },
99
+ );
100
+ }
101
+
102
+ return body;
103
+ }
104
+
105
+ async store(payload: Record<string, unknown>) {
106
+ return this.invoke("store", payload, { ensureCapability: true });
107
+ }
108
+
109
+ async retrieve(payload: Record<string, unknown>) {
110
+ return this.invoke("retrieve", payload, { ensureCapability: true });
111
+ }
112
+
113
+ async search(payload: Record<string, unknown>) {
114
+ return this.invoke("search", payload, { ensureCapability: true });
115
+ }
116
+
117
+ async list(payload: Record<string, unknown> = {}) {
118
+ return this.invoke("list", payload, { ensureCapability: true });
119
+ }
120
+
121
+ async recent(payload: Record<string, unknown> = {}) {
122
+ return this.invoke("recent", payload, { ensureCapability: true });
123
+ }
124
+ }
package/src/errors.ts ADDED
@@ -0,0 +1,20 @@
1
+ import type { MuninError } from "./types.js";
2
+
3
+ export class MuninSdkError extends Error {
4
+ public readonly code: MuninError["code"];
5
+ public readonly details?: Record<string, unknown>;
6
+
7
+ constructor(error: MuninError) {
8
+ super(error.message);
9
+ this.name = "MuninSdkError";
10
+ this.code = error.code;
11
+ this.details = error.details;
12
+ }
13
+ }
14
+
15
+ export class MuninTransportError extends Error {
16
+ constructor(message: string) {
17
+ super(message);
18
+ this.name = "MuninTransportError";
19
+ }
20
+ }
package/src/index.ts ADDED
@@ -0,0 +1,4 @@
1
+ export * from "./types.js";
2
+ export * from "./errors.js";
3
+ export * from "./capabilities.js";
4
+ export * from "./client.js";
package/src/types.ts ADDED
@@ -0,0 +1,63 @@
1
+ export type MuninAction =
2
+ | "store"
3
+ | "retrieve"
4
+ | "search"
5
+ | "list"
6
+ | "recent"
7
+ | "share"
8
+ | "versions"
9
+ | "rollback"
10
+ | "encrypt"
11
+ | "decrypt";
12
+
13
+ export interface MuninActionEnvelope<TPayload = Record<string, unknown>> {
14
+ action: MuninAction;
15
+ project: string;
16
+ payload: TPayload;
17
+ requestId?: string;
18
+ client?: {
19
+ name: string;
20
+ version: string;
21
+ };
22
+ }
23
+
24
+ export interface MuninCapabilities {
25
+ specVersion: string;
26
+ actions: {
27
+ core: string[];
28
+ optional: string[];
29
+ };
30
+ features: Record<string, { supported: boolean; reason?: string }>;
31
+ metadata: {
32
+ serverVersion: string;
33
+ timestamp: string;
34
+ [key: string]: unknown;
35
+ };
36
+ }
37
+
38
+ export interface MuninError {
39
+ code:
40
+ | "AUTH_INVALID"
41
+ | "FEATURE_DISABLED"
42
+ | "NOT_FOUND"
43
+ | "RATE_LIMITED"
44
+ | "VALIDATION_ERROR"
45
+ | "INTERNAL_ERROR";
46
+ message: string;
47
+ details?: Record<string, unknown>;
48
+ }
49
+
50
+ export interface MuninResponse<TData = unknown> {
51
+ ok: boolean;
52
+ data?: TData;
53
+ error?: MuninError;
54
+ requestId?: string;
55
+ }
56
+
57
+ export interface MuninClientConfig {
58
+ baseUrl: string;
59
+ apiKey?: string;
60
+ project: string;
61
+ timeoutMs?: number;
62
+ fetchImpl?: typeof fetch;
63
+ }
@@ -0,0 +1,64 @@
1
+ import assert from "node:assert/strict";
2
+ import { MuninClient } from "../src/client";
3
+
4
+ async function run() {
5
+ const fakeFetch: typeof fetch = (async (input: RequestInfo | URL, init?: RequestInit) => {
6
+ const url = String(input);
7
+
8
+ if (url.endsWith("/api/mcp/capabilities")) {
9
+ return new Response(
10
+ JSON.stringify({
11
+ ok: true,
12
+ data: {
13
+ specVersion: "v1.0.0",
14
+ actions: {
15
+ core: ["store", "retrieve", "search", "list", "recent"],
16
+ optional: ["share"],
17
+ },
18
+ features: {
19
+ encryption: { supported: true },
20
+ },
21
+ metadata: {
22
+ serverVersion: "0.0.1",
23
+ timestamp: new Date().toISOString(),
24
+ },
25
+ },
26
+ }),
27
+ { status: 200, headers: { "Content-Type": "application/json" } },
28
+ );
29
+ }
30
+
31
+ if (url.endsWith("/api/mcp/action") && init?.method === "POST") {
32
+ const body = JSON.parse(String(init.body));
33
+ return new Response(
34
+ JSON.stringify({
35
+ ok: true,
36
+ data: {
37
+ echoedAction: body.action,
38
+ echoedProject: body.project,
39
+ },
40
+ }),
41
+ { status: 200, headers: { "Content-Type": "application/json" } },
42
+ );
43
+ }
44
+
45
+ return new Response(JSON.stringify({ ok: false }), { status: 404 });
46
+ }) as typeof fetch;
47
+
48
+ const client = new MuninClient({
49
+ baseUrl: "http://localhost:4000",
50
+ project: "default",
51
+ fetchImpl: fakeFetch,
52
+ });
53
+
54
+ const capabilities = await client.capabilities();
55
+ assert.equal(capabilities.specVersion, "v1.0.0");
56
+
57
+ const result = await client.store({ key: "hello", content: "world" });
58
+ assert.equal(result.ok, true);
59
+ assert.equal((result.data as any).echoedAction, "store");
60
+
61
+ console.log("sdk-ts tests passed");
62
+ }
63
+
64
+ void run();
package/tsconfig.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "rootDir": "src",
5
+ "outDir": "dist"
6
+ },
7
+ "include": ["src"]
8
+ }