klamdo-mcp 1.0.0 → 1.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/dist/http.d.ts +14 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +119 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +11 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -284
- package/dist/index.js.map +1 -1
- package/dist/tools.d.ts +93 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +224 -0
- package/dist/tools.js.map +1 -0
- package/package.json +5 -3
- package/smithery.yaml +22 -0
- package/src/http.ts +135 -0
- package/src/index.ts +21 -336
- package/src/tools.ts +283 -0
package/dist/tools.js
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Shared tool definitions and implementations for both stdio and HTTP transports.
|
|
4
|
+
* The apiKey is per-request in HTTP mode, per-process in stdio mode.
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.TOOLS = exports.BASE_URL = void 0;
|
|
8
|
+
exports.klamdo = klamdo;
|
|
9
|
+
exports.registerHandlers = registerHandlers;
|
|
10
|
+
const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
|
|
11
|
+
exports.BASE_URL = process.env.KLAMDO_BASE_URL ?? "https://klamdo.app";
|
|
12
|
+
async function klamdo(path, apiKey, body) {
|
|
13
|
+
const res = await fetch(`${exports.BASE_URL}/api/mcp${path}`, {
|
|
14
|
+
method: body ? "POST" : "GET",
|
|
15
|
+
headers: {
|
|
16
|
+
"Content-Type": "application/json",
|
|
17
|
+
Authorization: `Bearer ${apiKey}`
|
|
18
|
+
},
|
|
19
|
+
body: body ? JSON.stringify(body) : undefined
|
|
20
|
+
});
|
|
21
|
+
if (!res.ok) {
|
|
22
|
+
const text = await res.text().catch(() => "");
|
|
23
|
+
if (res.status === 401) {
|
|
24
|
+
throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidRequest, "Invalid API key. Get your key at https://klamdo.app/profile");
|
|
25
|
+
}
|
|
26
|
+
throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Klamdo API error ${res.status}: ${text.slice(0, 200)}`);
|
|
27
|
+
}
|
|
28
|
+
return res.json();
|
|
29
|
+
}
|
|
30
|
+
exports.TOOLS = [
|
|
31
|
+
{
|
|
32
|
+
name: "generate_image",
|
|
33
|
+
description: "Generate a 4K identity-locked image using the user's reference photo on Klamdo. " +
|
|
34
|
+
"Returns a job ID — use check_job to poll for the result. Costs 3 credits.",
|
|
35
|
+
inputSchema: {
|
|
36
|
+
type: "object",
|
|
37
|
+
properties: {
|
|
38
|
+
prompt: {
|
|
39
|
+
type: "string",
|
|
40
|
+
description: "Describe the content you want to generate. Be specific about setting, mood, style, and purpose."
|
|
41
|
+
},
|
|
42
|
+
aspectRatio: {
|
|
43
|
+
type: "string",
|
|
44
|
+
enum: ["1:1", "16:9", "9:16"],
|
|
45
|
+
description: "Output aspect ratio. Default: 1:1 (square).",
|
|
46
|
+
default: "1:1"
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
required: ["prompt"]
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: "generate_video",
|
|
54
|
+
description: "Generate a 5-second vertical identity-locked video from the user's reference photo on Klamdo. " +
|
|
55
|
+
"Returns a job ID — use check_job to poll. Aspect ratio locked to 9:16. Costs 6 credits.",
|
|
56
|
+
inputSchema: {
|
|
57
|
+
type: "object",
|
|
58
|
+
properties: {
|
|
59
|
+
prompt: {
|
|
60
|
+
type: "string",
|
|
61
|
+
description: "Describe the video content with motion cues for best results."
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
required: ["prompt"]
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: "check_job",
|
|
69
|
+
description: "Check the status of a Klamdo generation job. Returns status and download URLs when complete.",
|
|
70
|
+
inputSchema: {
|
|
71
|
+
type: "object",
|
|
72
|
+
properties: {
|
|
73
|
+
jobId: { type: "string", description: "The job ID returned from generate_image or generate_video" }
|
|
74
|
+
},
|
|
75
|
+
required: ["jobId"]
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
name: "get_account",
|
|
80
|
+
description: "Get current Klamdo account status: credit balance, plan, and free sample eligibility.",
|
|
81
|
+
inputSchema: { type: "object", properties: {} }
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
name: "list_jobs",
|
|
85
|
+
description: "List recent Klamdo generation jobs for this account.",
|
|
86
|
+
inputSchema: {
|
|
87
|
+
type: "object",
|
|
88
|
+
properties: {
|
|
89
|
+
limit: { type: "number", description: "Maximum jobs to return (default: 10, max: 50)", default: 10 }
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
];
|
|
94
|
+
function registerHandlers(server, getApiKey) {
|
|
95
|
+
server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({ tools: exports.TOOLS }));
|
|
96
|
+
server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
97
|
+
const { name, arguments: args } = request.params;
|
|
98
|
+
const input = (args ?? {});
|
|
99
|
+
const apiKey = getApiKey();
|
|
100
|
+
if (!apiKey) {
|
|
101
|
+
throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidRequest, "No API key provided. Set KLAMDO_API_KEY or pass it via Authorization header.");
|
|
102
|
+
}
|
|
103
|
+
try {
|
|
104
|
+
switch (name) {
|
|
105
|
+
case "generate_image": {
|
|
106
|
+
const result = await klamdo("/jobs", apiKey, { prompt: input.prompt, mode: "image", aspectRatio: input.aspectRatio ?? "1:1" });
|
|
107
|
+
return {
|
|
108
|
+
content: [
|
|
109
|
+
{
|
|
110
|
+
type: "text",
|
|
111
|
+
text: `Image generation started.\n\nJob ID: ${result.jobId}\nStatus: ${result.status}\nCredits reserved: ${result.creditsReserved}\n\nUse check_job("${result.jobId}") to get the result. Images typically complete in 30–90 seconds.`
|
|
112
|
+
}
|
|
113
|
+
]
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
case "generate_video": {
|
|
117
|
+
const result = await klamdo("/jobs", apiKey, { prompt: input.prompt, mode: "video", aspectRatio: "9:16" });
|
|
118
|
+
return {
|
|
119
|
+
content: [
|
|
120
|
+
{
|
|
121
|
+
type: "text",
|
|
122
|
+
text: `Video generation started.\n\nJob ID: ${result.jobId}\nStatus: ${result.status}\nCredits reserved: ${result.creditsReserved}\n\nUse check_job("${result.jobId}") to get the result. Videos typically complete in 2–5 minutes.`
|
|
123
|
+
}
|
|
124
|
+
]
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
case "check_job": {
|
|
128
|
+
const jobId = String(input.jobId ?? "");
|
|
129
|
+
if (!jobId)
|
|
130
|
+
throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidParams, "jobId is required");
|
|
131
|
+
const result = await klamdo(`/jobs/${jobId}`, apiKey);
|
|
132
|
+
if (result.status === "completed") {
|
|
133
|
+
const imageAsset = result.assets.find((a) => a.kind === "image");
|
|
134
|
+
const videoAsset = result.assets.find((a) => a.kind === "video");
|
|
135
|
+
const lines = [
|
|
136
|
+
`Job ${jobId} — Completed ✓`,
|
|
137
|
+
"",
|
|
138
|
+
imageAsset ? `Image URL: ${imageAsset.url}` : null,
|
|
139
|
+
videoAsset ? `Video URL: ${videoAsset.url}` : null,
|
|
140
|
+
result.caption ? `\nSuggested caption:\n${result.caption}` : null,
|
|
141
|
+
"",
|
|
142
|
+
`Share page: ${exports.BASE_URL}/share/${jobId}`
|
|
143
|
+
].filter(Boolean);
|
|
144
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
145
|
+
}
|
|
146
|
+
if (result.status === "failed") {
|
|
147
|
+
return {
|
|
148
|
+
content: [
|
|
149
|
+
{
|
|
150
|
+
type: "text",
|
|
151
|
+
text: `Job ${jobId} failed: ${result.errorMessage ?? "Unknown error"}. Credits have been refunded.`
|
|
152
|
+
}
|
|
153
|
+
]
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
return {
|
|
157
|
+
content: [
|
|
158
|
+
{
|
|
159
|
+
type: "text",
|
|
160
|
+
text: `Job ${jobId} is still running (status: ${result.status}). Check again in 15–30 seconds.`
|
|
161
|
+
}
|
|
162
|
+
]
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
case "get_account": {
|
|
166
|
+
const result = await klamdo("/account", apiKey);
|
|
167
|
+
return {
|
|
168
|
+
content: [
|
|
169
|
+
{
|
|
170
|
+
type: "text",
|
|
171
|
+
text: [
|
|
172
|
+
`Klamdo Account: ${result.name} (${result.email})`,
|
|
173
|
+
`Plan: ${result.planTier}`,
|
|
174
|
+
`Credits: ${result.availableCredits}`,
|
|
175
|
+
result.freeSampleEligible ? "Free sample: available" : ""
|
|
176
|
+
].filter(Boolean).join("\n")
|
|
177
|
+
}
|
|
178
|
+
]
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
case "list_jobs": {
|
|
182
|
+
const limit = Math.min(Number(input.limit ?? 10), 50);
|
|
183
|
+
const result = await klamdo(`/jobs?limit=${limit}`, apiKey);
|
|
184
|
+
const lines = result.jobs.map((j) => `[${j.status}] ${j.id} — ${j.mode} — "${j.prompt.slice(0, 60)}" (${j.createdAt.slice(0, 10)})`);
|
|
185
|
+
return {
|
|
186
|
+
content: [{ type: "text", text: lines.length ? lines.join("\n") : "No jobs found." }]
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
default:
|
|
190
|
+
throw new types_js_1.McpError(types_js_1.ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
catch (err) {
|
|
194
|
+
if (err instanceof types_js_1.McpError)
|
|
195
|
+
throw err;
|
|
196
|
+
throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, err instanceof Error ? err.message : String(err));
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
server.setRequestHandler(types_js_1.ListResourcesRequestSchema, async () => ({
|
|
200
|
+
resources: [
|
|
201
|
+
{
|
|
202
|
+
uri: "klamdo://docs",
|
|
203
|
+
name: "Klamdo API Documentation",
|
|
204
|
+
description: "How to use Klamdo's MCP tools",
|
|
205
|
+
mimeType: "text/plain"
|
|
206
|
+
}
|
|
207
|
+
]
|
|
208
|
+
}));
|
|
209
|
+
server.setRequestHandler(types_js_1.ReadResourceRequestSchema, async (request) => {
|
|
210
|
+
if (request.params.uri === "klamdo://docs") {
|
|
211
|
+
return {
|
|
212
|
+
contents: [
|
|
213
|
+
{
|
|
214
|
+
uri: "klamdo://docs",
|
|
215
|
+
mimeType: "text/plain",
|
|
216
|
+
text: `# Klamdo MCP Server\n\nTools: generate_image, generate_video, check_job, get_account, list_jobs\n\nGet your API key at https://klamdo.app/profile\nPricing: $19.99/mo for 120 credits (3/image, 6/video)\n`
|
|
217
|
+
}
|
|
218
|
+
]
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidRequest, `Resource not found: ${request.params.uri}`);
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
//# sourceMappingURL=tools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tools.js","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAcH,wBA6BC;AAsED,4CAsKC;AArRD,iEAO4C;AAG/B,QAAA,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,oBAAoB,CAAC;AAErE,KAAK,UAAU,MAAM,CAC1B,IAAY,EACZ,MAAc,EACd,IAA8B;IAE9B,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,gBAAQ,WAAW,IAAI,EAAE,EAAE;QACpD,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK;QAC7B,OAAO,EAAE;YACP,cAAc,EAAE,kBAAkB;YAClC,aAAa,EAAE,UAAU,MAAM,EAAE;SAClC;QACD,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS;KAC9C,CAAC,CAAC;IAEH,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;QAC9C,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YACvB,MAAM,IAAI,mBAAQ,CAChB,oBAAS,CAAC,cAAc,EACxB,6DAA6D,CAC9D,CAAC;QACJ,CAAC;QACD,MAAM,IAAI,mBAAQ,CAChB,oBAAS,CAAC,aAAa,EACvB,oBAAoB,GAAG,CAAC,MAAM,KAAK,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CACxD,CAAC;IACJ,CAAC;IAED,OAAO,GAAG,CAAC,IAAI,EAAgB,CAAC;AAClC,CAAC;AAEY,QAAA,KAAK,GAAG;IACnB;QACE,IAAI,EAAE,gBAAgB;QACtB,WAAW,EACT,kFAAkF;YAClF,2EAA2E;QAC7E,WAAW,EAAE;YACX,IAAI,EAAE,QAAQ;YACd,UAAU,EAAE;gBACV,MAAM,EAAE;oBACN,IAAI,EAAE,QAAQ;oBACd,WAAW,EACT,iGAAiG;iBACpG;gBACD,WAAW,EAAE;oBACX,IAAI,EAAE,QAAQ;oBACd,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC;oBAC7B,WAAW,EAAE,6CAA6C;oBAC1D,OAAO,EAAE,KAAK;iBACf;aACF;YACD,QAAQ,EAAE,CAAC,QAAQ,CAAC;SACrB;KACF;IACD;QACE,IAAI,EAAE,gBAAgB;QACtB,WAAW,EACT,gGAAgG;YAChG,yFAAyF;QAC3F,WAAW,EAAE;YACX,IAAI,EAAE,QAAQ;YACd,UAAU,EAAE;gBACV,MAAM,EAAE;oBACN,IAAI,EAAE,QAAQ;oBACd,WAAW,EAAE,+DAA+D;iBAC7E;aACF;YACD,QAAQ,EAAE,CAAC,QAAQ,CAAC;SACrB;KACF;IACD;QACE,IAAI,EAAE,WAAW;QACjB,WAAW,EAAE,8FAA8F;QAC3G,WAAW,EAAE;YACX,IAAI,EAAE,QAAQ;YACd,UAAU,EAAE;gBACV,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,2DAA2D,EAAE;aACpG;YACD,QAAQ,EAAE,CAAC,OAAO,CAAC;SACpB;KACF;IACD;QACE,IAAI,EAAE,aAAa;QACnB,WAAW,EAAE,uFAAuF;QACpG,WAAW,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,EAAE,EAAE;KAChD;IACD;QACE,IAAI,EAAE,WAAW;QACjB,WAAW,EAAE,sDAAsD;QACnE,WAAW,EAAE;YACX,IAAI,EAAE,QAAQ;YACd,UAAU,EAAE;gBACV,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,+CAA+C,EAAE,OAAO,EAAE,EAAE,EAAE;aACrG;SACF;KACF;CACF,CAAC;AAEF,SAAgB,gBAAgB,CAAC,MAAc,EAAE,SAAuB;IACtE,MAAM,CAAC,iBAAiB,CAAC,iCAAsB,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,aAAK,EAAE,CAAC,CAAC,CAAC;IAEjF,MAAM,CAAC,iBAAiB,CAAC,gCAAqB,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;QAChE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC;QACjD,MAAM,KAAK,GAAG,CAAC,IAAI,IAAI,EAAE,CAA4B,CAAC;QACtD,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;QAE3B,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,IAAI,mBAAQ,CAChB,oBAAS,CAAC,cAAc,EACxB,8EAA8E,CAC/E,CAAC;QACJ,CAAC;QAED,IAAI,CAAC;YACH,QAAQ,IAAI,EAAE,CAAC;gBACb,KAAK,gBAAgB,CAAC,CAAC,CAAC;oBACtB,MAAM,MAAM,GAAG,MAAM,MAAM,CACzB,OAAO,EACP,MAAM,EACN,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,WAAW,EAAE,KAAK,CAAC,WAAW,IAAI,KAAK,EAAE,CACjF,CAAC;oBACF,OAAO;wBACL,OAAO,EAAE;4BACP;gCACE,IAAI,EAAE,MAAM;gCACZ,IAAI,EAAE,wCAAwC,MAAM,CAAC,KAAK,aAAa,MAAM,CAAC,MAAM,uBAAuB,MAAM,CAAC,eAAe,sBAAsB,MAAM,CAAC,KAAK,mEAAmE;6BACvO;yBACF;qBACF,CAAC;gBACJ,CAAC;gBAED,KAAK,gBAAgB,CAAC,CAAC,CAAC;oBACtB,MAAM,MAAM,GAAG,MAAM,MAAM,CACzB,OAAO,EACP,MAAM,EACN,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,CAC7D,CAAC;oBACF,OAAO;wBACL,OAAO,EAAE;4BACP;gCACE,IAAI,EAAE,MAAM;gCACZ,IAAI,EAAE,wCAAwC,MAAM,CAAC,KAAK,aAAa,MAAM,CAAC,MAAM,uBAAuB,MAAM,CAAC,eAAe,sBAAsB,MAAM,CAAC,KAAK,iEAAiE;6BACrO;yBACF;qBACF,CAAC;gBACJ,CAAC;gBAED,KAAK,WAAW,CAAC,CAAC,CAAC;oBACjB,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC;oBACxC,IAAI,CAAC,KAAK;wBAAE,MAAM,IAAI,mBAAQ,CAAC,oBAAS,CAAC,aAAa,EAAE,mBAAmB,CAAC,CAAC;oBAE7E,MAAM,MAAM,GAAG,MAAM,MAAM,CAKxB,SAAS,KAAK,EAAE,EAAE,MAAM,CAAC,CAAC;oBAE7B,IAAI,MAAM,CAAC,MAAM,KAAK,WAAW,EAAE,CAAC;wBAClC,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC;wBACjE,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC;wBACjE,MAAM,KAAK,GAAG;4BACZ,OAAO,KAAK,gBAAgB;4BAC5B,EAAE;4BACF,UAAU,CAAC,CAAC,CAAC,cAAc,UAAU,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,IAAI;4BAClD,UAAU,CAAC,CAAC,CAAC,cAAc,UAAU,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,IAAI;4BAClD,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,yBAAyB,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,IAAI;4BACjE,EAAE;4BACF,eAAe,gBAAQ,UAAU,KAAK,EAAE;yBACzC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;wBAClB,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;oBACjE,CAAC;oBAED,IAAI,MAAM,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;wBAC/B,OAAO;4BACL,OAAO,EAAE;gCACP;oCACE,IAAI,EAAE,MAAM;oCACZ,IAAI,EAAE,OAAO,KAAK,YAAY,MAAM,CAAC,YAAY,IAAI,eAAe,+BAA+B;iCACpG;6BACF;yBACF,CAAC;oBACJ,CAAC;oBAED,OAAO;wBACL,OAAO,EAAE;4BACP;gCACE,IAAI,EAAE,MAAM;gCACZ,IAAI,EAAE,OAAO,KAAK,8BAA8B,MAAM,CAAC,MAAM,kCAAkC;6BAChG;yBACF;qBACF,CAAC;gBACJ,CAAC;gBAED,KAAK,aAAa,CAAC,CAAC,CAAC;oBACnB,MAAM,MAAM,GAAG,MAAM,MAAM,CAMxB,UAAU,EAAE,MAAM,CAAC,CAAC;oBACvB,OAAO;wBACL,OAAO,EAAE;4BACP;gCACE,IAAI,EAAE,MAAM;gCACZ,IAAI,EAAE;oCACJ,mBAAmB,MAAM,CAAC,IAAI,KAAK,MAAM,CAAC,KAAK,GAAG;oCAClD,SAAS,MAAM,CAAC,QAAQ,EAAE;oCAC1B,YAAY,MAAM,CAAC,gBAAgB,EAAE;oCACrC,MAAM,CAAC,kBAAkB,CAAC,CAAC,CAAC,wBAAwB,CAAC,CAAC,CAAC,EAAE;iCAC1D,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;6BAC7B;yBACF;qBACF,CAAC;gBACJ,CAAC;gBAED,KAAK,WAAW,CAAC,CAAC,CAAC;oBACjB,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;oBACtD,MAAM,MAAM,GAAG,MAAM,MAAM,CAExB,eAAe,KAAK,EAAE,EAAE,MAAM,CAAC,CAAC;oBACnC,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAC3B,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,IAAI,OAAO,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CACtG,CAAC;oBACF,OAAO;wBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,gBAAgB,EAAE,CAAC;qBACtF,CAAC;gBACJ,CAAC;gBAED;oBACE,MAAM,IAAI,mBAAQ,CAAC,oBAAS,CAAC,cAAc,EAAE,iBAAiB,IAAI,EAAE,CAAC,CAAC;YAC1E,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,GAAG,YAAY,mBAAQ;gBAAE,MAAM,GAAG,CAAC;YACvC,MAAM,IAAI,mBAAQ,CAAC,oBAAS,CAAC,aAAa,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;QAChG,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,MAAM,CAAC,iBAAiB,CAAC,qCAA0B,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC;QAChE,SAAS,EAAE;YACT;gBACE,GAAG,EAAE,eAAe;gBACpB,IAAI,EAAE,0BAA0B;gBAChC,WAAW,EAAE,+BAA+B;gBAC5C,QAAQ,EAAE,YAAY;aACvB;SACF;KACF,CAAC,CAAC,CAAC;IAEJ,MAAM,CAAC,iBAAiB,CAAC,oCAAyB,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;QACpE,IAAI,OAAO,CAAC,MAAM,CAAC,GAAG,KAAK,eAAe,EAAE,CAAC;YAC3C,OAAO;gBACL,QAAQ,EAAE;oBACR;wBACE,GAAG,EAAE,eAAe;wBACpB,QAAQ,EAAE,YAAY;wBACtB,IAAI,EAAE,4MAA4M;qBACnN;iBACF;aACF,CAAC;QACJ,CAAC;QACD,MAAM,IAAI,mBAAQ,CAAC,oBAAS,CAAC,cAAc,EAAE,uBAAuB,OAAO,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC;IAC5F,CAAC,CAAC,CAAC;AACL,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "klamdo-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "MCP server for Klamdo — AI content generation for coaches and creator-founders. Generate 4K images and 5-second vertical videos from a reference photo via Claude Desktop or any MCP-compatible client.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -9,12 +9,14 @@
|
|
|
9
9
|
"scripts": {
|
|
10
10
|
"build": "tsc",
|
|
11
11
|
"start": "node dist/index.js",
|
|
12
|
-
"
|
|
12
|
+
"start:http": "node dist/http.js",
|
|
13
|
+
"dev": "ts-node src/index.ts",
|
|
14
|
+
"dev:http": "ts-node src/http.ts"
|
|
13
15
|
},
|
|
14
16
|
"keywords": ["mcp", "klamdo", "ai-content", "image-generation", "video-generation", "creator-tools"],
|
|
15
17
|
"license": "MIT",
|
|
16
18
|
"dependencies": {
|
|
17
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
19
|
+
"@modelcontextprotocol/sdk": "^1.10.0"
|
|
18
20
|
},
|
|
19
21
|
"devDependencies": {
|
|
20
22
|
"@types/node": "^20.0.0",
|
package/smithery.yaml
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
name: Klamdo — AI Content for Coaches & Creators
|
|
2
|
+
description: |
|
|
3
|
+
Generate identity-locked 4K images and 5-second vertical videos from a single
|
|
4
|
+
reference photo. Built for coaches, consultants, and founder-creators who need
|
|
5
|
+
consistent personal brand content without filming.
|
|
6
|
+
|
|
7
|
+
Tools: generate_image, generate_video, check_job, get_account, list_jobs
|
|
8
|
+
|
|
9
|
+
Requires a Klamdo API key — get yours free at https://klamdo.app/profile
|
|
10
|
+
|
|
11
|
+
version: "1.1.0"
|
|
12
|
+
homepage: https://klamdo.app
|
|
13
|
+
documentationUrl: https://klamdo.app/answers
|
|
14
|
+
|
|
15
|
+
startCommand:
|
|
16
|
+
type: http
|
|
17
|
+
url: https://mcp.klamdo.app/mcp
|
|
18
|
+
|
|
19
|
+
auth:
|
|
20
|
+
type: bearer
|
|
21
|
+
label: Klamdo API Key
|
|
22
|
+
hint: Get your key at https://klamdo.app/profile
|
package/src/http.ts
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Klamdo MCP Server — Streamable HTTP transport
|
|
4
|
+
*
|
|
5
|
+
* Runs as a hosted service on mark-mini-s, exposed via Cloudflare Tunnel at:
|
|
6
|
+
* https://mcp.klamdo.app
|
|
7
|
+
*
|
|
8
|
+
* Each request is stateless. API key is read from Authorization: Bearer <key> header.
|
|
9
|
+
* Users get their key from https://klamdo.app/profile
|
|
10
|
+
*
|
|
11
|
+
* Smithery URL: https://mcp.klamdo.app/mcp
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
15
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
16
|
+
import { createServer, type IncomingMessage, type ServerResponse } from "http";
|
|
17
|
+
import { registerHandlers } from "./tools.js";
|
|
18
|
+
|
|
19
|
+
const PORT = parseInt(process.env.PORT ?? "3838", 10);
|
|
20
|
+
const HOST = process.env.HOST ?? "127.0.0.1";
|
|
21
|
+
|
|
22
|
+
function extractApiKey(req: IncomingMessage): string {
|
|
23
|
+
const auth = req.headers["authorization"] ?? "";
|
|
24
|
+
if (auth.startsWith("Bearer ")) return auth.slice(7).trim();
|
|
25
|
+
// Also allow x-api-key header for clients that don't support Bearer
|
|
26
|
+
const xApiKey = req.headers["x-api-key"];
|
|
27
|
+
if (typeof xApiKey === "string") return xApiKey.trim();
|
|
28
|
+
return "";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function readBody(req: IncomingMessage): Promise<Buffer> {
|
|
32
|
+
return new Promise((resolve, reject) => {
|
|
33
|
+
const chunks: Buffer[] = [];
|
|
34
|
+
req.on("data", (chunk: Buffer) => chunks.push(chunk));
|
|
35
|
+
req.on("end", () => resolve(Buffer.concat(chunks)));
|
|
36
|
+
req.on("error", reject);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function createMcpServer(apiKey: string): Server {
|
|
41
|
+
const server = new Server(
|
|
42
|
+
{ name: "klamdo", version: "1.1.0" },
|
|
43
|
+
{ capabilities: { tools: {}, resources: {} } }
|
|
44
|
+
);
|
|
45
|
+
registerHandlers(server, () => apiKey);
|
|
46
|
+
return server;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const httpServer = createServer(async (req: IncomingMessage, res: ServerResponse) => {
|
|
50
|
+
const url = new URL(req.url ?? "/", `http://${req.headers.host}`);
|
|
51
|
+
|
|
52
|
+
// Health check
|
|
53
|
+
if (req.method === "GET" && url.pathname === "/health") {
|
|
54
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
55
|
+
res.end(JSON.stringify({ ok: true, service: "klamdo-mcp", version: "1.1.0" }));
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// CORS preflight
|
|
60
|
+
if (req.method === "OPTIONS") {
|
|
61
|
+
res.writeHead(204, {
|
|
62
|
+
"Access-Control-Allow-Origin": "*",
|
|
63
|
+
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
|
|
64
|
+
"Access-Control-Allow-Headers": "Authorization, Content-Type, x-api-key, mcp-session-id"
|
|
65
|
+
});
|
|
66
|
+
res.end();
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// MCP endpoint
|
|
71
|
+
if (url.pathname === "/mcp") {
|
|
72
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
73
|
+
res.setHeader("Access-Control-Allow-Headers", "Authorization, Content-Type, x-api-key, mcp-session-id");
|
|
74
|
+
|
|
75
|
+
const apiKey = extractApiKey(req);
|
|
76
|
+
|
|
77
|
+
// Require auth — return 401 so Smithery knows OAuth is expected
|
|
78
|
+
if (!apiKey) {
|
|
79
|
+
res.writeHead(401, {
|
|
80
|
+
"Content-Type": "application/json",
|
|
81
|
+
"WWW-Authenticate": 'Bearer realm="Klamdo MCP", error="missing_token"'
|
|
82
|
+
});
|
|
83
|
+
res.end(JSON.stringify({
|
|
84
|
+
error: "missing_api_key",
|
|
85
|
+
message: "Get your API key at https://klamdo.app/profile"
|
|
86
|
+
}));
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
const body = req.method === "POST" ? await readBody(req) : undefined;
|
|
92
|
+
|
|
93
|
+
// Stateless: new server+transport per request
|
|
94
|
+
const mcpServer = createMcpServer(apiKey);
|
|
95
|
+
const transport = new StreamableHTTPServerTransport({
|
|
96
|
+
sessionIdGenerator: undefined // stateless mode
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
await mcpServer.connect(transport);
|
|
100
|
+
|
|
101
|
+
await transport.handleRequest(req, res, body ? JSON.parse(body.toString()) : undefined);
|
|
102
|
+
} catch (err) {
|
|
103
|
+
if (!res.headersSent) {
|
|
104
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
105
|
+
res.end(JSON.stringify({ error: "internal_error", message: String(err) }));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Root — basic info page
|
|
112
|
+
if (url.pathname === "/" || url.pathname === "") {
|
|
113
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
114
|
+
res.end(JSON.stringify({
|
|
115
|
+
service: "Klamdo MCP Server",
|
|
116
|
+
version: "1.1.0",
|
|
117
|
+
mcpEndpoint: "/mcp",
|
|
118
|
+
docs: "https://klamdo.app/answers",
|
|
119
|
+
getApiKey: "https://klamdo.app/profile"
|
|
120
|
+
}));
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
125
|
+
res.end(JSON.stringify({ error: "not_found" }));
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
httpServer.listen(PORT, HOST, () => {
|
|
129
|
+
process.stdout.write(`[klamdo-mcp:http] Listening on http://${HOST}:${PORT}/mcp\n`);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
httpServer.on("error", (err) => {
|
|
133
|
+
process.stderr.write(`[klamdo-mcp:http] Server error: ${err}\n`);
|
|
134
|
+
process.exit(1);
|
|
135
|
+
});
|