@kubbi.ai/mcp 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.
package/README.md ADDED
@@ -0,0 +1,172 @@
1
+ # @kubbi.ai/mcp
2
+
3
+ MCP server for [Kubbi](https://kubbi.ai) — send, claim, and inspect ephemeral encrypted payloads from any AI agent. Built on the [@kubbi.ai/sdk](https://www.npmjs.com/package/@kubbi.ai/sdk) TypeScript SDK.
4
+
5
+ ## What This Does
6
+
7
+ This MCP server gives AI agents (Claude, Cursor, VS Code Copilot, etc.) the ability to:
8
+
9
+ - **Claim** a kubbi — retrieve encrypted content using just a claim URL (zero configuration)
10
+ - **Inspect** a kubbi — check metadata without consuming it
11
+ - **Send** a kubbi — create an ephemeral payload and get a shareable claim URL
12
+ - **Get/Delete** kubbis you've created
13
+
14
+ The consumer tools (`kubbi_inspect`, `kubbi_claim`) need **zero auth** — any agent can receive and consume kubbis with no API key, no account, no setup. This is the killer feature for agent-to-agent handoffs.
15
+
16
+ ## Quick Start
17
+
18
+ ### Claude Desktop
19
+
20
+ Add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):
21
+
22
+ ```json
23
+ {
24
+ "mcpServers": {
25
+ "kubbi": {
26
+ "command": "npx",
27
+ "args": ["-y", "@kubbi.ai/mcp"],
28
+ "env": {
29
+ "KUBBI_API_KEY": "kb_your_key_here"
30
+ }
31
+ }
32
+ }
33
+ }
34
+ ```
35
+
36
+ ### Cursor
37
+
38
+ Add to `.cursor/mcp.json` in your project or global settings:
39
+
40
+ ```json
41
+ {
42
+ "mcpServers": {
43
+ "kubbi": {
44
+ "command": "npx",
45
+ "args": ["-y", "@kubbi.ai/mcp"],
46
+ "env": {
47
+ "KUBBI_API_KEY": "kb_your_key_here"
48
+ }
49
+ }
50
+ }
51
+ }
52
+ ```
53
+
54
+ ### VS Code
55
+
56
+ Add to `.vscode/mcp.json`:
57
+
58
+ ```json
59
+ {
60
+ "servers": {
61
+ "kubbi": {
62
+ "type": "stdio",
63
+ "command": "npx",
64
+ "args": ["-y", "@kubbi.ai/mcp"],
65
+ "env": {
66
+ "KUBBI_API_KEY": "kb_your_key_here"
67
+ }
68
+ }
69
+ }
70
+ }
71
+ ```
72
+
73
+ > **Consumers only?** Omit `KUBBI_API_KEY` entirely. The `kubbi_inspect` and `kubbi_claim` tools will work with no configuration at all.
74
+
75
+ ## Environment Variables
76
+
77
+ | Variable | Required | Description |
78
+ |---|---|---|
79
+ | `KUBBI_API_KEY` | No* | API key for creating kubbis. Create one at [dashboard.kubbi.ai](https://dashboard.kubbi.ai). Only needed for producer tools. |
80
+ | `KUBBI_BASE_URL` | No | API base URL (default: `https://api.kubbi.ai`) |
81
+
82
+ *Producer tools (`kubbi_send`, `kubbi_send_files`, `kubbi_get`, `kubbi_delete`) require an API key. Consumer tools work without one.
83
+
84
+ ## Tools
85
+
86
+ ### kubbi_inspect (no auth)
87
+
88
+ Inspect a kubbi without consuming it. Returns metadata: status, content type, retrieval count, expiry. Does not count as a retrieval.
89
+
90
+ | Parameter | Type | Description |
91
+ |---|---|---|
92
+ | `claimUrl` | string | Full claim URL or bare claim token |
93
+
94
+ ### kubbi_claim (no auth)
95
+
96
+ Claim a kubbi and retrieve its decrypted content. Counts as a retrieval — if `max_retrievals` is reached, the content is permanently destroyed.
97
+
98
+ | Parameter | Type | Description |
99
+ |---|---|---|
100
+ | `claimUrl` | string | Full claim URL or bare claim token |
101
+
102
+ ### kubbi_send (requires API key)
103
+
104
+ Create an ephemeral encrypted kubbi. Returns a claim URL that any consumer can use.
105
+
106
+ | Parameter | Type | Default | Description |
107
+ |---|---|---|---|
108
+ | `content` | string or object | — | Payload to send (max 16KB) |
109
+ | `contentType` | string | `application/json` | `text/plain` or `application/json` |
110
+ | `ttlSeconds` | number | `3600` | Time-to-live in seconds (60–86400) |
111
+ | `maxRetrievals` | number | unlimited | Max claims before auto-burn. `1` = burn-after-read |
112
+ | `metadata` | object | — | Arbitrary metadata (max 1KB) |
113
+
114
+ ### kubbi_send_files (requires API key)
115
+
116
+ Create a multi-file kubbi package — up to 5 files (5 MB total) encrypted together with a single claim URL. For binary files, base64-encode the content and set `encoding` to `"base64"`.
117
+
118
+ | Parameter | Type | Default | Description |
119
+ |---|---|---|---|
120
+ | `files` | array | — | Array of file objects (1–5, max 5 MB total) |
121
+ | `files[].name` | string | — | Filename (e.g. `"config.json"`) |
122
+ | `files[].content` | string | — | File content (text or base64-encoded binary) |
123
+ | `files[].contentType` | string | `text/plain` | MIME type (e.g. `application/json`, `image/png`) |
124
+ | `files[].role` | string | — | Semantic role: `instructions`, `data`, `context`, `config`, or `attachment` |
125
+ | `files[].encoding` | string | — | Set to `base64` if content is base64-encoded |
126
+ | `ttlSeconds` | number | `3600` | Time-to-live in seconds (60–86400) |
127
+ | `maxRetrievals` | number | unlimited | Max claims before auto-burn |
128
+ | `metadata` | object | — | Arbitrary metadata (max 1KB) |
129
+
130
+ ### kubbi_get (requires API key)
131
+
132
+ Get detailed metadata for a kubbi you created.
133
+
134
+ | Parameter | Type | Description |
135
+ |---|---|---|
136
+ | `id` | string | Kubbi ID (UUID) |
137
+
138
+ ### kubbi_delete (requires API key)
139
+
140
+ Delete a kubbi, immediately wiping the encrypted payload.
141
+
142
+ | Parameter | Type | Description |
143
+ |---|---|---|
144
+ | `id` | string | Kubbi ID (UUID) |
145
+
146
+ ## Example: Agent-to-Agent Handoff
147
+
148
+ **Agent A** (has API key) creates a kubbi and passes the claim URL in its response:
149
+
150
+ > "I've stored the analysis results in a secure kubbi. Here's the claim URL: `https://api.kubbi.ai/r/x7k9m2...`"
151
+
152
+ **Agent B** (no API key, no setup) claims it:
153
+
154
+ > Tool call: `kubbi_claim({ claimUrl: "https://api.kubbi.ai/r/x7k9m2..." })`
155
+
156
+ The content is retrieved and automatically destroyed (if `maxRetrievals: 1`).
157
+
158
+ ## Development
159
+
160
+ ```bash
161
+ git clone https://github.com/kubbi/kubbi-mcp.git
162
+ cd kubbi-mcp
163
+ npm install
164
+ npm run build
165
+
166
+ # Test locally with MCP Inspector
167
+ npx @modelcontextprotocol/inspector node dist/index.js
168
+ ```
169
+
170
+ ## License
171
+
172
+ MIT
@@ -0,0 +1,7 @@
1
+ export declare function formatError(err: unknown): {
2
+ content: {
3
+ type: "text";
4
+ text: string;
5
+ }[];
6
+ isError: boolean;
7
+ };
package/dist/errors.js ADDED
@@ -0,0 +1,35 @@
1
+ import { KubbiApiError, KubbiValidationError, KubbiQuotaExceededError, KubbiRateLimitError, KubbiAuthenticationError, KubbiNotFoundError, KubbiGoneError, KubbiConflictError, KubbiNetworkError, } from "@kubbi.ai/sdk";
2
+ export function formatError(err) {
3
+ let message;
4
+ if (err instanceof KubbiValidationError) {
5
+ message = `Validation error: ${err.messages?.join(", ") ?? err.message}`;
6
+ }
7
+ else if (err instanceof KubbiQuotaExceededError) {
8
+ message = `Quota exceeded: ${err.message}`;
9
+ }
10
+ else if (err instanceof KubbiRateLimitError) {
11
+ message = `Rate limited: ${err.message}. Try again shortly.`;
12
+ }
13
+ else if (err instanceof KubbiAuthenticationError) {
14
+ message = `Authentication failed: ${err.message}. Check your KUBBI_API_KEY.`;
15
+ }
16
+ else if (err instanceof KubbiConflictError) {
17
+ message = `Conflict: ${err.message}`;
18
+ }
19
+ else if (err instanceof KubbiGoneError) {
20
+ message = `This kubbi has been consumed or expired (410 Gone). ${err.message}`;
21
+ }
22
+ else if (err instanceof KubbiNotFoundError) {
23
+ message = `Not found (404). ${err.message}`;
24
+ }
25
+ else if (err instanceof KubbiNetworkError) {
26
+ message = `Network error contacting Kubbi API: ${err.message}`;
27
+ }
28
+ else if (err instanceof KubbiApiError) {
29
+ message = `Kubbi API error (${err.status}): ${err.message}`;
30
+ }
31
+ else {
32
+ message = `Unexpected error: ${err instanceof Error ? err.message : String(err)}`;
33
+ }
34
+ return { content: [{ type: "text", text: message }], isError: true };
35
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { KubbiClient } from "@kubbi.ai/sdk";
5
+ import { registerInspect } from "./tools/inspect.js";
6
+ import { registerClaim } from "./tools/claim.js";
7
+ import { registerSend } from "./tools/send.js";
8
+ import { registerGet } from "./tools/get.js";
9
+ import { registerDelete } from "./tools/delete.js";
10
+ const apiKey = process.env.KUBBI_API_KEY;
11
+ const baseUrl = process.env.KUBBI_BASE_URL ?? "https://api.kubbi.ai";
12
+ const client = apiKey
13
+ ? new KubbiClient({ apiKey, baseUrl })
14
+ : null;
15
+ const server = new McpServer({
16
+ name: "kubbi",
17
+ version: "0.1.0",
18
+ });
19
+ registerInspect(server, baseUrl);
20
+ registerClaim(server, baseUrl);
21
+ registerSend(server, client);
22
+ registerGet(server, client);
23
+ registerDelete(server, client);
24
+ const transport = new StdioServerTransport();
25
+ await server.connect(transport);
@@ -0,0 +1,2 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerClaim(server: McpServer, baseUrl: string): void;
@@ -0,0 +1,59 @@
1
+ import { z } from "zod";
2
+ import { KubbiClient, isPackageClaim } from "@kubbi.ai/sdk";
3
+ import { formatError } from "../errors.js";
4
+ export function registerClaim(server, baseUrl) {
5
+ server.registerTool("kubbi_claim", {
6
+ description: "Claim a kubbi and retrieve its decrypted content. This COUNTS as a retrieval — if max_retrievals is reached, the content is permanently destroyed afterward. Works for both single-content and multi-file kubbis. Accepts a full claim URL or a bare claim token. No API key required.",
7
+ inputSchema: z.object({
8
+ claimUrl: z
9
+ .string()
10
+ .describe("Full claim URL (e.g. https://api.kubbi.ai/r/abc123) or bare claim token"),
11
+ }),
12
+ }, async ({ claimUrl }) => {
13
+ try {
14
+ const result = await KubbiClient.claim(claimUrl, { baseUrl });
15
+ if (isPackageClaim(result)) {
16
+ const lines = [
17
+ `Kubbi package claimed (${result.fileCount} file${result.fileCount === 1 ? "" : "s"}).`,
18
+ `Created: ${result.createdAt}`,
19
+ `Expires: ${result.expiresAt}`,
20
+ ];
21
+ if (result.metadata && Object.keys(result.metadata).length > 0) {
22
+ lines.push(`Metadata: ${JSON.stringify(result.metadata)}`);
23
+ }
24
+ for (const file of result.files) {
25
+ lines.push("");
26
+ lines.push(`--- ${file.name} (${file.contentType}, ${file.sizeBytes} bytes${file.role ? `, role: ${file.role}` : ""}) ---`);
27
+ if (file.encoding === "base64") {
28
+ lines.push(`[base64-encoded binary, ${file.sizeBytes} bytes]`);
29
+ lines.push(file.content);
30
+ }
31
+ else {
32
+ lines.push(file.content);
33
+ }
34
+ }
35
+ return {
36
+ content: [{ type: "text", text: lines.join("\n") }],
37
+ };
38
+ }
39
+ const contentStr = typeof result.content === "string"
40
+ ? result.content
41
+ : JSON.stringify(result.content, null, 2);
42
+ const parts = [
43
+ `Content-Type: ${result.contentType}`,
44
+ `Created: ${result.createdAt}`,
45
+ `Expires: ${result.expiresAt}`,
46
+ ];
47
+ if (result.metadata && Object.keys(result.metadata).length > 0) {
48
+ parts.push(`Metadata: ${JSON.stringify(result.metadata)}`);
49
+ }
50
+ parts.push("", "--- Content ---", contentStr);
51
+ return {
52
+ content: [{ type: "text", text: parts.join("\n") }],
53
+ };
54
+ }
55
+ catch (err) {
56
+ return formatError(err);
57
+ }
58
+ });
59
+ }
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { KubbiClient } from "@kubbi.ai/sdk";
3
+ export declare function registerDelete(server: McpServer, client: KubbiClient | null): void;
@@ -0,0 +1,32 @@
1
+ import { z } from "zod";
2
+ import { formatError } from "../errors.js";
3
+ const NO_KEY_MSG = "KUBBI_API_KEY is not configured. Set it in your MCP server environment to delete kubbis.";
4
+ export function registerDelete(server, client) {
5
+ server.registerTool("kubbi_delete", {
6
+ description: "Delete (burn) a kubbi you created. Immediately wipes the encrypted payload. Any subsequent claim attempts will receive 410 Gone. Requires KUBBI_API_KEY.",
7
+ inputSchema: z.object({
8
+ id: z.string().describe("The kubbi ID (UUID) to delete"),
9
+ }),
10
+ }, async ({ id }) => {
11
+ if (!client) {
12
+ return {
13
+ content: [{ type: "text", text: NO_KEY_MSG }],
14
+ isError: true,
15
+ };
16
+ }
17
+ try {
18
+ const result = await client.delete(id);
19
+ return {
20
+ content: [
21
+ {
22
+ type: "text",
23
+ text: `Kubbi ${id} deleted. Status: ${result.status}`,
24
+ },
25
+ ],
26
+ };
27
+ }
28
+ catch (err) {
29
+ return formatError(err);
30
+ }
31
+ });
32
+ }
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { KubbiClient } from "@kubbi.ai/sdk";
3
+ export declare function registerGet(server: McpServer, client: KubbiClient | null): void;
@@ -0,0 +1,29 @@
1
+ import { z } from "zod";
2
+ import { formatError } from "../errors.js";
3
+ const NO_KEY_MSG = "KUBBI_API_KEY is not configured. Set it in your MCP server environment to view kubbi details.";
4
+ export function registerGet(server, client) {
5
+ server.registerTool("kubbi_get", {
6
+ description: "Get detailed metadata for a kubbi you created (by ID). Shows status, retrieval count, timestamps, and whether it has been claimed. Requires KUBBI_API_KEY.",
7
+ inputSchema: z.object({
8
+ id: z.string().describe("The kubbi ID (UUID) returned when it was created"),
9
+ }),
10
+ }, async ({ id }) => {
11
+ if (!client) {
12
+ return {
13
+ content: [{ type: "text", text: NO_KEY_MSG }],
14
+ isError: true,
15
+ };
16
+ }
17
+ try {
18
+ const result = await client.get(id);
19
+ return {
20
+ content: [
21
+ { type: "text", text: JSON.stringify(result, null, 2) },
22
+ ],
23
+ };
24
+ }
25
+ catch (err) {
26
+ return formatError(err);
27
+ }
28
+ });
29
+ }
@@ -0,0 +1,2 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerInspect(server: McpServer, baseUrl: string): void;
@@ -0,0 +1,62 @@
1
+ import { z } from "zod";
2
+ import { KubbiClient, isPackageInspect } from "@kubbi.ai/sdk";
3
+ import { formatError } from "../errors.js";
4
+ export function registerInspect(server, baseUrl) {
5
+ server.registerTool("kubbi_inspect", {
6
+ description: "Inspect a kubbi without consuming it. Returns metadata (status, content type, retrieval count, expiry). For multi-file kubbis, also shows the file manifest. Does NOT count as a retrieval — safe to call repeatedly. Accepts a full claim URL or a bare claim token. No API key required.",
7
+ inputSchema: z.object({
8
+ claimUrl: z
9
+ .string()
10
+ .describe("Full claim URL (e.g. https://api.kubbi.ai/r/abc123) or bare claim token"),
11
+ }),
12
+ }, async ({ claimUrl }) => {
13
+ try {
14
+ const result = await KubbiClient.inspect(claimUrl, { baseUrl });
15
+ if (isPackageInspect(result)) {
16
+ const lines = [
17
+ `Kubbi Package (${result.fileCount} file${result.fileCount === 1 ? "" : "s"}, ${result.totalSizeBytes} bytes total)`,
18
+ `Status: ${result.status}`,
19
+ `Max Retrievals: ${result.maxRetrievals ?? "unlimited"}`,
20
+ `Retrieval Count: ${result.retrievalCount}`,
21
+ `Remaining Reads: ${result.remainingReads ?? "unlimited"}`,
22
+ `Created: ${result.createdAt}`,
23
+ `Expires: ${result.expiresAt}`,
24
+ ];
25
+ if (result.metadata && Object.keys(result.metadata).length > 0) {
26
+ lines.push(`Metadata: ${JSON.stringify(result.metadata)}`);
27
+ }
28
+ lines.push("", "Files:");
29
+ for (const file of result.files) {
30
+ const rolePart = file.role ? ` (${file.role})` : "";
31
+ lines.push(` - ${file.name} [${file.contentType}, ${file.sizeBytes} bytes]${rolePart}`);
32
+ }
33
+ if (result.claim) {
34
+ lines.push("", `Claim: POST ${result.claim.url}`);
35
+ }
36
+ return {
37
+ content: [{ type: "text", text: lines.join("\n") }],
38
+ };
39
+ }
40
+ const lines = [
41
+ `Status: ${result.status}`,
42
+ `Content-Type: ${result.contentType}`,
43
+ `Max Retrievals: ${result.maxRetrievals ?? "unlimited"}`,
44
+ `Retrieval Count: ${result.retrievalCount}`,
45
+ `Created: ${result.createdAt}`,
46
+ `Expires: ${result.expiresAt}`,
47
+ ];
48
+ if (result.metadata && Object.keys(result.metadata).length > 0) {
49
+ lines.push(`Metadata: ${JSON.stringify(result.metadata)}`);
50
+ }
51
+ if (result.claim) {
52
+ lines.push("", `Claim: POST ${result.claim.url}`);
53
+ }
54
+ return {
55
+ content: [{ type: "text", text: lines.join("\n") }],
56
+ };
57
+ }
58
+ catch (err) {
59
+ return formatError(err);
60
+ }
61
+ });
62
+ }
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { KubbiClient } from "@kubbi.ai/sdk";
3
+ export declare function registerSend(server: McpServer, client: KubbiClient | null): void;
@@ -0,0 +1,142 @@
1
+ import { z } from "zod";
2
+ import { formatError } from "../errors.js";
3
+ const NO_KEY_MSG = "KUBBI_API_KEY is not configured. Set it in your MCP server environment to create kubbis. Consumer tools (kubbi_inspect, kubbi_claim) work without a key.";
4
+ export function registerSend(server, client) {
5
+ server.registerTool("kubbi_send", {
6
+ description: "Create a kubbi — an ephemeral, encrypted payload with a unique claim URL. Share the claim URL with any consumer (agent, service, human). The consumer can retrieve the content with no API key. Requires KUBBI_API_KEY.",
7
+ inputSchema: z.object({
8
+ content: z
9
+ .union([z.string(), z.record(z.unknown())])
10
+ .describe("Payload to send — a string or JSON object (max 16KB)"),
11
+ contentType: z
12
+ .enum(["text/plain", "application/json"])
13
+ .default("application/json")
14
+ .describe("MIME type of the content"),
15
+ ttlSeconds: z
16
+ .number()
17
+ .min(60)
18
+ .max(86400)
19
+ .default(3600)
20
+ .describe("Time-to-live in seconds (60–86400). Default: 3600 (1 hour)"),
21
+ maxRetrievals: z
22
+ .number()
23
+ .min(1)
24
+ .optional()
25
+ .describe("Max number of claims before auto-burn. Set to 1 for burn-after-read. Omit for unlimited."),
26
+ metadata: z
27
+ .record(z.unknown())
28
+ .optional()
29
+ .describe("Arbitrary metadata attached to the kubbi (max 1KB)"),
30
+ }),
31
+ }, async ({ content, contentType, ttlSeconds, maxRetrievals, metadata }) => {
32
+ if (!client) {
33
+ return {
34
+ content: [{ type: "text", text: NO_KEY_MSG }],
35
+ isError: true,
36
+ };
37
+ }
38
+ try {
39
+ const result = await client.send({
40
+ content,
41
+ contentType,
42
+ ttlSeconds,
43
+ maxRetrievals,
44
+ metadata,
45
+ });
46
+ const lines = [
47
+ "Kubbi created successfully.",
48
+ "",
49
+ `Claim URL: ${result.claimUrl}`,
50
+ `ID: ${result.id}`,
51
+ `Status: ${result.status}`,
52
+ `Content-Type: ${result.contentType}`,
53
+ `Max Retrievals: ${result.maxRetrievals ?? "unlimited"}`,
54
+ `Expires: ${result.expiresAt}`,
55
+ ];
56
+ if (result.metadata && Object.keys(result.metadata).length > 0) {
57
+ lines.push(`Metadata: ${JSON.stringify(result.metadata)}`);
58
+ }
59
+ return {
60
+ content: [{ type: "text", text: lines.join("\n") }],
61
+ };
62
+ }
63
+ catch (err) {
64
+ return formatError(err);
65
+ }
66
+ });
67
+ server.registerTool("kubbi_send_files", {
68
+ description: "Create a multi-file kubbi package — up to 5 files (5 MB total) encrypted together with a single claim URL. For text files, pass content as a string. For binary files, base64-encode the content and set encoding to 'base64'. Requires KUBBI_API_KEY.",
69
+ inputSchema: z.object({
70
+ files: z
71
+ .array(z.object({
72
+ name: z.string().describe("Filename (e.g. 'config.json', 'readme.md')"),
73
+ content: z.string().describe("File content as a string (text) or base64-encoded string (binary)"),
74
+ contentType: z
75
+ .string()
76
+ .default("text/plain")
77
+ .describe("MIME type (e.g. 'text/plain', 'application/json', 'image/png')"),
78
+ role: z
79
+ .enum(["instructions", "data", "context", "config", "attachment"])
80
+ .optional()
81
+ .describe("Semantic role of the file"),
82
+ encoding: z
83
+ .enum(["base64"])
84
+ .optional()
85
+ .describe("Set to 'base64' if content is base64-encoded binary"),
86
+ }))
87
+ .min(1)
88
+ .max(5)
89
+ .describe("Array of files (1–5, max 5 MB total)"),
90
+ ttlSeconds: z
91
+ .number()
92
+ .min(60)
93
+ .max(86400)
94
+ .default(3600)
95
+ .describe("Time-to-live in seconds (60–86400). Default: 3600 (1 hour)"),
96
+ maxRetrievals: z
97
+ .number()
98
+ .min(1)
99
+ .optional()
100
+ .describe("Max number of claims before auto-burn. Omit for unlimited."),
101
+ metadata: z
102
+ .record(z.unknown())
103
+ .optional()
104
+ .describe("Arbitrary metadata attached to the kubbi (max 1KB)"),
105
+ }),
106
+ }, async ({ files, ttlSeconds, maxRetrievals, metadata }) => {
107
+ if (!client) {
108
+ return {
109
+ content: [{ type: "text", text: NO_KEY_MSG }],
110
+ isError: true,
111
+ };
112
+ }
113
+ try {
114
+ const result = await client.sendFiles({
115
+ files,
116
+ ttlSeconds,
117
+ maxRetrievals,
118
+ metadata,
119
+ });
120
+ const lines = [
121
+ "Multi-file kubbi created successfully.",
122
+ "",
123
+ `Claim URL: ${result.claimUrl}`,
124
+ `ID: ${result.id}`,
125
+ `Status: ${result.status}`,
126
+ `Files: ${result.fileCount}`,
127
+ `Total Size: ${result.totalSizeBytes} bytes`,
128
+ `Max Retrievals: ${result.maxRetrievals ?? "unlimited"}`,
129
+ `Expires: ${result.expiresAt}`,
130
+ ];
131
+ if (result.metadata && Object.keys(result.metadata).length > 0) {
132
+ lines.push(`Metadata: ${JSON.stringify(result.metadata)}`);
133
+ }
134
+ return {
135
+ content: [{ type: "text", text: lines.join("\n") }],
136
+ };
137
+ }
138
+ catch (err) {
139
+ return formatError(err);
140
+ }
141
+ });
142
+ }
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@kubbi.ai/mcp",
3
+ "version": "0.1.0",
4
+ "description": "MCP server for Kubbi — send, claim, and inspect ephemeral encrypted payloads from any AI agent",
5
+ "type": "module",
6
+ "bin": {
7
+ "kubbi-mcp": "./dist/index.js"
8
+ },
9
+ "scripts": {
10
+ "build": "tsc",
11
+ "dev": "tsx src/index.ts",
12
+ "prepublishOnly": "npm run build"
13
+ },
14
+ "files": [
15
+ "dist"
16
+ ],
17
+ "keywords": [
18
+ "kubbi",
19
+ "mcp",
20
+ "model-context-protocol",
21
+ "ephemeral",
22
+ "encrypted",
23
+ "payload",
24
+ "handoff",
25
+ "agent",
26
+ "ai",
27
+ "tool"
28
+ ],
29
+ "author": "Kubbi",
30
+ "license": "MIT",
31
+ "engines": {
32
+ "node": ">=20.0.0"
33
+ },
34
+ "dependencies": {
35
+ "@kubbi.ai/sdk": "^0.1.0",
36
+ "@modelcontextprotocol/sdk": "^1.12.1",
37
+ "zod": "^3.25.0"
38
+ },
39
+ "devDependencies": {
40
+ "@types/node": "^22.0.0",
41
+ "tsx": "^4.0.0",
42
+ "typescript": "^5.6.0"
43
+ }
44
+ }