@kaddo/mcp 3.82.0 → 3.83.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/index.js +143 -10
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -11657,7 +11657,7 @@ function systemImpactCandidatesTool(root, args) {
|
|
|
11657
11657
|
|
|
11658
11658
|
// ../cli/src/services/integrations.ts
|
|
11659
11659
|
import matter13 from "gray-matter";
|
|
11660
|
-
import { parse as parseYaml12 } from "yaml";
|
|
11660
|
+
import { parse as parseYaml12, stringify as stringifyYaml6 } from "yaml";
|
|
11661
11661
|
|
|
11662
11662
|
// ../cli/src/core/work-item-write.ts
|
|
11663
11663
|
import fs5 from "fs";
|
|
@@ -11751,6 +11751,26 @@ function parseCredentials(raw, id, findings) {
|
|
|
11751
11751
|
}
|
|
11752
11752
|
return creds;
|
|
11753
11753
|
}
|
|
11754
|
+
function parseSecrets(raw, id, findings) {
|
|
11755
|
+
const secrets = {};
|
|
11756
|
+
if (raw == null) return secrets;
|
|
11757
|
+
if (typeof raw !== "object" || Array.isArray(raw)) {
|
|
11758
|
+
findings.push({ level: "blocking", id, message: `Integration "${id}": secrets must be a mapping of logical references.` });
|
|
11759
|
+
return secrets;
|
|
11760
|
+
}
|
|
11761
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
11762
|
+
if (typeof value === "string" && value.trim()) {
|
|
11763
|
+
if (value.length > 100 || /^(ghp_|sk-|xox[bpsa]-|glpat-|ey[A-Za-z0-9])/i.test(value)) {
|
|
11764
|
+
findings.push({ level: "blocking", id, message: `Integration "${id}": secret "${key}" appears to contain an actual credential, not a reference name.` });
|
|
11765
|
+
continue;
|
|
11766
|
+
}
|
|
11767
|
+
secrets[key] = value.trim();
|
|
11768
|
+
} else {
|
|
11769
|
+
findings.push({ level: "warning", id, message: `Integration "${id}": ignoring non-string secret entry "${key}".` });
|
|
11770
|
+
}
|
|
11771
|
+
}
|
|
11772
|
+
return secrets;
|
|
11773
|
+
}
|
|
11754
11774
|
function parseIntegrationsConfig(raw, opts) {
|
|
11755
11775
|
const findings = [];
|
|
11756
11776
|
const integrations = [];
|
|
@@ -11783,12 +11803,13 @@ function parseIntegrationsConfig(raw, opts) {
|
|
|
11783
11803
|
const enabled = o2.enabled === void 0 ? true : o2.enabled === true || o2.enabled === "true";
|
|
11784
11804
|
const config = o2.config && typeof o2.config === "object" && !Array.isArray(o2.config) ? o2.config : {};
|
|
11785
11805
|
const credentials = parseCredentials(o2.credentials, id, findings);
|
|
11806
|
+
const secrets = parseSecrets(o2.secrets, id, findings);
|
|
11786
11807
|
const timeoutMs = typeof o2.timeout_ms === "number" ? o2.timeout_ms : typeof o2.timeoutMs === "number" ? o2.timeoutMs : void 0;
|
|
11787
|
-
integrations.push({ id, adapter, enabled, config, credentials, timeoutMs });
|
|
11808
|
+
integrations.push({ id, adapter, enabled, config, credentials, secrets, timeoutMs });
|
|
11788
11809
|
}
|
|
11789
11810
|
return { integrations, findings };
|
|
11790
11811
|
}
|
|
11791
|
-
function
|
|
11812
|
+
async function resolveAllCredentials(integration, resolver, env) {
|
|
11792
11813
|
const credentials = {};
|
|
11793
11814
|
const missing = [];
|
|
11794
11815
|
for (const [name, ref] of Object.entries(integration.credentials)) {
|
|
@@ -11796,9 +11817,91 @@ function resolveCredentials(integration, env) {
|
|
|
11796
11817
|
if (value && value.length > 0) credentials[name] = value;
|
|
11797
11818
|
else missing.push(ref.env);
|
|
11798
11819
|
}
|
|
11820
|
+
for (const [name, ref] of Object.entries(integration.secrets)) {
|
|
11821
|
+
if (credentials[name]) continue;
|
|
11822
|
+
const value = await resolver.resolve(ref);
|
|
11823
|
+
if (value !== void 0) credentials[name] = value;
|
|
11824
|
+
else missing.push(ref);
|
|
11825
|
+
}
|
|
11799
11826
|
return { credentials, missing };
|
|
11800
11827
|
}
|
|
11801
11828
|
|
|
11829
|
+
// ../integrations/src/secrets.ts
|
|
11830
|
+
import { readFileSync as readFileSync2, writeFileSync, mkdirSync, existsSync as existsSync2, unlinkSync } from "fs";
|
|
11831
|
+
import { dirname as dirname2 } from "path";
|
|
11832
|
+
var SECRETS_FILENAME = ".secrets.json";
|
|
11833
|
+
function secretsPath(projectDir) {
|
|
11834
|
+
return `${projectDir}/.kaddo/${SECRETS_FILENAME}`;
|
|
11835
|
+
}
|
|
11836
|
+
function loadStore(filePath) {
|
|
11837
|
+
if (!existsSync2(filePath)) return {};
|
|
11838
|
+
try {
|
|
11839
|
+
const raw = readFileSync2(filePath, "utf-8");
|
|
11840
|
+
const parsed = JSON.parse(raw);
|
|
11841
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return parsed;
|
|
11842
|
+
return {};
|
|
11843
|
+
} catch {
|
|
11844
|
+
return {};
|
|
11845
|
+
}
|
|
11846
|
+
}
|
|
11847
|
+
function saveStore(filePath, store) {
|
|
11848
|
+
mkdirSync(dirname2(filePath), { recursive: true });
|
|
11849
|
+
writeFileSync(filePath, JSON.stringify(store, null, 2) + "\n", "utf-8");
|
|
11850
|
+
}
|
|
11851
|
+
function createLocalSecretProvider(projectDir) {
|
|
11852
|
+
const fp = secretsPath(projectDir);
|
|
11853
|
+
return {
|
|
11854
|
+
async get(key) {
|
|
11855
|
+
const store = loadStore(fp);
|
|
11856
|
+
const val = store[key];
|
|
11857
|
+
return val !== void 0 && val !== "" ? val : void 0;
|
|
11858
|
+
},
|
|
11859
|
+
async set(key, value) {
|
|
11860
|
+
const store = loadStore(fp);
|
|
11861
|
+
store[key] = value;
|
|
11862
|
+
saveStore(fp, store);
|
|
11863
|
+
},
|
|
11864
|
+
async delete(key) {
|
|
11865
|
+
const store = loadStore(fp);
|
|
11866
|
+
delete store[key];
|
|
11867
|
+
saveStore(fp, store);
|
|
11868
|
+
},
|
|
11869
|
+
async exists(key) {
|
|
11870
|
+
const store = loadStore(fp);
|
|
11871
|
+
return key in store && store[key] !== void 0 && store[key] !== "";
|
|
11872
|
+
}
|
|
11873
|
+
};
|
|
11874
|
+
}
|
|
11875
|
+
function createEnvSecretProvider(env = process.env) {
|
|
11876
|
+
return {
|
|
11877
|
+
async get(key) {
|
|
11878
|
+
const val = env[key];
|
|
11879
|
+
return val !== void 0 && val !== "" ? val : void 0;
|
|
11880
|
+
},
|
|
11881
|
+
async set() {
|
|
11882
|
+
throw new Error("Environment secret provider is read-only.");
|
|
11883
|
+
},
|
|
11884
|
+
async delete() {
|
|
11885
|
+
throw new Error("Environment secret provider is read-only.");
|
|
11886
|
+
},
|
|
11887
|
+
async exists(key) {
|
|
11888
|
+
const val = env[key];
|
|
11889
|
+
return val !== void 0 && val !== "";
|
|
11890
|
+
}
|
|
11891
|
+
};
|
|
11892
|
+
}
|
|
11893
|
+
function createCompositeResolver(...providers) {
|
|
11894
|
+
return {
|
|
11895
|
+
async resolve(reference) {
|
|
11896
|
+
for (const provider of providers) {
|
|
11897
|
+
const val = await provider.get(reference);
|
|
11898
|
+
if (val !== void 0) return val;
|
|
11899
|
+
}
|
|
11900
|
+
return void 0;
|
|
11901
|
+
}
|
|
11902
|
+
};
|
|
11903
|
+
}
|
|
11904
|
+
|
|
11802
11905
|
// ../integrations/src/registry.ts
|
|
11803
11906
|
var DuplicateAdapterError = class extends Error {
|
|
11804
11907
|
constructor(id) {
|
|
@@ -11883,7 +11986,31 @@ function createMockAdapter(opts = {}) {
|
|
|
11883
11986
|
id: MOCK_ADAPTER_ID,
|
|
11884
11987
|
displayName: "Mock Work Source",
|
|
11885
11988
|
version: "1.0.0",
|
|
11886
|
-
description: "Deterministic offline reference adapter for validating the integration foundation."
|
|
11989
|
+
description: "Deterministic offline reference adapter for validating the integration foundation.",
|
|
11990
|
+
configSchema: {
|
|
11991
|
+
simulate: {
|
|
11992
|
+
type: "select",
|
|
11993
|
+
required: false,
|
|
11994
|
+
label: "Simulation mode",
|
|
11995
|
+
description: "Controls what the mock adapter simulates during verify/read operations.",
|
|
11996
|
+
options: [
|
|
11997
|
+
{ value: "available", label: "Available" },
|
|
11998
|
+
{ value: "unauthorized", label: "Unauthorized" },
|
|
11999
|
+
{ value: "rate-limited", label: "Rate Limited" },
|
|
12000
|
+
{ value: "unavailable", label: "Unavailable" },
|
|
12001
|
+
{ value: "timeout", label: "Timeout" }
|
|
12002
|
+
],
|
|
12003
|
+
defaultValue: "available"
|
|
12004
|
+
}
|
|
12005
|
+
},
|
|
12006
|
+
secretSchema: {
|
|
12007
|
+
token: {
|
|
12008
|
+
type: "string",
|
|
12009
|
+
required: false,
|
|
12010
|
+
label: "API Token",
|
|
12011
|
+
description: "Optional token for testing secret handling (not used by the mock adapter)."
|
|
12012
|
+
}
|
|
12013
|
+
}
|
|
11887
12014
|
},
|
|
11888
12015
|
capabilities: {
|
|
11889
12016
|
workItems: { list: true, read: true, import: true, write: false, statusSync: false, comments: false, webhooks: false }
|
|
@@ -11960,6 +12087,9 @@ function configStatus(integration, findings) {
|
|
|
11960
12087
|
if (blocking || !registry.has(integration.adapter)) return "invalid-config";
|
|
11961
12088
|
return "configured";
|
|
11962
12089
|
}
|
|
12090
|
+
function secretResolver(dir, env) {
|
|
12091
|
+
return createCompositeResolver(createLocalSecretProvider(dir), createEnvSecretProvider(env));
|
|
12092
|
+
}
|
|
11963
12093
|
function listIntegrations(dir) {
|
|
11964
12094
|
const { integrations, findings } = loadIntegrations(dir);
|
|
11965
12095
|
return integrations.map((integration) => {
|
|
@@ -11973,6 +12103,8 @@ function listIntegrations(dir) {
|
|
|
11973
12103
|
capabilities: adapter?.capabilities ?? null,
|
|
11974
12104
|
metadata: adapter?.metadata ?? null,
|
|
11975
12105
|
credentialRefs: Object.values(integration.credentials).map((c) => c.env),
|
|
12106
|
+
secretRefs: Object.values(integration.secrets),
|
|
12107
|
+
secretStatus: {},
|
|
11976
12108
|
findings: findings.filter((f3) => f3.id === integration.id)
|
|
11977
12109
|
};
|
|
11978
12110
|
});
|
|
@@ -11981,15 +12113,16 @@ function requireIntegration(dir, id) {
|
|
|
11981
12113
|
const { integrations, findings } = loadIntegrations(dir);
|
|
11982
12114
|
const integration = integrations.find((i) => i.id === id);
|
|
11983
12115
|
if (!integration) throw new IntegrationServiceError("INTEGRATION_NOT_CONFIGURED", `No integration "${id}" is configured in this project.`);
|
|
11984
|
-
return { integration, findings };
|
|
12116
|
+
return { integration, findings, all: integrations };
|
|
11985
12117
|
}
|
|
11986
12118
|
function resolveAdapter(integration) {
|
|
11987
12119
|
const adapter = registry.get(integration.adapter);
|
|
11988
12120
|
if (!adapter) throw new IntegrationServiceError("ADAPTER_NOT_FOUND", `Unknown integration adapter "${integration.adapter}".`);
|
|
11989
12121
|
return adapter;
|
|
11990
12122
|
}
|
|
11991
|
-
function
|
|
11992
|
-
const
|
|
12123
|
+
async function buildContextWithSecrets(dir, integration, env) {
|
|
12124
|
+
const resolver = secretResolver(dir, env);
|
|
12125
|
+
const { credentials, missing } = await resolveAllCredentials(integration, resolver, env);
|
|
11993
12126
|
return {
|
|
11994
12127
|
context: { integrationId: integration.id, config: integration.config, credentials, timeoutMs: integration.timeoutMs ?? DEFAULT_TIMEOUT_MS },
|
|
11995
12128
|
missing
|
|
@@ -12012,7 +12145,7 @@ async function verifyIntegration(dir, id, env = process.env) {
|
|
|
12012
12145
|
if (cfgStatus === "disabled") return { id, status: "disabled", connection: null, missingCredentials: [] };
|
|
12013
12146
|
if (cfgStatus === "invalid-config") return { id, status: "invalid-config", connection: null, missingCredentials: [] };
|
|
12014
12147
|
const adapter = resolveAdapter(integration);
|
|
12015
|
-
const { context, missing } =
|
|
12148
|
+
const { context, missing } = await buildContextWithSecrets(dir, integration, env);
|
|
12016
12149
|
try {
|
|
12017
12150
|
const connection = await withTimeout(adapter.verifyConnection(context), context.timeoutMs);
|
|
12018
12151
|
return { id, status: statusOf(connection), connection, missingCredentials: missing, message: connection.message };
|
|
@@ -12037,14 +12170,14 @@ async function listExternalWorkItems(dir, id, opts = {}, env = process.env) {
|
|
|
12037
12170
|
const { integration } = requireIntegration(dir, id);
|
|
12038
12171
|
const adapter = resolveAdapter(integration);
|
|
12039
12172
|
if (!adapter.capabilities.workItems.list) throw integrationError("UNSUPPORTED_CAPABILITY", `Adapter "${adapter.id}" cannot list work items.`);
|
|
12040
|
-
const { context } =
|
|
12173
|
+
const { context } = await buildContextWithSecrets(dir, integration, env);
|
|
12041
12174
|
return withTimeout(adapter.listWorkItems({ context, cursor: opts.cursor, pageSize: opts.pageSize, filters: opts.filters }), context.timeoutMs);
|
|
12042
12175
|
}
|
|
12043
12176
|
async function getExternalWorkItem(dir, id, externalId, env = process.env) {
|
|
12044
12177
|
const { integration } = requireIntegration(dir, id);
|
|
12045
12178
|
const adapter = resolveAdapter(integration);
|
|
12046
12179
|
if (!adapter.capabilities.workItems.read) throw integrationError("UNSUPPORTED_CAPABILITY", `Adapter "${adapter.id}" cannot read work items.`);
|
|
12047
|
-
const { context } =
|
|
12180
|
+
const { context } = await buildContextWithSecrets(dir, integration, env);
|
|
12048
12181
|
return withTimeout(adapter.getWorkItem({ context, externalId }), context.timeoutMs);
|
|
12049
12182
|
}
|
|
12050
12183
|
|
package/package.json
CHANGED