@pairsystems/goodmem-mcp 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +52 -35
  2. package/dist/index.js +48 -26
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -2,43 +2,55 @@
2
2
 
3
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
- 41 tools across 10 namespaces: embedders, LLMs, rerankers, spaces, memories, retrieval, OCR, users, API keys, and system administration.
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
- ## Installation
12
+ ## Configuration
13
13
 
14
- ### Claude Desktop
14
+ ### What you need to configure
15
15
 
16
- Add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):
16
+ **GoodMem credentials** (required):
17
17
 
18
- ```json
19
- {
20
- "mcpServers": {
21
- "goodmem": {
22
- "command": "npx",
23
- "args": ["-y", "@pairsystems/goodmem-mcp"],
24
- "env": {
25
- "GOODMEM_BASE_URL": "https://your-server.example.com",
26
- "GOODMEM_API_KEY": "gm_..."
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
- ### Claude Code
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
- claude mcp add goodmem -- env GOODMEM_BASE_URL=https://your-server.example.com env GOODMEM_API_KEY=gm_... npx -y @pairsystems/goodmem-mcp
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
- Or add to `.claude/mcp.json` in your project:
49
+ **3. MCP client JSON config**
50
+
51
+ Add an `env` block in your MCP client's JSON configuration:
40
52
 
41
- ```json
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
- ### VS Code / Cursor / Other MCP clients
70
+ The JSON file location depends on your MCP client:
57
71
 
58
- Use the same `npx -y @pairsystems/goodmem-mcp` command pattern with `GOODMEM_BASE_URL` and `GOODMEM_API_KEY` set in the environment.
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
- ### Global install
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
- npm install -g @pairsystems/goodmem-mcp
64
- goodmem-mcp
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
- ## Runtime configuration
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 with your API key.`);
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
- const camelToSnake = {
21893
- providerType: "provider_type",
21894
- endpointUrl: "endpoint_url",
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[key] ?? key;
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.1"
21922
+ version: "0.1.4"
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.string()).describe("Provider-specific client configuration as flexible JSON structure").optional(),
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.string()).describe("Update provider-specific client configuration (replaces entire config; no merging)").optional(),
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.string()).describe("Additional metadata for the memory").optional(),
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.string()).describe("Configuration parameters for the post-processor. Fields depend on the selected processor; see the linked documentation for the built-in ChatPostProcessor schema.").optional()
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
- const result = await callApi("GET", `/v1/users/${encodeURIComponent(String(args.id))}`, void 0);
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.2",
3
+ "version": "0.1.4",
4
4
  "description": "GoodMem MCP server \u2014 exposes GoodMem API as MCP tools",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",