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