@pairsystems/goodmem-mcp 0.1.13 → 0.1.15

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 +54 -58
  2. package/dist/index.js +459 -256
  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.13"
21846
+ version: "0.1.15"
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.13",
21998
- basedOnGoodmemCommit: BASED_ON_GOODMEM_COMMIT
21928
+ mcp_version: "0.1.15",
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,7 @@ 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_drain", "Request the server to enter drain mode\n\nInitiates drain mode and optionally waits for the server to quiesce.", {
22021
21952
  timeout_sec: external_exports.number().int().describe("Maximum seconds to wait for the server to quiesce before returning.").optional(),
22022
21953
  reason: external_exports.string().describe("Human-readable reason for initiating drain mode.").optional(),
22023
21954
  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 +21960,108 @@ function registerTools() {
22029
21960
  body["reason"] = args.reason;
22030
21961
  if (args.wait_for_quiesce !== void 0)
22031
21962
  body["waitForQuiesce"] = args.wait_for_quiesce;
22032
- const result = await callApi("POST", `/v1/admin:drain`, body);
21963
+ const result = await callApi("POST", `/v1/admin:drain`, body, void 0, ((args.timeout_sec ?? 900) + 30) * 1e3);
22033
21964
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22034
21965
  });
22035
- server.tool("goodmem_admin_license_reload", "Reload the active license from disk", {}, async (args) => {
21966
+ 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
21967
  const result = await callApi("POST", `/v1/admin/license:reload`, void 0);
22037
21968
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22038
21969
  });
22039
- server.tool("goodmem_apikeys_create", "Create a new API key", {
21970
+ server.tool("goodmem_admin_retrieve_memory_log_policies_create", "Create a RetrieveMemory log policy\n\nCreates an immutable administrative policy that can automatically enable durable RetrieveMemory request logging.", {
21971
+ policy_id: external_exports.string().describe("Optional client-provided policy UUID.").optional(),
21972
+ display_name: external_exports.string().describe("Human-readable policy name."),
21973
+ description: external_exports.string().describe("Optional operator description.").optional(),
21974
+ condition: external_exports.object({
21975
+ matchAll: external_exports.boolean().describe("When true, match every authenticated RetrieveMemory request. Defaults to false when omitted.").optional(),
21976
+ anyOf: external_exports.array(external_exports.object({
21977
+ requestorUserIds: external_exports.array(external_exports.string()).describe("Authenticated requestor user UUID strings.").optional(),
21978
+ apiKeyIds: external_exports.array(external_exports.string()).describe("API key UUID strings used to authenticate RetrieveMemory requests.").optional(),
21979
+ spaceIds: external_exports.array(external_exports.string()).describe("Post-permission accessible space UUID strings.").optional(),
21980
+ apiKeyLabelSelectors: external_exports.record(external_exports.string(), external_exports.string()).describe("Exact API-key label selectors that must all match.").optional(),
21981
+ spaceLabelSelectors: external_exports.record(external_exports.string(), external_exports.string()).describe("Exact space label selectors that must match at least one accessible space.").optional()
21982
+ }).passthrough()).describe("OR clauses used for scoped matching. Required when matchAll is false or omitted, and must contain at least one non-empty clause.").optional()
21983
+ }).passthrough().describe("Policy match condition."),
21984
+ active_from: external_exports.number().int().describe("Inclusive activation time in milliseconds since epoch.").optional(),
21985
+ active_until: external_exports.number().int().describe("Exclusive deactivation time in milliseconds since epoch.").optional(),
21986
+ labels: external_exports.record(external_exports.string(), external_exports.string()).describe("Operator labels for listing and administration.").optional()
21987
+ }, async (args) => {
21988
+ const body = {};
21989
+ if (args.policy_id !== void 0)
21990
+ body["policyId"] = args.policy_id;
21991
+ if (args.display_name !== void 0)
21992
+ body["displayName"] = args.display_name;
21993
+ if (args.description !== void 0)
21994
+ body["description"] = args.description;
21995
+ if (args.condition !== void 0)
21996
+ body["condition"] = args.condition;
21997
+ if (args.active_from !== void 0)
21998
+ body["activeFrom"] = args.active_from;
21999
+ if (args.active_until !== void 0)
22000
+ body["activeUntil"] = args.active_until;
22001
+ if (args.labels !== void 0)
22002
+ body["labels"] = args.labels;
22003
+ const result = await callApi("POST", `/v1/admin/retrieve-memory-log-policies`, body);
22004
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22005
+ });
22006
+ server.tool("goodmem_admin_retrieve_memory_log_policies_delete", "Delete a RetrieveMemory log policy\n\nIdempotently tombstones an immutable RetrieveMemory log policy.", {
22007
+ id: external_exports.string().describe("The UUID of the policy to delete"),
22008
+ reason: external_exports.string().describe("Optional tombstone reason.").optional()
22009
+ }, async (args) => {
22010
+ if (typeof args.id !== "string" || args.id === "") {
22011
+ throw new Error("'id' must be a non-empty string");
22012
+ }
22013
+ const body = {};
22014
+ if (args.reason !== void 0)
22015
+ body["reason"] = args.reason;
22016
+ await callApi("DELETE", `/v1/admin/retrieve-memory-log-policies/${encodeURIComponent(String(args.id))}`, body);
22017
+ return { content: [{ type: "text", text: "Deleted successfully." }] };
22018
+ });
22019
+ server.tool("goodmem_admin_retrieve_memory_log_policies_get", "Get a RetrieveMemory log policy\n\nRetrieves a live RetrieveMemory log policy by UUID, or a tombstoned policy when include_deleted is true.", {
22020
+ id: external_exports.string().describe("The UUID of the policy to retrieve"),
22021
+ include_deleted: external_exports.boolean().describe("Whether to include tombstoned policies. Also accepts include_deleted.").optional()
22022
+ }, async (args) => {
22023
+ if (typeof args.id !== "string" || args.id === "") {
22024
+ throw new Error("'id' must be a non-empty string");
22025
+ }
22026
+ const query = {};
22027
+ if (args.include_deleted !== void 0)
22028
+ query["includeDeleted"] = String(args.include_deleted);
22029
+ const result = await callApi("GET", `/v1/admin/retrieve-memory-log-policies/${encodeURIComponent(String(args.id))}`, void 0, query);
22030
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22031
+ });
22032
+ server.tool("goodmem_admin_retrieve_memory_log_policies_list", "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).", {
22033
+ include_deleted: external_exports.boolean().describe("Whether to include tombstoned policies. Also accepts include_deleted.").optional(),
22034
+ name_filter: external_exports.string().describe("Case-insensitive substring filter on policy display names. Also accepts name_filter.").optional(),
22035
+ active_at: external_exports.number().int().describe("Only return policies active at this millisecond epoch timestamp. Also accepts active_at.").optional(),
22036
+ max_results: external_exports.number().int().describe("Maximum number of policies to return. Also accepts max_results.").optional(),
22037
+ next_token: external_exports.string().describe("Opaque pagination token returned by the previous list response. Also accepts next_token.").optional(),
22038
+ 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(),
22039
+ sort_order: external_exports.enum(["ASCENDING", "DESCENDING", "SORT_ORDER_UNSPECIFIED"]).describe("Sort order. Also accepts sort_order.").optional(),
22040
+ 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()
22041
+ }, async (args) => {
22042
+ const query = {};
22043
+ if (args.include_deleted !== void 0)
22044
+ query["includeDeleted"] = String(args.include_deleted);
22045
+ if (args.name_filter !== void 0)
22046
+ query["nameFilter"] = String(args.name_filter);
22047
+ if (args.active_at !== void 0)
22048
+ query["activeAt"] = String(args.active_at);
22049
+ if (args.max_results !== void 0)
22050
+ query["maxResults"] = String(args.max_results);
22051
+ if (args.next_token !== void 0)
22052
+ query["nextToken"] = String(args.next_token);
22053
+ if (args.sort_by !== void 0)
22054
+ query["sortBy"] = String(args.sort_by);
22055
+ if (args.sort_order !== void 0)
22056
+ query["sortOrder"] = String(args.sort_order);
22057
+ if (args.label) {
22058
+ for (const [k, v] of Object.entries(args.label))
22059
+ query[`label[${k}]`] = v;
22060
+ }
22061
+ const result = await callApi("GET", `/v1/admin/retrieve-memory-log-policies`, void 0, query);
22062
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22063
+ });
22064
+ 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
22065
  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
22066
  expires_at: external_exports.number().int().describe("Expiration timestamp in milliseconds since epoch. If not provided, the key does not expire.").optional(),
22042
22067
  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 +22076,32 @@ function registerTools() {
22051
22076
  const result = await callApi("POST", `/v1/apikeys`, body);
22052
22077
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22053
22078
  });
22054
- server.tool("goodmem_apikeys_delete", "Delete an API key", {
22079
+ 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
22080
  id: external_exports.string().describe("The UUID of the API key to delete")
22056
22081
  }, async (args) => {
22082
+ if (typeof args.id !== "string" || args.id === "") {
22083
+ throw new Error("'id' must be a non-empty string");
22084
+ }
22057
22085
  await callApi("DELETE", `/v1/apikeys/${encodeURIComponent(String(args.id))}`, void 0);
22058
22086
  return { content: [{ type: "text", text: "Deleted successfully." }] };
22059
22087
  });
22060
- server.tool("goodmem_apikeys_list", "List API keys", {}, async (args) => {
22088
+ 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
22089
  const result = await callApi("GET", `/v1/apikeys`, void 0);
22062
22090
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22063
22091
  });
22064
- server.tool("goodmem_apikeys_update", "Update an API key", {
22092
+ 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
22093
  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()
22094
+ status: external_exports.enum(["ACTIVE", "INACTIVE"]).describe("New status for the API key. Allowed values: ACTIVE, INACTIVE.").optional(),
22095
+ 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(),
22096
+ 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
22097
  }, async (args) => {
22098
+ if (typeof args.id !== "string" || args.id === "") {
22099
+ throw new Error("'id' must be a non-empty string");
22100
+ }
22101
+ const _set = [args.replace_labels !== void 0 && args.replace_labels !== null, args.merge_labels !== void 0 && args.merge_labels !== null].filter(Boolean).length;
22102
+ if (_set > 1) {
22103
+ throw new Error("at most one of replace_labels or merge_labels may be set");
22104
+ }
22070
22105
  const body = {};
22071
22106
  if (args.status !== void 0)
22072
22107
  body["status"] = args.status;
@@ -22077,7 +22112,7 @@ function registerTools() {
22077
22112
  const result = await callApi("PUT", `/v1/apikeys/${encodeURIComponent(String(args.id))}`, body);
22078
22113
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22079
22114
  });
22080
- server.tool("goodmem_embedders_create", "Create a new embedder", {
22115
+ 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
22116
  display_name: external_exports.string().describe("User-facing name of the embedder"),
22082
22117
  description: external_exports.string().describe("Description of the embedder").optional(),
22083
22118
  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,16 +22129,16 @@ function registerTools() {
22094
22129
  inlineSecret: external_exports.string().describe("Secret stored directly in GoodMem (mutually exclusive with secretRef)").optional(),
22095
22130
  secretRef: external_exports.object({
22096
22131
  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()
22132
+ 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
22133
  }).passthrough().describe("Reference to an external secret manager entry (mutually exclusive with inlineSecret)").optional(),
22099
22134
  headerName: external_exports.string().describe("Desired HTTP header to carry the credential (defaults to Authorization)").optional(),
22100
22135
  prefix: external_exports.string().describe('Optional prefix prepended to the secret (e.g., "Bearer ")').optional()
22101
22136
  }).passthrough().describe("Configuration when kind is CREDENTIAL_KIND_API_KEY").optional(),
22102
22137
  gcpAdc: external_exports.object({
22103
- scopes: external_exports.any().describe("Additional OAuth scopes. Empty list falls back to the default cloud-platform scope.").optional(),
22138
+ scopes: external_exports.array(external_exports.string()).describe("Additional OAuth scopes. Empty list falls back to the default cloud-platform scope.").optional(),
22104
22139
  quotaProjectId: external_exports.string().describe("Optional quota project used for billing").optional()
22105
22140
  }).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()
22141
+ labels: external_exports.record(external_exports.string(), external_exports.string()).describe('Optional annotations to aid operators (e.g., "owner=vertex")').optional()
22107
22142
  }).passthrough().describe("Structured credential payload describing how to authenticate with the provider. Required for SaaS providers; optional for local or proxy providers.").optional(),
22108
22143
  labels: external_exports.record(external_exports.string(), external_exports.string()).describe("User-defined labels for categorization").optional(),
22109
22144
  version: external_exports.string().describe("Version information").optional(),
@@ -22149,19 +22184,25 @@ function registerTools() {
22149
22184
  const result = await callApi("POST", `/v1/embedders`, body);
22150
22185
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22151
22186
  });
22152
- server.tool("goodmem_embedders_delete", "Delete an embedder", {
22187
+ 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
22188
  id: external_exports.string().describe("The unique identifier of the embedder to delete")
22154
22189
  }, async (args) => {
22190
+ if (typeof args.id !== "string" || args.id === "") {
22191
+ throw new Error("'id' must be a non-empty string");
22192
+ }
22155
22193
  await callApi("DELETE", `/v1/embedders/${encodeURIComponent(String(args.id))}`, void 0);
22156
22194
  return { content: [{ type: "text", text: "Deleted successfully." }] };
22157
22195
  });
22158
- server.tool("goodmem_embedders_get", "Get an embedder by ID", {
22196
+ 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
22197
  id: external_exports.string().describe("The unique identifier of the embedder to retrieve")
22160
22198
  }, async (args) => {
22199
+ if (typeof args.id !== "string" || args.id === "") {
22200
+ throw new Error("'id' must be a non-empty string");
22201
+ }
22161
22202
  const result = await callApi("GET", `/v1/embedders/${encodeURIComponent(String(args.id))}`, void 0);
22162
22203
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22163
22204
  });
22164
- server.tool("goodmem_embedders_list", "List embedders", {
22205
+ 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
22206
  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
22207
  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
22208
  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 +22219,7 @@ function registerTools() {
22178
22219
  const result = await callApi("GET", `/v1/embedders`, void 0, query);
22179
22220
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22180
22221
  });
22181
- server.tool("goodmem_embedders_update", "Update an embedder", {
22222
+ 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
22223
  id: external_exports.string().describe("The unique identifier of the embedder to update"),
22183
22224
  display_name: external_exports.string().describe("User-facing name of the embedder").optional(),
22184
22225
  description: external_exports.string().describe("Description of the embedder").optional(),
@@ -22188,29 +22229,35 @@ function registerTools() {
22188
22229
  dimensionality: external_exports.number().int().describe("Output vector dimensions").optional(),
22189
22230
  distribution_type: external_exports.enum(["DENSE", "SPARSE"]).describe("Type of embedding distribution (DENSE or SPARSE)").optional(),
22190
22231
  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
22232
  credentials: external_exports.object({
22193
22233
  kind: external_exports.enum(["CREDENTIAL_KIND_UNSPECIFIED", "CREDENTIAL_KIND_API_KEY", "CREDENTIAL_KIND_GCP_ADC"]).describe("Selected credential strategy"),
22194
22234
  apiKey: external_exports.object({
22195
22235
  inlineSecret: external_exports.string().describe("Secret stored directly in GoodMem (mutually exclusive with secretRef)").optional(),
22196
22236
  secretRef: external_exports.object({
22197
22237
  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()
22238
+ 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
22239
  }).passthrough().describe("Reference to an external secret manager entry (mutually exclusive with inlineSecret)").optional(),
22200
22240
  headerName: external_exports.string().describe("Desired HTTP header to carry the credential (defaults to Authorization)").optional(),
22201
22241
  prefix: external_exports.string().describe('Optional prefix prepended to the secret (e.g., "Bearer ")').optional()
22202
22242
  }).passthrough().describe("Configuration when kind is CREDENTIAL_KIND_API_KEY").optional(),
22203
22243
  gcpAdc: external_exports.object({
22204
- scopes: external_exports.any().describe("Additional OAuth scopes. Empty list falls back to the default cloud-platform scope.").optional(),
22244
+ scopes: external_exports.array(external_exports.string()).describe("Additional OAuth scopes. Empty list falls back to the default cloud-platform scope.").optional(),
22205
22245
  quotaProjectId: external_exports.string().describe("Optional quota project used for billing").optional()
22206
22246
  }).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()
22247
+ labels: external_exports.record(external_exports.string(), external_exports.string()).describe('Optional annotations to aid operators (e.g., "owner=vertex")').optional()
22208
22248
  }).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(),
22249
+ replace_labels: external_exports.record(external_exports.string(), external_exports.string()).describe("Replace all existing labels with these (mutually exclusive with merge_labels)").optional(),
22250
+ 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
22251
  version: external_exports.string().describe("Version information").optional(),
22212
22252
  monitoring_endpoint: external_exports.string().describe("Monitoring endpoint URL").optional()
22213
22253
  }, async (args) => {
22254
+ if (typeof args.id !== "string" || args.id === "") {
22255
+ throw new Error("'id' must be a non-empty string");
22256
+ }
22257
+ const _set = [args.replace_labels !== void 0 && args.replace_labels !== null, args.merge_labels !== void 0 && args.merge_labels !== null].filter(Boolean).length;
22258
+ if (_set > 1) {
22259
+ throw new Error("at most one of replace_labels or merge_labels may be set");
22260
+ }
22214
22261
  const body = {};
22215
22262
  if (args.display_name !== void 0)
22216
22263
  body["displayName"] = args.display_name;
@@ -22228,8 +22275,6 @@ function registerTools() {
22228
22275
  body["distributionType"] = args.distribution_type;
22229
22276
  if (args.max_sequence_length !== void 0)
22230
22277
  body["maxSequenceLength"] = args.max_sequence_length;
22231
- if (args.supported_modalities !== void 0)
22232
- body["supportedModalities"] = args.supported_modalities;
22233
22278
  if (args.credentials !== void 0)
22234
22279
  body["credentials"] = args.credentials;
22235
22280
  if (args.replace_labels !== void 0)
@@ -22243,7 +22288,7 @@ function registerTools() {
22243
22288
  const result = await callApi("PUT", `/v1/embedders/${encodeURIComponent(String(args.id))}`, body);
22244
22289
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22245
22290
  });
22246
- server.tool("goodmem_llms_create", "Create a new LLM", {
22291
+ 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
22292
  display_name: external_exports.string().describe("User-facing name of the LLM"),
22248
22293
  description: external_exports.string().describe("Description of the LLM").optional(),
22249
22294
  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,16 +22302,16 @@ function registerTools() {
22257
22302
  inlineSecret: external_exports.string().describe("Secret stored directly in GoodMem (mutually exclusive with secretRef)").optional(),
22258
22303
  secretRef: external_exports.object({
22259
22304
  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()
22305
+ 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
22306
  }).passthrough().describe("Reference to an external secret manager entry (mutually exclusive with inlineSecret)").optional(),
22262
22307
  headerName: external_exports.string().describe("Desired HTTP header to carry the credential (defaults to Authorization)").optional(),
22263
22308
  prefix: external_exports.string().describe('Optional prefix prepended to the secret (e.g., "Bearer ")').optional()
22264
22309
  }).passthrough().describe("Configuration when kind is CREDENTIAL_KIND_API_KEY").optional(),
22265
22310
  gcpAdc: external_exports.object({
22266
- scopes: external_exports.any().describe("Additional OAuth scopes. Empty list falls back to the default cloud-platform scope.").optional(),
22311
+ scopes: external_exports.array(external_exports.string()).describe("Additional OAuth scopes. Empty list falls back to the default cloud-platform scope.").optional(),
22267
22312
  quotaProjectId: external_exports.string().describe("Optional quota project used for billing").optional()
22268
22313
  }).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()
22314
+ labels: external_exports.record(external_exports.string(), external_exports.string()).describe('Optional annotations to aid operators (e.g., "owner=vertex")').optional()
22270
22315
  }).passthrough().describe("Structured credential payload describing how to authenticate with the provider. Required for SaaS providers; optional for local or proxy providers.").optional(),
22271
22316
  labels: external_exports.record(external_exports.string(), external_exports.string()).describe("User-defined labels for categorization").optional(),
22272
22317
  version: external_exports.string().describe("Version information").optional(),
@@ -22281,15 +22326,15 @@ function registerTools() {
22281
22326
  }).passthrough().describe("LLM capabilities defining supported features and modes. Optional - server infers capabilities from model identifier if not provided.").optional(),
22282
22327
  default_sampling_params: external_exports.object({
22283
22328
  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(),
22329
+ temperature: external_exports.number().describe("Sampling temperature; valid range depends on the configured provider").optional(),
22285
22330
  topP: external_exports.number().describe("Nucleus sampling threshold 0.0-1.0 (smaller values focus on higher probability tokens)").optional(),
22286
22331
  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()
22332
+ frequencyPenalty: external_exports.number().describe("Frequency penalty; valid range depends on the configured provider").optional(),
22333
+ presencePenalty: external_exports.number().describe("Presence penalty; valid range depends on the configured provider").optional(),
22334
+ stopSequences: external_exports.array(external_exports.string()).describe("Generation stop sequences; generation halts on exact match").optional()
22290
22335
  }).passthrough().describe("Default sampling parameters for generation requests").optional(),
22291
22336
  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(),
22337
+ client_config: external_exports.record(external_exports.string(), external_exports.any()).describe("Provider-specific client configuration as flexible JSON structure").optional(),
22293
22338
  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
22339
  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
22340
  }, async (args) => {
@@ -22333,19 +22378,25 @@ function registerTools() {
22333
22378
  const result = await callApi("POST", `/v1/llms`, body);
22334
22379
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22335
22380
  });
22336
- server.tool("goodmem_llms_delete", "Delete an LLM", {
22381
+ 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
22382
  id: external_exports.string().describe("The unique identifier of the LLM to delete")
22338
22383
  }, async (args) => {
22384
+ if (typeof args.id !== "string" || args.id === "") {
22385
+ throw new Error("'id' must be a non-empty string");
22386
+ }
22339
22387
  await callApi("DELETE", `/v1/llms/${encodeURIComponent(String(args.id))}`, void 0);
22340
22388
  return { content: [{ type: "text", text: "Deleted successfully." }] };
22341
22389
  });
22342
- server.tool("goodmem_llms_get", "Get an LLM by ID", {
22390
+ 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
22391
  id: external_exports.string().describe("The unique identifier of the LLM to retrieve")
22344
22392
  }, async (args) => {
22393
+ if (typeof args.id !== "string" || args.id === "") {
22394
+ throw new Error("'id' must be a non-empty string");
22395
+ }
22345
22396
  const result = await callApi("GET", `/v1/llms/${encodeURIComponent(String(args.id))}`, void 0);
22346
22397
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22347
22398
  });
22348
- server.tool("goodmem_llms_list", "List LLMs", {
22399
+ 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
22400
  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
22401
  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
22402
  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 +22413,30 @@ function registerTools() {
22362
22413
  const result = await callApi("GET", `/v1/llms`, void 0, query);
22363
22414
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22364
22415
  });
22365
- server.tool("goodmem_llms_update", "Update an LLM", {
22416
+ 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
22417
  id: external_exports.string().describe("The unique identifier of the LLM to update"),
22367
22418
  display_name: external_exports.string().describe("Update display name").optional(),
22368
22419
  description: external_exports.string().describe("Update description").optional(),
22369
22420
  endpoint_url: external_exports.string().describe("Update endpoint base URL (OpenAI-compatible base, typically ends with /v1)").optional(),
22370
22421
  api_path: external_exports.string().describe("Update API path").optional(),
22371
22422
  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(),
22423
+ 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
22424
  credentials: external_exports.object({
22374
22425
  kind: external_exports.enum(["CREDENTIAL_KIND_UNSPECIFIED", "CREDENTIAL_KIND_API_KEY", "CREDENTIAL_KIND_GCP_ADC"]).describe("Selected credential strategy"),
22375
22426
  apiKey: external_exports.object({
22376
22427
  inlineSecret: external_exports.string().describe("Secret stored directly in GoodMem (mutually exclusive with secretRef)").optional(),
22377
22428
  secretRef: external_exports.object({
22378
22429
  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()
22430
+ 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
22431
  }).passthrough().describe("Reference to an external secret manager entry (mutually exclusive with inlineSecret)").optional(),
22381
22432
  headerName: external_exports.string().describe("Desired HTTP header to carry the credential (defaults to Authorization)").optional(),
22382
22433
  prefix: external_exports.string().describe('Optional prefix prepended to the secret (e.g., "Bearer ")').optional()
22383
22434
  }).passthrough().describe("Configuration when kind is CREDENTIAL_KIND_API_KEY").optional(),
22384
22435
  gcpAdc: external_exports.object({
22385
- scopes: external_exports.any().describe("Additional OAuth scopes. Empty list falls back to the default cloud-platform scope.").optional(),
22436
+ scopes: external_exports.array(external_exports.string()).describe("Additional OAuth scopes. Empty list falls back to the default cloud-platform scope.").optional(),
22386
22437
  quotaProjectId: external_exports.string().describe("Optional quota project used for billing").optional()
22387
22438
  }).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()
22439
+ labels: external_exports.record(external_exports.string(), external_exports.string()).describe('Optional annotations to aid operators (e.g., "owner=vertex")').optional()
22389
22440
  }).passthrough().describe("Update credentials").optional(),
22390
22441
  version: external_exports.string().describe("Update version information").optional(),
22391
22442
  monitoring_endpoint: external_exports.string().describe("Update monitoring endpoint URL").optional(),
@@ -22399,18 +22450,25 @@ function registerTools() {
22399
22450
  }).passthrough().describe("Update LLM capabilities (replaces entire capability set; clients MUST send all flags)").optional(),
22400
22451
  default_sampling_params: external_exports.object({
22401
22452
  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(),
22453
+ temperature: external_exports.number().describe("Sampling temperature; valid range depends on the configured provider").optional(),
22403
22454
  topP: external_exports.number().describe("Nucleus sampling threshold 0.0-1.0 (smaller values focus on higher probability tokens)").optional(),
22404
22455
  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()
22456
+ frequencyPenalty: external_exports.number().describe("Frequency penalty; valid range depends on the configured provider").optional(),
22457
+ presencePenalty: external_exports.number().describe("Presence penalty; valid range depends on the configured provider").optional(),
22458
+ stopSequences: external_exports.array(external_exports.string()).describe("Generation stop sequences; generation halts on exact match").optional()
22408
22459
  }).passthrough().describe("Update default sampling parameters").optional(),
22409
22460
  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()
22461
+ client_config: external_exports.record(external_exports.string(), external_exports.any()).describe("Update provider-specific client configuration (replaces entire config; no merging)").optional(),
22462
+ 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(),
22463
+ 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
22464
  }, async (args) => {
22465
+ if (typeof args.id !== "string" || args.id === "") {
22466
+ throw new Error("'id' must be a non-empty string");
22467
+ }
22468
+ const _set = [args.replace_labels !== void 0 && args.replace_labels !== null, args.merge_labels !== void 0 && args.merge_labels !== null].filter(Boolean).length;
22469
+ if (_set > 1) {
22470
+ throw new Error("at most one of replace_labels or merge_labels may be set");
22471
+ }
22414
22472
  const body = {};
22415
22473
  if (args.display_name !== void 0)
22416
22474
  body["displayName"] = args.display_name;
@@ -22445,21 +22503,21 @@ function registerTools() {
22445
22503
  const result = await callApi("PUT", `/v1/llms/${encodeURIComponent(String(args.id))}`, body);
22446
22504
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22447
22505
  });
22448
- server.tool("goodmem_memories_batch_create", "Create multiple memories in a batch", {
22506
+ 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
22507
  requests: external_exports.array(external_exports.object({
22450
22508
  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
22509
  spaceId: external_exports.string().describe("ID of the space where this memory will be stored"),
22452
22510
  originalContent: external_exports.string().describe("Original content as plain text (use either this or originalContentB64)").optional(),
22453
22511
  originalContentB64: external_exports.string().describe("Original content as base64-encoded binary data (use either this or originalContent)").optional(),
22454
22512
  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(),
22513
+ contentType: external_exports.string().describe("MIME type of the content").optional(),
22514
+ metadata: external_exports.record(external_exports.string(), external_exports.any()).describe("Additional metadata for the memory").optional(),
22457
22515
  chunkingConfig: external_exports.object({
22458
22516
  none: external_exports.any().describe("No chunking strategy - preserve original content as single unit").optional(),
22459
22517
  recursive: external_exports.object({
22460
22518
  chunkSize: external_exports.number().int().describe("Maximum size of a chunk (should be \u2264 context window)"),
22461
22519
  chunkOverlap: external_exports.number().int().describe("Sliding overlap between chunks"),
22462
- separators: external_exports.any().describe("Hierarchical separator list (order = preference)").optional(),
22520
+ separators: external_exports.array(external_exports.string()).describe("Hierarchical separator list (order = preference)").optional(),
22463
22521
  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
22522
  separatorIsRegex: external_exports.boolean().describe("Whether separators are regex patterns").optional(),
22465
22523
  lengthMeasurement: external_exports.enum(["CHARACTER_COUNT", "TOKEN_COUNT", "CUSTOM"]).describe("How to measure chunk length")
@@ -22471,22 +22529,38 @@ function registerTools() {
22471
22529
  lengthMeasurement: external_exports.enum(["CHARACTER_COUNT", "TOKEN_COUNT", "CUSTOM"]).describe("How to measure chunk length")
22472
22530
  }).passthrough().describe("Sentence-based chunking strategy with language detection").optional()
22473
22531
  }).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.")
22532
+ extractPageImages: external_exports.boolean().describe("Optional hint to extract page images for eligible document types (for example, PDFs)").optional()
22533
+ }).passthrough()).describe("Array of memory creation requests.")
22477
22534
  }, async (args) => {
22535
+ for (const [_i, _item] of (Array.isArray(args.requests) ? args.requests : []).entries()) {
22536
+ const _itemSet = [_item?.["originalContent"] !== void 0 && _item?.["originalContent"] !== null, _item?.["originalContentB64"] !== void 0 && _item?.["originalContentB64"] !== null].filter(Boolean).length;
22537
+ if (_itemSet !== 1) {
22538
+ throw new Error(`requests[${_i}]: exactly one of originalContent or originalContentB64 must be set`);
22539
+ }
22540
+ if (_item?.["originalContentB64"] !== void 0 && _item?.["originalContentB64"] !== null && (_item?.["contentType"] === void 0 || _item?.["contentType"] === null)) {
22541
+ 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`);
22542
+ }
22543
+ }
22478
22544
  const body = {};
22479
22545
  if (args.requests !== void 0)
22480
22546
  body["requests"] = args.requests;
22547
+ if (Array.isArray(body["requests"])) {
22548
+ body["requests"] = body["requests"].map((item) => {
22549
+ if (item && typeof item === "object" && item["originalContent"] !== void 0 && item["contentType"] === void 0) {
22550
+ return { ...item, contentType: "text/plain" };
22551
+ }
22552
+ return item;
22553
+ });
22554
+ }
22481
22555
  const result = await callApi("POST", `/v1/memories:batchCreate`, body);
22482
22556
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22483
22557
  });
22484
- server.tool("goodmem_memories_batch_delete", "Delete memories in batch", {
22558
+ 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
22559
  requests: external_exports.array(external_exports.object({
22486
22560
  memoryId: external_exports.string().describe("Deletes one specific memory by UUID").optional(),
22487
22561
  filterSelector: external_exports.object({
22488
22562
  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(),
22563
+ statusFilter: external_exports.enum(["PENDING", "PROCESSING", "COMPLETED", "FAILED"]).describe("Optional processing status filter (PENDING, PROCESSING, COMPLETED, FAILED)").optional(),
22490
22564
  filter: external_exports.string().describe("Optional metadata filter expression").optional()
22491
22565
  }).passthrough().describe("Deletes a filtered set of memories within a specific space").optional()
22492
22566
  }).passthrough()).describe("Array of delete selectors")
@@ -22497,7 +22571,7 @@ function registerTools() {
22497
22571
  const result = await callApi("POST", `/v1/memories:batchDelete`, body);
22498
22572
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22499
22573
  });
22500
- server.tool("goodmem_memories_batch_get", "Get multiple memories by ID", {
22574
+ 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
22575
  memory_ids: external_exports.array(external_exports.string()).describe("Array of memory IDs to retrieve"),
22502
22576
  include_content: external_exports.boolean().describe("Whether to include the original content in the response").optional(),
22503
22577
  include_processing_history: external_exports.boolean().describe("Whether to include background job processing history for each memory").optional()
@@ -22512,20 +22586,20 @@ function registerTools() {
22512
22586
  const result = await callApi("POST", `/v1/memories:batchGet`, body);
22513
22587
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22514
22588
  });
22515
- server.tool("goodmem_memories_create", "Create a memory from text content or base64-encoded content. Content must be provided via exactly one of `original_content` or `original_content_b64`.", {
22589
+ 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
22590
  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
22591
  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(),
22592
+ original_content: external_exports.string().describe("Original content as plain text (use either this or original_content_b64)").optional(),
22593
+ original_content_b64: external_exports.string().describe("Original content as base64-encoded binary data (use either this or original_content)").optional(),
22520
22594
  original_content_ref: external_exports.string().describe("Reference to external content location").optional(),
22521
22595
  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(),
22596
+ metadata: external_exports.record(external_exports.string(), external_exports.any()).describe("Additional metadata for the memory").optional(),
22523
22597
  chunking_config: external_exports.object({
22524
22598
  none: external_exports.any().describe("No chunking strategy - preserve original content as single unit").optional(),
22525
22599
  recursive: external_exports.object({
22526
22600
  chunkSize: external_exports.number().int().describe("Maximum size of a chunk (should be \u2264 context window)"),
22527
22601
  chunkOverlap: external_exports.number().int().describe("Sliding overlap between chunks"),
22528
- separators: external_exports.any().describe("Hierarchical separator list (order = preference)").optional(),
22602
+ separators: external_exports.array(external_exports.string()).describe("Hierarchical separator list (order = preference)").optional(),
22529
22603
  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
22604
  separatorIsRegex: external_exports.boolean().describe("Whether separators are regex patterns").optional(),
22531
22605
  lengthMeasurement: external_exports.enum(["CHARACTER_COUNT", "TOKEN_COUNT", "CUSTOM"]).describe("How to measure chunk length")
@@ -22539,6 +22613,13 @@ function registerTools() {
22539
22613
  }).passthrough().describe("Chunking strategy for this memory (if not provided, uses space default)").optional(),
22540
22614
  extract_page_images: external_exports.boolean().describe("Optional hint to extract page images for eligible document types (for example, PDFs)").optional()
22541
22615
  }, async (args) => {
22616
+ 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;
22617
+ if (_set !== 1) {
22618
+ throw new Error("exactly one of original_content or original_content_b64 must be set");
22619
+ }
22620
+ if (args.original_content_b64 !== void 0 && args.original_content_b64 !== null && (args.content_type === void 0 || args.content_type === null)) {
22621
+ 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');
22622
+ }
22542
22623
  const body = {};
22543
22624
  if (args.memory_id !== void 0)
22544
22625
  body["memoryId"] = args.memory_id;
@@ -22564,17 +22645,23 @@ function registerTools() {
22564
22645
  const result = await callApi("POST", `/v1/memories`, body);
22565
22646
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22566
22647
  });
22567
- server.tool("goodmem_memories_delete", "Delete a memory", {
22648
+ 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
22649
  id: external_exports.string().describe("The UUID of the memory to delete")
22569
22650
  }, async (args) => {
22651
+ if (typeof args.id !== "string" || args.id === "") {
22652
+ throw new Error("'id' must be a non-empty string");
22653
+ }
22570
22654
  await callApi("DELETE", `/v1/memories/${encodeURIComponent(String(args.id))}`, void 0);
22571
22655
  return { content: [{ type: "text", text: "Deleted successfully." }] };
22572
22656
  });
22573
- server.tool("goodmem_memories_get", "Get a memory by ID", {
22657
+ 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
22658
  id: external_exports.string().describe("The UUID of the memory to retrieve"),
22575
22659
  include_content: external_exports.boolean().describe("Whether to include the original content in the response (defaults to false).").optional(),
22576
22660
  include_processing_history: external_exports.boolean().describe("Whether to include background job processing history in the response (defaults to false).").optional()
22577
22661
  }, async (args) => {
22662
+ if (typeof args.id !== "string" || args.id === "") {
22663
+ throw new Error("'id' must be a non-empty string");
22664
+ }
22578
22665
  const query = {};
22579
22666
  if (args.include_content !== void 0)
22580
22667
  query["includeContent"] = String(args.include_content);
@@ -22583,17 +22670,20 @@ function registerTools() {
22583
22670
  const result = await callApi("GET", `/v1/memories/${encodeURIComponent(String(args.id))}`, void 0, query);
22584
22671
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22585
22672
  });
22586
- server.tool("goodmem_memories_list", "Lists memories within a given space. Results are paginated \u2014 the returned `Page` eagerly fetches the first page. Access `.data` for items and `.next_token` to resume. Use `for mem in page` to auto-paginate all pages, `page_size` to control items per API call, and `max_items` to cap total results.", {
22673
+ 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
22674
  space_id: external_exports.string().describe("The UUID of the space containing the memories"),
22588
22675
  include_content: external_exports.boolean().describe("Whether to include the original content in the response (defaults to false).").optional(),
22589
22676
  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(),
22677
+ status_filter: external_exports.enum(["PENDING", "PROCESSING", "COMPLETED", "FAILED"]).describe("Filter memories by processing status (PENDING, PROCESSING, COMPLETED, FAILED).").optional(),
22591
22678
  filter: external_exports.string().describe("Optional metadata filter expression for list results").optional(),
22592
22679
  max_results: external_exports.number().int().describe("Maximum number of results per page (defaults to 50, clamped to [1, 500]).").optional(),
22593
22680
  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(),
22681
+ sort_by: external_exports.enum(["created_at", "updated_at", "content_type", "processing_status"]).describe("Field to sort by (e.g., 'created_at').").optional(),
22595
22682
  sort_order: external_exports.enum(["ASCENDING", "DESCENDING", "SORT_ORDER_UNSPECIFIED"]).describe("Sort direction (ASCENDING or DESCENDING).").optional()
22596
22683
  }, async (args) => {
22684
+ if (typeof args.space_id !== "string" || args.space_id === "") {
22685
+ throw new Error("'space_id' must be a non-empty string");
22686
+ }
22597
22687
  const query = {};
22598
22688
  if (args.include_content !== void 0)
22599
22689
  query["includeContent"] = String(args.include_content);
@@ -22614,7 +22704,7 @@ function registerTools() {
22614
22704
  const result = await callApi("GET", `/v1/spaces/${encodeURIComponent(String(args.space_id))}/memories`, void 0, query);
22615
22705
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22616
22706
  });
22617
- server.tool("goodmem_memories_pages", "List memory page images", {
22707
+ server.tool("goodmem_memories_pages", "List memory page images\n\nLists extracted page-image metadata for a memory with optional filters and pagination.", {
22618
22708
  id: external_exports.string().describe("Memory UUID"),
22619
22709
  start_page_index: external_exports.number().int().describe("Optional lower bound for returned page indices, inclusive.").optional(),
22620
22710
  end_page_index: external_exports.number().int().describe("Optional upper bound for returned page indices, inclusive.").optional(),
@@ -22623,6 +22713,9 @@ function registerTools() {
22623
22713
  max_results: external_exports.number().int().describe("Maximum number of results per page.").optional(),
22624
22714
  next_token: external_exports.string().describe("Opaque pagination token for the next page. Do not parse or construct it.").optional()
22625
22715
  }, async (args) => {
22716
+ if (typeof args.id !== "string" || args.id === "") {
22717
+ throw new Error("'id' must be a non-empty string");
22718
+ }
22626
22719
  const query = {};
22627
22720
  if (args.start_page_index !== void 0)
22628
22721
  query["startPageIndex"] = String(args.start_page_index);
@@ -22650,12 +22743,18 @@ function registerTools() {
22650
22743
  }).passthrough()).describe("Optional context items (text or binary) to provide additional context for the search.").optional(),
22651
22744
  space_keys: external_exports.array(external_exports.object({
22652
22745
  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(),
22746
+ embedderWeights: external_exports.array(external_exports.object({
22747
+ embedderId: external_exports.string().describe("The UUID for the embedder."),
22748
+ weight: external_exports.number().describe("The weight to apply to this embedder's results. Can be positive, negative, or zero.")
22749
+ }).passthrough()).describe("Optional per-embedder weight overrides for this space. If not specified, database defaults are used.").optional(),
22654
22750
  filter: external_exports.string().describe("Optional filter expression that must evaluate to true for memories in this space.").optional()
22655
22751
  }).passthrough()).describe("List of spaces to search with optional per-embedder weight overrides."),
22656
22752
  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(),
22753
+ output_budget: external_exports.object({
22754
+ tokens: external_exports.number().int().describe("Token count for the budget. Must be positive.")
22755
+ }).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(),
22756
+ 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(),
22757
+ 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
22758
  hnsw: external_exports.object({
22660
22759
  efSearch: external_exports.number().int().describe("HNSW candidate list size (1..1000).").optional(),
22661
22760
  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 +22763,12 @@ function registerTools() {
22664
22763
  }).passthrough().describe("Optional request-level HNSW tuning overrides. Advanced usage; available on POST retrieve.").optional(),
22665
22764
  post_processor: external_exports.object({
22666
22765
  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()
22766
+ 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
22767
  }).passthrough().describe("Optional post-processor configuration to transform retrieval results.").optional(),
22669
22768
  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(),
22769
+ 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(),
22770
+ 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()
22771
+ }).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
22772
  timeout_sec: external_exports.number().int().describe("Maximum seconds to wait for the streaming response (default: 200).").optional()
22674
22773
  }, async (args) => {
22675
22774
  const body = {};
@@ -22681,6 +22780,8 @@ function registerTools() {
22681
22780
  body["spaceKeys"] = args.space_keys;
22682
22781
  if (args.requested_size !== void 0)
22683
22782
  body["requestedSize"] = args.requested_size;
22783
+ if (args.output_budget !== void 0)
22784
+ body["outputBudget"] = args.output_budget;
22684
22785
  if (args.fetch_memory !== void 0)
22685
22786
  body["fetchMemory"] = args.fetch_memory;
22686
22787
  if (args.fetch_memory_content !== void 0)
@@ -22694,7 +22795,7 @@ function registerTools() {
22694
22795
  const result = await callApiNdjson("POST", `/v1/memories:retrieve`, body, void 0, args.timeout_sec);
22695
22796
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22696
22797
  });
22697
- server.tool("goodmem_ocr_document", "Run OCR on a document or image. Pass base64-encoded document bytes as `content`. Optionally specify `format` (e.g., PDF, PNG) \u2014 if omitted, the server auto-detects.", {
22798
+ 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
22799
  content: external_exports.string().describe("Base64-encoded document bytes"),
22699
22800
  format: external_exports.enum(["AUTO", "PDF", "TIFF", "PNG", "JPEG", "BMP"]).describe("Input format hint (AUTO, PDF, TIFF, PNG, JPEG, BMP)").optional(),
22700
22801
  include_raw_json: external_exports.boolean().describe("Include raw OCR JSON payload in the response").optional(),
@@ -22718,7 +22819,79 @@ function registerTools() {
22718
22819
  const result = await callApi("POST", `/v1/ocr:document`, body);
22719
22820
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22720
22821
  });
22721
- server.tool("goodmem_rerankers_create", "Create a new reranker", {
22822
+ server.tool("goodmem_ping_once", "Run a single ping probe\n\nRuns a single ping probe and returns the probe result.", {
22823
+ target_id: external_exports.string().describe("Target resource ID (UUID)"),
22824
+ target_type_hint: external_exports.enum(["TARGET_TYPE_UNSPECIFIED", "EMBEDDER", "RERANKER", "LLM"]).describe("Optional hint for the target resource type").optional(),
22825
+ payload_type: external_exports.enum(["PAYLOAD_TYPE_UNSPECIFIED", "TEXT", "JSON", "BINARY"]).describe("Desired payload type (defaults to provider-specific value)").optional(),
22826
+ payload: external_exports.string().describe("Explicit UTF-8 payload to send with the probe (mutually exclusive with payload_size_bytes)").optional(),
22827
+ payload_size_bytes: external_exports.number().int().describe("Synthetic payload size in bytes (mutually exclusive with payload)").optional(),
22828
+ timeout_ms: external_exports.number().int().describe("Per-probe timeout in milliseconds (0 uses server default)").optional()
22829
+ }, async (args) => {
22830
+ const _set = [args.payload !== void 0 && args.payload !== null, args.payload_size_bytes !== void 0 && args.payload_size_bytes !== null].filter(Boolean).length;
22831
+ if (_set > 1) {
22832
+ throw new Error("at most one of payload or payload_size_bytes may be set");
22833
+ }
22834
+ const body = {};
22835
+ if (args.target_id !== void 0)
22836
+ body["targetId"] = args.target_id;
22837
+ if (args.target_type_hint !== void 0)
22838
+ body["targetTypeHint"] = args.target_type_hint;
22839
+ if (args.payload_type !== void 0)
22840
+ body["payloadType"] = args.payload_type;
22841
+ if (args.payload !== void 0)
22842
+ body["payload"] = args.payload;
22843
+ if (args.payload_size_bytes !== void 0)
22844
+ body["payloadSizeBytes"] = args.payload_size_bytes;
22845
+ if (args.timeout_ms !== void 0)
22846
+ body["timeoutMs"] = args.timeout_ms;
22847
+ const result = await callApi("POST", `/v1/ping:once`, body, void 0, (args.timeout_ms ?? 5e3) + 3e4);
22848
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22849
+ });
22850
+ server.tool("goodmem_ping_stream", "Stream ping probe results\n\nOpens a streaming ping session and returns per-probe results plus a terminal summary.", {
22851
+ target_id: external_exports.string().describe("Target resource ID (UUID)"),
22852
+ target_type_hint: external_exports.enum(["TARGET_TYPE_UNSPECIFIED", "EMBEDDER", "RERANKER", "LLM"]).describe("Optional hint for the target resource type").optional(),
22853
+ count: external_exports.number().int().describe("Number of probes to run (0 uses server default)").optional(),
22854
+ interval_ms: external_exports.number().int().describe("Delay between probes in milliseconds (0 uses server default)").optional(),
22855
+ timeout_ms: external_exports.number().int().describe("Per-probe timeout in milliseconds (0 uses server default)").optional(),
22856
+ payload_type: external_exports.enum(["PAYLOAD_TYPE_UNSPECIFIED", "TEXT", "JSON", "BINARY"]).describe("Desired payload type (defaults to provider-specific value)").optional(),
22857
+ payload: external_exports.string().describe("Explicit UTF-8 payload to send with each probe (mutually exclusive with payload_size_bytes)").optional(),
22858
+ payload_size_bytes: external_exports.number().int().describe("Synthetic payload size in bytes (mutually exclusive with payload)").optional(),
22859
+ max_in_flight: external_exports.number().int().describe("Maximum concurrent probes (defaults to 1)").optional(),
22860
+ jitter: external_exports.boolean().describe("Add jitter to probe scheduling").optional(),
22861
+ labels: external_exports.record(external_exports.string(), external_exports.string()).describe("Optional labels to attach to the ping session").optional(),
22862
+ timeout_sec: external_exports.number().int().describe("Maximum seconds to wait for the streaming response (default: 200).").optional()
22863
+ }, async (args) => {
22864
+ const _set = [args.payload !== void 0 && args.payload !== null, args.payload_size_bytes !== void 0 && args.payload_size_bytes !== null].filter(Boolean).length;
22865
+ if (_set > 1) {
22866
+ throw new Error("at most one of payload or payload_size_bytes may be set");
22867
+ }
22868
+ const body = {};
22869
+ if (args.target_id !== void 0)
22870
+ body["targetId"] = args.target_id;
22871
+ if (args.target_type_hint !== void 0)
22872
+ body["targetTypeHint"] = args.target_type_hint;
22873
+ if (args.count !== void 0)
22874
+ body["count"] = args.count;
22875
+ if (args.interval_ms !== void 0)
22876
+ body["intervalMs"] = args.interval_ms;
22877
+ if (args.timeout_ms !== void 0)
22878
+ body["timeoutMs"] = args.timeout_ms;
22879
+ if (args.payload_type !== void 0)
22880
+ body["payloadType"] = args.payload_type;
22881
+ if (args.payload !== void 0)
22882
+ body["payload"] = args.payload;
22883
+ if (args.payload_size_bytes !== void 0)
22884
+ body["payloadSizeBytes"] = args.payload_size_bytes;
22885
+ if (args.max_in_flight !== void 0)
22886
+ body["maxInFlight"] = args.max_in_flight;
22887
+ if (args.jitter !== void 0)
22888
+ body["jitter"] = args.jitter;
22889
+ if (args.labels !== void 0)
22890
+ body["labels"] = args.labels;
22891
+ 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));
22892
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22893
+ });
22894
+ 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
22895
  display_name: external_exports.string().describe("User-facing name of the reranker"),
22723
22896
  description: external_exports.string().describe("Description of the reranker").optional(),
22724
22897
  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,16 +22905,16 @@ function registerTools() {
22732
22905
  inlineSecret: external_exports.string().describe("Secret stored directly in GoodMem (mutually exclusive with secretRef)").optional(),
22733
22906
  secretRef: external_exports.object({
22734
22907
  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()
22908
+ 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
22909
  }).passthrough().describe("Reference to an external secret manager entry (mutually exclusive with inlineSecret)").optional(),
22737
22910
  headerName: external_exports.string().describe("Desired HTTP header to carry the credential (defaults to Authorization)").optional(),
22738
22911
  prefix: external_exports.string().describe('Optional prefix prepended to the secret (e.g., "Bearer ")').optional()
22739
22912
  }).passthrough().describe("Configuration when kind is CREDENTIAL_KIND_API_KEY").optional(),
22740
22913
  gcpAdc: external_exports.object({
22741
- scopes: external_exports.any().describe("Additional OAuth scopes. Empty list falls back to the default cloud-platform scope.").optional(),
22914
+ scopes: external_exports.array(external_exports.string()).describe("Additional OAuth scopes. Empty list falls back to the default cloud-platform scope.").optional(),
22742
22915
  quotaProjectId: external_exports.string().describe("Optional quota project used for billing").optional()
22743
22916
  }).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()
22917
+ labels: external_exports.record(external_exports.string(), external_exports.string()).describe('Optional annotations to aid operators (e.g., "owner=vertex")').optional()
22745
22918
  }).passthrough().describe("Structured credential payload describing how to authenticate with the provider. Required for SaaS providers; optional for local or proxy providers.").optional(),
22746
22919
  labels: external_exports.record(external_exports.string(), external_exports.string()).describe("User-defined labels for categorization").optional(),
22747
22920
  version: external_exports.string().describe("Version information").optional(),
@@ -22781,19 +22954,25 @@ function registerTools() {
22781
22954
  const result = await callApi("POST", `/v1/rerankers`, body);
22782
22955
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22783
22956
  });
22784
- server.tool("goodmem_rerankers_delete", "Delete a reranker", {
22957
+ 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
22958
  id: external_exports.string().describe("The unique identifier of the reranker to delete")
22786
22959
  }, async (args) => {
22960
+ if (typeof args.id !== "string" || args.id === "") {
22961
+ throw new Error("'id' must be a non-empty string");
22962
+ }
22787
22963
  await callApi("DELETE", `/v1/rerankers/${encodeURIComponent(String(args.id))}`, void 0);
22788
22964
  return { content: [{ type: "text", text: "Deleted successfully." }] };
22789
22965
  });
22790
- server.tool("goodmem_rerankers_get", "Get a reranker by ID", {
22966
+ 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
22967
  id: external_exports.string().describe("The unique identifier of the reranker to retrieve")
22792
22968
  }, async (args) => {
22969
+ if (typeof args.id !== "string" || args.id === "") {
22970
+ throw new Error("'id' must be a non-empty string");
22971
+ }
22793
22972
  const result = await callApi("GET", `/v1/rerankers/${encodeURIComponent(String(args.id))}`, void 0);
22794
22973
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22795
22974
  });
22796
- server.tool("goodmem_rerankers_list", "List rerankers", {
22975
+ 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
22976
  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
22977
  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
22978
  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 +22989,43 @@ function registerTools() {
22810
22989
  const result = await callApi("GET", `/v1/rerankers`, void 0, query);
22811
22990
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22812
22991
  });
22813
- server.tool("goodmem_rerankers_update", "Update a reranker", {
22992
+ 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
22993
  id: external_exports.string().describe("The unique identifier of the reranker to update"),
22815
22994
  display_name: external_exports.string().describe("User-facing name of the reranker").optional(),
22816
22995
  description: external_exports.string().describe("Description of the reranker").optional(),
22817
22996
  endpoint_url: external_exports.string().describe("API endpoint URL").optional(),
22818
22997
  api_path: external_exports.string().describe("API path for reranking request").optional(),
22819
22998
  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(),
22999
+ 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
23000
  credentials: external_exports.object({
22822
23001
  kind: external_exports.enum(["CREDENTIAL_KIND_UNSPECIFIED", "CREDENTIAL_KIND_API_KEY", "CREDENTIAL_KIND_GCP_ADC"]).describe("Selected credential strategy"),
22823
23002
  apiKey: external_exports.object({
22824
23003
  inlineSecret: external_exports.string().describe("Secret stored directly in GoodMem (mutually exclusive with secretRef)").optional(),
22825
23004
  secretRef: external_exports.object({
22826
23005
  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()
23006
+ 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
23007
  }).passthrough().describe("Reference to an external secret manager entry (mutually exclusive with inlineSecret)").optional(),
22829
23008
  headerName: external_exports.string().describe("Desired HTTP header to carry the credential (defaults to Authorization)").optional(),
22830
23009
  prefix: external_exports.string().describe('Optional prefix prepended to the secret (e.g., "Bearer ")').optional()
22831
23010
  }).passthrough().describe("Configuration when kind is CREDENTIAL_KIND_API_KEY").optional(),
22832
23011
  gcpAdc: external_exports.object({
22833
- scopes: external_exports.any().describe("Additional OAuth scopes. Empty list falls back to the default cloud-platform scope.").optional(),
23012
+ scopes: external_exports.array(external_exports.string()).describe("Additional OAuth scopes. Empty list falls back to the default cloud-platform scope.").optional(),
22834
23013
  quotaProjectId: external_exports.string().describe("Optional quota project used for billing").optional()
22835
23014
  }).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()
23015
+ labels: external_exports.record(external_exports.string(), external_exports.string()).describe('Optional annotations to aid operators (e.g., "owner=vertex")').optional()
22837
23016
  }).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(),
23017
+ replace_labels: external_exports.record(external_exports.string(), external_exports.string()).describe("Replace all existing labels with these (mutually exclusive with merge_labels)").optional(),
23018
+ 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
23019
  version: external_exports.string().describe("Version information").optional(),
22841
23020
  monitoring_endpoint: external_exports.string().describe("Monitoring endpoint URL").optional()
22842
23021
  }, async (args) => {
23022
+ if (typeof args.id !== "string" || args.id === "") {
23023
+ throw new Error("'id' must be a non-empty string");
23024
+ }
23025
+ const _set = [args.replace_labels !== void 0 && args.replace_labels !== null, args.merge_labels !== void 0 && args.merge_labels !== null].filter(Boolean).length;
23026
+ if (_set > 1) {
23027
+ throw new Error("at most one of replace_labels or merge_labels may be set");
23028
+ }
22843
23029
  const body = {};
22844
23030
  if (args.display_name !== void 0)
22845
23031
  body["displayName"] = args.display_name;
@@ -22866,13 +23052,13 @@ function registerTools() {
22866
23052
  const result = await callApi("PUT", `/v1/rerankers/${encodeURIComponent(String(args.id))}`, body);
22867
23053
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22868
23054
  });
22869
- server.tool("goodmem_spaces_create", "Create a new Space", {
23055
+ 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
23056
  name: external_exports.string().describe("The desired name for the space. Must be unique within the user's scope."),
22871
23057
  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
23058
  space_embedders: external_exports.array(external_exports.object({
22873
23059
  embedderId: external_exports.string().describe("The UUID for the embedder to associate with the space."),
22874
23060
  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."),
23061
+ }).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
23062
  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
23063
  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
23064
  default_chunking_config: external_exports.object({
@@ -22880,7 +23066,7 @@ function registerTools() {
22880
23066
  recursive: external_exports.object({
22881
23067
  chunkSize: external_exports.number().int().describe("Maximum size of a chunk (should be \u2264 context window)"),
22882
23068
  chunkOverlap: external_exports.number().int().describe("Sliding overlap between chunks"),
22883
- separators: external_exports.any().describe("Hierarchical separator list (order = preference)").optional(),
23069
+ separators: external_exports.array(external_exports.string()).describe("Hierarchical separator list (order = preference)").optional(),
22884
23070
  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
23071
  separatorIsRegex: external_exports.boolean().describe("Whether separators are regex patterns").optional(),
22886
23072
  lengthMeasurement: external_exports.enum(["CHARACTER_COUNT", "TOKEN_COUNT", "CUSTOM"]).describe("How to measure chunk length")
@@ -22912,24 +23098,30 @@ function registerTools() {
22912
23098
  const result = await callApi("POST", `/v1/spaces`, body);
22913
23099
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22914
23100
  });
22915
- server.tool("goodmem_spaces_delete", "Delete a space", {
23101
+ 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
23102
  id: external_exports.string().describe("The unique identifier of the space to delete")
22917
23103
  }, async (args) => {
23104
+ if (typeof args.id !== "string" || args.id === "") {
23105
+ throw new Error("'id' must be a non-empty string");
23106
+ }
22918
23107
  await callApi("DELETE", `/v1/spaces/${encodeURIComponent(String(args.id))}`, void 0);
22919
23108
  return { content: [{ type: "text", text: "Deleted successfully." }] };
22920
23109
  });
22921
- server.tool("goodmem_spaces_get", "Get a space by ID", {
23110
+ 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
23111
  id: external_exports.string().describe("The unique identifier of the space to retrieve")
22923
23112
  }, async (args) => {
23113
+ if (typeof args.id !== "string" || args.id === "") {
23114
+ throw new Error("'id' must be a non-empty string");
23115
+ }
22924
23116
  const result = await callApi("GET", `/v1/spaces/${encodeURIComponent(String(args.id))}`, void 0);
22925
23117
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22926
23118
  });
22927
- server.tool("goodmem_spaces_list", "List spaces accessible to the caller, with optional filtering by owner, labels, and name. Results are paginated \u2014 the returned `Page` eagerly fetches the first page. Access `.data` for items and `.next_token` to resume. Use `for space in page` to auto-paginate all pages, `page_size` to control items per API call, and `max_items` to cap total results.", {
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(),
23119
+ 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.", {
23120
+ 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
23121
  name_filter: external_exports.string().describe("Filter spaces by name using glob pattern matching").optional(),
22930
23122
  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
23123
  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(),
23124
+ 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
23125
  sort_order: external_exports.enum(["ASCENDING", "DESCENDING", "SORT_ORDER_UNSPECIFIED"]).describe("Sort order (ASCENDING or DESCENDING, default: DESCENDING)").optional(),
22934
23126
  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
23127
  }, async (args) => {
@@ -22953,13 +23145,20 @@ function registerTools() {
22953
23145
  const result = await callApi("GET", `/v1/spaces`, void 0, query);
22954
23146
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22955
23147
  });
22956
- server.tool("goodmem_spaces_update", "Update a space", {
23148
+ 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
23149
  id: external_exports.string().describe("The unique identifier of the space to update"),
22958
23150
  name: external_exports.string().describe("The new name for the space.").optional(),
22959
23151
  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()
23152
+ replace_labels: external_exports.record(external_exports.string(), external_exports.string()).describe("Labels to replace all existing labels. Mutually exclusive with merge_labels.").optional(),
23153
+ 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
23154
  }, async (args) => {
23155
+ if (typeof args.id !== "string" || args.id === "") {
23156
+ throw new Error("'id' must be a non-empty string");
23157
+ }
23158
+ const _set = [args.replace_labels !== void 0 && args.replace_labels !== null, args.merge_labels !== void 0 && args.merge_labels !== null].filter(Boolean).length;
23159
+ if (_set > 1) {
23160
+ throw new Error("at most one of replace_labels or merge_labels may be set");
23161
+ }
22963
23162
  const body = {};
22964
23163
  if (args.name !== void 0)
22965
23164
  body["name"] = args.name;
@@ -22972,27 +23171,29 @@ function registerTools() {
22972
23171
  const result = await callApi("PUT", `/v1/spaces/${encodeURIComponent(String(args.id))}`, body);
22973
23172
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22974
23173
  });
22975
- server.tool("goodmem_system_info", "Retrieve server build metadata", {}, async (args) => {
23174
+ 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
23175
  const result = await callApi("GET", `/v1/system/info`, void 0);
22977
23176
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22978
23177
  });
22979
- server.tool("goodmem_system_init", "Initialize the system", {}, async (args) => {
23178
+ 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
23179
  const result = await callApi("POST", `/v1/system/init`, void 0);
22981
23180
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22982
23181
  });
22983
- 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 `client.users.me()`.", {
23182
+ 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
23183
  id: external_exports.string().describe("The UUID of the user to retrieve").optional(),
22985
23184
  email: external_exports.string().describe("The email address of the user to retrieve").optional()
22986
23185
  }, async (args) => {
22987
- if (args.id !== void 0 && args.email !== void 0)
23186
+ const _hasId = typeof args.id === "string" && args.id !== "";
23187
+ const _hasEmail = typeof args.email === "string" && args.email !== "";
23188
+ if (_hasId && _hasEmail)
22988
23189
  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))}`;
23190
+ if (!_hasId && !_hasEmail)
23191
+ throw new Error("Exactly one of 'id', 'email' must be provided (non-empty).");
23192
+ const url = _hasEmail ? `/v1/users/email/${encodeURIComponent(String(args.email))}` : `/v1/users/${encodeURIComponent(String(args.id))}`;
22992
23193
  const result = await callApi("GET", url, void 0);
22993
23194
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22994
23195
  });
22995
- server.tool("goodmem_users_me", "Get current user profile", {}, async (args) => {
23196
+ 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
23197
  const result = await callApi("GET", `/v1/users/me`, void 0);
22997
23198
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22998
23199
  });
@@ -23004,17 +23205,19 @@ async function healthCheck() {
23004
23205
  return;
23005
23206
  }
23006
23207
  try {
23007
- const url = new URL("/v1/system/info", goodmemBaseUrl);
23208
+ const url = _buildUrl(goodmemBaseUrl, "/v1/system/info");
23008
23209
  const resp = await fetch(url.toString(), {
23009
23210
  headers: { "x-api-key": goodmemApiKey },
23010
- signal: AbortSignal.timeout(1e4)
23211
+ signal: AbortSignal.timeout(1e4),
23212
+ // See callApi for the redirect-policy rationale.
23213
+ redirect: "error"
23011
23214
  });
23012
23215
  if (resp.ok) {
23013
23216
  const info = await resp.json();
23014
23217
  console.error(`[goodmem-mcp] Connected to ${goodmemBaseUrl} (server ${info.version ?? "unknown"}).`);
23015
23218
  } else {
23016
23219
  const text = await resp.text();
23017
- console.error(`[goodmem-mcp] Health check failed (${resp.status}): ${text}`);
23220
+ console.error(`[goodmem-mcp] Health check failed (${resp.status}): ${_trimBody(text)}`);
23018
23221
  }
23019
23222
  } catch (err) {
23020
23223
  const msg = err instanceof Error ? err.message : String(err);