@pairsystems/goodmem-mcp 0.1.12 → 0.1.14

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 (3) hide show
  1. package/README.md +53 -58
  2. package/dist/index.js +427 -260
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -20988,15 +20988,28 @@ var StdioServerTransport = class {
20988
20988
  // dist/index.js
20989
20989
  var goodmemBaseUrl = process.env.GOODMEM_BASE_URL || "";
20990
20990
  var goodmemApiKey = process.env.GOODMEM_API_KEY || "";
20991
- var BASED_ON_GOODMEM_COMMIT = "82cedec39260bff105828bed1c1e779d9f1e811f";
20991
+ var BASED_ON_GOODMEM_COMMIT = "62c6d77befdfab1bfe38ef917c6a202f4569eec7";
20992
20992
  function isConfigured() {
20993
- return goodmemBaseUrl !== "" && goodmemApiKey !== "";
20993
+ return goodmemBaseUrl !== "";
20994
20994
  }
20995
- async function callApi(method, path, body, query) {
20995
+ function _buildUrl(base, path) {
20996
+ const baseUrl = new URL(base);
20997
+ const basePath = baseUrl.pathname.endsWith("/") ? baseUrl.pathname.slice(0, -1) : baseUrl.pathname;
20998
+ const tail = path.startsWith("/") ? path : "/" + path;
20999
+ baseUrl.pathname = basePath + tail;
21000
+ baseUrl.search = "";
21001
+ baseUrl.hash = "";
21002
+ return baseUrl;
21003
+ }
21004
+ var DEFAULT_TIMEOUT_MS = 6e4;
21005
+ function _trimBody(text) {
21006
+ return text.length > 500 ? text.slice(0, 500) + "...[truncated]" : text;
21007
+ }
21008
+ async function callApi(method, path, body, query, timeoutMs) {
20996
21009
  if (!isConfigured()) {
20997
21010
  throw new Error("GoodMem is not configured. Call the goodmem_configure tool first with your base_url and api_key, or set GOODMEM_BASE_URL and GOODMEM_API_KEY environment variables.");
20998
21011
  }
20999
- const url = new URL(path, goodmemBaseUrl);
21012
+ const url = _buildUrl(goodmemBaseUrl, path);
21000
21013
  if (query) {
21001
21014
  for (const [k, v] of Object.entries(query)) {
21002
21015
  if (v !== void 0 && v !== null && v !== "")
@@ -21004,18 +21017,26 @@ async function callApi(method, path, body, query) {
21004
21017
  }
21005
21018
  }
21006
21019
  const headers = {
21007
- "Content-Type": "application/json",
21008
- "x-api-key": goodmemApiKey
21020
+ "Content-Type": "application/json"
21009
21021
  };
21022
+ if (goodmemApiKey !== "") {
21023
+ headers["x-api-key"] = goodmemApiKey;
21024
+ }
21010
21025
  const resp = await fetch(url.toString(), {
21011
21026
  method,
21012
21027
  headers,
21013
- signal: AbortSignal.timeout(3e4),
21028
+ signal: AbortSignal.timeout(timeoutMs ?? DEFAULT_TIMEOUT_MS),
21029
+ // ``x-api-key`` is a custom header — fetch/undici don't strip it on
21030
+ // 3xx like they strip Authorization. ``redirect: "error"`` fails the
21031
+ // request instead of carrying the key across a host boundary. BYO
21032
+ // callers can implement their own redirect policy at a higher layer.
21033
+ // amin3141 pre-GA blocker #2, cycle 14. ``inv-no-redirect-with-custom-auth-header``.
21034
+ redirect: "error",
21014
21035
  ...body ? { body: JSON.stringify(body) } : {}
21015
21036
  });
21016
21037
  if (!resp.ok) {
21017
21038
  const text = await resp.text();
21018
- throw new Error(`GoodMem API error ${resp.status}: ${text}`);
21039
+ throw new Error(`GoodMem API error ${resp.status}: ${_trimBody(text)}`);
21019
21040
  }
21020
21041
  const ct = resp.headers.get("content-type") || "";
21021
21042
  if (ct.includes("application/json")) {
@@ -21027,7 +21048,7 @@ async function callApiNdjson(method, path, body, query, timeoutSec) {
21027
21048
  if (!isConfigured()) {
21028
21049
  throw new Error("GoodMem is not configured. Call the goodmem_configure tool first with your base_url and api_key, or set GOODMEM_BASE_URL and GOODMEM_API_KEY environment variables.");
21029
21050
  }
21030
- const url = new URL(path, goodmemBaseUrl);
21051
+ const url = _buildUrl(goodmemBaseUrl, path);
21031
21052
  if (query) {
21032
21053
  for (const [k, v] of Object.entries(query)) {
21033
21054
  if (v !== void 0 && v !== null && v !== "")
@@ -21036,18 +21057,22 @@ async function callApiNdjson(method, path, body, query, timeoutSec) {
21036
21057
  }
21037
21058
  const headers = {
21038
21059
  "Content-Type": "application/json",
21039
- "Accept": "application/x-ndjson",
21040
- "x-api-key": goodmemApiKey
21060
+ "Accept": "application/x-ndjson"
21041
21061
  };
21062
+ if (goodmemApiKey !== "") {
21063
+ headers["x-api-key"] = goodmemApiKey;
21064
+ }
21042
21065
  const resp = await fetch(url.toString(), {
21043
21066
  method,
21044
21067
  headers,
21045
21068
  signal: AbortSignal.timeout((timeoutSec ?? 200) * 1e3),
21069
+ // See callApi for the redirect-policy rationale.
21070
+ redirect: "error",
21046
21071
  ...body ? { body: JSON.stringify(body) } : {}
21047
21072
  });
21048
21073
  if (!resp.ok) {
21049
21074
  const text2 = await resp.text();
21050
- throw new Error(`GoodMem API error ${resp.status}: ${text2}`);
21075
+ throw new Error(`GoodMem API error ${resp.status}: ${_trimBody(text2)}`);
21051
21076
  }
21052
21077
  const text = await resp.text();
21053
21078
  const lines = text.split("\n").map((line) => line.trim()).filter(Boolean);
@@ -21176,8 +21201,7 @@ var MODEL_REGISTRY = {
21176
21201
  },
21177
21202
  "supportedModalities": [
21178
21203
  "TEXT",
21179
- "IMAGE",
21180
- "PDF"
21204
+ "IMAGE"
21181
21205
  ],
21182
21206
  "maxSequenceLength": 32768
21183
21207
  },
@@ -21639,114 +21663,6 @@ var MODEL_REGISTRY = {
21639
21663
  "TEXT"
21640
21664
  ],
21641
21665
  "maxContextLength": 128e3
21642
- },
21643
- "command-a-03-2025": {
21644
- "providerType": "COHERE",
21645
- "supportedModalities": [
21646
- "TEXT"
21647
- ],
21648
- "maxContextLength": 256e3
21649
- },
21650
- "command-r7b-12-2024": {
21651
- "providerType": "COHERE",
21652
- "supportedModalities": [
21653
- "TEXT"
21654
- ],
21655
- "maxContextLength": 128e3
21656
- },
21657
- "command-a-translate-08-2025": {
21658
- "providerType": "COHERE",
21659
- "supportedModalities": [
21660
- "TEXT"
21661
- ],
21662
- "maxContextLength": 8e3
21663
- },
21664
- "command-a-reasoning-08-2025": {
21665
- "providerType": "COHERE",
21666
- "supportedModalities": [
21667
- "TEXT"
21668
- ],
21669
- "maxContextLength": 256e3
21670
- },
21671
- "command-a-vision-07-2025": {
21672
- "providerType": "COHERE",
21673
- "supportedModalities": [
21674
- "TEXT",
21675
- "IMAGE"
21676
- ],
21677
- "maxContextLength": 128e3
21678
- },
21679
- "command-r-08-2024": {
21680
- "providerType": "COHERE",
21681
- "supportedModalities": [
21682
- "TEXT"
21683
- ],
21684
- "maxContextLength": 128e3
21685
- },
21686
- "command-r-plus-08-2024": {
21687
- "providerType": "COHERE",
21688
- "supportedModalities": [
21689
- "TEXT"
21690
- ],
21691
- "maxContextLength": 128e3
21692
- },
21693
- "c4ai-aya-expanse-8b": {
21694
- "providerType": "COHERE",
21695
- "supportedModalities": [
21696
- "TEXT"
21697
- ],
21698
- "maxContextLength": 8e3
21699
- },
21700
- "c4ai-aya-expanse-32b": {
21701
- "providerType": "COHERE",
21702
- "supportedModalities": [
21703
- "TEXT"
21704
- ],
21705
- "maxContextLength": 128e3
21706
- },
21707
- "c4ai-aya-vision-8b": {
21708
- "providerType": "COHERE",
21709
- "supportedModalities": [
21710
- "TEXT",
21711
- "IMAGE"
21712
- ],
21713
- "maxContextLength": 16e3
21714
- },
21715
- "c4ai-aya-vision-32b": {
21716
- "providerType": "COHERE",
21717
- "supportedModalities": [
21718
- "TEXT",
21719
- "IMAGE"
21720
- ],
21721
- "maxContextLength": 16e3
21722
- },
21723
- "command-r-03-2024": {
21724
- "providerType": "COHERE",
21725
- "supportedModalities": [
21726
- "TEXT"
21727
- ],
21728
- "maxContextLength": 128e3
21729
- },
21730
- "command-r-plus-04-2024": {
21731
- "providerType": "COHERE",
21732
- "supportedModalities": [
21733
- "TEXT"
21734
- ],
21735
- "maxContextLength": 128e3
21736
- },
21737
- "command": {
21738
- "providerType": "COHERE",
21739
- "supportedModalities": [
21740
- "TEXT"
21741
- ],
21742
- "maxContextLength": 4e3
21743
- },
21744
- "command-light": {
21745
- "providerType": "COHERE",
21746
- "supportedModalities": [
21747
- "TEXT"
21748
- ],
21749
- "maxContextLength": 4e3
21750
21666
  }
21751
21667
  },
21752
21668
  "rerankers": {
@@ -21927,36 +21843,43 @@ function autoInferFromRegistry(registryType, args) {
21927
21843
  }
21928
21844
  var server = new McpServer({
21929
21845
  name: "goodmem",
21930
- version: "0.1.12"
21846
+ version: "0.1.14"
21931
21847
  });
21932
- server.tool("goodmem_configure", "Configure GoodMem server connection. Call this before using other GoodMem tools if GOODMEM_BASE_URL and GOODMEM_API_KEY environment variables are not set. Session credentials override environment variables.", {
21848
+ server.tool("goodmem_configure", "Configure GoodMem server connection. Call this before using other GoodMem tools if GOODMEM_BASE_URL and GOODMEM_API_KEY environment variables are not set. Session credentials override environment variables. For bootstrap workflows that need to call /v1/system/info or /v1/system/init before keys exist, api_key may be omitted; most other tools will return AuthenticationException until a real key is provided.", {
21933
21849
  base_url: external_exports.string().describe("GoodMem server URL (e.g., https://your-server.example.com)"),
21934
- api_key: external_exports.string().describe("GoodMem API key (starts with gm_)")
21850
+ api_key: external_exports.string().describe("GoodMem API key (starts with gm_). Omit for bootstrap-only sessions that call /v1/system/info or /v1/system/init.").optional()
21935
21851
  }, async (args) => {
21936
21852
  if (!/^https?:\/\//i.test(args.base_url)) {
21937
- return { content: [{ type: "text", text: `base_url must include a scheme (e.g., http://${args.base_url} or https://${args.base_url}).` }] };
21853
+ return { isError: true, content: [{ type: "text", text: `base_url must include a scheme (e.g., http://${args.base_url} or https://${args.base_url}).` }] };
21938
21854
  }
21939
21855
  try {
21940
- const url = new URL("/v1/system/info", args.base_url);
21856
+ const url = _buildUrl(args.base_url, "/v1/system/info");
21857
+ const verifyHeaders = {};
21858
+ if (args.api_key && args.api_key !== "") {
21859
+ verifyHeaders["x-api-key"] = args.api_key;
21860
+ }
21941
21861
  const resp = await fetch(url.toString(), {
21942
- headers: { "x-api-key": args.api_key },
21943
- signal: AbortSignal.timeout(1e4)
21862
+ headers: verifyHeaders,
21863
+ signal: AbortSignal.timeout(1e4),
21864
+ // See callApi for the redirect-policy rationale.
21865
+ redirect: "error"
21944
21866
  });
21945
21867
  if (!resp.ok) {
21946
21868
  const text = await resp.text();
21947
- return { content: [{ type: "text", text: `Connection failed (${resp.status}): ${text}. Credentials were NOT saved. Check your base_url and api_key.` }] };
21869
+ return { isError: true, content: [{ type: "text", text: `Connection failed (${resp.status}): ${_trimBody(text)}. Credentials were NOT saved. Check your base_url and api_key.` }] };
21948
21870
  }
21871
+ const info = await resp.json().catch(() => ({}));
21949
21872
  goodmemBaseUrl = args.base_url;
21950
- goodmemApiKey = args.api_key;
21951
- const info = await resp.json();
21952
- return { content: [{ type: "text", text: `Connected to GoodMem at ${goodmemBaseUrl}. Server version: ${info.version ?? "unknown"}.` }] };
21873
+ goodmemApiKey = args.api_key ?? "";
21874
+ const keyNote = goodmemApiKey === "" ? " (bootstrap-only session \u2014 no api_key set)" : "";
21875
+ return { content: [{ type: "text", text: `Connected to GoodMem at ${goodmemBaseUrl}. Server version: ${info.version ?? "unknown"}.${keyNote}` }] };
21953
21876
  } catch (err) {
21954
21877
  const msg = err instanceof Error ? err.message : String(err);
21955
21878
  let hint = "Check your base_url.";
21956
21879
  if (msg.includes("CERT") || msg.includes("certificate") || msg.includes("self-signed") || msg.includes("self signed") || msg.includes("UNABLE_TO_VERIFY")) {
21957
21880
  hint = "TLS certificate error. If using self-signed certs, set NODE_TLS_REJECT_UNAUTHORIZED=0 (dev only) or NODE_EXTRA_CA_CERTS=/path/to/rootCA.pem in your MCP server env config.";
21958
21881
  }
21959
- return { content: [{ type: "text", text: `Could not connect to ${args.base_url}: ${msg}. Credentials were NOT saved. ${hint}` }] };
21882
+ return { isError: true, content: [{ type: "text", text: `Could not connect to ${args.base_url}: ${msg}. Credentials were NOT saved. ${hint}` }] };
21960
21883
  }
21961
21884
  });
21962
21885
  server.tool("goodmem_lookup_model", "Look up a model in the registry to see its provider, endpoint, dimensions, and other auto-inferred fields. Use this to check what models are available before creating an embedder, LLM, or reranker.", {
@@ -21975,6 +21898,12 @@ server.tool("goodmem_lookup_model", "Look up a model in the registry to see its
21975
21898
  if (dims?.default !== void 0) {
21976
21899
  snakeEntry.dimensionality = dims.default;
21977
21900
  }
21901
+ if (dims?.range !== void 0) {
21902
+ snakeEntry.dimensions_range = dims.range;
21903
+ }
21904
+ if (dims?.allowed !== void 0) {
21905
+ snakeEntry.dimensions_allowed = dims.allowed;
21906
+ }
21978
21907
  } else {
21979
21908
  snakeEntry[camelToSnake(k)] = v;
21980
21909
  }
@@ -21988,22 +21917,24 @@ server.tool("goodmem_lookup_model", "Look up a model in the registry to see its
21988
21917
  }, null, 2) }] };
21989
21918
  }
21990
21919
  }
21991
- const allModels = Object.entries(MODEL_REGISTRY).flatMap(([type, models]) => Object.keys(models).map((m) => `${type.slice(0, -1)}: ${m}`));
21992
- return { content: [{ type: "text", text: `Model "${args.model_identifier}" not found in registry. Available models:
21920
+ const filterTypes = args.type ? [args.type + "s"] : Object.keys(MODEL_REGISTRY);
21921
+ const allModels = filterTypes.filter((t) => MODEL_REGISTRY[t]).flatMap((t) => Object.keys(MODEL_REGISTRY[t]).map((m) => `${t.slice(0, -1)}: ${m}`));
21922
+ const scope = args.type ? `${args.type}s` : "any registry";
21923
+ return { content: [{ type: "text", text: `Model "${args.model_identifier}" not found in ${scope}. Available models:
21993
21924
  ${allModels.join("\n")}` }] };
21994
21925
  });
21995
21926
  server.tool("goodmem_client_info", "Return the MCP client package version and the goodmem server commit it was built against. No server connection required.", {}, async () => {
21996
21927
  return { content: [{ type: "text", text: JSON.stringify({
21997
- mcpVersion: "0.1.12",
21998
- basedOnGoodmemCommit: BASED_ON_GOODMEM_COMMIT
21928
+ mcp_version: "0.1.14",
21929
+ based_on_goodmem_commit: BASED_ON_GOODMEM_COMMIT
21999
21930
  }, null, 2) }] };
22000
21931
  });
22001
21932
  function registerTools() {
22002
- server.tool("goodmem_admin_background_jobs_purge", "Purge completed background jobs", {
21933
+ server.tool("goodmem_admin_background_jobs_purge", "Purge completed background jobs\n\nDeletes terminal background jobs older than a retention cutoff.", {
22003
21934
  older_than: external_exports.string().describe("ISO-8601 timestamp cutoff; only terminal jobs older than this instant are eligible."),
22004
- statuses: external_exports.array(external_exports.string()).describe("Optional terminal background job statuses to target for purging.").optional(),
21935
+ statuses: external_exports.array(external_exports.enum(["BACKGROUND_JOB_SUCCEEDED", "BACKGROUND_JOB_FAILED", "BACKGROUND_JOB_CANCELED"])).describe("Optional terminal background job statuses to target for purging. If omitted, all terminal statuses are eligible. Canonical values are BACKGROUND_JOB_SUCCEEDED, BACKGROUND_JOB_FAILED, and BACKGROUND_JOB_CANCELED. Short aliases SUCCEEDED, FAILED, and CANCELED are also accepted for compatibility.").optional(),
22005
21936
  dry_run: external_exports.boolean().describe("If true, report purge counts without deleting any rows.").optional(),
22006
- limit: external_exports.number().int().describe("Maximum number of jobs to purge in this request.").optional()
21937
+ limit: external_exports.number().int().describe("Maximum number of jobs to purge in this request. Must be >= 0; 0 means no limit.").optional()
22007
21938
  }, async (args) => {
22008
21939
  const body = {};
22009
21940
  if (args.older_than !== void 0)
@@ -22017,7 +21948,20 @@ function registerTools() {
22017
21948
  const result = await callApi("POST", `/v1/admin/background-jobs:purge`, body);
22018
21949
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22019
21950
  });
22020
- server.tool("goodmem_admin_drain", "Request the server to enter drain mode", {
21951
+ server.tool("goodmem_admin_delete", "Delete a RetrieveMemory log policy\n\nIdempotently tombstones an immutable RetrieveMemory log policy.", {
21952
+ id: external_exports.string().describe("The UUID of the policy to delete"),
21953
+ reason: external_exports.string().describe("Optional tombstone reason.").optional()
21954
+ }, async (args) => {
21955
+ if (typeof args.id !== "string" || args.id === "") {
21956
+ throw new Error("'id' must be a non-empty string");
21957
+ }
21958
+ const body = {};
21959
+ if (args.reason !== void 0)
21960
+ body["reason"] = args.reason;
21961
+ await callApi("DELETE", `/v1/admin/retrieve-memory-log-policies/${encodeURIComponent(String(args.id))}`, body);
21962
+ return { content: [{ type: "text", text: "Deleted successfully." }] };
21963
+ });
21964
+ server.tool("goodmem_admin_drain", "Request the server to enter drain mode\n\nInitiates drain mode and optionally waits for the server to quiesce.", {
22021
21965
  timeout_sec: external_exports.number().int().describe("Maximum seconds to wait for the server to quiesce before returning.").optional(),
22022
21966
  reason: external_exports.string().describe("Human-readable reason for initiating drain mode.").optional(),
22023
21967
  wait_for_quiesce: external_exports.boolean().describe("If true, wait for in-flight requests to complete and the server to reach QUIESCED before responding.").optional()
@@ -22029,14 +21973,59 @@ function registerTools() {
22029
21973
  body["reason"] = args.reason;
22030
21974
  if (args.wait_for_quiesce !== void 0)
22031
21975
  body["waitForQuiesce"] = args.wait_for_quiesce;
22032
- const result = await callApi("POST", `/v1/admin:drain`, body);
21976
+ const result = await callApi("POST", `/v1/admin:drain`, body, void 0, ((args.timeout_sec ?? 900) + 30) * 1e3);
22033
21977
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22034
21978
  });
22035
- server.tool("goodmem_admin_license_reload", "Reload the active license from disk", {}, async (args) => {
21979
+ server.tool("goodmem_admin_get", "Get a RetrieveMemory log policy\n\nRetrieves a live RetrieveMemory log policy by UUID, or a tombstoned policy when include_deleted is true.", {
21980
+ id: external_exports.string().describe("The UUID of the policy to retrieve"),
21981
+ include_deleted: external_exports.boolean().describe("Whether to include tombstoned policies. Also accepts include_deleted.").optional()
21982
+ }, async (args) => {
21983
+ if (typeof args.id !== "string" || args.id === "") {
21984
+ throw new Error("'id' must be a non-empty string");
21985
+ }
21986
+ const query = {};
21987
+ if (args.include_deleted !== void 0)
21988
+ query["includeDeleted"] = String(args.include_deleted);
21989
+ const result = await callApi("GET", `/v1/admin/retrieve-memory-log-policies/${encodeURIComponent(String(args.id))}`, void 0, query);
21990
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
21991
+ });
21992
+ server.tool("goodmem_admin_license_reload", "Reload the active license from disk\n\nTriggers the server to reload its license file from the configured directory and returns metadata about the currently active license.", {}, async (args) => {
22036
21993
  const result = await callApi("POST", `/v1/admin/license:reload`, void 0);
22037
21994
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22038
21995
  });
22039
- server.tool("goodmem_apikeys_create", "Create a new API key", {
21996
+ server.tool("goodmem_admin_retrieve_memory_log_policies", "List RetrieveMemory log policies\n\nLists RetrieveMemory log policies with optional tombstone, active-time, name, label, sort, and pagination filters. LABEL FILTERS: Label filters accept either label.<key>=<value> or label[key]=value (for example, label.environment=production or label[environment]=production).", {
21997
+ include_deleted: external_exports.boolean().describe("Whether to include tombstoned policies. Also accepts include_deleted.").optional(),
21998
+ name_filter: external_exports.string().describe("Case-insensitive substring filter on policy display names. Also accepts name_filter.").optional(),
21999
+ active_at: external_exports.number().int().describe("Only return policies active at this millisecond epoch timestamp. Also accepts active_at.").optional(),
22000
+ max_results: external_exports.number().int().describe("Maximum number of policies to return. Also accepts max_results.").optional(),
22001
+ next_token: external_exports.string().describe("Opaque pagination token returned by the previous list response. Also accepts next_token.").optional(),
22002
+ sort_by: external_exports.enum(["created_at", "updated_at", "display_name"]).describe("Sort field: created_at, updated_at, or display_name. Also accepts sort_by.").optional(),
22003
+ sort_order: external_exports.enum(["ASCENDING", "DESCENDING", "SORT_ORDER_UNSPECIFIED"]).describe("Sort order. Also accepts sort_order.").optional(),
22004
+ label: external_exports.record(external_exports.string(), external_exports.string()).describe("Filter by label. Each key-value pair is sent as label[key]=value query parameter.").optional()
22005
+ }, async (args) => {
22006
+ const query = {};
22007
+ if (args.include_deleted !== void 0)
22008
+ query["includeDeleted"] = String(args.include_deleted);
22009
+ if (args.name_filter !== void 0)
22010
+ query["nameFilter"] = String(args.name_filter);
22011
+ if (args.active_at !== void 0)
22012
+ query["activeAt"] = String(args.active_at);
22013
+ if (args.max_results !== void 0)
22014
+ query["maxResults"] = String(args.max_results);
22015
+ if (args.next_token !== void 0)
22016
+ query["nextToken"] = String(args.next_token);
22017
+ if (args.sort_by !== void 0)
22018
+ query["sortBy"] = String(args.sort_by);
22019
+ if (args.sort_order !== void 0)
22020
+ query["sortOrder"] = String(args.sort_order);
22021
+ if (args.label) {
22022
+ for (const [k, v] of Object.entries(args.label))
22023
+ query[`label[${k}]`] = v;
22024
+ }
22025
+ const result = await callApi("GET", `/v1/admin/retrieve-memory-log-policies`, void 0, query);
22026
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22027
+ });
22028
+ server.tool("goodmem_apikeys_create", "Create a new API key\n\nCreates a new API key for authenticating with the API. New keys start in ACTIVE status and can be used immediately for authentication. The response includes the one-time raw API key value which will not be retrievable later - clients must save this value as it cannot be recovered. Requires CREATE_APIKEY_OWN permission (or CREATE_APIKEY_ANY for admin users). Side effects include generating cryptographically secure key material, recording creation timestamp, and tracking creator ID. Returns INVALID_ARGUMENT if expires_at is set to a time in the past.", {
22040
22029
  labels: external_exports.record(external_exports.string(), external_exports.string()).describe("Key-value pairs of metadata associated with the API key. Used for organization and filtering.").optional(),
22041
22030
  expires_at: external_exports.number().int().describe("Expiration timestamp in milliseconds since epoch. If not provided, the key does not expire.").optional(),
22042
22031
  api_key_id: external_exports.string().describe("Optional client-provided UUID for idempotent creation. If not provided, server generates a new UUID. Returns ALREADY_EXISTS if ID is already in use.").optional()
@@ -22051,22 +22040,32 @@ function registerTools() {
22051
22040
  const result = await callApi("POST", `/v1/apikeys`, body);
22052
22041
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22053
22042
  });
22054
- server.tool("goodmem_apikeys_delete", "Delete an API key", {
22043
+ server.tool("goodmem_apikeys_delete", 'Delete an API key\n\nPermanently deletes an API key. This operation cannot be undone and immediately invalidates the key for all future authentication attempts. TIP: For reversible deactivation, use the `goodmem_apikeys_update` tool with `status: "INACTIVE"` instead. Requires DELETE_APIKEY_OWN permission for keys you own (or DELETE_APIKEY_ANY for admin users to delete any user\'s keys). Side effects include permanently removing the key record from the database and immediate authentication invalidation.', {
22055
22044
  id: external_exports.string().describe("The UUID of the API key to delete")
22056
22045
  }, async (args) => {
22046
+ if (typeof args.id !== "string" || args.id === "") {
22047
+ throw new Error("'id' must be a non-empty string");
22048
+ }
22057
22049
  await callApi("DELETE", `/v1/apikeys/${encodeURIComponent(String(args.id))}`, void 0);
22058
22050
  return { content: [{ type: "text", text: "Deleted successfully." }] };
22059
22051
  });
22060
- server.tool("goodmem_apikeys_list", "List API keys", {}, async (args) => {
22052
+ server.tool("goodmem_apikeys_list", "List API keys\n\nRetrieves a list of API keys belonging to the authenticated user. The list includes metadata about each key but not the actual key values or key hashes for security reasons. Requires LIST_APIKEY_OWN permission (or LIST_APIKEY_ANY for admin users to view keys from any user). This is a read-only operation with no side effects.", {}, async (args) => {
22061
22053
  const result = await callApi("GET", `/v1/apikeys`, void 0);
22062
22054
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22063
22055
  });
22064
- server.tool("goodmem_apikeys_update", "Update an API key", {
22056
+ server.tool("goodmem_apikeys_update", "Update an API key\n\nUpdates an existing API key with new values for status or labels. IMPORTANT: Key ID, user ownership, key material, and audit fields cannot be modified - only status (ACTIVE/INACTIVE) and labels are mutable. Requires UPDATE_APIKEY_OWN permission for keys you own (or UPDATE_APIKEY_ANY for admin users to modify any user's keys). Side effects include updating the updated_at timestamp and recording the updater's user ID. This operation is idempotent - repeated identical requests have no additional effect.", {
22065
22057
  id: external_exports.string().describe("The UUID of the API key to update"),
22066
- status: external_exports.string().describe("New status for the API key. Allowed values: ACTIVE, INACTIVE.").optional(),
22067
- replace_labels: external_exports.record(external_exports.string(), external_exports.string()).describe("Replace all existing labels with this set. Mutually exclusive with mergeLabels.").optional(),
22068
- merge_labels: external_exports.record(external_exports.string(), external_exports.string()).describe("Merge these labels with existing ones. Mutually exclusive with replaceLabels.").optional()
22058
+ status: external_exports.enum(["ACTIVE", "INACTIVE"]).describe("New status for the API key. Allowed values: ACTIVE, INACTIVE.").optional(),
22059
+ replace_labels: external_exports.record(external_exports.string(), external_exports.string()).describe("Replace all existing labels with this set. Mutually exclusive with merge_labels.").optional(),
22060
+ merge_labels: external_exports.record(external_exports.string(), external_exports.string()).describe("Merge these labels with existing ones. Mutually exclusive with replace_labels.").optional()
22069
22061
  }, async (args) => {
22062
+ if (typeof args.id !== "string" || args.id === "") {
22063
+ throw new Error("'id' must be a non-empty string");
22064
+ }
22065
+ const _set = [args.replace_labels !== void 0 && args.replace_labels !== null, args.merge_labels !== void 0 && args.merge_labels !== null].filter(Boolean).length;
22066
+ if (_set > 1) {
22067
+ throw new Error("at most one of replace_labels or merge_labels may be set");
22068
+ }
22070
22069
  const body = {};
22071
22070
  if (args.status !== void 0)
22072
22071
  body["status"] = args.status;
@@ -22077,7 +22076,7 @@ function registerTools() {
22077
22076
  const result = await callApi("PUT", `/v1/apikeys/${encodeURIComponent(String(args.id))}`, body);
22078
22077
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22079
22078
  });
22080
- server.tool("goodmem_embedders_create", "Create a new embedder", {
22079
+ server.tool("goodmem_embedders_create", "Create a new embedder\n\nCreates a new embedder configuration for vectorizing content. Embedders represent connections to different embedding API services (like OpenAI, vLLM, etc.) and include all the necessary configuration to use them with memory spaces. DUPLICATE DETECTION: Returns HTTP 409 Conflict (ALREADY_EXISTS) if another embedder exists with identical {owner_id, provider_type, endpoint_url, api_path, model_identifier, dimensionality, distribution_type, credentialsFingerprint} after URL canonicalization. Uniqueness is enforced per-owner, allowing different users to have identical configurations. Credentials are hashed (SHA-256) for uniqueness while remaining encrypted. The api_path field defaults to '/v2/embed' for Cohere, '/embed' for TEI, and '/embeddings' for other providers when omitted. Requires CREATE_EMBEDDER_OWN permission (or CREATE_EMBEDDER_ANY for admin users).", {
22081
22080
  display_name: external_exports.string().describe("User-facing name of the embedder"),
22082
22081
  description: external_exports.string().describe("Description of the embedder").optional(),
22083
22082
  provider_type: external_exports.enum(["OPENAI", "VLLM", "TEI", "LLAMA_CPP", "VOYAGE", "COHERE", "JINA"]).describe("Type of embedding provider (auto-inferred from model_identifier if omitted)").optional(),
@@ -22094,17 +22093,17 @@ function registerTools() {
22094
22093
  inlineSecret: external_exports.string().describe("Secret stored directly in GoodMem (mutually exclusive with secretRef)").optional(),
22095
22094
  secretRef: external_exports.object({
22096
22095
  uri: external_exports.string().describe("URI identifying where the secret can be resolved (e.g., vault://, env://)"),
22097
- hints: external_exports.any().describe('Optional metadata to help resolvers decode the secret (e.g., {"encoding":"base64"})').optional()
22096
+ hints: external_exports.record(external_exports.string(), external_exports.string()).describe('Optional metadata to help resolvers decode the secret (e.g., {"encoding":"base64"})').optional()
22098
22097
  }).passthrough().describe("Reference to an external secret manager entry (mutually exclusive with inlineSecret)").optional(),
22099
22098
  headerName: external_exports.string().describe("Desired HTTP header to carry the credential (defaults to Authorization)").optional(),
22100
22099
  prefix: external_exports.string().describe('Optional prefix prepended to the secret (e.g., "Bearer ")').optional()
22101
22100
  }).passthrough().describe("Configuration when kind is CREDENTIAL_KIND_API_KEY").optional(),
22102
22101
  gcpAdc: external_exports.object({
22103
- scopes: external_exports.any().describe("Additional OAuth scopes. Empty list falls back to the default cloud-platform scope.").optional(),
22102
+ scopes: external_exports.array(external_exports.string()).describe("Additional OAuth scopes. Empty list falls back to the default cloud-platform scope.").optional(),
22104
22103
  quotaProjectId: external_exports.string().describe("Optional quota project used for billing").optional()
22105
22104
  }).passthrough().describe("Configuration when kind is CREDENTIAL_KIND_GCP_ADC").optional(),
22106
- labels: external_exports.any().describe('Optional annotations to aid operators (e.g., "owner=vertex")').optional()
22107
- }).passthrough().describe("Structured credential payload describing how to authenticate with the provider. Required for SaaS providers such as COHERE, JINA, and VOYAGE; optional for local or proxy providers.").optional(),
22105
+ labels: external_exports.record(external_exports.string(), external_exports.string()).describe('Optional annotations to aid operators (e.g., "owner=vertex")').optional()
22106
+ }).passthrough().describe("Structured credential payload describing how to authenticate with the provider. Required for SaaS providers; optional for local or proxy providers.").optional(),
22108
22107
  labels: external_exports.record(external_exports.string(), external_exports.string()).describe("User-defined labels for categorization").optional(),
22109
22108
  version: external_exports.string().describe("Version information").optional(),
22110
22109
  monitoring_endpoint: external_exports.string().describe("Monitoring endpoint URL").optional(),
@@ -22149,19 +22148,25 @@ function registerTools() {
22149
22148
  const result = await callApi("POST", `/v1/embedders`, body);
22150
22149
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22151
22150
  });
22152
- server.tool("goodmem_embedders_delete", "Delete an embedder", {
22151
+ server.tool("goodmem_embedders_delete", "Delete an embedder\n\nPermanently deletes an embedder configuration. This operation cannot be undone and removes the embedder record and securely deletes stored credentials. IMPORTANT: This does NOT invalidate or delete embeddings previously created with this embedder - existing embeddings remain accessible. CONFLICT: Returns HTTP 409 Conflict if the embedder is still referenced by a space. Requires DELETE_EMBEDDER_OWN permission for embedders you own (or DELETE_EMBEDDER_ANY for admin users).", {
22153
22152
  id: external_exports.string().describe("The unique identifier of the embedder to delete")
22154
22153
  }, async (args) => {
22154
+ if (typeof args.id !== "string" || args.id === "") {
22155
+ throw new Error("'id' must be a non-empty string");
22156
+ }
22155
22157
  await callApi("DELETE", `/v1/embedders/${encodeURIComponent(String(args.id))}`, void 0);
22156
22158
  return { content: [{ type: "text", text: "Deleted successfully." }] };
22157
22159
  });
22158
- server.tool("goodmem_embedders_get", "Get an embedder by ID", {
22160
+ server.tool("goodmem_embedders_get", "Get an embedder by ID\n\nRetrieves the details of a specific embedder configuration by its unique identifier. Requires READ_EMBEDDER_OWN permission for embedders you own (or READ_EMBEDDER_ANY for admin users to view any user's embedders). This is a read-only operation with no side effects.", {
22159
22161
  id: external_exports.string().describe("The unique identifier of the embedder to retrieve")
22160
22162
  }, async (args) => {
22163
+ if (typeof args.id !== "string" || args.id === "") {
22164
+ throw new Error("'id' must be a non-empty string");
22165
+ }
22161
22166
  const result = await callApi("GET", `/v1/embedders/${encodeURIComponent(String(args.id))}`, void 0);
22162
22167
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22163
22168
  });
22164
- server.tool("goodmem_embedders_list", "List embedders", {
22169
+ server.tool("goodmem_embedders_list", "List embedders\n\nRetrieves a list of embedder configurations accessible to the caller, with optional filtering. LABEL FILTERS: Label filters accept either label.<key>=<value> or label[key]=value (for example, label.environment=production or label[environment]=production). PERMISSION-BASED FILTERING: With LIST_EMBEDDER_OWN permission, you can only see your own embedders (owner_id filter is ignored if set to another user). With LIST_EMBEDDER_ANY permission, you can see all embedders or filter by any owner_id. This is a read-only operation with no side effects.", {
22165
22170
  owner_id: external_exports.string().describe("Filter embedders by owner ID. With LIST_EMBEDDER_ANY permission, omitting this shows all accessible embedders; providing it filters by that owner. With LIST_EMBEDDER_OWN permission, only your own embedders are shown regardless of this parameter.").optional(),
22166
22171
  provider_type: external_exports.enum(["OPENAI", "VLLM", "TEI", "LLAMA_CPP", "VOYAGE", "COHERE", "JINA"]).describe("Filter embedders by provider type. Allowed values match the ProviderType schema.").optional(),
22167
22172
  label: external_exports.record(external_exports.string(), external_exports.string()).describe("Filter by label. Each key-value pair is sent as label[key]=value query parameter.").optional()
@@ -22178,7 +22183,7 @@ function registerTools() {
22178
22183
  const result = await callApi("GET", `/v1/embedders`, void 0, query);
22179
22184
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22180
22185
  });
22181
- server.tool("goodmem_embedders_update", "Update an embedder", {
22186
+ server.tool("goodmem_embedders_update", "Update an embedder\n\nUpdates an existing embedder configuration including display information, endpoint configuration, model parameters, credentials, and labels. All fields are optional - only specified fields will be updated. IMPORTANT: providerType is IMMUTABLE after creation and cannot be changed. CRITICAL: Returns HTTP 412 Precondition Failed (FAILED_PRECONDITION) if attempting to update core fields (dimensionality, distribution_type, model_identifier) while the embedder is actively referenced by spaces or ingestion jobs. DUPLICATE DETECTION: Returns HTTP 409 Conflict (ALREADY_EXISTS) if the update would create a duplicate embedder with the same provider, endpoint, model, dimensionality, distribution, and credentials for this owner. Requires UPDATE_EMBEDDER_OWN permission for embedders you own (or UPDATE_EMBEDDER_ANY for admin users).", {
22182
22187
  id: external_exports.string().describe("The unique identifier of the embedder to update"),
22183
22188
  display_name: external_exports.string().describe("User-facing name of the embedder").optional(),
22184
22189
  description: external_exports.string().describe("Description of the embedder").optional(),
@@ -22188,29 +22193,35 @@ function registerTools() {
22188
22193
  dimensionality: external_exports.number().int().describe("Output vector dimensions").optional(),
22189
22194
  distribution_type: external_exports.enum(["DENSE", "SPARSE"]).describe("Type of embedding distribution (DENSE or SPARSE)").optional(),
22190
22195
  max_sequence_length: external_exports.number().int().describe("Maximum input sequence length").optional(),
22191
- supported_modalities: external_exports.array(external_exports.enum(["TEXT", "IMAGE", "AUDIO", "VIDEO"])).describe("Supported content modalities").optional(),
22192
22196
  credentials: external_exports.object({
22193
22197
  kind: external_exports.enum(["CREDENTIAL_KIND_UNSPECIFIED", "CREDENTIAL_KIND_API_KEY", "CREDENTIAL_KIND_GCP_ADC"]).describe("Selected credential strategy"),
22194
22198
  apiKey: external_exports.object({
22195
22199
  inlineSecret: external_exports.string().describe("Secret stored directly in GoodMem (mutually exclusive with secretRef)").optional(),
22196
22200
  secretRef: external_exports.object({
22197
22201
  uri: external_exports.string().describe("URI identifying where the secret can be resolved (e.g., vault://, env://)"),
22198
- hints: external_exports.any().describe('Optional metadata to help resolvers decode the secret (e.g., {"encoding":"base64"})').optional()
22202
+ hints: external_exports.record(external_exports.string(), external_exports.string()).describe('Optional metadata to help resolvers decode the secret (e.g., {"encoding":"base64"})').optional()
22199
22203
  }).passthrough().describe("Reference to an external secret manager entry (mutually exclusive with inlineSecret)").optional(),
22200
22204
  headerName: external_exports.string().describe("Desired HTTP header to carry the credential (defaults to Authorization)").optional(),
22201
22205
  prefix: external_exports.string().describe('Optional prefix prepended to the secret (e.g., "Bearer ")').optional()
22202
22206
  }).passthrough().describe("Configuration when kind is CREDENTIAL_KIND_API_KEY").optional(),
22203
22207
  gcpAdc: external_exports.object({
22204
- scopes: external_exports.any().describe("Additional OAuth scopes. Empty list falls back to the default cloud-platform scope.").optional(),
22208
+ scopes: external_exports.array(external_exports.string()).describe("Additional OAuth scopes. Empty list falls back to the default cloud-platform scope.").optional(),
22205
22209
  quotaProjectId: external_exports.string().describe("Optional quota project used for billing").optional()
22206
22210
  }).passthrough().describe("Configuration when kind is CREDENTIAL_KIND_GCP_ADC").optional(),
22207
- labels: external_exports.any().describe('Optional annotations to aid operators (e.g., "owner=vertex")').optional()
22211
+ labels: external_exports.record(external_exports.string(), external_exports.string()).describe('Optional annotations to aid operators (e.g., "owner=vertex")').optional()
22208
22212
  }).passthrough().describe("Structured credential payload describing how to authenticate with the provider").optional(),
22209
- replace_labels: external_exports.record(external_exports.string(), external_exports.string()).describe("Replace all existing labels with these (mutually exclusive with mergeLabels)").optional(),
22210
- merge_labels: external_exports.record(external_exports.string(), external_exports.string()).describe("Merge these labels with existing ones (mutually exclusive with replaceLabels)").optional(),
22213
+ replace_labels: external_exports.record(external_exports.string(), external_exports.string()).describe("Replace all existing labels with these (mutually exclusive with merge_labels)").optional(),
22214
+ merge_labels: external_exports.record(external_exports.string(), external_exports.string()).describe("Merge these labels with existing ones (mutually exclusive with replace_labels)").optional(),
22211
22215
  version: external_exports.string().describe("Version information").optional(),
22212
22216
  monitoring_endpoint: external_exports.string().describe("Monitoring endpoint URL").optional()
22213
22217
  }, async (args) => {
22218
+ if (typeof args.id !== "string" || args.id === "") {
22219
+ throw new Error("'id' must be a non-empty string");
22220
+ }
22221
+ const _set = [args.replace_labels !== void 0 && args.replace_labels !== null, args.merge_labels !== void 0 && args.merge_labels !== null].filter(Boolean).length;
22222
+ if (_set > 1) {
22223
+ throw new Error("at most one of replace_labels or merge_labels may be set");
22224
+ }
22214
22225
  const body = {};
22215
22226
  if (args.display_name !== void 0)
22216
22227
  body["displayName"] = args.display_name;
@@ -22228,8 +22239,6 @@ function registerTools() {
22228
22239
  body["distributionType"] = args.distribution_type;
22229
22240
  if (args.max_sequence_length !== void 0)
22230
22241
  body["maxSequenceLength"] = args.max_sequence_length;
22231
- if (args.supported_modalities !== void 0)
22232
- body["supportedModalities"] = args.supported_modalities;
22233
22242
  if (args.credentials !== void 0)
22234
22243
  body["credentials"] = args.credentials;
22235
22244
  if (args.replace_labels !== void 0)
@@ -22243,7 +22252,7 @@ function registerTools() {
22243
22252
  const result = await callApi("PUT", `/v1/embedders/${encodeURIComponent(String(args.id))}`, body);
22244
22253
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22245
22254
  });
22246
- server.tool("goodmem_llms_create", "Create a new LLM", {
22255
+ server.tool("goodmem_llms_create", "Create a new LLM\n\nCreates a new LLM configuration for text generation services. LLMs represent connections to different language model API services (like OpenAI, vLLM, etc.) and include all the necessary configuration to use them for text generation. DUPLICATE DETECTION: Returns HTTP 409 Conflict (ALREADY_EXISTS) if another LLM exists with identical {owner_id, provider_type, endpoint_url, api_path, model_identifier, credentialsFingerprint} after URL canonicalization. Uniqueness is enforced per-owner. Credentials are hashed (SHA-256) for uniqueness while remaining encrypted. The api_path field defaults to '/chat/completions' if omitted. Requires CREATE_LLM_OWN permission (or CREATE_LLM_ANY for admin users).", {
22247
22256
  display_name: external_exports.string().describe("User-facing name of the LLM"),
22248
22257
  description: external_exports.string().describe("Description of the LLM").optional(),
22249
22258
  provider_type: external_exports.enum(["OPENAI", "LITELLM_PROXY", "OPEN_ROUTER", "VLLM", "OLLAMA", "LLAMA_CPP", "CUSTOM_OPENAI_COMPATIBLE"]).describe("Type of LLM provider (auto-inferred from model_identifier if omitted)").optional(),
@@ -22257,17 +22266,17 @@ function registerTools() {
22257
22266
  inlineSecret: external_exports.string().describe("Secret stored directly in GoodMem (mutually exclusive with secretRef)").optional(),
22258
22267
  secretRef: external_exports.object({
22259
22268
  uri: external_exports.string().describe("URI identifying where the secret can be resolved (e.g., vault://, env://)"),
22260
- hints: external_exports.any().describe('Optional metadata to help resolvers decode the secret (e.g., {"encoding":"base64"})').optional()
22269
+ hints: external_exports.record(external_exports.string(), external_exports.string()).describe('Optional metadata to help resolvers decode the secret (e.g., {"encoding":"base64"})').optional()
22261
22270
  }).passthrough().describe("Reference to an external secret manager entry (mutually exclusive with inlineSecret)").optional(),
22262
22271
  headerName: external_exports.string().describe("Desired HTTP header to carry the credential (defaults to Authorization)").optional(),
22263
22272
  prefix: external_exports.string().describe('Optional prefix prepended to the secret (e.g., "Bearer ")').optional()
22264
22273
  }).passthrough().describe("Configuration when kind is CREDENTIAL_KIND_API_KEY").optional(),
22265
22274
  gcpAdc: external_exports.object({
22266
- scopes: external_exports.any().describe("Additional OAuth scopes. Empty list falls back to the default cloud-platform scope.").optional(),
22275
+ scopes: external_exports.array(external_exports.string()).describe("Additional OAuth scopes. Empty list falls back to the default cloud-platform scope.").optional(),
22267
22276
  quotaProjectId: external_exports.string().describe("Optional quota project used for billing").optional()
22268
22277
  }).passthrough().describe("Configuration when kind is CREDENTIAL_KIND_GCP_ADC").optional(),
22269
- labels: external_exports.any().describe('Optional annotations to aid operators (e.g., "owner=vertex")').optional()
22270
- }).passthrough().describe("Structured credential payload describing how to authenticate with the provider. Omit for deployments that do not require credentials.").optional(),
22278
+ labels: external_exports.record(external_exports.string(), external_exports.string()).describe('Optional annotations to aid operators (e.g., "owner=vertex")').optional()
22279
+ }).passthrough().describe("Structured credential payload describing how to authenticate with the provider. Required for SaaS providers; optional for local or proxy providers.").optional(),
22271
22280
  labels: external_exports.record(external_exports.string(), external_exports.string()).describe("User-defined labels for categorization").optional(),
22272
22281
  version: external_exports.string().describe("Version information").optional(),
22273
22282
  monitoring_endpoint: external_exports.string().describe("Monitoring endpoint URL").optional(),
@@ -22281,15 +22290,15 @@ function registerTools() {
22281
22290
  }).passthrough().describe("LLM capabilities defining supported features and modes. Optional - server infers capabilities from model identifier if not provided.").optional(),
22282
22291
  default_sampling_params: external_exports.object({
22283
22292
  maxTokens: external_exports.number().int().describe("Maximum tokens to generate (>0 if set; provider-dependent limits apply)").optional(),
22284
- temperature: external_exports.number().describe("Sampling temperature 0.0-2.0 (0.0=deterministic, 2.0=highly random)").optional(),
22293
+ temperature: external_exports.number().describe("Sampling temperature; valid range depends on the configured provider").optional(),
22285
22294
  topP: external_exports.number().describe("Nucleus sampling threshold 0.0-1.0 (smaller values focus on higher probability tokens)").optional(),
22286
22295
  topK: external_exports.number().int().describe("Top-k sampling limit (>0 if set; primarily for local/open-source models)").optional(),
22287
- frequencyPenalty: external_exports.number().describe("Frequency penalty -2.0 to 2.0 (positive values reduce repetition based on frequency)").optional(),
22288
- presencePenalty: external_exports.number().describe("Presence penalty -2.0 to 2.0 (positive values encourage topic diversity)").optional(),
22289
- stopSequences: external_exports.any().describe("Generation stop sequences (\u226410 sequences; each \u2264100 chars; generation halts on exact match)").optional()
22296
+ frequencyPenalty: external_exports.number().describe("Frequency penalty; valid range depends on the configured provider").optional(),
22297
+ presencePenalty: external_exports.number().describe("Presence penalty; valid range depends on the configured provider").optional(),
22298
+ stopSequences: external_exports.array(external_exports.string()).describe("Generation stop sequences; generation halts on exact match").optional()
22290
22299
  }).passthrough().describe("Default sampling parameters for generation requests").optional(),
22291
22300
  max_context_length: external_exports.number().int().describe("Maximum context window size in tokens").optional(),
22292
- client_config: external_exports.any().describe("Provider-specific client configuration as flexible JSON structure").optional(),
22301
+ client_config: external_exports.record(external_exports.string(), external_exports.any()).describe("Provider-specific client configuration as flexible JSON structure").optional(),
22293
22302
  owner_id: external_exports.string().describe("Optional owner ID. If not provided, derived from the authentication context. Requires CREATE_LLM_ANY permission if specified.").optional(),
22294
22303
  llm_id: external_exports.string().describe("Optional client-provided UUID for idempotent creation. If not provided, server generates a new UUID. Returns ALREADY_EXISTS if ID is already in use.").optional()
22295
22304
  }, async (args) => {
@@ -22333,19 +22342,25 @@ function registerTools() {
22333
22342
  const result = await callApi("POST", `/v1/llms`, body);
22334
22343
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22335
22344
  });
22336
- server.tool("goodmem_llms_delete", "Delete an LLM", {
22345
+ server.tool("goodmem_llms_delete", "Delete an LLM\n\nPermanently deletes an LLM configuration. This operation cannot be undone and removes the LLM record and securely deletes stored credentials. IMPORTANT: This does NOT invalidate or delete any previously generated content using this LLM - existing generations remain accessible. Requires DELETE_LLM_OWN permission for LLMs you own (or DELETE_LLM_ANY for admin users).", {
22337
22346
  id: external_exports.string().describe("The unique identifier of the LLM to delete")
22338
22347
  }, async (args) => {
22348
+ if (typeof args.id !== "string" || args.id === "") {
22349
+ throw new Error("'id' must be a non-empty string");
22350
+ }
22339
22351
  await callApi("DELETE", `/v1/llms/${encodeURIComponent(String(args.id))}`, void 0);
22340
22352
  return { content: [{ type: "text", text: "Deleted successfully." }] };
22341
22353
  });
22342
- server.tool("goodmem_llms_get", "Get an LLM by ID", {
22354
+ server.tool("goodmem_llms_get", "Get an LLM by ID\n\nRetrieves the details of a specific LLM configuration by its unique identifier. Requires READ_LLM_OWN permission for LLMs you own (or READ_LLM_ANY for admin users to view any user's LLMs). This is a read-only operation with no side effects.", {
22343
22355
  id: external_exports.string().describe("The unique identifier of the LLM to retrieve")
22344
22356
  }, async (args) => {
22357
+ if (typeof args.id !== "string" || args.id === "") {
22358
+ throw new Error("'id' must be a non-empty string");
22359
+ }
22345
22360
  const result = await callApi("GET", `/v1/llms/${encodeURIComponent(String(args.id))}`, void 0);
22346
22361
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22347
22362
  });
22348
- server.tool("goodmem_llms_list", "List LLMs", {
22363
+ server.tool("goodmem_llms_list", "List LLMs\n\nRetrieves a list of LLM configurations accessible to the caller, with optional filtering. LABEL FILTERS: Label filters accept either label.<key>=<value> or label[key]=value (for example, label.environment=production or label[environment]=production). PERMISSION-BASED FILTERING: With LIST_LLM_OWN permission, you can only see your own LLMs (owner_id filter is ignored if set to another user). With LIST_LLM_ANY permission, you can see all LLMs or filter by any owner_id. This is a read-only operation with no side effects.", {
22349
22364
  owner_id: external_exports.string().describe("Filter LLMs by owner ID. With LIST_LLM_ANY permission, omitting this shows all accessible LLMs; providing it filters by that owner. With LIST_LLM_OWN permission, only your own LLMs are shown regardless of this parameter.").optional(),
22350
22365
  provider_type: external_exports.enum(["OPENAI", "LITELLM_PROXY", "OPEN_ROUTER", "VLLM", "OLLAMA", "LLAMA_CPP", "CUSTOM_OPENAI_COMPATIBLE"]).describe("Filter LLMs by provider type. Allowed values match the LLMProviderType schema.").optional(),
22351
22366
  label: external_exports.record(external_exports.string(), external_exports.string()).describe("Filter by label. Each key-value pair is sent as label[key]=value query parameter.").optional()
@@ -22362,30 +22377,30 @@ function registerTools() {
22362
22377
  const result = await callApi("GET", `/v1/llms`, void 0, query);
22363
22378
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22364
22379
  });
22365
- server.tool("goodmem_llms_update", "Update an LLM", {
22380
+ server.tool("goodmem_llms_update", "Update an LLM\n\nUpdates an existing LLM configuration including display information, endpoint configuration, model parameters, credentials, and labels. All fields are optional - only specified fields will be updated. SUPPORTED_MODALITIES UPDATE: If the array contains >=1 elements, it replaces the stored set; if empty or omitted, no change occurs and it does not count as an update by itself. IMPORTANT: providerType is IMMUTABLE after creation and cannot be changed. Requires UPDATE_LLM_OWN permission for LLMs you own (or UPDATE_LLM_ANY for admin users).", {
22366
22381
  id: external_exports.string().describe("The unique identifier of the LLM to update"),
22367
22382
  display_name: external_exports.string().describe("Update display name").optional(),
22368
22383
  description: external_exports.string().describe("Update description").optional(),
22369
22384
  endpoint_url: external_exports.string().describe("Update endpoint base URL (OpenAI-compatible base, typically ends with /v1)").optional(),
22370
22385
  api_path: external_exports.string().describe("Update API path").optional(),
22371
22386
  model_identifier: external_exports.string().describe("Update model identifier (cannot be empty)").optional(),
22372
- supported_modalities: external_exports.array(external_exports.enum(["TEXT", "IMAGE", "AUDIO", "VIDEO"])).describe("Update supported modalities (if array contains \u22651 elements, replaces stored set; if empty or omitted, no change)").optional(),
22387
+ supported_modalities: external_exports.array(external_exports.enum(["TEXT", "IMAGE", "AUDIO", "VIDEO"])).describe("Update supported modalities (if array contains >=1 elements, replaces stored set; if empty or omitted, no change and does not count as an update by itself)").optional(),
22373
22388
  credentials: external_exports.object({
22374
22389
  kind: external_exports.enum(["CREDENTIAL_KIND_UNSPECIFIED", "CREDENTIAL_KIND_API_KEY", "CREDENTIAL_KIND_GCP_ADC"]).describe("Selected credential strategy"),
22375
22390
  apiKey: external_exports.object({
22376
22391
  inlineSecret: external_exports.string().describe("Secret stored directly in GoodMem (mutually exclusive with secretRef)").optional(),
22377
22392
  secretRef: external_exports.object({
22378
22393
  uri: external_exports.string().describe("URI identifying where the secret can be resolved (e.g., vault://, env://)"),
22379
- hints: external_exports.any().describe('Optional metadata to help resolvers decode the secret (e.g., {"encoding":"base64"})').optional()
22394
+ hints: external_exports.record(external_exports.string(), external_exports.string()).describe('Optional metadata to help resolvers decode the secret (e.g., {"encoding":"base64"})').optional()
22380
22395
  }).passthrough().describe("Reference to an external secret manager entry (mutually exclusive with inlineSecret)").optional(),
22381
22396
  headerName: external_exports.string().describe("Desired HTTP header to carry the credential (defaults to Authorization)").optional(),
22382
22397
  prefix: external_exports.string().describe('Optional prefix prepended to the secret (e.g., "Bearer ")').optional()
22383
22398
  }).passthrough().describe("Configuration when kind is CREDENTIAL_KIND_API_KEY").optional(),
22384
22399
  gcpAdc: external_exports.object({
22385
- scopes: external_exports.any().describe("Additional OAuth scopes. Empty list falls back to the default cloud-platform scope.").optional(),
22400
+ scopes: external_exports.array(external_exports.string()).describe("Additional OAuth scopes. Empty list falls back to the default cloud-platform scope.").optional(),
22386
22401
  quotaProjectId: external_exports.string().describe("Optional quota project used for billing").optional()
22387
22402
  }).passthrough().describe("Configuration when kind is CREDENTIAL_KIND_GCP_ADC").optional(),
22388
- labels: external_exports.any().describe('Optional annotations to aid operators (e.g., "owner=vertex")').optional()
22403
+ labels: external_exports.record(external_exports.string(), external_exports.string()).describe('Optional annotations to aid operators (e.g., "owner=vertex")').optional()
22389
22404
  }).passthrough().describe("Update credentials").optional(),
22390
22405
  version: external_exports.string().describe("Update version information").optional(),
22391
22406
  monitoring_endpoint: external_exports.string().describe("Update monitoring endpoint URL").optional(),
@@ -22399,18 +22414,25 @@ function registerTools() {
22399
22414
  }).passthrough().describe("Update LLM capabilities (replaces entire capability set; clients MUST send all flags)").optional(),
22400
22415
  default_sampling_params: external_exports.object({
22401
22416
  maxTokens: external_exports.number().int().describe("Maximum tokens to generate (>0 if set; provider-dependent limits apply)").optional(),
22402
- temperature: external_exports.number().describe("Sampling temperature 0.0-2.0 (0.0=deterministic, 2.0=highly random)").optional(),
22417
+ temperature: external_exports.number().describe("Sampling temperature; valid range depends on the configured provider").optional(),
22403
22418
  topP: external_exports.number().describe("Nucleus sampling threshold 0.0-1.0 (smaller values focus on higher probability tokens)").optional(),
22404
22419
  topK: external_exports.number().int().describe("Top-k sampling limit (>0 if set; primarily for local/open-source models)").optional(),
22405
- frequencyPenalty: external_exports.number().describe("Frequency penalty -2.0 to 2.0 (positive values reduce repetition based on frequency)").optional(),
22406
- presencePenalty: external_exports.number().describe("Presence penalty -2.0 to 2.0 (positive values encourage topic diversity)").optional(),
22407
- stopSequences: external_exports.any().describe("Generation stop sequences (\u226410 sequences; each \u2264100 chars; generation halts on exact match)").optional()
22420
+ frequencyPenalty: external_exports.number().describe("Frequency penalty; valid range depends on the configured provider").optional(),
22421
+ presencePenalty: external_exports.number().describe("Presence penalty; valid range depends on the configured provider").optional(),
22422
+ stopSequences: external_exports.array(external_exports.string()).describe("Generation stop sequences; generation halts on exact match").optional()
22408
22423
  }).passthrough().describe("Update default sampling parameters").optional(),
22409
22424
  max_context_length: external_exports.number().int().describe("Update maximum context window size in tokens").optional(),
22410
- client_config: external_exports.any().describe("Update provider-specific client configuration (replaces entire config; no merging)").optional(),
22411
- replace_labels: external_exports.record(external_exports.string(), external_exports.string()).describe("Replace all existing labels with this set. Empty map clears all labels. Cannot be used with mergeLabels.").optional(),
22412
- merge_labels: external_exports.record(external_exports.string(), external_exports.string()).describe("Merge with existing labels: upserts with overwrite. Labels not mentioned are preserved. Cannot be used with replaceLabels.").optional()
22425
+ client_config: external_exports.record(external_exports.string(), external_exports.any()).describe("Update provider-specific client configuration (replaces entire config; no merging)").optional(),
22426
+ replace_labels: external_exports.record(external_exports.string(), external_exports.string()).describe("Replace all existing labels with this set. Empty map clears all labels. Cannot be used with merge_labels.").optional(),
22427
+ merge_labels: external_exports.record(external_exports.string(), external_exports.string()).describe("Merge with existing labels: upserts with overwrite. Labels not mentioned are preserved. Cannot be used with replace_labels.").optional()
22413
22428
  }, async (args) => {
22429
+ if (typeof args.id !== "string" || args.id === "") {
22430
+ throw new Error("'id' must be a non-empty string");
22431
+ }
22432
+ const _set = [args.replace_labels !== void 0 && args.replace_labels !== null, args.merge_labels !== void 0 && args.merge_labels !== null].filter(Boolean).length;
22433
+ if (_set > 1) {
22434
+ throw new Error("at most one of replace_labels or merge_labels may be set");
22435
+ }
22414
22436
  const body = {};
22415
22437
  if (args.display_name !== void 0)
22416
22438
  body["displayName"] = args.display_name;
@@ -22445,21 +22467,21 @@ function registerTools() {
22445
22467
  const result = await callApi("PUT", `/v1/llms/${encodeURIComponent(String(args.id))}`, body);
22446
22468
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22447
22469
  });
22448
- server.tool("goodmem_memories_batch_create", "Create multiple memories in a batch", {
22470
+ server.tool("goodmem_memories_batch_create", "Create multiple memories in one batch. EACH item in `requests` must satisfy: exactly one of `originalContent` or `originalContentB64`; if `originalContentB64`, set `contentType` (e.g. `application/pdf`); `originalContentRef` is metadata only. Returns per-item success/failure; partial failures don't abort.", {
22449
22471
  requests: external_exports.array(external_exports.object({
22450
22472
  memoryId: external_exports.string().describe("Optional client-provided UUID for the memory. If omitted, the server generates one. Returns ALREADY_EXISTS if the ID is already in use.").optional(),
22451
22473
  spaceId: external_exports.string().describe("ID of the space where this memory will be stored"),
22452
22474
  originalContent: external_exports.string().describe("Original content as plain text (use either this or originalContentB64)").optional(),
22453
22475
  originalContentB64: external_exports.string().describe("Original content as base64-encoded binary data (use either this or originalContent)").optional(),
22454
22476
  originalContentRef: external_exports.string().describe("Reference to external content location").optional(),
22455
- contentType: external_exports.string().describe("MIME type of the content"),
22456
- metadata: external_exports.any().describe("Additional metadata for the memory").optional(),
22477
+ contentType: external_exports.string().describe("MIME type of the content").optional(),
22478
+ metadata: external_exports.record(external_exports.string(), external_exports.any()).describe("Additional metadata for the memory").optional(),
22457
22479
  chunkingConfig: external_exports.object({
22458
22480
  none: external_exports.any().describe("No chunking strategy - preserve original content as single unit").optional(),
22459
22481
  recursive: external_exports.object({
22460
22482
  chunkSize: external_exports.number().int().describe("Maximum size of a chunk (should be \u2264 context window)"),
22461
22483
  chunkOverlap: external_exports.number().int().describe("Sliding overlap between chunks"),
22462
- separators: external_exports.any().describe("Hierarchical separator list (order = preference)").optional(),
22484
+ separators: external_exports.array(external_exports.string()).describe("Hierarchical separator list (order = preference)").optional(),
22463
22485
  keepStrategy: external_exports.enum(["KEEP_NONE", "KEEP_START", "KEEP_END"]).describe("How to handle separators after splitting. KEEP_NONE is deprecated and behaves as KEEP_END."),
22464
22486
  separatorIsRegex: external_exports.boolean().describe("Whether separators are regex patterns").optional(),
22465
22487
  lengthMeasurement: external_exports.enum(["CHARACTER_COUNT", "TOKEN_COUNT", "CUSTOM"]).describe("How to measure chunk length")
@@ -22471,22 +22493,38 @@ function registerTools() {
22471
22493
  lengthMeasurement: external_exports.enum(["CHARACTER_COUNT", "TOKEN_COUNT", "CUSTOM"]).describe("How to measure chunk length")
22472
22494
  }).passthrough().describe("Sentence-based chunking strategy with language detection").optional()
22473
22495
  }).passthrough().describe("Chunking strategy for this memory (if not provided, uses space default)").optional(),
22474
- extractPageImages: external_exports.boolean().describe("Optional hint to extract page images for eligible document types (for example, PDFs)").optional(),
22475
- fileField: external_exports.string().describe("Optional multipart file field name to bind binary content; required when multiple files are uploaded in a batch multipart request.").optional()
22476
- }).passthrough()).min(1).describe("Array of memory creation requests.")
22496
+ extractPageImages: external_exports.boolean().describe("Optional hint to extract page images for eligible document types (for example, PDFs)").optional()
22497
+ }).passthrough()).describe("Array of memory creation requests.")
22477
22498
  }, async (args) => {
22499
+ for (const [_i, _item] of (Array.isArray(args.requests) ? args.requests : []).entries()) {
22500
+ const _itemSet = [_item?.["originalContent"] !== void 0 && _item?.["originalContent"] !== null, _item?.["originalContentB64"] !== void 0 && _item?.["originalContentB64"] !== null].filter(Boolean).length;
22501
+ if (_itemSet !== 1) {
22502
+ throw new Error(`requests[${_i}]: exactly one of originalContent or originalContentB64 must be set`);
22503
+ }
22504
+ if (_item?.["originalContentB64"] !== void 0 && _item?.["originalContentB64"] !== null && (_item?.["contentType"] === void 0 || _item?.["contentType"] === null)) {
22505
+ throw new Error(`requests[${_i}]: 'contentType' is required when 'originalContentB64' is set (the server cannot infer MIME type from base64 bytes). Hint: pass contentType="application/pdf" or whatever matches the decoded payload`);
22506
+ }
22507
+ }
22478
22508
  const body = {};
22479
22509
  if (args.requests !== void 0)
22480
22510
  body["requests"] = args.requests;
22511
+ if (Array.isArray(body["requests"])) {
22512
+ body["requests"] = body["requests"].map((item) => {
22513
+ if (item && typeof item === "object" && item["originalContent"] !== void 0 && item["contentType"] === void 0) {
22514
+ return { ...item, contentType: "text/plain" };
22515
+ }
22516
+ return item;
22517
+ });
22518
+ }
22481
22519
  const result = await callApi("POST", `/v1/memories:batchCreate`, body);
22482
22520
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22483
22521
  });
22484
- server.tool("goodmem_memories_batch_delete", "Delete memories in batch", {
22522
+ server.tool("goodmem_memories_batch_delete", "Delete memories in batch\n\nDeletes memories using selector entries. Each selector can target either a specific memory ID or a filtered subset scoped to a specific space.", {
22485
22523
  requests: external_exports.array(external_exports.object({
22486
22524
  memoryId: external_exports.string().describe("Deletes one specific memory by UUID").optional(),
22487
22525
  filterSelector: external_exports.object({
22488
22526
  spaceId: external_exports.string().describe("Space ID scope for the filtered delete"),
22489
- statusFilter: external_exports.string().describe("Optional processing status filter (PENDING, PROCESSING, COMPLETED, FAILED)").optional(),
22527
+ statusFilter: external_exports.enum(["PENDING", "PROCESSING", "COMPLETED", "FAILED"]).describe("Optional processing status filter (PENDING, PROCESSING, COMPLETED, FAILED)").optional(),
22490
22528
  filter: external_exports.string().describe("Optional metadata filter expression").optional()
22491
22529
  }).passthrough().describe("Deletes a filtered set of memories within a specific space").optional()
22492
22530
  }).passthrough()).describe("Array of delete selectors")
@@ -22497,7 +22535,7 @@ function registerTools() {
22497
22535
  const result = await callApi("POST", `/v1/memories:batchDelete`, body);
22498
22536
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22499
22537
  });
22500
- server.tool("goodmem_memories_batch_get", "Get multiple memories by ID", {
22538
+ server.tool("goodmem_memories_batch_get", "Get multiple memories by ID\n\nRetrieves multiple memories in a single operation, with individual success/failure results.", {
22501
22539
  memory_ids: external_exports.array(external_exports.string()).describe("Array of memory IDs to retrieve"),
22502
22540
  include_content: external_exports.boolean().describe("Whether to include the original content in the response").optional(),
22503
22541
  include_processing_history: external_exports.boolean().describe("Whether to include background job processing history for each memory").optional()
@@ -22512,20 +22550,20 @@ function registerTools() {
22512
22550
  const result = await callApi("POST", `/v1/memories:batchGet`, body);
22513
22551
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22514
22552
  });
22515
- server.tool("goodmem_memories_create", "Create a new memory", {
22553
+ server.tool("goodmem_memories_create", "Create a memory from text content or base64-encoded binary content. Provide exactly one of `original_content` or `original_content_b64` per call; if `original_content_b64`, `content_type` is required. `original_content_ref` is a metadata URL pointer (the server does NOT fetch it) and may be set alongside the content source.", {
22516
22554
  memory_id: external_exports.string().describe("Optional client-provided UUID for the memory. If omitted, the server generates one. Returns ALREADY_EXISTS if the ID is already in use.").optional(),
22517
22555
  space_id: external_exports.string().describe("ID of the space where this memory will be stored"),
22518
- original_content: external_exports.string().describe("Original content as plain text (use either this or originalContentB64)").optional(),
22519
- original_content_b64: external_exports.string().describe("Original content as base64-encoded binary data (use either this or originalContent)").optional(),
22556
+ original_content: external_exports.string().describe("Original content as plain text (use either this or original_content_b64)").optional(),
22557
+ original_content_b64: external_exports.string().describe("Original content as base64-encoded binary data (use either this or original_content)").optional(),
22520
22558
  original_content_ref: external_exports.string().describe("Reference to external content location").optional(),
22521
22559
  content_type: external_exports.string().describe("MIME type of the content (defaults to text/plain when original_content is provided)").optional(),
22522
- metadata: external_exports.any().describe("Additional metadata for the memory").optional(),
22560
+ metadata: external_exports.record(external_exports.string(), external_exports.any()).describe("Additional metadata for the memory").optional(),
22523
22561
  chunking_config: external_exports.object({
22524
22562
  none: external_exports.any().describe("No chunking strategy - preserve original content as single unit").optional(),
22525
22563
  recursive: external_exports.object({
22526
22564
  chunkSize: external_exports.number().int().describe("Maximum size of a chunk (should be \u2264 context window)"),
22527
22565
  chunkOverlap: external_exports.number().int().describe("Sliding overlap between chunks"),
22528
- separators: external_exports.any().describe("Hierarchical separator list (order = preference)").optional(),
22566
+ separators: external_exports.array(external_exports.string()).describe("Hierarchical separator list (order = preference)").optional(),
22529
22567
  keepStrategy: external_exports.enum(["KEEP_NONE", "KEEP_START", "KEEP_END"]).describe("How to handle separators after splitting. KEEP_NONE is deprecated and behaves as KEEP_END."),
22530
22568
  separatorIsRegex: external_exports.boolean().describe("Whether separators are regex patterns").optional(),
22531
22569
  lengthMeasurement: external_exports.enum(["CHARACTER_COUNT", "TOKEN_COUNT", "CUSTOM"]).describe("How to measure chunk length")
@@ -22539,6 +22577,13 @@ function registerTools() {
22539
22577
  }).passthrough().describe("Chunking strategy for this memory (if not provided, uses space default)").optional(),
22540
22578
  extract_page_images: external_exports.boolean().describe("Optional hint to extract page images for eligible document types (for example, PDFs)").optional()
22541
22579
  }, async (args) => {
22580
+ const _set = [args.original_content !== void 0 && args.original_content !== null, args.original_content_b64 !== void 0 && args.original_content_b64 !== null].filter(Boolean).length;
22581
+ if (_set !== 1) {
22582
+ throw new Error("exactly one of original_content or original_content_b64 must be set");
22583
+ }
22584
+ if (args.original_content_b64 !== void 0 && args.original_content_b64 !== null && (args.content_type === void 0 || args.content_type === null)) {
22585
+ throw new Error('`content_type` is required when `original_content_b64` is set (the server cannot infer MIME type from base64 bytes). Hint: pass content_type="application/pdf" or whatever matches the decoded payload');
22586
+ }
22542
22587
  const body = {};
22543
22588
  if (args.memory_id !== void 0)
22544
22589
  body["memoryId"] = args.memory_id;
@@ -22564,17 +22609,23 @@ function registerTools() {
22564
22609
  const result = await callApi("POST", `/v1/memories`, body);
22565
22610
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22566
22611
  });
22567
- server.tool("goodmem_memories_delete", "Delete a memory", {
22612
+ server.tool("goodmem_memories_delete", "Delete a memory\n\nPermanently deletes a memory and its associated chunks. This operation cannot be undone and immediately removes the memory record from the database. IDEMPOTENCY: This operation is safe to retry - may return NOT_FOUND if the memory was already deleted or never existed. Requires DELETE_MEMORY_OWN permission for memories in spaces you own (or DELETE_MEMORY_ANY for admin users to delete any memory). Side effects include permanent removal of the memory record and all associated chunk data.", {
22568
22613
  id: external_exports.string().describe("The UUID of the memory to delete")
22569
22614
  }, async (args) => {
22615
+ if (typeof args.id !== "string" || args.id === "") {
22616
+ throw new Error("'id' must be a non-empty string");
22617
+ }
22570
22618
  await callApi("DELETE", `/v1/memories/${encodeURIComponent(String(args.id))}`, void 0);
22571
22619
  return { content: [{ type: "text", text: "Deleted successfully." }] };
22572
22620
  });
22573
- server.tool("goodmem_memories_get", "Get a memory by ID", {
22621
+ server.tool("goodmem_memories_get", "Get a memory by ID\n\nRetrieves a single memory by its ID. PERMISSION CLARIFICATION: With READ_MEMORY_OWN permission, access is granted if you own the parent space OR if the parent space is public (public_read=true). With READ_MEMORY_ANY permission, you can access any memory regardless of ownership. This is a read-only operation with no side effects and is safe to retry. Returns NOT_FOUND if the memory or its parent space does not exist.", {
22574
22622
  id: external_exports.string().describe("The UUID of the memory to retrieve"),
22575
22623
  include_content: external_exports.boolean().describe("Whether to include the original content in the response (defaults to false).").optional(),
22576
22624
  include_processing_history: external_exports.boolean().describe("Whether to include background job processing history in the response (defaults to false).").optional()
22577
22625
  }, async (args) => {
22626
+ if (typeof args.id !== "string" || args.id === "") {
22627
+ throw new Error("'id' must be a non-empty string");
22628
+ }
22578
22629
  const query = {};
22579
22630
  if (args.include_content !== void 0)
22580
22631
  query["includeContent"] = String(args.include_content);
@@ -22583,17 +22634,20 @@ function registerTools() {
22583
22634
  const result = await callApi("GET", `/v1/memories/${encodeURIComponent(String(args.id))}`, void 0, query);
22584
22635
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22585
22636
  });
22586
- server.tool("goodmem_memories_list", "List memories in a space", {
22637
+ server.tool("goodmem_memories_list", "Lists memories within a given space. Results are paginated; pass the response's `next_token` back as a query parameter to fetch subsequent pages.", {
22587
22638
  space_id: external_exports.string().describe("The UUID of the space containing the memories"),
22588
22639
  include_content: external_exports.boolean().describe("Whether to include the original content in the response (defaults to false).").optional(),
22589
22640
  include_processing_history: external_exports.boolean().describe("Whether to include background job processing history in the response (defaults to false).").optional(),
22590
- status_filter: external_exports.string().describe("Filter memories by processing status (PENDING, PROCESSING, COMPLETED, FAILED).").optional(),
22641
+ status_filter: external_exports.enum(["PENDING", "PROCESSING", "COMPLETED", "FAILED"]).describe("Filter memories by processing status (PENDING, PROCESSING, COMPLETED, FAILED).").optional(),
22591
22642
  filter: external_exports.string().describe("Optional metadata filter expression for list results").optional(),
22592
22643
  max_results: external_exports.number().int().describe("Maximum number of results per page (defaults to 50, clamped to [1, 500]).").optional(),
22593
22644
  next_token: external_exports.string().describe("Opaque pagination token for the next page. URL-safe Base64 without padding; do not parse or construct it.").optional(),
22594
- sort_by: external_exports.string().describe("Field to sort by (e.g., 'created_at').").optional(),
22645
+ sort_by: external_exports.enum(["created_at", "updated_at", "content_type", "processing_status"]).describe("Field to sort by (e.g., 'created_at').").optional(),
22595
22646
  sort_order: external_exports.enum(["ASCENDING", "DESCENDING", "SORT_ORDER_UNSPECIFIED"]).describe("Sort direction (ASCENDING or DESCENDING).").optional()
22596
22647
  }, async (args) => {
22648
+ if (typeof args.space_id !== "string" || args.space_id === "") {
22649
+ throw new Error("'space_id' must be a non-empty string");
22650
+ }
22597
22651
  const query = {};
22598
22652
  if (args.include_content !== void 0)
22599
22653
  query["includeContent"] = String(args.include_content);
@@ -22614,7 +22668,7 @@ function registerTools() {
22614
22668
  const result = await callApi("GET", `/v1/spaces/${encodeURIComponent(String(args.space_id))}/memories`, void 0, query);
22615
22669
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22616
22670
  });
22617
- server.tool("goodmem_memories_pages", "List memory page images", {
22671
+ server.tool("goodmem_memories_pages", "List memory page images\n\nLists extracted page-image metadata for a memory with optional filters and pagination.", {
22618
22672
  id: external_exports.string().describe("Memory UUID"),
22619
22673
  start_page_index: external_exports.number().int().describe("Optional lower bound for returned page indices, inclusive.").optional(),
22620
22674
  end_page_index: external_exports.number().int().describe("Optional upper bound for returned page indices, inclusive.").optional(),
@@ -22623,6 +22677,9 @@ function registerTools() {
22623
22677
  max_results: external_exports.number().int().describe("Maximum number of results per page.").optional(),
22624
22678
  next_token: external_exports.string().describe("Opaque pagination token for the next page. Do not parse or construct it.").optional()
22625
22679
  }, async (args) => {
22680
+ if (typeof args.id !== "string" || args.id === "") {
22681
+ throw new Error("'id' must be a non-empty string");
22682
+ }
22626
22683
  const query = {};
22627
22684
  if (args.start_page_index !== void 0)
22628
22685
  query["startPageIndex"] = String(args.start_page_index);
@@ -22639,7 +22696,7 @@ function registerTools() {
22639
22696
  const result = await callApi("GET", `/v1/memories/${encodeURIComponent(String(args.id))}/pages`, void 0, query);
22640
22697
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22641
22698
  });
22642
- server.tool("goodmem_memories_retrieve", "Advanced semantic memory retrieval with JSON", {
22699
+ server.tool("goodmem_memories_retrieve", "Performs a streaming semantic search across one or more memory spaces and returns matching chunks ranked by relevance as well as LLM-postprocessed results (like summarization, question-answering, etc.).", {
22643
22700
  message: external_exports.string().describe("Primary query/message for semantic search."),
22644
22701
  context: external_exports.array(external_exports.object({
22645
22702
  text: external_exports.string().describe("Text content for this context item.").optional(),
@@ -22650,12 +22707,18 @@ function registerTools() {
22650
22707
  }).passthrough()).describe("Optional context items (text or binary) to provide additional context for the search.").optional(),
22651
22708
  space_keys: external_exports.array(external_exports.object({
22652
22709
  spaceId: external_exports.string().describe("The UUID for the space to search."),
22653
- embedderWeights: external_exports.any().describe("Optional per-embedder weight overrides for this space. If not specified, database defaults are used.").optional(),
22710
+ embedderWeights: external_exports.array(external_exports.object({
22711
+ embedderId: external_exports.string().describe("The UUID for the embedder."),
22712
+ weight: external_exports.number().describe("The weight to apply to this embedder's results. Can be positive, negative, or zero.")
22713
+ }).passthrough()).describe("Optional per-embedder weight overrides for this space. If not specified, database defaults are used.").optional(),
22654
22714
  filter: external_exports.string().describe("Optional filter expression that must evaluate to true for memories in this space.").optional()
22655
22715
  }).passthrough()).describe("List of spaces to search with optional per-embedder weight overrides."),
22656
22716
  requested_size: external_exports.number().int().describe("Maximum number of memories to retrieve.").optional(),
22657
- fetch_memory: external_exports.boolean().describe("Whether to include streamed memory-definition records in the response. Defaults to true when omitted. These records include memory metadata and audit fields, but omit originalContent unless fetchMemoryContent is true.").optional(),
22658
- fetch_memory_content: external_exports.boolean().describe("Whether streamed memory-definition records include originalContent bytes. Requires fetchMemory=true. Defaults to false when omitted.").optional(),
22717
+ output_budget: external_exports.object({
22718
+ tokens: external_exports.number().int().describe("Token count for the budget. Must be positive.")
22719
+ }).passthrough().describe("Optional soft cap for generated retrieve replies. When set to a positive token count, chat post-processing uses this value as the LLM completion-token budget.").optional(),
22720
+ fetch_memory: external_exports.boolean().describe("Whether to include streamed memory-definition records in the response. Defaults to true when omitted. These records include memory metadata and audit fields, but omit originalContent unless fetch_memory_content is true.").optional(),
22721
+ fetch_memory_content: external_exports.boolean().describe("Whether streamed memory-definition records include originalContent bytes. Requires fetch_memory=true. Defaults to false when omitted.").optional(),
22659
22722
  hnsw: external_exports.object({
22660
22723
  efSearch: external_exports.number().int().describe("HNSW candidate list size (1..1000).").optional(),
22661
22724
  iterativeScan: external_exports.enum(["ITERATIVE_SCAN_UNSPECIFIED", "ITERATIVE_SCAN_OFF", "ITERATIVE_SCAN_RELAXED_ORDER", "ITERATIVE_SCAN_STRICT_ORDER"]).describe("HNSW iterative scan mode. Use POST retrieve for this advanced tuning control.").optional(),
@@ -22664,12 +22727,12 @@ function registerTools() {
22664
22727
  }).passthrough().describe("Optional request-level HNSW tuning overrides. Advanced usage; available on POST retrieve.").optional(),
22665
22728
  post_processor: external_exports.object({
22666
22729
  name: external_exports.string().describe("Fully qualified factory class name of the post-processor to apply."),
22667
- config: external_exports.any().describe("Configuration parameters for the post-processor. Fields depend on the selected processor; see the linked documentation for the built-in ChatPostProcessor schema.").optional()
22730
+ config: external_exports.record(external_exports.string(), external_exports.any()).describe("Configuration parameters for the post-processor. Fields depend on the selected processor; see the linked documentation for the built-in ChatPostProcessor schema.").optional()
22668
22731
  }).passthrough().describe("Optional post-processor configuration to transform retrieval results.").optional(),
22669
22732
  logging: external_exports.object({
22670
- enabled: external_exports.boolean().describe("Opts this request in to durable server-side request logging."),
22671
- callerAttributes: external_exports.any().describe("Optional flat scalar attributes attached to the persisted log row. Supported value types are string, integer, floating-point, and boolean.").optional()
22672
- }).passthrough().describe("Optional durable request logging block for POST retrieve requests. Supply logging.enabled=true to opt in, and optionally attach flat scalar logging.callerAttributes for persisted experiment or tracing metadata.").optional(),
22733
+ enabled: external_exports.boolean().describe("Opts this request in to durable server-side request logging. False or omitted does not opt out of admin policy logging.").optional(),
22734
+ callerAttributes: external_exports.record(external_exports.string(), external_exports.any()).describe("Optional flat scalar attributes attached if this request is persisted by caller opt-in or admin policy logging. Supported value types are string, integer, floating-point, and boolean. At most 32 entries are accepted.").optional()
22735
+ }).passthrough().describe("Optional durable request logging block for POST retrieve requests. Set logging.enabled=true to opt in, or supply flat scalar logging.callerAttributes to attach if caller opt-in or an admin policy logs the request.").optional(),
22673
22736
  timeout_sec: external_exports.number().int().describe("Maximum seconds to wait for the streaming response (default: 200).").optional()
22674
22737
  }, async (args) => {
22675
22738
  const body = {};
@@ -22681,6 +22744,8 @@ function registerTools() {
22681
22744
  body["spaceKeys"] = args.space_keys;
22682
22745
  if (args.requested_size !== void 0)
22683
22746
  body["requestedSize"] = args.requested_size;
22747
+ if (args.output_budget !== void 0)
22748
+ body["outputBudget"] = args.output_budget;
22684
22749
  if (args.fetch_memory !== void 0)
22685
22750
  body["fetchMemory"] = args.fetch_memory;
22686
22751
  if (args.fetch_memory_content !== void 0)
@@ -22694,7 +22759,7 @@ function registerTools() {
22694
22759
  const result = await callApiNdjson("POST", `/v1/memories:retrieve`, body, void 0, args.timeout_sec);
22695
22760
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22696
22761
  });
22697
- server.tool("goodmem_ocr_document", "Run OCR on a document or image", {
22762
+ server.tool("goodmem_ocr_document", "Run OCR on a document or image. Pass base64-encoded document bytes as `content`. The server auto-detects format; set `format` explicitly only if needed.", {
22698
22763
  content: external_exports.string().describe("Base64-encoded document bytes"),
22699
22764
  format: external_exports.enum(["AUTO", "PDF", "TIFF", "PNG", "JPEG", "BMP"]).describe("Input format hint (AUTO, PDF, TIFF, PNG, JPEG, BMP)").optional(),
22700
22765
  include_raw_json: external_exports.boolean().describe("Include raw OCR JSON payload in the response").optional(),
@@ -22718,7 +22783,79 @@ function registerTools() {
22718
22783
  const result = await callApi("POST", `/v1/ocr:document`, body);
22719
22784
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22720
22785
  });
22721
- server.tool("goodmem_rerankers_create", "Create a new reranker", {
22786
+ server.tool("goodmem_ping_once", "Run a single ping probe\n\nRuns a single ping probe and returns the probe result.", {
22787
+ target_id: external_exports.string().describe("Target resource ID (UUID)"),
22788
+ target_type_hint: external_exports.enum(["TARGET_TYPE_UNSPECIFIED", "EMBEDDER", "RERANKER", "LLM"]).describe("Optional hint for the target resource type").optional(),
22789
+ payload_type: external_exports.enum(["PAYLOAD_TYPE_UNSPECIFIED", "TEXT", "JSON", "BINARY"]).describe("Desired payload type (defaults to provider-specific value)").optional(),
22790
+ payload: external_exports.string().describe("Explicit UTF-8 payload to send with the probe (mutually exclusive with payload_size_bytes)").optional(),
22791
+ payload_size_bytes: external_exports.number().int().describe("Synthetic payload size in bytes (mutually exclusive with payload)").optional(),
22792
+ timeout_ms: external_exports.number().int().describe("Per-probe timeout in milliseconds (0 uses server default)").optional()
22793
+ }, async (args) => {
22794
+ const _set = [args.payload !== void 0 && args.payload !== null, args.payload_size_bytes !== void 0 && args.payload_size_bytes !== null].filter(Boolean).length;
22795
+ if (_set > 1) {
22796
+ throw new Error("at most one of payload or payload_size_bytes may be set");
22797
+ }
22798
+ const body = {};
22799
+ if (args.target_id !== void 0)
22800
+ body["targetId"] = args.target_id;
22801
+ if (args.target_type_hint !== void 0)
22802
+ body["targetTypeHint"] = args.target_type_hint;
22803
+ if (args.payload_type !== void 0)
22804
+ body["payloadType"] = args.payload_type;
22805
+ if (args.payload !== void 0)
22806
+ body["payload"] = args.payload;
22807
+ if (args.payload_size_bytes !== void 0)
22808
+ body["payloadSizeBytes"] = args.payload_size_bytes;
22809
+ if (args.timeout_ms !== void 0)
22810
+ body["timeoutMs"] = args.timeout_ms;
22811
+ const result = await callApi("POST", `/v1/ping:once`, body, void 0, (args.timeout_ms ?? 5e3) + 3e4);
22812
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22813
+ });
22814
+ server.tool("goodmem_ping_stream", "Stream ping probe results\n\nOpens a streaming ping session and returns per-probe results plus a terminal summary.", {
22815
+ target_id: external_exports.string().describe("Target resource ID (UUID)"),
22816
+ target_type_hint: external_exports.enum(["TARGET_TYPE_UNSPECIFIED", "EMBEDDER", "RERANKER", "LLM"]).describe("Optional hint for the target resource type").optional(),
22817
+ count: external_exports.number().int().describe("Number of probes to run (0 uses server default)").optional(),
22818
+ interval_ms: external_exports.number().int().describe("Delay between probes in milliseconds (0 uses server default)").optional(),
22819
+ timeout_ms: external_exports.number().int().describe("Per-probe timeout in milliseconds (0 uses server default)").optional(),
22820
+ payload_type: external_exports.enum(["PAYLOAD_TYPE_UNSPECIFIED", "TEXT", "JSON", "BINARY"]).describe("Desired payload type (defaults to provider-specific value)").optional(),
22821
+ payload: external_exports.string().describe("Explicit UTF-8 payload to send with each probe (mutually exclusive with payload_size_bytes)").optional(),
22822
+ payload_size_bytes: external_exports.number().int().describe("Synthetic payload size in bytes (mutually exclusive with payload)").optional(),
22823
+ max_in_flight: external_exports.number().int().describe("Maximum concurrent probes (defaults to 1)").optional(),
22824
+ jitter: external_exports.boolean().describe("Add jitter to probe scheduling").optional(),
22825
+ labels: external_exports.record(external_exports.string(), external_exports.string()).describe("Optional labels to attach to the ping session").optional(),
22826
+ timeout_sec: external_exports.number().int().describe("Maximum seconds to wait for the streaming response (default: 200).").optional()
22827
+ }, async (args) => {
22828
+ const _set = [args.payload !== void 0 && args.payload !== null, args.payload_size_bytes !== void 0 && args.payload_size_bytes !== null].filter(Boolean).length;
22829
+ if (_set > 1) {
22830
+ throw new Error("at most one of payload or payload_size_bytes may be set");
22831
+ }
22832
+ const body = {};
22833
+ if (args.target_id !== void 0)
22834
+ body["targetId"] = args.target_id;
22835
+ if (args.target_type_hint !== void 0)
22836
+ body["targetTypeHint"] = args.target_type_hint;
22837
+ if (args.count !== void 0)
22838
+ body["count"] = args.count;
22839
+ if (args.interval_ms !== void 0)
22840
+ body["intervalMs"] = args.interval_ms;
22841
+ if (args.timeout_ms !== void 0)
22842
+ body["timeoutMs"] = args.timeout_ms;
22843
+ if (args.payload_type !== void 0)
22844
+ body["payloadType"] = args.payload_type;
22845
+ if (args.payload !== void 0)
22846
+ body["payload"] = args.payload;
22847
+ if (args.payload_size_bytes !== void 0)
22848
+ body["payloadSizeBytes"] = args.payload_size_bytes;
22849
+ if (args.max_in_flight !== void 0)
22850
+ body["maxInFlight"] = args.max_in_flight;
22851
+ if (args.jitter !== void 0)
22852
+ body["jitter"] = args.jitter;
22853
+ if (args.labels !== void 0)
22854
+ body["labels"] = args.labels;
22855
+ const result = await callApiNdjson("POST", `/v1/ping:stream`, body, void 0, args.timeout_sec ?? Math.ceil(((args.count || 100) * (args.interval_ms || 1e3) + (args.timeout_ms || 5e3) + 6e4) / 1e3));
22856
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22857
+ });
22858
+ server.tool("goodmem_rerankers_create", "Create a new reranker\n\nCreates a new reranker configuration for ranking search results. Rerankers represent connections to different reranking API services (like TEI, OpenAI, etc.) and include all the necessary configuration to use them for result ranking. DUPLICATE DETECTION: Returns HTTP 409 Conflict (ALREADY_EXISTS) if another reranker exists with identical {owner_id, provider_type, endpoint_url, api_path, model_identifier, credentialsFingerprint} after URL canonicalization. Uniqueness is enforced per-owner, allowing different users to have identical configurations. Credentials are hashed (SHA-256) for uniqueness while remaining encrypted. DEFAULTS: api_path defaults to '/v2/rerank' for Cohere and '/rerank' for other providers if omitted; supported_modalities defaults to [TEXT] if omitted. Requires CREATE_RERANKER_OWN permission (or CREATE_RERANKER_ANY for admin users). This operation is NOT idempotent - each request creates a new reranker record.", {
22722
22859
  display_name: external_exports.string().describe("User-facing name of the reranker"),
22723
22860
  description: external_exports.string().describe("Description of the reranker").optional(),
22724
22861
  provider_type: external_exports.enum(["OPENAI", "VLLM", "TEI", "LLAMA_CPP", "VOYAGE", "COHERE", "JINA"]).describe("Type of reranking provider (auto-inferred from model_identifier if omitted)").optional(),
@@ -22732,17 +22869,17 @@ function registerTools() {
22732
22869
  inlineSecret: external_exports.string().describe("Secret stored directly in GoodMem (mutually exclusive with secretRef)").optional(),
22733
22870
  secretRef: external_exports.object({
22734
22871
  uri: external_exports.string().describe("URI identifying where the secret can be resolved (e.g., vault://, env://)"),
22735
- hints: external_exports.any().describe('Optional metadata to help resolvers decode the secret (e.g., {"encoding":"base64"})').optional()
22872
+ hints: external_exports.record(external_exports.string(), external_exports.string()).describe('Optional metadata to help resolvers decode the secret (e.g., {"encoding":"base64"})').optional()
22736
22873
  }).passthrough().describe("Reference to an external secret manager entry (mutually exclusive with inlineSecret)").optional(),
22737
22874
  headerName: external_exports.string().describe("Desired HTTP header to carry the credential (defaults to Authorization)").optional(),
22738
22875
  prefix: external_exports.string().describe('Optional prefix prepended to the secret (e.g., "Bearer ")').optional()
22739
22876
  }).passthrough().describe("Configuration when kind is CREDENTIAL_KIND_API_KEY").optional(),
22740
22877
  gcpAdc: external_exports.object({
22741
- scopes: external_exports.any().describe("Additional OAuth scopes. Empty list falls back to the default cloud-platform scope.").optional(),
22878
+ scopes: external_exports.array(external_exports.string()).describe("Additional OAuth scopes. Empty list falls back to the default cloud-platform scope.").optional(),
22742
22879
  quotaProjectId: external_exports.string().describe("Optional quota project used for billing").optional()
22743
22880
  }).passthrough().describe("Configuration when kind is CREDENTIAL_KIND_GCP_ADC").optional(),
22744
- labels: external_exports.any().describe('Optional annotations to aid operators (e.g., "owner=vertex")').optional()
22745
- }).passthrough().describe("Structured credential payload describing how to authenticate with the provider. Required for SaaS providers; optional for local deployments.").optional(),
22881
+ labels: external_exports.record(external_exports.string(), external_exports.string()).describe('Optional annotations to aid operators (e.g., "owner=vertex")').optional()
22882
+ }).passthrough().describe("Structured credential payload describing how to authenticate with the provider. Required for SaaS providers; optional for local or proxy providers.").optional(),
22746
22883
  labels: external_exports.record(external_exports.string(), external_exports.string()).describe("User-defined labels for categorization").optional(),
22747
22884
  version: external_exports.string().describe("Version information").optional(),
22748
22885
  monitoring_endpoint: external_exports.string().describe("Monitoring endpoint URL").optional(),
@@ -22781,19 +22918,25 @@ function registerTools() {
22781
22918
  const result = await callApi("POST", `/v1/rerankers`, body);
22782
22919
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22783
22920
  });
22784
- server.tool("goodmem_rerankers_delete", "Delete a reranker", {
22921
+ server.tool("goodmem_rerankers_delete", "Delete a reranker\n\nPermanently deletes a reranker configuration. This operation cannot be undone and immediately removes the reranker record from the database. SIDE EFFECTS: Invalidates any cached references to this reranker; does not affect historical usage data or audit logs. Requires DELETE_RERANKER_OWN permission for rerankers you own (or DELETE_RERANKER_ANY for admin users). This operation is safe to retry - may return NOT_FOUND if already deleted.", {
22785
22922
  id: external_exports.string().describe("The unique identifier of the reranker to delete")
22786
22923
  }, async (args) => {
22924
+ if (typeof args.id !== "string" || args.id === "") {
22925
+ throw new Error("'id' must be a non-empty string");
22926
+ }
22787
22927
  await callApi("DELETE", `/v1/rerankers/${encodeURIComponent(String(args.id))}`, void 0);
22788
22928
  return { content: [{ type: "text", text: "Deleted successfully." }] };
22789
22929
  });
22790
- server.tool("goodmem_rerankers_get", "Get a reranker by ID", {
22930
+ server.tool("goodmem_rerankers_get", "Get a reranker by ID\n\nRetrieves the details of a specific reranker configuration by its unique identifier. Response payloads include stored credentials, matching gRPC response semantics. Requires READ_RERANKER_OWN permission for rerankers you own (or READ_RERANKER_ANY for admin users). This is a read-only operation with no side effects and is safe to retry.", {
22791
22931
  id: external_exports.string().describe("The unique identifier of the reranker to retrieve")
22792
22932
  }, async (args) => {
22933
+ if (typeof args.id !== "string" || args.id === "") {
22934
+ throw new Error("'id' must be a non-empty string");
22935
+ }
22793
22936
  const result = await callApi("GET", `/v1/rerankers/${encodeURIComponent(String(args.id))}`, void 0);
22794
22937
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22795
22938
  });
22796
- server.tool("goodmem_rerankers_list", "List rerankers", {
22939
+ server.tool("goodmem_rerankers_list", "List rerankers\n\nRetrieves a list of reranker configurations accessible to the caller, with optional filtering. IMPORTANT: Pagination is NOT supported - all matching results are returned. Results are ordered by created_at descending. Responses include stored credentials, matching gRPC response semantics. LABEL FILTERS: Label filters accept either label.<key>=<value> or label[key]=value (for example, label.environment=production or label[environment]=production). PERMISSION-BASED FILTERING: With LIST_RERANKER_OWN permission, only your own rerankers are shown. With LIST_RERANKER_ANY permission, you can see all rerankers or filter by any owner_id. Specifying owner_id without LIST_RERANKER_ANY permission returns PERMISSION_DENIED.", {
22797
22940
  owner_id: external_exports.string().describe("Filter rerankers by owner ID. With LIST_RERANKER_ANY permission, omitting this shows all accessible rerankers; providing it filters by that owner. With LIST_RERANKER_OWN permission, only your own rerankers are shown regardless of this parameter (PERMISSION_DENIED if set to another user).").optional(),
22798
22941
  provider_type: external_exports.enum(["OPENAI", "VLLM", "TEI", "LLAMA_CPP", "VOYAGE", "COHERE", "JINA"]).describe("Filter rerankers by provider type. Allowed values match the ProviderType schema.").optional(),
22799
22942
  label: external_exports.record(external_exports.string(), external_exports.string()).describe("Filter by label. Each key-value pair is sent as label[key]=value query parameter.").optional()
@@ -22810,36 +22953,43 @@ function registerTools() {
22810
22953
  const result = await callApi("GET", `/v1/rerankers`, void 0, query);
22811
22954
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22812
22955
  });
22813
- server.tool("goodmem_rerankers_update", "Update a reranker", {
22956
+ server.tool("goodmem_rerankers_update", "Update a reranker\n\nUpdates an existing reranker configuration including display information, endpoint configuration, model parameters, credentials, and labels. All fields are optional - only specified fields will be updated. IMMUTABLE FIELDS: providerType and ownerId cannot be changed after creation. SUPPORTED_MODALITIES UPDATE: If the array contains >=1 elements, it replaces the stored set; if empty or omitted, no change occurs and it does not count as an update by itself. Returns ALREADY_EXISTS if update would create duplicate with same {ownerId, providerType, endpoint_url, api_path, model_identifier, credentialsFingerprint} after URL canonicalization (HTTP 409 Conflict / ALREADY_EXISTS). Requires UPDATE_RERANKER_OWN permission for rerankers you own (or UPDATE_RERANKER_ANY for admin users). This operation is idempotent.", {
22814
22957
  id: external_exports.string().describe("The unique identifier of the reranker to update"),
22815
22958
  display_name: external_exports.string().describe("User-facing name of the reranker").optional(),
22816
22959
  description: external_exports.string().describe("Description of the reranker").optional(),
22817
22960
  endpoint_url: external_exports.string().describe("API endpoint URL").optional(),
22818
22961
  api_path: external_exports.string().describe("API path for reranking request").optional(),
22819
22962
  model_identifier: external_exports.string().describe("Model identifier").optional(),
22820
- supported_modalities: external_exports.array(external_exports.enum(["TEXT", "IMAGE", "AUDIO", "VIDEO"])).describe("Supported content modalities").optional(),
22963
+ supported_modalities: external_exports.array(external_exports.enum(["TEXT", "IMAGE", "AUDIO", "VIDEO"])).describe("Update supported modalities (if array contains >=1 elements, replaces stored set; if empty or omitted, no change and does not count as an update by itself)").optional(),
22821
22964
  credentials: external_exports.object({
22822
22965
  kind: external_exports.enum(["CREDENTIAL_KIND_UNSPECIFIED", "CREDENTIAL_KIND_API_KEY", "CREDENTIAL_KIND_GCP_ADC"]).describe("Selected credential strategy"),
22823
22966
  apiKey: external_exports.object({
22824
22967
  inlineSecret: external_exports.string().describe("Secret stored directly in GoodMem (mutually exclusive with secretRef)").optional(),
22825
22968
  secretRef: external_exports.object({
22826
22969
  uri: external_exports.string().describe("URI identifying where the secret can be resolved (e.g., vault://, env://)"),
22827
- hints: external_exports.any().describe('Optional metadata to help resolvers decode the secret (e.g., {"encoding":"base64"})').optional()
22970
+ hints: external_exports.record(external_exports.string(), external_exports.string()).describe('Optional metadata to help resolvers decode the secret (e.g., {"encoding":"base64"})').optional()
22828
22971
  }).passthrough().describe("Reference to an external secret manager entry (mutually exclusive with inlineSecret)").optional(),
22829
22972
  headerName: external_exports.string().describe("Desired HTTP header to carry the credential (defaults to Authorization)").optional(),
22830
22973
  prefix: external_exports.string().describe('Optional prefix prepended to the secret (e.g., "Bearer ")').optional()
22831
22974
  }).passthrough().describe("Configuration when kind is CREDENTIAL_KIND_API_KEY").optional(),
22832
22975
  gcpAdc: external_exports.object({
22833
- scopes: external_exports.any().describe("Additional OAuth scopes. Empty list falls back to the default cloud-platform scope.").optional(),
22976
+ scopes: external_exports.array(external_exports.string()).describe("Additional OAuth scopes. Empty list falls back to the default cloud-platform scope.").optional(),
22834
22977
  quotaProjectId: external_exports.string().describe("Optional quota project used for billing").optional()
22835
22978
  }).passthrough().describe("Configuration when kind is CREDENTIAL_KIND_GCP_ADC").optional(),
22836
- labels: external_exports.any().describe('Optional annotations to aid operators (e.g., "owner=vertex")').optional()
22979
+ labels: external_exports.record(external_exports.string(), external_exports.string()).describe('Optional annotations to aid operators (e.g., "owner=vertex")').optional()
22837
22980
  }).passthrough().describe("Structured credential payload describing how to authenticate with the provider. Omit to keep existing credentials.").optional(),
22838
- replace_labels: external_exports.record(external_exports.string(), external_exports.string()).describe("Replace all existing labels with these (mutually exclusive with mergeLabels)").optional(),
22839
- merge_labels: external_exports.record(external_exports.string(), external_exports.string()).describe("Merge these labels with existing ones (mutually exclusive with replaceLabels)").optional(),
22981
+ replace_labels: external_exports.record(external_exports.string(), external_exports.string()).describe("Replace all existing labels with these (mutually exclusive with merge_labels)").optional(),
22982
+ merge_labels: external_exports.record(external_exports.string(), external_exports.string()).describe("Merge these labels with existing ones (mutually exclusive with replace_labels)").optional(),
22840
22983
  version: external_exports.string().describe("Version information").optional(),
22841
22984
  monitoring_endpoint: external_exports.string().describe("Monitoring endpoint URL").optional()
22842
22985
  }, async (args) => {
22986
+ if (typeof args.id !== "string" || args.id === "") {
22987
+ throw new Error("'id' must be a non-empty string");
22988
+ }
22989
+ const _set = [args.replace_labels !== void 0 && args.replace_labels !== null, args.merge_labels !== void 0 && args.merge_labels !== null].filter(Boolean).length;
22990
+ if (_set > 1) {
22991
+ throw new Error("at most one of replace_labels or merge_labels may be set");
22992
+ }
22843
22993
  const body = {};
22844
22994
  if (args.display_name !== void 0)
22845
22995
  body["displayName"] = args.display_name;
@@ -22866,13 +23016,13 @@ function registerTools() {
22866
23016
  const result = await callApi("PUT", `/v1/rerankers/${encodeURIComponent(String(args.id))}`, body);
22867
23017
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22868
23018
  });
22869
- server.tool("goodmem_spaces_create", "Create a new Space", {
23019
+ server.tool("goodmem_spaces_create", "Create a new Space\n\nCreates a new space with the provided name, labels, and embedder configuration. A space is a container for organizing related memories. OWNER DEFAULTS: Owner defaults to authenticated user unless owner_id is provided (requires CREATE_SPACE_ANY if differs). EMBEDDER REQUIREMENTS: At least one embedder configuration must be specified. DUPLICATE DETECTION: Returns ALREADY_EXISTS if another space exists with identical {owner_id, name} (case-sensitive). Requires CREATE_SPACE_OWN permission (or CREATE_SPACE_ANY for admin users). This operation is NOT idempotent.", {
22870
23020
  name: external_exports.string().describe("The desired name for the space. Must be unique within the user's scope."),
22871
23021
  labels: external_exports.record(external_exports.string(), external_exports.string()).describe("A set of key-value pairs to categorize or tag the space. Used for filtering and organizational purposes.").optional(),
22872
23022
  space_embedders: external_exports.array(external_exports.object({
22873
23023
  embedderId: external_exports.string().describe("The UUID for the embedder to associate with the space."),
22874
23024
  defaultRetrievalWeight: external_exports.number().describe("Relative weight for this embedder used by default during retrieval. If omitted, defaults to 1.0; values need not sum to 1 and can be overridden per request.").optional()
22875
- }).passthrough()).min(1).describe("List of embedder configurations to associate with this space. At least one embedder configuration is required. Each specifies an embedder ID and a relative default retrieval weight used when no per-request overrides are provided."),
23025
+ }).passthrough()).describe("List of embedder configurations to associate with this space. At least one embedder configuration is required. Each specifies an embedder ID and a relative default retrieval weight used when no per-request overrides are provided."),
22876
23026
  public_read: external_exports.boolean().describe("Indicates if the space and its memories can be read by unauthenticated users or users other than the owner. Defaults to false.").optional(),
22877
23027
  owner_id: external_exports.string().describe("Optional owner ID. If not provided, derived from the authentication context. Requires CREATE_SPACE_ANY permission if specified.").optional(),
22878
23028
  default_chunking_config: external_exports.object({
@@ -22880,7 +23030,7 @@ function registerTools() {
22880
23030
  recursive: external_exports.object({
22881
23031
  chunkSize: external_exports.number().int().describe("Maximum size of a chunk (should be \u2264 context window)"),
22882
23032
  chunkOverlap: external_exports.number().int().describe("Sliding overlap between chunks"),
22883
- separators: external_exports.any().describe("Hierarchical separator list (order = preference)").optional(),
23033
+ separators: external_exports.array(external_exports.string()).describe("Hierarchical separator list (order = preference)").optional(),
22884
23034
  keepStrategy: external_exports.enum(["KEEP_NONE", "KEEP_START", "KEEP_END"]).describe("How to handle separators after splitting. KEEP_NONE is deprecated and behaves as KEEP_END."),
22885
23035
  separatorIsRegex: external_exports.boolean().describe("Whether separators are regex patterns").optional(),
22886
23036
  lengthMeasurement: external_exports.enum(["CHARACTER_COUNT", "TOKEN_COUNT", "CUSTOM"]).describe("How to measure chunk length")
@@ -22912,24 +23062,30 @@ function registerTools() {
22912
23062
  const result = await callApi("POST", `/v1/spaces`, body);
22913
23063
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22914
23064
  });
22915
- server.tool("goodmem_spaces_delete", "Delete a space", {
23065
+ server.tool("goodmem_spaces_delete", "Delete a space\n\nPermanently deletes a space and all associated content. This operation cannot be undone. CASCADE DELETION: Removes the space record and cascades deletion to associated memories, chunks, and embedder associations. Requires DELETE_SPACE_OWN permission for spaces you own (or DELETE_SPACE_ANY for admin users). This operation is safe to retry - may return NOT_FOUND if already deleted.", {
22916
23066
  id: external_exports.string().describe("The unique identifier of the space to delete")
22917
23067
  }, async (args) => {
23068
+ if (typeof args.id !== "string" || args.id === "") {
23069
+ throw new Error("'id' must be a non-empty string");
23070
+ }
22918
23071
  await callApi("DELETE", `/v1/spaces/${encodeURIComponent(String(args.id))}`, void 0);
22919
23072
  return { content: [{ type: "text", text: "Deleted successfully." }] };
22920
23073
  });
22921
- server.tool("goodmem_spaces_get", "Get a space by ID", {
23074
+ server.tool("goodmem_spaces_get", "Get a space by ID\n\nRetrieves a specific space by its unique identifier. Returns the complete space information, including name, labels, embedder configuration, and metadata. PUBLIC SPACE ACCESS: When public_read=true, any authenticated user can retrieve the space metadata, bypassing ownership checks. Otherwise, requires ownership or DISPLAY_SPACE_ANY permission. Requires DISPLAY_SPACE_OWN permission for owned spaces (or DISPLAY_SPACE_ANY for admin users to view any space). This is a read-only operation safe to retry.", {
22922
23075
  id: external_exports.string().describe("The unique identifier of the space to retrieve")
22923
23076
  }, async (args) => {
23077
+ if (typeof args.id !== "string" || args.id === "") {
23078
+ throw new Error("'id' must be a non-empty string");
23079
+ }
22924
23080
  const result = await callApi("GET", `/v1/spaces/${encodeURIComponent(String(args.id))}`, void 0);
22925
23081
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22926
23082
  });
22927
- server.tool("goodmem_spaces_list", "List spaces", {
22928
- owner_id: external_exports.string().describe("Filter spaces by owner ID. With LIST_SPACE_ANY permission and ownerId omitted, returns all visible spaces. Otherwise returns caller-owned spaces only. Specifying ownerId without LIST_SPACE_ANY returns PERMISSION_DENIED.").optional(),
23083
+ server.tool("goodmem_spaces_list", "List spaces accessible to the caller, with optional filtering by owner, labels, and name. Results are paginated; pass the response's `next_token` back as a query parameter to fetch subsequent pages.", {
23084
+ owner_id: external_exports.string().describe("Filter spaces by owner ID. With LIST_SPACE_ANY permission and owner_id omitted, returns all visible spaces. Otherwise returns caller-owned spaces only. Specifying owner_id without LIST_SPACE_ANY returns PERMISSION_DENIED.").optional(),
22929
23085
  name_filter: external_exports.string().describe("Filter spaces by name using glob pattern matching").optional(),
22930
23086
  max_results: external_exports.number().int().describe("Maximum number of results to return in a single page (defaults to 50, clamped to [1, 1000])").optional(),
22931
23087
  next_token: external_exports.string().describe("Pagination token for retrieving the next set of results").optional(),
22932
- sort_by: external_exports.string().describe("Field to sort by: 'created_time', 'updated_time', or 'name' (default: 'created_time'). Unsupported values return INVALID_ARGUMENT.").optional(),
23088
+ sort_by: external_exports.enum(["created_time", "updated_time", "name"]).describe("Field to sort by: 'created_time', 'updated_time', or 'name' (default: 'created_time'). Unsupported values return INVALID_ARGUMENT.").optional(),
22933
23089
  sort_order: external_exports.enum(["ASCENDING", "DESCENDING", "SORT_ORDER_UNSPECIFIED"]).describe("Sort order (ASCENDING or DESCENDING, default: DESCENDING)").optional(),
22934
23090
  label: external_exports.record(external_exports.string(), external_exports.string()).describe("Filter by label. Each key-value pair is sent as label[key]=value query parameter.").optional()
22935
23091
  }, async (args) => {
@@ -22953,13 +23109,20 @@ function registerTools() {
22953
23109
  const result = await callApi("GET", `/v1/spaces`, void 0, query);
22954
23110
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22955
23111
  });
22956
- server.tool("goodmem_spaces_update", "Update a space", {
23112
+ server.tool("goodmem_spaces_update", "Update a space\n\nUpdates an existing space with new values for the specified fields. Only name, public_read, and labels can be updated. Fields not included in the request remain unchanged. IMMUTABLE FIELDS: space_embedders, default_chunking_config, and ownerId cannot be modified after creation. NAME UNIQUENESS: Name must be unique per owner - returns ALREADY_EXISTS if name conflicts with existing space. Requires UPDATE_SPACE_OWN permission for spaces you own (or UPDATE_SPACE_ANY for admin users). This operation is idempotent.", {
22957
23113
  id: external_exports.string().describe("The unique identifier of the space to update"),
22958
23114
  name: external_exports.string().describe("The new name for the space.").optional(),
22959
23115
  public_read: external_exports.boolean().describe("Whether the space is publicly readable by all users.").optional(),
22960
- replace_labels: external_exports.record(external_exports.string(), external_exports.string()).describe("Labels to replace all existing labels. Mutually exclusive with mergeLabels.").optional(),
22961
- merge_labels: external_exports.record(external_exports.string(), external_exports.string()).describe("Labels to merge with existing labels. Mutually exclusive with replaceLabels.").optional()
23116
+ replace_labels: external_exports.record(external_exports.string(), external_exports.string()).describe("Labels to replace all existing labels. Mutually exclusive with merge_labels.").optional(),
23117
+ merge_labels: external_exports.record(external_exports.string(), external_exports.string()).describe("Labels to merge with existing labels. Mutually exclusive with replace_labels.").optional()
22962
23118
  }, async (args) => {
23119
+ if (typeof args.id !== "string" || args.id === "") {
23120
+ throw new Error("'id' must be a non-empty string");
23121
+ }
23122
+ const _set = [args.replace_labels !== void 0 && args.replace_labels !== null, args.merge_labels !== void 0 && args.merge_labels !== null].filter(Boolean).length;
23123
+ if (_set > 1) {
23124
+ throw new Error("at most one of replace_labels or merge_labels may be set");
23125
+ }
22963
23126
  const body = {};
22964
23127
  if (args.name !== void 0)
22965
23128
  body["name"] = args.name;
@@ -22972,27 +23135,29 @@ function registerTools() {
22972
23135
  const result = await callApi("PUT", `/v1/spaces/${encodeURIComponent(String(args.id))}`, body);
22973
23136
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22974
23137
  });
22975
- server.tool("goodmem_system_info", "Retrieve server build metadata", {}, async (args) => {
23138
+ server.tool("goodmem_system_info", "Retrieve server build metadata\n\nReturns the server's advertised semantic version, git metadata, build timestamp, and optional capability flags. The endpoint is intentionally unauthenticated so bootstrap tooling can call it before API keys exist.", {}, async (args) => {
22976
23139
  const result = await callApi("GET", `/v1/system/info`, void 0);
22977
23140
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22978
23141
  });
22979
- server.tool("goodmem_system_init", "Initialize the system", {}, async (args) => {
23142
+ server.tool("goodmem_system_init", "Initialize the system\n\nInitializes the system by creating a root user and API key. This endpoint should only be called once during first-time setup. If the system is already initialized, the endpoint will return a success response without creating new credentials.", {}, async (args) => {
22980
23143
  const result = await callApi("POST", `/v1/system/init`, void 0);
22981
23144
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22982
23145
  });
22983
- server.tool("goodmem_users_get", "Get a user by ID", {
23146
+ server.tool("goodmem_users_get", "Retrieves a user by ID or email address. Exactly one of `id` and `email` must be provided. For getting your own profile, use the `goodmem_users_me` tool.", {
22984
23147
  id: external_exports.string().describe("The UUID of the user to retrieve").optional(),
22985
23148
  email: external_exports.string().describe("The email address of the user to retrieve").optional()
22986
23149
  }, async (args) => {
22987
- if (args.id !== void 0 && args.email !== void 0)
23150
+ const _hasId = typeof args.id === "string" && args.id !== "";
23151
+ const _hasEmail = typeof args.email === "string" && args.email !== "";
23152
+ if (_hasId && _hasEmail)
22988
23153
  throw new Error("Exactly one of 'id', 'email' must be provided, not both.");
22989
- if (args.id === void 0 && args.email === void 0)
22990
- throw new Error("Exactly one of 'id', 'email' must be provided.");
22991
- const url = args.email ? `/v1/users/email/${encodeURIComponent(String(args.email))}` : `/v1/users/${encodeURIComponent(String(args.id))}`;
23154
+ if (!_hasId && !_hasEmail)
23155
+ throw new Error("Exactly one of 'id', 'email' must be provided (non-empty).");
23156
+ const url = _hasEmail ? `/v1/users/email/${encodeURIComponent(String(args.email))}` : `/v1/users/${encodeURIComponent(String(args.id))}`;
22992
23157
  const result = await callApi("GET", url, void 0);
22993
23158
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22994
23159
  });
22995
- server.tool("goodmem_users_me", "Get current user profile", {}, async (args) => {
23160
+ server.tool("goodmem_users_me", "Get current user profile\n\nRetrieves the authenticated user's profile information including email, display name, and creation time. This endpoint does not require any parameters as it automatically returns the caller's information.", {}, async (args) => {
22996
23161
  const result = await callApi("GET", `/v1/users/me`, void 0);
22997
23162
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22998
23163
  });
@@ -23004,17 +23169,19 @@ async function healthCheck() {
23004
23169
  return;
23005
23170
  }
23006
23171
  try {
23007
- const url = new URL("/v1/system/info", goodmemBaseUrl);
23172
+ const url = _buildUrl(goodmemBaseUrl, "/v1/system/info");
23008
23173
  const resp = await fetch(url.toString(), {
23009
23174
  headers: { "x-api-key": goodmemApiKey },
23010
- signal: AbortSignal.timeout(1e4)
23175
+ signal: AbortSignal.timeout(1e4),
23176
+ // See callApi for the redirect-policy rationale.
23177
+ redirect: "error"
23011
23178
  });
23012
23179
  if (resp.ok) {
23013
23180
  const info = await resp.json();
23014
23181
  console.error(`[goodmem-mcp] Connected to ${goodmemBaseUrl} (server ${info.version ?? "unknown"}).`);
23015
23182
  } else {
23016
23183
  const text = await resp.text();
23017
- console.error(`[goodmem-mcp] Health check failed (${resp.status}): ${text}`);
23184
+ console.error(`[goodmem-mcp] Health check failed (${resp.status}): ${_trimBody(text)}`);
23018
23185
  }
23019
23186
  } catch (err) {
23020
23187
  const msg = err instanceof Error ? err.message : String(err);