@pairsystems/goodmem-mcp 0.1.17 → 0.1.18

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 +51 -3
  2. package/dist/index.js +797 -155
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -20988,7 +20988,21 @@ 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 = "d15028640e4a884f3a648db9b25aee4cdfab719d";
20991
+ var DEFAULT_STREAM_MAX_LINE_BYTES = 8 * 1024 * 1024;
20992
+ function _parseStreamMaxLineBytes(raw) {
20993
+ if (!raw)
20994
+ return 0;
20995
+ const n = Number(raw);
20996
+ return Number.isFinite(n) && n > 0 ? Math.floor(n) : 0;
20997
+ }
20998
+ var goodmemStreamMaxLineBytes = _parseStreamMaxLineBytes(process.env.GOODMEM_STREAM_MAX_LINE_BYTES);
20999
+ function _concatBytes(a, b) {
21000
+ const out = new Uint8Array(a.length + b.length);
21001
+ out.set(a, 0);
21002
+ out.set(b, a.length);
21003
+ return out;
21004
+ }
21005
+ var BASED_ON_GOODMEM_COMMIT = "d868460a59745bd219c2b630d843ef6b555e7d35";
20992
21006
  function isConfigured() {
20993
21007
  return goodmemBaseUrl !== "";
20994
21008
  }
@@ -21104,18 +21118,61 @@ async function callApiNdjson(method, path, body, query, timeoutSec) {
21104
21118
  ...body ? { body: JSON.stringify(body) } : {}
21105
21119
  });
21106
21120
  if (!resp.ok) {
21107
- const text2 = await resp.text();
21108
- throw new Error(`GoodMem API error ${resp.status}: ${_trimBody(text2)}`);
21121
+ const text = await resp.text();
21122
+ throw new Error(`GoodMem API error ${resp.status}: ${_trimBody(text)}`);
21109
21123
  }
21110
- const text = await resp.text();
21111
- const lines = text.split("\n").map((line) => line.trim()).filter(Boolean);
21112
- return lines.map((line, i) => {
21124
+ const maxLineBytes = goodmemStreamMaxLineBytes > 0 ? goodmemStreamMaxLineBytes : DEFAULT_STREAM_MAX_LINE_BYTES;
21125
+ const results = [];
21126
+ if (resp.body === null) {
21127
+ return results;
21128
+ }
21129
+ const _overCap = () => new Error(`stream event exceeded the ${maxLineBytes}-byte per-event line cap; raise it via the goodmem_configure stream_max_line_bytes option (or the GOODMEM_STREAM_MAX_LINE_BYTES env var), or avoid inlining large bodies by leaving fetch_memory_content unset and fetching content via the memory content tool`);
21130
+ const reader = resp.body.getReader();
21131
+ const decoder = new TextDecoder("utf-8");
21132
+ let pending = new Uint8Array(0);
21133
+ let lineNo = 0;
21134
+ const _emit = (bytes) => {
21135
+ const line = decoder.decode(bytes).trim();
21136
+ if (!line)
21137
+ return;
21138
+ lineNo++;
21113
21139
  try {
21114
- return JSON.parse(line);
21140
+ results.push(JSON.parse(line));
21115
21141
  } catch (e) {
21116
- throw new Error(`NDJSON parse error on line ${i + 1}: ${e instanceof Error ? e.message : String(e)}`);
21142
+ throw new Error(`NDJSON parse error on line ${lineNo}: ${e instanceof Error ? e.message : String(e)}`);
21117
21143
  }
21118
- });
21144
+ };
21145
+ try {
21146
+ for (; ; ) {
21147
+ const { done, value } = await reader.read();
21148
+ if (done)
21149
+ break;
21150
+ if (value === void 0 || value.length === 0)
21151
+ continue;
21152
+ pending = pending.length === 0 ? value : _concatBytes(pending, value);
21153
+ let nl;
21154
+ while ((nl = pending.indexOf(10)) !== -1) {
21155
+ const lineBytes = pending.subarray(0, nl);
21156
+ if (lineBytes.length > maxLineBytes)
21157
+ throw _overCap();
21158
+ _emit(lineBytes);
21159
+ pending = pending.subarray(nl + 1);
21160
+ }
21161
+ if (pending.length > maxLineBytes)
21162
+ throw _overCap();
21163
+ }
21164
+ if (pending.length > 0) {
21165
+ if (pending.length > maxLineBytes)
21166
+ throw _overCap();
21167
+ _emit(pending);
21168
+ }
21169
+ } finally {
21170
+ try {
21171
+ reader.releaseLock();
21172
+ } catch {
21173
+ }
21174
+ }
21175
+ return results;
21119
21176
  }
21120
21177
  var MODEL_REGISTRY = {
21121
21178
  "embedders": {
@@ -21220,15 +21277,74 @@ var MODEL_REGISTRY = {
21220
21277
  ],
21221
21278
  "maxSequenceLength": 512
21222
21279
  },
21223
- "jina-embeddings-v4": {
21280
+ "jina-embeddings-v5-text-small": {
21224
21281
  "providerType": "JINA",
21225
21282
  "dimensions": {
21226
21283
  "default": 1024,
21227
- "allowed": [
21228
- 128,
21229
- 256,
21230
- 512,
21231
- 1024,
21284
+ "range": [
21285
+ 1,
21286
+ 1024
21287
+ ]
21288
+ },
21289
+ "supportedModalities": [
21290
+ "TEXT"
21291
+ ],
21292
+ "maxSequenceLength": 32768
21293
+ },
21294
+ "jina-embeddings-v5-text-nano": {
21295
+ "providerType": "JINA",
21296
+ "dimensions": {
21297
+ "default": 768,
21298
+ "range": [
21299
+ 1,
21300
+ 768
21301
+ ]
21302
+ },
21303
+ "supportedModalities": [
21304
+ "TEXT"
21305
+ ],
21306
+ "maxSequenceLength": 8192
21307
+ },
21308
+ "jina-embeddings-v5-omni-small": {
21309
+ "providerType": "JINA",
21310
+ "dimensions": {
21311
+ "default": 1024,
21312
+ "range": [
21313
+ 1,
21314
+ 1024
21315
+ ]
21316
+ },
21317
+ "supportedModalities": [
21318
+ "TEXT",
21319
+ "IMAGE",
21320
+ "AUDIO",
21321
+ "VIDEO"
21322
+ ],
21323
+ "maxSequenceLength": 32768
21324
+ },
21325
+ "jina-embeddings-v5-omni-nano": {
21326
+ "providerType": "JINA",
21327
+ "dimensions": {
21328
+ "default": 768,
21329
+ "range": [
21330
+ 1,
21331
+ 768
21332
+ ]
21333
+ },
21334
+ "supportedModalities": [
21335
+ "TEXT",
21336
+ "IMAGE",
21337
+ "AUDIO",
21338
+ "VIDEO"
21339
+ ],
21340
+ "maxSequenceLength": 8192
21341
+ },
21342
+ "jina-embeddings-v4": {
21343
+ "providerType": "JINA",
21344
+ "dimensions": {
21345
+ "default": 1536,
21346
+ "range": [
21347
+ 1,
21232
21348
  2048
21233
21349
  ]
21234
21350
  },
@@ -21238,17 +21354,40 @@ var MODEL_REGISTRY = {
21238
21354
  ],
21239
21355
  "maxSequenceLength": 32768
21240
21356
  },
21357
+ "jina-code-embeddings-1.5b": {
21358
+ "providerType": "JINA",
21359
+ "dimensions": {
21360
+ "default": 1536,
21361
+ "range": [
21362
+ 1,
21363
+ 1536
21364
+ ]
21365
+ },
21366
+ "supportedModalities": [
21367
+ "TEXT"
21368
+ ],
21369
+ "maxSequenceLength": 32768
21370
+ },
21371
+ "jina-code-embeddings-0.5b": {
21372
+ "providerType": "JINA",
21373
+ "dimensions": {
21374
+ "default": 896,
21375
+ "range": [
21376
+ 1,
21377
+ 896
21378
+ ]
21379
+ },
21380
+ "supportedModalities": [
21381
+ "TEXT"
21382
+ ],
21383
+ "maxSequenceLength": 32768
21384
+ },
21241
21385
  "jina-embeddings-v3": {
21242
21386
  "providerType": "JINA",
21243
21387
  "dimensions": {
21244
21388
  "default": 1024,
21245
- "allowed": [
21246
- 32,
21247
- 64,
21248
- 128,
21249
- 256,
21250
- 512,
21251
- 768,
21389
+ "range": [
21390
+ 1,
21252
21391
  1024
21253
21392
  ]
21254
21393
  },
@@ -21340,12 +21479,8 @@ var MODEL_REGISTRY = {
21340
21479
  "providerType": "JINA",
21341
21480
  "dimensions": {
21342
21481
  "default": 1024,
21343
- "allowed": [
21344
- 64,
21345
- 128,
21346
- 256,
21347
- 512,
21348
- 768,
21482
+ "range": [
21483
+ 1,
21349
21484
  1024
21350
21485
  ]
21351
21486
  },
@@ -21403,6 +21538,22 @@ var MODEL_REGISTRY = {
21403
21538
  ],
21404
21539
  "maxSequenceLength": 32e3
21405
21540
  },
21541
+ "voyage-code-4": {
21542
+ "providerType": "VOYAGE",
21543
+ "dimensions": {
21544
+ "default": 1024,
21545
+ "allowed": [
21546
+ 256,
21547
+ 512,
21548
+ 1024,
21549
+ 2048
21550
+ ]
21551
+ },
21552
+ "supportedModalities": [
21553
+ "TEXT"
21554
+ ],
21555
+ "maxSequenceLength": 32e3
21556
+ },
21406
21557
  "voyage-code-3": {
21407
21558
  "providerType": "VOYAGE",
21408
21559
  "dimensions": {
@@ -21876,12 +22027,13 @@ function autoInferFromRegistry(registryType, args) {
21876
22027
  }
21877
22028
  var server = new McpServer({
21878
22029
  name: "goodmem",
21879
- version: "0.1.17"
22030
+ version: "0.1.18"
21880
22031
  });
21881
22032
  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.", {
21882
22033
  base_url: external_exports.string().describe("GoodMem server URL (e.g., https://your-server.example.com)"),
21883
- 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()
21884
- }, async (args) => {
22034
+ 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(),
22035
+ stream_max_line_bytes: external_exports.number().int().positive().describe("Maximum size in bytes of a single NDJSON stream event (memories.retrieve, ping.stream). Events larger than this are rejected instead of buffering without bound. Defaults to 8388608 (8 MiB).").optional()
22036
+ }, { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true }, async (args) => {
21885
22037
  if (!/^https?:\/\//i.test(args.base_url)) {
21886
22038
  return { isError: true, content: [{ type: "text", text: `base_url must include a scheme (e.g., http://${args.base_url} or https://${args.base_url}).` }] };
21887
22039
  }
@@ -21904,6 +22056,9 @@ server.tool("goodmem_configure", "Configure GoodMem server connection. Call this
21904
22056
  const info = await resp.json().catch(() => ({}));
21905
22057
  goodmemBaseUrl = args.base_url;
21906
22058
  goodmemApiKey = args.api_key ?? "";
22059
+ if (args.stream_max_line_bytes !== void 0) {
22060
+ goodmemStreamMaxLineBytes = args.stream_max_line_bytes;
22061
+ }
21907
22062
  const keyNote = goodmemApiKey === "" ? " (bootstrap-only session \u2014 no api_key set)" : "";
21908
22063
  return { content: [{ type: "text", text: `Connected to GoodMem at ${goodmemBaseUrl}. Server version: ${info.version ?? "unknown"}.${keyNote}` }] };
21909
22064
  } catch (err) {
@@ -21918,7 +22073,7 @@ server.tool("goodmem_configure", "Configure GoodMem server connection. Call this
21918
22073
  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.", {
21919
22074
  model_identifier: external_exports.string().describe("Model name to look up (e.g., text-embedding-3-large, gpt-4o, rerank-2)"),
21920
22075
  type: external_exports.enum(["embedder", "llm", "reranker"]).describe("Model type to search").optional()
21921
- }, async (args) => {
22076
+ }, { readOnlyHint: true, destructiveHint: false, openWorldHint: false }, async (args) => {
21922
22077
  const searches = args.type ? [[args.type + "s", args.type]] : [["embedders", "embedder"], ["llms", "llm"], ["rerankers", "reranker"]];
21923
22078
  for (const [registryKey, label] of searches) {
21924
22079
  const registry2 = MODEL_REGISTRY[registryKey] ?? {};
@@ -21956,19 +22111,166 @@ server.tool("goodmem_lookup_model", "Look up a model in the registry to see its
21956
22111
  return { content: [{ type: "text", text: `Model "${args.model_identifier}" not found in ${scope}. Available models:
21957
22112
  ${allModels.join("\n")}` }] };
21958
22113
  });
21959
- 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 () => {
22114
+ server.tool("goodmem_client_info", "Return the MCP client package version and the goodmem server commit it was built against. No server connection required.", {}, { readOnlyHint: true, destructiveHint: false, openWorldHint: false }, async () => {
21960
22115
  return { content: [{ type: "text", text: JSON.stringify({
21961
- mcp_version: "0.1.17",
22116
+ mcp_version: "0.1.18",
21962
22117
  based_on_goodmem_commit: BASED_ON_GOODMEM_COMMIT
21963
22118
  }, null, 2) }] };
21964
22119
  });
21965
22120
  function registerTools() {
22121
+ server.tool("goodmem_access_policy_check", "Check effective authorization\n\nEvaluates 1 to 50 concrete operation-and-target checks under the authenticated caller's live authority and any API-key ceiling. Results are positional and advisory: missing targets and denied operations both return allowed=false, and every later resource request performs fresh authorization. Top-level creates target INSTANCE; CREATE_MEMORY and LIST_MEMORY target their parent SPACE; reads, mutations, proxy operations, and access-policy administration target concrete resources. LIST_API_KEY and LIST_RETRIEVE_MEMORY_LOG_POLICY are rejected because their current candidate-based list rules have no instance-wide preflight.", {
22122
+ checks: external_exports.array(external_exports.object({
22123
+ operation: external_exports.enum(["CREATE_USER", "READ_USER", "UPDATE_USER", "DELETE_USER", "LIST_USER", "MANAGE_USER_ENROLLMENT", "CREATE_SERVICE_IDENTITY", "READ_SERVICE_IDENTITY", "UPDATE_SERVICE_IDENTITY", "DELETE_SERVICE_IDENTITY", "LIST_SERVICE_IDENTITY", "CREATE_SPACE", "READ_SPACE", "UPDATE_SPACE", "DELETE_SPACE", "LIST_SPACE", "CREATE_API_KEY", "READ_API_KEY", "UPDATE_API_KEY", "DELETE_API_KEY", "LIST_API_KEY", "CREATE_EMBEDDER", "READ_EMBEDDER", "UPDATE_EMBEDDER", "DELETE_EMBEDDER", "LIST_EMBEDDER", "PING_EMBEDDER", "EXECUTE_EMBEDDER", "READ_EMBEDDER_CREDENTIALS", "CREATE_RERANKER", "READ_RERANKER", "UPDATE_RERANKER", "DELETE_RERANKER", "LIST_RERANKER", "PING_RERANKER", "EXECUTE_RERANKER", "READ_RERANKER_CREDENTIALS", "CREATE_LLM", "READ_LLM", "UPDATE_LLM", "DELETE_LLM", "LIST_LLM", "PING_LLM", "EXECUTE_LLM", "READ_LLM_CREDENTIALS", "PROXY_INFERENCE_TARGET", "OCR_DOCUMENT", "CREATE_MEMORY", "READ_MEMORY", "DELETE_MEMORY", "LIST_MEMORY", "CREATE_EXTENSION", "READ_EXTENSION", "UPDATE_EXTENSION", "DELETE_EXTENSION", "LIST_EXTENSION", "DOWNLOAD_EXTENSION", "READ_INSTANCE", "TRANSFER_INSTANCE_OWNERSHIP", "TRANSFER_RESOURCE_OWNERSHIP", "RELOAD_LICENSE", "DRAIN_SERVER", "PURGE_BACKGROUND_JOBS", "CREATE_RETRIEVE_MEMORY_LOG_POLICY", "READ_RETRIEVE_MEMORY_LOG_POLICY", "LIST_RETRIEVE_MEMORY_LOG_POLICY", "DELETE_RETRIEVE_MEMORY_LOG_POLICY", "MANAGE_ACCESS"]).describe("Operation the caller proposes to perform."),
22124
+ target: external_exports.object({
22125
+ kind: external_exports.enum(["INSTANCE", "USER", "SERVICE_IDENTITY", "SPACE", "API_KEY", "EMBEDDER", "RERANKER", "LLM", "MEMORY", "EXTENSION", "RETRIEVE_MEMORY_LOG_POLICY"]).describe("Concrete target kind."),
22126
+ resourceId: external_exports.string().describe("Concrete resource UUID; omitted for the singleton INSTANCE target.").optional()
22127
+ }).passthrough().describe("Target required by the operation: INSTANCE for top-level creates, parent SPACE for CREATE_MEMORY or LIST_MEMORY, otherwise the concrete resource.")
22128
+ }).passthrough()).describe("Concrete checks evaluated in request order.")
22129
+ }, { readOnlyHint: true, destructiveHint: false, openWorldHint: false }, async (args) => {
22130
+ const body = {};
22131
+ if (args.checks !== void 0)
22132
+ body["checks"] = args.checks;
22133
+ const result = await callApi("POST", `/v1/access-policy:check`, body);
22134
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22135
+ });
22136
+ server.tool("goodmem_access_policy_grants_create", "Create an authorization grant\n\nCreates one direct grant after resolving its typed policy target and requiring MANAGE_ACCESS. Direct grants cannot confer credential-read or ownership-transfer authority. ALL_AUTHENTICATED grants require an assigned-resource selector. MANAGE_ACCESS and MANAGE_USER_ENROLLMENT require a concrete principal and ANY or EXACT; MANAGE_USER_ENROLLMENT with EXACT must target USER.", {
22137
+ grant_id: external_exports.string().describe("Optional caller-provided grant UUID.").optional(),
22138
+ audience: external_exports.object({
22139
+ principalId: external_exports.string().describe("Active HUMAN or SERVICE principal UUID.").optional(),
22140
+ allAuthenticated: external_exports.literal(true).describe("Set to true to address every authenticated principal.").optional()
22141
+ }).passthrough().describe("Audience receiving the grant."),
22142
+ rule: external_exports.object({
22143
+ operation: external_exports.enum(["CREATE_USER", "READ_USER", "UPDATE_USER", "DELETE_USER", "LIST_USER", "MANAGE_USER_ENROLLMENT", "CREATE_SERVICE_IDENTITY", "READ_SERVICE_IDENTITY", "UPDATE_SERVICE_IDENTITY", "DELETE_SERVICE_IDENTITY", "LIST_SERVICE_IDENTITY", "CREATE_SPACE", "READ_SPACE", "UPDATE_SPACE", "DELETE_SPACE", "LIST_SPACE", "CREATE_API_KEY", "READ_API_KEY", "UPDATE_API_KEY", "DELETE_API_KEY", "LIST_API_KEY", "CREATE_EMBEDDER", "READ_EMBEDDER", "UPDATE_EMBEDDER", "DELETE_EMBEDDER", "LIST_EMBEDDER", "PING_EMBEDDER", "EXECUTE_EMBEDDER", "READ_EMBEDDER_CREDENTIALS", "CREATE_RERANKER", "READ_RERANKER", "UPDATE_RERANKER", "DELETE_RERANKER", "LIST_RERANKER", "PING_RERANKER", "EXECUTE_RERANKER", "READ_RERANKER_CREDENTIALS", "CREATE_LLM", "READ_LLM", "UPDATE_LLM", "DELETE_LLM", "LIST_LLM", "PING_LLM", "EXECUTE_LLM", "READ_LLM_CREDENTIALS", "PROXY_INFERENCE_TARGET", "OCR_DOCUMENT", "CREATE_MEMORY", "READ_MEMORY", "DELETE_MEMORY", "LIST_MEMORY", "CREATE_EXTENSION", "READ_EXTENSION", "UPDATE_EXTENSION", "DELETE_EXTENSION", "LIST_EXTENSION", "DOWNLOAD_EXTENSION", "READ_INSTANCE", "TRANSFER_INSTANCE_OWNERSHIP", "TRANSFER_RESOURCE_OWNERSHIP", "RELOAD_LICENSE", "DRAIN_SERVER", "PURGE_BACKGROUND_JOBS", "CREATE_RETRIEVE_MEMORY_LOG_POLICY", "READ_RETRIEVE_MEMORY_LOG_POLICY", "LIST_RETRIEVE_MEMORY_LOG_POLICY", "DELETE_RETRIEVE_MEMORY_LOG_POLICY", "MANAGE_ACCESS"]).describe("Protected operation."),
22144
+ selector: external_exports.enum(["ANY", "OWN", "EXACT", "DIRECT_MEMBERS_OF"]).describe("Resource-selection semantics."),
22145
+ assignedResource: external_exports.object({
22146
+ kind: external_exports.enum(["INSTANCE", "USER", "SERVICE_IDENTITY", "SPACE", "API_KEY", "EMBEDDER", "RERANKER", "LLM", "MEMORY", "EXTENSION", "RETRIEVE_MEMORY_LOG_POLICY"]).describe("Concrete target kind."),
22147
+ resourceId: external_exports.string().describe("Concrete resource UUID; omitted for the singleton INSTANCE target.").optional()
22148
+ }).passthrough().describe("Required exactly for EXACT and DIRECT_MEMBERS_OF selectors.").optional()
22149
+ }).passthrough().describe("Authorization descriptor to grant.")
22150
+ }, { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false }, async (args) => {
22151
+ const body = {};
22152
+ if (args.grant_id !== void 0)
22153
+ body["grantId"] = args.grant_id;
22154
+ if (args.audience !== void 0)
22155
+ body["audience"] = args.audience;
22156
+ if (args.rule !== void 0)
22157
+ body["rule"] = args.rule;
22158
+ const result = await callApi("POST", `/v1/access-policy/grants`, body);
22159
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22160
+ });
22161
+ server.tool("goodmem_access_policy_grants_delete", "Revoke an authorization grant\n\nSoft-revokes one grant and returns its durable historical row. Repeating the request is idempotent while the caller retains MANAGE_ACCESS on the target.", {
22162
+ id: external_exports.string().describe("Grant UUID")
22163
+ }, { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }, async (args) => {
22164
+ if (typeof args.id !== "string" || args.id === "") {
22165
+ throw new Error("'id' must be a non-empty string");
22166
+ }
22167
+ await callApi("DELETE", `/v1/access-policy/grants/${encodeURIComponent(String(args.id))}`, void 0);
22168
+ return { content: [{ type: "text", text: "Deleted successfully." }] };
22169
+ });
22170
+ server.tool("goodmem_access_policy_grants_get", "Get an authorization grant\n\nReads one live grant, or one revoked historical grant when include_revoked is true, after requiring MANAGE_ACCESS on its policy target.", {
22171
+ id: external_exports.string().describe("Grant UUID"),
22172
+ include_revoked: external_exports.boolean().describe("Include a revoked historical row").optional()
22173
+ }, { readOnlyHint: true, destructiveHint: false, openWorldHint: false }, async (args) => {
22174
+ if (typeof args.id !== "string" || args.id === "") {
22175
+ throw new Error("'id' must be a non-empty string");
22176
+ }
22177
+ const query = {};
22178
+ if (args.include_revoked !== void 0)
22179
+ query["includeRevoked"] = String(args.include_revoked);
22180
+ const result = await callApi("GET", `/v1/access-policy/grants/${encodeURIComponent(String(args.id))}`, void 0, query);
22181
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22182
+ });
22183
+ server.tool("goodmem_access_policy_grants_list", "List authorization grants\n\nLists grants attached to one resource. MANAGE_ACCESS is required on that resource; continuation tokens are bound to the caller and filters.", {
22184
+ resource_kind: external_exports.enum(["INSTANCE", "USER", "SERVICE_IDENTITY", "SPACE", "API_KEY", "EMBEDDER", "RERANKER", "LLM", "MEMORY", "EXTENSION", "RETRIEVE_MEMORY_LOG_POLICY"]).describe("Required target resource kind"),
22185
+ resource_id: external_exports.string().describe("Required target UUID except when resource_kind is INSTANCE").optional(),
22186
+ include_revoked: external_exports.boolean().describe("Include revoked history").optional(),
22187
+ max_results: external_exports.number().int().describe("Page size; 0 or omission uses the default of 50, maximum 1,000").optional(),
22188
+ next_token: external_exports.string().describe("Opaque continuation token").optional()
22189
+ }, { readOnlyHint: true, destructiveHint: false, openWorldHint: false }, async (args) => {
22190
+ const query = {};
22191
+ if (args.resource_kind !== void 0)
22192
+ query["resourceKind"] = String(args.resource_kind);
22193
+ if (args.resource_id !== void 0)
22194
+ query["resourceId"] = String(args.resource_id);
22195
+ if (args.include_revoked !== void 0)
22196
+ query["includeRevoked"] = String(args.include_revoked);
22197
+ if (args.max_results !== void 0)
22198
+ query["maxResults"] = String(args.max_results);
22199
+ if (args.next_token !== void 0)
22200
+ query["nextToken"] = String(args.next_token);
22201
+ const result = await callApi("GET", `/v1/access-policy/grants`, void 0, query);
22202
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22203
+ });
22204
+ server.tool("goodmem_access_policy_role_assignments_create", "Assign a scoped role\n\nAssigns one code-defined role to an active principal at INSTANCE or SPACE scope after requiring MANAGE_ACCESS. ROOT is maintained only by ownership workflows.", {
22205
+ role_assignment_id: external_exports.string().describe("Optional caller-provided role-assignment UUID.").optional(),
22206
+ principal_id: external_exports.string().describe("Active principal receiving the role."),
22207
+ role: external_exports.enum(["ADMIN", "USER", "SPACE_VIEWER", "SPACE_CONTRIBUTOR", "SPACE_CONTENT_MANAGER", "SPACE_ADMIN"]).describe("Code-defined non-ROOT role to assign."),
22208
+ assigned_resource: external_exports.object({
22209
+ kind: external_exports.enum(["INSTANCE", "SPACE"]).describe("Role-assignment target kind."),
22210
+ resourceId: external_exports.string().describe("Memory-space UUID; omitted for the singleton INSTANCE target.").optional()
22211
+ }).passthrough().describe("INSTANCE or SPACE boundary receiving the assignment.")
22212
+ }, { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false }, async (args) => {
22213
+ const body = {};
22214
+ if (args.role_assignment_id !== void 0)
22215
+ body["roleAssignmentId"] = args.role_assignment_id;
22216
+ if (args.principal_id !== void 0)
22217
+ body["principalId"] = args.principal_id;
22218
+ if (args.role !== void 0)
22219
+ body["role"] = args.role;
22220
+ if (args.assigned_resource !== void 0)
22221
+ body["assignedResource"] = args.assigned_resource;
22222
+ const result = await callApi("POST", `/v1/access-policy/role-assignments`, body);
22223
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22224
+ });
22225
+ server.tool("goodmem_access_policy_role_assignments_delete", "Revoke a scoped role assignment\n\nSoft-revokes one non-ROOT assignment and returns its durable historical row. Repeating the request is idempotent while the caller retains MANAGE_ACCESS.", {
22226
+ id: external_exports.string().describe("Role-assignment UUID")
22227
+ }, { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }, async (args) => {
22228
+ if (typeof args.id !== "string" || args.id === "") {
22229
+ throw new Error("'id' must be a non-empty string");
22230
+ }
22231
+ await callApi("DELETE", `/v1/access-policy/role-assignments/${encodeURIComponent(String(args.id))}`, void 0);
22232
+ return { content: [{ type: "text", text: "Deleted successfully." }] };
22233
+ });
22234
+ server.tool("goodmem_access_policy_role_assignments_get", "Get a scoped role assignment\n\nReads one live assignment, or one revoked historical assignment when include_revoked is true, after requiring MANAGE_ACCESS on its policy target.", {
22235
+ id: external_exports.string().describe("Role-assignment UUID"),
22236
+ include_revoked: external_exports.boolean().describe("Include a revoked historical row").optional()
22237
+ }, { readOnlyHint: true, destructiveHint: false, openWorldHint: false }, async (args) => {
22238
+ if (typeof args.id !== "string" || args.id === "") {
22239
+ throw new Error("'id' must be a non-empty string");
22240
+ }
22241
+ const query = {};
22242
+ if (args.include_revoked !== void 0)
22243
+ query["includeRevoked"] = String(args.include_revoked);
22244
+ const result = await callApi("GET", `/v1/access-policy/role-assignments/${encodeURIComponent(String(args.id))}`, void 0, query);
22245
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22246
+ });
22247
+ server.tool("goodmem_access_policy_role_assignments_list", "List scoped role assignments\n\nLists assignments attached to one required INSTANCE or SPACE boundary after requiring MANAGE_ACCESS. Continuation tokens are bound to the caller and filters.", {
22248
+ resource_kind: external_exports.enum(["INSTANCE", "SPACE"]).describe("Required INSTANCE or SPACE kind"),
22249
+ resource_id: external_exports.string().describe("Required space UUID; omitted for INSTANCE").optional(),
22250
+ include_revoked: external_exports.boolean().describe("Include revoked history").optional(),
22251
+ max_results: external_exports.number().int().describe("Page size; 0 or omission uses the default of 50, maximum 1,000").optional(),
22252
+ next_token: external_exports.string().describe("Opaque continuation token").optional()
22253
+ }, { readOnlyHint: true, destructiveHint: false, openWorldHint: false }, async (args) => {
22254
+ const query = {};
22255
+ if (args.resource_kind !== void 0)
22256
+ query["resourceKind"] = String(args.resource_kind);
22257
+ if (args.resource_id !== void 0)
22258
+ query["resourceId"] = String(args.resource_id);
22259
+ if (args.include_revoked !== void 0)
22260
+ query["includeRevoked"] = String(args.include_revoked);
22261
+ if (args.max_results !== void 0)
22262
+ query["maxResults"] = String(args.max_results);
22263
+ if (args.next_token !== void 0)
22264
+ query["nextToken"] = String(args.next_token);
22265
+ const result = await callApi("GET", `/v1/access-policy/role-assignments`, void 0, query);
22266
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22267
+ });
21966
22268
  server.tool("goodmem_admin_background_jobs_purge", "Purge completed background jobs\n\nDeletes terminal background jobs older than a retention cutoff.", {
21967
22269
  older_than: external_exports.string().describe("ISO-8601 timestamp cutoff; only terminal jobs older than this instant are eligible."),
21968
22270
  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(),
21969
22271
  dry_run: external_exports.boolean().describe("If true, report purge counts without deleting any rows.").optional(),
21970
22272
  limit: external_exports.number().int().describe("Maximum number of jobs to purge in this request. Must be >= 0; 0 means no limit.").optional()
21971
- }, async (args) => {
22273
+ }, { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false }, async (args) => {
21972
22274
  const body = {};
21973
22275
  if (args.older_than !== void 0)
21974
22276
  body["olderThan"] = args.older_than;
@@ -21985,7 +22287,7 @@ function registerTools() {
21985
22287
  timeout_sec: external_exports.number().int().describe("Maximum seconds to wait for the server to quiesce before returning.").optional(),
21986
22288
  reason: external_exports.string().describe("Human-readable reason for initiating drain mode.").optional(),
21987
22289
  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()
21988
- }, async (args) => {
22290
+ }, { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }, async (args) => {
21989
22291
  const body = {};
21990
22292
  if (args.timeout_sec !== void 0)
21991
22293
  body["timeoutSec"] = args.timeout_sec;
@@ -21996,7 +22298,7 @@ function registerTools() {
21996
22298
  const result = await callApi("POST", `/v1/admin:drain`, body, void 0, ((args.timeout_sec ?? 900) + 30) * 1e3);
21997
22299
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
21998
22300
  });
21999
- 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) => {
22301
+ 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.", {}, { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false }, async (args) => {
22000
22302
  const result = await callApi("POST", `/v1/admin/license:reload`, void 0);
22001
22303
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22002
22304
  });
@@ -22017,7 +22319,7 @@ function registerTools() {
22017
22319
  active_from: external_exports.number().int().describe("Inclusive activation time in milliseconds since epoch.").optional(),
22018
22320
  active_until: external_exports.number().int().describe("Exclusive deactivation time in milliseconds since epoch.").optional(),
22019
22321
  labels: external_exports.record(external_exports.string(), external_exports.string()).describe("Operator labels for listing and administration.").optional()
22020
- }, async (args) => {
22322
+ }, { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false }, async (args) => {
22021
22323
  const body = {};
22022
22324
  if (args.policy_id !== void 0)
22023
22325
  body["policyId"] = args.policy_id;
@@ -22039,7 +22341,7 @@ function registerTools() {
22039
22341
  server.tool("goodmem_admin_retrieve_memory_log_policies_delete", "Delete a RetrieveMemory log policy\n\nIdempotently tombstones an immutable RetrieveMemory log policy.", {
22040
22342
  id: external_exports.string().describe("The UUID of the policy to delete"),
22041
22343
  reason: external_exports.string().describe("Optional tombstone reason.").optional()
22042
- }, async (args) => {
22344
+ }, { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }, async (args) => {
22043
22345
  if (typeof args.id !== "string" || args.id === "") {
22044
22346
  throw new Error("'id' must be a non-empty string");
22045
22347
  }
@@ -22052,7 +22354,7 @@ function registerTools() {
22052
22354
  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.", {
22053
22355
  id: external_exports.string().describe("The UUID of the policy to retrieve"),
22054
22356
  include_deleted: external_exports.boolean().describe("Whether to include tombstoned policies. Also accepts include_deleted.").optional()
22055
- }, async (args) => {
22357
+ }, { readOnlyHint: true, destructiveHint: false, openWorldHint: false }, async (args) => {
22056
22358
  if (typeof args.id !== "string" || args.id === "") {
22057
22359
  throw new Error("'id' must be a non-empty string");
22058
22360
  }
@@ -22071,7 +22373,7 @@ function registerTools() {
22071
22373
  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(),
22072
22374
  sort_order: external_exports.enum(["ASCENDING", "DESCENDING", "SORT_ORDER_UNSPECIFIED"]).describe("Sort order. Also accepts sort_order.").optional(),
22073
22375
  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()
22074
- }, async (args) => {
22376
+ }, { readOnlyHint: true, destructiveHint: false, openWorldHint: false }, async (args) => {
22075
22377
  const query = {};
22076
22378
  if (args.include_deleted !== void 0)
22077
22379
  query["includeDeleted"] = String(args.include_deleted);
@@ -22094,11 +22396,31 @@ function registerTools() {
22094
22396
  const result = await callApi("GET", `/v1/admin/retrieve-memory-log-policies`, void 0, query);
22095
22397
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22096
22398
  });
22097
- 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.", {
22098
- 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(),
22099
- expires_at: external_exports.number().int().describe("Expiration timestamp in milliseconds since epoch. If not provided, the key does not expire.").optional(),
22100
- 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()
22101
- }, async (args) => {
22399
+ server.tool("goodmem_admin_transfer_instance_ownership", "Transfer GoodMem instance ownership\n\nTransfers the singleton GoodMem instance to another active human principal. Only the current instance owner may invoke this operation; ADMIN, MANAGE_ACCESS, and ordinary grants are insufficient. Ownership and the synthetic ROOT assignment move atomically. All ordinary roles, including ADMIN, remain unchanged. No credential is created or returned. After an unknown outcome, read the current owner before retrying.", {
22400
+ new_owner_id: external_exports.string().describe("Existing principal UUID that will become the new owner.")
22401
+ }, { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false }, async (args) => {
22402
+ const body = {};
22403
+ if (args.new_owner_id !== void 0)
22404
+ body["newOwnerId"] = args.new_owner_id;
22405
+ const result = await callApi("POST", `/v1/admin:transferInstanceOwnership`, body);
22406
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22407
+ });
22408
+ server.tool("goodmem_apikeys_create", "Create a new API key\n\nIssues a new API key and returns its raw value exactly once. Omitted subject and authority_mode create a self-issued human key that inherits the subject's live authority. A SCOPED key requires a nonempty immutable ceiling, and every ceiling rule must be conservatively covered by both the subject's live authority and the issuing credential's effective authority. A scoped issuer can create only scoped children. SERVICE subjects require SCOPED mode and MANAGE_ACCESS on the service identity. AUTHORIZATION: Requires CREATE_API_KEY on the proposed credential; subject and ceiling checks are repeated by the final insertion statement.", {
22409
+ 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. At most 20 entries; keys and values contain at most 255 characters; keys use [a-z0-9._-].").optional(),
22410
+ expires_at: external_exports.number().int().describe("Exclusive expiration timestamp in milliseconds since epoch. It must be later than valid_from, which defaults to issuance time; if omitted, the key does not expire.").optional(),
22411
+ 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(),
22412
+ subject_principal_id: external_exports.string().describe("Principal authenticated by this key. Omit to use the authenticated principal.").optional(),
22413
+ authority_mode: external_exports.enum(["INHERIT_SUBJECT", "SCOPED"]).describe("Authority mode. Omit to create a self-issued human key that inherits live authority. A scoped issuing credential may create only SCOPED children.").optional(),
22414
+ ceiling: external_exports.array(external_exports.object({
22415
+ operation: external_exports.enum(["CREATE_USER", "READ_USER", "UPDATE_USER", "DELETE_USER", "LIST_USER", "MANAGE_USER_ENROLLMENT", "CREATE_SERVICE_IDENTITY", "READ_SERVICE_IDENTITY", "UPDATE_SERVICE_IDENTITY", "DELETE_SERVICE_IDENTITY", "LIST_SERVICE_IDENTITY", "CREATE_SPACE", "READ_SPACE", "UPDATE_SPACE", "DELETE_SPACE", "LIST_SPACE", "CREATE_API_KEY", "READ_API_KEY", "UPDATE_API_KEY", "DELETE_API_KEY", "LIST_API_KEY", "CREATE_EMBEDDER", "READ_EMBEDDER", "UPDATE_EMBEDDER", "DELETE_EMBEDDER", "LIST_EMBEDDER", "PING_EMBEDDER", "EXECUTE_EMBEDDER", "READ_EMBEDDER_CREDENTIALS", "CREATE_RERANKER", "READ_RERANKER", "UPDATE_RERANKER", "DELETE_RERANKER", "LIST_RERANKER", "PING_RERANKER", "EXECUTE_RERANKER", "READ_RERANKER_CREDENTIALS", "CREATE_LLM", "READ_LLM", "UPDATE_LLM", "DELETE_LLM", "LIST_LLM", "PING_LLM", "EXECUTE_LLM", "READ_LLM_CREDENTIALS", "PROXY_INFERENCE_TARGET", "OCR_DOCUMENT", "CREATE_MEMORY", "READ_MEMORY", "DELETE_MEMORY", "LIST_MEMORY", "CREATE_EXTENSION", "READ_EXTENSION", "UPDATE_EXTENSION", "DELETE_EXTENSION", "LIST_EXTENSION", "DOWNLOAD_EXTENSION", "READ_INSTANCE", "TRANSFER_INSTANCE_OWNERSHIP", "TRANSFER_RESOURCE_OWNERSHIP", "RELOAD_LICENSE", "DRAIN_SERVER", "PURGE_BACKGROUND_JOBS", "CREATE_RETRIEVE_MEMORY_LOG_POLICY", "READ_RETRIEVE_MEMORY_LOG_POLICY", "LIST_RETRIEVE_MEMORY_LOG_POLICY", "DELETE_RETRIEVE_MEMORY_LOG_POLICY", "MANAGE_ACCESS"]).describe("Protected operation."),
22416
+ selector: external_exports.enum(["ANY", "OWN", "EXACT", "DIRECT_MEMBERS_OF"]).describe("Resource-selection semantics."),
22417
+ assignedResource: external_exports.object({
22418
+ kind: external_exports.enum(["INSTANCE", "USER", "SERVICE_IDENTITY", "SPACE", "API_KEY", "EMBEDDER", "RERANKER", "LLM", "MEMORY", "EXTENSION", "RETRIEVE_MEMORY_LOG_POLICY"]).describe("Concrete target kind."),
22419
+ resourceId: external_exports.string().describe("Concrete resource UUID; omitted for the singleton INSTANCE target.").optional()
22420
+ }).passthrough().describe("Required exactly for EXACT and DIRECT_MEMBERS_OF selectors.").optional()
22421
+ }).passthrough()).describe("Immutable authorization ceiling. Required and nonempty for SCOPED; omitted for INHERIT_SUBJECT, with at most 1,000 rules. Every rule must be covered by both the subject's live authority and the issuing credential's effective authority.").optional(),
22422
+ valid_from: external_exports.number().int().describe("Inclusive activation time in epoch milliseconds. Omit to activate at issuance time.").optional()
22423
+ }, { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false }, async (args) => {
22102
22424
  const body = {};
22103
22425
  if (args.labels !== void 0)
22104
22426
  body["labels"] = args.labels;
@@ -22106,28 +22428,65 @@ function registerTools() {
22106
22428
  body["expiresAt"] = args.expires_at;
22107
22429
  if (args.api_key_id !== void 0)
22108
22430
  body["apiKeyId"] = args.api_key_id;
22431
+ if (args.subject_principal_id !== void 0)
22432
+ body["subjectPrincipalId"] = args.subject_principal_id;
22433
+ if (args.authority_mode !== void 0)
22434
+ body["authorityMode"] = args.authority_mode;
22435
+ if (args.ceiling !== void 0)
22436
+ body["ceiling"] = args.ceiling;
22437
+ if (args.valid_from !== void 0)
22438
+ body["validFrom"] = args.valid_from;
22109
22439
  const result = await callApi("POST", `/v1/apikeys`, body);
22110
22440
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22111
22441
  });
22112
- 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.', {
22442
+ server.tool("goodmem_apikeys_delete", 'Delete an API key\n\nPermanently revokes an API key and immediately rejects it for future authentication. The durable credential and audit history remain stored. This operation requires DELETE_API_KEY and records the revocation time and actor; it cannot be undone. The `goodmem_apikeys_update` tool with `status: "INACTIVE"` performs the same permanent revocation; revoked keys cannot be reactivated.', {
22113
22443
  id: external_exports.string().describe("The UUID of the API key to delete")
22114
- }, async (args) => {
22444
+ }, { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }, async (args) => {
22115
22445
  if (typeof args.id !== "string" || args.id === "") {
22116
22446
  throw new Error("'id' must be a non-empty string");
22117
22447
  }
22118
22448
  await callApi("DELETE", `/v1/apikeys/${encodeURIComponent(String(args.id))}`, void 0);
22119
22449
  return { content: [{ type: "text", text: "Deleted successfully." }] };
22120
22450
  });
22121
- 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) => {
22122
- const result = await callApi("GET", `/v1/apikeys`, void 0);
22451
+ server.tool("goodmem_apikeys_get", "Get an API key\n\nReturns complete non-secret metadata for one existing credential after requiring effective READ_API_KEY authority. The immutable ceiling is always complete and ceilingOmitted is false. Raw key material and hashes are never returned.", {
22452
+ id: external_exports.string().describe("API-key UUID")
22453
+ }, { readOnlyHint: true, destructiveHint: false, openWorldHint: false }, async (args) => {
22454
+ if (typeof args.id !== "string" || args.id === "") {
22455
+ throw new Error("'id' must be a non-empty string");
22456
+ }
22457
+ const result = await callApi("GET", `/v1/apikeys/${encodeURIComponent(String(args.id))}`, void 0);
22458
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22459
+ });
22460
+ server.tool("goodmem_apikeys_list", "List API keys\n\nRetrieves one UUID-ordered page of API keys discoverable under the authenticated principal's effective API-key policy. Subject, owner, and lifecycle filters are applied after authorization. FULL includes complete immutable ceilings; BASIC omits them, sets ceilingOmitted=true, and permits larger pages. Raw key values and key hashes are never returned.", {
22461
+ subject_principal_id: external_exports.string().describe("Filter by exact subject-principal UUID").optional(),
22462
+ owner_principal_id: external_exports.string().describe("Filter by exact administrative-owner UUID").optional(),
22463
+ lifecycle_state: external_exports.enum(["NOT_YET_VALID", "USABLE", "EXPIRED", "REVOKED"]).describe("Filter by precise lifecycle state").optional(),
22464
+ view: external_exports.enum(["FULL", "BASIC"]).describe("Metadata projection; omission defaults to FULL").optional(),
22465
+ max_results: external_exports.number().int().describe("Page size; FULL defaults to 10 and permits at most 20, while BASIC defaults to 50 and permits at most 1,000").optional(),
22466
+ next_token: external_exports.string().describe("Opaque continuation token returned by the preceding page").optional()
22467
+ }, { readOnlyHint: true, destructiveHint: false, openWorldHint: false }, async (args) => {
22468
+ const query = {};
22469
+ if (args.subject_principal_id !== void 0)
22470
+ query["subjectPrincipalId"] = String(args.subject_principal_id);
22471
+ if (args.owner_principal_id !== void 0)
22472
+ query["ownerPrincipalId"] = String(args.owner_principal_id);
22473
+ if (args.lifecycle_state !== void 0)
22474
+ query["lifecycleState"] = String(args.lifecycle_state);
22475
+ if (args.view !== void 0)
22476
+ query["view"] = String(args.view);
22477
+ if (args.max_results !== void 0)
22478
+ query["maxResults"] = String(args.max_results);
22479
+ if (args.next_token !== void 0)
22480
+ query["nextToken"] = String(args.next_token);
22481
+ const result = await callApi("GET", `/v1/apikeys`, void 0, query);
22123
22482
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22124
22483
  });
22125
- 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.", {
22484
+ server.tool("goodmem_apikeys_update", "Update an API key\n\nUpdates an existing API key's labels or lifecycle status. Key ID, subject, ownership, key material, validity window, and creation audit fields remain immutable. Label changes require UPDATE_API_KEY; setting status=INACTIVE permanently revokes the key and requires DELETE_API_KEY; a request doing both requires both operations. Revoked keys cannot be reactivated. Side effects include updating administrative audit fields and, for revocation, recording the revocation time and actor.", {
22126
22485
  id: external_exports.string().describe("The UUID of the API key to update"),
22127
- status: external_exports.enum(["ACTIVE", "INACTIVE"]).describe("New status for the API key. Allowed values: ACTIVE, INACTIVE.").optional(),
22128
- 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(),
22129
- merge_labels: external_exports.record(external_exports.string(), external_exports.string()).describe("Merge these labels with existing ones. Mutually exclusive with replace_labels.").optional()
22130
- }, async (args) => {
22486
+ status: external_exports.enum(["ACTIVE", "INACTIVE"]).describe("New status for the API key. INACTIVE is permanent; revoked keys cannot be reactivated.").optional(),
22487
+ replace_labels: external_exports.record(external_exports.string(), external_exports.string()).describe("Replace all existing labels with this set. Mutually exclusive with merge_labels. The stored map may contain at most 20 entries; keys and values contain at most 255 characters; keys use [a-z0-9._-].").optional(),
22488
+ merge_labels: external_exports.record(external_exports.string(), external_exports.string()).describe("Merge these labels with existing ones. Mutually exclusive with replace_labels. The final stored map may contain at most 20 entries; keys and values contain at most 255 characters; keys use [a-z0-9._-].").optional()
22489
+ }, { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }, async (args) => {
22131
22490
  if (typeof args.id !== "string" || args.id === "") {
22132
22491
  throw new Error("'id' must be a non-empty string");
22133
22492
  }
@@ -22145,12 +22504,12 @@ function registerTools() {
22145
22504
  const result = await callApi("PUT", `/v1/apikeys/${encodeURIComponent(String(args.id))}`, body);
22146
22505
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22147
22506
  });
22148
- 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).", {
22507
+ 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. A SHA-256 credential fingerprint participates in uniqueness checking. The api_path field defaults to '/v2/embed' for Cohere, '/v1/embeddings' for Jina, '/embed' for TEI, and '/embeddings' for other providers when omitted. OWNER DEFAULTS: Owner defaults to the authenticated principal unless owner_id is provided; CREATE_EMBEDDER is evaluated against the proposed embedder and owner.", {
22149
22508
  display_name: external_exports.string().describe("User-facing name of the embedder"),
22150
22509
  description: external_exports.string().describe("Description of the embedder").optional(),
22151
22510
  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(),
22152
22511
  endpoint_url: external_exports.string().describe("API endpoint URL (auto-inferred from model_identifier if omitted)").optional(),
22153
- api_path: external_exports.string().describe("API path for embeddings request (defaults: Cohere /v2/embed, TEI /embed, others /embeddings)").optional(),
22512
+ api_path: external_exports.string().describe("API path for embeddings requests (defaults: Cohere /v2/embed, Jina /v1/embeddings, TEI /embed, others /embeddings)").optional(),
22154
22513
  model_identifier: external_exports.string().describe("Model identifier"),
22155
22514
  dimensionality: external_exports.number().int().describe("Output vector dimensions (auto-inferred from model_identifier if omitted)").optional(),
22156
22515
  distribution_type: external_exports.enum(["DENSE", "SPARSE"]).describe("Type of embedding distribution (DENSE or SPARSE) (auto-inferred from model_identifier if omitted)").optional(),
@@ -22176,9 +22535,9 @@ function registerTools() {
22176
22535
  labels: external_exports.record(external_exports.string(), external_exports.string()).describe("User-defined labels for categorization").optional(),
22177
22536
  version: external_exports.string().describe("Version information").optional(),
22178
22537
  monitoring_endpoint: external_exports.string().describe("Monitoring endpoint URL").optional(),
22179
- owner_id: external_exports.string().describe("Optional owner ID. If not provided, derived from the authentication context. Requires CREATE_EMBEDDER_ANY permission if specified.").optional(),
22538
+ owner_id: external_exports.string().describe("Optional owner principal UUID. If omitted, defaults to the authenticated principal. CREATE_EMBEDDER is evaluated against the proposed embedder and owner.").optional(),
22180
22539
  embedder_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()
22181
- }, async (args) => {
22540
+ }, { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false }, async (args) => {
22182
22541
  const inferred = autoInferFromRegistry("embedders", args);
22183
22542
  checkCredentialRequired(inferred);
22184
22543
  const body = {};
@@ -22217,29 +22576,33 @@ function registerTools() {
22217
22576
  const result = await callApi("POST", `/v1/embedders`, body);
22218
22577
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22219
22578
  });
22220
- 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).", {
22579
+ 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 on the requested embedder.", {
22221
22580
  id: external_exports.string().describe("The unique identifier of the embedder to delete")
22222
- }, async (args) => {
22581
+ }, { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }, async (args) => {
22223
22582
  if (typeof args.id !== "string" || args.id === "") {
22224
22583
  throw new Error("'id' must be a non-empty string");
22225
22584
  }
22226
22585
  await callApi("DELETE", `/v1/embedders/${encodeURIComponent(String(args.id))}`, void 0);
22227
22586
  return { content: [{ type: "text", text: "Deleted successfully." }] };
22228
22587
  });
22229
- 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.", {
22230
- id: external_exports.string().describe("The unique identifier of the embedder to retrieve")
22231
- }, async (args) => {
22588
+ 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 on the requested embedder. The service distinguishes a missing embedder from an existing embedder the caller cannot read. This is a read-only operation with no side effects.", {
22589
+ id: external_exports.string().describe("The unique identifier of the embedder to retrieve"),
22590
+ include_credentials: external_exports.boolean().describe("Whether to return stored credentials. Also accepts include_credentials. Requires READ_EMBEDDER_CREDENTIALS in addition to READ_EMBEDDER.").optional()
22591
+ }, { readOnlyHint: true, destructiveHint: false, openWorldHint: false }, async (args) => {
22232
22592
  if (typeof args.id !== "string" || args.id === "") {
22233
22593
  throw new Error("'id' must be a non-empty string");
22234
22594
  }
22235
- const result = await callApi("GET", `/v1/embedders/${encodeURIComponent(String(args.id))}`, void 0);
22595
+ const query = {};
22596
+ if (args.include_credentials !== void 0)
22597
+ query["includeCredentials"] = String(args.include_credentials);
22598
+ const result = await callApi("GET", `/v1/embedders/${encodeURIComponent(String(args.id))}`, void 0, query);
22236
22599
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22237
22600
  });
22238
- 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.", {
22239
- 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(),
22601
+ 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). AUTHORIZATION: Requires LIST_EMBEDDER on the GoodMem instance. Each returned embedder must also be visible through READ_EMBEDDER; unauthorized embedders are filtered in PostgreSQL. The owner_id parameter filters that already-authorized result set and does not grant additional visibility. This is a read-only operation with no side effects.", {
22602
+ owner_id: external_exports.string().describe("Filter the already-authorized result set by owner principal UUID. Omitting this parameter does not bypass per-embedder READ_EMBEDDER filtering.").optional(),
22240
22603
  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(),
22241
22604
  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()
22242
- }, async (args) => {
22605
+ }, { readOnlyHint: true, destructiveHint: false, openWorldHint: false }, async (args) => {
22243
22606
  const query = {};
22244
22607
  if (args.owner_id !== void 0)
22245
22608
  query["ownerId"] = String(args.owner_id);
@@ -22252,12 +22615,12 @@ function registerTools() {
22252
22615
  const result = await callApi("GET", `/v1/embedders`, void 0, query);
22253
22616
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22254
22617
  });
22255
- 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).", {
22618
+ 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. Omitting api_path preserves the existing path; setting it blank resets it to '/v2/embed' for Cohere, '/v1/embeddings' for Jina, '/embed' for TEI, or '/embeddings' for other providers. 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 on the requested embedder.", {
22256
22619
  id: external_exports.string().describe("The unique identifier of the embedder to update"),
22257
22620
  display_name: external_exports.string().describe("User-facing name of the embedder").optional(),
22258
22621
  description: external_exports.string().describe("Description of the embedder").optional(),
22259
22622
  endpoint_url: external_exports.string().describe("API endpoint URL").optional(),
22260
- api_path: external_exports.string().describe("API path for embeddings request").optional(),
22623
+ api_path: external_exports.string().describe("API path for embeddings requests. Omit to keep the existing path; set blank to reset to the provider default (Cohere /v2/embed, Jina /v1/embeddings, TEI /embed, others /embeddings).").optional(),
22261
22624
  model_identifier: external_exports.string().describe("Model identifier").optional(),
22262
22625
  dimensionality: external_exports.number().int().describe("Output vector dimensions").optional(),
22263
22626
  distribution_type: external_exports.enum(["DENSE", "SPARSE"]).describe("Type of embedding distribution (DENSE or SPARSE)").optional(),
@@ -22278,12 +22641,12 @@ function registerTools() {
22278
22641
  quotaProjectId: external_exports.string().describe("Optional quota project used for billing").optional()
22279
22642
  }).passthrough().describe("Configuration when kind is CREDENTIAL_KIND_GCP_ADC").optional(),
22280
22643
  labels: external_exports.record(external_exports.string(), external_exports.string()).describe('Optional annotations to aid operators (e.g., "owner=vertex")').optional()
22281
- }).passthrough().describe("Structured credential payload describing how to authenticate with the provider").optional(),
22644
+ }).passthrough().describe("Replace stored credentials. Omit this field to preserve the current credentials; a present empty payload is invalid and never clears them.").optional(),
22282
22645
  replace_labels: external_exports.record(external_exports.string(), external_exports.string()).describe("Replace all existing labels with these (mutually exclusive with merge_labels)").optional(),
22283
22646
  merge_labels: external_exports.record(external_exports.string(), external_exports.string()).describe("Merge these labels with existing ones (mutually exclusive with replace_labels)").optional(),
22284
22647
  version: external_exports.string().describe("Version information").optional(),
22285
22648
  monitoring_endpoint: external_exports.string().describe("Monitoring endpoint URL").optional()
22286
- }, async (args) => {
22649
+ }, { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }, async (args) => {
22287
22650
  if (typeof args.id !== "string" || args.id === "") {
22288
22651
  throw new Error("'id' must be a non-empty string");
22289
22652
  }
@@ -22321,7 +22684,11 @@ function registerTools() {
22321
22684
  const result = await callApi("PUT", `/v1/embedders/${encodeURIComponent(String(args.id))}`, body);
22322
22685
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22323
22686
  });
22324
- 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).", {
22687
+ server.tool("goodmem_instance_get", "Get the GoodMem instance\n\nReturns singleton instance identity, ownership, and audit metadata after requiring effective READ_INSTANCE authority. The built-in ADMIN role supplies this authority; instance ownership alone does not. A scoped API key must also retain READ_INSTANCE in its immutable ceiling. This operation does not expose credentials or mutate instance state.", {}, { readOnlyHint: true, destructiveHint: false, openWorldHint: false }, async (args) => {
22688
+ const result = await callApi("GET", `/v1/instance`, void 0);
22689
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22690
+ });
22691
+ 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. OWNER DEFAULTS: Owner defaults to the authenticated principal unless owner_id is provided; CREATE_LLM is evaluated against the proposed LLM and owner.", {
22325
22692
  display_name: external_exports.string().describe("User-facing name of the LLM"),
22326
22693
  description: external_exports.string().describe("Description of the LLM").optional(),
22327
22694
  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(),
@@ -22368,9 +22735,9 @@ function registerTools() {
22368
22735
  }).passthrough().describe("Default sampling parameters for generation requests").optional(),
22369
22736
  max_context_length: external_exports.number().int().describe("Maximum context window size in tokens").optional(),
22370
22737
  client_config: external_exports.record(external_exports.string(), external_exports.any()).describe("Provider-specific client configuration as flexible JSON structure").optional(),
22371
- 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(),
22738
+ owner_id: external_exports.string().describe("Optional owner principal UUID. If omitted, defaults to the authenticated principal. CREATE_LLM is evaluated against the proposed LLM and owner.").optional(),
22372
22739
  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()
22373
- }, async (args) => {
22740
+ }, { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false }, async (args) => {
22374
22741
  const inferred = autoInferFromRegistry("llms", args);
22375
22742
  checkCredentialRequired(inferred);
22376
22743
  const body = {};
@@ -22411,29 +22778,33 @@ function registerTools() {
22411
22778
  const result = await callApi("POST", `/v1/llms`, body);
22412
22779
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22413
22780
  });
22414
- 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).", {
22781
+ 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 on the requested LLM.", {
22415
22782
  id: external_exports.string().describe("The unique identifier of the LLM to delete")
22416
- }, async (args) => {
22783
+ }, { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }, async (args) => {
22417
22784
  if (typeof args.id !== "string" || args.id === "") {
22418
22785
  throw new Error("'id' must be a non-empty string");
22419
22786
  }
22420
22787
  await callApi("DELETE", `/v1/llms/${encodeURIComponent(String(args.id))}`, void 0);
22421
22788
  return { content: [{ type: "text", text: "Deleted successfully." }] };
22422
22789
  });
22423
- 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.", {
22424
- id: external_exports.string().describe("The unique identifier of the LLM to retrieve")
22425
- }, async (args) => {
22790
+ 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 on the requested LLM. The service distinguishes a missing LLM from an existing LLM the caller cannot read. This is a read-only operation with no side effects.", {
22791
+ id: external_exports.string().describe("The unique identifier of the LLM to retrieve"),
22792
+ include_credentials: external_exports.boolean().describe("Whether to return stored credentials. Also accepts include_credentials. Requires READ_LLM_CREDENTIALS in addition to READ_LLM.").optional()
22793
+ }, { readOnlyHint: true, destructiveHint: false, openWorldHint: false }, async (args) => {
22426
22794
  if (typeof args.id !== "string" || args.id === "") {
22427
22795
  throw new Error("'id' must be a non-empty string");
22428
22796
  }
22429
- const result = await callApi("GET", `/v1/llms/${encodeURIComponent(String(args.id))}`, void 0);
22797
+ const query = {};
22798
+ if (args.include_credentials !== void 0)
22799
+ query["includeCredentials"] = String(args.include_credentials);
22800
+ const result = await callApi("GET", `/v1/llms/${encodeURIComponent(String(args.id))}`, void 0, query);
22430
22801
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22431
22802
  });
22432
- 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.", {
22433
- 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(),
22803
+ 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). AUTHORIZATION: Requires LIST_LLM on the GoodMem instance. Each returned LLM must also be visible through READ_LLM; unauthorized LLMs are filtered in PostgreSQL. The owner_id parameter filters that already-authorized result set and does not grant additional visibility. This is a read-only operation with no side effects.", {
22804
+ owner_id: external_exports.string().describe("Filter the already-authorized result set by owner principal UUID. Omitting this parameter does not bypass per-LLM READ_LLM filtering.").optional(),
22434
22805
  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(),
22435
22806
  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()
22436
- }, async (args) => {
22807
+ }, { readOnlyHint: true, destructiveHint: false, openWorldHint: false }, async (args) => {
22437
22808
  const query = {};
22438
22809
  if (args.owner_id !== void 0)
22439
22810
  query["ownerId"] = String(args.owner_id);
@@ -22446,7 +22817,7 @@ function registerTools() {
22446
22817
  const result = await callApi("GET", `/v1/llms`, void 0, query);
22447
22818
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22448
22819
  });
22449
- 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).", {
22820
+ 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 on the requested LLM.", {
22450
22821
  id: external_exports.string().describe("The unique identifier of the LLM to update"),
22451
22822
  display_name: external_exports.string().describe("Update display name").optional(),
22452
22823
  description: external_exports.string().describe("Update description").optional(),
@@ -22470,7 +22841,7 @@ function registerTools() {
22470
22841
  quotaProjectId: external_exports.string().describe("Optional quota project used for billing").optional()
22471
22842
  }).passthrough().describe("Configuration when kind is CREDENTIAL_KIND_GCP_ADC").optional(),
22472
22843
  labels: external_exports.record(external_exports.string(), external_exports.string()).describe('Optional annotations to aid operators (e.g., "owner=vertex")').optional()
22473
- }).passthrough().describe("Update credentials").optional(),
22844
+ }).passthrough().describe("Replace stored credentials. Omit this field to preserve the current credentials; a present empty payload is invalid and never clears them.").optional(),
22474
22845
  version: external_exports.string().describe("Update version information").optional(),
22475
22846
  monitoring_endpoint: external_exports.string().describe("Update monitoring endpoint URL").optional(),
22476
22847
  capabilities: external_exports.object({
@@ -22494,7 +22865,7 @@ function registerTools() {
22494
22865
  client_config: external_exports.record(external_exports.string(), external_exports.any()).describe("Update provider-specific client configuration (replaces entire config; no merging)").optional(),
22495
22866
  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(),
22496
22867
  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()
22497
- }, async (args) => {
22868
+ }, { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }, async (args) => {
22498
22869
  if (typeof args.id !== "string" || args.id === "") {
22499
22870
  throw new Error("'id' must be a non-empty string");
22500
22871
  }
@@ -22564,7 +22935,7 @@ function registerTools() {
22564
22935
  }).passthrough().describe("Chunking strategy for this memory (if not provided, uses space default)").optional(),
22565
22936
  extractPageImages: external_exports.boolean().describe("Optional hint to extract page images for eligible document types (for example, PDFs)").optional()
22566
22937
  }).passthrough()).describe("Array of memory creation requests.")
22567
- }, async (args) => {
22938
+ }, { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false }, async (args) => {
22568
22939
  for (const [_i, _item] of (Array.isArray(args.requests) ? args.requests : []).entries()) {
22569
22940
  const _itemSet = [_item?.["originalContent"] !== void 0 && _item?.["originalContent"] !== null, _item?.["originalContentB64"] !== void 0 && _item?.["originalContentB64"] !== null].filter(Boolean).length;
22570
22941
  if (_itemSet !== 1) {
@@ -22588,7 +22959,7 @@ function registerTools() {
22588
22959
  const result = await callApi("POST", `/v1/memories:batchCreate`, body);
22589
22960
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22590
22961
  });
22591
- 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.", {
22962
+ 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. Each selected memory requires DELETE_MEMORY; authority may be granted through DIRECT_MEMBERS_OF its containing space.", {
22592
22963
  requests: external_exports.array(external_exports.object({
22593
22964
  memoryId: external_exports.string().describe("Deletes one specific memory by UUID").optional(),
22594
22965
  filterSelector: external_exports.object({
@@ -22597,18 +22968,18 @@ function registerTools() {
22597
22968
  filter: external_exports.string().describe("Optional metadata filter expression").optional()
22598
22969
  }).passthrough().describe("Deletes a filtered set of memories within a specific space").optional()
22599
22970
  }).passthrough()).describe("Array of delete selectors")
22600
- }, async (args) => {
22971
+ }, { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }, async (args) => {
22601
22972
  const body = {};
22602
22973
  if (args.requests !== void 0)
22603
22974
  body["requests"] = args.requests;
22604
22975
  const result = await callApi("POST", `/v1/memories:batchDelete`, body);
22605
22976
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22606
22977
  });
22607
- server.tool("goodmem_memories_batch_get", "Get multiple memories by ID\n\nRetrieves multiple memories in a single operation, with individual success/failure results.", {
22978
+ server.tool("goodmem_memories_batch_get", "Get multiple memories by ID\n\nRetrieves multiple memories in a single operation, with individual success/failure results. Each item requires READ_MEMORY on the requested memory; authority may be granted through DIRECT_MEMBERS_OF its containing space.", {
22608
22979
  memory_ids: external_exports.array(external_exports.string()).describe("Array of memory IDs to retrieve"),
22609
22980
  include_content: external_exports.boolean().describe("Whether to include the original content in the response").optional(),
22610
22981
  include_processing_history: external_exports.boolean().describe("Whether to include background job processing history for each memory").optional()
22611
- }, async (args) => {
22982
+ }, { readOnlyHint: true, destructiveHint: false, openWorldHint: false }, async (args) => {
22612
22983
  const body = {};
22613
22984
  if (args.memory_ids !== void 0)
22614
22985
  body["memoryIds"] = args.memory_ids;
@@ -22619,10 +22990,10 @@ function registerTools() {
22619
22990
  const result = await callApi("POST", `/v1/memories:batchGet`, body);
22620
22991
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22621
22992
  });
22622
- server.tool("goodmem_memories_content", "Download memory content\n\nReturns the original binary payload for a memory. The response uses the memory's stored content type when available. Returns 404 when the memory does not have inline content; clients can check originalContentRef from the metadata endpoint to locate external content.", {
22993
+ server.tool("goodmem_memories_content", "Download memory content\n\nReturns the original binary payload for a memory. The response uses the memory's stored content type when available. Returns 404 when the memory does not have inline content; clients can check originalContentRef from the metadata endpoint to locate external content. Requires READ_MEMORY on the requested memory; authority may be granted through DIRECT_MEMBERS_OF its containing space.", {
22623
22994
  id: external_exports.string().describe("The UUID of the memory to download"),
22624
22995
  max_inline_bytes: external_exports.number().int().positive().describe("Maximum bytes to deliver inline as a content block. Defaults to 8388608. Hard server ceiling: 67108864. If the payload exceeds this cap the tool errors with the actual size \u2014 retry with a larger value (up to the ceiling) to receive it.").optional()
22625
- }, async (args) => {
22996
+ }, { readOnlyHint: true, destructiveHint: false, openWorldHint: false }, async (args) => {
22626
22997
  if (typeof args.id !== "string" || args.id === "") {
22627
22998
  throw new Error("'id' must be a non-empty string");
22628
22999
  }
@@ -22668,7 +23039,7 @@ function registerTools() {
22668
23039
  }).passthrough().describe("Sentence-based chunking strategy with language detection").optional()
22669
23040
  }).passthrough().describe("Chunking strategy for this memory (if not provided, uses space default)").optional(),
22670
23041
  extract_page_images: external_exports.boolean().describe("Optional hint to extract page images for eligible document types (for example, PDFs)").optional()
22671
- }, async (args) => {
23042
+ }, { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false }, async (args) => {
22672
23043
  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;
22673
23044
  if (_set !== 1) {
22674
23045
  throw new Error("exactly one of original_content or original_content_b64 must be set");
@@ -22701,20 +23072,20 @@ function registerTools() {
22701
23072
  const result = await callApi("POST", `/v1/memories`, body);
22702
23073
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22703
23074
  });
22704
- 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.", {
23075
+ 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. AUTHORIZATION: Requires DELETE_MEMORY on the requested memory; authority may be granted through DIRECT_MEMBERS_OF its containing space. Side effects include permanent removal of the memory record and all associated chunk data.", {
22705
23076
  id: external_exports.string().describe("The UUID of the memory to delete")
22706
- }, async (args) => {
23077
+ }, { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }, async (args) => {
22707
23078
  if (typeof args.id !== "string" || args.id === "") {
22708
23079
  throw new Error("'id' must be a non-empty string");
22709
23080
  }
22710
23081
  await callApi("DELETE", `/v1/memories/${encodeURIComponent(String(args.id))}`, void 0);
22711
23082
  return { content: [{ type: "text", text: "Deleted successfully." }] };
22712
23083
  });
22713
- 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.", {
23084
+ server.tool("goodmem_memories_get", "Get a memory by ID\n\nRetrieves a single memory by its ID. AUTHORIZATION: Requires READ_MEMORY on the requested memory; authority may be granted directly or through DIRECT_MEMBERS_OF its containing space. 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.", {
22714
23085
  id: external_exports.string().describe("The UUID of the memory to retrieve"),
22715
23086
  include_content: external_exports.boolean().describe("Whether to include the original content in the response (defaults to false).").optional(),
22716
23087
  include_processing_history: external_exports.boolean().describe("Whether to include background job processing history in the response (defaults to false).").optional()
22717
- }, async (args) => {
23088
+ }, { readOnlyHint: true, destructiveHint: false, openWorldHint: false }, async (args) => {
22718
23089
  if (typeof args.id !== "string" || args.id === "") {
22719
23090
  throw new Error("'id' must be a non-empty string");
22720
23091
  }
@@ -22736,7 +23107,7 @@ function registerTools() {
22736
23107
  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(),
22737
23108
  sort_by: external_exports.enum(["created_at", "updated_at", "content_type", "processing_status"]).describe("Field to sort by (e.g., 'created_at').").optional(),
22738
23109
  sort_order: external_exports.enum(["ASCENDING", "DESCENDING", "SORT_ORDER_UNSPECIFIED"]).describe("Sort direction (ASCENDING or DESCENDING).").optional()
22739
- }, async (args) => {
23110
+ }, { readOnlyHint: true, destructiveHint: false, openWorldHint: false }, async (args) => {
22740
23111
  if (typeof args.space_id !== "string" || args.space_id === "") {
22741
23112
  throw new Error("'space_id' must be a non-empty string");
22742
23113
  }
@@ -22760,7 +23131,7 @@ function registerTools() {
22760
23131
  const result = await callApi("GET", `/v1/spaces/${encodeURIComponent(String(args.space_id))}/memories`, void 0, query);
22761
23132
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22762
23133
  });
22763
- server.tool("goodmem_memories_pages", "List memory page images\n\nLists extracted page-image metadata for a memory with optional filters and pagination.", {
23134
+ server.tool("goodmem_memories_pages", "List memory page images\n\nLists extracted page-image metadata for a memory with optional filters and pagination. Requires READ_MEMORY on the containing memory; authority may be granted through DIRECT_MEMBERS_OF its containing space.", {
22764
23135
  id: external_exports.string().describe("Memory UUID"),
22765
23136
  start_page_index: external_exports.number().int().describe("Optional lower bound for returned page indices, inclusive.").optional(),
22766
23137
  end_page_index: external_exports.number().int().describe("Optional upper bound for returned page indices, inclusive.").optional(),
@@ -22768,7 +23139,7 @@ function registerTools() {
22768
23139
  content_type: external_exports.string().describe("Optional rendition filter for page-image MIME type, such as image/png.").optional(),
22769
23140
  max_results: external_exports.number().int().describe("Maximum number of results per page.").optional(),
22770
23141
  next_token: external_exports.string().describe("Opaque pagination token for the next page. Do not parse or construct it.").optional()
22771
- }, async (args) => {
23142
+ }, { readOnlyHint: true, destructiveHint: false, openWorldHint: false }, async (args) => {
22772
23143
  if (typeof args.id !== "string" || args.id === "") {
22773
23144
  throw new Error("'id' must be a non-empty string");
22774
23145
  }
@@ -22788,13 +23159,13 @@ function registerTools() {
22788
23159
  const result = await callApi("GET", `/v1/memories/${encodeURIComponent(String(args.id))}/pages`, void 0, query);
22789
23160
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22790
23161
  });
22791
- server.tool("goodmem_memories_pages_image", "Download memory page image content\n\nDownloads inline bytes for one page image. The page index is required. The optional dpi and content type query parameters act as rendition filters; if omitted, the server returns the unique rendition for that page or rejects ambiguous matches.", {
23162
+ server.tool("goodmem_memories_pages_image", "Download memory page image content\n\nDownloads inline bytes for one page image. The page index is required. The optional dpi and content type query parameters act as rendition filters; if omitted, the server returns the unique rendition for that page or rejects ambiguous matches. Requires READ_MEMORY on the containing memory; authority may be granted through DIRECT_MEMBERS_OF its containing space.", {
22792
23163
  id: external_exports.string().describe("Memory UUID"),
22793
23164
  page_index: external_exports.number().int().describe("0-based page index"),
22794
23165
  dpi: external_exports.number().int().describe("Optional rendition filter. If omitted, the unique page-image rendition for the page is returned; if multiple renditions exist, specify dpi and/or content_type.").optional(),
22795
23166
  content_type: external_exports.string().describe("Optional rendition filter. MIME type of the desired page image, such as image/png.").optional(),
22796
23167
  max_inline_bytes: external_exports.number().int().positive().describe("Maximum bytes to deliver inline as a content block. Defaults to 8388608. Hard server ceiling: 67108864. If the payload exceeds this cap the tool errors with the actual size \u2014 retry with a larger value (up to the ceiling) to receive it.").optional()
22797
- }, async (args) => {
23168
+ }, { readOnlyHint: true, destructiveHint: false, openWorldHint: false }, async (args) => {
22798
23169
  if (typeof args.id !== "string" || args.id === "") {
22799
23170
  throw new Error("'id' must be a non-empty string");
22800
23171
  }
@@ -22855,12 +23226,8 @@ function registerTools() {
22855
23226
  name: external_exports.string().describe("Fully qualified factory class name of the post-processor to apply."),
22856
23227
  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()
22857
23228
  }).passthrough().describe("Optional post-processor configuration to transform retrieval results.").optional(),
22858
- logging: external_exports.object({
22859
- 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(),
22860
- 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()
22861
- }).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(),
22862
23229
  timeout_sec: external_exports.number().int().describe("Maximum seconds to wait for the streaming response (default: 200).").optional()
22863
- }, async (args) => {
23230
+ }, { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false }, async (args) => {
22864
23231
  const body = {};
22865
23232
  if (args.message !== void 0)
22866
23233
  body["message"] = args.message;
@@ -22880,8 +23247,6 @@ function registerTools() {
22880
23247
  body["hnsw"] = args.hnsw;
22881
23248
  if (args.post_processor !== void 0)
22882
23249
  body["postProcessor"] = args.post_processor;
22883
- if (args.logging !== void 0)
22884
- body["logging"] = args.logging;
22885
23250
  const result = await callApiNdjson("POST", `/v1/memories:retrieve`, body, void 0, args.timeout_sec);
22886
23251
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22887
23252
  });
@@ -22892,7 +23257,7 @@ function registerTools() {
22892
23257
  include_markdown: external_exports.boolean().describe("Include markdown rendering in the response").optional(),
22893
23258
  start_page: external_exports.number().int().describe("0-based inclusive start page").optional(),
22894
23259
  end_page: external_exports.number().int().describe("0-based inclusive end page").optional()
22895
- }, async (args) => {
23260
+ }, { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false }, async (args) => {
22896
23261
  const body = {};
22897
23262
  if (args.content !== void 0)
22898
23263
  body["content"] = args.content;
@@ -22909,14 +23274,14 @@ function registerTools() {
22909
23274
  const result = await callApi("POST", `/v1/ocr:document`, body);
22910
23275
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22911
23276
  });
22912
- server.tool("goodmem_ping_once", "Run a single ping probe\n\nRuns a single ping probe and returns the probe result.", {
23277
+ server.tool("goodmem_ping_once", "Run a single ping probe\n\nRuns a single ping probe and returns the probe result. Requires both the target-specific PING operation and its corresponding EXECUTE operation: PING_EMBEDDER plus EXECUTE_EMBEDDER, PING_RERANKER plus EXECUTE_RERANKER, or PING_LLM plus EXECUTE_LLM.", {
22913
23278
  target_id: external_exports.string().describe("Target resource ID (UUID)"),
22914
23279
  target_type_hint: external_exports.enum(["TARGET_TYPE_UNSPECIFIED", "EMBEDDER", "RERANKER", "LLM"]).describe("Optional hint for the target resource type").optional(),
22915
23280
  payload_type: external_exports.enum(["PAYLOAD_TYPE_UNSPECIFIED", "TEXT", "JSON", "BINARY"]).describe("Desired payload type (defaults to provider-specific value)").optional(),
22916
23281
  payload: external_exports.string().describe("Explicit UTF-8 payload to send with the probe (mutually exclusive with payload_size_bytes)").optional(),
22917
23282
  payload_size_bytes: external_exports.number().int().describe("Synthetic payload size in bytes (mutually exclusive with payload)").optional(),
22918
23283
  timeout_ms: external_exports.number().int().describe("Per-probe timeout in milliseconds (0 uses server default)").optional()
22919
- }, async (args) => {
23284
+ }, { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false }, async (args) => {
22920
23285
  const _set = [args.payload !== void 0 && args.payload !== null, args.payload_size_bytes !== void 0 && args.payload_size_bytes !== null].filter(Boolean).length;
22921
23286
  if (_set > 1) {
22922
23287
  throw new Error("at most one of payload or payload_size_bytes may be set");
@@ -22937,7 +23302,7 @@ function registerTools() {
22937
23302
  const result = await callApi("POST", `/v1/ping:once`, body, void 0, (args.timeout_ms ?? 5e3) + 3e4);
22938
23303
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22939
23304
  });
22940
- server.tool("goodmem_ping_stream", "Stream ping probe results\n\nOpens a streaming ping session and returns per-probe results plus a terminal summary.", {
23305
+ server.tool("goodmem_ping_stream", "Stream ping probe results\n\nOpens a streaming ping session and returns per-probe results plus a terminal summary. Requires both the target-specific PING operation and its corresponding EXECUTE operation: PING_EMBEDDER plus EXECUTE_EMBEDDER, PING_RERANKER plus EXECUTE_RERANKER, or PING_LLM plus EXECUTE_LLM.", {
22941
23306
  target_id: external_exports.string().describe("Target resource ID (UUID)"),
22942
23307
  target_type_hint: external_exports.enum(["TARGET_TYPE_UNSPECIFIED", "EMBEDDER", "RERANKER", "LLM"]).describe("Optional hint for the target resource type").optional(),
22943
23308
  count: external_exports.number().int().describe("Number of probes to run (0 uses server default)").optional(),
@@ -22950,7 +23315,7 @@ function registerTools() {
22950
23315
  jitter: external_exports.boolean().describe("Add jitter to probe scheduling").optional(),
22951
23316
  labels: external_exports.record(external_exports.string(), external_exports.string()).describe("Optional labels to attach to the ping session").optional(),
22952
23317
  timeout_sec: external_exports.number().int().describe("Maximum seconds to wait for the streaming response (default: 200).").optional()
22953
- }, async (args) => {
23318
+ }, { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false }, async (args) => {
22954
23319
  const _set = [args.payload !== void 0 && args.payload !== null, args.payload_size_bytes !== void 0 && args.payload_size_bytes !== null].filter(Boolean).length;
22955
23320
  if (_set > 1) {
22956
23321
  throw new Error("at most one of payload or payload_size_bytes may be set");
@@ -22981,7 +23346,7 @@ function registerTools() {
22981
23346
  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));
22982
23347
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
22983
23348
  });
22984
- 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.", {
23349
+ 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. A SHA-256 credential fingerprint participates in uniqueness checking. DEFAULTS: api_path defaults to '/v2/rerank' for Cohere and '/rerank' for other providers if omitted; supported_modalities defaults to [TEXT] if omitted. OWNER DEFAULTS: Owner defaults to the authenticated principal unless owner_id is provided; CREATE_RERANKER is evaluated against the proposed reranker and owner. This operation is NOT idempotent - each request creates a new reranker record.", {
22985
23350
  display_name: external_exports.string().describe("User-facing name of the reranker"),
22986
23351
  description: external_exports.string().describe("Description of the reranker").optional(),
22987
23352
  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(),
@@ -23009,9 +23374,9 @@ function registerTools() {
23009
23374
  labels: external_exports.record(external_exports.string(), external_exports.string()).describe("User-defined labels for categorization").optional(),
23010
23375
  version: external_exports.string().describe("Version information").optional(),
23011
23376
  monitoring_endpoint: external_exports.string().describe("Monitoring endpoint URL").optional(),
23012
- owner_id: external_exports.string().describe("Optional owner ID. If not provided, derived from the authentication context. Requires CREATE_RERANKER_ANY permission if specified.").optional(),
23377
+ owner_id: external_exports.string().describe("Optional owner principal UUID. If omitted, defaults to the authenticated principal. CREATE_RERANKER is evaluated against the proposed reranker and owner.").optional(),
23013
23378
  reranker_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()
23014
- }, async (args) => {
23379
+ }, { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false }, async (args) => {
23015
23380
  const inferred = autoInferFromRegistry("rerankers", args);
23016
23381
  checkCredentialRequired(inferred);
23017
23382
  const body = {};
@@ -23044,29 +23409,33 @@ function registerTools() {
23044
23409
  const result = await callApi("POST", `/v1/rerankers`, body);
23045
23410
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
23046
23411
  });
23047
- 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.", {
23412
+ 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 on the requested reranker. This operation is safe to retry - may return NOT_FOUND if already deleted.", {
23048
23413
  id: external_exports.string().describe("The unique identifier of the reranker to delete")
23049
- }, async (args) => {
23414
+ }, { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }, async (args) => {
23050
23415
  if (typeof args.id !== "string" || args.id === "") {
23051
23416
  throw new Error("'id' must be a non-empty string");
23052
23417
  }
23053
23418
  await callApi("DELETE", `/v1/rerankers/${encodeURIComponent(String(args.id))}`, void 0);
23054
23419
  return { content: [{ type: "text", text: "Deleted successfully." }] };
23055
23420
  });
23056
- 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.", {
23057
- id: external_exports.string().describe("The unique identifier of the reranker to retrieve")
23058
- }, async (args) => {
23421
+ server.tool("goodmem_rerankers_get", "Get a reranker by ID\n\nRetrieves the details of a specific reranker configuration by its unique identifier. Stored credentials are omitted unless include_credentials is true and the caller also has READ_RERANKER_CREDENTIALS. Requires READ_RERANKER on the requested reranker. The service distinguishes a missing reranker from an existing reranker the caller cannot read. This is a read-only operation with no side effects and is safe to retry.", {
23422
+ id: external_exports.string().describe("The unique identifier of the reranker to retrieve"),
23423
+ include_credentials: external_exports.boolean().describe("Whether to return stored credentials. Also accepts include_credentials. Requires READ_RERANKER_CREDENTIALS in addition to READ_RERANKER.").optional()
23424
+ }, { readOnlyHint: true, destructiveHint: false, openWorldHint: false }, async (args) => {
23059
23425
  if (typeof args.id !== "string" || args.id === "") {
23060
23426
  throw new Error("'id' must be a non-empty string");
23061
23427
  }
23062
- const result = await callApi("GET", `/v1/rerankers/${encodeURIComponent(String(args.id))}`, void 0);
23428
+ const query = {};
23429
+ if (args.include_credentials !== void 0)
23430
+ query["includeCredentials"] = String(args.include_credentials);
23431
+ const result = await callApi("GET", `/v1/rerankers/${encodeURIComponent(String(args.id))}`, void 0, query);
23063
23432
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
23064
23433
  });
23065
- 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.", {
23066
- 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(),
23434
+ 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. Stored credentials are never included in list responses. LABEL FILTERS: Label filters accept either label.<key>=<value> or label[key]=value (for example, label.environment=production or label[environment]=production). AUTHORIZATION: Requires LIST_RERANKER on the GoodMem instance. Each returned reranker must also be visible through READ_RERANKER; unauthorized rerankers are filtered in PostgreSQL. The owner_id parameter filters that already-authorized result set and does not grant additional visibility.", {
23435
+ owner_id: external_exports.string().describe("Filter the already-authorized result set by owner principal UUID. Omitting this parameter does not bypass per-reranker READ_RERANKER filtering.").optional(),
23067
23436
  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(),
23068
23437
  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()
23069
- }, async (args) => {
23438
+ }, { readOnlyHint: true, destructiveHint: false, openWorldHint: false }, async (args) => {
23070
23439
  const query = {};
23071
23440
  if (args.owner_id !== void 0)
23072
23441
  query["ownerId"] = String(args.owner_id);
@@ -23079,7 +23448,7 @@ function registerTools() {
23079
23448
  const result = await callApi("GET", `/v1/rerankers`, void 0, query);
23080
23449
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
23081
23450
  });
23082
- 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.", {
23451
+ 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 on the requested reranker. This operation is idempotent.", {
23083
23452
  id: external_exports.string().describe("The unique identifier of the reranker to update"),
23084
23453
  display_name: external_exports.string().describe("User-facing name of the reranker").optional(),
23085
23454
  description: external_exports.string().describe("Description of the reranker").optional(),
@@ -23103,12 +23472,12 @@ function registerTools() {
23103
23472
  quotaProjectId: external_exports.string().describe("Optional quota project used for billing").optional()
23104
23473
  }).passthrough().describe("Configuration when kind is CREDENTIAL_KIND_GCP_ADC").optional(),
23105
23474
  labels: external_exports.record(external_exports.string(), external_exports.string()).describe('Optional annotations to aid operators (e.g., "owner=vertex")').optional()
23106
- }).passthrough().describe("Structured credential payload describing how to authenticate with the provider. Omit to keep existing credentials.").optional(),
23475
+ }).passthrough().describe("Replace stored credentials. Omit this field to preserve the current credentials; a present empty payload is invalid and never clears them.").optional(),
23107
23476
  replace_labels: external_exports.record(external_exports.string(), external_exports.string()).describe("Replace all existing labels with these (mutually exclusive with merge_labels)").optional(),
23108
23477
  merge_labels: external_exports.record(external_exports.string(), external_exports.string()).describe("Merge these labels with existing ones (mutually exclusive with replace_labels)").optional(),
23109
23478
  version: external_exports.string().describe("Version information").optional(),
23110
23479
  monitoring_endpoint: external_exports.string().describe("Monitoring endpoint URL").optional()
23111
- }, async (args) => {
23480
+ }, { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }, async (args) => {
23112
23481
  if (typeof args.id !== "string" || args.id === "") {
23113
23482
  throw new Error("'id' must be a non-empty string");
23114
23483
  }
@@ -23142,15 +23511,112 @@ function registerTools() {
23142
23511
  const result = await callApi("PUT", `/v1/rerankers/${encodeURIComponent(String(args.id))}`, body);
23143
23512
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
23144
23513
  });
23145
- 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.", {
23514
+ server.tool("goodmem_service_identities_create", "Create a service identity\n\nCreates one production-workload identity owned by the authenticated human. No API key, role, grant, or authentication mapping is created implicitly.", {
23515
+ service_identity_id: external_exports.string().describe("Optional client-provided UUID; generated by the server when omitted.").optional(),
23516
+ display_name: external_exports.string().describe("Globally unique, nonblank operator-facing name."),
23517
+ description: external_exports.string().describe("Optional operator description.").optional(),
23518
+ labels: external_exports.record(external_exports.string(), external_exports.string()).describe("Optional labels for organization and filtering. At most 20 entries; keys and values contain at most 255 characters; keys use [a-z0-9._-].").optional()
23519
+ }, { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false }, async (args) => {
23520
+ const body = {};
23521
+ if (args.service_identity_id !== void 0)
23522
+ body["serviceIdentityId"] = args.service_identity_id;
23523
+ if (args.display_name !== void 0)
23524
+ body["displayName"] = args.display_name;
23525
+ if (args.description !== void 0)
23526
+ body["description"] = args.description;
23527
+ if (args.labels !== void 0)
23528
+ body["labels"] = args.labels;
23529
+ const result = await callApi("POST", `/v1/service-identities`, body);
23530
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
23531
+ });
23532
+ server.tool("goodmem_service_identities_delete", "Delete a service identity\n\nPermanently soft-deletes the principal. Its stored credentials remain audit records but can no longer authenticate because their subject is deleted. Repeating an authorized delete succeeds without rewriting audit data.", {
23533
+ id: external_exports.string().describe("Service-identity UUID")
23534
+ }, { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }, async (args) => {
23535
+ if (typeof args.id !== "string" || args.id === "") {
23536
+ throw new Error("'id' must be a non-empty string");
23537
+ }
23538
+ await callApi("DELETE", `/v1/service-identities/${encodeURIComponent(String(args.id))}`, void 0);
23539
+ return { content: [{ type: "text", text: "Deleted successfully." }] };
23540
+ });
23541
+ server.tool("goodmem_service_identities_get", "Get a service identity\n\nReturns a service identity after applying READ_SERVICE_IDENTITY authority. include_deleted permits an authorized caller to inspect a permanent tombstone; it does not grant additional authority.", {
23542
+ id: external_exports.string().describe("Service-identity UUID"),
23543
+ include_deleted: external_exports.boolean().describe("Permit an authorized read of a permanent tombstone").optional()
23544
+ }, { readOnlyHint: true, destructiveHint: false, openWorldHint: false }, async (args) => {
23545
+ if (typeof args.id !== "string" || args.id === "") {
23546
+ throw new Error("'id' must be a non-empty string");
23547
+ }
23548
+ const query = {};
23549
+ if (args.include_deleted !== void 0)
23550
+ query["includeDeleted"] = String(args.include_deleted);
23551
+ const result = await callApi("GET", `/v1/service-identities/${encodeURIComponent(String(args.id))}`, void 0, query);
23552
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
23553
+ });
23554
+ server.tool("goodmem_service_identities_list", "List service identities\n\nRequires LIST_SERVICE_IDENTITY on the GoodMem instance and READ_SERVICE_IDENTITY on each returned row. Owner and label filters, lifecycle filtering, authorization, and keyset pagination execute in PostgreSQL. LABEL FILTERS: Label filters accept either label.<key>=<value> or label[key]=value (for example, label.environment=production or label[environment]=production).", {
23555
+ owner_principal_id: external_exports.string().describe("Exact current administrative-owner principal UUID").optional(),
23556
+ include_deleted: external_exports.boolean().describe("Include readable permanent tombstones").optional(),
23557
+ max_results: external_exports.number().int().describe("Page size; defaults to 50 and must be between 1 and 1000").optional(),
23558
+ next_token: external_exports.string().describe("Opaque continuation token").optional(),
23559
+ 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()
23560
+ }, { readOnlyHint: true, destructiveHint: false, openWorldHint: false }, async (args) => {
23561
+ const query = {};
23562
+ if (args.owner_principal_id !== void 0)
23563
+ query["ownerPrincipalId"] = String(args.owner_principal_id);
23564
+ if (args.include_deleted !== void 0)
23565
+ query["includeDeleted"] = String(args.include_deleted);
23566
+ if (args.max_results !== void 0)
23567
+ query["maxResults"] = String(args.max_results);
23568
+ if (args.next_token !== void 0)
23569
+ query["nextToken"] = String(args.next_token);
23570
+ if (args.label) {
23571
+ for (const [k, v] of Object.entries(args.label))
23572
+ query[`label[${k}]`] = v;
23573
+ }
23574
+ const result = await callApi("GET", `/v1/service-identities`, void 0, query);
23575
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
23576
+ });
23577
+ server.tool("goodmem_service_identities_transfer_ownership", "Transfer service-identity ownership\n\nTransfers administrative ownership to another active principal. A service identity cannot own itself. The service identity's subject, immutable creator, credentials, grants, and roles are unchanged.", {
23578
+ id: external_exports.string().describe("Service-identity UUID"),
23579
+ new_owner_id: external_exports.string().describe("Existing principal UUID that will become the new owner.")
23580
+ }, { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false }, async (args) => {
23581
+ if (typeof args.id !== "string" || args.id === "") {
23582
+ throw new Error("'id' must be a non-empty string");
23583
+ }
23584
+ const body = {};
23585
+ if (args.new_owner_id !== void 0)
23586
+ body["newOwnerId"] = args.new_owner_id;
23587
+ const result = await callApi("POST", `/v1/service-identities/${encodeURIComponent(String(args.id))}:transferOwnership`, body);
23588
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
23589
+ });
23590
+ server.tool("goodmem_service_identities_update", "Update a service identity\n\nUpdates only fields present in the request. Empty description clears that optional field. Ownership changes use the dedicated transfer endpoint.", {
23591
+ id: external_exports.string().describe("Service-identity UUID"),
23592
+ display_name: external_exports.string().describe("Replacement display name. A present blank value is invalid.").optional(),
23593
+ description: external_exports.string().describe("Replacement description. An empty string clears the description.").optional(),
23594
+ replace_labels: external_exports.record(external_exports.string(), external_exports.string()).describe("Complete replacement label map; an empty map clears all labels and is mutually exclusive with merge_labels. At most 20 entries; keys and values contain at most 255 characters; keys use [a-z0-9._-].").optional(),
23595
+ merge_labels: external_exports.record(external_exports.string(), external_exports.string()).describe("Labels to upsert; must contain at least one entry and is mutually exclusive with replace_labels. The final stored map may contain at most 20 entries; keys and values contain at most 255 characters; keys use [a-z0-9._-].").optional()
23596
+ }, { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }, async (args) => {
23597
+ if (typeof args.id !== "string" || args.id === "") {
23598
+ throw new Error("'id' must be a non-empty string");
23599
+ }
23600
+ const body = {};
23601
+ if (args.display_name !== void 0)
23602
+ body["displayName"] = args.display_name;
23603
+ if (args.description !== void 0)
23604
+ body["description"] = args.description;
23605
+ if (args.replace_labels !== void 0)
23606
+ body["replaceLabels"] = args.replace_labels;
23607
+ if (args.merge_labels !== void 0)
23608
+ body["mergeLabels"] = args.merge_labels;
23609
+ const result = await callApi("PUT", `/v1/service-identities/${encodeURIComponent(String(args.id))}`, body);
23610
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
23611
+ });
23612
+ 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 the authenticated principal unless owner_id is provided; CREATE_SPACE is evaluated against the proposed space and owner. EMBEDDER REQUIREMENTS: At least one embedder configuration must be specified. Every referenced embedder must exist, and the caller must have EXECUTE_EMBEDDER on each one. DUPLICATE DETECTION: Returns ALREADY_EXISTS if another space exists with identical {owner_id, name} (case-sensitive). This operation is NOT idempotent.", {
23146
23613
  name: external_exports.string().describe("The desired name for the space. Must be unique within the user's scope."),
23147
23614
  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(),
23148
23615
  space_embedders: external_exports.array(external_exports.object({
23149
23616
  embedderId: external_exports.string().describe("The UUID for the embedder to associate with the space."),
23150
23617
  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()
23151
23618
  }).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."),
23152
- 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(),
23153
- 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(),
23619
+ owner_id: external_exports.string().describe("Optional owner principal UUID. If omitted, defaults to the authenticated principal. CREATE_SPACE is evaluated against the proposed space and owner.").optional(),
23154
23620
  default_chunking_config: external_exports.object({
23155
23621
  none: external_exports.any().describe("No chunking strategy - preserve original content as single unit").optional(),
23156
23622
  recursive: external_exports.object({
@@ -23169,7 +23635,7 @@ function registerTools() {
23169
23635
  }).passthrough().describe("Sentence-based chunking strategy with language detection").optional()
23170
23636
  }).passthrough().describe("Default chunking strategy for memories in this space"),
23171
23637
  space_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()
23172
- }, async (args) => {
23638
+ }, { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false }, async (args) => {
23173
23639
  const body = {};
23174
23640
  if (args.name !== void 0)
23175
23641
  body["name"] = args.name;
@@ -23177,8 +23643,6 @@ function registerTools() {
23177
23643
  body["labels"] = args.labels;
23178
23644
  if (args.space_embedders !== void 0)
23179
23645
  body["spaceEmbedders"] = args.space_embedders;
23180
- if (args.public_read !== void 0)
23181
- body["publicRead"] = args.public_read;
23182
23646
  if (args.owner_id !== void 0)
23183
23647
  body["ownerId"] = args.owner_id;
23184
23648
  if (args.default_chunking_config !== void 0)
@@ -23188,18 +23652,18 @@ function registerTools() {
23188
23652
  const result = await callApi("POST", `/v1/spaces`, body);
23189
23653
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
23190
23654
  });
23191
- 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.", {
23655
+ 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 on the requested space. This operation is safe to retry and may return NOT_FOUND if the space was already deleted.", {
23192
23656
  id: external_exports.string().describe("The unique identifier of the space to delete")
23193
- }, async (args) => {
23657
+ }, { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }, async (args) => {
23194
23658
  if (typeof args.id !== "string" || args.id === "") {
23195
23659
  throw new Error("'id' must be a non-empty string");
23196
23660
  }
23197
23661
  await callApi("DELETE", `/v1/spaces/${encodeURIComponent(String(args.id))}`, void 0);
23198
23662
  return { content: [{ type: "text", text: "Deleted successfully." }] };
23199
23663
  });
23200
- 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.", {
23664
+ 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. Requires READ_SPACE on the requested space. The service distinguishes a missing space from an existing space the caller cannot read. This is a read-only operation safe to retry.", {
23201
23665
  id: external_exports.string().describe("The unique identifier of the space to retrieve")
23202
- }, async (args) => {
23666
+ }, { readOnlyHint: true, destructiveHint: false, openWorldHint: false }, async (args) => {
23203
23667
  if (typeof args.id !== "string" || args.id === "") {
23204
23668
  throw new Error("'id' must be a non-empty string");
23205
23669
  }
@@ -23207,14 +23671,14 @@ function registerTools() {
23207
23671
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
23208
23672
  });
23209
23673
  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.", {
23210
- 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(),
23674
+ owner_id: external_exports.string().describe("Filter the already-authorized result set by owner principal UUID. Omitting this parameter does not bypass per-space READ_SPACE filtering.").optional(),
23211
23675
  name_filter: external_exports.string().describe("Filter spaces by name using glob pattern matching").optional(),
23212
23676
  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(),
23213
23677
  next_token: external_exports.string().describe("Pagination token for retrieving the next set of results").optional(),
23214
23678
  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(),
23215
23679
  sort_order: external_exports.enum(["ASCENDING", "DESCENDING", "SORT_ORDER_UNSPECIFIED"]).describe("Sort order (ASCENDING or DESCENDING, default: DESCENDING)").optional(),
23216
23680
  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()
23217
- }, async (args) => {
23681
+ }, { readOnlyHint: true, destructiveHint: false, openWorldHint: false }, async (args) => {
23218
23682
  const query = {};
23219
23683
  if (args.owner_id !== void 0)
23220
23684
  query["ownerId"] = String(args.owner_id);
@@ -23235,13 +23699,25 @@ function registerTools() {
23235
23699
  const result = await callApi("GET", `/v1/spaces`, void 0, query);
23236
23700
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
23237
23701
  });
23238
- 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.", {
23702
+ server.tool("goodmem_spaces_transfer_ownership", "Transfer ownership of a space\n\nTransfers an existing space to another active human or service principal. The current space owner, the GoodMem instance owner, or an instance administrator may transfer it; a space-scoped administrator cannot. Only owner and update-audit fields change. Memories, embedder associations, grants, and role assignments remain unchanged. This operation is not idempotent under response semantics: after an unknown outcome, read the space before retrying.", {
23703
+ id: external_exports.string().describe("UUID of the existing space to transfer"),
23704
+ new_owner_id: external_exports.string().describe("Existing principal UUID that will become the new owner.")
23705
+ }, { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false }, async (args) => {
23706
+ if (typeof args.id !== "string" || args.id === "") {
23707
+ throw new Error("'id' must be a non-empty string");
23708
+ }
23709
+ const body = {};
23710
+ if (args.new_owner_id !== void 0)
23711
+ body["newOwnerId"] = args.new_owner_id;
23712
+ const result = await callApi("POST", `/v1/spaces/${encodeURIComponent(String(args.id))}:transferOwnership`, body);
23713
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
23714
+ });
23715
+ server.tool("goodmem_spaces_update", "Update a space\n\nUpdates an existing space with new values for the specified fields. Only name 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 an existing space. Requires UPDATE_SPACE on the requested space. This operation is idempotent.", {
23239
23716
  id: external_exports.string().describe("The unique identifier of the space to update"),
23240
23717
  name: external_exports.string().describe("The new name for the space.").optional(),
23241
- public_read: external_exports.boolean().describe("Whether the space is publicly readable by all users.").optional(),
23242
23718
  replace_labels: external_exports.record(external_exports.string(), external_exports.string()).describe("Labels to replace all existing labels. Mutually exclusive with merge_labels.").optional(),
23243
23719
  merge_labels: external_exports.record(external_exports.string(), external_exports.string()).describe("Labels to merge with existing labels. Mutually exclusive with replace_labels.").optional()
23244
- }, async (args) => {
23720
+ }, { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }, async (args) => {
23245
23721
  if (typeof args.id !== "string" || args.id === "") {
23246
23722
  throw new Error("'id' must be a non-empty string");
23247
23723
  }
@@ -23252,8 +23728,6 @@ function registerTools() {
23252
23728
  const body = {};
23253
23729
  if (args.name !== void 0)
23254
23730
  body["name"] = args.name;
23255
- if (args.public_read !== void 0)
23256
- body["publicRead"] = args.public_read;
23257
23731
  if (args.replace_labels !== void 0)
23258
23732
  body["replaceLabels"] = args.replace_labels;
23259
23733
  if (args.merge_labels !== void 0)
@@ -23261,18 +23735,80 @@ function registerTools() {
23261
23735
  const result = await callApi("PUT", `/v1/spaces/${encodeURIComponent(String(args.id))}`, body);
23262
23736
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
23263
23737
  });
23264
- 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) => {
23738
+ 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.", {}, { readOnlyHint: true, destructiveHint: false, openWorldHint: false }, async (args) => {
23265
23739
  const result = await callApi("GET", `/v1/system/info`, void 0);
23266
23740
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
23267
23741
  });
23268
- 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) => {
23742
+ 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.", {}, { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false }, async (args) => {
23269
23743
  const result = await callApi("POST", `/v1/system/init`, void 0);
23270
23744
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
23271
23745
  });
23746
+ server.tool("goodmem_user_enrollments_complete", "Complete human-user enrollment\n\nExchanges a one-time enrollment credential for the human's initial self-owned API key. This endpoint does not use ordinary API-key authentication. Omit both optional key fields for server generation; a fresh response discloses the raw key exactly once and cannot be safely retried after an ambiguous outcome. Supply and retain both fields for client mode, where only an exact tuple retry is safe and raw material is never echoed. Status precedence is request-shape errors (400), then the same generic authentication failure for every nonblank unusable enrollment (401); key conflicts or concurrent lifecycle winners return 409, rate limits return 429, and unexpected failures return 500.", {
23747
+ enrollment_token: external_exports.string().describe("Required one-time enrollment credential. Never log or persist it."),
23748
+ api_key_id: external_exports.string().describe("Optional client-generated API-key UUID retained for exact retries. Must be supplied together with raw_api_key, or both fields must be omitted.").optional(),
23749
+ raw_api_key: external_exports.string().describe("Optional canonical client-generated API key. Must be supplied together with api_key_id, never logged, and retained for exact retries; omit both fields for server generation.").optional()
23750
+ }, { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false }, async (args) => {
23751
+ const body = {};
23752
+ if (args.enrollment_token !== void 0)
23753
+ body["enrollmentToken"] = args.enrollment_token;
23754
+ if (args.api_key_id !== void 0)
23755
+ body["apiKeyId"] = args.api_key_id;
23756
+ if (args.raw_api_key !== void 0)
23757
+ body["rawApiKey"] = args.raw_api_key;
23758
+ const result = await callApi("POST", `/v1/user-enrollments:complete`, body);
23759
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
23760
+ });
23761
+ server.tool("goodmem_users_create", "Create a human user\n\nCreates one dormant human user after requiring instance-wide CREATE_USER authority. Creation does not issue a credential or create a role, grant, or authentication mapping.", {
23762
+ user_id: external_exports.string().describe("Optional client-provided user UUID; generated by the server when omitted.").optional(),
23763
+ email: external_exports.string().describe("Unique, nonempty email address."),
23764
+ username: external_exports.string().describe("Optional unique username.").optional(),
23765
+ display_name: external_exports.string().describe("Optional human-facing display name.").optional(),
23766
+ labels: external_exports.record(external_exports.string(), external_exports.string()).describe("Optional labels for organization and filtering. At most 20 entries; keys and values contain at most 255 characters; keys use [a-z0-9._-].").optional()
23767
+ }, { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false }, async (args) => {
23768
+ const body = {};
23769
+ if (args.user_id !== void 0)
23770
+ body["userId"] = args.user_id;
23771
+ if (args.email !== void 0)
23772
+ body["email"] = args.email;
23773
+ if (args.username !== void 0)
23774
+ body["username"] = args.username;
23775
+ if (args.display_name !== void 0)
23776
+ body["displayName"] = args.display_name;
23777
+ if (args.labels !== void 0)
23778
+ body["labels"] = args.labels;
23779
+ const result = await callApi("POST", `/v1/users`, body);
23780
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
23781
+ });
23782
+ server.tool("goodmem_users_create_enrollment", "Create a human-user enrollment\n\nCreates a short-lived, one-time enrollment credential for an existing dormant human. Requires MANAGE_USER_ENROLLMENT with ANY or EXACT authority on the target user. The raw credential is returned only once. rotate_existing atomically revokes and replaces a live enrollment; an expired enrollment is replaced automatically.", {
23783
+ user_id: external_exports.string().describe("Human-user UUID"),
23784
+ enrollment_id: external_exports.string().describe("Optional client-provided enrollment UUID; generated when omitted.").optional(),
23785
+ rotate_existing: external_exports.boolean().describe("Whether an existing live enrollment may be revoked and atomically replaced.").optional()
23786
+ }, { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false }, async (args) => {
23787
+ if (typeof args.user_id !== "string" || args.user_id === "") {
23788
+ throw new Error("'user_id' must be a non-empty string");
23789
+ }
23790
+ const body = {};
23791
+ if (args.enrollment_id !== void 0)
23792
+ body["enrollmentId"] = args.enrollment_id;
23793
+ if (args.rotate_existing !== void 0)
23794
+ body["rotateExisting"] = args.rotate_existing;
23795
+ const result = await callApi("POST", `/v1/users/${encodeURIComponent(String(args.user_id))}/enrollments`, body);
23796
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
23797
+ });
23798
+ server.tool("goodmem_users_delete", "Delete a human user\n\nPermanently soft-deletes the user and invalidates credentials acting for that subject. The GoodMem instance owner cannot be deleted; transfer ownership first. Repeating an authorized delete succeeds without rewriting audit data.", {
23799
+ id: external_exports.string().describe("User UUID")
23800
+ }, { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }, async (args) => {
23801
+ if (typeof args.id !== "string" || args.id === "") {
23802
+ throw new Error("'id' must be a non-empty string");
23803
+ }
23804
+ await callApi("DELETE", `/v1/users/${encodeURIComponent(String(args.id))}`, void 0);
23805
+ return { content: [{ type: "text", text: "Deleted successfully." }] };
23806
+ });
23272
23807
  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.", {
23273
- id: external_exports.string().describe("The UUID of the user to retrieve").optional(),
23274
- email: external_exports.string().describe("The email address of the user to retrieve").optional()
23275
- }, async (args) => {
23808
+ id: external_exports.string().describe("User UUID").optional(),
23809
+ email: external_exports.string().describe("URL-encoded email address").optional(),
23810
+ include_deleted: external_exports.boolean().describe("Permit an authorized read of a permanent tombstone").optional()
23811
+ }, { readOnlyHint: true, destructiveHint: false, openWorldHint: false }, async (args) => {
23276
23812
  const _hasId = typeof args.id === "string" && args.id !== "";
23277
23813
  const _hasEmail = typeof args.email === "string" && args.email !== "";
23278
23814
  if (_hasId && _hasEmail)
@@ -23280,13 +23816,119 @@ function registerTools() {
23280
23816
  if (!_hasId && !_hasEmail)
23281
23817
  throw new Error("Exactly one of 'id', 'email' must be provided (non-empty).");
23282
23818
  const url = _hasEmail ? `/v1/users/email/${encodeURIComponent(String(args.email))}` : `/v1/users/${encodeURIComponent(String(args.id))}`;
23283
- const result = await callApi("GET", url, void 0);
23819
+ const query = {};
23820
+ if (args.include_deleted !== void 0)
23821
+ query["includeDeleted"] = String(args.include_deleted);
23822
+ const result = await callApi("GET", url, void 0, query);
23823
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
23824
+ });
23825
+ server.tool("goodmem_users_get_by_username", "Get user by username\n\nReturns a user selected by exact username after applying READ_USER authority. include_deleted permits an authorized caller to inspect a permanent tombstone; it does not grant additional authority. Missing and unauthorized matches both return 404 so this guessable identifier cannot reveal whether a user exists.", {
23826
+ username: external_exports.string().describe("Exact username"),
23827
+ include_deleted: external_exports.boolean().describe("Permit an authorized read of a permanent tombstone").optional()
23828
+ }, { readOnlyHint: true, destructiveHint: false, openWorldHint: false }, async (args) => {
23829
+ if (typeof args.username !== "string" || args.username === "") {
23830
+ throw new Error("'username' must be a non-empty string");
23831
+ }
23832
+ const query = {};
23833
+ if (args.include_deleted !== void 0)
23834
+ query["includeDeleted"] = String(args.include_deleted);
23835
+ const result = await callApi("GET", `/v1/users/username/${encodeURIComponent(String(args.username))}`, void 0, query);
23836
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
23837
+ });
23838
+ server.tool("goodmem_users_get_enrollment", "Get a human-user enrollment\n\nReturns non-secret metadata for one current or historical enrollment. The target user is resolved before the enrollment, and MANAGE_USER_ENROLLMENT with ANY or EXACT authority on that user is required. Raw enrollment tokens are never returned.", {
23839
+ user_id: external_exports.string().describe("Human-user UUID"),
23840
+ enrollment_id: external_exports.string().describe("Enrollment UUID")
23841
+ }, { readOnlyHint: true, destructiveHint: false, openWorldHint: false }, async (args) => {
23842
+ if (typeof args.user_id !== "string" || args.user_id === "") {
23843
+ throw new Error("'user_id' must be a non-empty string");
23844
+ }
23845
+ if (typeof args.enrollment_id !== "string" || args.enrollment_id === "") {
23846
+ throw new Error("'enrollment_id' must be a non-empty string");
23847
+ }
23848
+ const result = await callApi("GET", `/v1/users/${encodeURIComponent(String(args.user_id))}/enrollments/${encodeURIComponent(String(args.enrollment_id))}`, void 0);
23849
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
23850
+ });
23851
+ server.tool("goodmem_users_list", "List human users\n\nRequires LIST_USER on the GoodMem instance and READ_USER on each returned row. Authorization, label filtering, lifecycle filtering, and keyset pagination run in PostgreSQL. include_deleted expands the lifecycle view but grants no access. include_enrollment_summary requests non-secret bootstrap posture only on active rows where MANAGE_USER_ENROLLMENT is independently authorized. LABEL FILTERS: Label filters accept either label.<key>=<value> or label[key]=value (for example, label.environment=production or label[environment]=production).", {
23852
+ include_deleted: external_exports.boolean().describe("Include readable permanent tombstones").optional(),
23853
+ include_enrollment_summary: external_exports.boolean().describe("Request non-secret enrollment posture on active rows where the caller also has MANAGE_USER_ENROLLMENT").optional(),
23854
+ max_results: external_exports.number().int().describe("Page size; defaults to 50 and must be between 1 and 1000").optional(),
23855
+ next_token: external_exports.string().describe("Opaque continuation token").optional(),
23856
+ 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()
23857
+ }, { readOnlyHint: true, destructiveHint: false, openWorldHint: false }, async (args) => {
23858
+ const query = {};
23859
+ if (args.include_deleted !== void 0)
23860
+ query["includeDeleted"] = String(args.include_deleted);
23861
+ if (args.include_enrollment_summary !== void 0)
23862
+ query["includeEnrollmentSummary"] = String(args.include_enrollment_summary);
23863
+ if (args.max_results !== void 0)
23864
+ query["maxResults"] = String(args.max_results);
23865
+ if (args.next_token !== void 0)
23866
+ query["nextToken"] = String(args.next_token);
23867
+ if (args.label) {
23868
+ for (const [k, v] of Object.entries(args.label))
23869
+ query[`label[${k}]`] = v;
23870
+ }
23871
+ const result = await callApi("GET", `/v1/users`, void 0, query);
23872
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
23873
+ });
23874
+ server.tool("goodmem_users_list_enrollments", "List a human user's enrollments\n\nReturns one newest-first page containing pending, expired, consumed, and revoked enrollment metadata. MANAGE_USER_ENROLLMENT with ANY or EXACT authority on the target user is required. The page never contains raw credentials.", {
23875
+ user_id: external_exports.string().describe("Human-user UUID"),
23876
+ max_results: external_exports.number().int().describe("Page size; defaults to 50 and must be between 1 and 1000").optional(),
23877
+ next_token: external_exports.string().describe("Opaque continuation token").optional()
23878
+ }, { readOnlyHint: true, destructiveHint: false, openWorldHint: false }, async (args) => {
23879
+ if (typeof args.user_id !== "string" || args.user_id === "") {
23880
+ throw new Error("'user_id' must be a non-empty string");
23881
+ }
23882
+ const query = {};
23883
+ if (args.max_results !== void 0)
23884
+ query["maxResults"] = String(args.max_results);
23885
+ if (args.next_token !== void 0)
23886
+ query["nextToken"] = String(args.next_token);
23887
+ const result = await callApi("GET", `/v1/users/${encodeURIComponent(String(args.user_id))}/enrollments`, void 0, query);
23284
23888
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
23285
23889
  });
23286
- 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) => {
23890
+ server.tool("goodmem_users_me", "Get current user profile\n\nReturns the human-user profile associated with the authenticated principal. Service principals do not have a human-user profile.", {}, { readOnlyHint: true, destructiveHint: false, openWorldHint: false }, async (args) => {
23287
23891
  const result = await callApi("GET", `/v1/users/me`, void 0);
23288
23892
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
23289
23893
  });
23894
+ server.tool("goodmem_users_revoke_enrollment", "Revoke a human-user enrollment\n\nPermanently revokes one outstanding enrollment after requiring MANAGE_USER_ENROLLMENT on its target user. Repeating an authorized revocation succeeds without replacing its original audit provenance. Consumed enrollments cannot be revoked.", {
23895
+ user_id: external_exports.string().describe("Human-user UUID"),
23896
+ enrollment_id: external_exports.string().describe("Enrollment UUID")
23897
+ }, { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }, async (args) => {
23898
+ if (typeof args.user_id !== "string" || args.user_id === "") {
23899
+ throw new Error("'user_id' must be a non-empty string");
23900
+ }
23901
+ if (typeof args.enrollment_id !== "string" || args.enrollment_id === "") {
23902
+ throw new Error("'enrollment_id' must be a non-empty string");
23903
+ }
23904
+ await callApi("DELETE", `/v1/users/${encodeURIComponent(String(args.user_id))}/enrollments/${encodeURIComponent(String(args.enrollment_id))}`, void 0);
23905
+ return { content: [{ type: "text", text: "Deleted successfully." }] };
23906
+ });
23907
+ server.tool("goodmem_users_update", "Update a human user\n\nUpdates only fields present in the request. Empty username or display_name values clear those optional fields. Updating a deleted user fails with 412.", {
23908
+ id: external_exports.string().describe("User UUID"),
23909
+ email: external_exports.string().describe("Replacement email. A present empty value is invalid.").optional(),
23910
+ username: external_exports.string().describe("Replacement username. An empty string clears the username.").optional(),
23911
+ display_name: external_exports.string().describe("Replacement display name. An empty string clears the display name.").optional(),
23912
+ replace_labels: external_exports.record(external_exports.string(), external_exports.string()).describe("Complete replacement label map; an empty map clears all labels and is mutually exclusive with merge_labels. At most 20 entries; keys and values contain at most 255 characters; keys use [a-z0-9._-].").optional(),
23913
+ merge_labels: external_exports.record(external_exports.string(), external_exports.string()).describe("Labels to upsert; must contain at least one entry and is mutually exclusive with replace_labels. The final stored map may contain at most 20 entries; keys and values contain at most 255 characters; keys use [a-z0-9._-].").optional()
23914
+ }, { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }, async (args) => {
23915
+ if (typeof args.id !== "string" || args.id === "") {
23916
+ throw new Error("'id' must be a non-empty string");
23917
+ }
23918
+ const body = {};
23919
+ if (args.email !== void 0)
23920
+ body["email"] = args.email;
23921
+ if (args.username !== void 0)
23922
+ body["username"] = args.username;
23923
+ if (args.display_name !== void 0)
23924
+ body["displayName"] = args.display_name;
23925
+ if (args.replace_labels !== void 0)
23926
+ body["replaceLabels"] = args.replace_labels;
23927
+ if (args.merge_labels !== void 0)
23928
+ body["mergeLabels"] = args.merge_labels;
23929
+ const result = await callApi("PUT", `/v1/users/${encodeURIComponent(String(args.id))}`, body);
23930
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
23931
+ });
23290
23932
  }
23291
23933
  registerTools();
23292
23934
  async function healthCheck() {