@pairsystems/goodmem-mcp 0.1.15 → 0.1.16
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 +42 -0
- package/dist/index.js +95 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -167,11 +167,13 @@ Memory spaces are containers that associate a set of memories with an embedder.
|
|
|
167
167
|
| `goodmem_memories_batch_create` | Create multiple memories in a batch |
|
|
168
168
|
| `goodmem_memories_batch_delete` | Delete memories in batch |
|
|
169
169
|
| `goodmem_memories_batch_get` | Get multiple memories by ID |
|
|
170
|
+
| `goodmem_memories_content` | Download memory content |
|
|
170
171
|
| `goodmem_memories_create` | Create a new memory |
|
|
171
172
|
| `goodmem_memories_delete` | Delete a memory |
|
|
172
173
|
| `goodmem_memories_get` | Get a memory by ID |
|
|
173
174
|
| `goodmem_memories_list` | List memories in a space |
|
|
174
175
|
| `goodmem_memories_pages` | List memory page images |
|
|
176
|
+
| `goodmem_memories_pages_image` | Download memory page image content |
|
|
175
177
|
| `goodmem_memories_retrieve` | Advanced semantic memory retrieval with JSON |
|
|
176
178
|
|
|
177
179
|
### OCR
|
|
@@ -223,6 +225,45 @@ Memory spaces are containers that associate a set of memories with an embedder.
|
|
|
223
225
|
| `goodmem_ping_stream` | Stream ping probe results |
|
|
224
226
|
|
|
225
227
|
<!-- AUTO-GENERATED MCP TOOL TABLE END -->
|
|
228
|
+
## Binary content delivery
|
|
229
|
+
|
|
230
|
+
Two memory tools return raw binary payloads:
|
|
231
|
+
|
|
232
|
+
- `goodmem_memories_content` — original ingested bytes (text, image, PDF, …)
|
|
233
|
+
- `goodmem_memories_pages_image` — one rendered page image as bytes
|
|
234
|
+
|
|
235
|
+
The MCP server wraps the response in the appropriate MCP content block
|
|
236
|
+
based on the response MIME type:
|
|
237
|
+
|
|
238
|
+
| Server `Content-Type` | MCP block | What the agent gets |
|
|
239
|
+
|---|---|---|
|
|
240
|
+
| `image/*` | `image` block | base64 data + `mimeType`; Claude reads as vision input. |
|
|
241
|
+
| `text/*`, `application/json` | embedded `resource` with `text` | MIME-signaled text — preserves `text/markdown`, `text/x-python`, etc. |
|
|
242
|
+
| anything else (PDF, octet-stream, …) | embedded `resource` with `blob` | base64 blob + `mimeType`. Forward-compatible; current Claude models cannot natively render PDF bytes from an MCP block. |
|
|
243
|
+
|
|
244
|
+
### Size cap
|
|
245
|
+
|
|
246
|
+
Both tools accept an optional `max_inline_bytes` parameter (units:
|
|
247
|
+
bytes) so the agent can self-limit per call:
|
|
248
|
+
|
|
249
|
+
| Knob | Value | Notes |
|
|
250
|
+
|---|---|---|
|
|
251
|
+
| **Hard server ceiling** | 64 MB (67,108,864 bytes) | Baked into the MCP server source. Above this, the tool always errors regardless of `max_inline_bytes`. |
|
|
252
|
+
| **Default cap** | 8 MB (8,388,608 bytes) | Used when the agent doesn't pass `max_inline_bytes`. |
|
|
253
|
+
| **Per-call override** | `max_inline_bytes` argument | The agent can raise it up to the hard ceiling, or lower it for tighter context-budget control. |
|
|
254
|
+
|
|
255
|
+
When the payload exceeds the effective cap, the tool throws an error
|
|
256
|
+
naming both the actual size and the ceiling — the agent can read the
|
|
257
|
+
error, retry with a larger `max_inline_bytes`, and succeed. Above the
|
|
258
|
+
hard ceiling, the only path is to fetch the content via REST directly
|
|
259
|
+
(`GET /v1/memories/{id}/content` or `/pages/{idx}/image`) rather than
|
|
260
|
+
through MCP.
|
|
261
|
+
|
|
262
|
+
These constants are not currently configurable via environment
|
|
263
|
+
variables. If you need to tune them per deployment, edit
|
|
264
|
+
`_clients_gen/mcp/emitter.py` (`ABSOLUTE_MAX_INLINE_BYTES`,
|
|
265
|
+
`DEFAULT_MAX_INLINE_BYTES`) and regenerate.
|
|
266
|
+
|
|
226
267
|
## Auto-inference
|
|
227
268
|
|
|
228
269
|
When creating embedders, LLMs, or rerankers, provide `model_identifier` and the MCP server fills in `provider_type`, `endpoint_url`, dimensions, and other fields automatically from the built-in registry of 79 models. Inference happens client-side in the MCP layer before the request is sent.
|
|
@@ -247,3 +288,4 @@ For self-hosted or local providers (vLLM, TEI, Ollama, custom endpoints), creden
|
|
|
247
288
|
- [GoodMem on GitHub](https://github.com/PAIR-Systems-Inc/goodmem)
|
|
248
289
|
- [Python SDK on PyPI](https://pypi.org/project/goodmem/)
|
|
249
290
|
- [Claude Code Plugin](https://github.com/PAIR-Systems-Inc/goodmem-claude-code-plugin)
|
|
291
|
+
|
package/dist/index.js
CHANGED
|
@@ -20988,7 +20988,7 @@ 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 = "b5f17ba5a019b3b11860f70609a994b6d2ade6ad";
|
|
20992
20992
|
function isConfigured() {
|
|
20993
20993
|
return goodmemBaseUrl !== "";
|
|
20994
20994
|
}
|
|
@@ -21002,6 +21002,8 @@ function _buildUrl(base, path) {
|
|
|
21002
21002
|
return baseUrl;
|
|
21003
21003
|
}
|
|
21004
21004
|
var DEFAULT_TIMEOUT_MS = 6e4;
|
|
21005
|
+
var ABSOLUTE_MAX_INLINE_BYTES = 67108864;
|
|
21006
|
+
var DEFAULT_MAX_INLINE_BYTES = 8388608;
|
|
21005
21007
|
function _trimBody(text) {
|
|
21006
21008
|
return text.length > 500 ? text.slice(0, 500) + "...[truncated]" : text;
|
|
21007
21009
|
}
|
|
@@ -21044,6 +21046,37 @@ async function callApi(method, path, body, query, timeoutMs) {
|
|
|
21044
21046
|
}
|
|
21045
21047
|
return resp.text();
|
|
21046
21048
|
}
|
|
21049
|
+
async function callApiBinary(method, path, query, timeoutMs) {
|
|
21050
|
+
if (!isConfigured()) {
|
|
21051
|
+
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.");
|
|
21052
|
+
}
|
|
21053
|
+
const url = _buildUrl(goodmemBaseUrl, path);
|
|
21054
|
+
if (query) {
|
|
21055
|
+
for (const [k, v] of Object.entries(query)) {
|
|
21056
|
+
if (v !== void 0 && v !== null && v !== "")
|
|
21057
|
+
url.searchParams.set(k, v);
|
|
21058
|
+
}
|
|
21059
|
+
}
|
|
21060
|
+
const headers = {};
|
|
21061
|
+
if (goodmemApiKey !== "") {
|
|
21062
|
+
headers["x-api-key"] = goodmemApiKey;
|
|
21063
|
+
}
|
|
21064
|
+
const resp = await fetch(url.toString(), {
|
|
21065
|
+
method,
|
|
21066
|
+
headers,
|
|
21067
|
+
signal: AbortSignal.timeout(timeoutMs ?? DEFAULT_TIMEOUT_MS),
|
|
21068
|
+
// See callApi for the redirect-policy rationale.
|
|
21069
|
+
redirect: "error"
|
|
21070
|
+
});
|
|
21071
|
+
if (!resp.ok) {
|
|
21072
|
+
const text = await resp.text();
|
|
21073
|
+
throw new Error(`GoodMem API error ${resp.status}: ${_trimBody(text)}`);
|
|
21074
|
+
}
|
|
21075
|
+
const buf = await resp.arrayBuffer();
|
|
21076
|
+
const bytes = new Uint8Array(buf);
|
|
21077
|
+
const mimeType = (resp.headers.get("content-type") || "application/octet-stream").split(";")[0].trim();
|
|
21078
|
+
return { bytes, mimeType, sizeBytes: bytes.byteLength };
|
|
21079
|
+
}
|
|
21047
21080
|
async function callApiNdjson(method, path, body, query, timeoutSec) {
|
|
21048
21081
|
if (!isConfigured()) {
|
|
21049
21082
|
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.");
|
|
@@ -21843,7 +21876,7 @@ function autoInferFromRegistry(registryType, args) {
|
|
|
21843
21876
|
}
|
|
21844
21877
|
var server = new McpServer({
|
|
21845
21878
|
name: "goodmem",
|
|
21846
|
-
version: "0.1.
|
|
21879
|
+
version: "0.1.16"
|
|
21847
21880
|
});
|
|
21848
21881
|
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.", {
|
|
21849
21882
|
base_url: external_exports.string().describe("GoodMem server URL (e.g., https://your-server.example.com)"),
|
|
@@ -21925,7 +21958,7 @@ ${allModels.join("\n")}` }] };
|
|
|
21925
21958
|
});
|
|
21926
21959
|
server.tool("goodmem_client_info", "Return the MCP client package version and the goodmem server commit it was built against. No server connection required.", {}, async () => {
|
|
21927
21960
|
return { content: [{ type: "text", text: JSON.stringify({
|
|
21928
|
-
mcp_version: "0.1.
|
|
21961
|
+
mcp_version: "0.1.16",
|
|
21929
21962
|
based_on_goodmem_commit: BASED_ON_GOODMEM_COMMIT
|
|
21930
21963
|
}, null, 2) }] };
|
|
21931
21964
|
});
|
|
@@ -21972,14 +22005,14 @@ function registerTools() {
|
|
|
21972
22005
|
display_name: external_exports.string().describe("Human-readable policy name."),
|
|
21973
22006
|
description: external_exports.string().describe("Optional operator description.").optional(),
|
|
21974
22007
|
condition: external_exports.object({
|
|
21975
|
-
matchAll: external_exports.boolean().describe("When true, match every authenticated RetrieveMemory request. Defaults to false when omitted.").optional(),
|
|
22008
|
+
matchAll: external_exports.boolean().describe("When true, match every authenticated RetrieveMemory request. Must be omitted or false when anyOf contains clauses. Defaults to false when omitted.").optional(),
|
|
21976
22009
|
anyOf: external_exports.array(external_exports.object({
|
|
21977
22010
|
requestorUserIds: external_exports.array(external_exports.string()).describe("Authenticated requestor user UUID strings.").optional(),
|
|
21978
22011
|
apiKeyIds: external_exports.array(external_exports.string()).describe("API key UUID strings used to authenticate RetrieveMemory requests.").optional(),
|
|
21979
22012
|
spaceIds: external_exports.array(external_exports.string()).describe("Post-permission accessible space UUID strings.").optional(),
|
|
21980
22013
|
apiKeyLabelSelectors: external_exports.record(external_exports.string(), external_exports.string()).describe("Exact API-key label selectors that must all match.").optional(),
|
|
21981
22014
|
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
|
|
22015
|
+
}).passthrough()).describe("OR clauses used for scoped matching. Required and non-empty when matchAll is false or omitted. Must be omitted or empty when matchAll is true. Each clause must include at least one match dimension.").optional()
|
|
21983
22016
|
}).passthrough().describe("Policy match condition."),
|
|
21984
22017
|
active_from: external_exports.number().int().describe("Inclusive activation time in milliseconds since epoch.").optional(),
|
|
21985
22018
|
active_until: external_exports.number().int().describe("Exclusive deactivation time in milliseconds since epoch.").optional(),
|
|
@@ -22586,6 +22619,29 @@ function registerTools() {
|
|
|
22586
22619
|
const result = await callApi("POST", `/v1/memories:batchGet`, body);
|
|
22587
22620
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
22588
22621
|
});
|
|
22622
|
+
server.tool("goodmem_memories_content", "Download memory content\n\nReturns the original binary payload for a memory. The response uses the memory's stored content type when available. Returns 404 when the memory does not have inline content; clients can check originalContentRef from the metadata endpoint to locate external content.", {
|
|
22623
|
+
id: external_exports.string().describe("The UUID of the memory to download"),
|
|
22624
|
+
max_inline_bytes: external_exports.number().int().positive().describe("Maximum bytes to deliver inline as a content block. Defaults to 8388608. Hard server ceiling: 67108864. If the payload exceeds this cap the tool errors with the actual size \u2014 retry with a larger value (up to the ceiling) to receive it.").optional()
|
|
22625
|
+
}, async (args) => {
|
|
22626
|
+
if (typeof args.id !== "string" || args.id === "") {
|
|
22627
|
+
throw new Error("'id' must be a non-empty string");
|
|
22628
|
+
}
|
|
22629
|
+
const _cap = Math.min(args.max_inline_bytes ?? DEFAULT_MAX_INLINE_BYTES, ABSOLUTE_MAX_INLINE_BYTES);
|
|
22630
|
+
const { bytes: _bytes, mimeType: _mimeType, sizeBytes: _sizeBytes } = await callApiBinary("GET", `/v1/memories/${encodeURIComponent(String(args.id))}/content`, void 0);
|
|
22631
|
+
if (_sizeBytes > _cap) {
|
|
22632
|
+
throw new Error(`Content is ${_sizeBytes} bytes, exceeded the inline cap of ${_cap} bytes (cap = min(max_inline_bytes ?? ${DEFAULT_MAX_INLINE_BYTES}, server absolute maximum ${ABSOLUTE_MAX_INLINE_BYTES})). Retry with max_inline_bytes=${_sizeBytes} or higher (up to ${ABSOLUTE_MAX_INLINE_BYTES}) to receive it inline.`);
|
|
22633
|
+
}
|
|
22634
|
+
const _uri = `goodmem://v1/memories/${encodeURIComponent(String(args.id))}/content`;
|
|
22635
|
+
const _b64 = Buffer.from(_bytes).toString("base64");
|
|
22636
|
+
if (_mimeType.startsWith("image/")) {
|
|
22637
|
+
return { content: [{ type: "image", data: _b64, mimeType: _mimeType }] };
|
|
22638
|
+
}
|
|
22639
|
+
if (_mimeType.startsWith("text/") || _mimeType === "application/json") {
|
|
22640
|
+
const _text = new TextDecoder("utf-8").decode(_bytes);
|
|
22641
|
+
return { content: [{ type: "resource", resource: { uri: _uri, mimeType: _mimeType, text: _text } }] };
|
|
22642
|
+
}
|
|
22643
|
+
return { content: [{ type: "resource", resource: { uri: _uri, mimeType: _mimeType, blob: _b64 } }] };
|
|
22644
|
+
});
|
|
22589
22645
|
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.", {
|
|
22590
22646
|
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(),
|
|
22591
22647
|
space_id: external_exports.string().describe("ID of the space where this memory will be stored"),
|
|
@@ -22732,6 +22788,40 @@ function registerTools() {
|
|
|
22732
22788
|
const result = await callApi("GET", `/v1/memories/${encodeURIComponent(String(args.id))}/pages`, void 0, query);
|
|
22733
22789
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
22734
22790
|
});
|
|
22791
|
+
server.tool("goodmem_memories_pages_image", "Download memory page image content\n\nDownloads inline bytes for one page image. The page index is required. The optional dpi and content type query parameters act as rendition filters; if omitted, the server returns the unique rendition for that page or rejects ambiguous matches.", {
|
|
22792
|
+
id: external_exports.string().describe("Memory UUID"),
|
|
22793
|
+
page_index: external_exports.number().int().describe("0-based page index"),
|
|
22794
|
+
dpi: external_exports.number().int().describe("Optional rendition filter. If omitted, the unique page-image rendition for the page is returned; if multiple renditions exist, specify dpi and/or content_type.").optional(),
|
|
22795
|
+
content_type: external_exports.string().describe("Optional rendition filter. MIME type of the desired page image, such as image/png.").optional(),
|
|
22796
|
+
max_inline_bytes: external_exports.number().int().positive().describe("Maximum bytes to deliver inline as a content block. Defaults to 8388608. Hard server ceiling: 67108864. If the payload exceeds this cap the tool errors with the actual size \u2014 retry with a larger value (up to the ceiling) to receive it.").optional()
|
|
22797
|
+
}, async (args) => {
|
|
22798
|
+
if (typeof args.id !== "string" || args.id === "") {
|
|
22799
|
+
throw new Error("'id' must be a non-empty string");
|
|
22800
|
+
}
|
|
22801
|
+
if (typeof args.page_index !== "number" || !Number.isFinite(args.page_index)) {
|
|
22802
|
+
throw new Error("'page_index' must be a finite number");
|
|
22803
|
+
}
|
|
22804
|
+
const query = {};
|
|
22805
|
+
if (args.dpi !== void 0)
|
|
22806
|
+
query["dpi"] = String(args.dpi);
|
|
22807
|
+
if (args.content_type !== void 0)
|
|
22808
|
+
query["contentType"] = String(args.content_type);
|
|
22809
|
+
const _cap = Math.min(args.max_inline_bytes ?? DEFAULT_MAX_INLINE_BYTES, ABSOLUTE_MAX_INLINE_BYTES);
|
|
22810
|
+
const { bytes: _bytes, mimeType: _mimeType, sizeBytes: _sizeBytes } = await callApiBinary("GET", `/v1/memories/${encodeURIComponent(String(args.id))}/pages/${encodeURIComponent(String(args.page_index))}/image`, query);
|
|
22811
|
+
if (_sizeBytes > _cap) {
|
|
22812
|
+
throw new Error(`Content is ${_sizeBytes} bytes, exceeded the inline cap of ${_cap} bytes (cap = min(max_inline_bytes ?? ${DEFAULT_MAX_INLINE_BYTES}, server absolute maximum ${ABSOLUTE_MAX_INLINE_BYTES})). Retry with max_inline_bytes=${_sizeBytes} or higher (up to ${ABSOLUTE_MAX_INLINE_BYTES}) to receive it inline.`);
|
|
22813
|
+
}
|
|
22814
|
+
const _uri = `goodmem://v1/memories/${encodeURIComponent(String(args.id))}/pages/${encodeURIComponent(String(args.page_index))}/image`;
|
|
22815
|
+
const _b64 = Buffer.from(_bytes).toString("base64");
|
|
22816
|
+
if (_mimeType.startsWith("image/")) {
|
|
22817
|
+
return { content: [{ type: "image", data: _b64, mimeType: _mimeType }] };
|
|
22818
|
+
}
|
|
22819
|
+
if (_mimeType.startsWith("text/") || _mimeType === "application/json") {
|
|
22820
|
+
const _text = new TextDecoder("utf-8").decode(_bytes);
|
|
22821
|
+
return { content: [{ type: "resource", resource: { uri: _uri, mimeType: _mimeType, text: _text } }] };
|
|
22822
|
+
}
|
|
22823
|
+
return { content: [{ type: "resource", resource: { uri: _uri, mimeType: _mimeType, blob: _b64 } }] };
|
|
22824
|
+
});
|
|
22735
22825
|
server.tool("goodmem_memories_retrieve", "Performs a streaming semantic search across one or more memory spaces and returns matching chunks ranked by relevance as well as LLM-postprocessed results (like summarization, question-answering, etc.).", {
|
|
22736
22826
|
message: external_exports.string().describe("Primary query/message for semantic search."),
|
|
22737
22827
|
context: external_exports.array(external_exports.object({
|