@pragmatic-growth/memory-mcp 2.1.0 → 2.2.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
@@ -1,20 +1,51 @@
1
- # NT-Memory MCP (stdio)
1
+ # PG-Memory MCP (stdio proxy)
2
2
 
3
- Local MCP stdio server for NT-Memory tax knowledge base. Use this with Raycast and other stdio-only MCP clients.
3
+ Stdio proxy for PG-Memory - connects stdio-based MCP clients to your PG-Memory HTTP server.
4
+
5
+ ## Two Transport Modes
6
+
7
+ PG-Memory supports **two MCP transport modes**:
8
+
9
+ ### 1. HTTP Transport (Direct)
10
+ For clients that support HTTP/SSE transport (Claude Code, mcp-remote):
11
+ ```json
12
+ {
13
+ "mcpServers": {
14
+ "pg-memory": {
15
+ "command": "npx",
16
+ "args": ["-y", "mcp-remote", "https://your-server.up.railway.app/api/mcp"],
17
+ "env": {
18
+ "API_KEY": "your-api-key"
19
+ }
20
+ }
21
+ }
22
+ }
23
+ ```
24
+
25
+ ### 2. Stdio Transport (This Package)
26
+ For clients that only support stdio (Raycast, local tools):
27
+ ```json
28
+ {
29
+ "name": "pg-memory",
30
+ "type": "stdio",
31
+ "command": "npx",
32
+ "args": ["-y", "@pragmatic-growth/memory-mcp"],
33
+ "env": {
34
+ "MCP_API_KEY": "your-api-key"
35
+ }
36
+ }
37
+ ```
4
38
 
5
39
  ## Two Operating Modes
6
40
 
7
- The server supports two modes for different use cases:
41
+ Both transports support two operating modes:
8
42
 
9
43
  ### Read-Only Mode (Default)
10
- Safe for general use - only search and read operations:
11
- - `search_tax_knowledge` - Semantic search
12
- - `answer_question` - RAG with auto-generation
13
- - `get_article` - Get article by ID
14
- - `list_articles` - Browse articles
15
- - `list_categories` - List categories with counts
16
- - `log_unanswered` - Flag gaps
17
- - `health_check` - System status
44
+ Safe for general use - search, read, and conversation tracking:
45
+ - **Core Knowledge**: search, get content, answer questions, list content
46
+ - **Knowledge Graph**: find related articles, get entities, search entities
47
+ - **Episodic Memory**: start/track conversations, get session context
48
+ - **Q&A**: add questions for later answering
18
49
 
19
50
  ### Full Mode (with `--full` flag)
20
51
  Includes all read-only tools PLUS write operations:
@@ -29,10 +60,10 @@ Includes all read-only tools PLUS write operations:
29
60
 
30
61
  ```json
31
62
  {
32
- "name": "nt-memory",
63
+ "name": "pg-memory",
33
64
  "type": "stdio",
34
65
  "command": "npx",
35
- "args": ["-y", "@anthropic/nt-memory-mcp"],
66
+ "args": ["-y", "@pragmatic-growth/memory-mcp"],
36
67
  "env": {
37
68
  "MCP_API_KEY": "your-api-key-here"
38
69
  }
@@ -43,10 +74,10 @@ Includes all read-only tools PLUS write operations:
43
74
 
44
75
  ```json
45
76
  {
46
- "name": "nt-memory-full",
77
+ "name": "pg-memory-full",
47
78
  "type": "stdio",
48
79
  "command": "npx",
49
- "args": ["-y", "@anthropic/nt-memory-mcp", "--full"],
80
+ "args": ["-y", "@pragmatic-growth/memory-mcp", "--full"],
50
81
  "env": {
51
82
  "MCP_API_KEY": "your-api-key-here"
52
83
  }
@@ -56,17 +87,17 @@ Includes all read-only tools PLUS write operations:
56
87
  ### Global Installation
57
88
 
58
89
  ```bash
59
- npm install -g @anthropic/nt-memory-mcp
90
+ npm install -g @pragmatic-growth/memory-mcp
60
91
  ```
61
92
 
62
93
  Then run directly:
63
94
 
64
95
  ```bash
65
96
  # Read-only mode (default)
66
- MCP_API_KEY=your-key nt-memory-mcp
97
+ MCP_API_KEY=your-key memory-mcp
67
98
 
68
99
  # Full mode
69
- MCP_API_KEY=your-key nt-memory-mcp --full
100
+ MCP_API_KEY=your-key memory-mcp --full
70
101
  ```
71
102
 
72
103
  ## Environment Variables
@@ -79,41 +110,97 @@ MCP_API_KEY=your-key nt-memory-mcp --full
79
110
 
80
111
  Note: The `--full` CLI flag takes precedence over `MCP_MODE` env var.
81
112
 
82
- ## Available Tools
113
+ ## Available Tools (21 Total)
114
+
115
+ ### Core Knowledge Tools (7)
83
116
 
84
- ### Core Tools (All Modes)
117
+ | Tool | Description |
118
+ |------|-------------|
119
+ | `search_knowledge` | Semantic search across articles AND Q&A entries |
120
+ | `get_content` | Retrieve full content by ID (articles or Q&A) |
121
+ | `answer_question` | Full RAG pipeline with graph-augmented retrieval |
122
+ | `log_unanswered` | Flag questions for knowledge gap analysis |
123
+ | `list_content` | Browse articles and Q&A with filtering |
124
+ | `list_categories` | List all categories with content counts |
125
+ | `health_check` | Check system status and connectivity |
126
+
127
+ ### Knowledge Graph Tools (4)
128
+
129
+ | Tool | Description |
130
+ |------|-------------|
131
+ | `find_related` | Find related articles via shared entities |
132
+ | `get_entities` | Get named entities from an article |
133
+ | `search_entities` | Search entities by name or type |
134
+ | `get_knowledge_graph` | Get graph structure for visualization |
135
+
136
+ ### Episodic Memory Tools (5)
137
+
138
+ | Tool | Description |
139
+ |------|-------------|
140
+ | `start_conversation` | Begin tracking a conversation session |
141
+ | `add_message` | Add message with automatic fact extraction |
142
+ | `get_conversation` | Retrieve conversation with all messages |
143
+ | `list_conversations` | List recent conversations |
144
+ | `get_session_context` | Get context for follow-up questions |
145
+
146
+ ### Q&A Tools (1)
85
147
 
86
148
  | Tool | Description |
87
149
  |------|-------------|
88
- | `search_tax_knowledge` | Search US non-resident tax knowledge base using semantic similarity |
89
- | `answer_question` | Get AI-powered answers with sources (generates articles if needed) |
90
- | `get_article` | Retrieve complete article content by ID |
91
- | `list_articles` | Browse recent articles with optional category filter |
92
- | `list_categories` | List all categories with article counts |
93
- | `log_unanswered` | Flag questions as unanswered for gap analysis |
94
- | `health_check` | Check system status, database connectivity, and current mode |
150
+ | `add_question` | Add question for later expert answering |
95
151
 
96
- ### Full Mode Tools
152
+ ### Full Mode Tools (4)
97
153
 
98
154
  | Tool | Description |
99
155
  |------|-------------|
100
- | `add_article` | Create a new article with automatic embedding generation |
156
+ | `add_article` | Create a new article with automatic embedding |
101
157
  | `edit_article` | Update article content, metadata, or category |
102
- | `remove_article` | Soft-delete an article (preserves data for audit) |
103
- | `rate_answer` | Rate previous answers: -1 (unhelpful), 0 (neutral), 1 (helpful) |
158
+ | `remove_article` | Soft-delete an article |
159
+ | `rate_answer` | Rate previous answers: -1, 0, or 1 |
160
+
161
+ ## MCP Resources
162
+
163
+ | URI | Description |
164
+ |-----|-------------|
165
+ | `pgmemory://categories` | List all knowledge base categories |
166
+ | `pgmemory://stats` | System statistics (article count, gaps, latency) |
167
+ | `pgmemory://recent` | Recently added/updated articles |
168
+
169
+ ## MCP Prompts
170
+
171
+ | Prompt | Description |
172
+ |--------|-------------|
173
+ | `system-context` | Knowledge base context for AI assistants |
174
+ | `search-tips` | Guidelines for effective search queries |
104
175
 
105
176
  ## How It Works
106
177
 
107
- This package runs locally as a stdio MCP server and proxies requests to the remote NT-Memory HTTP server on Railway. This allows Raycast (which only supports stdio) to use our cloud-hosted knowledge base.
178
+ This package runs locally as a stdio MCP server and proxies requests to the remote PG-Memory HTTP server. This allows clients that only support stdio to use the cloud-hosted knowledge base.
108
179
 
109
180
  ```
110
- Raycast (stdio) → nt-memory-mcp (local) → HTTP → Railway (cloud)
181
+ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
182
+ │ Raycast/Local │────▶│ memory-mcp │────▶│ PG-Memory │
183
+ │ (stdio only) │ │ (this package) │ │ HTTP Server │
184
+ └─────────────────┘ └─────────────────┘ └─────────────────┘
185
+ stdio proxy HTTP
111
186
  ```
112
187
 
113
- The server also respects the remote server's mode configuration. For full mode to work:
188
+ For full mode to work:
114
189
  1. The stdio proxy must have `--full` flag OR `MCP_MODE=full`
115
190
  2. The remote server must have `MCP_MODE=full` in its environment
116
191
 
192
+ ## Changelog
193
+
194
+ ### v2.2.0
195
+ - Added unified `get_content` and `list_content` tools (replacing `get_article`/`list_articles`)
196
+ - Added Knowledge Graph tools: `find_related`, `get_entities`, `search_entities`, `get_knowledge_graph`
197
+ - Added Episodic Memory tools: `start_conversation`, `add_message`, `get_conversation`, `list_conversations`, `get_session_context`
198
+ - Added Q&A tool: `add_question`
199
+ - Updated to MCP Protocol 2025-11-25
200
+
201
+ ### v2.1.1
202
+ - Initial public release with core knowledge tools
203
+
117
204
  ## License
118
205
 
119
206
  MIT
package/dist/index.js CHANGED
@@ -75,7 +75,7 @@ async function initializeRemoteSession() {
75
75
  capabilities: {},
76
76
  clientInfo: {
77
77
  name: 'pg-memory-stdio',
78
- version: '2.1.0',
78
+ version: '2.2.0',
79
79
  },
80
80
  });
81
81
  }
@@ -95,28 +95,30 @@ async function main() {
95
95
  // Create local MCP server
96
96
  const server = new McpServer({
97
97
  name: 'pg-memory',
98
- version: '2.1.0',
98
+ version: '2.2.0',
99
99
  description: `PG-Memory knowledge base (${modeLabel} mode)`,
100
100
  });
101
101
  // ============================================================
102
- // READ-ONLY TOOLS (available in all modes)
102
+ // CORE KNOWLEDGE TOOLS (available in all modes)
103
103
  // ============================================================
104
- // Tool 1: Search Knowledge (matches HTTP server's search_knowledge)
104
+ // Tool 1: Search Knowledge (Articles + Q&A)
105
105
  server.tool('search_knowledge', `Search the nonresident.tax knowledge base using semantic similarity.
106
106
 
107
- This knowledge base contains comprehensive information about:
108
- - Services: LLC formation, tax filing, registered agent, accounting
109
- - Pricing: Package costs, annual fees, consultation fees
107
+ **YOU MUST CALL THIS TOOL** before answering any domain-specific question.
108
+
109
+ Topics covered:
110
110
  - Tax compliance: Form 5472, Form 1120, ECI rules, penalties, deadlines
111
111
  - Company formation: Wyoming LLC, Delaware LLC, single-member LLC
112
112
  - Banking: Mercury, Wise, US bank account requirements
113
- - Process: Onboarding steps, document requirements, timelines
114
- - Dissolution: LLC closure, final filings, IRS compliance
113
+ - Services & pricing: LLC formation, tax filing, registered agent
115
114
 
116
- Returns relevant articles with relevance scores. Use get_article to retrieve full content.`, {
115
+ Returns relevant articles AND Q&A entries with relevance scores.
116
+ Use get_content with the ID to retrieve full content.`, {
117
117
  query: z.string().describe('The question or topic to search for'),
118
- limit: z.number().optional().describe('Maximum number of results to return (default: 5)'),
119
- threshold: z.number().optional().describe('Minimum similarity threshold 0-1 (default: 0.55)'),
118
+ limit: z.number().optional().describe('Maximum results to return (default: 5)'),
119
+ threshold: z.number().optional().describe('Minimum similarity 0-1 (default: 0.55)'),
120
+ include_articles: z.boolean().optional().describe('Include articles (default: true)'),
121
+ include_qa: z.boolean().optional().describe('Include Q&A entries (default: true)'),
120
122
  }, async (args) => {
121
123
  const result = await callRemoteServer('tools/call', {
122
124
  name: 'search_knowledge',
@@ -124,44 +126,38 @@ Returns relevant articles with relevance scores. Use get_article to retrieve ful
124
126
  });
125
127
  return result;
126
128
  });
127
- // Tool 2: Get Article
128
- server.tool('get_article', `Retrieve the complete content of a knowledge base article by ID.
129
+ // Tool 2: Get Content (Unified - Articles + Q&A)
130
+ server.tool('get_content', `Retrieve complete content from the knowledge base by ID.
129
131
 
130
- Use this tool when:
131
- - You have an article ID from search_knowledge or list_articles
132
- - You need the full article content (not just summary)
133
- - You want to verify specific details from search results
132
+ Automatically detects content type from ID prefix:
133
+ - 'art_*' Article (knowledge base article)
134
+ - 'qa_*' Q&A entry (question and answer)
134
135
 
135
- Returns: Full article with title, content, summary, category, tags, version, and last update date.`, {
136
- article_id: z.string().describe('The article ID (e.g., art_abc123)'),
136
+ Use summary_only=true first to check content size, then fetch with max_length if needed.`, {
137
+ content_id: z.string().describe('Content ID (e.g., art_abc123 or qa_xyz789)'),
138
+ max_length: z.number().optional().describe('Max content length in chars (truncates if exceeded)'),
139
+ summary_only: z.boolean().optional().describe('Return only summary/metadata (default: false)'),
137
140
  }, async (args) => {
138
141
  const result = await callRemoteServer('tools/call', {
139
- name: 'get_article',
142
+ name: 'get_content',
140
143
  arguments: args,
141
144
  });
142
145
  return result;
143
146
  });
144
147
  // Tool 3: Answer Question (Full RAG)
145
- server.tool('answer_question', `Answer a customer question using the knowledge base with full RAG.
146
-
147
- This tool:
148
- 1. Searches for relevant articles in the knowledge base
149
- 2. If found: Generates an answer synthesizing the retrieved content
150
- 3. If not found: Optionally generates a new article to fill the gap
148
+ server.tool('answer_question', `Answer a customer question using the knowledge base with full RAG pipeline.
151
149
 
152
- Use for questions about:
153
- - Services and pricing
154
- - Tax filing requirements and deadlines
155
- - LLC formation process
156
- - Bank account options
157
- - Document requirements
158
- - How onboarding works
159
- - Any nonresident.tax topic
150
+ How it works:
151
+ 1. Searches for relevant articles AND published Q&A
152
+ 2. Uses knowledge graph to find related content via entities
153
+ 3. Generates a synthesized answer from all sources
154
+ 4. Returns source references for transparency
160
155
 
161
- The answer includes source references for transparency.`, {
156
+ Use this when you need a complete answer with sources, not just search results.`, {
162
157
  question: z.string().describe('The customer question to answer'),
163
- context: z.string().optional().describe('Additional context about the question'),
164
- generate_if_not_found: z.boolean().optional().describe('Generate new article if no relevant content found (default: true)'),
158
+ context: z.string().optional().describe('Additional context'),
159
+ generate_if_not_found: z.boolean().optional().describe('Generate new article if not found (default: true)'),
160
+ use_graph: z.boolean().optional().describe('Enable graph-augmented retrieval (default: true)'),
165
161
  }, async (args) => {
166
162
  const result = await callRemoteServer('tools/call', {
167
163
  name: 'answer_question',
@@ -172,19 +168,10 @@ The answer includes source references for transparency.`, {
172
168
  // Tool 4: Log Unanswered
173
169
  server.tool('log_unanswered', `Flag a customer question as unanswered for knowledge gap analysis.
174
170
 
175
- WHEN TO USE:
176
- - After search_knowledge returns no relevant results
177
- - When existing articles don't fully answer the question
178
- - When a customer asks something completely new
179
- - To track recurring questions that need dedicated articles
180
-
181
- WHY IT MATTERS:
182
- - Flagged questions appear in the admin "Knowledge Gaps" section
183
- - Helps prioritize which new articles to create
184
- - Builds a list of topics customers actually need
185
- - Improves the knowledge base over time`, {
171
+ **CALL THIS** when search_knowledge returns no results or low confidence.
172
+ Flagged questions appear in admin "Knowledge Gaps" section.`, {
186
173
  query: z.string().describe('The question that could not be answered'),
187
- reason: z.string().optional().describe('Reason why the question could not be answered'),
174
+ reason: z.string().optional().describe('Reason why'),
188
175
  }, async (args) => {
189
176
  const result = await callRemoteServer('tools/call', {
190
177
  name: 'log_unanswered',
@@ -192,31 +179,24 @@ WHY IT MATTERS:
192
179
  });
193
180
  return result;
194
181
  });
195
- // Tool 5: List Articles
196
- server.tool('list_articles', `Browse articles in the knowledge base with optional filtering.
197
-
198
- Parameters:
199
- - limit: Number of articles (default 10, max 50)
200
- - category: Filter by category slug (use list_categories first)
201
-
202
- Returns article ID, title, and summary for each match.
203
- Use get_article with the ID to retrieve full content.
182
+ // Tool 5: List Content (Unified - Articles + Q&A)
183
+ server.tool('list_content', `Browse content in the knowledge base with optional filtering.
204
184
 
205
- WORKFLOW:
206
- 1. Call list_categories to discover available categories
207
- 2. Call list_articles with specific category for focused results
208
- 3. Call get_article for articles that look relevant`, {
209
- limit: z.number().optional().describe('Number of articles to return (max 50)'),
185
+ Supports: articles, qa, or all (default).
186
+ Use list_categories first to discover available categories.`, {
187
+ type: z.enum(['articles', 'qa', 'all']).optional().describe('Content type (default: all)'),
188
+ limit: z.number().optional().describe('Number of items (default: 10, max: 50)'),
210
189
  category: z.string().optional().describe('Filter by category slug'),
190
+ status: z.string().optional().describe('Filter Q&A by status: pending, draft, published'),
211
191
  }, async (args) => {
212
192
  const result = await callRemoteServer('tools/call', {
213
- name: 'list_articles',
193
+ name: 'list_content',
214
194
  arguments: args,
215
195
  });
216
196
  return result;
217
197
  });
218
198
  // Tool 6: Health Check
219
- server.tool('health_check', 'Check the health status of the memory system.', {}, async () => {
199
+ server.tool('health_check', 'Check the health status of the memory system including article/Q&A counts.', {}, async () => {
220
200
  const result = await callRemoteServer('tools/call', {
221
201
  name: 'health_check',
222
202
  arguments: {},
@@ -224,22 +204,8 @@ WORKFLOW:
224
204
  return result;
225
205
  });
226
206
  // Tool 7: List Categories
227
- server.tool('list_categories', `List all categories in the knowledge base with article counts.
228
-
229
- CRITICAL FOR EFFICIENT SEARCH:
230
- Categories help you quickly narrow down to relevant content.
231
-
232
- Returns for each category:
233
- - slug: Category identifier for filtering
234
- - name: Human-readable name
235
- - count: Number of articles
236
- - description: What the category covers
237
-
238
- RECOMMENDED WORKFLOW:
239
- 1. Call list_categories to see what's available
240
- 2. Call list_articles with category slug
241
- 3. Call get_article for full content
242
- 4. Or use search_knowledge for semantic search across all`, {}, async () => {
207
+ server.tool('list_categories', `List all categories in the knowledge base with content counts.
208
+ Use category slugs with list_content for filtered browsing.`, {}, async () => {
243
209
  const result = await callRemoteServer('tools/call', {
244
210
  name: 'list_categories',
245
211
  arguments: {},
@@ -247,17 +213,152 @@ RECOMMENDED WORKFLOW:
247
213
  return result;
248
214
  });
249
215
  // ============================================================
250
- // FULL MODE TOOLS (only available with --full flag or MCP_MODE=full)
216
+ // KNOWLEDGE GRAPH TOOLS (available in all modes)
217
+ // ============================================================
218
+ // Tool 8: Find Related Articles
219
+ server.tool('find_related', 'Find articles related through shared entities or explicit relationships. Uses graph traversal.', {
220
+ article_id: z.string().describe('Article ID to find relations for'),
221
+ max_depth: z.number().optional().describe('Relationship hops 1-5 (default: 2)'),
222
+ min_strength: z.number().optional().describe('Min strength 0-1 (default: 0.3)'),
223
+ limit: z.number().optional().describe('Max articles (default: 10)'),
224
+ }, async (args) => {
225
+ const result = await callRemoteServer('tools/call', {
226
+ name: 'find_related',
227
+ arguments: args,
228
+ });
229
+ return result;
230
+ });
231
+ // Tool 9: Get Entities
232
+ server.tool('get_entities', 'Get named entities from an article (people, orgs, forms, states, etc.).', {
233
+ article_id: z.string().describe('Article ID to get entities for'),
234
+ }, async (args) => {
235
+ const result = await callRemoteServer('tools/call', {
236
+ name: 'get_entities',
237
+ arguments: args,
238
+ });
239
+ return result;
240
+ });
241
+ // Tool 10: Search Entities
242
+ server.tool('search_entities', 'Search for entities by name or type across the knowledge base.', {
243
+ query: z.string().optional().describe('Search query for entity name'),
244
+ type: z.string().optional().describe('Filter: PERSON, ORGANIZATION, FORM, STATE, COUNTRY, DATE, AMOUNT, CONCEPT, REGULATION, SERVICE, DOCUMENT'),
245
+ limit: z.number().optional().describe('Max entities (default: 20)'),
246
+ }, async (args) => {
247
+ const result = await callRemoteServer('tools/call', {
248
+ name: 'search_entities',
249
+ arguments: args,
250
+ });
251
+ return result;
252
+ });
253
+ // Tool 11: Get Knowledge Graph
254
+ server.tool('get_knowledge_graph', 'Get the knowledge graph structure for visualization. Returns nodes and edges.', {
255
+ center_article_id: z.string().optional().describe('Center on this article'),
256
+ depth: z.number().optional().describe('Traversal depth 1-5 (default: 2)'),
257
+ include_entities: z.boolean().optional().describe('Include entity nodes (default: true)'),
258
+ include_metadata: z.boolean().optional().describe('Include full metadata (default: false)'),
259
+ limit: z.number().optional().describe('Max nodes (default: 20, max: 100)'),
260
+ }, async (args) => {
261
+ const result = await callRemoteServer('tools/call', {
262
+ name: 'get_knowledge_graph',
263
+ arguments: args,
264
+ });
265
+ return result;
266
+ });
267
+ // ============================================================
268
+ // EPISODIC MEMORY TOOLS (available in all modes)
269
+ // ============================================================
270
+ // Tool 12: Start Conversation
271
+ server.tool('start_conversation', `Start a new conversation for tracking episodic memory.
272
+ Call at the beginning of each session to enable context continuity.`, {
273
+ session_id: z.string().describe('Session ID (e.g., MCP session ID)'),
274
+ title: z.string().optional().describe('Conversation title'),
275
+ user_id: z.string().optional().describe('User ID for multi-user tracking'),
276
+ }, async (args) => {
277
+ const result = await callRemoteServer('tools/call', {
278
+ name: 'start_conversation',
279
+ arguments: args,
280
+ });
281
+ return result;
282
+ });
283
+ // Tool 13: Add Message
284
+ server.tool('add_message', `Add a message to an existing conversation.
285
+ Call after each user message and assistant response.
286
+ User facts are automatically extracted.`, {
287
+ conversation_id: z.string().describe('Conversation ID from start_conversation'),
288
+ role: z.enum(['user', 'assistant', 'system']).describe('Message role'),
289
+ content: z.string().describe('Message content'),
290
+ referenced_articles: z.string().optional().describe('Comma-separated article IDs referenced'),
291
+ }, async (args) => {
292
+ const result = await callRemoteServer('tools/call', {
293
+ name: 'add_message',
294
+ arguments: args,
295
+ });
296
+ return result;
297
+ });
298
+ // Tool 14: Get Conversation
299
+ server.tool('get_conversation', 'Retrieve a conversation with all its messages by ID.', {
300
+ conversation_id: z.string().describe('Conversation ID'),
301
+ }, async (args) => {
302
+ const result = await callRemoteServer('tools/call', {
303
+ name: 'get_conversation',
304
+ arguments: args,
305
+ });
306
+ return result;
307
+ });
308
+ // Tool 15: List Conversations
309
+ server.tool('list_conversations', 'List conversations for a session or user. Returns recent first.', {
310
+ session_id: z.string().optional().describe('Filter by session ID'),
311
+ user_id: z.string().optional().describe('Filter by user ID'),
312
+ limit: z.number().optional().describe('Max conversations (default: 20)'),
313
+ }, async (args) => {
314
+ const result = await callRemoteServer('tools/call', {
315
+ name: 'list_conversations',
316
+ arguments: args,
317
+ });
318
+ return result;
319
+ });
320
+ // Tool 16: Get Session Context
321
+ server.tool('get_session_context', `Get recent conversation context for a session.
322
+ Call when user refers to prior conversation or asks follow-up questions.`, {
323
+ session_id: z.string().describe('Session ID to get context for'),
324
+ message_limit: z.number().optional().describe('Max messages (default: 10)'),
325
+ }, async (args) => {
326
+ const result = await callRemoteServer('tools/call', {
327
+ name: 'get_session_context',
328
+ arguments: args,
329
+ });
330
+ return result;
331
+ });
332
+ // ============================================================
333
+ // Q&A TOOLS (available in all modes)
334
+ // ============================================================
335
+ // Tool 17: Add Question
336
+ server.tool('add_question', `Add a new question to the Q&A store for later answering.
337
+ Use to capture questions from external channels.`, {
338
+ question: z.string().describe('The question text'),
339
+ category: z.string().optional().describe('Category slug'),
340
+ tags: z.string().optional().describe('Comma-separated tags'),
341
+ priority: z.string().optional().describe('Priority: low, medium, high, critical'),
342
+ source: z.string().optional().describe('Source: mcp, email, manual, import'),
343
+ }, async (args) => {
344
+ const result = await callRemoteServer('tools/call', {
345
+ name: 'add_question',
346
+ arguments: args,
347
+ });
348
+ return result;
349
+ });
350
+ // ============================================================
351
+ // FULL MODE TOOLS (only with --full flag or MCP_MODE=full)
251
352
  // ============================================================
252
353
  if (isFullMode) {
253
- // Tool 8: Add Article
254
- server.tool('add_article', 'Create a new article in the knowledge base. Automatically generates embedding for semantic search.', {
354
+ // Tool 18: Add Article
355
+ server.tool('add_article', 'Create a new article in the knowledge base. Generates embedding automatically.', {
255
356
  title: z.string().describe('Article title'),
256
- content: z.string().describe('Article content in markdown format'),
257
- summary: z.string().optional().describe('Brief summary (auto-generated if not provided)'),
258
- category: z.string().optional().describe('Category (e.g., federal-tax, state-compliance)'),
357
+ content: z.string().describe('Article content in markdown'),
358
+ summary: z.string().optional().describe('Brief summary (auto-generated if omitted)'),
359
+ category: z.string().optional().describe('Category slug'),
259
360
  tags: z.string().optional().describe('Comma-separated tags'),
260
- source: z.string().optional().describe('Source of the article (e.g., manual, irs.gov)'),
361
+ source: z.string().optional().describe('Source (e.g., manual, irs.gov)'),
261
362
  }, async (args) => {
262
363
  const result = await callRemoteServer('tools/call', {
263
364
  name: 'add_article',
@@ -265,7 +366,7 @@ RECOMMENDED WORKFLOW:
265
366
  });
266
367
  return result;
267
368
  });
268
- // Tool 9: Edit Article
369
+ // Tool 19: Edit Article
269
370
  server.tool('edit_article', 'Update an existing article. Re-generates embedding if content changes.', {
270
371
  article_id: z.string().describe('Article ID to update'),
271
372
  title: z.string().optional().describe('New title'),
@@ -280,10 +381,10 @@ RECOMMENDED WORKFLOW:
280
381
  });
281
382
  return result;
282
383
  });
283
- // Tool 10: Remove Article
284
- server.tool('remove_article', 'Soft-delete an article from the knowledge base. Article is marked as deleted but not permanently removed.', {
384
+ // Tool 20: Remove Article
385
+ server.tool('remove_article', 'Soft-delete an article. Marked as deleted but not permanently removed.', {
285
386
  article_id: z.string().describe('Article ID to remove'),
286
- reason: z.string().optional().describe('Reason for deletion (for audit log)'),
387
+ reason: z.string().optional().describe('Reason for deletion'),
287
388
  }, async (args) => {
288
389
  const result = await callRemoteServer('tools/call', {
289
390
  name: 'remove_article',
@@ -291,11 +392,11 @@ RECOMMENDED WORKFLOW:
291
392
  });
292
393
  return result;
293
394
  });
294
- // Tool 11: Rate Answer
295
- server.tool('rate_answer', 'Rate a previous answer as helpful, unhelpful, or neutral. Helps improve the knowledge base.', {
296
- query_id: z.string().describe('Query log ID from a previous answer_question response'),
395
+ // Tool 21: Rate Answer
396
+ server.tool('rate_answer', 'Rate a previous answer as helpful, unhelpful, or neutral.', {
397
+ query_id: z.string().describe('Query log ID from answer_question'),
297
398
  rating: z.number().describe('Rating: -1 (unhelpful), 0 (neutral), 1 (helpful)'),
298
- feedback: z.string().optional().describe('Optional text feedback'),
399
+ feedback: z.string().optional().describe('Optional feedback text'),
299
400
  }, async (args) => {
300
401
  const result = await callRemoteServer('tools/call', {
301
402
  name: 'rate_answer',
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@pragmatic-growth/memory-mcp",
3
- "version": "2.1.0",
4
- "description": "MCP stdio client for PG-Memory knowledge base - connect AI agents to your PostgreSQL knowledge base via Model Context Protocol",
3
+ "version": "2.2.0",
4
+ "description": "Stdio proxy for PG-Memory - connects stdio-based MCP clients (Raycast, local tools) to your PG-Memory HTTP server. PG-Memory supports both HTTP transport (Claude Code, mcp-remote) and stdio transport (this package).",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
7
7
  "memory-mcp": "./dist/index.js"
@@ -15,12 +15,17 @@
15
15
  "keywords": [
16
16
  "mcp",
17
17
  "model-context-protocol",
18
+ "stdio",
19
+ "http",
18
20
  "raycast",
21
+ "claude-code",
19
22
  "knowledge-base",
20
23
  "postgresql",
21
24
  "pgvector",
22
25
  "ai",
23
- "rag"
26
+ "rag",
27
+ "semantic-search",
28
+ "memory"
24
29
  ],
25
30
  "author": "Pragmatic Growth",
26
31
  "license": "MIT",
package/src/index.ts CHANGED
@@ -97,7 +97,7 @@ async function initializeRemoteSession(): Promise<void> {
97
97
  capabilities: {},
98
98
  clientInfo: {
99
99
  name: 'pg-memory-stdio',
100
- version: '2.1.0',
100
+ version: '2.2.0',
101
101
  },
102
102
  });
103
103
  }
@@ -119,33 +119,35 @@ async function main(): Promise<void> {
119
119
  // Create local MCP server
120
120
  const server = new McpServer({
121
121
  name: 'pg-memory',
122
- version: '2.1.0',
122
+ version: '2.2.0',
123
123
  description: `PG-Memory knowledge base (${modeLabel} mode)`,
124
124
  });
125
125
 
126
126
  // ============================================================
127
- // READ-ONLY TOOLS (available in all modes)
127
+ // CORE KNOWLEDGE TOOLS (available in all modes)
128
128
  // ============================================================
129
129
 
130
- // Tool 1: Search Knowledge (matches HTTP server's search_knowledge)
130
+ // Tool 1: Search Knowledge (Articles + Q&A)
131
131
  server.tool(
132
132
  'search_knowledge',
133
133
  `Search the nonresident.tax knowledge base using semantic similarity.
134
134
 
135
- This knowledge base contains comprehensive information about:
136
- - Services: LLC formation, tax filing, registered agent, accounting
137
- - Pricing: Package costs, annual fees, consultation fees
135
+ **YOU MUST CALL THIS TOOL** before answering any domain-specific question.
136
+
137
+ Topics covered:
138
138
  - Tax compliance: Form 5472, Form 1120, ECI rules, penalties, deadlines
139
139
  - Company formation: Wyoming LLC, Delaware LLC, single-member LLC
140
140
  - Banking: Mercury, Wise, US bank account requirements
141
- - Process: Onboarding steps, document requirements, timelines
142
- - Dissolution: LLC closure, final filings, IRS compliance
141
+ - Services & pricing: LLC formation, tax filing, registered agent
143
142
 
144
- Returns relevant articles with relevance scores. Use get_article to retrieve full content.`,
143
+ Returns relevant articles AND Q&A entries with relevance scores.
144
+ Use get_content with the ID to retrieve full content.`,
145
145
  {
146
146
  query: z.string().describe('The question or topic to search for'),
147
- limit: z.number().optional().describe('Maximum number of results to return (default: 5)'),
148
- threshold: z.number().optional().describe('Minimum similarity threshold 0-1 (default: 0.55)'),
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
+ include_articles: z.boolean().optional().describe('Include articles (default: true)'),
150
+ include_qa: z.boolean().optional().describe('Include Q&A entries (default: true)'),
149
151
  },
150
152
  async (args) => {
151
153
  const result = await callRemoteServer('tools/call', {
@@ -156,23 +158,24 @@ Returns relevant articles with relevance scores. Use get_article to retrieve ful
156
158
  }
157
159
  );
158
160
 
159
- // Tool 2: Get Article
161
+ // Tool 2: Get Content (Unified - Articles + Q&A)
160
162
  server.tool(
161
- 'get_article',
162
- `Retrieve the complete content of a knowledge base article by ID.
163
+ 'get_content',
164
+ `Retrieve complete content from the knowledge base by ID.
163
165
 
164
- Use this tool when:
165
- - You have an article ID from search_knowledge or list_articles
166
- - You need the full article content (not just summary)
167
- - You want to verify specific details from search results
166
+ Automatically detects content type from ID prefix:
167
+ - 'art_*' Article (knowledge base article)
168
+ - 'qa_*' Q&A entry (question and answer)
168
169
 
169
- Returns: Full article with title, content, summary, category, tags, version, and last update date.`,
170
+ Use summary_only=true first to check content size, then fetch with max_length if needed.`,
170
171
  {
171
- article_id: z.string().describe('The article ID (e.g., art_abc123)'),
172
+ content_id: z.string().describe('Content ID (e.g., art_abc123 or qa_xyz789)'),
173
+ max_length: z.number().optional().describe('Max content length in chars (truncates if exceeded)'),
174
+ summary_only: z.boolean().optional().describe('Return only summary/metadata (default: false)'),
172
175
  },
173
176
  async (args) => {
174
177
  const result = await callRemoteServer('tools/call', {
175
- name: 'get_article',
178
+ name: 'get_content',
176
179
  arguments: args,
177
180
  }) as { content: Array<{ type: 'text'; text: string }> };
178
181
  return result;
@@ -182,27 +185,20 @@ Returns: Full article with title, content, summary, category, tags, version, and
182
185
  // Tool 3: Answer Question (Full RAG)
183
186
  server.tool(
184
187
  'answer_question',
185
- `Answer a customer question using the knowledge base with full RAG.
186
-
187
- This tool:
188
- 1. Searches for relevant articles in the knowledge base
189
- 2. If found: Generates an answer synthesizing the retrieved content
190
- 3. If not found: Optionally generates a new article to fill the gap
191
-
192
- Use for questions about:
193
- - Services and pricing
194
- - Tax filing requirements and deadlines
195
- - LLC formation process
196
- - Bank account options
197
- - Document requirements
198
- - How onboarding works
199
- - Any nonresident.tax topic
200
-
201
- The answer includes source references for transparency.`,
188
+ `Answer a customer question using the knowledge base with full RAG pipeline.
189
+
190
+ How it works:
191
+ 1. Searches for relevant articles AND published Q&A
192
+ 2. Uses knowledge graph to find related content via entities
193
+ 3. Generates a synthesized answer from all sources
194
+ 4. Returns source references for transparency
195
+
196
+ Use this when you need a complete answer with sources, not just search results.`,
202
197
  {
203
198
  question: z.string().describe('The customer question to answer'),
204
- context: z.string().optional().describe('Additional context about the question'),
205
- generate_if_not_found: z.boolean().optional().describe('Generate new article if no relevant content found (default: true)'),
199
+ context: z.string().optional().describe('Additional context'),
200
+ generate_if_not_found: z.boolean().optional().describe('Generate new article if not found (default: true)'),
201
+ use_graph: z.boolean().optional().describe('Enable graph-augmented retrieval (default: true)'),
206
202
  },
207
203
  async (args) => {
208
204
  const result = await callRemoteServer('tools/call', {
@@ -218,20 +214,11 @@ The answer includes source references for transparency.`,
218
214
  'log_unanswered',
219
215
  `Flag a customer question as unanswered for knowledge gap analysis.
220
216
 
221
- WHEN TO USE:
222
- - After search_knowledge returns no relevant results
223
- - When existing articles don't fully answer the question
224
- - When a customer asks something completely new
225
- - To track recurring questions that need dedicated articles
226
-
227
- WHY IT MATTERS:
228
- - Flagged questions appear in the admin "Knowledge Gaps" section
229
- - Helps prioritize which new articles to create
230
- - Builds a list of topics customers actually need
231
- - Improves the knowledge base over time`,
217
+ **CALL THIS** when search_knowledge returns no results or low confidence.
218
+ Flagged questions appear in admin "Knowledge Gaps" section.`,
232
219
  {
233
220
  query: z.string().describe('The question that could not be answered'),
234
- reason: z.string().optional().describe('Reason why the question could not be answered'),
221
+ reason: z.string().optional().describe('Reason why'),
235
222
  },
236
223
  async (args) => {
237
224
  const result = await callRemoteServer('tools/call', {
@@ -242,29 +229,22 @@ WHY IT MATTERS:
242
229
  }
243
230
  );
244
231
 
245
- // Tool 5: List Articles
232
+ // Tool 5: List Content (Unified - Articles + Q&A)
246
233
  server.tool(
247
- 'list_articles',
248
- `Browse articles in the knowledge base with optional filtering.
249
-
250
- Parameters:
251
- - limit: Number of articles (default 10, max 50)
252
- - category: Filter by category slug (use list_categories first)
234
+ 'list_content',
235
+ `Browse content in the knowledge base with optional filtering.
253
236
 
254
- Returns article ID, title, and summary for each match.
255
- Use get_article with the ID to retrieve full content.
256
-
257
- WORKFLOW:
258
- 1. Call list_categories to discover available categories
259
- 2. Call list_articles with specific category for focused results
260
- 3. Call get_article for articles that look relevant`,
237
+ Supports: articles, qa, or all (default).
238
+ Use list_categories first to discover available categories.`,
261
239
  {
262
- limit: z.number().optional().describe('Number of articles to return (max 50)'),
240
+ type: z.enum(['articles', 'qa', 'all']).optional().describe('Content type (default: all)'),
241
+ limit: z.number().optional().describe('Number of items (default: 10, max: 50)'),
263
242
  category: z.string().optional().describe('Filter by category slug'),
243
+ status: z.string().optional().describe('Filter Q&A by status: pending, draft, published'),
264
244
  },
265
245
  async (args) => {
266
246
  const result = await callRemoteServer('tools/call', {
267
- name: 'list_articles',
247
+ name: 'list_content',
268
248
  arguments: args,
269
249
  }) as { content: Array<{ type: 'text'; text: string }> };
270
250
  return result;
@@ -274,7 +254,7 @@ WORKFLOW:
274
254
  // Tool 6: Health Check
275
255
  server.tool(
276
256
  'health_check',
277
- 'Check the health status of the memory system.',
257
+ 'Check the health status of the memory system including article/Q&A counts.',
278
258
  {},
279
259
  async () => {
280
260
  const result = await callRemoteServer('tools/call', {
@@ -288,22 +268,8 @@ WORKFLOW:
288
268
  // Tool 7: List Categories
289
269
  server.tool(
290
270
  'list_categories',
291
- `List all categories in the knowledge base with article counts.
292
-
293
- CRITICAL FOR EFFICIENT SEARCH:
294
- Categories help you quickly narrow down to relevant content.
295
-
296
- Returns for each category:
297
- - slug: Category identifier for filtering
298
- - name: Human-readable name
299
- - count: Number of articles
300
- - description: What the category covers
301
-
302
- RECOMMENDED WORKFLOW:
303
- 1. Call list_categories to see what's available
304
- 2. Call list_articles with category slug
305
- 3. Call get_article for full content
306
- 4. Or use search_knowledge for semantic search across all`,
271
+ `List all categories in the knowledge base with content counts.
272
+ Use category slugs with list_content for filtered browsing.`,
307
273
  {},
308
274
  async () => {
309
275
  const result = await callRemoteServer('tools/call', {
@@ -315,21 +281,219 @@ RECOMMENDED WORKFLOW:
315
281
  );
316
282
 
317
283
  // ============================================================
318
- // FULL MODE TOOLS (only available with --full flag or MCP_MODE=full)
284
+ // KNOWLEDGE GRAPH TOOLS (available in all modes)
285
+ // ============================================================
286
+
287
+ // Tool 8: Find Related Articles
288
+ server.tool(
289
+ 'find_related',
290
+ 'Find articles related through shared entities or explicit relationships. Uses graph traversal.',
291
+ {
292
+ article_id: z.string().describe('Article ID to find relations for'),
293
+ max_depth: z.number().optional().describe('Relationship hops 1-5 (default: 2)'),
294
+ min_strength: z.number().optional().describe('Min strength 0-1 (default: 0.3)'),
295
+ limit: z.number().optional().describe('Max articles (default: 10)'),
296
+ },
297
+ async (args) => {
298
+ const result = await callRemoteServer('tools/call', {
299
+ name: 'find_related',
300
+ arguments: args,
301
+ }) as { content: Array<{ type: 'text'; text: string }> };
302
+ return result;
303
+ }
304
+ );
305
+
306
+ // Tool 9: Get Entities
307
+ server.tool(
308
+ 'get_entities',
309
+ 'Get named entities from an article (people, orgs, forms, states, etc.).',
310
+ {
311
+ article_id: z.string().describe('Article ID to get entities for'),
312
+ },
313
+ async (args) => {
314
+ const result = await callRemoteServer('tools/call', {
315
+ name: 'get_entities',
316
+ arguments: args,
317
+ }) as { content: Array<{ type: 'text'; text: string }> };
318
+ return result;
319
+ }
320
+ );
321
+
322
+ // Tool 10: Search Entities
323
+ server.tool(
324
+ 'search_entities',
325
+ 'Search for entities by name or type across the knowledge base.',
326
+ {
327
+ query: z.string().optional().describe('Search query for entity name'),
328
+ type: z.string().optional().describe('Filter: PERSON, ORGANIZATION, FORM, STATE, COUNTRY, DATE, AMOUNT, CONCEPT, REGULATION, SERVICE, DOCUMENT'),
329
+ limit: z.number().optional().describe('Max entities (default: 20)'),
330
+ },
331
+ async (args) => {
332
+ const result = await callRemoteServer('tools/call', {
333
+ name: 'search_entities',
334
+ arguments: args,
335
+ }) as { content: Array<{ type: 'text'; text: string }> };
336
+ return result;
337
+ }
338
+ );
339
+
340
+ // Tool 11: Get Knowledge Graph
341
+ server.tool(
342
+ 'get_knowledge_graph',
343
+ 'Get the knowledge graph structure for visualization. Returns nodes and edges.',
344
+ {
345
+ center_article_id: z.string().optional().describe('Center on this article'),
346
+ depth: z.number().optional().describe('Traversal depth 1-5 (default: 2)'),
347
+ include_entities: z.boolean().optional().describe('Include entity nodes (default: true)'),
348
+ include_metadata: z.boolean().optional().describe('Include full metadata (default: false)'),
349
+ limit: z.number().optional().describe('Max nodes (default: 20, max: 100)'),
350
+ },
351
+ async (args) => {
352
+ const result = await callRemoteServer('tools/call', {
353
+ name: 'get_knowledge_graph',
354
+ arguments: args,
355
+ }) as { content: Array<{ type: 'text'; text: string }> };
356
+ return result;
357
+ }
358
+ );
359
+
360
+ // ============================================================
361
+ // EPISODIC MEMORY TOOLS (available in all modes)
362
+ // ============================================================
363
+
364
+ // Tool 12: Start Conversation
365
+ server.tool(
366
+ 'start_conversation',
367
+ `Start a new conversation for tracking episodic memory.
368
+ Call at the beginning of each session to enable context continuity.`,
369
+ {
370
+ session_id: z.string().describe('Session ID (e.g., MCP session ID)'),
371
+ title: z.string().optional().describe('Conversation title'),
372
+ user_id: z.string().optional().describe('User ID for multi-user tracking'),
373
+ },
374
+ async (args) => {
375
+ const result = await callRemoteServer('tools/call', {
376
+ name: 'start_conversation',
377
+ arguments: args,
378
+ }) as { content: Array<{ type: 'text'; text: string }> };
379
+ return result;
380
+ }
381
+ );
382
+
383
+ // Tool 13: Add Message
384
+ server.tool(
385
+ 'add_message',
386
+ `Add a message to an existing conversation.
387
+ Call after each user message and assistant response.
388
+ User facts are automatically extracted.`,
389
+ {
390
+ conversation_id: z.string().describe('Conversation ID from start_conversation'),
391
+ role: z.enum(['user', 'assistant', 'system']).describe('Message role'),
392
+ content: z.string().describe('Message content'),
393
+ referenced_articles: z.string().optional().describe('Comma-separated article IDs referenced'),
394
+ },
395
+ async (args) => {
396
+ const result = await callRemoteServer('tools/call', {
397
+ name: 'add_message',
398
+ arguments: args,
399
+ }) as { content: Array<{ type: 'text'; text: string }> };
400
+ return result;
401
+ }
402
+ );
403
+
404
+ // Tool 14: Get Conversation
405
+ server.tool(
406
+ 'get_conversation',
407
+ 'Retrieve a conversation with all its messages by ID.',
408
+ {
409
+ conversation_id: z.string().describe('Conversation ID'),
410
+ },
411
+ async (args) => {
412
+ const result = await callRemoteServer('tools/call', {
413
+ name: 'get_conversation',
414
+ arguments: args,
415
+ }) as { content: Array<{ type: 'text'; text: string }> };
416
+ return result;
417
+ }
418
+ );
419
+
420
+ // Tool 15: List Conversations
421
+ server.tool(
422
+ 'list_conversations',
423
+ 'List conversations for a session or user. Returns recent first.',
424
+ {
425
+ session_id: z.string().optional().describe('Filter by session ID'),
426
+ user_id: z.string().optional().describe('Filter by user ID'),
427
+ limit: z.number().optional().describe('Max conversations (default: 20)'),
428
+ },
429
+ async (args) => {
430
+ const result = await callRemoteServer('tools/call', {
431
+ name: 'list_conversations',
432
+ arguments: args,
433
+ }) as { content: Array<{ type: 'text'; text: string }> };
434
+ return result;
435
+ }
436
+ );
437
+
438
+ // Tool 16: Get Session Context
439
+ server.tool(
440
+ 'get_session_context',
441
+ `Get recent conversation context for a session.
442
+ Call when user refers to prior conversation or asks follow-up questions.`,
443
+ {
444
+ session_id: z.string().describe('Session ID to get context for'),
445
+ message_limit: z.number().optional().describe('Max messages (default: 10)'),
446
+ },
447
+ async (args) => {
448
+ const result = await callRemoteServer('tools/call', {
449
+ name: 'get_session_context',
450
+ arguments: args,
451
+ }) as { content: Array<{ type: 'text'; text: string }> };
452
+ return result;
453
+ }
454
+ );
455
+
456
+ // ============================================================
457
+ // Q&A TOOLS (available in all modes)
458
+ // ============================================================
459
+
460
+ // Tool 17: Add Question
461
+ server.tool(
462
+ 'add_question',
463
+ `Add a new question to the Q&A store for later answering.
464
+ Use to capture questions from external channels.`,
465
+ {
466
+ question: z.string().describe('The question text'),
467
+ category: z.string().optional().describe('Category slug'),
468
+ tags: z.string().optional().describe('Comma-separated tags'),
469
+ priority: z.string().optional().describe('Priority: low, medium, high, critical'),
470
+ source: z.string().optional().describe('Source: mcp, email, manual, import'),
471
+ },
472
+ async (args) => {
473
+ const result = await callRemoteServer('tools/call', {
474
+ name: 'add_question',
475
+ arguments: args,
476
+ }) as { content: Array<{ type: 'text'; text: string }> };
477
+ return result;
478
+ }
479
+ );
480
+
481
+ // ============================================================
482
+ // FULL MODE TOOLS (only with --full flag or MCP_MODE=full)
319
483
  // ============================================================
320
484
 
321
485
  if (isFullMode) {
322
- // Tool 8: Add Article
486
+ // Tool 18: Add Article
323
487
  server.tool(
324
488
  'add_article',
325
- 'Create a new article in the knowledge base. Automatically generates embedding for semantic search.',
489
+ 'Create a new article in the knowledge base. Generates embedding automatically.',
326
490
  {
327
491
  title: z.string().describe('Article title'),
328
- content: z.string().describe('Article content in markdown format'),
329
- summary: z.string().optional().describe('Brief summary (auto-generated if not provided)'),
330
- category: z.string().optional().describe('Category (e.g., federal-tax, state-compliance)'),
492
+ content: z.string().describe('Article content in markdown'),
493
+ summary: z.string().optional().describe('Brief summary (auto-generated if omitted)'),
494
+ category: z.string().optional().describe('Category slug'),
331
495
  tags: z.string().optional().describe('Comma-separated tags'),
332
- source: z.string().optional().describe('Source of the article (e.g., manual, irs.gov)'),
496
+ source: z.string().optional().describe('Source (e.g., manual, irs.gov)'),
333
497
  },
334
498
  async (args) => {
335
499
  const result = await callRemoteServer('tools/call', {
@@ -340,7 +504,7 @@ RECOMMENDED WORKFLOW:
340
504
  }
341
505
  );
342
506
 
343
- // Tool 9: Edit Article
507
+ // Tool 19: Edit Article
344
508
  server.tool(
345
509
  'edit_article',
346
510
  'Update an existing article. Re-generates embedding if content changes.',
@@ -361,13 +525,13 @@ RECOMMENDED WORKFLOW:
361
525
  }
362
526
  );
363
527
 
364
- // Tool 10: Remove Article
528
+ // Tool 20: Remove Article
365
529
  server.tool(
366
530
  'remove_article',
367
- 'Soft-delete an article from the knowledge base. Article is marked as deleted but not permanently removed.',
531
+ 'Soft-delete an article. Marked as deleted but not permanently removed.',
368
532
  {
369
533
  article_id: z.string().describe('Article ID to remove'),
370
- reason: z.string().optional().describe('Reason for deletion (for audit log)'),
534
+ reason: z.string().optional().describe('Reason for deletion'),
371
535
  },
372
536
  async (args) => {
373
537
  const result = await callRemoteServer('tools/call', {
@@ -378,14 +542,14 @@ RECOMMENDED WORKFLOW:
378
542
  }
379
543
  );
380
544
 
381
- // Tool 11: Rate Answer
545
+ // Tool 21: Rate Answer
382
546
  server.tool(
383
547
  'rate_answer',
384
- 'Rate a previous answer as helpful, unhelpful, or neutral. Helps improve the knowledge base.',
548
+ 'Rate a previous answer as helpful, unhelpful, or neutral.',
385
549
  {
386
- query_id: z.string().describe('Query log ID from a previous answer_question response'),
550
+ query_id: z.string().describe('Query log ID from answer_question'),
387
551
  rating: z.number().describe('Rating: -1 (unhelpful), 0 (neutral), 1 (helpful)'),
388
- feedback: z.string().optional().describe('Optional text feedback'),
552
+ feedback: z.string().optional().describe('Optional feedback text'),
389
553
  },
390
554
  async (args) => {
391
555
  const result = await callRemoteServer('tools/call', {
package/.npmrc.bak DELETED
@@ -1,2 +0,0 @@
1
- @pragmatic-growth:registry=https://registry.npmjs.org/
2
- //registry.npmjs.org/:_authToken=${NPM_TOKEN}