@kaddo/mcp 3.82.0 → 3.84.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 +313 -14
  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,71 @@ 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
+ }
11774
+ function toStringArray3(raw) {
11775
+ if (raw == null) return void 0;
11776
+ if (Array.isArray(raw)) return raw.filter((v) => typeof v === "string" && v.trim() !== "").map((v) => v.trim());
11777
+ if (typeof raw === "string" && raw.trim()) return [raw.trim()];
11778
+ return void 0;
11779
+ }
11780
+ function parseFilters(raw) {
11781
+ if (raw == null || typeof raw !== "object" || Array.isArray(raw)) return void 0;
11782
+ const o2 = raw;
11783
+ const filters = {};
11784
+ let hasAny = false;
11785
+ const types = toStringArray3(o2.types);
11786
+ if (types?.length) {
11787
+ filters.types = types;
11788
+ hasAny = true;
11789
+ }
11790
+ const statuses = toStringArray3(o2.statuses);
11791
+ if (statuses?.length) {
11792
+ filters.statuses = statuses;
11793
+ hasAny = true;
11794
+ }
11795
+ const labels = toStringArray3(o2.labels);
11796
+ if (labels?.length) {
11797
+ filters.labels = labels;
11798
+ hasAny = true;
11799
+ }
11800
+ const assignees = toStringArray3(o2.assignees);
11801
+ if (assignees?.length) {
11802
+ filters.assignees = assignees;
11803
+ hasAny = true;
11804
+ }
11805
+ if (typeof o2.updatedAfter === "string" && o2.updatedAfter.trim()) {
11806
+ filters.updatedAfter = o2.updatedAfter.trim();
11807
+ hasAny = true;
11808
+ }
11809
+ if (typeof o2.search === "string" && o2.search.trim()) {
11810
+ filters.search = o2.search.trim();
11811
+ hasAny = true;
11812
+ }
11813
+ if (typeof o2.providerQuery === "string" && o2.providerQuery.trim()) {
11814
+ filters.providerQuery = o2.providerQuery.trim();
11815
+ hasAny = true;
11816
+ }
11817
+ return hasAny ? filters : void 0;
11818
+ }
11754
11819
  function parseIntegrationsConfig(raw, opts) {
11755
11820
  const findings = [];
11756
11821
  const integrations = [];
@@ -11783,12 +11848,14 @@ function parseIntegrationsConfig(raw, opts) {
11783
11848
  const enabled = o2.enabled === void 0 ? true : o2.enabled === true || o2.enabled === "true";
11784
11849
  const config = o2.config && typeof o2.config === "object" && !Array.isArray(o2.config) ? o2.config : {};
11785
11850
  const credentials = parseCredentials(o2.credentials, id, findings);
11851
+ const secrets = parseSecrets(o2.secrets, id, findings);
11852
+ const filters = parseFilters(o2.filters);
11786
11853
  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 });
11854
+ integrations.push({ id, adapter, enabled, config, credentials, secrets, filters, timeoutMs });
11788
11855
  }
11789
11856
  return { integrations, findings };
11790
11857
  }
11791
- function resolveCredentials(integration, env) {
11858
+ async function resolveAllCredentials(integration, resolver, env) {
11792
11859
  const credentials = {};
11793
11860
  const missing = [];
11794
11861
  for (const [name, ref] of Object.entries(integration.credentials)) {
@@ -11796,8 +11863,104 @@ function resolveCredentials(integration, env) {
11796
11863
  if (value && value.length > 0) credentials[name] = value;
11797
11864
  else missing.push(ref.env);
11798
11865
  }
11866
+ for (const [name, ref] of Object.entries(integration.secrets)) {
11867
+ if (credentials[name]) continue;
11868
+ const value = await resolver.resolve(ref);
11869
+ if (value !== void 0) credentials[name] = value;
11870
+ else missing.push(ref);
11871
+ }
11799
11872
  return { credentials, missing };
11800
11873
  }
11874
+ function mergeFilters(base, overlay) {
11875
+ if (!base && !overlay) return {};
11876
+ if (!base) return { ...overlay };
11877
+ if (!overlay) return { ...base };
11878
+ return {
11879
+ types: overlay.types?.length ? overlay.types : base.types,
11880
+ statuses: overlay.statuses?.length ? overlay.statuses : base.statuses,
11881
+ labels: overlay.labels?.length ? overlay.labels : base.labels,
11882
+ assignees: overlay.assignees?.length ? overlay.assignees : base.assignees,
11883
+ updatedAfter: overlay.updatedAfter ?? base.updatedAfter,
11884
+ search: overlay.search ?? base.search,
11885
+ providerQuery: overlay.providerQuery ?? base.providerQuery
11886
+ };
11887
+ }
11888
+
11889
+ // ../integrations/src/secrets.ts
11890
+ import { readFileSync as readFileSync2, writeFileSync, mkdirSync, existsSync as existsSync2, unlinkSync } from "fs";
11891
+ import { dirname as dirname2 } from "path";
11892
+ var SECRETS_FILENAME = ".secrets.json";
11893
+ function secretsPath(projectDir) {
11894
+ return `${projectDir}/.kaddo/${SECRETS_FILENAME}`;
11895
+ }
11896
+ function loadStore(filePath) {
11897
+ if (!existsSync2(filePath)) return {};
11898
+ try {
11899
+ const raw = readFileSync2(filePath, "utf-8");
11900
+ const parsed = JSON.parse(raw);
11901
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return parsed;
11902
+ return {};
11903
+ } catch {
11904
+ return {};
11905
+ }
11906
+ }
11907
+ function saveStore(filePath, store) {
11908
+ mkdirSync(dirname2(filePath), { recursive: true });
11909
+ writeFileSync(filePath, JSON.stringify(store, null, 2) + "\n", "utf-8");
11910
+ }
11911
+ function createLocalSecretProvider(projectDir) {
11912
+ const fp = secretsPath(projectDir);
11913
+ return {
11914
+ async get(key) {
11915
+ const store = loadStore(fp);
11916
+ const val = store[key];
11917
+ return val !== void 0 && val !== "" ? val : void 0;
11918
+ },
11919
+ async set(key, value) {
11920
+ const store = loadStore(fp);
11921
+ store[key] = value;
11922
+ saveStore(fp, store);
11923
+ },
11924
+ async delete(key) {
11925
+ const store = loadStore(fp);
11926
+ delete store[key];
11927
+ saveStore(fp, store);
11928
+ },
11929
+ async exists(key) {
11930
+ const store = loadStore(fp);
11931
+ return key in store && store[key] !== void 0 && store[key] !== "";
11932
+ }
11933
+ };
11934
+ }
11935
+ function createEnvSecretProvider(env = process.env) {
11936
+ return {
11937
+ async get(key) {
11938
+ const val = env[key];
11939
+ return val !== void 0 && val !== "" ? val : void 0;
11940
+ },
11941
+ async set() {
11942
+ throw new Error("Environment secret provider is read-only.");
11943
+ },
11944
+ async delete() {
11945
+ throw new Error("Environment secret provider is read-only.");
11946
+ },
11947
+ async exists(key) {
11948
+ const val = env[key];
11949
+ return val !== void 0 && val !== "";
11950
+ }
11951
+ };
11952
+ }
11953
+ function createCompositeResolver(...providers) {
11954
+ return {
11955
+ async resolve(reference) {
11956
+ for (const provider of providers) {
11957
+ const val = await provider.get(reference);
11958
+ if (val !== void 0) return val;
11959
+ }
11960
+ return void 0;
11961
+ }
11962
+ };
11963
+ }
11801
11964
 
11802
11965
  // ../integrations/src/registry.ts
11803
11966
  var DuplicateAdapterError = class extends Error {
@@ -11838,6 +12001,7 @@ var DEFAULT_ITEMS = [
11838
12001
  status: "Open",
11839
12002
  url: "https://example.test/mock/EXT-001",
11840
12003
  labels: ["registration", "beta"],
12004
+ assignees: [{ name: "Alice" }],
11841
12005
  createdAt: "2026-01-05T10:00:00.000Z",
11842
12006
  updatedAt: "2026-02-01T09:30:00.000Z",
11843
12007
  rawMetadata: { board: "delivery" }
@@ -11851,8 +12015,86 @@ var DEFAULT_ITEMS = [
11851
12015
  status: "To Do",
11852
12016
  url: "https://example.test/mock/EXT-002",
11853
12017
  labels: ["onboarding"],
12018
+ assignees: [{ name: "Bob" }],
11854
12019
  createdAt: "2026-01-08T12:00:00.000Z",
11855
12020
  updatedAt: "2026-01-20T15:00:00.000Z"
12021
+ },
12022
+ {
12023
+ externalId: "EXT-003",
12024
+ provider: MOCK_ADAPTER_ID,
12025
+ title: "Fix authentication error on mobile",
12026
+ description: "Users on iOS 17 see a blank screen after OAuth redirect.",
12027
+ type: "Bug",
12028
+ status: "In Progress",
12029
+ url: "https://example.test/mock/EXT-003",
12030
+ labels: ["auth", "mobile", "critical"],
12031
+ assignees: [{ name: "Alice" }],
12032
+ createdAt: "2026-02-10T08:00:00.000Z",
12033
+ updatedAt: "2026-03-01T14:00:00.000Z"
12034
+ },
12035
+ {
12036
+ externalId: "EXT-004",
12037
+ provider: MOCK_ADAPTER_ID,
12038
+ title: "Design system token audit",
12039
+ description: "Review and consolidate design tokens across the component library.",
12040
+ type: "Task",
12041
+ status: "To Do",
12042
+ url: "https://example.test/mock/EXT-004",
12043
+ labels: ["frontend", "design-system"],
12044
+ createdAt: "2026-02-15T10:00:00.000Z",
12045
+ updatedAt: "2026-02-20T11:00:00.000Z"
12046
+ },
12047
+ {
12048
+ externalId: "EXT-005",
12049
+ provider: MOCK_ADAPTER_ID,
12050
+ title: "Migrate billing service to new payment gateway",
12051
+ description: "Stripe v2 migration \u2014 switch from legacy Charges API to Payment Intents.",
12052
+ type: "Epic",
12053
+ status: "Open",
12054
+ url: "https://example.test/mock/EXT-005",
12055
+ labels: ["billing", "backend", "migration"],
12056
+ assignees: [{ name: "Carol" }],
12057
+ createdAt: "2026-01-20T09:00:00.000Z",
12058
+ updatedAt: "2026-03-05T16:00:00.000Z"
12059
+ },
12060
+ {
12061
+ externalId: "EXT-006",
12062
+ provider: MOCK_ADAPTER_ID,
12063
+ title: "Add CSV export to reports",
12064
+ description: "Users need to export analytics reports as CSV for external tools.",
12065
+ type: "Feature",
12066
+ status: "In Progress",
12067
+ url: "https://example.test/mock/EXT-006",
12068
+ labels: ["reports", "backend"],
12069
+ assignees: [{ name: "Bob" }],
12070
+ createdAt: "2026-02-25T13:00:00.000Z",
12071
+ updatedAt: "2026-03-10T10:00:00.000Z"
12072
+ },
12073
+ {
12074
+ externalId: "EXT-007",
12075
+ provider: MOCK_ADAPTER_ID,
12076
+ title: "Improve search relevance scoring",
12077
+ description: "Tweak TF-IDF weights and add trigram matching for better search results.",
12078
+ type: "Task",
12079
+ status: "Done",
12080
+ url: "https://example.test/mock/EXT-007",
12081
+ labels: ["search", "backend"],
12082
+ assignees: [{ name: "Alice" }],
12083
+ createdAt: "2026-01-12T07:00:00.000Z",
12084
+ updatedAt: "2026-02-28T17:00:00.000Z"
12085
+ },
12086
+ {
12087
+ externalId: "EXT-008",
12088
+ provider: MOCK_ADAPTER_ID,
12089
+ title: "Set up E2E test pipeline",
12090
+ description: "Configure Playwright tests running on every PR via GitHub Actions.",
12091
+ type: "Task",
12092
+ status: "Done",
12093
+ url: "https://example.test/mock/EXT-008",
12094
+ labels: ["testing", "ci"],
12095
+ assignees: [{ name: "Carol" }],
12096
+ createdAt: "2026-01-30T11:00:00.000Z",
12097
+ updatedAt: "2026-02-15T09:00:00.000Z"
11856
12098
  }
11857
12099
  ];
11858
12100
  function simulationOf(context, fallback) {
@@ -11883,7 +12125,41 @@ function createMockAdapter(opts = {}) {
11883
12125
  id: MOCK_ADAPTER_ID,
11884
12126
  displayName: "Mock Work Source",
11885
12127
  version: "1.0.0",
11886
- description: "Deterministic offline reference adapter for validating the integration foundation."
12128
+ description: "Deterministic offline reference adapter for validating the integration foundation.",
12129
+ icon: "mock",
12130
+ filterCapabilities: {
12131
+ types: { supported: true, multiple: true },
12132
+ statuses: { supported: true, multiple: true },
12133
+ labels: { supported: true, multiple: true },
12134
+ assignees: { supported: true, multiple: true },
12135
+ updatedAfter: { supported: true },
12136
+ search: { supported: true },
12137
+ providerQuery: { supported: false }
12138
+ },
12139
+ configSchema: {
12140
+ simulate: {
12141
+ type: "select",
12142
+ required: false,
12143
+ label: "Simulation mode",
12144
+ description: "Controls what the mock adapter simulates during verify/read operations.",
12145
+ options: [
12146
+ { value: "available", label: "Available" },
12147
+ { value: "unauthorized", label: "Unauthorized" },
12148
+ { value: "rate-limited", label: "Rate Limited" },
12149
+ { value: "unavailable", label: "Unavailable" },
12150
+ { value: "timeout", label: "Timeout" }
12151
+ ],
12152
+ defaultValue: "available"
12153
+ }
12154
+ },
12155
+ secretSchema: {
12156
+ token: {
12157
+ type: "string",
12158
+ required: false,
12159
+ label: "API Token",
12160
+ description: "Optional token for testing secret handling (not used by the mock adapter)."
12161
+ }
12162
+ }
11887
12163
  },
11888
12164
  capabilities: {
11889
12165
  workItems: { list: true, read: true, import: true, write: false, statusSync: false, comments: false, webhooks: false }
@@ -11906,9 +12182,24 @@ function createMockAdapter(opts = {}) {
11906
12182
  readFailure(simulationOf(request.context, defaultSim));
11907
12183
  let pool = items;
11908
12184
  const f3 = request.filters;
11909
- if (f3?.status) pool = pool.filter((i) => (i.status ?? "").toLowerCase() === f3.status.toLowerCase());
11910
- if (f3?.query) pool = pool.filter((i) => `${i.title} ${i.description ?? ""}`.toLowerCase().includes(f3.query.toLowerCase()));
11911
- if (f3?.updatedSince) pool = pool.filter((i) => (i.updatedAt ?? "") >= f3.updatedSince);
12185
+ if (f3?.statuses?.length) {
12186
+ const lower = f3.statuses.map((s) => s.toLowerCase());
12187
+ pool = pool.filter((i) => lower.includes((i.status ?? "").toLowerCase()));
12188
+ }
12189
+ if (f3?.types?.length) {
12190
+ const lower = f3.types.map((t) => t.toLowerCase());
12191
+ pool = pool.filter((i) => lower.includes((i.type ?? "").toLowerCase()));
12192
+ }
12193
+ if (f3?.labels?.length) {
12194
+ const lower = f3.labels.map((l2) => l2.toLowerCase());
12195
+ pool = pool.filter((i) => i.labels?.some((il) => lower.includes(il.toLowerCase())));
12196
+ }
12197
+ if (f3?.assignees?.length) {
12198
+ const lower = f3.assignees.map((a2) => a2.toLowerCase());
12199
+ pool = pool.filter((i) => i.assignees?.some((a2) => lower.includes((a2.name ?? "").toLowerCase())));
12200
+ }
12201
+ if (f3?.search) pool = pool.filter((i) => `${i.title} ${i.description ?? ""}`.toLowerCase().includes(f3.search.toLowerCase()));
12202
+ if (f3?.updatedAfter) pool = pool.filter((i) => (i.updatedAt ?? "") >= f3.updatedAfter);
11912
12203
  const size = Math.max(1, request.pageSize ?? defaultPageSize);
11913
12204
  const start = request.cursor ? Math.max(0, Number.parseInt(request.cursor, 10) || 0) : 0;
11914
12205
  const slice = pool.slice(start, start + size);
@@ -11960,6 +12251,9 @@ function configStatus(integration, findings) {
11960
12251
  if (blocking || !registry.has(integration.adapter)) return "invalid-config";
11961
12252
  return "configured";
11962
12253
  }
12254
+ function secretResolver(dir, env) {
12255
+ return createCompositeResolver(createLocalSecretProvider(dir), createEnvSecretProvider(env));
12256
+ }
11963
12257
  function listIntegrations(dir) {
11964
12258
  const { integrations, findings } = loadIntegrations(dir);
11965
12259
  return integrations.map((integration) => {
@@ -11973,6 +12267,9 @@ function listIntegrations(dir) {
11973
12267
  capabilities: adapter?.capabilities ?? null,
11974
12268
  metadata: adapter?.metadata ?? null,
11975
12269
  credentialRefs: Object.values(integration.credentials).map((c) => c.env),
12270
+ secretRefs: Object.values(integration.secrets),
12271
+ secretStatus: {},
12272
+ filters: integration.filters,
11976
12273
  findings: findings.filter((f3) => f3.id === integration.id)
11977
12274
  };
11978
12275
  });
@@ -11981,15 +12278,16 @@ function requireIntegration(dir, id) {
11981
12278
  const { integrations, findings } = loadIntegrations(dir);
11982
12279
  const integration = integrations.find((i) => i.id === id);
11983
12280
  if (!integration) throw new IntegrationServiceError("INTEGRATION_NOT_CONFIGURED", `No integration "${id}" is configured in this project.`);
11984
- return { integration, findings };
12281
+ return { integration, findings, all: integrations };
11985
12282
  }
11986
12283
  function resolveAdapter(integration) {
11987
12284
  const adapter = registry.get(integration.adapter);
11988
12285
  if (!adapter) throw new IntegrationServiceError("ADAPTER_NOT_FOUND", `Unknown integration adapter "${integration.adapter}".`);
11989
12286
  return adapter;
11990
12287
  }
11991
- function buildContext(integration, env) {
11992
- const { credentials, missing } = resolveCredentials(integration, env);
12288
+ async function buildContextWithSecrets(dir, integration, env) {
12289
+ const resolver = secretResolver(dir, env);
12290
+ const { credentials, missing } = await resolveAllCredentials(integration, resolver, env);
11993
12291
  return {
11994
12292
  context: { integrationId: integration.id, config: integration.config, credentials, timeoutMs: integration.timeoutMs ?? DEFAULT_TIMEOUT_MS },
11995
12293
  missing
@@ -12012,7 +12310,7 @@ async function verifyIntegration(dir, id, env = process.env) {
12012
12310
  if (cfgStatus === "disabled") return { id, status: "disabled", connection: null, missingCredentials: [] };
12013
12311
  if (cfgStatus === "invalid-config") return { id, status: "invalid-config", connection: null, missingCredentials: [] };
12014
12312
  const adapter = resolveAdapter(integration);
12015
- const { context, missing } = buildContext(integration, env);
12313
+ const { context, missing } = await buildContextWithSecrets(dir, integration, env);
12016
12314
  try {
12017
12315
  const connection = await withTimeout(adapter.verifyConnection(context), context.timeoutMs);
12018
12316
  return { id, status: statusOf(connection), connection, missingCredentials: missing, message: connection.message };
@@ -12037,14 +12335,15 @@ async function listExternalWorkItems(dir, id, opts = {}, env = process.env) {
12037
12335
  const { integration } = requireIntegration(dir, id);
12038
12336
  const adapter = resolveAdapter(integration);
12039
12337
  if (!adapter.capabilities.workItems.list) throw integrationError("UNSUPPORTED_CAPABILITY", `Adapter "${adapter.id}" cannot list work items.`);
12040
- const { context } = buildContext(integration, env);
12041
- return withTimeout(adapter.listWorkItems({ context, cursor: opts.cursor, pageSize: opts.pageSize, filters: opts.filters }), context.timeoutMs);
12338
+ const { context } = await buildContextWithSecrets(dir, integration, env);
12339
+ const filters = mergeFilters(integration.filters, opts.filters);
12340
+ return withTimeout(adapter.listWorkItems({ context, cursor: opts.cursor, pageSize: opts.pageSize, filters }), context.timeoutMs);
12042
12341
  }
12043
12342
  async function getExternalWorkItem(dir, id, externalId, env = process.env) {
12044
12343
  const { integration } = requireIntegration(dir, id);
12045
12344
  const adapter = resolveAdapter(integration);
12046
12345
  if (!adapter.capabilities.workItems.read) throw integrationError("UNSUPPORTED_CAPABILITY", `Adapter "${adapter.id}" cannot read work items.`);
12047
- const { context } = buildContext(integration, env);
12346
+ const { context } = await buildContextWithSecrets(dir, integration, env);
12048
12347
  return withTimeout(adapter.getWorkItem({ context, externalId }), context.timeoutMs);
12049
12348
  }
12050
12349
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaddo/mcp",
3
- "version": "3.82.0",
3
+ "version": "3.84.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": {