@kadoa/mcp 0.2.2 → 0.2.4
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 +41 -2
- package/dist/index.js +155 -14
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,8 +1,47 @@
|
|
|
1
1
|
# Kadoa MCP Server
|
|
2
2
|
|
|
3
|
-
Use [Kadoa](https://kadoa.com) from Claude
|
|
3
|
+
Use [Kadoa](https://kadoa.com) from ChatGPT, Claude.ai, Claude Code, Cursor, and other MCP clients.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Remote Server (no install needed)
|
|
6
|
+
|
|
7
|
+
A hosted MCP server is available at `https://mcp.kadoa.com/mcp`. Connect from any MCP client — no local install, no API key config. You sign in with your Kadoa account via OAuth.
|
|
8
|
+
|
|
9
|
+
### ChatGPT
|
|
10
|
+
|
|
11
|
+
1. Go to **Settings → Connectors → Add MCP server**
|
|
12
|
+
2. Enter the URL: `https://mcp.kadoa.com/mcp`
|
|
13
|
+
3. Choose OAuth authentication and sign in with your Kadoa account (Google)
|
|
14
|
+
|
|
15
|
+
### Claude.ai
|
|
16
|
+
|
|
17
|
+
1. Go to **Settings → Connectors → Add custom MCP**
|
|
18
|
+
2. Enter the URL: `https://mcp.kadoa.com/mcp`
|
|
19
|
+
3. Sign in with your Kadoa account via OAuth
|
|
20
|
+
|
|
21
|
+
### Cursor (Remote)
|
|
22
|
+
|
|
23
|
+
Add to `.cursor/mcp.json`:
|
|
24
|
+
|
|
25
|
+
```json
|
|
26
|
+
{
|
|
27
|
+
"mcpServers": {
|
|
28
|
+
"kadoa": {
|
|
29
|
+
"type": "http",
|
|
30
|
+
"url": "https://mcp.kadoa.com/mcp"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Any MCP Client
|
|
37
|
+
|
|
38
|
+
Point your client to `https://mcp.kadoa.com/mcp` with OAuth authentication.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Local Setup (stdio)
|
|
43
|
+
|
|
44
|
+
If you prefer to run the MCP server locally (e.g., for development or to use your own API key), install via npx:
|
|
6
45
|
|
|
7
46
|
### Claude Code
|
|
8
47
|
|
package/dist/index.js
CHANGED
|
@@ -48922,21 +48922,26 @@ var init_dist2 = __esm(() => {
|
|
|
48922
48922
|
});
|
|
48923
48923
|
|
|
48924
48924
|
// src/client.ts
|
|
48925
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
48926
|
-
import { join } from "node:path";
|
|
48925
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
48926
|
+
import { dirname, join } from "node:path";
|
|
48927
48927
|
import { homedir } from "node:os";
|
|
48928
|
-
function
|
|
48928
|
+
function getConfigPath() {
|
|
48929
48929
|
const home = process.env.HOME || homedir();
|
|
48930
|
-
|
|
48930
|
+
return join(home, ".kadoa", "config.json");
|
|
48931
|
+
}
|
|
48932
|
+
function loadConfig() {
|
|
48933
|
+
const configFile = getConfigPath();
|
|
48931
48934
|
if (!existsSync(configFile))
|
|
48932
|
-
return;
|
|
48935
|
+
return {};
|
|
48933
48936
|
try {
|
|
48934
|
-
|
|
48935
|
-
return config2.apiKey;
|
|
48937
|
+
return JSON.parse(readFileSync(configFile, "utf-8"));
|
|
48936
48938
|
} catch {
|
|
48937
|
-
return;
|
|
48939
|
+
return {};
|
|
48938
48940
|
}
|
|
48939
48941
|
}
|
|
48942
|
+
function loadApiKeyFromConfig() {
|
|
48943
|
+
return loadConfig().apiKey;
|
|
48944
|
+
}
|
|
48940
48945
|
function resolveApiKey(apiKey) {
|
|
48941
48946
|
const key = apiKey || process.env.KADOA_API_KEY || loadApiKeyFromConfig();
|
|
48942
48947
|
if (!key) {
|
|
@@ -48951,6 +48956,33 @@ var init_client = __esm(() => {
|
|
|
48951
48956
|
init_dist2();
|
|
48952
48957
|
});
|
|
48953
48958
|
|
|
48959
|
+
// src/client.ts
|
|
48960
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
48961
|
+
import { dirname as dirname2, join as join2 } from "node:path";
|
|
48962
|
+
import { homedir as homedir2 } from "node:os";
|
|
48963
|
+
function getConfigPath2() {
|
|
48964
|
+
const home = process.env.HOME || homedir2();
|
|
48965
|
+
return join2(home, ".kadoa", "config.json");
|
|
48966
|
+
}
|
|
48967
|
+
function loadConfig2() {
|
|
48968
|
+
const configFile = getConfigPath2();
|
|
48969
|
+
if (!existsSync2(configFile))
|
|
48970
|
+
return {};
|
|
48971
|
+
try {
|
|
48972
|
+
return JSON.parse(readFileSync2(configFile, "utf-8"));
|
|
48973
|
+
} catch {
|
|
48974
|
+
return {};
|
|
48975
|
+
}
|
|
48976
|
+
}
|
|
48977
|
+
function saveConfig(config2) {
|
|
48978
|
+
const configFile = getConfigPath2();
|
|
48979
|
+
mkdirSync2(dirname2(configFile), { recursive: true });
|
|
48980
|
+
writeFileSync2(configFile, JSON.stringify(config2, null, 2), "utf-8");
|
|
48981
|
+
}
|
|
48982
|
+
var init_client2 = __esm(() => {
|
|
48983
|
+
init_dist2();
|
|
48984
|
+
});
|
|
48985
|
+
|
|
48954
48986
|
// src/tools.ts
|
|
48955
48987
|
function jsonResult(data) {
|
|
48956
48988
|
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
@@ -48961,13 +48993,59 @@ function errorResult(message) {
|
|
|
48961
48993
|
isError: true
|
|
48962
48994
|
};
|
|
48963
48995
|
}
|
|
48996
|
+
function classifyError(error48) {
|
|
48997
|
+
if (KadoaSdkException.isInstance(error48)) {
|
|
48998
|
+
const code = error48.code;
|
|
48999
|
+
if (KadoaHttpException.isInstance(error48)) {
|
|
49000
|
+
const status = error48.httpStatus ? ` (HTTP ${error48.httpStatus})` : "";
|
|
49001
|
+
switch (code) {
|
|
49002
|
+
case "AUTH_ERROR":
|
|
49003
|
+
return `Authentication failed${status}. Your Kadoa API key may be invalid or expired. Please check your KADOA_API_KEY or re-authenticate.`;
|
|
49004
|
+
case "NOT_FOUND":
|
|
49005
|
+
return `Not found${status}. The workflow may have been deleted or the ID is incorrect.`;
|
|
49006
|
+
case "RATE_LIMITED":
|
|
49007
|
+
return `Rate limit exceeded${status}. Please wait a moment and try again.`;
|
|
49008
|
+
case "NETWORK_ERROR":
|
|
49009
|
+
return `Unable to reach the Kadoa API${status}. Check your network connection.`;
|
|
49010
|
+
case "TIMEOUT":
|
|
49011
|
+
return `Request timed out${status}. Please try again.`;
|
|
49012
|
+
case "VALIDATION_ERROR":
|
|
49013
|
+
case "BAD_REQUEST":
|
|
49014
|
+
return error48.message;
|
|
49015
|
+
default:
|
|
49016
|
+
return `Kadoa API error${status}. Please try again later.`;
|
|
49017
|
+
}
|
|
49018
|
+
}
|
|
49019
|
+
switch (code) {
|
|
49020
|
+
case "AUTH_ERROR":
|
|
49021
|
+
return "Authentication failed. Your Kadoa API key may be invalid or expired. Please check your KADOA_API_KEY or re-authenticate.";
|
|
49022
|
+
case "NOT_FOUND":
|
|
49023
|
+
return "Not found. The workflow may have been deleted or the ID is incorrect.";
|
|
49024
|
+
case "RATE_LIMITED":
|
|
49025
|
+
return "Rate limit exceeded. Please wait a moment and try again.";
|
|
49026
|
+
case "NETWORK_ERROR":
|
|
49027
|
+
return "Unable to reach the Kadoa API. Check your network connection.";
|
|
49028
|
+
case "TIMEOUT":
|
|
49029
|
+
return "Request timed out. Please try again.";
|
|
49030
|
+
case "VALIDATION_ERROR":
|
|
49031
|
+
case "BAD_REQUEST":
|
|
49032
|
+
return error48.message;
|
|
49033
|
+
default:
|
|
49034
|
+
return error48.message;
|
|
49035
|
+
}
|
|
49036
|
+
}
|
|
49037
|
+
if (error48 instanceof Error) {
|
|
49038
|
+
return error48.message;
|
|
49039
|
+
}
|
|
49040
|
+
return "Unknown error";
|
|
49041
|
+
}
|
|
48964
49042
|
function withErrorHandling(name, handler) {
|
|
48965
49043
|
return async (...args) => {
|
|
48966
49044
|
try {
|
|
48967
49045
|
return await handler(...args);
|
|
48968
49046
|
} catch (error48) {
|
|
48969
|
-
const message = error48
|
|
48970
|
-
console.error(`[Tool Error] ${name}:`,
|
|
49047
|
+
const message = classifyError(error48);
|
|
49048
|
+
console.error(`[Tool Error] ${name}:`, error48);
|
|
48971
49049
|
return errorResult(message);
|
|
48972
49050
|
}
|
|
48973
49051
|
};
|
|
@@ -49143,10 +49221,61 @@ function registerTools(server, client) {
|
|
|
49143
49221
|
workflow: result
|
|
49144
49222
|
});
|
|
49145
49223
|
}));
|
|
49224
|
+
server.registerTool("team_list", {
|
|
49225
|
+
description: "List all teams the current user belongs to, showing which team is currently active",
|
|
49226
|
+
inputSchema: {},
|
|
49227
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true }
|
|
49228
|
+
}, withErrorHandling("team_list", async () => {
|
|
49229
|
+
const response = await client.axiosInstance.get("/v5/user", {
|
|
49230
|
+
baseURL: client.baseUrl,
|
|
49231
|
+
headers: { "x-api-key": client.apiKey }
|
|
49232
|
+
});
|
|
49233
|
+
const teams = response.data?.teams ?? [];
|
|
49234
|
+
const config2 = loadConfig2();
|
|
49235
|
+
const activeTeamId = config2.teamId ?? teams[0]?.id;
|
|
49236
|
+
return jsonResult({
|
|
49237
|
+
teams: teams.map((t) => ({
|
|
49238
|
+
id: t.id,
|
|
49239
|
+
name: t.name,
|
|
49240
|
+
role: t.role,
|
|
49241
|
+
active: t.id === activeTeamId
|
|
49242
|
+
})),
|
|
49243
|
+
activeTeamId
|
|
49244
|
+
});
|
|
49245
|
+
}));
|
|
49246
|
+
server.registerTool("team_switch", {
|
|
49247
|
+
description: "Switch the active team by name or ID. After switching, all subsequent API calls will use the new team context.",
|
|
49248
|
+
inputSchema: {
|
|
49249
|
+
teamIdentifier: exports_external.string().describe("Team name or team ID to switch to")
|
|
49250
|
+
},
|
|
49251
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true }
|
|
49252
|
+
}, withErrorHandling("team_switch", async (args) => {
|
|
49253
|
+
const response = await client.axiosInstance.get("/v5/user", {
|
|
49254
|
+
baseURL: client.baseUrl,
|
|
49255
|
+
headers: { "x-api-key": client.apiKey }
|
|
49256
|
+
});
|
|
49257
|
+
const teams = response.data?.teams ?? [];
|
|
49258
|
+
const identifier = args.teamIdentifier;
|
|
49259
|
+
const match = teams.find((t) => t.id === identifier || t.name.toLowerCase() === identifier.toLowerCase());
|
|
49260
|
+
if (!match) {
|
|
49261
|
+
return errorResult(`Team not found: "${identifier}". Available teams: ${teams.map((t) => t.name).join(", ")}`);
|
|
49262
|
+
}
|
|
49263
|
+
const config2 = loadConfig2();
|
|
49264
|
+
config2.teamId = match.id;
|
|
49265
|
+
config2.teamName = match.name;
|
|
49266
|
+
saveConfig(config2);
|
|
49267
|
+
return jsonResult({
|
|
49268
|
+
success: true,
|
|
49269
|
+
teamId: match.id,
|
|
49270
|
+
teamName: match.name
|
|
49271
|
+
});
|
|
49272
|
+
}));
|
|
49146
49273
|
}
|
|
49147
49274
|
var SchemaFieldShape;
|
|
49148
49275
|
var init_tools = __esm(() => {
|
|
49149
49276
|
init_zod();
|
|
49277
|
+
init_dist2();
|
|
49278
|
+
init_client2();
|
|
49150
49279
|
SchemaFieldShape = {
|
|
49151
49280
|
name: exports_external.string().describe("Field name"),
|
|
49152
49281
|
description: exports_external.string().optional().describe("What this field contains"),
|
|
@@ -53217,13 +53346,12 @@ class KadoaOAuthProvider {
|
|
|
53217
53346
|
params,
|
|
53218
53347
|
supabaseCodeVerifier: verifier
|
|
53219
53348
|
});
|
|
53220
|
-
const redirectTo = `${serverUrl}/auth/callback`;
|
|
53349
|
+
const redirectTo = `${serverUrl}/auth/callback?mcp_state=${state}`;
|
|
53221
53350
|
const authUrl = new URL(`${supabaseUrl}/auth/v1/authorize`);
|
|
53222
53351
|
authUrl.searchParams.set("provider", "google");
|
|
53223
53352
|
authUrl.searchParams.set("redirect_to", redirectTo);
|
|
53224
53353
|
authUrl.searchParams.set("code_challenge", challenge);
|
|
53225
53354
|
authUrl.searchParams.set("code_challenge_method", "S256");
|
|
53226
|
-
authUrl.searchParams.set("state", state);
|
|
53227
53355
|
res.redirect(authUrl.toString());
|
|
53228
53356
|
}
|
|
53229
53357
|
async challengeForAuthorizationCode(_client, authorizationCode) {
|
|
@@ -53313,9 +53441,9 @@ class KadoaOAuthProvider {
|
|
|
53313
53441
|
};
|
|
53314
53442
|
}
|
|
53315
53443
|
async handleAuthCallback(req, res) {
|
|
53316
|
-
const { code, state } = req.query;
|
|
53444
|
+
const { code, mcp_state: state } = req.query;
|
|
53317
53445
|
if (!code || !state) {
|
|
53318
|
-
res.status(400).send("Missing code or
|
|
53446
|
+
res.status(400).send("Missing code or mcp_state parameter");
|
|
53319
53447
|
return;
|
|
53320
53448
|
}
|
|
53321
53449
|
const pending = pendingAuths.get(state);
|
|
@@ -53389,6 +53517,7 @@ function resolveApiKey3(req) {
|
|
|
53389
53517
|
async function startHttpServer() {
|
|
53390
53518
|
const port = parseInt(process.env.PORT || "3000", 10);
|
|
53391
53519
|
const app = createMcpExpressApp({ host: "0.0.0.0" });
|
|
53520
|
+
app.set("trust proxy", 1);
|
|
53392
53521
|
const sessions = {};
|
|
53393
53522
|
const provider = new KadoaOAuthProvider;
|
|
53394
53523
|
const serverUrl = process.env.MCP_SERVER_URL || `http://localhost:${port}`;
|
|
@@ -53530,6 +53659,17 @@ function createServer(apiKey) {
|
|
|
53530
53659
|
server.server.onerror = (error48) => console.error("[MCP Error]", error48);
|
|
53531
53660
|
return server;
|
|
53532
53661
|
}
|
|
53662
|
+
async function validateApiKey() {
|
|
53663
|
+
const client = createKadoaClient();
|
|
53664
|
+
try {
|
|
53665
|
+
await client.workflow.list({ limit: 1 });
|
|
53666
|
+
} catch (error48) {
|
|
53667
|
+
if (KadoaSdkException.isInstance(error48) && error48.code === "AUTH_ERROR") {
|
|
53668
|
+
console.error("Kadoa MCP: Invalid API key. Check KADOA_API_KEY or run 'kadoa login'.");
|
|
53669
|
+
process.exit(1);
|
|
53670
|
+
}
|
|
53671
|
+
}
|
|
53672
|
+
}
|
|
53533
53673
|
var init_src = __esm(async () => {
|
|
53534
53674
|
init_mcp();
|
|
53535
53675
|
init_stdio2();
|
|
@@ -53541,6 +53681,7 @@ var init_src = __esm(async () => {
|
|
|
53541
53681
|
const { startHttpServer: startHttpServer2 } = await init_http2().then(() => exports_http);
|
|
53542
53682
|
await startHttpServer2();
|
|
53543
53683
|
} else {
|
|
53684
|
+
await validateApiKey();
|
|
53544
53685
|
const server = createServer();
|
|
53545
53686
|
const transport = new StdioServerTransport;
|
|
53546
53687
|
await server.connect(transport);
|