@pairsystems/goodmem-mcp 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -36
- package/dist/index.js +48 -26
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -1,44 +1,56 @@
|
|
|
1
1
|
# @pairsystems/goodmem-mcp
|
|
2
2
|
|
|
3
|
-
[GoodMem](https://
|
|
3
|
+
[GoodMem](https://goodmem.ai/) MCP server — gives AI agents direct access to GoodMem memory infrastructure via the [Model Context Protocol](https://modelcontextprotocol.io).
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Tools across namespaces: embedders, LLMs, rerankers, spaces, memories, retrieval, OCR, users, API keys, and system administration.
|
|
6
6
|
|
|
7
7
|
## Prerequisites
|
|
8
8
|
|
|
9
9
|
- A running [GoodMem](https://github.com/PAIR-Systems-Inc/goodmem) server
|
|
10
10
|
- Node.js 18+
|
|
11
11
|
|
|
12
|
-
##
|
|
12
|
+
## Configuration
|
|
13
13
|
|
|
14
|
-
###
|
|
14
|
+
### What you need to configure
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
**GoodMem credentials** (required):
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
```
|
|
18
|
+
- `GOODMEM_BASE_URL` — GoodMem server URL (e.g., `https://your-server.example.com`)
|
|
19
|
+
- `GOODMEM_API_KEY` — GoodMem API key (starts with `gm_`)
|
|
20
|
+
|
|
21
|
+
**TLS / self-signed certificates** (optional):
|
|
22
|
+
|
|
23
|
+
This is an advanced topic. If you do not even know what that is, skip this section.
|
|
24
|
+
|
|
25
|
+
If the GoodMem server uses self-signed CA certificates, set one and only one of the following:
|
|
26
|
+
|
|
27
|
+
- `NODE_EXTRA_CA_CERTS=/path/to/rootCA.pem` — adds the CA to the trusted store. More secure, but requires you to copy the certificate file from the GoodMem server to the machine where the MCP server is running.
|
|
28
|
+
- `NODE_TLS_REJECT_UNAUTHORIZED=0` — disables certificate verification entirely. Easy and hassle-free, but not recommended for production.
|
|
32
29
|
|
|
33
|
-
###
|
|
30
|
+
### How to configure
|
|
31
|
+
|
|
32
|
+
**1. In English from chat** (GoodMem credentials only; TLS variables cannot be set this way)
|
|
33
|
+
|
|
34
|
+
Just tell the MCP server your server details — it will call `goodmem_configure` automatically:
|
|
35
|
+
|
|
36
|
+
> "Configure GoodMem with base URL https://my-server.com and API key gm_abc123"
|
|
37
|
+
|
|
38
|
+
Credentials persist for the session. You can reconfigure anytime to switch servers.
|
|
39
|
+
|
|
40
|
+
**2. Shell export** (before launching the MCP server; works for all configurations)
|
|
34
41
|
|
|
35
42
|
```bash
|
|
36
|
-
|
|
43
|
+
export GOODMEM_BASE_URL="https://your-server.example.com"
|
|
44
|
+
export GOODMEM_API_KEY="gm_..."
|
|
45
|
+
export NODE_EXTRA_CA_CERTS="/path/to/rootCA.pem" # only if needed for self-signed certs
|
|
46
|
+
export NODE_TLS_REJECT_UNAUTHORIZED=0 # only if you want to skip cert verification
|
|
37
47
|
```
|
|
38
48
|
|
|
39
|
-
|
|
49
|
+
**3. MCP client JSON config**
|
|
50
|
+
|
|
51
|
+
Add an `env` block in your MCP client's JSON configuration:
|
|
40
52
|
|
|
41
|
-
```
|
|
53
|
+
```jsonc
|
|
42
54
|
{
|
|
43
55
|
"mcpServers": {
|
|
44
56
|
"goodmem": {
|
|
@@ -46,33 +58,38 @@ Or add to `.claude/mcp.json` in your project:
|
|
|
46
58
|
"args": ["-y", "@pairsystems/goodmem-mcp"],
|
|
47
59
|
"env": {
|
|
48
60
|
"GOODMEM_BASE_URL": "https://your-server.example.com",
|
|
49
|
-
"GOODMEM_API_KEY": "gm_..."
|
|
61
|
+
"GOODMEM_API_KEY": "gm_...",
|
|
62
|
+
"NODE_EXTRA_CA_CERTS": "/path/to/rootCA.pem", // only if needed for self-signed certs
|
|
63
|
+
"NODE_TLS_REJECT_UNAUTHORIZED": "0" // only if you want to skip cert verification
|
|
50
64
|
}
|
|
51
65
|
}
|
|
52
66
|
}
|
|
53
67
|
}
|
|
54
68
|
```
|
|
55
69
|
|
|
56
|
-
|
|
70
|
+
The JSON file location depends on your MCP client:
|
|
57
71
|
|
|
58
|
-
|
|
72
|
+
- **Claude Desktop**: `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows)
|
|
73
|
+
- **Claude Code**: `.claude/mcp.json` in your project
|
|
74
|
+
- **VS Code / Cursor**: see your client's MCP server configuration docs
|
|
59
75
|
|
|
60
|
-
|
|
76
|
+
**4. App launcher CLI** (only works for apps that support configuration environment variables in is launcher)
|
|
77
|
+
|
|
78
|
+
For example, in Claude Code:
|
|
61
79
|
|
|
62
80
|
```bash
|
|
63
|
-
|
|
64
|
-
|
|
81
|
+
claude mcp add goodmem -- \
|
|
82
|
+
env GOODMEM_BASE_URL=https://your-server.example.com \
|
|
83
|
+
env GOODMEM_API_KEY=gm_... \
|
|
84
|
+
env NODE_EXTRA_CA_CERTS=/path/to/rootCA.pem
|
|
65
85
|
```
|
|
66
86
|
|
|
67
|
-
##
|
|
68
|
-
|
|
69
|
-
If you can't set environment variables before launch, call `goodmem_configure` from chat:
|
|
87
|
+
## Global install
|
|
70
88
|
|
|
89
|
+
```bash
|
|
90
|
+
npm install -g @pairsystems/goodmem-mcp
|
|
91
|
+
goodmem-mcp
|
|
71
92
|
```
|
|
72
|
-
Configure GoodMem with base URL https://my-server.com and API key gm_abc123
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
Credentials set this way persist for the session and override environment variables.
|
|
76
93
|
|
|
77
94
|
## Quick start
|
|
78
95
|
|
package/dist/index.js
CHANGED
|
@@ -21009,6 +21009,7 @@ async function callApi(method, path, body, query) {
|
|
|
21009
21009
|
const resp = await fetch(url.toString(), {
|
|
21010
21010
|
method,
|
|
21011
21011
|
headers,
|
|
21012
|
+
signal: AbortSignal.timeout(3e4),
|
|
21012
21013
|
...body ? { body: JSON.stringify(body) } : {}
|
|
21013
21014
|
});
|
|
21014
21015
|
if (!resp.ok) {
|
|
@@ -21040,6 +21041,7 @@ async function callApiNdjson(method, path, body, query) {
|
|
|
21040
21041
|
const resp = await fetch(url.toString(), {
|
|
21041
21042
|
method,
|
|
21042
21043
|
headers,
|
|
21044
|
+
signal: AbortSignal.timeout(2e5),
|
|
21043
21045
|
...body ? { body: JSON.stringify(body) } : {}
|
|
21044
21046
|
});
|
|
21045
21047
|
if (!resp.ok) {
|
|
@@ -21879,7 +21881,7 @@ function checkCredentialRequired(args) {
|
|
|
21879
21881
|
return;
|
|
21880
21882
|
}
|
|
21881
21883
|
if (requiredHosts.includes(host)) {
|
|
21882
|
-
throw new Error(`Provider '${provider}' at '${endpointUrl}' requires credentials. Pass credentials
|
|
21884
|
+
throw new Error(`Provider '${provider}' at '${endpointUrl}' requires credentials. Pass credentials: { kind: "CREDENTIAL_KIND_API_KEY", apiKey: { inlineSecret: "your-key-here" } }`);
|
|
21883
21885
|
}
|
|
21884
21886
|
}
|
|
21885
21887
|
function autoInferFromRegistry(registryType, args) {
|
|
@@ -21889,18 +21891,12 @@ function autoInferFromRegistry(registryType, args) {
|
|
|
21889
21891
|
return result;
|
|
21890
21892
|
const registry2 = MODEL_REGISTRY[registryType] ?? {};
|
|
21891
21893
|
const entry = registry2[modelId];
|
|
21892
|
-
|
|
21893
|
-
|
|
21894
|
-
|
|
21895
|
-
dimensionality: "dimensionality",
|
|
21896
|
-
distributionType: "distribution_type",
|
|
21897
|
-
supportedModalities: "supported_modalities",
|
|
21898
|
-
maxSequenceLength: "max_sequence_length",
|
|
21899
|
-
maxContextLength: "max_context_length"
|
|
21900
|
-
};
|
|
21894
|
+
function camelToSnake(s) {
|
|
21895
|
+
return s.replace(/([A-Z]+)([A-Z][a-z]{2,})/g, "$1_$2").replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase();
|
|
21896
|
+
}
|
|
21901
21897
|
if (entry) {
|
|
21902
21898
|
for (const [key, value] of Object.entries(entry)) {
|
|
21903
|
-
const snakeKey = camelToSnake
|
|
21899
|
+
const snakeKey = camelToSnake(key);
|
|
21904
21900
|
if (key === "dimensions") {
|
|
21905
21901
|
const dims = value;
|
|
21906
21902
|
if (dims?.default !== void 0 && result.dimensionality === void 0) {
|
|
@@ -21923,18 +21919,22 @@ function autoInferFromRegistry(registryType, args) {
|
|
|
21923
21919
|
}
|
|
21924
21920
|
var server = new McpServer({
|
|
21925
21921
|
name: "goodmem",
|
|
21926
|
-
version: "0.1.
|
|
21922
|
+
version: "0.1.3"
|
|
21927
21923
|
});
|
|
21928
21924
|
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.", {
|
|
21929
21925
|
base_url: external_exports.string().describe("GoodMem server URL (e.g., https://your-server.example.com)"),
|
|
21930
21926
|
api_key: external_exports.string().describe("GoodMem API key (starts with gm_)")
|
|
21931
21927
|
}, async (args) => {
|
|
21928
|
+
if (!/^https?:\/\//i.test(args.base_url)) {
|
|
21929
|
+
return { content: [{ type: "text", text: `base_url must include a scheme (e.g., http://${args.base_url} or https://${args.base_url}).` }] };
|
|
21930
|
+
}
|
|
21932
21931
|
goodmemBaseUrl = args.base_url;
|
|
21933
21932
|
goodmemApiKey = args.api_key;
|
|
21934
21933
|
try {
|
|
21935
21934
|
const url = new URL("/v1/system/info", goodmemBaseUrl);
|
|
21936
21935
|
const resp = await fetch(url.toString(), {
|
|
21937
|
-
headers: { "x-api-key": goodmemApiKey }
|
|
21936
|
+
headers: { "x-api-key": goodmemApiKey },
|
|
21937
|
+
signal: AbortSignal.timeout(1e4)
|
|
21938
21938
|
});
|
|
21939
21939
|
if (!resp.ok) {
|
|
21940
21940
|
const text = await resp.text();
|
|
@@ -22121,13 +22121,18 @@ function registerTools() {
|
|
|
22121
22121
|
});
|
|
22122
22122
|
server.tool("goodmem_embedders_list", "List embedders", {
|
|
22123
22123
|
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(),
|
|
22124
|
-
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()
|
|
22124
|
+
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(),
|
|
22125
|
+
labels: external_exports.record(external_exports.string(), external_exports.string()).describe("Filter by labels. Each key-value pair is sent as a label.<key>=<value> query parameter.").optional()
|
|
22125
22126
|
}, async (args) => {
|
|
22126
22127
|
const query = {};
|
|
22127
22128
|
if (args.owner_id !== void 0)
|
|
22128
22129
|
query["ownerId"] = String(args.owner_id);
|
|
22129
22130
|
if (args.provider_type !== void 0)
|
|
22130
22131
|
query["providerType"] = String(args.provider_type);
|
|
22132
|
+
if (args.labels) {
|
|
22133
|
+
for (const [k, v] of Object.entries(args.labels))
|
|
22134
|
+
query[`label.${k}`] = v;
|
|
22135
|
+
}
|
|
22131
22136
|
const result = await callApi("GET", `/v1/embedders`, void 0, query);
|
|
22132
22137
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
22133
22138
|
});
|
|
@@ -22217,7 +22222,7 @@ function registerTools() {
|
|
|
22217
22222
|
stopSequences: external_exports.any().describe("Generation stop sequences (\u226410 sequences; each \u2264100 chars; generation halts on exact match)").optional()
|
|
22218
22223
|
}).passthrough().describe("Default sampling parameters for generation requests").optional(),
|
|
22219
22224
|
max_context_length: external_exports.number().int().describe("Maximum context window size in tokens").optional(),
|
|
22220
|
-
client_config: external_exports.record(external_exports.string(), external_exports.
|
|
22225
|
+
client_config: external_exports.record(external_exports.string(), external_exports.unknown()).describe("Provider-specific client configuration as flexible JSON structure").optional(),
|
|
22221
22226
|
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(),
|
|
22222
22227
|
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()
|
|
22223
22228
|
}, async (args) => {
|
|
@@ -22275,13 +22280,18 @@ function registerTools() {
|
|
|
22275
22280
|
});
|
|
22276
22281
|
server.tool("goodmem_llms_list", "List LLMs", {
|
|
22277
22282
|
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(),
|
|
22278
|
-
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()
|
|
22283
|
+
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(),
|
|
22284
|
+
labels: external_exports.record(external_exports.string(), external_exports.string()).describe("Filter by labels. Each key-value pair is sent as a label.<key>=<value> query parameter.").optional()
|
|
22279
22285
|
}, async (args) => {
|
|
22280
22286
|
const query = {};
|
|
22281
22287
|
if (args.owner_id !== void 0)
|
|
22282
22288
|
query["ownerId"] = String(args.owner_id);
|
|
22283
22289
|
if (args.provider_type !== void 0)
|
|
22284
22290
|
query["providerType"] = String(args.provider_type);
|
|
22291
|
+
if (args.labels) {
|
|
22292
|
+
for (const [k, v] of Object.entries(args.labels))
|
|
22293
|
+
query[`label.${k}`] = v;
|
|
22294
|
+
}
|
|
22285
22295
|
const result = await callApi("GET", `/v1/llms`, void 0, query);
|
|
22286
22296
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
22287
22297
|
});
|
|
@@ -22319,7 +22329,7 @@ function registerTools() {
|
|
|
22319
22329
|
stopSequences: external_exports.any().describe("Generation stop sequences (\u226410 sequences; each \u2264100 chars; generation halts on exact match)").optional()
|
|
22320
22330
|
}).passthrough().describe("Update default sampling parameters").optional(),
|
|
22321
22331
|
max_context_length: external_exports.number().int().describe("Update maximum context window size in tokens").optional(),
|
|
22322
|
-
client_config: external_exports.record(external_exports.string(), external_exports.
|
|
22332
|
+
client_config: external_exports.record(external_exports.string(), external_exports.unknown()).describe("Update provider-specific client configuration (replaces entire config; no merging)").optional(),
|
|
22323
22333
|
replace_labels: external_exports.record(external_exports.string(), external_exports.string()).describe("Replace all existing labels with this set. Empty map clears all labels. Cannot be used with mergeLabels.").optional(),
|
|
22324
22334
|
merge_labels: external_exports.record(external_exports.string(), external_exports.string()).describe("Merge with existing labels: upserts with overwrite. Labels not mentioned are preserved. Cannot be used with replaceLabels.").optional()
|
|
22325
22335
|
}, async (args) => {
|
|
@@ -22400,14 +22410,13 @@ function registerTools() {
|
|
|
22400
22410
|
original_content_b64: external_exports.string().describe("Original content as base64-encoded binary data (use either this or originalContent)").optional(),
|
|
22401
22411
|
original_content_ref: external_exports.string().describe("Reference to external content location").optional(),
|
|
22402
22412
|
content_type: external_exports.string().describe("MIME type of the content"),
|
|
22403
|
-
metadata: external_exports.record(external_exports.string(), external_exports.
|
|
22413
|
+
metadata: external_exports.record(external_exports.string(), external_exports.unknown()).describe("Additional metadata for the memory").optional(),
|
|
22404
22414
|
chunking_config: external_exports.object({
|
|
22405
22415
|
none: external_exports.any().describe("No chunking strategy - preserve original content as single unit").optional(),
|
|
22406
22416
|
recursive: external_exports.any().describe("Recursive hierarchical chunking strategy with configurable separators").optional(),
|
|
22407
22417
|
sentence: external_exports.any().describe("Sentence-based chunking strategy with language detection").optional()
|
|
22408
22418
|
}).passthrough().describe("Chunking strategy for this memory (if not provided, uses space default)").optional(),
|
|
22409
|
-
extract_page_images: external_exports.boolean().describe("Optional hint to extract page images for eligible document types (for example, PDFs)").optional()
|
|
22410
|
-
file_field: external_exports.string().describe("Optional multipart file field name to bind binary content; required when multiple files are uploaded in a batch multipart request.").optional()
|
|
22419
|
+
extract_page_images: external_exports.boolean().describe("Optional hint to extract page images for eligible document types (for example, PDFs)").optional()
|
|
22411
22420
|
}, async (args) => {
|
|
22412
22421
|
const body = {};
|
|
22413
22422
|
if (args.memory_id !== void 0)
|
|
@@ -22428,8 +22437,6 @@ function registerTools() {
|
|
|
22428
22437
|
body["chunkingConfig"] = args.chunking_config;
|
|
22429
22438
|
if (args.extract_page_images !== void 0)
|
|
22430
22439
|
body["extractPageImages"] = args.extract_page_images;
|
|
22431
|
-
if (args.file_field !== void 0)
|
|
22432
|
-
body["fileField"] = args.file_field;
|
|
22433
22440
|
const result = await callApi("POST", `/v1/memories`, body);
|
|
22434
22441
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
22435
22442
|
});
|
|
@@ -22530,7 +22537,7 @@ function registerTools() {
|
|
|
22530
22537
|
}).passthrough().describe("Optional request-level HNSW tuning overrides. Advanced usage; available on POST retrieve.").optional(),
|
|
22531
22538
|
post_processor: external_exports.object({
|
|
22532
22539
|
name: external_exports.string().describe("Fully qualified factory class name of the post-processor to apply."),
|
|
22533
|
-
config: external_exports.record(external_exports.string(), external_exports.
|
|
22540
|
+
config: external_exports.record(external_exports.string(), external_exports.unknown()).describe("Configuration parameters for the post-processor. Fields depend on the selected processor; see the linked documentation for the built-in ChatPostProcessor schema.").optional()
|
|
22534
22541
|
}).passthrough().describe("Optional post-processor configuration to transform retrieval results.").optional()
|
|
22535
22542
|
}, async (args) => {
|
|
22536
22543
|
const body = {};
|
|
@@ -22643,13 +22650,18 @@ function registerTools() {
|
|
|
22643
22650
|
});
|
|
22644
22651
|
server.tool("goodmem_rerankers_list", "List rerankers", {
|
|
22645
22652
|
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(),
|
|
22646
|
-
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()
|
|
22653
|
+
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(),
|
|
22654
|
+
labels: external_exports.record(external_exports.string(), external_exports.string()).describe("Filter by labels. Each key-value pair is sent as a label.<key>=<value> query parameter.").optional()
|
|
22647
22655
|
}, async (args) => {
|
|
22648
22656
|
const query = {};
|
|
22649
22657
|
if (args.owner_id !== void 0)
|
|
22650
22658
|
query["ownerId"] = String(args.owner_id);
|
|
22651
22659
|
if (args.provider_type !== void 0)
|
|
22652
22660
|
query["providerType"] = String(args.provider_type);
|
|
22661
|
+
if (args.labels) {
|
|
22662
|
+
for (const [k, v] of Object.entries(args.labels))
|
|
22663
|
+
query[`label.${k}`] = v;
|
|
22664
|
+
}
|
|
22653
22665
|
const result = await callApi("GET", `/v1/rerankers`, void 0, query);
|
|
22654
22666
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
22655
22667
|
});
|
|
@@ -22750,7 +22762,8 @@ function registerTools() {
|
|
|
22750
22762
|
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(),
|
|
22751
22763
|
next_token: external_exports.string().describe("Pagination token for retrieving the next set of results").optional(),
|
|
22752
22764
|
sort_by: external_exports.string().describe("Field to sort by: 'created_time', 'updated_time', or 'name' (default: 'created_time'). Unsupported values return INVALID_ARGUMENT.").optional(),
|
|
22753
|
-
sort_order: external_exports.enum(["ASCENDING", "DESCENDING", "SORT_ORDER_UNSPECIFIED"]).describe("Sort order (ASCENDING or DESCENDING, default: DESCENDING)").optional()
|
|
22765
|
+
sort_order: external_exports.enum(["ASCENDING", "DESCENDING", "SORT_ORDER_UNSPECIFIED"]).describe("Sort order (ASCENDING or DESCENDING, default: DESCENDING)").optional(),
|
|
22766
|
+
labels: external_exports.record(external_exports.string(), external_exports.string()).describe("Filter by labels. Each key-value pair is sent as a label.<key>=<value> query parameter.").optional()
|
|
22754
22767
|
}, async (args) => {
|
|
22755
22768
|
const query = {};
|
|
22756
22769
|
if (args.owner_id !== void 0)
|
|
@@ -22765,6 +22778,10 @@ function registerTools() {
|
|
|
22765
22778
|
query["sortBy"] = String(args.sort_by);
|
|
22766
22779
|
if (args.sort_order !== void 0)
|
|
22767
22780
|
query["sortOrder"] = String(args.sort_order);
|
|
22781
|
+
if (args.labels) {
|
|
22782
|
+
for (const [k, v] of Object.entries(args.labels))
|
|
22783
|
+
query[`label.${k}`] = v;
|
|
22784
|
+
}
|
|
22768
22785
|
const result = await callApi("GET", `/v1/spaces`, void 0, query);
|
|
22769
22786
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
22770
22787
|
});
|
|
@@ -22799,7 +22816,12 @@ function registerTools() {
|
|
|
22799
22816
|
id: external_exports.string().describe("The unique identifier of the user to retrieve").optional(),
|
|
22800
22817
|
email: external_exports.string().describe("The email address of the user to retrieve").optional()
|
|
22801
22818
|
}, async (args) => {
|
|
22802
|
-
|
|
22819
|
+
if (args.id && args.email)
|
|
22820
|
+
throw new Error("Exactly one of 'id', 'email' must be provided, not both.");
|
|
22821
|
+
if (!args.id && !args.email)
|
|
22822
|
+
throw new Error("Exactly one of 'id', 'email' must be provided.");
|
|
22823
|
+
const url = args.email ? `/v1/users/email/${encodeURIComponent(String(args.email))}` : `/v1/users/${encodeURIComponent(String(args.id))}`;
|
|
22824
|
+
const result = await callApi("GET", url, void 0);
|
|
22803
22825
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
22804
22826
|
});
|
|
22805
22827
|
server.tool("goodmem_users_me", "Get current user profile", {}, async (args) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pairsystems/goodmem-mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "GoodMem MCP server \u2014 exposes GoodMem API as MCP tools",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
},
|
|
10
10
|
"files": [
|
|
11
11
|
"dist/index.js",
|
|
12
|
-
"README.md"
|
|
12
|
+
"README.md",
|
|
13
|
+
"LICENSE"
|
|
13
14
|
],
|
|
14
15
|
"scripts": {
|
|
15
16
|
"build": "tsc && esbuild dist/index.js --bundle --platform=node --format=esm --outfile=dist/index.js --allow-overwrite",
|