@pragmatic-growth/memory-mcp 3.4.0 → 3.5.0

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 CHANGED
@@ -37,16 +37,68 @@ For clients that only support stdio (Raycast, local tools):
37
37
  }
38
38
  ```
39
39
 
40
- ## Operating Mode
40
+ ## Operating Modes
41
41
 
42
- PG-Memory provides a **personal knowledge base** focused on semantic search and content retrieval:
43
- - **Core Knowledge**: search, get content, list content, list categories, health check, smart gap logging
44
- - **AI-Powered**: Automatic question quality evaluation, deduplication, and priority assignment
42
+ PG-Memory supports two operating modes:
45
43
 
46
- All tools are available by default - no special flags required.
44
+ ### Read-Only Mode (default, 6 tools)
45
+ Semantic search, content retrieval, and smart gap logging. All tools are available by default.
46
+
47
+ ### Admin Mode (14 tools)
48
+ Full CRUD operations on articles and Q&A records. Enable with:
49
+ ```json
50
+ {
51
+ "env": {
52
+ "CLERK_API_KEY": "your-api-key",
53
+ "PG_MEMORY_ADMIN": "true"
54
+ }
55
+ }
56
+ ```
47
57
 
48
58
  ## Installation
49
59
 
60
+ ### For Claude Code (`.mcp.json`)
61
+
62
+ Add to your project's `.mcp.json` or `~/.claude/.mcp.json`:
63
+
64
+ ```json
65
+ {
66
+ "mcpServers": {
67
+ "pg-memory": {
68
+ "type": "stdio",
69
+ "command": "npx",
70
+ "args": ["-y", "@pragmatic-growth/memory-mcp"],
71
+ "env": {
72
+ "CLERK_API_KEY": "your-api-key-here",
73
+ "PG_MEMORY_ADMIN": "true"
74
+ }
75
+ }
76
+ }
77
+ }
78
+ ```
79
+
80
+ Remove `PG_MEMORY_ADMIN` for read-only mode (6 tools).
81
+
82
+ ### For Claude Desktop (`claude_desktop_config.json`)
83
+
84
+ macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
85
+ Windows: `%APPDATA%\Claude\claude_desktop_config.json`
86
+
87
+ ```json
88
+ {
89
+ "mcpServers": {
90
+ "pg-memory": {
91
+ "command": "npx",
92
+ "args": ["-y", "@pragmatic-growth/memory-mcp"],
93
+ "env": {
94
+ "CLERK_API_KEY": "your-api-key-here",
95
+ "PG_MEMORY_ADMIN": "true"
96
+ }
97
+ }
98
+ }
99
+ }
100
+ ```
101
+
50
102
  ### For Raycast
51
103
 
52
104
  ```json
@@ -79,28 +131,41 @@ CLERK_API_KEY=your-key memory-mcp
79
131
  |----------|----------|-------------|
80
132
  | `CLERK_API_KEY` | Yes | Clerk API key for authentication |
81
133
  | `MCP_SERVER_URL` | No | Custom server URL (default: production) |
134
+ | `PG_MEMORY_ADMIN` | No | Set to `true` to enable admin tools (default: read-only) |
82
135
 
83
- ## Available Tools (7 Total)
136
+ ## Available Tools
84
137
 
85
- The tool list below reflects the current token-efficient MCP architecture with AI-powered question handling.
138
+ ### Read Tools (6, always available)
86
139
 
87
140
  | Tool | Description |
88
141
  |------|-------------|
89
- | `__IMPORTANT` | Workflow instructions (3-layer pattern: search get_content smart_log_gap) |
90
- | `search_knowledge` | Semantic search across articles (returns metadata only - use get_content for full text) |
91
- | `get_content` | Retrieve full article content by ID |
92
- | `smart_log_gap` | AI-evaluated gap logging with deduplication, quality filtering, and priority assignment |
93
- | `list_content` | Browse articles with filtering (by type, category, status) |
94
- | `list_categories` | List all knowledge base categories |
95
- | `health_check` | System health status and statistics |
142
+ | `search` | Unified semantic search across articles + approved Q&A. Returns lightweight results (ID, type, title/question, similarity, date). |
143
+ | `get_article` | Retrieve full article content by ID |
144
+ | `get_qa` | Retrieve a Q&A record by ID (question, answer, status, metadata) |
145
+ | `log_gap` | Smart gap logging with deduplication (>85%), answer detection (>70%), AI quality filtering, and auto-priority |
146
+ | `list_categories` | List all knowledge base categories with content counts |
147
+ | `health_check` | System health status including article counts, Q&A counts, and connection pool stats |
148
+
149
+ ### Admin Tools (8, requires `PG_MEMORY_ADMIN=true`)
150
+
151
+ | Tool | Description |
152
+ |------|-------------|
153
+ | `list_gaps` | List unanswered knowledge gaps (Q&A with status='gap') |
154
+ | `create_article` | Create a new article with auto-generated embedding |
155
+ | `update_article` | Update an existing article with optional re-embedding |
156
+ | `create_qa` | Create a Q&A record with full control over status and fields |
157
+ | `draft_qa_answer` | Draft an answer for a Q&A record (sets status to 'draft') |
158
+ | `approve_qa` | Approve a Q&A record and generate search embedding |
159
+ | `delete_article` | Permanently delete an article |
160
+ | `delete_qa` | Permanently delete a Q&A record |
96
161
 
97
162
  ## Architecture Philosophy
98
163
 
99
- **Token Efficiency**: Following claude-mem's pattern, `search_knowledge` returns only metadata (ID, title, similarity). Use `get_content` to fetch full article text on-demand.
164
+ **Unified Search**: `search` queries both articles and approved Q&A in parallel with graceful degradation. Returns metadata only for token efficiency — use `get_article` or `get_qa` for full content.
100
165
 
101
- **AI-Powered Quality**: `smart_log_gap` uses LLM evaluation to filter spam, detect duplicates (>85% similarity), suggest existing answers (>70% match), and auto-assign priority.
166
+ **Q&A Lifecycle**: Questions flow through `gap` `draft` `approved` status. Only approved Q&A appears in search results.
102
167
 
103
- **Simplicity**: 7 focused tools instead of 15+ complex ones. Personal knowledge base with intelligent question handling.
168
+ **AI-Powered Quality**: `log_gap` uses LLM evaluation to filter spam, detect duplicates (>85% similarity), suggest existing answers (>70% match), and auto-assign priority.
104
169
 
105
170
  ## How It Works
106
171
 
@@ -116,7 +181,16 @@ This package runs locally as a stdio MCP server and proxies requests to the remo
116
181
 
117
182
  ## Changelog
118
183
 
119
- ### v3.4.0 (Latest)
184
+ ### v3.5.0 (Latest)
185
+ - **Two operating modes**: Read-only (6 tools) and Admin (14 tools) via `PG_MEMORY_ADMIN=true`
186
+ - **Unified search**: `search` replaces `search_knowledge`, queries articles + approved Q&A in parallel
187
+ - **Q&A lifecycle**: Full CRUD with `create_qa`, `draft_qa_answer`, `approve_qa`, `delete_qa`
188
+ - **Article management**: `create_article`, `update_article`, `delete_article` admin tools
189
+ - **Smart log_gap**: Re-added deduplication, answer detection, and AI quality filtering
190
+ - **Centralized schemas**: Shared Zod schemas between HTTP and stdio servers
191
+ - **Version alignment**: HTTP server and stdio proxy both report v3.5.0
192
+
193
+ ### v3.4.0
120
194
  - **Server URL Update**: Changed default server to `mem-mcp.pragmaticgrowth.com` (API subdomain)
121
195
  - **Validation Documentation**: Added constraints to tool descriptions (enforced server-side):
122
196
  - `search_knowledge.query`: Minimum 3 characters
package/dist/index.js CHANGED
@@ -1,232 +1,323 @@
1
1
  #!/usr/bin/env node
2
- /**
3
- * PG-Memory MCP Stdio Server
4
- *
5
- * This is a local stdio MCP server that proxies requests to the
6
- * remote PG-Memory HTTP server on Railway.
7
- *
8
- * Usage:
9
- * npx @pragmatic-growth/memory-mcp # Read-only mode (default)
10
- *
11
- * Environment variables:
12
- * CLERK_API_KEY - Clerk API key for authentication (required)
13
- * MCP_SERVER_URL - Server URL (default: https://mem-mcp.pragmaticgrowth.com/mcp)
14
- *
15
- * Authentication:
16
- * Uses Clerk API key authentication via X-API-Key header.
17
- * Get your API key from the Clerk Dashboard or the app's API Docs page.
18
- */
19
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
20
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
21
- import { z } from 'zod';
22
- // Configuration
23
- const SERVER_URL = process.env.MCP_SERVER_URL || 'https://mem-mcp.pragmaticgrowth.com/mcp';
24
- const API_KEY = process.env.CLERK_API_KEY;
2
+
3
+ // src/index.ts
4
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
+
7
+ // ../mcp/src/schemas.ts
8
+ import { z } from "zod";
9
+ var searchSchema = z.object({
10
+ query: z.string().min(3, "Query must be at least 3 characters").describe("Search query"),
11
+ threshold: z.number().min(0).max(1).optional().default(0.6).describe("Similarity threshold (0-1)"),
12
+ limit: z.number().min(1).max(50).optional().default(5).describe("Max results")
13
+ });
14
+ var getArticleSchema = z.object({
15
+ id: z.string().describe("Article ID (e.g., art_...)")
16
+ });
17
+ var getQASchema = z.object({
18
+ id: z.string().describe("Q&A ID (e.g., qa_...)")
19
+ });
20
+ var logGapSchema = z.object({
21
+ question: z.string().min(10, "Question must be at least 10 characters").max(2e3).describe("The unanswered question"),
22
+ context: z.string().max(1e3).optional().describe("Additional context (email subject, sender, etc)"),
23
+ source: z.enum(["mcp", "email", "chat"]).optional().default("mcp").describe("Question source")
24
+ });
25
+ var listCategoriesSchema = z.object({});
26
+ var healthCheckSchema = z.object({});
27
+ var listGapsSchema = z.object({
28
+ limit: z.number().min(1).max(100).optional().default(20).describe("Max results"),
29
+ offset: z.number().min(0).optional().default(0).describe("Pagination offset")
30
+ });
31
+ var createArticleSchema = z.object({
32
+ title: z.string().min(1).describe("Article title"),
33
+ content: z.string().min(1).describe("Full article content (markdown)"),
34
+ summary: z.string().optional().describe("Article summary"),
35
+ category_id: z.string().optional().describe("Category UUID"),
36
+ metadata: z.record(z.string(), z.unknown()).optional().describe("Additional metadata")
37
+ });
38
+ var updateArticleSchema = z.object({
39
+ id: z.string().describe("Article ID"),
40
+ title: z.string().optional().describe("New title"),
41
+ content: z.string().optional().describe("New content"),
42
+ summary: z.string().optional().describe("New summary"),
43
+ category_id: z.string().nullable().optional().describe("New category UUID (null to remove)"),
44
+ metadata: z.record(z.string(), z.unknown()).optional().describe("New metadata")
45
+ });
46
+ var createQASchema = z.object({
47
+ question: z.string().min(1).describe("The question"),
48
+ answer: z.string().optional().describe("The answer (optional for gaps)"),
49
+ status: z.enum(["gap", "draft", "approved"]).optional().default("gap").describe("Q&A status"),
50
+ source: z.enum(["email", "chat", "mcp", "manual"]).optional().default("manual").describe("Question source"),
51
+ related_article_id: z.string().optional().describe("Related article ID"),
52
+ category_id: z.string().optional().describe("Category UUID"),
53
+ metadata: z.record(z.string(), z.unknown()).optional().describe("Additional metadata")
54
+ });
55
+ var draftQAAnswerSchema = z.object({
56
+ id: z.string().describe("Q&A ID"),
57
+ answer: z.string().min(1).describe("The drafted answer")
58
+ });
59
+ var approveQASchema = z.object({
60
+ id: z.string().describe("Q&A ID to approve")
61
+ });
62
+ var deleteArticleSchema = z.object({
63
+ id: z.string().describe("Article ID to delete")
64
+ });
65
+ var deleteQASchema = z.object({
66
+ id: z.string().describe("Q&A ID to delete")
67
+ });
68
+
69
+ // src/index.ts
70
+ var SERVER_URL = process.env.MCP_SERVER_URL || "https://mem-mcp.pragmaticgrowth.com/mcp";
71
+ var API_KEY = process.env.CLERK_API_KEY;
25
72
  if (!API_KEY) {
26
- console.error('Error: CLERK_API_KEY environment variable is required');
27
- console.error('Get your Clerk API key from the Clerk Dashboard or app API Docs page');
28
- console.error('Set it in your MCP client configuration under "env"');
29
- process.exit(1);
73
+ console.error("Error: CLERK_API_KEY environment variable is required");
74
+ console.error("Get your Clerk API key from the Clerk Dashboard or app API Docs page");
75
+ console.error('Set it in your MCP client configuration under "env"');
76
+ process.exit(1);
30
77
  }
31
- // HTTP session management
32
- let sessionId = null;
33
- let negotiatedProtocolVersion = null;
34
- /**
35
- * Make an HTTP request to the remote MCP server.
36
- * Uses X-API-Key header for Clerk API key authentication.
37
- */
78
+ var IS_ADMIN = process.env.PG_MEMORY_ADMIN === "true";
79
+ var sessionId = null;
80
+ var negotiatedProtocolVersion = null;
38
81
  async function callRemoteServer(method, params) {
39
- const headers = {
40
- 'Content-Type': 'application/json',
41
- 'X-API-Key': API_KEY, // Safe: already validated above
42
- };
43
- if (sessionId) {
44
- headers['Mcp-Session-Id'] = sessionId;
45
- }
46
- if (negotiatedProtocolVersion && method !== 'initialize') {
47
- headers['Mcp-Protocol-Version'] = negotiatedProtocolVersion;
48
- }
49
- const body = {
50
- jsonrpc: '2.0',
51
- id: Date.now(),
52
- method,
53
- params,
54
- };
55
- const response = await fetch(SERVER_URL, {
56
- method: 'POST',
57
- headers,
58
- body: JSON.stringify(body),
59
- });
60
- // Capture session ID from initialize response
61
- const newSessionId = response.headers.get('mcp-session-id');
62
- if (newSessionId) {
63
- sessionId = newSessionId;
64
- }
65
- const result = await response.json();
66
- if (result.error) {
67
- throw new Error(result.error.message);
68
- }
69
- return result.result;
82
+ const headers = {
83
+ "Content-Type": "application/json",
84
+ "X-API-Key": API_KEY
85
+ // Safe: already validated above
86
+ };
87
+ if (sessionId) {
88
+ headers["Mcp-Session-Id"] = sessionId;
89
+ }
90
+ if (negotiatedProtocolVersion && method !== "initialize") {
91
+ headers["Mcp-Protocol-Version"] = negotiatedProtocolVersion;
92
+ }
93
+ const body = {
94
+ jsonrpc: "2.0",
95
+ id: Date.now(),
96
+ method,
97
+ params
98
+ };
99
+ const response = await fetch(SERVER_URL, {
100
+ method: "POST",
101
+ headers,
102
+ body: JSON.stringify(body)
103
+ });
104
+ const newSessionId = response.headers.get("mcp-session-id");
105
+ if (newSessionId) {
106
+ sessionId = newSessionId;
107
+ }
108
+ const result = await response.json();
109
+ if (result.error) {
110
+ throw new Error(result.error.message);
111
+ }
112
+ return result.result;
70
113
  }
71
- /**
72
- * Initialize connection to remote server.
73
- */
74
114
  async function initializeRemoteSession() {
75
- const result = await callRemoteServer('initialize', {
76
- protocolVersion: '2025-11-25',
77
- capabilities: {},
78
- clientInfo: {
79
- name: 'pg-memory-stdio',
80
- version: '3.4.0',
81
- },
82
- });
83
- if (typeof result === 'object' && result && 'protocolVersion' in result) {
84
- const protocolVersion = result.protocolVersion;
85
- if (protocolVersion) {
86
- negotiatedProtocolVersion = protocolVersion;
87
- }
115
+ const result = await callRemoteServer("initialize", {
116
+ protocolVersion: "2025-11-25",
117
+ capabilities: {},
118
+ clientInfo: {
119
+ name: "pg-memory-stdio",
120
+ version: "3.5.0"
88
121
  }
122
+ });
123
+ if (typeof result === "object" && result && "protocolVersion" in result) {
124
+ const protocolVersion = result.protocolVersion;
125
+ if (protocolVersion) {
126
+ negotiatedProtocolVersion = protocolVersion;
127
+ }
128
+ }
89
129
  }
90
- /**
91
- * Create and start the stdio MCP server.
92
- */
93
130
  async function main() {
94
- // Initialize remote session first
95
- try {
96
- await initializeRemoteSession();
131
+ try {
132
+ await initializeRemoteSession();
133
+ } catch (error) {
134
+ console.error("Failed to connect to remote server:", error);
135
+ process.exit(1);
136
+ }
137
+ const server = new McpServer({
138
+ name: "pg-memory",
139
+ version: "3.5.0",
140
+ description: `PG-Memory knowledge base with ${IS_ADMIN ? "14" : "6"} tools (${IS_ADMIN ? "admin mode" : "read-only mode"})`
141
+ });
142
+ server.tool(
143
+ "search",
144
+ `Search knowledge base across articles and approved Q&A by semantic similarity.
145
+ Returns lightweight results (ID, type, title/question, similarity, date).
146
+ Use get_article or get_qa to fetch full content.
147
+ ALWAYS call this before answering domain questions.`,
148
+ searchSchema.shape,
149
+ async (args) => {
150
+ const result = await callRemoteServer("tools/call", {
151
+ name: "search",
152
+ arguments: args
153
+ });
154
+ return result;
97
155
  }
98
- catch (error) {
99
- console.error('Failed to connect to remote server:', error);
100
- process.exit(1);
156
+ );
157
+ server.tool(
158
+ "get_article",
159
+ `Retrieve full content of a knowledge base article by ID.
160
+ Call this after finding relevant items via search.`,
161
+ getArticleSchema.shape,
162
+ async (args) => {
163
+ const result = await callRemoteServer("tools/call", {
164
+ name: "get_article",
165
+ arguments: args
166
+ });
167
+ return result;
101
168
  }
102
- // Create local MCP server
103
- const server = new McpServer({
104
- name: 'pg-memory',
105
- version: '3.4.0',
106
- description: 'PG-Memory personal knowledge base with AI-powered question handling',
107
- });
108
- // ============================================================
109
- // CORE KNOWLEDGE TOOLS (available in all modes)
110
- // ============================================================
111
- // Tool 0: __IMPORTANT - Workflow Instructions (pseudo-tool)
112
- server.tool('__IMPORTANT', `3-LAYER WORKFLOW FOR QUESTION ANSWERING (ALWAYS FOLLOW):
113
-
114
- 1. search_knowledge(query) → Get lightweight index with IDs (~50 tokens/result)
115
- - Searches articles in knowledge base
116
- - Returns: id, title, similarity, date
117
- - DO NOT fetch full content yet
118
-
119
- 2. get_content(id) → Fetch full content for filtered IDs only
120
- - Only fetch what you need after reviewing search results
121
- - Saves 10x tokens vs fetching all results
122
-
123
- 3. smart_log_gap(question, context?) → Log unanswered questions
124
- - AI evaluates quality, deduplicates, suggests existing answers
125
- - Returns: {action: 'logged' | 'duplicate' | 'answered' | 'rejected'}
126
- - NEVER log without searching first
127
-
128
- CRITICAL RULES:
129
- - NEVER fetch full content before reviewing search results
130
- - NEVER log gaps without searching first
131
- - ALWAYS check for duplicates using similarity`, {}, async () => {
132
- const result = await callRemoteServer('tools/call', {
133
- name: '__IMPORTANT',
134
- arguments: {},
169
+ );
170
+ server.tool(
171
+ "get_qa",
172
+ `Retrieve a Q&A record by ID. Returns question, answer, status, and metadata.`,
173
+ getQASchema.shape,
174
+ async (args) => {
175
+ const result = await callRemoteServer("tools/call", {
176
+ name: "get_qa",
177
+ arguments: args
178
+ });
179
+ return result;
180
+ }
181
+ );
182
+ server.tool(
183
+ "log_gap",
184
+ `Log an unanswered question as a knowledge gap.
185
+ Creates a Q&A record with status='gap'. Search first before logging.`,
186
+ logGapSchema.shape,
187
+ async (args) => {
188
+ const result = await callRemoteServer("tools/call", {
189
+ name: "log_gap",
190
+ arguments: args
191
+ });
192
+ return result;
193
+ }
194
+ );
195
+ server.tool(
196
+ "list_categories",
197
+ `List all categories in the knowledge base with content counts.`,
198
+ listCategoriesSchema.shape,
199
+ async () => {
200
+ const result = await callRemoteServer("tools/call", {
201
+ name: "list_categories",
202
+ arguments: {}
203
+ });
204
+ return result;
205
+ }
206
+ );
207
+ server.tool(
208
+ "health_check",
209
+ "Check the health status of the knowledge base system including article counts, Q&A counts, and connection pool stats.",
210
+ healthCheckSchema.shape,
211
+ async () => {
212
+ const result = await callRemoteServer("tools/call", {
213
+ name: "health_check",
214
+ arguments: {}
215
+ });
216
+ return result;
217
+ }
218
+ );
219
+ if (IS_ADMIN) {
220
+ server.tool(
221
+ "list_gaps",
222
+ `[ADMIN] List unanswered knowledge gaps (Q&A with status='gap').`,
223
+ listGapsSchema.shape,
224
+ async (args) => {
225
+ const result = await callRemoteServer("tools/call", {
226
+ name: "list_gaps",
227
+ arguments: args
135
228
  });
136
229
  return result;
137
- });
138
- // Tool 1: Search Knowledge
139
- // @ts-expect-error - SDK type inference depth exceeds TypeScript limit
140
- server.tool('search_knowledge', `Search the knowledge base using semantic similarity.
141
-
142
- **YOU MUST CALL THIS TOOL** before answering any domain-specific question.
143
-
144
- Returns relevant articles with similarity scores.
145
- Use get_content with the ID to retrieve full content.`, {
146
- query: z.string().describe('The question or topic to search for'),
147
- limit: z.number().optional().describe('Maximum results to return (default: 5)'),
148
- threshold: z.number().optional().describe('Minimum similarity 0-1 (default: 0.55)'),
149
- }, async (args) => {
150
- const result = await callRemoteServer('tools/call', {
151
- name: 'search_knowledge',
152
- arguments: args,
230
+ }
231
+ );
232
+ server.tool(
233
+ "create_article",
234
+ `[ADMIN] Create a new knowledge base article. Auto-generates embedding from title + summary.`,
235
+ createArticleSchema.shape,
236
+ async (args) => {
237
+ const result = await callRemoteServer("tools/call", {
238
+ name: "create_article",
239
+ arguments: args
153
240
  });
154
241
  return result;
155
- });
156
- // Tool 2: Get Content
157
- server.tool('get_content', `Retrieve complete article content from the knowledge base by ID.
158
-
159
- Call this after reviewing search_knowledge results to fetch full content only for relevant articles.`, {
160
- id: z.string().describe('Article ID from search results (e.g., art_abc123)'),
161
- }, async (args) => {
162
- const result = await callRemoteServer('tools/call', {
163
- name: 'get_content',
164
- arguments: args,
242
+ }
243
+ );
244
+ server.tool(
245
+ "update_article",
246
+ `[ADMIN] Update an existing article. Re-generates embedding if title or content changes.`,
247
+ updateArticleSchema.shape,
248
+ async (args) => {
249
+ const result = await callRemoteServer("tools/call", {
250
+ name: "update_article",
251
+ arguments: args
165
252
  });
166
253
  return result;
167
- });
168
- // Tool 3: Smart Log Gap - AI-evaluated gap logging
169
- // @ts-expect-error - SDK type inference depth exceeds TypeScript limit
170
- server.tool('smart_log_gap', `Step 3: Log unanswered question with AI evaluation.
171
-
172
- SMART BEHAVIORS:
173
- - Checks for similar existing gaps (>85% = returns duplicate)
174
- - Checks for matching articles (>70% = suggests existing answer)
175
- - AI evaluates quality (filters garbage/spam)
176
- - Auto-assigns priority based on urgency signals
177
-
178
- ALWAYS search first before calling this. Do not log without searching.
179
-
180
- Returns one of:
181
- - {action: 'logged', gapId, priority, quality} - Question logged
182
- - {action: 'duplicate', existingGapId, similarity} - Duplicate detected
183
- - {action: 'answered', articleId, title, similarity} - Already answered
184
- - {action: 'rejected', reason, quality} - Spam/garbage filtered`, {
185
- question: z.string().describe('The unanswered question'),
186
- context: z.string().optional().describe('Additional context (email subject, sender, etc)'),
187
- source: z.enum(['mcp', 'email', 'chat']).optional().describe('Question source (default: mcp)'),
188
- }, async (args) => {
189
- const result = await callRemoteServer('tools/call', {
190
- name: 'smart_log_gap',
191
- arguments: args,
254
+ }
255
+ );
256
+ server.tool(
257
+ "create_qa",
258
+ `[ADMIN] Create a new Q&A record with full control over status and fields.`,
259
+ createQASchema.shape,
260
+ async (args) => {
261
+ const result = await callRemoteServer("tools/call", {
262
+ name: "create_qa",
263
+ arguments: args
192
264
  });
193
265
  return result;
194
- });
195
- // Tool 4: List Content
196
- server.tool('list_content', `Browse articles in the knowledge base with optional filtering.
197
-
198
- Use list_categories first to discover available categories.`, {
199
- limit: z.number().optional().describe('Number of items (default: 10, max: 50)'),
200
- category: z.string().optional().describe('Filter by category slug'),
201
- }, async (args) => {
202
- const result = await callRemoteServer('tools/call', {
203
- name: 'list_content',
204
- arguments: args,
266
+ }
267
+ );
268
+ server.tool(
269
+ "draft_qa_answer",
270
+ `[ADMIN] Draft an answer for a Q&A record. Sets status to 'draft' for review.`,
271
+ draftQAAnswerSchema.shape,
272
+ async (args) => {
273
+ const result = await callRemoteServer("tools/call", {
274
+ name: "draft_qa_answer",
275
+ arguments: args
205
276
  });
206
277
  return result;
207
- });
208
- // Tool 5: Health Check
209
- server.tool('health_check', 'Check the health status of the knowledge base system including article counts and connection pool stats.', {}, async () => {
210
- const result = await callRemoteServer('tools/call', {
211
- name: 'health_check',
212
- arguments: {},
278
+ }
279
+ );
280
+ server.tool(
281
+ "approve_qa",
282
+ `[ADMIN] Approve a Q&A record. Generates embedding from question+answer for search indexing.`,
283
+ approveQASchema.shape,
284
+ async (args) => {
285
+ const result = await callRemoteServer("tools/call", {
286
+ name: "approve_qa",
287
+ arguments: args
213
288
  });
214
289
  return result;
215
- });
216
- // Tool 6: List Categories
217
- server.tool('list_categories', `List all categories in the knowledge base with content counts.
218
- Use category slugs with list_content for filtered browsing.`, {}, async () => {
219
- const result = await callRemoteServer('tools/call', {
220
- name: 'list_categories',
221
- arguments: {},
290
+ }
291
+ );
292
+ server.tool(
293
+ "delete_article",
294
+ `[ADMIN] Delete an article from the knowledge base. This is permanent.`,
295
+ deleteArticleSchema.shape,
296
+ async (args) => {
297
+ const result = await callRemoteServer("tools/call", {
298
+ name: "delete_article",
299
+ arguments: args
222
300
  });
223
301
  return result;
224
- });
225
- // Connect via stdio transport
226
- const transport = new StdioServerTransport();
227
- await server.connect(transport);
302
+ }
303
+ );
304
+ server.tool(
305
+ "delete_qa",
306
+ `[ADMIN] Delete a Q&A record. This is permanent.`,
307
+ deleteQASchema.shape,
308
+ async (args) => {
309
+ const result = await callRemoteServer("tools/call", {
310
+ name: "delete_qa",
311
+ arguments: args
312
+ });
313
+ return result;
314
+ }
315
+ );
316
+ }
317
+ const transport = new StdioServerTransport();
318
+ await server.connect(transport);
228
319
  }
229
320
  main().catch((error) => {
230
- console.error('Fatal error:', error);
231
- process.exit(1);
321
+ console.error("Fatal error:", error);
322
+ process.exit(1);
232
323
  });
package/package.json CHANGED
@@ -1,17 +1,12 @@
1
1
  {
2
2
  "name": "@pragmatic-growth/memory-mcp",
3
- "version": "3.4.0",
4
- "description": "Stdio proxy for PG-Memory - connects stdio-based MCP clients (Claude Desktop, Raycast) to your PG-Memory HTTP server using Clerk API key authentication. Personal knowledge base with 7 focused tools including AI-powered question handling with smart deduplication and quality filtering.",
3
+ "version": "3.5.0",
4
+ "description": "Stdio proxy for PG-Memory - connects stdio-based MCP clients (Claude Desktop, Raycast) to your PG-Memory HTTP server using Clerk API key authentication. Personal knowledge base with 14 tools (6 read + 8 admin) including articles, Q&A, semantic search, and admin-gated knowledge management.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
7
7
  "memory-mcp": "dist/index.js"
8
8
  },
9
9
  "type": "module",
10
- "scripts": {
11
- "build": "tsc",
12
- "start": "node dist/index.js",
13
- "dev": "tsx src/index.ts"
14
- },
15
10
  "keywords": [
16
11
  "mcp",
17
12
  "model-context-protocol",
@@ -31,10 +26,11 @@
31
26
  "license": "MIT",
32
27
  "dependencies": {
33
28
  "@modelcontextprotocol/sdk": "^1.25.1",
34
- "zod": "^3.24.0"
29
+ "zod": "^4.2.1"
35
30
  },
36
31
  "devDependencies": {
37
32
  "@types/node": "^20",
33
+ "tsup": "^8.5.1",
38
34
  "tsx": "^4.19.0",
39
35
  "typescript": "^5"
40
36
  },
@@ -54,5 +50,10 @@
54
50
  ],
55
51
  "publishConfig": {
56
52
  "access": "public"
53
+ },
54
+ "scripts": {
55
+ "build": "tsup",
56
+ "start": "node dist/index.js",
57
+ "dev": "tsx src/index.ts"
57
58
  }
58
- }
59
+ }
package/dist/CLAUDE.md DELETED
@@ -1,11 +0,0 @@
1
- <claude-mem-context>
2
- # Recent Activity
3
-
4
- <!-- This section is auto-generated by claude-mem. Edit content outside the tags. -->
5
-
6
- ### Jan 16, 2026
7
-
8
- | ID | Time | T | Title | Read |
9
- |----|------|---|-------|------|
10
- | #26372 | 2:48 AM | ✅ | Committed v3.4.0 release to main branch | ~238 |
11
- </claude-mem-context>
package/dist/index.d.ts DELETED
@@ -1,19 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * PG-Memory MCP Stdio Server
4
- *
5
- * This is a local stdio MCP server that proxies requests to the
6
- * remote PG-Memory HTTP server on Railway.
7
- *
8
- * Usage:
9
- * npx @pragmatic-growth/memory-mcp # Read-only mode (default)
10
- *
11
- * Environment variables:
12
- * CLERK_API_KEY - Clerk API key for authentication (required)
13
- * MCP_SERVER_URL - Server URL (default: https://mem-mcp.pragmaticgrowth.com/mcp)
14
- *
15
- * Authentication:
16
- * Uses Clerk API key authentication via X-API-Key header.
17
- * Get your API key from the Clerk Dashboard or the app's API Docs page.
18
- */
19
- export {};