@pairsystems/goodmem-mcp 0.1.13 → 0.1.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +54 -58
- package/dist/index.js +459 -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.15"
|
|
21931
21847
|
});
|
|
21932
|
-
server.tool("goodmem_configure", "Configure GoodMem server connection. Call this before using other GoodMem tools if GOODMEM_BASE_URL and GOODMEM_API_KEY environment variables are not set. Session credentials override environment variables.", {
|
|
21848
|
+
server.tool("goodmem_configure", "Configure GoodMem server connection. Call this before using other GoodMem tools if GOODMEM_BASE_URL and GOODMEM_API_KEY environment variables are not set. Session credentials override environment variables. For bootstrap workflows that need to call /v1/system/info or /v1/system/init before keys exist, api_key may be omitted; most other tools will return AuthenticationException until a real key is provided.", {
|
|
21933
21849
|
base_url: external_exports.string().describe("GoodMem server URL (e.g., https://your-server.example.com)"),
|
|
21934
|
-
api_key: external_exports.string().describe("GoodMem API key (starts with gm_)")
|
|
21850
|
+
api_key: external_exports.string().describe("GoodMem API key (starts with gm_). Omit for bootstrap-only sessions that call /v1/system/info or /v1/system/init.").optional()
|
|
21935
21851
|
}, async (args) => {
|
|
21936
21852
|
if (!/^https?:\/\//i.test(args.base_url)) {
|
|
21937
|
-
return { content: [{ type: "text", text: `base_url must include a scheme (e.g., http://${args.base_url} or https://${args.base_url}).` }] };
|
|
21853
|
+
return { isError: true, content: [{ type: "text", text: `base_url must include a scheme (e.g., http://${args.base_url} or https://${args.base_url}).` }] };
|
|
21938
21854
|
}
|
|
21939
21855
|
try {
|
|
21940
|
-
const url =
|
|
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.15",
|
|
21929
|
+
based_on_goodmem_commit: BASED_ON_GOODMEM_COMMIT
|
|
21999
21930
|
}, null, 2) }] };
|
|
22000
21931
|
});
|
|
22001
21932
|
function registerTools() {
|
|
22002
|
-
server.tool("goodmem_admin_background_jobs_purge", "Purge completed background jobs", {
|
|
21933
|
+
server.tool("goodmem_admin_background_jobs_purge", "Purge completed background jobs\n\nDeletes terminal background jobs older than a retention cutoff.", {
|
|
22003
21934
|
older_than: external_exports.string().describe("ISO-8601 timestamp cutoff; only terminal jobs older than this instant are eligible."),
|
|
22004
|
-
statuses: external_exports.array(external_exports.
|
|
21935
|
+
statuses: external_exports.array(external_exports.enum(["BACKGROUND_JOB_SUCCEEDED", "BACKGROUND_JOB_FAILED", "BACKGROUND_JOB_CANCELED"])).describe("Optional terminal background job statuses to target for purging. If omitted, all terminal statuses are eligible. Canonical values are BACKGROUND_JOB_SUCCEEDED, BACKGROUND_JOB_FAILED, and BACKGROUND_JOB_CANCELED. Short aliases SUCCEEDED, FAILED, and CANCELED are also accepted for compatibility.").optional(),
|
|
22005
21936
|
dry_run: external_exports.boolean().describe("If true, report purge counts without deleting any rows.").optional(),
|
|
22006
|
-
limit: external_exports.number().int().describe("Maximum number of jobs to purge in this request.").optional()
|
|
21937
|
+
limit: external_exports.number().int().describe("Maximum number of jobs to purge in this request. Must be >= 0; 0 means no limit.").optional()
|
|
22007
21938
|
}, async (args) => {
|
|
22008
21939
|
const body = {};
|
|
22009
21940
|
if (args.older_than !== void 0)
|
|
@@ -22017,7 +21948,7 @@ function registerTools() {
|
|
|
22017
21948
|
const result = await callApi("POST", `/v1/admin/background-jobs:purge`, body);
|
|
22018
21949
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
22019
21950
|
});
|
|
22020
|
-
server.tool("goodmem_admin_drain", "Request the server to enter drain mode", {
|
|
21951
|
+
server.tool("goodmem_admin_drain", "Request the server to enter drain mode\n\nInitiates drain mode and optionally waits for the server to quiesce.", {
|
|
22021
21952
|
timeout_sec: external_exports.number().int().describe("Maximum seconds to wait for the server to quiesce before returning.").optional(),
|
|
22022
21953
|
reason: external_exports.string().describe("Human-readable reason for initiating drain mode.").optional(),
|
|
22023
21954
|
wait_for_quiesce: external_exports.boolean().describe("If true, wait for in-flight requests to complete and the server to reach QUIESCED before responding.").optional()
|
|
@@ -22029,14 +21960,108 @@ function registerTools() {
|
|
|
22029
21960
|
body["reason"] = args.reason;
|
|
22030
21961
|
if (args.wait_for_quiesce !== void 0)
|
|
22031
21962
|
body["waitForQuiesce"] = args.wait_for_quiesce;
|
|
22032
|
-
const result = await callApi("POST", `/v1/admin:drain`, body);
|
|
21963
|
+
const result = await callApi("POST", `/v1/admin:drain`, body, void 0, ((args.timeout_sec ?? 900) + 30) * 1e3);
|
|
22033
21964
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
22034
21965
|
});
|
|
22035
|
-
server.tool("goodmem_admin_license_reload", "Reload the active license from disk", {}, async (args) => {
|
|
21966
|
+
server.tool("goodmem_admin_license_reload", "Reload the active license from disk\n\nTriggers the server to reload its license file from the configured directory and returns metadata about the currently active license.", {}, async (args) => {
|
|
22036
21967
|
const result = await callApi("POST", `/v1/admin/license:reload`, void 0);
|
|
22037
21968
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
22038
21969
|
});
|
|
22039
|
-
server.tool("
|
|
21970
|
+
server.tool("goodmem_admin_retrieve_memory_log_policies_create", "Create a RetrieveMemory log policy\n\nCreates an immutable administrative policy that can automatically enable durable RetrieveMemory request logging.", {
|
|
21971
|
+
policy_id: external_exports.string().describe("Optional client-provided policy UUID.").optional(),
|
|
21972
|
+
display_name: external_exports.string().describe("Human-readable policy name."),
|
|
21973
|
+
description: external_exports.string().describe("Optional operator description.").optional(),
|
|
21974
|
+
condition: external_exports.object({
|
|
21975
|
+
matchAll: external_exports.boolean().describe("When true, match every authenticated RetrieveMemory request. Defaults to false when omitted.").optional(),
|
|
21976
|
+
anyOf: external_exports.array(external_exports.object({
|
|
21977
|
+
requestorUserIds: external_exports.array(external_exports.string()).describe("Authenticated requestor user UUID strings.").optional(),
|
|
21978
|
+
apiKeyIds: external_exports.array(external_exports.string()).describe("API key UUID strings used to authenticate RetrieveMemory requests.").optional(),
|
|
21979
|
+
spaceIds: external_exports.array(external_exports.string()).describe("Post-permission accessible space UUID strings.").optional(),
|
|
21980
|
+
apiKeyLabelSelectors: external_exports.record(external_exports.string(), external_exports.string()).describe("Exact API-key label selectors that must all match.").optional(),
|
|
21981
|
+
spaceLabelSelectors: external_exports.record(external_exports.string(), external_exports.string()).describe("Exact space label selectors that must match at least one accessible space.").optional()
|
|
21982
|
+
}).passthrough()).describe("OR clauses used for scoped matching. Required when matchAll is false or omitted, and must contain at least one non-empty clause.").optional()
|
|
21983
|
+
}).passthrough().describe("Policy match condition."),
|
|
21984
|
+
active_from: external_exports.number().int().describe("Inclusive activation time in milliseconds since epoch.").optional(),
|
|
21985
|
+
active_until: external_exports.number().int().describe("Exclusive deactivation time in milliseconds since epoch.").optional(),
|
|
21986
|
+
labels: external_exports.record(external_exports.string(), external_exports.string()).describe("Operator labels for listing and administration.").optional()
|
|
21987
|
+
}, async (args) => {
|
|
21988
|
+
const body = {};
|
|
21989
|
+
if (args.policy_id !== void 0)
|
|
21990
|
+
body["policyId"] = args.policy_id;
|
|
21991
|
+
if (args.display_name !== void 0)
|
|
21992
|
+
body["displayName"] = args.display_name;
|
|
21993
|
+
if (args.description !== void 0)
|
|
21994
|
+
body["description"] = args.description;
|
|
21995
|
+
if (args.condition !== void 0)
|
|
21996
|
+
body["condition"] = args.condition;
|
|
21997
|
+
if (args.active_from !== void 0)
|
|
21998
|
+
body["activeFrom"] = args.active_from;
|
|
21999
|
+
if (args.active_until !== void 0)
|
|
22000
|
+
body["activeUntil"] = args.active_until;
|
|
22001
|
+
if (args.labels !== void 0)
|
|
22002
|
+
body["labels"] = args.labels;
|
|
22003
|
+
const result = await callApi("POST", `/v1/admin/retrieve-memory-log-policies`, body);
|
|
22004
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
22005
|
+
});
|
|
22006
|
+
server.tool("goodmem_admin_retrieve_memory_log_policies_delete", "Delete a RetrieveMemory log policy\n\nIdempotently tombstones an immutable RetrieveMemory log policy.", {
|
|
22007
|
+
id: external_exports.string().describe("The UUID of the policy to delete"),
|
|
22008
|
+
reason: external_exports.string().describe("Optional tombstone reason.").optional()
|
|
22009
|
+
}, async (args) => {
|
|
22010
|
+
if (typeof args.id !== "string" || args.id === "") {
|
|
22011
|
+
throw new Error("'id' must be a non-empty string");
|
|
22012
|
+
}
|
|
22013
|
+
const body = {};
|
|
22014
|
+
if (args.reason !== void 0)
|
|
22015
|
+
body["reason"] = args.reason;
|
|
22016
|
+
await callApi("DELETE", `/v1/admin/retrieve-memory-log-policies/${encodeURIComponent(String(args.id))}`, body);
|
|
22017
|
+
return { content: [{ type: "text", text: "Deleted successfully." }] };
|
|
22018
|
+
});
|
|
22019
|
+
server.tool("goodmem_admin_retrieve_memory_log_policies_get", "Get a RetrieveMemory log policy\n\nRetrieves a live RetrieveMemory log policy by UUID, or a tombstoned policy when include_deleted is true.", {
|
|
22020
|
+
id: external_exports.string().describe("The UUID of the policy to retrieve"),
|
|
22021
|
+
include_deleted: external_exports.boolean().describe("Whether to include tombstoned policies. Also accepts include_deleted.").optional()
|
|
22022
|
+
}, async (args) => {
|
|
22023
|
+
if (typeof args.id !== "string" || args.id === "") {
|
|
22024
|
+
throw new Error("'id' must be a non-empty string");
|
|
22025
|
+
}
|
|
22026
|
+
const query = {};
|
|
22027
|
+
if (args.include_deleted !== void 0)
|
|
22028
|
+
query["includeDeleted"] = String(args.include_deleted);
|
|
22029
|
+
const result = await callApi("GET", `/v1/admin/retrieve-memory-log-policies/${encodeURIComponent(String(args.id))}`, void 0, query);
|
|
22030
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
22031
|
+
});
|
|
22032
|
+
server.tool("goodmem_admin_retrieve_memory_log_policies_list", "List RetrieveMemory log policies\n\nLists RetrieveMemory log policies with optional tombstone, active-time, name, label, sort, and pagination filters. LABEL FILTERS: Label filters accept either label.<key>=<value> or label[key]=value (for example, label.environment=production or label[environment]=production).", {
|
|
22033
|
+
include_deleted: external_exports.boolean().describe("Whether to include tombstoned policies. Also accepts include_deleted.").optional(),
|
|
22034
|
+
name_filter: external_exports.string().describe("Case-insensitive substring filter on policy display names. Also accepts name_filter.").optional(),
|
|
22035
|
+
active_at: external_exports.number().int().describe("Only return policies active at this millisecond epoch timestamp. Also accepts active_at.").optional(),
|
|
22036
|
+
max_results: external_exports.number().int().describe("Maximum number of policies to return. Also accepts max_results.").optional(),
|
|
22037
|
+
next_token: external_exports.string().describe("Opaque pagination token returned by the previous list response. Also accepts next_token.").optional(),
|
|
22038
|
+
sort_by: external_exports.enum(["created_at", "updated_at", "display_name"]).describe("Sort field: created_at, updated_at, or display_name. Also accepts sort_by.").optional(),
|
|
22039
|
+
sort_order: external_exports.enum(["ASCENDING", "DESCENDING", "SORT_ORDER_UNSPECIFIED"]).describe("Sort order. Also accepts sort_order.").optional(),
|
|
22040
|
+
label: external_exports.record(external_exports.string(), external_exports.string()).describe("Filter by label. Each key-value pair is sent as label[key]=value query parameter.").optional()
|
|
22041
|
+
}, async (args) => {
|
|
22042
|
+
const query = {};
|
|
22043
|
+
if (args.include_deleted !== void 0)
|
|
22044
|
+
query["includeDeleted"] = String(args.include_deleted);
|
|
22045
|
+
if (args.name_filter !== void 0)
|
|
22046
|
+
query["nameFilter"] = String(args.name_filter);
|
|
22047
|
+
if (args.active_at !== void 0)
|
|
22048
|
+
query["activeAt"] = String(args.active_at);
|
|
22049
|
+
if (args.max_results !== void 0)
|
|
22050
|
+
query["maxResults"] = String(args.max_results);
|
|
22051
|
+
if (args.next_token !== void 0)
|
|
22052
|
+
query["nextToken"] = String(args.next_token);
|
|
22053
|
+
if (args.sort_by !== void 0)
|
|
22054
|
+
query["sortBy"] = String(args.sort_by);
|
|
22055
|
+
if (args.sort_order !== void 0)
|
|
22056
|
+
query["sortOrder"] = String(args.sort_order);
|
|
22057
|
+
if (args.label) {
|
|
22058
|
+
for (const [k, v] of Object.entries(args.label))
|
|
22059
|
+
query[`label[${k}]`] = v;
|
|
22060
|
+
}
|
|
22061
|
+
const result = await callApi("GET", `/v1/admin/retrieve-memory-log-policies`, void 0, query);
|
|
22062
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
22063
|
+
});
|
|
22064
|
+
server.tool("goodmem_apikeys_create", "Create a new API key\n\nCreates a new API key for authenticating with the API. New keys start in ACTIVE status and can be used immediately for authentication. The response includes the one-time raw API key value which will not be retrievable later - clients must save this value as it cannot be recovered. Requires CREATE_APIKEY_OWN permission (or CREATE_APIKEY_ANY for admin users). Side effects include generating cryptographically secure key material, recording creation timestamp, and tracking creator ID. Returns INVALID_ARGUMENT if expires_at is set to a time in the past.", {
|
|
22040
22065
|
labels: external_exports.record(external_exports.string(), external_exports.string()).describe("Key-value pairs of metadata associated with the API key. Used for organization and filtering.").optional(),
|
|
22041
22066
|
expires_at: external_exports.number().int().describe("Expiration timestamp in milliseconds since epoch. If not provided, the key does not expire.").optional(),
|
|
22042
22067
|
api_key_id: external_exports.string().describe("Optional client-provided UUID for idempotent creation. If not provided, server generates a new UUID. Returns ALREADY_EXISTS if ID is already in use.").optional()
|
|
@@ -22051,22 +22076,32 @@ function registerTools() {
|
|
|
22051
22076
|
const result = await callApi("POST", `/v1/apikeys`, body);
|
|
22052
22077
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
22053
22078
|
});
|
|
22054
|
-
server.tool("goodmem_apikeys_delete",
|
|
22079
|
+
server.tool("goodmem_apikeys_delete", 'Delete an API key\n\nPermanently deletes an API key. This operation cannot be undone and immediately invalidates the key for all future authentication attempts. TIP: For reversible deactivation, use the `goodmem_apikeys_update` tool with `status: "INACTIVE"` instead. Requires DELETE_APIKEY_OWN permission for keys you own (or DELETE_APIKEY_ANY for admin users to delete any user\'s keys). Side effects include permanently removing the key record from the database and immediate authentication invalidation.', {
|
|
22055
22080
|
id: external_exports.string().describe("The UUID of the API key to delete")
|
|
22056
22081
|
}, async (args) => {
|
|
22082
|
+
if (typeof args.id !== "string" || args.id === "") {
|
|
22083
|
+
throw new Error("'id' must be a non-empty string");
|
|
22084
|
+
}
|
|
22057
22085
|
await callApi("DELETE", `/v1/apikeys/${encodeURIComponent(String(args.id))}`, void 0);
|
|
22058
22086
|
return { content: [{ type: "text", text: "Deleted successfully." }] };
|
|
22059
22087
|
});
|
|
22060
|
-
server.tool("goodmem_apikeys_list", "List API keys", {}, async (args) => {
|
|
22088
|
+
server.tool("goodmem_apikeys_list", "List API keys\n\nRetrieves a list of API keys belonging to the authenticated user. The list includes metadata about each key but not the actual key values or key hashes for security reasons. Requires LIST_APIKEY_OWN permission (or LIST_APIKEY_ANY for admin users to view keys from any user). This is a read-only operation with no side effects.", {}, async (args) => {
|
|
22061
22089
|
const result = await callApi("GET", `/v1/apikeys`, void 0);
|
|
22062
22090
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
22063
22091
|
});
|
|
22064
|
-
server.tool("goodmem_apikeys_update", "Update an API key", {
|
|
22092
|
+
server.tool("goodmem_apikeys_update", "Update an API key\n\nUpdates an existing API key with new values for status or labels. IMPORTANT: Key ID, user ownership, key material, and audit fields cannot be modified - only status (ACTIVE/INACTIVE) and labels are mutable. Requires UPDATE_APIKEY_OWN permission for keys you own (or UPDATE_APIKEY_ANY for admin users to modify any user's keys). Side effects include updating the updated_at timestamp and recording the updater's user ID. This operation is idempotent - repeated identical requests have no additional effect.", {
|
|
22065
22093
|
id: external_exports.string().describe("The UUID of the API key to update"),
|
|
22066
|
-
status: external_exports.
|
|
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
|
|
22094
|
+
status: external_exports.enum(["ACTIVE", "INACTIVE"]).describe("New status for the API key. Allowed values: ACTIVE, INACTIVE.").optional(),
|
|
22095
|
+
replace_labels: external_exports.record(external_exports.string(), external_exports.string()).describe("Replace all existing labels with this set. Mutually exclusive with merge_labels.").optional(),
|
|
22096
|
+
merge_labels: external_exports.record(external_exports.string(), external_exports.string()).describe("Merge these labels with existing ones. Mutually exclusive with replace_labels.").optional()
|
|
22069
22097
|
}, async (args) => {
|
|
22098
|
+
if (typeof args.id !== "string" || args.id === "") {
|
|
22099
|
+
throw new Error("'id' must be a non-empty string");
|
|
22100
|
+
}
|
|
22101
|
+
const _set = [args.replace_labels !== void 0 && args.replace_labels !== null, args.merge_labels !== void 0 && args.merge_labels !== null].filter(Boolean).length;
|
|
22102
|
+
if (_set > 1) {
|
|
22103
|
+
throw new Error("at most one of replace_labels or merge_labels may be set");
|
|
22104
|
+
}
|
|
22070
22105
|
const body = {};
|
|
22071
22106
|
if (args.status !== void 0)
|
|
22072
22107
|
body["status"] = args.status;
|
|
@@ -22077,7 +22112,7 @@ function registerTools() {
|
|
|
22077
22112
|
const result = await callApi("PUT", `/v1/apikeys/${encodeURIComponent(String(args.id))}`, body);
|
|
22078
22113
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
22079
22114
|
});
|
|
22080
|
-
server.tool("goodmem_embedders_create", "Create a new embedder", {
|
|
22115
|
+
server.tool("goodmem_embedders_create", "Create a new embedder\n\nCreates a new embedder configuration for vectorizing content. Embedders represent connections to different embedding API services (like OpenAI, vLLM, etc.) and include all the necessary configuration to use them with memory spaces. DUPLICATE DETECTION: Returns HTTP 409 Conflict (ALREADY_EXISTS) if another embedder exists with identical {owner_id, provider_type, endpoint_url, api_path, model_identifier, dimensionality, distribution_type, credentialsFingerprint} after URL canonicalization. Uniqueness is enforced per-owner, allowing different users to have identical configurations. Credentials are hashed (SHA-256) for uniqueness while remaining encrypted. The api_path field defaults to '/v2/embed' for Cohere, '/embed' for TEI, and '/embeddings' for other providers when omitted. Requires CREATE_EMBEDDER_OWN permission (or CREATE_EMBEDDER_ANY for admin users).", {
|
|
22081
22116
|
display_name: external_exports.string().describe("User-facing name of the embedder"),
|
|
22082
22117
|
description: external_exports.string().describe("Description of the embedder").optional(),
|
|
22083
22118
|
provider_type: external_exports.enum(["OPENAI", "VLLM", "TEI", "LLAMA_CPP", "VOYAGE", "COHERE", "JINA"]).describe("Type of embedding provider (auto-inferred from model_identifier if omitted)").optional(),
|
|
@@ -22094,16 +22129,16 @@ function registerTools() {
|
|
|
22094
22129
|
inlineSecret: external_exports.string().describe("Secret stored directly in GoodMem (mutually exclusive with secretRef)").optional(),
|
|
22095
22130
|
secretRef: external_exports.object({
|
|
22096
22131
|
uri: external_exports.string().describe("URI identifying where the secret can be resolved (e.g., vault://, env://)"),
|
|
22097
|
-
hints: external_exports.
|
|
22132
|
+
hints: external_exports.record(external_exports.string(), external_exports.string()).describe('Optional metadata to help resolvers decode the secret (e.g., {"encoding":"base64"})').optional()
|
|
22098
22133
|
}).passthrough().describe("Reference to an external secret manager entry (mutually exclusive with inlineSecret)").optional(),
|
|
22099
22134
|
headerName: external_exports.string().describe("Desired HTTP header to carry the credential (defaults to Authorization)").optional(),
|
|
22100
22135
|
prefix: external_exports.string().describe('Optional prefix prepended to the secret (e.g., "Bearer ")').optional()
|
|
22101
22136
|
}).passthrough().describe("Configuration when kind is CREDENTIAL_KIND_API_KEY").optional(),
|
|
22102
22137
|
gcpAdc: external_exports.object({
|
|
22103
|
-
scopes: external_exports.
|
|
22138
|
+
scopes: external_exports.array(external_exports.string()).describe("Additional OAuth scopes. Empty list falls back to the default cloud-platform scope.").optional(),
|
|
22104
22139
|
quotaProjectId: external_exports.string().describe("Optional quota project used for billing").optional()
|
|
22105
22140
|
}).passthrough().describe("Configuration when kind is CREDENTIAL_KIND_GCP_ADC").optional(),
|
|
22106
|
-
labels: external_exports.
|
|
22141
|
+
labels: external_exports.record(external_exports.string(), external_exports.string()).describe('Optional annotations to aid operators (e.g., "owner=vertex")').optional()
|
|
22107
22142
|
}).passthrough().describe("Structured credential payload describing how to authenticate with the provider. Required for SaaS providers; optional for local or proxy providers.").optional(),
|
|
22108
22143
|
labels: external_exports.record(external_exports.string(), external_exports.string()).describe("User-defined labels for categorization").optional(),
|
|
22109
22144
|
version: external_exports.string().describe("Version information").optional(),
|
|
@@ -22149,19 +22184,25 @@ function registerTools() {
|
|
|
22149
22184
|
const result = await callApi("POST", `/v1/embedders`, body);
|
|
22150
22185
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
22151
22186
|
});
|
|
22152
|
-
server.tool("goodmem_embedders_delete", "Delete an embedder", {
|
|
22187
|
+
server.tool("goodmem_embedders_delete", "Delete an embedder\n\nPermanently deletes an embedder configuration. This operation cannot be undone and removes the embedder record and securely deletes stored credentials. IMPORTANT: This does NOT invalidate or delete embeddings previously created with this embedder - existing embeddings remain accessible. CONFLICT: Returns HTTP 409 Conflict if the embedder is still referenced by a space. Requires DELETE_EMBEDDER_OWN permission for embedders you own (or DELETE_EMBEDDER_ANY for admin users).", {
|
|
22153
22188
|
id: external_exports.string().describe("The unique identifier of the embedder to delete")
|
|
22154
22189
|
}, async (args) => {
|
|
22190
|
+
if (typeof args.id !== "string" || args.id === "") {
|
|
22191
|
+
throw new Error("'id' must be a non-empty string");
|
|
22192
|
+
}
|
|
22155
22193
|
await callApi("DELETE", `/v1/embedders/${encodeURIComponent(String(args.id))}`, void 0);
|
|
22156
22194
|
return { content: [{ type: "text", text: "Deleted successfully." }] };
|
|
22157
22195
|
});
|
|
22158
|
-
server.tool("goodmem_embedders_get", "Get an embedder by ID", {
|
|
22196
|
+
server.tool("goodmem_embedders_get", "Get an embedder by ID\n\nRetrieves the details of a specific embedder configuration by its unique identifier. Requires READ_EMBEDDER_OWN permission for embedders you own (or READ_EMBEDDER_ANY for admin users to view any user's embedders). This is a read-only operation with no side effects.", {
|
|
22159
22197
|
id: external_exports.string().describe("The unique identifier of the embedder to retrieve")
|
|
22160
22198
|
}, async (args) => {
|
|
22199
|
+
if (typeof args.id !== "string" || args.id === "") {
|
|
22200
|
+
throw new Error("'id' must be a non-empty string");
|
|
22201
|
+
}
|
|
22161
22202
|
const result = await callApi("GET", `/v1/embedders/${encodeURIComponent(String(args.id))}`, void 0);
|
|
22162
22203
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
22163
22204
|
});
|
|
22164
|
-
server.tool("goodmem_embedders_list", "List embedders", {
|
|
22205
|
+
server.tool("goodmem_embedders_list", "List embedders\n\nRetrieves a list of embedder configurations accessible to the caller, with optional filtering. LABEL FILTERS: Label filters accept either label.<key>=<value> or label[key]=value (for example, label.environment=production or label[environment]=production). PERMISSION-BASED FILTERING: With LIST_EMBEDDER_OWN permission, you can only see your own embedders (owner_id filter is ignored if set to another user). With LIST_EMBEDDER_ANY permission, you can see all embedders or filter by any owner_id. This is a read-only operation with no side effects.", {
|
|
22165
22206
|
owner_id: external_exports.string().describe("Filter embedders by owner ID. With LIST_EMBEDDER_ANY permission, omitting this shows all accessible embedders; providing it filters by that owner. With LIST_EMBEDDER_OWN permission, only your own embedders are shown regardless of this parameter.").optional(),
|
|
22166
22207
|
provider_type: external_exports.enum(["OPENAI", "VLLM", "TEI", "LLAMA_CPP", "VOYAGE", "COHERE", "JINA"]).describe("Filter embedders by provider type. Allowed values match the ProviderType schema.").optional(),
|
|
22167
22208
|
label: external_exports.record(external_exports.string(), external_exports.string()).describe("Filter by label. Each key-value pair is sent as label[key]=value query parameter.").optional()
|
|
@@ -22178,7 +22219,7 @@ function registerTools() {
|
|
|
22178
22219
|
const result = await callApi("GET", `/v1/embedders`, void 0, query);
|
|
22179
22220
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
22180
22221
|
});
|
|
22181
|
-
server.tool("goodmem_embedders_update", "Update an embedder", {
|
|
22222
|
+
server.tool("goodmem_embedders_update", "Update an embedder\n\nUpdates an existing embedder configuration including display information, endpoint configuration, model parameters, credentials, and labels. All fields are optional - only specified fields will be updated. IMPORTANT: providerType is IMMUTABLE after creation and cannot be changed. CRITICAL: Returns HTTP 412 Precondition Failed (FAILED_PRECONDITION) if attempting to update core fields (dimensionality, distribution_type, model_identifier) while the embedder is actively referenced by spaces or ingestion jobs. DUPLICATE DETECTION: Returns HTTP 409 Conflict (ALREADY_EXISTS) if the update would create a duplicate embedder with the same provider, endpoint, model, dimensionality, distribution, and credentials for this owner. Requires UPDATE_EMBEDDER_OWN permission for embedders you own (or UPDATE_EMBEDDER_ANY for admin users).", {
|
|
22182
22223
|
id: external_exports.string().describe("The unique identifier of the embedder to update"),
|
|
22183
22224
|
display_name: external_exports.string().describe("User-facing name of the embedder").optional(),
|
|
22184
22225
|
description: external_exports.string().describe("Description of the embedder").optional(),
|
|
@@ -22188,29 +22229,35 @@ function registerTools() {
|
|
|
22188
22229
|
dimensionality: external_exports.number().int().describe("Output vector dimensions").optional(),
|
|
22189
22230
|
distribution_type: external_exports.enum(["DENSE", "SPARSE"]).describe("Type of embedding distribution (DENSE or SPARSE)").optional(),
|
|
22190
22231
|
max_sequence_length: external_exports.number().int().describe("Maximum input sequence length").optional(),
|
|
22191
|
-
supported_modalities: external_exports.array(external_exports.enum(["TEXT", "IMAGE", "AUDIO", "VIDEO"])).describe("Supported content modalities").optional(),
|
|
22192
22232
|
credentials: external_exports.object({
|
|
22193
22233
|
kind: external_exports.enum(["CREDENTIAL_KIND_UNSPECIFIED", "CREDENTIAL_KIND_API_KEY", "CREDENTIAL_KIND_GCP_ADC"]).describe("Selected credential strategy"),
|
|
22194
22234
|
apiKey: external_exports.object({
|
|
22195
22235
|
inlineSecret: external_exports.string().describe("Secret stored directly in GoodMem (mutually exclusive with secretRef)").optional(),
|
|
22196
22236
|
secretRef: external_exports.object({
|
|
22197
22237
|
uri: external_exports.string().describe("URI identifying where the secret can be resolved (e.g., vault://, env://)"),
|
|
22198
|
-
hints: external_exports.
|
|
22238
|
+
hints: external_exports.record(external_exports.string(), external_exports.string()).describe('Optional metadata to help resolvers decode the secret (e.g., {"encoding":"base64"})').optional()
|
|
22199
22239
|
}).passthrough().describe("Reference to an external secret manager entry (mutually exclusive with inlineSecret)").optional(),
|
|
22200
22240
|
headerName: external_exports.string().describe("Desired HTTP header to carry the credential (defaults to Authorization)").optional(),
|
|
22201
22241
|
prefix: external_exports.string().describe('Optional prefix prepended to the secret (e.g., "Bearer ")').optional()
|
|
22202
22242
|
}).passthrough().describe("Configuration when kind is CREDENTIAL_KIND_API_KEY").optional(),
|
|
22203
22243
|
gcpAdc: external_exports.object({
|
|
22204
|
-
scopes: external_exports.
|
|
22244
|
+
scopes: external_exports.array(external_exports.string()).describe("Additional OAuth scopes. Empty list falls back to the default cloud-platform scope.").optional(),
|
|
22205
22245
|
quotaProjectId: external_exports.string().describe("Optional quota project used for billing").optional()
|
|
22206
22246
|
}).passthrough().describe("Configuration when kind is CREDENTIAL_KIND_GCP_ADC").optional(),
|
|
22207
|
-
labels: external_exports.
|
|
22247
|
+
labels: external_exports.record(external_exports.string(), external_exports.string()).describe('Optional annotations to aid operators (e.g., "owner=vertex")').optional()
|
|
22208
22248
|
}).passthrough().describe("Structured credential payload describing how to authenticate with the provider").optional(),
|
|
22209
|
-
replace_labels: external_exports.record(external_exports.string(), external_exports.string()).describe("Replace all existing labels with these (mutually exclusive with
|
|
22210
|
-
merge_labels: external_exports.record(external_exports.string(), external_exports.string()).describe("Merge these labels with existing ones (mutually exclusive with
|
|
22249
|
+
replace_labels: external_exports.record(external_exports.string(), external_exports.string()).describe("Replace all existing labels with these (mutually exclusive with merge_labels)").optional(),
|
|
22250
|
+
merge_labels: external_exports.record(external_exports.string(), external_exports.string()).describe("Merge these labels with existing ones (mutually exclusive with replace_labels)").optional(),
|
|
22211
22251
|
version: external_exports.string().describe("Version information").optional(),
|
|
22212
22252
|
monitoring_endpoint: external_exports.string().describe("Monitoring endpoint URL").optional()
|
|
22213
22253
|
}, async (args) => {
|
|
22254
|
+
if (typeof args.id !== "string" || args.id === "") {
|
|
22255
|
+
throw new Error("'id' must be a non-empty string");
|
|
22256
|
+
}
|
|
22257
|
+
const _set = [args.replace_labels !== void 0 && args.replace_labels !== null, args.merge_labels !== void 0 && args.merge_labels !== null].filter(Boolean).length;
|
|
22258
|
+
if (_set > 1) {
|
|
22259
|
+
throw new Error("at most one of replace_labels or merge_labels may be set");
|
|
22260
|
+
}
|
|
22214
22261
|
const body = {};
|
|
22215
22262
|
if (args.display_name !== void 0)
|
|
22216
22263
|
body["displayName"] = args.display_name;
|
|
@@ -22228,8 +22275,6 @@ function registerTools() {
|
|
|
22228
22275
|
body["distributionType"] = args.distribution_type;
|
|
22229
22276
|
if (args.max_sequence_length !== void 0)
|
|
22230
22277
|
body["maxSequenceLength"] = args.max_sequence_length;
|
|
22231
|
-
if (args.supported_modalities !== void 0)
|
|
22232
|
-
body["supportedModalities"] = args.supported_modalities;
|
|
22233
22278
|
if (args.credentials !== void 0)
|
|
22234
22279
|
body["credentials"] = args.credentials;
|
|
22235
22280
|
if (args.replace_labels !== void 0)
|
|
@@ -22243,7 +22288,7 @@ function registerTools() {
|
|
|
22243
22288
|
const result = await callApi("PUT", `/v1/embedders/${encodeURIComponent(String(args.id))}`, body);
|
|
22244
22289
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
22245
22290
|
});
|
|
22246
|
-
server.tool("goodmem_llms_create", "Create a new LLM", {
|
|
22291
|
+
server.tool("goodmem_llms_create", "Create a new LLM\n\nCreates a new LLM configuration for text generation services. LLMs represent connections to different language model API services (like OpenAI, vLLM, etc.) and include all the necessary configuration to use them for text generation. DUPLICATE DETECTION: Returns HTTP 409 Conflict (ALREADY_EXISTS) if another LLM exists with identical {owner_id, provider_type, endpoint_url, api_path, model_identifier, credentialsFingerprint} after URL canonicalization. Uniqueness is enforced per-owner. Credentials are hashed (SHA-256) for uniqueness while remaining encrypted. The api_path field defaults to '/chat/completions' if omitted. Requires CREATE_LLM_OWN permission (or CREATE_LLM_ANY for admin users).", {
|
|
22247
22292
|
display_name: external_exports.string().describe("User-facing name of the LLM"),
|
|
22248
22293
|
description: external_exports.string().describe("Description of the LLM").optional(),
|
|
22249
22294
|
provider_type: external_exports.enum(["OPENAI", "LITELLM_PROXY", "OPEN_ROUTER", "VLLM", "OLLAMA", "LLAMA_CPP", "CUSTOM_OPENAI_COMPATIBLE"]).describe("Type of LLM provider (auto-inferred from model_identifier if omitted)").optional(),
|
|
@@ -22257,16 +22302,16 @@ function registerTools() {
|
|
|
22257
22302
|
inlineSecret: external_exports.string().describe("Secret stored directly in GoodMem (mutually exclusive with secretRef)").optional(),
|
|
22258
22303
|
secretRef: external_exports.object({
|
|
22259
22304
|
uri: external_exports.string().describe("URI identifying where the secret can be resolved (e.g., vault://, env://)"),
|
|
22260
|
-
hints: external_exports.
|
|
22305
|
+
hints: external_exports.record(external_exports.string(), external_exports.string()).describe('Optional metadata to help resolvers decode the secret (e.g., {"encoding":"base64"})').optional()
|
|
22261
22306
|
}).passthrough().describe("Reference to an external secret manager entry (mutually exclusive with inlineSecret)").optional(),
|
|
22262
22307
|
headerName: external_exports.string().describe("Desired HTTP header to carry the credential (defaults to Authorization)").optional(),
|
|
22263
22308
|
prefix: external_exports.string().describe('Optional prefix prepended to the secret (e.g., "Bearer ")').optional()
|
|
22264
22309
|
}).passthrough().describe("Configuration when kind is CREDENTIAL_KIND_API_KEY").optional(),
|
|
22265
22310
|
gcpAdc: external_exports.object({
|
|
22266
|
-
scopes: external_exports.
|
|
22311
|
+
scopes: external_exports.array(external_exports.string()).describe("Additional OAuth scopes. Empty list falls back to the default cloud-platform scope.").optional(),
|
|
22267
22312
|
quotaProjectId: external_exports.string().describe("Optional quota project used for billing").optional()
|
|
22268
22313
|
}).passthrough().describe("Configuration when kind is CREDENTIAL_KIND_GCP_ADC").optional(),
|
|
22269
|
-
labels: external_exports.
|
|
22314
|
+
labels: external_exports.record(external_exports.string(), external_exports.string()).describe('Optional annotations to aid operators (e.g., "owner=vertex")').optional()
|
|
22270
22315
|
}).passthrough().describe("Structured credential payload describing how to authenticate with the provider. Required for SaaS providers; optional for local or proxy providers.").optional(),
|
|
22271
22316
|
labels: external_exports.record(external_exports.string(), external_exports.string()).describe("User-defined labels for categorization").optional(),
|
|
22272
22317
|
version: external_exports.string().describe("Version information").optional(),
|
|
@@ -22281,15 +22326,15 @@ function registerTools() {
|
|
|
22281
22326
|
}).passthrough().describe("LLM capabilities defining supported features and modes. Optional - server infers capabilities from model identifier if not provided.").optional(),
|
|
22282
22327
|
default_sampling_params: external_exports.object({
|
|
22283
22328
|
maxTokens: external_exports.number().int().describe("Maximum tokens to generate (>0 if set; provider-dependent limits apply)").optional(),
|
|
22284
|
-
temperature: external_exports.number().describe("Sampling temperature
|
|
22329
|
+
temperature: external_exports.number().describe("Sampling temperature; valid range depends on the configured provider").optional(),
|
|
22285
22330
|
topP: external_exports.number().describe("Nucleus sampling threshold 0.0-1.0 (smaller values focus on higher probability tokens)").optional(),
|
|
22286
22331
|
topK: external_exports.number().int().describe("Top-k sampling limit (>0 if set; primarily for local/open-source models)").optional(),
|
|
22287
|
-
frequencyPenalty: external_exports.number().describe("Frequency penalty
|
|
22288
|
-
presencePenalty: external_exports.number().describe("Presence penalty
|
|
22289
|
-
stopSequences: external_exports.
|
|
22332
|
+
frequencyPenalty: external_exports.number().describe("Frequency penalty; valid range depends on the configured provider").optional(),
|
|
22333
|
+
presencePenalty: external_exports.number().describe("Presence penalty; valid range depends on the configured provider").optional(),
|
|
22334
|
+
stopSequences: external_exports.array(external_exports.string()).describe("Generation stop sequences; generation halts on exact match").optional()
|
|
22290
22335
|
}).passthrough().describe("Default sampling parameters for generation requests").optional(),
|
|
22291
22336
|
max_context_length: external_exports.number().int().describe("Maximum context window size in tokens").optional(),
|
|
22292
|
-
client_config: external_exports.any().describe("Provider-specific client configuration as flexible JSON structure").optional(),
|
|
22337
|
+
client_config: external_exports.record(external_exports.string(), external_exports.any()).describe("Provider-specific client configuration as flexible JSON structure").optional(),
|
|
22293
22338
|
owner_id: external_exports.string().describe("Optional owner ID. If not provided, derived from the authentication context. Requires CREATE_LLM_ANY permission if specified.").optional(),
|
|
22294
22339
|
llm_id: external_exports.string().describe("Optional client-provided UUID for idempotent creation. If not provided, server generates a new UUID. Returns ALREADY_EXISTS if ID is already in use.").optional()
|
|
22295
22340
|
}, async (args) => {
|
|
@@ -22333,19 +22378,25 @@ function registerTools() {
|
|
|
22333
22378
|
const result = await callApi("POST", `/v1/llms`, body);
|
|
22334
22379
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
22335
22380
|
});
|
|
22336
|
-
server.tool("goodmem_llms_delete", "Delete an LLM", {
|
|
22381
|
+
server.tool("goodmem_llms_delete", "Delete an LLM\n\nPermanently deletes an LLM configuration. This operation cannot be undone and removes the LLM record and securely deletes stored credentials. IMPORTANT: This does NOT invalidate or delete any previously generated content using this LLM - existing generations remain accessible. Requires DELETE_LLM_OWN permission for LLMs you own (or DELETE_LLM_ANY for admin users).", {
|
|
22337
22382
|
id: external_exports.string().describe("The unique identifier of the LLM to delete")
|
|
22338
22383
|
}, async (args) => {
|
|
22384
|
+
if (typeof args.id !== "string" || args.id === "") {
|
|
22385
|
+
throw new Error("'id' must be a non-empty string");
|
|
22386
|
+
}
|
|
22339
22387
|
await callApi("DELETE", `/v1/llms/${encodeURIComponent(String(args.id))}`, void 0);
|
|
22340
22388
|
return { content: [{ type: "text", text: "Deleted successfully." }] };
|
|
22341
22389
|
});
|
|
22342
|
-
server.tool("goodmem_llms_get", "Get an LLM by ID", {
|
|
22390
|
+
server.tool("goodmem_llms_get", "Get an LLM by ID\n\nRetrieves the details of a specific LLM configuration by its unique identifier. Requires READ_LLM_OWN permission for LLMs you own (or READ_LLM_ANY for admin users to view any user's LLMs). This is a read-only operation with no side effects.", {
|
|
22343
22391
|
id: external_exports.string().describe("The unique identifier of the LLM to retrieve")
|
|
22344
22392
|
}, async (args) => {
|
|
22393
|
+
if (typeof args.id !== "string" || args.id === "") {
|
|
22394
|
+
throw new Error("'id' must be a non-empty string");
|
|
22395
|
+
}
|
|
22345
22396
|
const result = await callApi("GET", `/v1/llms/${encodeURIComponent(String(args.id))}`, void 0);
|
|
22346
22397
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
22347
22398
|
});
|
|
22348
|
-
server.tool("goodmem_llms_list", "List LLMs", {
|
|
22399
|
+
server.tool("goodmem_llms_list", "List LLMs\n\nRetrieves a list of LLM configurations accessible to the caller, with optional filtering. LABEL FILTERS: Label filters accept either label.<key>=<value> or label[key]=value (for example, label.environment=production or label[environment]=production). PERMISSION-BASED FILTERING: With LIST_LLM_OWN permission, you can only see your own LLMs (owner_id filter is ignored if set to another user). With LIST_LLM_ANY permission, you can see all LLMs or filter by any owner_id. This is a read-only operation with no side effects.", {
|
|
22349
22400
|
owner_id: external_exports.string().describe("Filter LLMs by owner ID. With LIST_LLM_ANY permission, omitting this shows all accessible LLMs; providing it filters by that owner. With LIST_LLM_OWN permission, only your own LLMs are shown regardless of this parameter.").optional(),
|
|
22350
22401
|
provider_type: external_exports.enum(["OPENAI", "LITELLM_PROXY", "OPEN_ROUTER", "VLLM", "OLLAMA", "LLAMA_CPP", "CUSTOM_OPENAI_COMPATIBLE"]).describe("Filter LLMs by provider type. Allowed values match the LLMProviderType schema.").optional(),
|
|
22351
22402
|
label: external_exports.record(external_exports.string(), external_exports.string()).describe("Filter by label. Each key-value pair is sent as label[key]=value query parameter.").optional()
|
|
@@ -22362,30 +22413,30 @@ function registerTools() {
|
|
|
22362
22413
|
const result = await callApi("GET", `/v1/llms`, void 0, query);
|
|
22363
22414
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
22364
22415
|
});
|
|
22365
|
-
server.tool("goodmem_llms_update", "Update an LLM", {
|
|
22416
|
+
server.tool("goodmem_llms_update", "Update an LLM\n\nUpdates an existing LLM configuration including display information, endpoint configuration, model parameters, credentials, and labels. All fields are optional - only specified fields will be updated. SUPPORTED_MODALITIES UPDATE: If the array contains >=1 elements, it replaces the stored set; if empty or omitted, no change occurs and it does not count as an update by itself. IMPORTANT: providerType is IMMUTABLE after creation and cannot be changed. Requires UPDATE_LLM_OWN permission for LLMs you own (or UPDATE_LLM_ANY for admin users).", {
|
|
22366
22417
|
id: external_exports.string().describe("The unique identifier of the LLM to update"),
|
|
22367
22418
|
display_name: external_exports.string().describe("Update display name").optional(),
|
|
22368
22419
|
description: external_exports.string().describe("Update description").optional(),
|
|
22369
22420
|
endpoint_url: external_exports.string().describe("Update endpoint base URL (OpenAI-compatible base, typically ends with /v1)").optional(),
|
|
22370
22421
|
api_path: external_exports.string().describe("Update API path").optional(),
|
|
22371
22422
|
model_identifier: external_exports.string().describe("Update model identifier (cannot be empty)").optional(),
|
|
22372
|
-
supported_modalities: external_exports.array(external_exports.enum(["TEXT", "IMAGE", "AUDIO", "VIDEO"])).describe("Update supported modalities (if array contains
|
|
22423
|
+
supported_modalities: external_exports.array(external_exports.enum(["TEXT", "IMAGE", "AUDIO", "VIDEO"])).describe("Update supported modalities (if array contains >=1 elements, replaces stored set; if empty or omitted, no change and does not count as an update by itself)").optional(),
|
|
22373
22424
|
credentials: external_exports.object({
|
|
22374
22425
|
kind: external_exports.enum(["CREDENTIAL_KIND_UNSPECIFIED", "CREDENTIAL_KIND_API_KEY", "CREDENTIAL_KIND_GCP_ADC"]).describe("Selected credential strategy"),
|
|
22375
22426
|
apiKey: external_exports.object({
|
|
22376
22427
|
inlineSecret: external_exports.string().describe("Secret stored directly in GoodMem (mutually exclusive with secretRef)").optional(),
|
|
22377
22428
|
secretRef: external_exports.object({
|
|
22378
22429
|
uri: external_exports.string().describe("URI identifying where the secret can be resolved (e.g., vault://, env://)"),
|
|
22379
|
-
hints: external_exports.
|
|
22430
|
+
hints: external_exports.record(external_exports.string(), external_exports.string()).describe('Optional metadata to help resolvers decode the secret (e.g., {"encoding":"base64"})').optional()
|
|
22380
22431
|
}).passthrough().describe("Reference to an external secret manager entry (mutually exclusive with inlineSecret)").optional(),
|
|
22381
22432
|
headerName: external_exports.string().describe("Desired HTTP header to carry the credential (defaults to Authorization)").optional(),
|
|
22382
22433
|
prefix: external_exports.string().describe('Optional prefix prepended to the secret (e.g., "Bearer ")').optional()
|
|
22383
22434
|
}).passthrough().describe("Configuration when kind is CREDENTIAL_KIND_API_KEY").optional(),
|
|
22384
22435
|
gcpAdc: external_exports.object({
|
|
22385
|
-
scopes: external_exports.
|
|
22436
|
+
scopes: external_exports.array(external_exports.string()).describe("Additional OAuth scopes. Empty list falls back to the default cloud-platform scope.").optional(),
|
|
22386
22437
|
quotaProjectId: external_exports.string().describe("Optional quota project used for billing").optional()
|
|
22387
22438
|
}).passthrough().describe("Configuration when kind is CREDENTIAL_KIND_GCP_ADC").optional(),
|
|
22388
|
-
labels: external_exports.
|
|
22439
|
+
labels: external_exports.record(external_exports.string(), external_exports.string()).describe('Optional annotations to aid operators (e.g., "owner=vertex")').optional()
|
|
22389
22440
|
}).passthrough().describe("Update credentials").optional(),
|
|
22390
22441
|
version: external_exports.string().describe("Update version information").optional(),
|
|
22391
22442
|
monitoring_endpoint: external_exports.string().describe("Update monitoring endpoint URL").optional(),
|
|
@@ -22399,18 +22450,25 @@ function registerTools() {
|
|
|
22399
22450
|
}).passthrough().describe("Update LLM capabilities (replaces entire capability set; clients MUST send all flags)").optional(),
|
|
22400
22451
|
default_sampling_params: external_exports.object({
|
|
22401
22452
|
maxTokens: external_exports.number().int().describe("Maximum tokens to generate (>0 if set; provider-dependent limits apply)").optional(),
|
|
22402
|
-
temperature: external_exports.number().describe("Sampling temperature
|
|
22453
|
+
temperature: external_exports.number().describe("Sampling temperature; valid range depends on the configured provider").optional(),
|
|
22403
22454
|
topP: external_exports.number().describe("Nucleus sampling threshold 0.0-1.0 (smaller values focus on higher probability tokens)").optional(),
|
|
22404
22455
|
topK: external_exports.number().int().describe("Top-k sampling limit (>0 if set; primarily for local/open-source models)").optional(),
|
|
22405
|
-
frequencyPenalty: external_exports.number().describe("Frequency penalty
|
|
22406
|
-
presencePenalty: external_exports.number().describe("Presence penalty
|
|
22407
|
-
stopSequences: external_exports.
|
|
22456
|
+
frequencyPenalty: external_exports.number().describe("Frequency penalty; valid range depends on the configured provider").optional(),
|
|
22457
|
+
presencePenalty: external_exports.number().describe("Presence penalty; valid range depends on the configured provider").optional(),
|
|
22458
|
+
stopSequences: external_exports.array(external_exports.string()).describe("Generation stop sequences; generation halts on exact match").optional()
|
|
22408
22459
|
}).passthrough().describe("Update default sampling parameters").optional(),
|
|
22409
22460
|
max_context_length: external_exports.number().int().describe("Update maximum context window size in tokens").optional(),
|
|
22410
|
-
client_config: external_exports.any().describe("Update provider-specific client configuration (replaces entire config; no merging)").optional(),
|
|
22411
|
-
replace_labels: external_exports.record(external_exports.string(), external_exports.string()).describe("Replace all existing labels with this set. Empty map clears all labels. Cannot be used with
|
|
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
|
|
22461
|
+
client_config: external_exports.record(external_exports.string(), external_exports.any()).describe("Update provider-specific client configuration (replaces entire config; no merging)").optional(),
|
|
22462
|
+
replace_labels: external_exports.record(external_exports.string(), external_exports.string()).describe("Replace all existing labels with this set. Empty map clears all labels. Cannot be used with merge_labels.").optional(),
|
|
22463
|
+
merge_labels: external_exports.record(external_exports.string(), external_exports.string()).describe("Merge with existing labels: upserts with overwrite. Labels not mentioned are preserved. Cannot be used with replace_labels.").optional()
|
|
22413
22464
|
}, async (args) => {
|
|
22465
|
+
if (typeof args.id !== "string" || args.id === "") {
|
|
22466
|
+
throw new Error("'id' must be a non-empty string");
|
|
22467
|
+
}
|
|
22468
|
+
const _set = [args.replace_labels !== void 0 && args.replace_labels !== null, args.merge_labels !== void 0 && args.merge_labels !== null].filter(Boolean).length;
|
|
22469
|
+
if (_set > 1) {
|
|
22470
|
+
throw new Error("at most one of replace_labels or merge_labels may be set");
|
|
22471
|
+
}
|
|
22414
22472
|
const body = {};
|
|
22415
22473
|
if (args.display_name !== void 0)
|
|
22416
22474
|
body["displayName"] = args.display_name;
|
|
@@ -22445,21 +22503,21 @@ function registerTools() {
|
|
|
22445
22503
|
const result = await callApi("PUT", `/v1/llms/${encodeURIComponent(String(args.id))}`, body);
|
|
22446
22504
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
22447
22505
|
});
|
|
22448
|
-
server.tool("goodmem_memories_batch_create", "Create multiple memories in
|
|
22506
|
+
server.tool("goodmem_memories_batch_create", "Create multiple memories in one batch. EACH item in `requests` must satisfy: exactly one of `originalContent` or `originalContentB64`; if `originalContentB64`, set `contentType` (e.g. `application/pdf`); `originalContentRef` is metadata only. Returns per-item success/failure; partial failures don't abort.", {
|
|
22449
22507
|
requests: external_exports.array(external_exports.object({
|
|
22450
22508
|
memoryId: external_exports.string().describe("Optional client-provided UUID for the memory. If omitted, the server generates one. Returns ALREADY_EXISTS if the ID is already in use.").optional(),
|
|
22451
22509
|
spaceId: external_exports.string().describe("ID of the space where this memory will be stored"),
|
|
22452
22510
|
originalContent: external_exports.string().describe("Original content as plain text (use either this or originalContentB64)").optional(),
|
|
22453
22511
|
originalContentB64: external_exports.string().describe("Original content as base64-encoded binary data (use either this or originalContent)").optional(),
|
|
22454
22512
|
originalContentRef: external_exports.string().describe("Reference to external content location").optional(),
|
|
22455
|
-
contentType: external_exports.string().describe("MIME type of the content"),
|
|
22456
|
-
metadata: external_exports.any().describe("Additional metadata for the memory").optional(),
|
|
22513
|
+
contentType: external_exports.string().describe("MIME type of the content").optional(),
|
|
22514
|
+
metadata: external_exports.record(external_exports.string(), external_exports.any()).describe("Additional metadata for the memory").optional(),
|
|
22457
22515
|
chunkingConfig: external_exports.object({
|
|
22458
22516
|
none: external_exports.any().describe("No chunking strategy - preserve original content as single unit").optional(),
|
|
22459
22517
|
recursive: external_exports.object({
|
|
22460
22518
|
chunkSize: external_exports.number().int().describe("Maximum size of a chunk (should be \u2264 context window)"),
|
|
22461
22519
|
chunkOverlap: external_exports.number().int().describe("Sliding overlap between chunks"),
|
|
22462
|
-
separators: external_exports.
|
|
22520
|
+
separators: external_exports.array(external_exports.string()).describe("Hierarchical separator list (order = preference)").optional(),
|
|
22463
22521
|
keepStrategy: external_exports.enum(["KEEP_NONE", "KEEP_START", "KEEP_END"]).describe("How to handle separators after splitting. KEEP_NONE is deprecated and behaves as KEEP_END."),
|
|
22464
22522
|
separatorIsRegex: external_exports.boolean().describe("Whether separators are regex patterns").optional(),
|
|
22465
22523
|
lengthMeasurement: external_exports.enum(["CHARACTER_COUNT", "TOKEN_COUNT", "CUSTOM"]).describe("How to measure chunk length")
|
|
@@ -22471,22 +22529,38 @@ function registerTools() {
|
|
|
22471
22529
|
lengthMeasurement: external_exports.enum(["CHARACTER_COUNT", "TOKEN_COUNT", "CUSTOM"]).describe("How to measure chunk length")
|
|
22472
22530
|
}).passthrough().describe("Sentence-based chunking strategy with language detection").optional()
|
|
22473
22531
|
}).passthrough().describe("Chunking strategy for this memory (if not provided, uses space default)").optional(),
|
|
22474
|
-
extractPageImages: external_exports.boolean().describe("Optional hint to extract page images for eligible document types (for example, PDFs)").optional()
|
|
22475
|
-
|
|
22476
|
-
}).passthrough()).min(1).describe("Array of memory creation requests.")
|
|
22532
|
+
extractPageImages: external_exports.boolean().describe("Optional hint to extract page images for eligible document types (for example, PDFs)").optional()
|
|
22533
|
+
}).passthrough()).describe("Array of memory creation requests.")
|
|
22477
22534
|
}, async (args) => {
|
|
22535
|
+
for (const [_i, _item] of (Array.isArray(args.requests) ? args.requests : []).entries()) {
|
|
22536
|
+
const _itemSet = [_item?.["originalContent"] !== void 0 && _item?.["originalContent"] !== null, _item?.["originalContentB64"] !== void 0 && _item?.["originalContentB64"] !== null].filter(Boolean).length;
|
|
22537
|
+
if (_itemSet !== 1) {
|
|
22538
|
+
throw new Error(`requests[${_i}]: exactly one of originalContent or originalContentB64 must be set`);
|
|
22539
|
+
}
|
|
22540
|
+
if (_item?.["originalContentB64"] !== void 0 && _item?.["originalContentB64"] !== null && (_item?.["contentType"] === void 0 || _item?.["contentType"] === null)) {
|
|
22541
|
+
throw new Error(`requests[${_i}]: 'contentType' is required when 'originalContentB64' is set (the server cannot infer MIME type from base64 bytes). Hint: pass contentType="application/pdf" or whatever matches the decoded payload`);
|
|
22542
|
+
}
|
|
22543
|
+
}
|
|
22478
22544
|
const body = {};
|
|
22479
22545
|
if (args.requests !== void 0)
|
|
22480
22546
|
body["requests"] = args.requests;
|
|
22547
|
+
if (Array.isArray(body["requests"])) {
|
|
22548
|
+
body["requests"] = body["requests"].map((item) => {
|
|
22549
|
+
if (item && typeof item === "object" && item["originalContent"] !== void 0 && item["contentType"] === void 0) {
|
|
22550
|
+
return { ...item, contentType: "text/plain" };
|
|
22551
|
+
}
|
|
22552
|
+
return item;
|
|
22553
|
+
});
|
|
22554
|
+
}
|
|
22481
22555
|
const result = await callApi("POST", `/v1/memories:batchCreate`, body);
|
|
22482
22556
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
22483
22557
|
});
|
|
22484
|
-
server.tool("goodmem_memories_batch_delete", "Delete memories in batch", {
|
|
22558
|
+
server.tool("goodmem_memories_batch_delete", "Delete memories in batch\n\nDeletes memories using selector entries. Each selector can target either a specific memory ID or a filtered subset scoped to a specific space.", {
|
|
22485
22559
|
requests: external_exports.array(external_exports.object({
|
|
22486
22560
|
memoryId: external_exports.string().describe("Deletes one specific memory by UUID").optional(),
|
|
22487
22561
|
filterSelector: external_exports.object({
|
|
22488
22562
|
spaceId: external_exports.string().describe("Space ID scope for the filtered delete"),
|
|
22489
|
-
statusFilter: external_exports.
|
|
22563
|
+
statusFilter: external_exports.enum(["PENDING", "PROCESSING", "COMPLETED", "FAILED"]).describe("Optional processing status filter (PENDING, PROCESSING, COMPLETED, FAILED)").optional(),
|
|
22490
22564
|
filter: external_exports.string().describe("Optional metadata filter expression").optional()
|
|
22491
22565
|
}).passthrough().describe("Deletes a filtered set of memories within a specific space").optional()
|
|
22492
22566
|
}).passthrough()).describe("Array of delete selectors")
|
|
@@ -22497,7 +22571,7 @@ function registerTools() {
|
|
|
22497
22571
|
const result = await callApi("POST", `/v1/memories:batchDelete`, body);
|
|
22498
22572
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
22499
22573
|
});
|
|
22500
|
-
server.tool("goodmem_memories_batch_get", "Get multiple memories by ID", {
|
|
22574
|
+
server.tool("goodmem_memories_batch_get", "Get multiple memories by ID\n\nRetrieves multiple memories in a single operation, with individual success/failure results.", {
|
|
22501
22575
|
memory_ids: external_exports.array(external_exports.string()).describe("Array of memory IDs to retrieve"),
|
|
22502
22576
|
include_content: external_exports.boolean().describe("Whether to include the original content in the response").optional(),
|
|
22503
22577
|
include_processing_history: external_exports.boolean().describe("Whether to include background job processing history for each memory").optional()
|
|
@@ -22512,20 +22586,20 @@ function registerTools() {
|
|
|
22512
22586
|
const result = await callApi("POST", `/v1/memories:batchGet`, body);
|
|
22513
22587
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
22514
22588
|
});
|
|
22515
|
-
server.tool("goodmem_memories_create", "Create a memory from text content or base64-encoded content.
|
|
22589
|
+
server.tool("goodmem_memories_create", "Create a memory from text content or base64-encoded binary content. Provide exactly one of `original_content` or `original_content_b64` per call; if `original_content_b64`, `content_type` is required. `original_content_ref` is a metadata URL pointer (the server does NOT fetch it) and may be set alongside the content source.", {
|
|
22516
22590
|
memory_id: external_exports.string().describe("Optional client-provided UUID for the memory. If omitted, the server generates one. Returns ALREADY_EXISTS if the ID is already in use.").optional(),
|
|
22517
22591
|
space_id: external_exports.string().describe("ID of the space where this memory will be stored"),
|
|
22518
|
-
original_content: external_exports.string().describe("Original content as plain text (use either this or
|
|
22519
|
-
original_content_b64: external_exports.string().describe("Original content as base64-encoded binary data (use either this or
|
|
22592
|
+
original_content: external_exports.string().describe("Original content as plain text (use either this or original_content_b64)").optional(),
|
|
22593
|
+
original_content_b64: external_exports.string().describe("Original content as base64-encoded binary data (use either this or original_content)").optional(),
|
|
22520
22594
|
original_content_ref: external_exports.string().describe("Reference to external content location").optional(),
|
|
22521
22595
|
content_type: external_exports.string().describe("MIME type of the content (defaults to text/plain when original_content is provided)").optional(),
|
|
22522
|
-
metadata: external_exports.any().describe("Additional metadata for the memory").optional(),
|
|
22596
|
+
metadata: external_exports.record(external_exports.string(), external_exports.any()).describe("Additional metadata for the memory").optional(),
|
|
22523
22597
|
chunking_config: external_exports.object({
|
|
22524
22598
|
none: external_exports.any().describe("No chunking strategy - preserve original content as single unit").optional(),
|
|
22525
22599
|
recursive: external_exports.object({
|
|
22526
22600
|
chunkSize: external_exports.number().int().describe("Maximum size of a chunk (should be \u2264 context window)"),
|
|
22527
22601
|
chunkOverlap: external_exports.number().int().describe("Sliding overlap between chunks"),
|
|
22528
|
-
separators: external_exports.
|
|
22602
|
+
separators: external_exports.array(external_exports.string()).describe("Hierarchical separator list (order = preference)").optional(),
|
|
22529
22603
|
keepStrategy: external_exports.enum(["KEEP_NONE", "KEEP_START", "KEEP_END"]).describe("How to handle separators after splitting. KEEP_NONE is deprecated and behaves as KEEP_END."),
|
|
22530
22604
|
separatorIsRegex: external_exports.boolean().describe("Whether separators are regex patterns").optional(),
|
|
22531
22605
|
lengthMeasurement: external_exports.enum(["CHARACTER_COUNT", "TOKEN_COUNT", "CUSTOM"]).describe("How to measure chunk length")
|
|
@@ -22539,6 +22613,13 @@ function registerTools() {
|
|
|
22539
22613
|
}).passthrough().describe("Chunking strategy for this memory (if not provided, uses space default)").optional(),
|
|
22540
22614
|
extract_page_images: external_exports.boolean().describe("Optional hint to extract page images for eligible document types (for example, PDFs)").optional()
|
|
22541
22615
|
}, async (args) => {
|
|
22616
|
+
const _set = [args.original_content !== void 0 && args.original_content !== null, args.original_content_b64 !== void 0 && args.original_content_b64 !== null].filter(Boolean).length;
|
|
22617
|
+
if (_set !== 1) {
|
|
22618
|
+
throw new Error("exactly one of original_content or original_content_b64 must be set");
|
|
22619
|
+
}
|
|
22620
|
+
if (args.original_content_b64 !== void 0 && args.original_content_b64 !== null && (args.content_type === void 0 || args.content_type === null)) {
|
|
22621
|
+
throw new Error('`content_type` is required when `original_content_b64` is set (the server cannot infer MIME type from base64 bytes). Hint: pass content_type="application/pdf" or whatever matches the decoded payload');
|
|
22622
|
+
}
|
|
22542
22623
|
const body = {};
|
|
22543
22624
|
if (args.memory_id !== void 0)
|
|
22544
22625
|
body["memoryId"] = args.memory_id;
|
|
@@ -22564,17 +22645,23 @@ function registerTools() {
|
|
|
22564
22645
|
const result = await callApi("POST", `/v1/memories`, body);
|
|
22565
22646
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
22566
22647
|
});
|
|
22567
|
-
server.tool("goodmem_memories_delete", "Delete a memory", {
|
|
22648
|
+
server.tool("goodmem_memories_delete", "Delete a memory\n\nPermanently deletes a memory and its associated chunks. This operation cannot be undone and immediately removes the memory record from the database. IDEMPOTENCY: This operation is safe to retry - may return NOT_FOUND if the memory was already deleted or never existed. Requires DELETE_MEMORY_OWN permission for memories in spaces you own (or DELETE_MEMORY_ANY for admin users to delete any memory). Side effects include permanent removal of the memory record and all associated chunk data.", {
|
|
22568
22649
|
id: external_exports.string().describe("The UUID of the memory to delete")
|
|
22569
22650
|
}, async (args) => {
|
|
22651
|
+
if (typeof args.id !== "string" || args.id === "") {
|
|
22652
|
+
throw new Error("'id' must be a non-empty string");
|
|
22653
|
+
}
|
|
22570
22654
|
await callApi("DELETE", `/v1/memories/${encodeURIComponent(String(args.id))}`, void 0);
|
|
22571
22655
|
return { content: [{ type: "text", text: "Deleted successfully." }] };
|
|
22572
22656
|
});
|
|
22573
|
-
server.tool("goodmem_memories_get", "Get a memory by ID", {
|
|
22657
|
+
server.tool("goodmem_memories_get", "Get a memory by ID\n\nRetrieves a single memory by its ID. PERMISSION CLARIFICATION: With READ_MEMORY_OWN permission, access is granted if you own the parent space OR if the parent space is public (public_read=true). With READ_MEMORY_ANY permission, you can access any memory regardless of ownership. This is a read-only operation with no side effects and is safe to retry. Returns NOT_FOUND if the memory or its parent space does not exist.", {
|
|
22574
22658
|
id: external_exports.string().describe("The UUID of the memory to retrieve"),
|
|
22575
22659
|
include_content: external_exports.boolean().describe("Whether to include the original content in the response (defaults to false).").optional(),
|
|
22576
22660
|
include_processing_history: external_exports.boolean().describe("Whether to include background job processing history in the response (defaults to false).").optional()
|
|
22577
22661
|
}, async (args) => {
|
|
22662
|
+
if (typeof args.id !== "string" || args.id === "") {
|
|
22663
|
+
throw new Error("'id' must be a non-empty string");
|
|
22664
|
+
}
|
|
22578
22665
|
const query = {};
|
|
22579
22666
|
if (args.include_content !== void 0)
|
|
22580
22667
|
query["includeContent"] = String(args.include_content);
|
|
@@ -22583,17 +22670,20 @@ function registerTools() {
|
|
|
22583
22670
|
const result = await callApi("GET", `/v1/memories/${encodeURIComponent(String(args.id))}`, void 0, query);
|
|
22584
22671
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
22585
22672
|
});
|
|
22586
|
-
server.tool("goodmem_memories_list", "Lists memories within a given space. Results are paginated
|
|
22673
|
+
server.tool("goodmem_memories_list", "Lists memories within a given space. Results are paginated; pass the response's `next_token` back as a query parameter to fetch subsequent pages.", {
|
|
22587
22674
|
space_id: external_exports.string().describe("The UUID of the space containing the memories"),
|
|
22588
22675
|
include_content: external_exports.boolean().describe("Whether to include the original content in the response (defaults to false).").optional(),
|
|
22589
22676
|
include_processing_history: external_exports.boolean().describe("Whether to include background job processing history in the response (defaults to false).").optional(),
|
|
22590
|
-
status_filter: external_exports.
|
|
22677
|
+
status_filter: external_exports.enum(["PENDING", "PROCESSING", "COMPLETED", "FAILED"]).describe("Filter memories by processing status (PENDING, PROCESSING, COMPLETED, FAILED).").optional(),
|
|
22591
22678
|
filter: external_exports.string().describe("Optional metadata filter expression for list results").optional(),
|
|
22592
22679
|
max_results: external_exports.number().int().describe("Maximum number of results per page (defaults to 50, clamped to [1, 500]).").optional(),
|
|
22593
22680
|
next_token: external_exports.string().describe("Opaque pagination token for the next page. URL-safe Base64 without padding; do not parse or construct it.").optional(),
|
|
22594
|
-
sort_by: external_exports.
|
|
22681
|
+
sort_by: external_exports.enum(["created_at", "updated_at", "content_type", "processing_status"]).describe("Field to sort by (e.g., 'created_at').").optional(),
|
|
22595
22682
|
sort_order: external_exports.enum(["ASCENDING", "DESCENDING", "SORT_ORDER_UNSPECIFIED"]).describe("Sort direction (ASCENDING or DESCENDING).").optional()
|
|
22596
22683
|
}, async (args) => {
|
|
22684
|
+
if (typeof args.space_id !== "string" || args.space_id === "") {
|
|
22685
|
+
throw new Error("'space_id' must be a non-empty string");
|
|
22686
|
+
}
|
|
22597
22687
|
const query = {};
|
|
22598
22688
|
if (args.include_content !== void 0)
|
|
22599
22689
|
query["includeContent"] = String(args.include_content);
|
|
@@ -22614,7 +22704,7 @@ function registerTools() {
|
|
|
22614
22704
|
const result = await callApi("GET", `/v1/spaces/${encodeURIComponent(String(args.space_id))}/memories`, void 0, query);
|
|
22615
22705
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
22616
22706
|
});
|
|
22617
|
-
server.tool("goodmem_memories_pages", "List memory page images", {
|
|
22707
|
+
server.tool("goodmem_memories_pages", "List memory page images\n\nLists extracted page-image metadata for a memory with optional filters and pagination.", {
|
|
22618
22708
|
id: external_exports.string().describe("Memory UUID"),
|
|
22619
22709
|
start_page_index: external_exports.number().int().describe("Optional lower bound for returned page indices, inclusive.").optional(),
|
|
22620
22710
|
end_page_index: external_exports.number().int().describe("Optional upper bound for returned page indices, inclusive.").optional(),
|
|
@@ -22623,6 +22713,9 @@ function registerTools() {
|
|
|
22623
22713
|
max_results: external_exports.number().int().describe("Maximum number of results per page.").optional(),
|
|
22624
22714
|
next_token: external_exports.string().describe("Opaque pagination token for the next page. Do not parse or construct it.").optional()
|
|
22625
22715
|
}, async (args) => {
|
|
22716
|
+
if (typeof args.id !== "string" || args.id === "") {
|
|
22717
|
+
throw new Error("'id' must be a non-empty string");
|
|
22718
|
+
}
|
|
22626
22719
|
const query = {};
|
|
22627
22720
|
if (args.start_page_index !== void 0)
|
|
22628
22721
|
query["startPageIndex"] = String(args.start_page_index);
|
|
@@ -22650,12 +22743,18 @@ function registerTools() {
|
|
|
22650
22743
|
}).passthrough()).describe("Optional context items (text or binary) to provide additional context for the search.").optional(),
|
|
22651
22744
|
space_keys: external_exports.array(external_exports.object({
|
|
22652
22745
|
spaceId: external_exports.string().describe("The UUID for the space to search."),
|
|
22653
|
-
embedderWeights: external_exports.
|
|
22746
|
+
embedderWeights: external_exports.array(external_exports.object({
|
|
22747
|
+
embedderId: external_exports.string().describe("The UUID for the embedder."),
|
|
22748
|
+
weight: external_exports.number().describe("The weight to apply to this embedder's results. Can be positive, negative, or zero.")
|
|
22749
|
+
}).passthrough()).describe("Optional per-embedder weight overrides for this space. If not specified, database defaults are used.").optional(),
|
|
22654
22750
|
filter: external_exports.string().describe("Optional filter expression that must evaluate to true for memories in this space.").optional()
|
|
22655
22751
|
}).passthrough()).describe("List of spaces to search with optional per-embedder weight overrides."),
|
|
22656
22752
|
requested_size: external_exports.number().int().describe("Maximum number of memories to retrieve.").optional(),
|
|
22657
|
-
|
|
22658
|
-
|
|
22753
|
+
output_budget: external_exports.object({
|
|
22754
|
+
tokens: external_exports.number().int().describe("Token count for the budget. Must be positive.")
|
|
22755
|
+
}).passthrough().describe("Optional soft cap for generated retrieve replies. When set to a positive token count, chat post-processing uses this value as the LLM completion-token budget.").optional(),
|
|
22756
|
+
fetch_memory: external_exports.boolean().describe("Whether to include streamed memory-definition records in the response. Defaults to true when omitted. These records include memory metadata and audit fields, but omit originalContent unless fetch_memory_content is true.").optional(),
|
|
22757
|
+
fetch_memory_content: external_exports.boolean().describe("Whether streamed memory-definition records include originalContent bytes. Requires fetch_memory=true. Defaults to false when omitted.").optional(),
|
|
22659
22758
|
hnsw: external_exports.object({
|
|
22660
22759
|
efSearch: external_exports.number().int().describe("HNSW candidate list size (1..1000).").optional(),
|
|
22661
22760
|
iterativeScan: external_exports.enum(["ITERATIVE_SCAN_UNSPECIFIED", "ITERATIVE_SCAN_OFF", "ITERATIVE_SCAN_RELAXED_ORDER", "ITERATIVE_SCAN_STRICT_ORDER"]).describe("HNSW iterative scan mode. Use POST retrieve for this advanced tuning control.").optional(),
|
|
@@ -22664,12 +22763,12 @@ function registerTools() {
|
|
|
22664
22763
|
}).passthrough().describe("Optional request-level HNSW tuning overrides. Advanced usage; available on POST retrieve.").optional(),
|
|
22665
22764
|
post_processor: external_exports.object({
|
|
22666
22765
|
name: external_exports.string().describe("Fully qualified factory class name of the post-processor to apply."),
|
|
22667
|
-
config: external_exports.any().describe("Configuration parameters for the post-processor. Fields depend on the selected processor; see the linked documentation for the built-in ChatPostProcessor schema.").optional()
|
|
22766
|
+
config: external_exports.record(external_exports.string(), external_exports.any()).describe("Configuration parameters for the post-processor. Fields depend on the selected processor; see the linked documentation for the built-in ChatPostProcessor schema.").optional()
|
|
22668
22767
|
}).passthrough().describe("Optional post-processor configuration to transform retrieval results.").optional(),
|
|
22669
22768
|
logging: external_exports.object({
|
|
22670
|
-
enabled: external_exports.boolean().describe("Opts this request in to durable server-side request logging."),
|
|
22671
|
-
callerAttributes: external_exports.any().describe("Optional flat scalar attributes attached
|
|
22672
|
-
}).passthrough().describe("Optional durable request logging block for POST retrieve requests.
|
|
22769
|
+
enabled: external_exports.boolean().describe("Opts this request in to durable server-side request logging. False or omitted does not opt out of admin policy logging.").optional(),
|
|
22770
|
+
callerAttributes: external_exports.record(external_exports.string(), external_exports.any()).describe("Optional flat scalar attributes attached if this request is persisted by caller opt-in or admin policy logging. Supported value types are string, integer, floating-point, and boolean. At most 32 entries are accepted.").optional()
|
|
22771
|
+
}).passthrough().describe("Optional durable request logging block for POST retrieve requests. Set logging.enabled=true to opt in, or supply flat scalar logging.callerAttributes to attach if caller opt-in or an admin policy logs the request.").optional(),
|
|
22673
22772
|
timeout_sec: external_exports.number().int().describe("Maximum seconds to wait for the streaming response (default: 200).").optional()
|
|
22674
22773
|
}, async (args) => {
|
|
22675
22774
|
const body = {};
|
|
@@ -22681,6 +22780,8 @@ function registerTools() {
|
|
|
22681
22780
|
body["spaceKeys"] = args.space_keys;
|
|
22682
22781
|
if (args.requested_size !== void 0)
|
|
22683
22782
|
body["requestedSize"] = args.requested_size;
|
|
22783
|
+
if (args.output_budget !== void 0)
|
|
22784
|
+
body["outputBudget"] = args.output_budget;
|
|
22684
22785
|
if (args.fetch_memory !== void 0)
|
|
22685
22786
|
body["fetchMemory"] = args.fetch_memory;
|
|
22686
22787
|
if (args.fetch_memory_content !== void 0)
|
|
@@ -22694,7 +22795,7 @@ function registerTools() {
|
|
|
22694
22795
|
const result = await callApiNdjson("POST", `/v1/memories:retrieve`, body, void 0, args.timeout_sec);
|
|
22695
22796
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
22696
22797
|
});
|
|
22697
|
-
server.tool("goodmem_ocr_document", "Run OCR on a document or image. Pass base64-encoded document bytes as `content`.
|
|
22798
|
+
server.tool("goodmem_ocr_document", "Run OCR on a document or image. Pass base64-encoded document bytes as `content`. The server auto-detects format; set `format` explicitly only if needed.", {
|
|
22698
22799
|
content: external_exports.string().describe("Base64-encoded document bytes"),
|
|
22699
22800
|
format: external_exports.enum(["AUTO", "PDF", "TIFF", "PNG", "JPEG", "BMP"]).describe("Input format hint (AUTO, PDF, TIFF, PNG, JPEG, BMP)").optional(),
|
|
22700
22801
|
include_raw_json: external_exports.boolean().describe("Include raw OCR JSON payload in the response").optional(),
|
|
@@ -22718,7 +22819,79 @@ function registerTools() {
|
|
|
22718
22819
|
const result = await callApi("POST", `/v1/ocr:document`, body);
|
|
22719
22820
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
22720
22821
|
});
|
|
22721
|
-
server.tool("
|
|
22822
|
+
server.tool("goodmem_ping_once", "Run a single ping probe\n\nRuns a single ping probe and returns the probe result.", {
|
|
22823
|
+
target_id: external_exports.string().describe("Target resource ID (UUID)"),
|
|
22824
|
+
target_type_hint: external_exports.enum(["TARGET_TYPE_UNSPECIFIED", "EMBEDDER", "RERANKER", "LLM"]).describe("Optional hint for the target resource type").optional(),
|
|
22825
|
+
payload_type: external_exports.enum(["PAYLOAD_TYPE_UNSPECIFIED", "TEXT", "JSON", "BINARY"]).describe("Desired payload type (defaults to provider-specific value)").optional(),
|
|
22826
|
+
payload: external_exports.string().describe("Explicit UTF-8 payload to send with the probe (mutually exclusive with payload_size_bytes)").optional(),
|
|
22827
|
+
payload_size_bytes: external_exports.number().int().describe("Synthetic payload size in bytes (mutually exclusive with payload)").optional(),
|
|
22828
|
+
timeout_ms: external_exports.number().int().describe("Per-probe timeout in milliseconds (0 uses server default)").optional()
|
|
22829
|
+
}, async (args) => {
|
|
22830
|
+
const _set = [args.payload !== void 0 && args.payload !== null, args.payload_size_bytes !== void 0 && args.payload_size_bytes !== null].filter(Boolean).length;
|
|
22831
|
+
if (_set > 1) {
|
|
22832
|
+
throw new Error("at most one of payload or payload_size_bytes may be set");
|
|
22833
|
+
}
|
|
22834
|
+
const body = {};
|
|
22835
|
+
if (args.target_id !== void 0)
|
|
22836
|
+
body["targetId"] = args.target_id;
|
|
22837
|
+
if (args.target_type_hint !== void 0)
|
|
22838
|
+
body["targetTypeHint"] = args.target_type_hint;
|
|
22839
|
+
if (args.payload_type !== void 0)
|
|
22840
|
+
body["payloadType"] = args.payload_type;
|
|
22841
|
+
if (args.payload !== void 0)
|
|
22842
|
+
body["payload"] = args.payload;
|
|
22843
|
+
if (args.payload_size_bytes !== void 0)
|
|
22844
|
+
body["payloadSizeBytes"] = args.payload_size_bytes;
|
|
22845
|
+
if (args.timeout_ms !== void 0)
|
|
22846
|
+
body["timeoutMs"] = args.timeout_ms;
|
|
22847
|
+
const result = await callApi("POST", `/v1/ping:once`, body, void 0, (args.timeout_ms ?? 5e3) + 3e4);
|
|
22848
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
22849
|
+
});
|
|
22850
|
+
server.tool("goodmem_ping_stream", "Stream ping probe results\n\nOpens a streaming ping session and returns per-probe results plus a terminal summary.", {
|
|
22851
|
+
target_id: external_exports.string().describe("Target resource ID (UUID)"),
|
|
22852
|
+
target_type_hint: external_exports.enum(["TARGET_TYPE_UNSPECIFIED", "EMBEDDER", "RERANKER", "LLM"]).describe("Optional hint for the target resource type").optional(),
|
|
22853
|
+
count: external_exports.number().int().describe("Number of probes to run (0 uses server default)").optional(),
|
|
22854
|
+
interval_ms: external_exports.number().int().describe("Delay between probes in milliseconds (0 uses server default)").optional(),
|
|
22855
|
+
timeout_ms: external_exports.number().int().describe("Per-probe timeout in milliseconds (0 uses server default)").optional(),
|
|
22856
|
+
payload_type: external_exports.enum(["PAYLOAD_TYPE_UNSPECIFIED", "TEXT", "JSON", "BINARY"]).describe("Desired payload type (defaults to provider-specific value)").optional(),
|
|
22857
|
+
payload: external_exports.string().describe("Explicit UTF-8 payload to send with each probe (mutually exclusive with payload_size_bytes)").optional(),
|
|
22858
|
+
payload_size_bytes: external_exports.number().int().describe("Synthetic payload size in bytes (mutually exclusive with payload)").optional(),
|
|
22859
|
+
max_in_flight: external_exports.number().int().describe("Maximum concurrent probes (defaults to 1)").optional(),
|
|
22860
|
+
jitter: external_exports.boolean().describe("Add jitter to probe scheduling").optional(),
|
|
22861
|
+
labels: external_exports.record(external_exports.string(), external_exports.string()).describe("Optional labels to attach to the ping session").optional(),
|
|
22862
|
+
timeout_sec: external_exports.number().int().describe("Maximum seconds to wait for the streaming response (default: 200).").optional()
|
|
22863
|
+
}, async (args) => {
|
|
22864
|
+
const _set = [args.payload !== void 0 && args.payload !== null, args.payload_size_bytes !== void 0 && args.payload_size_bytes !== null].filter(Boolean).length;
|
|
22865
|
+
if (_set > 1) {
|
|
22866
|
+
throw new Error("at most one of payload or payload_size_bytes may be set");
|
|
22867
|
+
}
|
|
22868
|
+
const body = {};
|
|
22869
|
+
if (args.target_id !== void 0)
|
|
22870
|
+
body["targetId"] = args.target_id;
|
|
22871
|
+
if (args.target_type_hint !== void 0)
|
|
22872
|
+
body["targetTypeHint"] = args.target_type_hint;
|
|
22873
|
+
if (args.count !== void 0)
|
|
22874
|
+
body["count"] = args.count;
|
|
22875
|
+
if (args.interval_ms !== void 0)
|
|
22876
|
+
body["intervalMs"] = args.interval_ms;
|
|
22877
|
+
if (args.timeout_ms !== void 0)
|
|
22878
|
+
body["timeoutMs"] = args.timeout_ms;
|
|
22879
|
+
if (args.payload_type !== void 0)
|
|
22880
|
+
body["payloadType"] = args.payload_type;
|
|
22881
|
+
if (args.payload !== void 0)
|
|
22882
|
+
body["payload"] = args.payload;
|
|
22883
|
+
if (args.payload_size_bytes !== void 0)
|
|
22884
|
+
body["payloadSizeBytes"] = args.payload_size_bytes;
|
|
22885
|
+
if (args.max_in_flight !== void 0)
|
|
22886
|
+
body["maxInFlight"] = args.max_in_flight;
|
|
22887
|
+
if (args.jitter !== void 0)
|
|
22888
|
+
body["jitter"] = args.jitter;
|
|
22889
|
+
if (args.labels !== void 0)
|
|
22890
|
+
body["labels"] = args.labels;
|
|
22891
|
+
const result = await callApiNdjson("POST", `/v1/ping:stream`, body, void 0, args.timeout_sec ?? Math.ceil(((args.count || 100) * (args.interval_ms || 1e3) + (args.timeout_ms || 5e3) + 6e4) / 1e3));
|
|
22892
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
22893
|
+
});
|
|
22894
|
+
server.tool("goodmem_rerankers_create", "Create a new reranker\n\nCreates a new reranker configuration for ranking search results. Rerankers represent connections to different reranking API services (like TEI, OpenAI, etc.) and include all the necessary configuration to use them for result ranking. DUPLICATE DETECTION: Returns HTTP 409 Conflict (ALREADY_EXISTS) if another reranker exists with identical {owner_id, provider_type, endpoint_url, api_path, model_identifier, credentialsFingerprint} after URL canonicalization. Uniqueness is enforced per-owner, allowing different users to have identical configurations. Credentials are hashed (SHA-256) for uniqueness while remaining encrypted. DEFAULTS: api_path defaults to '/v2/rerank' for Cohere and '/rerank' for other providers if omitted; supported_modalities defaults to [TEXT] if omitted. Requires CREATE_RERANKER_OWN permission (or CREATE_RERANKER_ANY for admin users). This operation is NOT idempotent - each request creates a new reranker record.", {
|
|
22722
22895
|
display_name: external_exports.string().describe("User-facing name of the reranker"),
|
|
22723
22896
|
description: external_exports.string().describe("Description of the reranker").optional(),
|
|
22724
22897
|
provider_type: external_exports.enum(["OPENAI", "VLLM", "TEI", "LLAMA_CPP", "VOYAGE", "COHERE", "JINA"]).describe("Type of reranking provider (auto-inferred from model_identifier if omitted)").optional(),
|
|
@@ -22732,16 +22905,16 @@ function registerTools() {
|
|
|
22732
22905
|
inlineSecret: external_exports.string().describe("Secret stored directly in GoodMem (mutually exclusive with secretRef)").optional(),
|
|
22733
22906
|
secretRef: external_exports.object({
|
|
22734
22907
|
uri: external_exports.string().describe("URI identifying where the secret can be resolved (e.g., vault://, env://)"),
|
|
22735
|
-
hints: external_exports.
|
|
22908
|
+
hints: external_exports.record(external_exports.string(), external_exports.string()).describe('Optional metadata to help resolvers decode the secret (e.g., {"encoding":"base64"})').optional()
|
|
22736
22909
|
}).passthrough().describe("Reference to an external secret manager entry (mutually exclusive with inlineSecret)").optional(),
|
|
22737
22910
|
headerName: external_exports.string().describe("Desired HTTP header to carry the credential (defaults to Authorization)").optional(),
|
|
22738
22911
|
prefix: external_exports.string().describe('Optional prefix prepended to the secret (e.g., "Bearer ")').optional()
|
|
22739
22912
|
}).passthrough().describe("Configuration when kind is CREDENTIAL_KIND_API_KEY").optional(),
|
|
22740
22913
|
gcpAdc: external_exports.object({
|
|
22741
|
-
scopes: external_exports.
|
|
22914
|
+
scopes: external_exports.array(external_exports.string()).describe("Additional OAuth scopes. Empty list falls back to the default cloud-platform scope.").optional(),
|
|
22742
22915
|
quotaProjectId: external_exports.string().describe("Optional quota project used for billing").optional()
|
|
22743
22916
|
}).passthrough().describe("Configuration when kind is CREDENTIAL_KIND_GCP_ADC").optional(),
|
|
22744
|
-
labels: external_exports.
|
|
22917
|
+
labels: external_exports.record(external_exports.string(), external_exports.string()).describe('Optional annotations to aid operators (e.g., "owner=vertex")').optional()
|
|
22745
22918
|
}).passthrough().describe("Structured credential payload describing how to authenticate with the provider. Required for SaaS providers; optional for local or proxy providers.").optional(),
|
|
22746
22919
|
labels: external_exports.record(external_exports.string(), external_exports.string()).describe("User-defined labels for categorization").optional(),
|
|
22747
22920
|
version: external_exports.string().describe("Version information").optional(),
|
|
@@ -22781,19 +22954,25 @@ function registerTools() {
|
|
|
22781
22954
|
const result = await callApi("POST", `/v1/rerankers`, body);
|
|
22782
22955
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
22783
22956
|
});
|
|
22784
|
-
server.tool("goodmem_rerankers_delete", "Delete a reranker", {
|
|
22957
|
+
server.tool("goodmem_rerankers_delete", "Delete a reranker\n\nPermanently deletes a reranker configuration. This operation cannot be undone and immediately removes the reranker record from the database. SIDE EFFECTS: Invalidates any cached references to this reranker; does not affect historical usage data or audit logs. Requires DELETE_RERANKER_OWN permission for rerankers you own (or DELETE_RERANKER_ANY for admin users). This operation is safe to retry - may return NOT_FOUND if already deleted.", {
|
|
22785
22958
|
id: external_exports.string().describe("The unique identifier of the reranker to delete")
|
|
22786
22959
|
}, async (args) => {
|
|
22960
|
+
if (typeof args.id !== "string" || args.id === "") {
|
|
22961
|
+
throw new Error("'id' must be a non-empty string");
|
|
22962
|
+
}
|
|
22787
22963
|
await callApi("DELETE", `/v1/rerankers/${encodeURIComponent(String(args.id))}`, void 0);
|
|
22788
22964
|
return { content: [{ type: "text", text: "Deleted successfully." }] };
|
|
22789
22965
|
});
|
|
22790
|
-
server.tool("goodmem_rerankers_get", "Get a reranker by ID", {
|
|
22966
|
+
server.tool("goodmem_rerankers_get", "Get a reranker by ID\n\nRetrieves the details of a specific reranker configuration by its unique identifier. Response payloads include stored credentials, matching gRPC response semantics. Requires READ_RERANKER_OWN permission for rerankers you own (or READ_RERANKER_ANY for admin users). This is a read-only operation with no side effects and is safe to retry.", {
|
|
22791
22967
|
id: external_exports.string().describe("The unique identifier of the reranker to retrieve")
|
|
22792
22968
|
}, async (args) => {
|
|
22969
|
+
if (typeof args.id !== "string" || args.id === "") {
|
|
22970
|
+
throw new Error("'id' must be a non-empty string");
|
|
22971
|
+
}
|
|
22793
22972
|
const result = await callApi("GET", `/v1/rerankers/${encodeURIComponent(String(args.id))}`, void 0);
|
|
22794
22973
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
22795
22974
|
});
|
|
22796
|
-
server.tool("goodmem_rerankers_list", "List rerankers", {
|
|
22975
|
+
server.tool("goodmem_rerankers_list", "List rerankers\n\nRetrieves a list of reranker configurations accessible to the caller, with optional filtering. IMPORTANT: Pagination is NOT supported - all matching results are returned. Results are ordered by created_at descending. Responses include stored credentials, matching gRPC response semantics. LABEL FILTERS: Label filters accept either label.<key>=<value> or label[key]=value (for example, label.environment=production or label[environment]=production). PERMISSION-BASED FILTERING: With LIST_RERANKER_OWN permission, only your own rerankers are shown. With LIST_RERANKER_ANY permission, you can see all rerankers or filter by any owner_id. Specifying owner_id without LIST_RERANKER_ANY permission returns PERMISSION_DENIED.", {
|
|
22797
22976
|
owner_id: external_exports.string().describe("Filter rerankers by owner ID. With LIST_RERANKER_ANY permission, omitting this shows all accessible rerankers; providing it filters by that owner. With LIST_RERANKER_OWN permission, only your own rerankers are shown regardless of this parameter (PERMISSION_DENIED if set to another user).").optional(),
|
|
22798
22977
|
provider_type: external_exports.enum(["OPENAI", "VLLM", "TEI", "LLAMA_CPP", "VOYAGE", "COHERE", "JINA"]).describe("Filter rerankers by provider type. Allowed values match the ProviderType schema.").optional(),
|
|
22799
22978
|
label: external_exports.record(external_exports.string(), external_exports.string()).describe("Filter by label. Each key-value pair is sent as label[key]=value query parameter.").optional()
|
|
@@ -22810,36 +22989,43 @@ function registerTools() {
|
|
|
22810
22989
|
const result = await callApi("GET", `/v1/rerankers`, void 0, query);
|
|
22811
22990
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
22812
22991
|
});
|
|
22813
|
-
server.tool("goodmem_rerankers_update", "Update a reranker", {
|
|
22992
|
+
server.tool("goodmem_rerankers_update", "Update a reranker\n\nUpdates an existing reranker configuration including display information, endpoint configuration, model parameters, credentials, and labels. All fields are optional - only specified fields will be updated. IMMUTABLE FIELDS: providerType and ownerId cannot be changed after creation. SUPPORTED_MODALITIES UPDATE: If the array contains >=1 elements, it replaces the stored set; if empty or omitted, no change occurs and it does not count as an update by itself. Returns ALREADY_EXISTS if update would create duplicate with same {ownerId, providerType, endpoint_url, api_path, model_identifier, credentialsFingerprint} after URL canonicalization (HTTP 409 Conflict / ALREADY_EXISTS). Requires UPDATE_RERANKER_OWN permission for rerankers you own (or UPDATE_RERANKER_ANY for admin users). This operation is idempotent.", {
|
|
22814
22993
|
id: external_exports.string().describe("The unique identifier of the reranker to update"),
|
|
22815
22994
|
display_name: external_exports.string().describe("User-facing name of the reranker").optional(),
|
|
22816
22995
|
description: external_exports.string().describe("Description of the reranker").optional(),
|
|
22817
22996
|
endpoint_url: external_exports.string().describe("API endpoint URL").optional(),
|
|
22818
22997
|
api_path: external_exports.string().describe("API path for reranking request").optional(),
|
|
22819
22998
|
model_identifier: external_exports.string().describe("Model identifier").optional(),
|
|
22820
|
-
supported_modalities: external_exports.array(external_exports.enum(["TEXT", "IMAGE", "AUDIO", "VIDEO"])).describe("
|
|
22999
|
+
supported_modalities: external_exports.array(external_exports.enum(["TEXT", "IMAGE", "AUDIO", "VIDEO"])).describe("Update supported modalities (if array contains >=1 elements, replaces stored set; if empty or omitted, no change and does not count as an update by itself)").optional(),
|
|
22821
23000
|
credentials: external_exports.object({
|
|
22822
23001
|
kind: external_exports.enum(["CREDENTIAL_KIND_UNSPECIFIED", "CREDENTIAL_KIND_API_KEY", "CREDENTIAL_KIND_GCP_ADC"]).describe("Selected credential strategy"),
|
|
22823
23002
|
apiKey: external_exports.object({
|
|
22824
23003
|
inlineSecret: external_exports.string().describe("Secret stored directly in GoodMem (mutually exclusive with secretRef)").optional(),
|
|
22825
23004
|
secretRef: external_exports.object({
|
|
22826
23005
|
uri: external_exports.string().describe("URI identifying where the secret can be resolved (e.g., vault://, env://)"),
|
|
22827
|
-
hints: external_exports.
|
|
23006
|
+
hints: external_exports.record(external_exports.string(), external_exports.string()).describe('Optional metadata to help resolvers decode the secret (e.g., {"encoding":"base64"})').optional()
|
|
22828
23007
|
}).passthrough().describe("Reference to an external secret manager entry (mutually exclusive with inlineSecret)").optional(),
|
|
22829
23008
|
headerName: external_exports.string().describe("Desired HTTP header to carry the credential (defaults to Authorization)").optional(),
|
|
22830
23009
|
prefix: external_exports.string().describe('Optional prefix prepended to the secret (e.g., "Bearer ")').optional()
|
|
22831
23010
|
}).passthrough().describe("Configuration when kind is CREDENTIAL_KIND_API_KEY").optional(),
|
|
22832
23011
|
gcpAdc: external_exports.object({
|
|
22833
|
-
scopes: external_exports.
|
|
23012
|
+
scopes: external_exports.array(external_exports.string()).describe("Additional OAuth scopes. Empty list falls back to the default cloud-platform scope.").optional(),
|
|
22834
23013
|
quotaProjectId: external_exports.string().describe("Optional quota project used for billing").optional()
|
|
22835
23014
|
}).passthrough().describe("Configuration when kind is CREDENTIAL_KIND_GCP_ADC").optional(),
|
|
22836
|
-
labels: external_exports.
|
|
23015
|
+
labels: external_exports.record(external_exports.string(), external_exports.string()).describe('Optional annotations to aid operators (e.g., "owner=vertex")').optional()
|
|
22837
23016
|
}).passthrough().describe("Structured credential payload describing how to authenticate with the provider. Omit to keep existing credentials.").optional(),
|
|
22838
|
-
replace_labels: external_exports.record(external_exports.string(), external_exports.string()).describe("Replace all existing labels with these (mutually exclusive with
|
|
22839
|
-
merge_labels: external_exports.record(external_exports.string(), external_exports.string()).describe("Merge these labels with existing ones (mutually exclusive with
|
|
23017
|
+
replace_labels: external_exports.record(external_exports.string(), external_exports.string()).describe("Replace all existing labels with these (mutually exclusive with merge_labels)").optional(),
|
|
23018
|
+
merge_labels: external_exports.record(external_exports.string(), external_exports.string()).describe("Merge these labels with existing ones (mutually exclusive with replace_labels)").optional(),
|
|
22840
23019
|
version: external_exports.string().describe("Version information").optional(),
|
|
22841
23020
|
monitoring_endpoint: external_exports.string().describe("Monitoring endpoint URL").optional()
|
|
22842
23021
|
}, async (args) => {
|
|
23022
|
+
if (typeof args.id !== "string" || args.id === "") {
|
|
23023
|
+
throw new Error("'id' must be a non-empty string");
|
|
23024
|
+
}
|
|
23025
|
+
const _set = [args.replace_labels !== void 0 && args.replace_labels !== null, args.merge_labels !== void 0 && args.merge_labels !== null].filter(Boolean).length;
|
|
23026
|
+
if (_set > 1) {
|
|
23027
|
+
throw new Error("at most one of replace_labels or merge_labels may be set");
|
|
23028
|
+
}
|
|
22843
23029
|
const body = {};
|
|
22844
23030
|
if (args.display_name !== void 0)
|
|
22845
23031
|
body["displayName"] = args.display_name;
|
|
@@ -22866,13 +23052,13 @@ function registerTools() {
|
|
|
22866
23052
|
const result = await callApi("PUT", `/v1/rerankers/${encodeURIComponent(String(args.id))}`, body);
|
|
22867
23053
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
22868
23054
|
});
|
|
22869
|
-
server.tool("goodmem_spaces_create", "Create a new Space", {
|
|
23055
|
+
server.tool("goodmem_spaces_create", "Create a new Space\n\nCreates a new space with the provided name, labels, and embedder configuration. A space is a container for organizing related memories. OWNER DEFAULTS: Owner defaults to authenticated user unless owner_id is provided (requires CREATE_SPACE_ANY if differs). EMBEDDER REQUIREMENTS: At least one embedder configuration must be specified. DUPLICATE DETECTION: Returns ALREADY_EXISTS if another space exists with identical {owner_id, name} (case-sensitive). Requires CREATE_SPACE_OWN permission (or CREATE_SPACE_ANY for admin users). This operation is NOT idempotent.", {
|
|
22870
23056
|
name: external_exports.string().describe("The desired name for the space. Must be unique within the user's scope."),
|
|
22871
23057
|
labels: external_exports.record(external_exports.string(), external_exports.string()).describe("A set of key-value pairs to categorize or tag the space. Used for filtering and organizational purposes.").optional(),
|
|
22872
23058
|
space_embedders: external_exports.array(external_exports.object({
|
|
22873
23059
|
embedderId: external_exports.string().describe("The UUID for the embedder to associate with the space."),
|
|
22874
23060
|
defaultRetrievalWeight: external_exports.number().describe("Relative weight for this embedder used by default during retrieval. If omitted, defaults to 1.0; values need not sum to 1 and can be overridden per request.").optional()
|
|
22875
|
-
}).passthrough()).
|
|
23061
|
+
}).passthrough()).describe("List of embedder configurations to associate with this space. At least one embedder configuration is required. Each specifies an embedder ID and a relative default retrieval weight used when no per-request overrides are provided."),
|
|
22876
23062
|
public_read: external_exports.boolean().describe("Indicates if the space and its memories can be read by unauthenticated users or users other than the owner. Defaults to false.").optional(),
|
|
22877
23063
|
owner_id: external_exports.string().describe("Optional owner ID. If not provided, derived from the authentication context. Requires CREATE_SPACE_ANY permission if specified.").optional(),
|
|
22878
23064
|
default_chunking_config: external_exports.object({
|
|
@@ -22880,7 +23066,7 @@ function registerTools() {
|
|
|
22880
23066
|
recursive: external_exports.object({
|
|
22881
23067
|
chunkSize: external_exports.number().int().describe("Maximum size of a chunk (should be \u2264 context window)"),
|
|
22882
23068
|
chunkOverlap: external_exports.number().int().describe("Sliding overlap between chunks"),
|
|
22883
|
-
separators: external_exports.
|
|
23069
|
+
separators: external_exports.array(external_exports.string()).describe("Hierarchical separator list (order = preference)").optional(),
|
|
22884
23070
|
keepStrategy: external_exports.enum(["KEEP_NONE", "KEEP_START", "KEEP_END"]).describe("How to handle separators after splitting. KEEP_NONE is deprecated and behaves as KEEP_END."),
|
|
22885
23071
|
separatorIsRegex: external_exports.boolean().describe("Whether separators are regex patterns").optional(),
|
|
22886
23072
|
lengthMeasurement: external_exports.enum(["CHARACTER_COUNT", "TOKEN_COUNT", "CUSTOM"]).describe("How to measure chunk length")
|
|
@@ -22912,24 +23098,30 @@ function registerTools() {
|
|
|
22912
23098
|
const result = await callApi("POST", `/v1/spaces`, body);
|
|
22913
23099
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
22914
23100
|
});
|
|
22915
|
-
server.tool("goodmem_spaces_delete", "Delete a space", {
|
|
23101
|
+
server.tool("goodmem_spaces_delete", "Delete a space\n\nPermanently deletes a space and all associated content. This operation cannot be undone. CASCADE DELETION: Removes the space record and cascades deletion to associated memories, chunks, and embedder associations. Requires DELETE_SPACE_OWN permission for spaces you own (or DELETE_SPACE_ANY for admin users). This operation is safe to retry - may return NOT_FOUND if already deleted.", {
|
|
22916
23102
|
id: external_exports.string().describe("The unique identifier of the space to delete")
|
|
22917
23103
|
}, async (args) => {
|
|
23104
|
+
if (typeof args.id !== "string" || args.id === "") {
|
|
23105
|
+
throw new Error("'id' must be a non-empty string");
|
|
23106
|
+
}
|
|
22918
23107
|
await callApi("DELETE", `/v1/spaces/${encodeURIComponent(String(args.id))}`, void 0);
|
|
22919
23108
|
return { content: [{ type: "text", text: "Deleted successfully." }] };
|
|
22920
23109
|
});
|
|
22921
|
-
server.tool("goodmem_spaces_get", "Get a space by ID", {
|
|
23110
|
+
server.tool("goodmem_spaces_get", "Get a space by ID\n\nRetrieves a specific space by its unique identifier. Returns the complete space information, including name, labels, embedder configuration, and metadata. PUBLIC SPACE ACCESS: When public_read=true, any authenticated user can retrieve the space metadata, bypassing ownership checks. Otherwise, requires ownership or DISPLAY_SPACE_ANY permission. Requires DISPLAY_SPACE_OWN permission for owned spaces (or DISPLAY_SPACE_ANY for admin users to view any space). This is a read-only operation safe to retry.", {
|
|
22922
23111
|
id: external_exports.string().describe("The unique identifier of the space to retrieve")
|
|
22923
23112
|
}, async (args) => {
|
|
23113
|
+
if (typeof args.id !== "string" || args.id === "") {
|
|
23114
|
+
throw new Error("'id' must be a non-empty string");
|
|
23115
|
+
}
|
|
22924
23116
|
const result = await callApi("GET", `/v1/spaces/${encodeURIComponent(String(args.id))}`, void 0);
|
|
22925
23117
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
22926
23118
|
});
|
|
22927
|
-
server.tool("goodmem_spaces_list", "List spaces accessible to the caller, with optional filtering by owner, labels, and name. Results are paginated
|
|
22928
|
-
owner_id: external_exports.string().describe("Filter spaces by owner ID. With LIST_SPACE_ANY permission and
|
|
23119
|
+
server.tool("goodmem_spaces_list", "List spaces accessible to the caller, with optional filtering by owner, labels, and name. Results are paginated; pass the response's `next_token` back as a query parameter to fetch subsequent pages.", {
|
|
23120
|
+
owner_id: external_exports.string().describe("Filter spaces by owner ID. With LIST_SPACE_ANY permission and owner_id omitted, returns all visible spaces. Otherwise returns caller-owned spaces only. Specifying owner_id without LIST_SPACE_ANY returns PERMISSION_DENIED.").optional(),
|
|
22929
23121
|
name_filter: external_exports.string().describe("Filter spaces by name using glob pattern matching").optional(),
|
|
22930
23122
|
max_results: external_exports.number().int().describe("Maximum number of results to return in a single page (defaults to 50, clamped to [1, 1000])").optional(),
|
|
22931
23123
|
next_token: external_exports.string().describe("Pagination token for retrieving the next set of results").optional(),
|
|
22932
|
-
sort_by: external_exports.
|
|
23124
|
+
sort_by: external_exports.enum(["created_time", "updated_time", "name"]).describe("Field to sort by: 'created_time', 'updated_time', or 'name' (default: 'created_time'). Unsupported values return INVALID_ARGUMENT.").optional(),
|
|
22933
23125
|
sort_order: external_exports.enum(["ASCENDING", "DESCENDING", "SORT_ORDER_UNSPECIFIED"]).describe("Sort order (ASCENDING or DESCENDING, default: DESCENDING)").optional(),
|
|
22934
23126
|
label: external_exports.record(external_exports.string(), external_exports.string()).describe("Filter by label. Each key-value pair is sent as label[key]=value query parameter.").optional()
|
|
22935
23127
|
}, async (args) => {
|
|
@@ -22953,13 +23145,20 @@ function registerTools() {
|
|
|
22953
23145
|
const result = await callApi("GET", `/v1/spaces`, void 0, query);
|
|
22954
23146
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
22955
23147
|
});
|
|
22956
|
-
server.tool("goodmem_spaces_update", "Update a space", {
|
|
23148
|
+
server.tool("goodmem_spaces_update", "Update a space\n\nUpdates an existing space with new values for the specified fields. Only name, public_read, and labels can be updated. Fields not included in the request remain unchanged. IMMUTABLE FIELDS: space_embedders, default_chunking_config, and ownerId cannot be modified after creation. NAME UNIQUENESS: Name must be unique per owner - returns ALREADY_EXISTS if name conflicts with existing space. Requires UPDATE_SPACE_OWN permission for spaces you own (or UPDATE_SPACE_ANY for admin users). This operation is idempotent.", {
|
|
22957
23149
|
id: external_exports.string().describe("The unique identifier of the space to update"),
|
|
22958
23150
|
name: external_exports.string().describe("The new name for the space.").optional(),
|
|
22959
23151
|
public_read: external_exports.boolean().describe("Whether the space is publicly readable by all users.").optional(),
|
|
22960
|
-
replace_labels: external_exports.record(external_exports.string(), external_exports.string()).describe("Labels to replace all existing labels. Mutually exclusive with
|
|
22961
|
-
merge_labels: external_exports.record(external_exports.string(), external_exports.string()).describe("Labels to merge with existing labels. Mutually exclusive with
|
|
23152
|
+
replace_labels: external_exports.record(external_exports.string(), external_exports.string()).describe("Labels to replace all existing labels. Mutually exclusive with merge_labels.").optional(),
|
|
23153
|
+
merge_labels: external_exports.record(external_exports.string(), external_exports.string()).describe("Labels to merge with existing labels. Mutually exclusive with replace_labels.").optional()
|
|
22962
23154
|
}, async (args) => {
|
|
23155
|
+
if (typeof args.id !== "string" || args.id === "") {
|
|
23156
|
+
throw new Error("'id' must be a non-empty string");
|
|
23157
|
+
}
|
|
23158
|
+
const _set = [args.replace_labels !== void 0 && args.replace_labels !== null, args.merge_labels !== void 0 && args.merge_labels !== null].filter(Boolean).length;
|
|
23159
|
+
if (_set > 1) {
|
|
23160
|
+
throw new Error("at most one of replace_labels or merge_labels may be set");
|
|
23161
|
+
}
|
|
22963
23162
|
const body = {};
|
|
22964
23163
|
if (args.name !== void 0)
|
|
22965
23164
|
body["name"] = args.name;
|
|
@@ -22972,27 +23171,29 @@ function registerTools() {
|
|
|
22972
23171
|
const result = await callApi("PUT", `/v1/spaces/${encodeURIComponent(String(args.id))}`, body);
|
|
22973
23172
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
22974
23173
|
});
|
|
22975
|
-
server.tool("goodmem_system_info", "Retrieve server build metadata", {}, async (args) => {
|
|
23174
|
+
server.tool("goodmem_system_info", "Retrieve server build metadata\n\nReturns the server's advertised semantic version, git metadata, build timestamp, and optional capability flags. The endpoint is intentionally unauthenticated so bootstrap tooling can call it before API keys exist.", {}, async (args) => {
|
|
22976
23175
|
const result = await callApi("GET", `/v1/system/info`, void 0);
|
|
22977
23176
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
22978
23177
|
});
|
|
22979
|
-
server.tool("goodmem_system_init", "Initialize the system", {}, async (args) => {
|
|
23178
|
+
server.tool("goodmem_system_init", "Initialize the system\n\nInitializes the system by creating a root user and API key. This endpoint should only be called once during first-time setup. If the system is already initialized, the endpoint will return a success response without creating new credentials.", {}, async (args) => {
|
|
22980
23179
|
const result = await callApi("POST", `/v1/system/init`, void 0);
|
|
22981
23180
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
22982
23181
|
});
|
|
22983
|
-
server.tool("goodmem_users_get", "Retrieves a user by ID or email address. Exactly one of `id` and `email` must be provided. For getting your own profile, use `
|
|
23182
|
+
server.tool("goodmem_users_get", "Retrieves a user by ID or email address. Exactly one of `id` and `email` must be provided. For getting your own profile, use the `goodmem_users_me` tool.", {
|
|
22984
23183
|
id: external_exports.string().describe("The UUID of the user to retrieve").optional(),
|
|
22985
23184
|
email: external_exports.string().describe("The email address of the user to retrieve").optional()
|
|
22986
23185
|
}, async (args) => {
|
|
22987
|
-
|
|
23186
|
+
const _hasId = typeof args.id === "string" && args.id !== "";
|
|
23187
|
+
const _hasEmail = typeof args.email === "string" && args.email !== "";
|
|
23188
|
+
if (_hasId && _hasEmail)
|
|
22988
23189
|
throw new Error("Exactly one of 'id', 'email' must be provided, not both.");
|
|
22989
|
-
if (
|
|
22990
|
-
throw new Error("Exactly one of 'id', 'email' must be provided.");
|
|
22991
|
-
const url =
|
|
23190
|
+
if (!_hasId && !_hasEmail)
|
|
23191
|
+
throw new Error("Exactly one of 'id', 'email' must be provided (non-empty).");
|
|
23192
|
+
const url = _hasEmail ? `/v1/users/email/${encodeURIComponent(String(args.email))}` : `/v1/users/${encodeURIComponent(String(args.id))}`;
|
|
22992
23193
|
const result = await callApi("GET", url, void 0);
|
|
22993
23194
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
22994
23195
|
});
|
|
22995
|
-
server.tool("goodmem_users_me", "Get current user profile", {}, async (args) => {
|
|
23196
|
+
server.tool("goodmem_users_me", "Get current user profile\n\nRetrieves the authenticated user's profile information including email, display name, and creation time. This endpoint does not require any parameters as it automatically returns the caller's information.", {}, async (args) => {
|
|
22996
23197
|
const result = await callApi("GET", `/v1/users/me`, void 0);
|
|
22997
23198
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
22998
23199
|
});
|
|
@@ -23004,17 +23205,19 @@ async function healthCheck() {
|
|
|
23004
23205
|
return;
|
|
23005
23206
|
}
|
|
23006
23207
|
try {
|
|
23007
|
-
const url =
|
|
23208
|
+
const url = _buildUrl(goodmemBaseUrl, "/v1/system/info");
|
|
23008
23209
|
const resp = await fetch(url.toString(), {
|
|
23009
23210
|
headers: { "x-api-key": goodmemApiKey },
|
|
23010
|
-
signal: AbortSignal.timeout(1e4)
|
|
23211
|
+
signal: AbortSignal.timeout(1e4),
|
|
23212
|
+
// See callApi for the redirect-policy rationale.
|
|
23213
|
+
redirect: "error"
|
|
23011
23214
|
});
|
|
23012
23215
|
if (resp.ok) {
|
|
23013
23216
|
const info = await resp.json();
|
|
23014
23217
|
console.error(`[goodmem-mcp] Connected to ${goodmemBaseUrl} (server ${info.version ?? "unknown"}).`);
|
|
23015
23218
|
} else {
|
|
23016
23219
|
const text = await resp.text();
|
|
23017
|
-
console.error(`[goodmem-mcp] Health check failed (${resp.status}): ${text}`);
|
|
23220
|
+
console.error(`[goodmem-mcp] Health check failed (${resp.status}): ${_trimBody(text)}`);
|
|
23018
23221
|
}
|
|
23019
23222
|
} catch (err) {
|
|
23020
23223
|
const msg = err instanceof Error ? err.message : String(err);
|