@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.
Files changed (2) hide show
  1. package/dist/index.js +143 -10
  2. 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 resolveCredentials(integration, env) {
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 buildContext(integration, env) {
11992
- const { credentials, missing } = resolveCredentials(integration, env);
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 } = buildContext(integration, env);
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 } = buildContext(integration, env);
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 } = buildContext(integration, env);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaddo/mcp",
3
- "version": "3.82.0",
3
+ "version": "3.83.0",
4
4
  "description": "MCP server for Kaddo project knowledge: read-only resources/tools/prompts plus safe derived generation under .kaddo/.",
5
5
  "license": "MIT",
6
6
  "repository": {