@pragmatic-growth/memory-mcp 2.1.1 → 2.3.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/.npmrc.backup +2 -0
- package/README.md +53 -19
- package/dist/index.d.ts +5 -1
- package/dist/index.js +207 -100
- package/package.json +2 -2
- package/src/index.ts +274 -104
package/.npmrc.backup
ADDED
package/README.md
CHANGED
|
@@ -41,14 +41,11 @@ For clients that only support stdio (Raycast, local tools):
|
|
|
41
41
|
Both transports support two operating modes:
|
|
42
42
|
|
|
43
43
|
### Read-Only Mode (Default)
|
|
44
|
-
Safe for general use -
|
|
45
|
-
-
|
|
46
|
-
-
|
|
47
|
-
-
|
|
48
|
-
-
|
|
49
|
-
- `list_categories` - List categories with article counts
|
|
50
|
-
- `log_unanswered` - Flag knowledge gaps
|
|
51
|
-
- `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
|
|
52
49
|
|
|
53
50
|
### Full Mode (with `--full` flag)
|
|
54
51
|
Includes all read-only tools PLUS write operations:
|
|
@@ -113,28 +110,53 @@ MCP_API_KEY=your-key memory-mcp --full
|
|
|
113
110
|
|
|
114
111
|
Note: The `--full` CLI flag takes precedence over `MCP_MODE` env var.
|
|
115
112
|
|
|
116
|
-
## Available Tools
|
|
113
|
+
## Available Tools (21 Total)
|
|
117
114
|
|
|
118
|
-
### Core Tools (
|
|
115
|
+
### Core Knowledge Tools (7)
|
|
119
116
|
|
|
120
117
|
| Tool | Description |
|
|
121
118
|
|------|-------------|
|
|
122
|
-
| `search_knowledge` | Semantic search
|
|
123
|
-
| `
|
|
124
|
-
| `
|
|
125
|
-
| `list_articles` | Browse articles with optional category filter |
|
|
126
|
-
| `list_categories` | List all categories with article counts |
|
|
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 |
|
|
127
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 |
|
|
128
125
|
| `health_check` | Check system status and connectivity |
|
|
129
126
|
|
|
130
|
-
###
|
|
127
|
+
### Knowledge Graph Tools (4)
|
|
131
128
|
|
|
132
129
|
| Tool | Description |
|
|
133
130
|
|------|-------------|
|
|
134
|
-
| `
|
|
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)
|
|
147
|
+
|
|
148
|
+
| Tool | Description |
|
|
149
|
+
|------|-------------|
|
|
150
|
+
| `add_question` | Add question for later expert answering |
|
|
151
|
+
|
|
152
|
+
### Full Mode Tools (4)
|
|
153
|
+
|
|
154
|
+
| Tool | Description |
|
|
155
|
+
|------|-------------|
|
|
156
|
+
| `add_article` | Create a new article with automatic embedding |
|
|
135
157
|
| `edit_article` | Update article content, metadata, or category |
|
|
136
|
-
| `remove_article` | Soft-delete an article
|
|
137
|
-
| `rate_answer` | Rate previous answers: -1
|
|
158
|
+
| `remove_article` | Soft-delete an article |
|
|
159
|
+
| `rate_answer` | Rate previous answers: -1, 0, or 1 |
|
|
138
160
|
|
|
139
161
|
## MCP Resources
|
|
140
162
|
|
|
@@ -167,6 +189,18 @@ For full mode to work:
|
|
|
167
189
|
1. The stdio proxy must have `--full` flag OR `MCP_MODE=full`
|
|
168
190
|
2. The remote server must have `MCP_MODE=full` in its environment
|
|
169
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
|
+
|
|
170
204
|
## License
|
|
171
205
|
|
|
172
206
|
MIT
|
package/dist/index.d.ts
CHANGED
|
@@ -11,8 +11,12 @@
|
|
|
11
11
|
* npx @pragmatic-growth/memory-mcp --edit # Alias for --full
|
|
12
12
|
*
|
|
13
13
|
* Environment variables:
|
|
14
|
-
* MCP_API_KEY - API key for authentication (required)
|
|
14
|
+
* MCP_API_KEY - Clerk API key for authentication (required)
|
|
15
15
|
* MCP_SERVER_URL - Server URL (default: https://pg-memory-production.up.railway.app/api/mcp)
|
|
16
16
|
* MCP_MODE - Mode: 'readonly' (default) or 'full' for edit capabilities
|
|
17
|
+
*
|
|
18
|
+
* Authentication:
|
|
19
|
+
* Uses Clerk API key authentication via X-API-Key header.
|
|
20
|
+
* Get your API key from the Clerk Dashboard or the app's API Docs page.
|
|
17
21
|
*/
|
|
18
22
|
export {};
|
package/dist/index.js
CHANGED
|
@@ -11,9 +11,13 @@
|
|
|
11
11
|
* npx @pragmatic-growth/memory-mcp --edit # Alias for --full
|
|
12
12
|
*
|
|
13
13
|
* Environment variables:
|
|
14
|
-
* MCP_API_KEY - API key for authentication (required)
|
|
14
|
+
* MCP_API_KEY - Clerk API key for authentication (required)
|
|
15
15
|
* MCP_SERVER_URL - Server URL (default: https://pg-memory-production.up.railway.app/api/mcp)
|
|
16
16
|
* MCP_MODE - Mode: 'readonly' (default) or 'full' for edit capabilities
|
|
17
|
+
*
|
|
18
|
+
* Authentication:
|
|
19
|
+
* Uses Clerk API key authentication via X-API-Key header.
|
|
20
|
+
* Get your API key from the Clerk Dashboard or the app's API Docs page.
|
|
17
21
|
*/
|
|
18
22
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
19
23
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
@@ -28,18 +32,20 @@ const SERVER_URL = process.env.MCP_SERVER_URL || 'https://pg-memory-production.u
|
|
|
28
32
|
const API_KEY = process.env.MCP_API_KEY;
|
|
29
33
|
if (!API_KEY) {
|
|
30
34
|
console.error('Error: MCP_API_KEY environment variable is required');
|
|
31
|
-
console.error('
|
|
35
|
+
console.error('Get your Clerk API key from the Clerk Dashboard or app API Docs page');
|
|
36
|
+
console.error('Set it in your MCP client configuration under "env"');
|
|
32
37
|
process.exit(1);
|
|
33
38
|
}
|
|
34
39
|
// HTTP session management
|
|
35
40
|
let sessionId = null;
|
|
36
41
|
/**
|
|
37
42
|
* Make an HTTP request to the remote MCP server.
|
|
43
|
+
* Uses X-API-Key header for Clerk API key authentication.
|
|
38
44
|
*/
|
|
39
45
|
async function callRemoteServer(method, params) {
|
|
40
46
|
const headers = {
|
|
41
47
|
'Content-Type': 'application/json',
|
|
42
|
-
'
|
|
48
|
+
'X-API-Key': API_KEY, // Safe: already validated above
|
|
43
49
|
};
|
|
44
50
|
if (sessionId) {
|
|
45
51
|
headers['Mcp-Session-Id'] = sessionId;
|
|
@@ -75,7 +81,7 @@ async function initializeRemoteSession() {
|
|
|
75
81
|
capabilities: {},
|
|
76
82
|
clientInfo: {
|
|
77
83
|
name: 'pg-memory-stdio',
|
|
78
|
-
version: '2.
|
|
84
|
+
version: '2.3.0',
|
|
79
85
|
},
|
|
80
86
|
});
|
|
81
87
|
}
|
|
@@ -95,28 +101,30 @@ async function main() {
|
|
|
95
101
|
// Create local MCP server
|
|
96
102
|
const server = new McpServer({
|
|
97
103
|
name: 'pg-memory',
|
|
98
|
-
version: '2.
|
|
104
|
+
version: '2.3.0',
|
|
99
105
|
description: `PG-Memory knowledge base (${modeLabel} mode)`,
|
|
100
106
|
});
|
|
101
107
|
// ============================================================
|
|
102
|
-
//
|
|
108
|
+
// CORE KNOWLEDGE TOOLS (available in all modes)
|
|
103
109
|
// ============================================================
|
|
104
|
-
// Tool 1: Search Knowledge (
|
|
110
|
+
// Tool 1: Search Knowledge (Articles + Q&A)
|
|
105
111
|
server.tool('search_knowledge', `Search the nonresident.tax knowledge base using semantic similarity.
|
|
106
112
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
113
|
+
**YOU MUST CALL THIS TOOL** before answering any domain-specific question.
|
|
114
|
+
|
|
115
|
+
Topics covered:
|
|
110
116
|
- Tax compliance: Form 5472, Form 1120, ECI rules, penalties, deadlines
|
|
111
117
|
- Company formation: Wyoming LLC, Delaware LLC, single-member LLC
|
|
112
118
|
- Banking: Mercury, Wise, US bank account requirements
|
|
113
|
-
-
|
|
114
|
-
- Dissolution: LLC closure, final filings, IRS compliance
|
|
119
|
+
- Services & pricing: LLC formation, tax filing, registered agent
|
|
115
120
|
|
|
116
|
-
Returns relevant articles with relevance scores.
|
|
121
|
+
Returns relevant articles AND Q&A entries with relevance scores.
|
|
122
|
+
Use get_content with the ID to retrieve full content.`, {
|
|
117
123
|
query: z.string().describe('The question or topic to search for'),
|
|
118
|
-
limit: z.number().optional().describe('Maximum
|
|
119
|
-
threshold: z.number().optional().describe('Minimum similarity
|
|
124
|
+
limit: z.number().optional().describe('Maximum results to return (default: 5)'),
|
|
125
|
+
threshold: z.number().optional().describe('Minimum similarity 0-1 (default: 0.55)'),
|
|
126
|
+
include_articles: z.boolean().optional().describe('Include articles (default: true)'),
|
|
127
|
+
include_qa: z.boolean().optional().describe('Include Q&A entries (default: true)'),
|
|
120
128
|
}, async (args) => {
|
|
121
129
|
const result = await callRemoteServer('tools/call', {
|
|
122
130
|
name: 'search_knowledge',
|
|
@@ -124,44 +132,38 @@ Returns relevant articles with relevance scores. Use get_article to retrieve ful
|
|
|
124
132
|
});
|
|
125
133
|
return result;
|
|
126
134
|
});
|
|
127
|
-
// Tool 2: Get
|
|
128
|
-
server.tool('
|
|
135
|
+
// Tool 2: Get Content (Unified - Articles + Q&A)
|
|
136
|
+
server.tool('get_content', `Retrieve complete content from the knowledge base by ID.
|
|
129
137
|
|
|
130
|
-
|
|
131
|
-
-
|
|
132
|
-
-
|
|
133
|
-
- You want to verify specific details from search results
|
|
138
|
+
Automatically detects content type from ID prefix:
|
|
139
|
+
- 'art_*' → Article (knowledge base article)
|
|
140
|
+
- 'qa_*' → Q&A entry (question and answer)
|
|
134
141
|
|
|
135
|
-
|
|
136
|
-
|
|
142
|
+
Use summary_only=true first to check content size, then fetch with max_length if needed.`, {
|
|
143
|
+
content_id: z.string().describe('Content ID (e.g., art_abc123 or qa_xyz789)'),
|
|
144
|
+
max_length: z.number().optional().describe('Max content length in chars (truncates if exceeded)'),
|
|
145
|
+
summary_only: z.boolean().optional().describe('Return only summary/metadata (default: false)'),
|
|
137
146
|
}, async (args) => {
|
|
138
147
|
const result = await callRemoteServer('tools/call', {
|
|
139
|
-
name: '
|
|
148
|
+
name: 'get_content',
|
|
140
149
|
arguments: args,
|
|
141
150
|
});
|
|
142
151
|
return result;
|
|
143
152
|
});
|
|
144
153
|
// 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
|
|
154
|
+
server.tool('answer_question', `Answer a customer question using the knowledge base with full RAG pipeline.
|
|
151
155
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
- Document requirements
|
|
158
|
-
- How onboarding works
|
|
159
|
-
- Any nonresident.tax topic
|
|
156
|
+
How it works:
|
|
157
|
+
1. Searches for relevant articles AND published Q&A
|
|
158
|
+
2. Uses knowledge graph to find related content via entities
|
|
159
|
+
3. Generates a synthesized answer from all sources
|
|
160
|
+
4. Returns source references for transparency
|
|
160
161
|
|
|
161
|
-
|
|
162
|
+
Use this when you need a complete answer with sources, not just search results.`, {
|
|
162
163
|
question: z.string().describe('The customer question to answer'),
|
|
163
|
-
context: z.string().optional().describe('Additional context
|
|
164
|
-
generate_if_not_found: z.boolean().optional().describe('Generate new article if
|
|
164
|
+
context: z.string().optional().describe('Additional context'),
|
|
165
|
+
generate_if_not_found: z.boolean().optional().describe('Generate new article if not found (default: true)'),
|
|
166
|
+
use_graph: z.boolean().optional().describe('Enable graph-augmented retrieval (default: true)'),
|
|
165
167
|
}, async (args) => {
|
|
166
168
|
const result = await callRemoteServer('tools/call', {
|
|
167
169
|
name: 'answer_question',
|
|
@@ -172,19 +174,10 @@ The answer includes source references for transparency.`, {
|
|
|
172
174
|
// Tool 4: Log Unanswered
|
|
173
175
|
server.tool('log_unanswered', `Flag a customer question as unanswered for knowledge gap analysis.
|
|
174
176
|
|
|
175
|
-
|
|
176
|
-
|
|
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`, {
|
|
177
|
+
**CALL THIS** when search_knowledge returns no results or low confidence.
|
|
178
|
+
Flagged questions appear in admin "Knowledge Gaps" section.`, {
|
|
186
179
|
query: z.string().describe('The question that could not be answered'),
|
|
187
|
-
reason: z.string().optional().describe('Reason why
|
|
180
|
+
reason: z.string().optional().describe('Reason why'),
|
|
188
181
|
}, async (args) => {
|
|
189
182
|
const result = await callRemoteServer('tools/call', {
|
|
190
183
|
name: 'log_unanswered',
|
|
@@ -192,31 +185,24 @@ WHY IT MATTERS:
|
|
|
192
185
|
});
|
|
193
186
|
return result;
|
|
194
187
|
});
|
|
195
|
-
// Tool 5: List Articles
|
|
196
|
-
server.tool('
|
|
197
|
-
|
|
198
|
-
Parameters:
|
|
199
|
-
- limit: Number of articles (default 10, max 50)
|
|
200
|
-
- category: Filter by category slug (use list_categories first)
|
|
188
|
+
// Tool 5: List Content (Unified - Articles + Q&A)
|
|
189
|
+
server.tool('list_content', `Browse content in the knowledge base with optional filtering.
|
|
201
190
|
|
|
202
|
-
|
|
203
|
-
Use
|
|
204
|
-
|
|
205
|
-
|
|
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)'),
|
|
191
|
+
Supports: articles, qa, or all (default).
|
|
192
|
+
Use list_categories first to discover available categories.`, {
|
|
193
|
+
type: z.enum(['articles', 'qa', 'all']).optional().describe('Content type (default: all)'),
|
|
194
|
+
limit: z.number().optional().describe('Number of items (default: 10, max: 50)'),
|
|
210
195
|
category: z.string().optional().describe('Filter by category slug'),
|
|
196
|
+
status: z.string().optional().describe('Filter Q&A by status: pending, draft, published'),
|
|
211
197
|
}, async (args) => {
|
|
212
198
|
const result = await callRemoteServer('tools/call', {
|
|
213
|
-
name: '
|
|
199
|
+
name: 'list_content',
|
|
214
200
|
arguments: args,
|
|
215
201
|
});
|
|
216
202
|
return result;
|
|
217
203
|
});
|
|
218
204
|
// Tool 6: Health Check
|
|
219
|
-
server.tool('health_check', 'Check the health status of the memory system.', {}, async () => {
|
|
205
|
+
server.tool('health_check', 'Check the health status of the memory system including article/Q&A counts.', {}, async () => {
|
|
220
206
|
const result = await callRemoteServer('tools/call', {
|
|
221
207
|
name: 'health_check',
|
|
222
208
|
arguments: {},
|
|
@@ -224,22 +210,8 @@ WORKFLOW:
|
|
|
224
210
|
return result;
|
|
225
211
|
});
|
|
226
212
|
// Tool 7: List Categories
|
|
227
|
-
server.tool('list_categories', `List all categories in the knowledge base with
|
|
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 () => {
|
|
213
|
+
server.tool('list_categories', `List all categories in the knowledge base with content counts.
|
|
214
|
+
Use category slugs with list_content for filtered browsing.`, {}, async () => {
|
|
243
215
|
const result = await callRemoteServer('tools/call', {
|
|
244
216
|
name: 'list_categories',
|
|
245
217
|
arguments: {},
|
|
@@ -247,17 +219,152 @@ RECOMMENDED WORKFLOW:
|
|
|
247
219
|
return result;
|
|
248
220
|
});
|
|
249
221
|
// ============================================================
|
|
250
|
-
//
|
|
222
|
+
// KNOWLEDGE GRAPH TOOLS (available in all modes)
|
|
223
|
+
// ============================================================
|
|
224
|
+
// Tool 8: Find Related Articles
|
|
225
|
+
server.tool('find_related', 'Find articles related through shared entities or explicit relationships. Uses graph traversal.', {
|
|
226
|
+
article_id: z.string().describe('Article ID to find relations for'),
|
|
227
|
+
max_depth: z.number().optional().describe('Relationship hops 1-5 (default: 2)'),
|
|
228
|
+
min_strength: z.number().optional().describe('Min strength 0-1 (default: 0.3)'),
|
|
229
|
+
limit: z.number().optional().describe('Max articles (default: 10)'),
|
|
230
|
+
}, async (args) => {
|
|
231
|
+
const result = await callRemoteServer('tools/call', {
|
|
232
|
+
name: 'find_related',
|
|
233
|
+
arguments: args,
|
|
234
|
+
});
|
|
235
|
+
return result;
|
|
236
|
+
});
|
|
237
|
+
// Tool 9: Get Entities
|
|
238
|
+
server.tool('get_entities', 'Get named entities from an article (people, orgs, forms, states, etc.).', {
|
|
239
|
+
article_id: z.string().describe('Article ID to get entities for'),
|
|
240
|
+
}, async (args) => {
|
|
241
|
+
const result = await callRemoteServer('tools/call', {
|
|
242
|
+
name: 'get_entities',
|
|
243
|
+
arguments: args,
|
|
244
|
+
});
|
|
245
|
+
return result;
|
|
246
|
+
});
|
|
247
|
+
// Tool 10: Search Entities
|
|
248
|
+
server.tool('search_entities', 'Search for entities by name or type across the knowledge base.', {
|
|
249
|
+
query: z.string().optional().describe('Search query for entity name'),
|
|
250
|
+
type: z.string().optional().describe('Filter: PERSON, ORGANIZATION, FORM, STATE, COUNTRY, DATE, AMOUNT, CONCEPT, REGULATION, SERVICE, DOCUMENT'),
|
|
251
|
+
limit: z.number().optional().describe('Max entities (default: 20)'),
|
|
252
|
+
}, async (args) => {
|
|
253
|
+
const result = await callRemoteServer('tools/call', {
|
|
254
|
+
name: 'search_entities',
|
|
255
|
+
arguments: args,
|
|
256
|
+
});
|
|
257
|
+
return result;
|
|
258
|
+
});
|
|
259
|
+
// Tool 11: Get Knowledge Graph
|
|
260
|
+
server.tool('get_knowledge_graph', 'Get the knowledge graph structure for visualization. Returns nodes and edges.', {
|
|
261
|
+
center_article_id: z.string().optional().describe('Center on this article'),
|
|
262
|
+
depth: z.number().optional().describe('Traversal depth 1-5 (default: 2)'),
|
|
263
|
+
include_entities: z.boolean().optional().describe('Include entity nodes (default: true)'),
|
|
264
|
+
include_metadata: z.boolean().optional().describe('Include full metadata (default: false)'),
|
|
265
|
+
limit: z.number().optional().describe('Max nodes (default: 20, max: 100)'),
|
|
266
|
+
}, async (args) => {
|
|
267
|
+
const result = await callRemoteServer('tools/call', {
|
|
268
|
+
name: 'get_knowledge_graph',
|
|
269
|
+
arguments: args,
|
|
270
|
+
});
|
|
271
|
+
return result;
|
|
272
|
+
});
|
|
273
|
+
// ============================================================
|
|
274
|
+
// EPISODIC MEMORY TOOLS (available in all modes)
|
|
275
|
+
// ============================================================
|
|
276
|
+
// Tool 12: Start Conversation
|
|
277
|
+
server.tool('start_conversation', `Start a new conversation for tracking episodic memory.
|
|
278
|
+
Call at the beginning of each session to enable context continuity.`, {
|
|
279
|
+
session_id: z.string().describe('Session ID (e.g., MCP session ID)'),
|
|
280
|
+
title: z.string().optional().describe('Conversation title'),
|
|
281
|
+
user_id: z.string().optional().describe('User ID for multi-user tracking'),
|
|
282
|
+
}, async (args) => {
|
|
283
|
+
const result = await callRemoteServer('tools/call', {
|
|
284
|
+
name: 'start_conversation',
|
|
285
|
+
arguments: args,
|
|
286
|
+
});
|
|
287
|
+
return result;
|
|
288
|
+
});
|
|
289
|
+
// Tool 13: Add Message
|
|
290
|
+
server.tool('add_message', `Add a message to an existing conversation.
|
|
291
|
+
Call after each user message and assistant response.
|
|
292
|
+
User facts are automatically extracted.`, {
|
|
293
|
+
conversation_id: z.string().describe('Conversation ID from start_conversation'),
|
|
294
|
+
role: z.enum(['user', 'assistant', 'system']).describe('Message role'),
|
|
295
|
+
content: z.string().describe('Message content'),
|
|
296
|
+
referenced_articles: z.string().optional().describe('Comma-separated article IDs referenced'),
|
|
297
|
+
}, async (args) => {
|
|
298
|
+
const result = await callRemoteServer('tools/call', {
|
|
299
|
+
name: 'add_message',
|
|
300
|
+
arguments: args,
|
|
301
|
+
});
|
|
302
|
+
return result;
|
|
303
|
+
});
|
|
304
|
+
// Tool 14: Get Conversation
|
|
305
|
+
server.tool('get_conversation', 'Retrieve a conversation with all its messages by ID.', {
|
|
306
|
+
conversation_id: z.string().describe('Conversation ID'),
|
|
307
|
+
}, async (args) => {
|
|
308
|
+
const result = await callRemoteServer('tools/call', {
|
|
309
|
+
name: 'get_conversation',
|
|
310
|
+
arguments: args,
|
|
311
|
+
});
|
|
312
|
+
return result;
|
|
313
|
+
});
|
|
314
|
+
// Tool 15: List Conversations
|
|
315
|
+
server.tool('list_conversations', 'List conversations for a session or user. Returns recent first.', {
|
|
316
|
+
session_id: z.string().optional().describe('Filter by session ID'),
|
|
317
|
+
user_id: z.string().optional().describe('Filter by user ID'),
|
|
318
|
+
limit: z.number().optional().describe('Max conversations (default: 20)'),
|
|
319
|
+
}, async (args) => {
|
|
320
|
+
const result = await callRemoteServer('tools/call', {
|
|
321
|
+
name: 'list_conversations',
|
|
322
|
+
arguments: args,
|
|
323
|
+
});
|
|
324
|
+
return result;
|
|
325
|
+
});
|
|
326
|
+
// Tool 16: Get Session Context
|
|
327
|
+
server.tool('get_session_context', `Get recent conversation context for a session.
|
|
328
|
+
Call when user refers to prior conversation or asks follow-up questions.`, {
|
|
329
|
+
session_id: z.string().describe('Session ID to get context for'),
|
|
330
|
+
message_limit: z.number().optional().describe('Max messages (default: 10)'),
|
|
331
|
+
}, async (args) => {
|
|
332
|
+
const result = await callRemoteServer('tools/call', {
|
|
333
|
+
name: 'get_session_context',
|
|
334
|
+
arguments: args,
|
|
335
|
+
});
|
|
336
|
+
return result;
|
|
337
|
+
});
|
|
338
|
+
// ============================================================
|
|
339
|
+
// Q&A TOOLS (available in all modes)
|
|
340
|
+
// ============================================================
|
|
341
|
+
// Tool 17: Add Question
|
|
342
|
+
server.tool('add_question', `Add a new question to the Q&A store for later answering.
|
|
343
|
+
Use to capture questions from external channels.`, {
|
|
344
|
+
question: z.string().describe('The question text'),
|
|
345
|
+
category: z.string().optional().describe('Category slug'),
|
|
346
|
+
tags: z.string().optional().describe('Comma-separated tags'),
|
|
347
|
+
priority: z.string().optional().describe('Priority: low, medium, high, critical'),
|
|
348
|
+
source: z.string().optional().describe('Source: mcp, email, manual, import'),
|
|
349
|
+
}, async (args) => {
|
|
350
|
+
const result = await callRemoteServer('tools/call', {
|
|
351
|
+
name: 'add_question',
|
|
352
|
+
arguments: args,
|
|
353
|
+
});
|
|
354
|
+
return result;
|
|
355
|
+
});
|
|
356
|
+
// ============================================================
|
|
357
|
+
// FULL MODE TOOLS (only with --full flag or MCP_MODE=full)
|
|
251
358
|
// ============================================================
|
|
252
359
|
if (isFullMode) {
|
|
253
|
-
// Tool
|
|
254
|
-
server.tool('add_article', 'Create a new article in the knowledge base.
|
|
360
|
+
// Tool 18: Add Article
|
|
361
|
+
server.tool('add_article', 'Create a new article in the knowledge base. Generates embedding automatically.', {
|
|
255
362
|
title: z.string().describe('Article title'),
|
|
256
|
-
content: z.string().describe('Article content in markdown
|
|
257
|
-
summary: z.string().optional().describe('Brief summary (auto-generated if
|
|
258
|
-
category: z.string().optional().describe('Category
|
|
363
|
+
content: z.string().describe('Article content in markdown'),
|
|
364
|
+
summary: z.string().optional().describe('Brief summary (auto-generated if omitted)'),
|
|
365
|
+
category: z.string().optional().describe('Category slug'),
|
|
259
366
|
tags: z.string().optional().describe('Comma-separated tags'),
|
|
260
|
-
source: z.string().optional().describe('Source
|
|
367
|
+
source: z.string().optional().describe('Source (e.g., manual, irs.gov)'),
|
|
261
368
|
}, async (args) => {
|
|
262
369
|
const result = await callRemoteServer('tools/call', {
|
|
263
370
|
name: 'add_article',
|
|
@@ -265,7 +372,7 @@ RECOMMENDED WORKFLOW:
|
|
|
265
372
|
});
|
|
266
373
|
return result;
|
|
267
374
|
});
|
|
268
|
-
// Tool
|
|
375
|
+
// Tool 19: Edit Article
|
|
269
376
|
server.tool('edit_article', 'Update an existing article. Re-generates embedding if content changes.', {
|
|
270
377
|
article_id: z.string().describe('Article ID to update'),
|
|
271
378
|
title: z.string().optional().describe('New title'),
|
|
@@ -280,10 +387,10 @@ RECOMMENDED WORKFLOW:
|
|
|
280
387
|
});
|
|
281
388
|
return result;
|
|
282
389
|
});
|
|
283
|
-
// Tool
|
|
284
|
-
server.tool('remove_article', 'Soft-delete an article
|
|
390
|
+
// Tool 20: Remove Article
|
|
391
|
+
server.tool('remove_article', 'Soft-delete an article. Marked as deleted but not permanently removed.', {
|
|
285
392
|
article_id: z.string().describe('Article ID to remove'),
|
|
286
|
-
reason: z.string().optional().describe('Reason for deletion
|
|
393
|
+
reason: z.string().optional().describe('Reason for deletion'),
|
|
287
394
|
}, async (args) => {
|
|
288
395
|
const result = await callRemoteServer('tools/call', {
|
|
289
396
|
name: 'remove_article',
|
|
@@ -291,11 +398,11 @@ RECOMMENDED WORKFLOW:
|
|
|
291
398
|
});
|
|
292
399
|
return result;
|
|
293
400
|
});
|
|
294
|
-
// Tool
|
|
295
|
-
server.tool('rate_answer', 'Rate a previous answer as helpful, unhelpful, or neutral.
|
|
296
|
-
query_id: z.string().describe('Query log ID from
|
|
401
|
+
// Tool 21: Rate Answer
|
|
402
|
+
server.tool('rate_answer', 'Rate a previous answer as helpful, unhelpful, or neutral.', {
|
|
403
|
+
query_id: z.string().describe('Query log ID from answer_question'),
|
|
297
404
|
rating: z.number().describe('Rating: -1 (unhelpful), 0 (neutral), 1 (helpful)'),
|
|
298
|
-
feedback: z.string().optional().describe('Optional text
|
|
405
|
+
feedback: z.string().optional().describe('Optional feedback text'),
|
|
299
406
|
}, async (args) => {
|
|
300
407
|
const result = await callRemoteServer('tools/call', {
|
|
301
408
|
name: 'rate_answer',
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pragmatic-growth/memory-mcp",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "Stdio proxy for PG-Memory - connects stdio-based MCP clients (
|
|
3
|
+
"version": "2.3.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. Supports both read-only and full edit modes.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"memory-mcp": "./dist/index.js"
|
package/src/index.ts
CHANGED
|
@@ -11,9 +11,13 @@
|
|
|
11
11
|
* npx @pragmatic-growth/memory-mcp --edit # Alias for --full
|
|
12
12
|
*
|
|
13
13
|
* Environment variables:
|
|
14
|
-
* MCP_API_KEY - API key for authentication (required)
|
|
14
|
+
* MCP_API_KEY - Clerk API key for authentication (required)
|
|
15
15
|
* MCP_SERVER_URL - Server URL (default: https://pg-memory-production.up.railway.app/api/mcp)
|
|
16
16
|
* MCP_MODE - Mode: 'readonly' (default) or 'full' for edit capabilities
|
|
17
|
+
*
|
|
18
|
+
* Authentication:
|
|
19
|
+
* Uses Clerk API key authentication via X-API-Key header.
|
|
20
|
+
* Get your API key from the Clerk Dashboard or the app's API Docs page.
|
|
17
21
|
*/
|
|
18
22
|
|
|
19
23
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
@@ -32,7 +36,8 @@ const API_KEY = process.env.MCP_API_KEY;
|
|
|
32
36
|
|
|
33
37
|
if (!API_KEY) {
|
|
34
38
|
console.error('Error: MCP_API_KEY environment variable is required');
|
|
35
|
-
console.error('
|
|
39
|
+
console.error('Get your Clerk API key from the Clerk Dashboard or app API Docs page');
|
|
40
|
+
console.error('Set it in your MCP client configuration under "env"');
|
|
36
41
|
process.exit(1);
|
|
37
42
|
}
|
|
38
43
|
|
|
@@ -49,11 +54,12 @@ interface JsonRpcResponse {
|
|
|
49
54
|
|
|
50
55
|
/**
|
|
51
56
|
* Make an HTTP request to the remote MCP server.
|
|
57
|
+
* Uses X-API-Key header for Clerk API key authentication.
|
|
52
58
|
*/
|
|
53
59
|
async function callRemoteServer(method: string, params?: Record<string, unknown>): Promise<unknown> {
|
|
54
60
|
const headers: Record<string, string> = {
|
|
55
61
|
'Content-Type': 'application/json',
|
|
56
|
-
'
|
|
62
|
+
'X-API-Key': API_KEY!, // Safe: already validated above
|
|
57
63
|
};
|
|
58
64
|
|
|
59
65
|
if (sessionId) {
|
|
@@ -97,7 +103,7 @@ async function initializeRemoteSession(): Promise<void> {
|
|
|
97
103
|
capabilities: {},
|
|
98
104
|
clientInfo: {
|
|
99
105
|
name: 'pg-memory-stdio',
|
|
100
|
-
version: '2.
|
|
106
|
+
version: '2.3.0',
|
|
101
107
|
},
|
|
102
108
|
});
|
|
103
109
|
}
|
|
@@ -119,33 +125,35 @@ async function main(): Promise<void> {
|
|
|
119
125
|
// Create local MCP server
|
|
120
126
|
const server = new McpServer({
|
|
121
127
|
name: 'pg-memory',
|
|
122
|
-
version: '2.
|
|
128
|
+
version: '2.3.0',
|
|
123
129
|
description: `PG-Memory knowledge base (${modeLabel} mode)`,
|
|
124
130
|
});
|
|
125
131
|
|
|
126
132
|
// ============================================================
|
|
127
|
-
//
|
|
133
|
+
// CORE KNOWLEDGE TOOLS (available in all modes)
|
|
128
134
|
// ============================================================
|
|
129
135
|
|
|
130
|
-
// Tool 1: Search Knowledge (
|
|
136
|
+
// Tool 1: Search Knowledge (Articles + Q&A)
|
|
131
137
|
server.tool(
|
|
132
138
|
'search_knowledge',
|
|
133
139
|
`Search the nonresident.tax knowledge base using semantic similarity.
|
|
134
140
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
141
|
+
**YOU MUST CALL THIS TOOL** before answering any domain-specific question.
|
|
142
|
+
|
|
143
|
+
Topics covered:
|
|
138
144
|
- Tax compliance: Form 5472, Form 1120, ECI rules, penalties, deadlines
|
|
139
145
|
- Company formation: Wyoming LLC, Delaware LLC, single-member LLC
|
|
140
146
|
- Banking: Mercury, Wise, US bank account requirements
|
|
141
|
-
-
|
|
142
|
-
- Dissolution: LLC closure, final filings, IRS compliance
|
|
147
|
+
- Services & pricing: LLC formation, tax filing, registered agent
|
|
143
148
|
|
|
144
|
-
Returns relevant articles with relevance scores.
|
|
149
|
+
Returns relevant articles AND Q&A entries with relevance scores.
|
|
150
|
+
Use get_content with the ID to retrieve full content.`,
|
|
145
151
|
{
|
|
146
152
|
query: z.string().describe('The question or topic to search for'),
|
|
147
|
-
limit: z.number().optional().describe('Maximum
|
|
148
|
-
threshold: z.number().optional().describe('Minimum similarity
|
|
153
|
+
limit: z.number().optional().describe('Maximum results to return (default: 5)'),
|
|
154
|
+
threshold: z.number().optional().describe('Minimum similarity 0-1 (default: 0.55)'),
|
|
155
|
+
include_articles: z.boolean().optional().describe('Include articles (default: true)'),
|
|
156
|
+
include_qa: z.boolean().optional().describe('Include Q&A entries (default: true)'),
|
|
149
157
|
},
|
|
150
158
|
async (args) => {
|
|
151
159
|
const result = await callRemoteServer('tools/call', {
|
|
@@ -156,23 +164,24 @@ Returns relevant articles with relevance scores. Use get_article to retrieve ful
|
|
|
156
164
|
}
|
|
157
165
|
);
|
|
158
166
|
|
|
159
|
-
// Tool 2: Get
|
|
167
|
+
// Tool 2: Get Content (Unified - Articles + Q&A)
|
|
160
168
|
server.tool(
|
|
161
|
-
'
|
|
162
|
-
`Retrieve
|
|
169
|
+
'get_content',
|
|
170
|
+
`Retrieve complete content from the knowledge base by ID.
|
|
163
171
|
|
|
164
|
-
|
|
165
|
-
-
|
|
166
|
-
-
|
|
167
|
-
- You want to verify specific details from search results
|
|
172
|
+
Automatically detects content type from ID prefix:
|
|
173
|
+
- 'art_*' → Article (knowledge base article)
|
|
174
|
+
- 'qa_*' → Q&A entry (question and answer)
|
|
168
175
|
|
|
169
|
-
|
|
176
|
+
Use summary_only=true first to check content size, then fetch with max_length if needed.`,
|
|
170
177
|
{
|
|
171
|
-
|
|
178
|
+
content_id: z.string().describe('Content ID (e.g., art_abc123 or qa_xyz789)'),
|
|
179
|
+
max_length: z.number().optional().describe('Max content length in chars (truncates if exceeded)'),
|
|
180
|
+
summary_only: z.boolean().optional().describe('Return only summary/metadata (default: false)'),
|
|
172
181
|
},
|
|
173
182
|
async (args) => {
|
|
174
183
|
const result = await callRemoteServer('tools/call', {
|
|
175
|
-
name: '
|
|
184
|
+
name: 'get_content',
|
|
176
185
|
arguments: args,
|
|
177
186
|
}) as { content: Array<{ type: 'text'; text: string }> };
|
|
178
187
|
return result;
|
|
@@ -182,27 +191,20 @@ Returns: Full article with title, content, summary, category, tags, version, and
|
|
|
182
191
|
// Tool 3: Answer Question (Full RAG)
|
|
183
192
|
server.tool(
|
|
184
193
|
'answer_question',
|
|
185
|
-
`Answer a customer question using the knowledge base with full RAG.
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
1. Searches for relevant articles
|
|
189
|
-
2.
|
|
190
|
-
3.
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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.`,
|
|
194
|
+
`Answer a customer question using the knowledge base with full RAG pipeline.
|
|
195
|
+
|
|
196
|
+
How it works:
|
|
197
|
+
1. Searches for relevant articles AND published Q&A
|
|
198
|
+
2. Uses knowledge graph to find related content via entities
|
|
199
|
+
3. Generates a synthesized answer from all sources
|
|
200
|
+
4. Returns source references for transparency
|
|
201
|
+
|
|
202
|
+
Use this when you need a complete answer with sources, not just search results.`,
|
|
202
203
|
{
|
|
203
204
|
question: z.string().describe('The customer question to answer'),
|
|
204
|
-
context: z.string().optional().describe('Additional context
|
|
205
|
-
generate_if_not_found: z.boolean().optional().describe('Generate new article if
|
|
205
|
+
context: z.string().optional().describe('Additional context'),
|
|
206
|
+
generate_if_not_found: z.boolean().optional().describe('Generate new article if not found (default: true)'),
|
|
207
|
+
use_graph: z.boolean().optional().describe('Enable graph-augmented retrieval (default: true)'),
|
|
206
208
|
},
|
|
207
209
|
async (args) => {
|
|
208
210
|
const result = await callRemoteServer('tools/call', {
|
|
@@ -218,20 +220,11 @@ The answer includes source references for transparency.`,
|
|
|
218
220
|
'log_unanswered',
|
|
219
221
|
`Flag a customer question as unanswered for knowledge gap analysis.
|
|
220
222
|
|
|
221
|
-
|
|
222
|
-
|
|
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`,
|
|
223
|
+
**CALL THIS** when search_knowledge returns no results or low confidence.
|
|
224
|
+
Flagged questions appear in admin "Knowledge Gaps" section.`,
|
|
232
225
|
{
|
|
233
226
|
query: z.string().describe('The question that could not be answered'),
|
|
234
|
-
reason: z.string().optional().describe('Reason why
|
|
227
|
+
reason: z.string().optional().describe('Reason why'),
|
|
235
228
|
},
|
|
236
229
|
async (args) => {
|
|
237
230
|
const result = await callRemoteServer('tools/call', {
|
|
@@ -242,29 +235,22 @@ WHY IT MATTERS:
|
|
|
242
235
|
}
|
|
243
236
|
);
|
|
244
237
|
|
|
245
|
-
// Tool 5: List Articles
|
|
238
|
+
// Tool 5: List Content (Unified - Articles + Q&A)
|
|
246
239
|
server.tool(
|
|
247
|
-
'
|
|
248
|
-
`Browse
|
|
240
|
+
'list_content',
|
|
241
|
+
`Browse content in the knowledge base with optional filtering.
|
|
249
242
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
- category: Filter by category slug (use list_categories first)
|
|
253
|
-
|
|
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`,
|
|
243
|
+
Supports: articles, qa, or all (default).
|
|
244
|
+
Use list_categories first to discover available categories.`,
|
|
261
245
|
{
|
|
262
|
-
|
|
246
|
+
type: z.enum(['articles', 'qa', 'all']).optional().describe('Content type (default: all)'),
|
|
247
|
+
limit: z.number().optional().describe('Number of items (default: 10, max: 50)'),
|
|
263
248
|
category: z.string().optional().describe('Filter by category slug'),
|
|
249
|
+
status: z.string().optional().describe('Filter Q&A by status: pending, draft, published'),
|
|
264
250
|
},
|
|
265
251
|
async (args) => {
|
|
266
252
|
const result = await callRemoteServer('tools/call', {
|
|
267
|
-
name: '
|
|
253
|
+
name: 'list_content',
|
|
268
254
|
arguments: args,
|
|
269
255
|
}) as { content: Array<{ type: 'text'; text: string }> };
|
|
270
256
|
return result;
|
|
@@ -274,7 +260,7 @@ WORKFLOW:
|
|
|
274
260
|
// Tool 6: Health Check
|
|
275
261
|
server.tool(
|
|
276
262
|
'health_check',
|
|
277
|
-
'Check the health status of the memory system.',
|
|
263
|
+
'Check the health status of the memory system including article/Q&A counts.',
|
|
278
264
|
{},
|
|
279
265
|
async () => {
|
|
280
266
|
const result = await callRemoteServer('tools/call', {
|
|
@@ -288,22 +274,8 @@ WORKFLOW:
|
|
|
288
274
|
// Tool 7: List Categories
|
|
289
275
|
server.tool(
|
|
290
276
|
'list_categories',
|
|
291
|
-
`List all categories in the knowledge base with
|
|
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`,
|
|
277
|
+
`List all categories in the knowledge base with content counts.
|
|
278
|
+
Use category slugs with list_content for filtered browsing.`,
|
|
307
279
|
{},
|
|
308
280
|
async () => {
|
|
309
281
|
const result = await callRemoteServer('tools/call', {
|
|
@@ -315,21 +287,219 @@ RECOMMENDED WORKFLOW:
|
|
|
315
287
|
);
|
|
316
288
|
|
|
317
289
|
// ============================================================
|
|
318
|
-
//
|
|
290
|
+
// KNOWLEDGE GRAPH TOOLS (available in all modes)
|
|
291
|
+
// ============================================================
|
|
292
|
+
|
|
293
|
+
// Tool 8: Find Related Articles
|
|
294
|
+
server.tool(
|
|
295
|
+
'find_related',
|
|
296
|
+
'Find articles related through shared entities or explicit relationships. Uses graph traversal.',
|
|
297
|
+
{
|
|
298
|
+
article_id: z.string().describe('Article ID to find relations for'),
|
|
299
|
+
max_depth: z.number().optional().describe('Relationship hops 1-5 (default: 2)'),
|
|
300
|
+
min_strength: z.number().optional().describe('Min strength 0-1 (default: 0.3)'),
|
|
301
|
+
limit: z.number().optional().describe('Max articles (default: 10)'),
|
|
302
|
+
},
|
|
303
|
+
async (args) => {
|
|
304
|
+
const result = await callRemoteServer('tools/call', {
|
|
305
|
+
name: 'find_related',
|
|
306
|
+
arguments: args,
|
|
307
|
+
}) as { content: Array<{ type: 'text'; text: string }> };
|
|
308
|
+
return result;
|
|
309
|
+
}
|
|
310
|
+
);
|
|
311
|
+
|
|
312
|
+
// Tool 9: Get Entities
|
|
313
|
+
server.tool(
|
|
314
|
+
'get_entities',
|
|
315
|
+
'Get named entities from an article (people, orgs, forms, states, etc.).',
|
|
316
|
+
{
|
|
317
|
+
article_id: z.string().describe('Article ID to get entities for'),
|
|
318
|
+
},
|
|
319
|
+
async (args) => {
|
|
320
|
+
const result = await callRemoteServer('tools/call', {
|
|
321
|
+
name: 'get_entities',
|
|
322
|
+
arguments: args,
|
|
323
|
+
}) as { content: Array<{ type: 'text'; text: string }> };
|
|
324
|
+
return result;
|
|
325
|
+
}
|
|
326
|
+
);
|
|
327
|
+
|
|
328
|
+
// Tool 10: Search Entities
|
|
329
|
+
server.tool(
|
|
330
|
+
'search_entities',
|
|
331
|
+
'Search for entities by name or type across the knowledge base.',
|
|
332
|
+
{
|
|
333
|
+
query: z.string().optional().describe('Search query for entity name'),
|
|
334
|
+
type: z.string().optional().describe('Filter: PERSON, ORGANIZATION, FORM, STATE, COUNTRY, DATE, AMOUNT, CONCEPT, REGULATION, SERVICE, DOCUMENT'),
|
|
335
|
+
limit: z.number().optional().describe('Max entities (default: 20)'),
|
|
336
|
+
},
|
|
337
|
+
async (args) => {
|
|
338
|
+
const result = await callRemoteServer('tools/call', {
|
|
339
|
+
name: 'search_entities',
|
|
340
|
+
arguments: args,
|
|
341
|
+
}) as { content: Array<{ type: 'text'; text: string }> };
|
|
342
|
+
return result;
|
|
343
|
+
}
|
|
344
|
+
);
|
|
345
|
+
|
|
346
|
+
// Tool 11: Get Knowledge Graph
|
|
347
|
+
server.tool(
|
|
348
|
+
'get_knowledge_graph',
|
|
349
|
+
'Get the knowledge graph structure for visualization. Returns nodes and edges.',
|
|
350
|
+
{
|
|
351
|
+
center_article_id: z.string().optional().describe('Center on this article'),
|
|
352
|
+
depth: z.number().optional().describe('Traversal depth 1-5 (default: 2)'),
|
|
353
|
+
include_entities: z.boolean().optional().describe('Include entity nodes (default: true)'),
|
|
354
|
+
include_metadata: z.boolean().optional().describe('Include full metadata (default: false)'),
|
|
355
|
+
limit: z.number().optional().describe('Max nodes (default: 20, max: 100)'),
|
|
356
|
+
},
|
|
357
|
+
async (args) => {
|
|
358
|
+
const result = await callRemoteServer('tools/call', {
|
|
359
|
+
name: 'get_knowledge_graph',
|
|
360
|
+
arguments: args,
|
|
361
|
+
}) as { content: Array<{ type: 'text'; text: string }> };
|
|
362
|
+
return result;
|
|
363
|
+
}
|
|
364
|
+
);
|
|
365
|
+
|
|
366
|
+
// ============================================================
|
|
367
|
+
// EPISODIC MEMORY TOOLS (available in all modes)
|
|
368
|
+
// ============================================================
|
|
369
|
+
|
|
370
|
+
// Tool 12: Start Conversation
|
|
371
|
+
server.tool(
|
|
372
|
+
'start_conversation',
|
|
373
|
+
`Start a new conversation for tracking episodic memory.
|
|
374
|
+
Call at the beginning of each session to enable context continuity.`,
|
|
375
|
+
{
|
|
376
|
+
session_id: z.string().describe('Session ID (e.g., MCP session ID)'),
|
|
377
|
+
title: z.string().optional().describe('Conversation title'),
|
|
378
|
+
user_id: z.string().optional().describe('User ID for multi-user tracking'),
|
|
379
|
+
},
|
|
380
|
+
async (args) => {
|
|
381
|
+
const result = await callRemoteServer('tools/call', {
|
|
382
|
+
name: 'start_conversation',
|
|
383
|
+
arguments: args,
|
|
384
|
+
}) as { content: Array<{ type: 'text'; text: string }> };
|
|
385
|
+
return result;
|
|
386
|
+
}
|
|
387
|
+
);
|
|
388
|
+
|
|
389
|
+
// Tool 13: Add Message
|
|
390
|
+
server.tool(
|
|
391
|
+
'add_message',
|
|
392
|
+
`Add a message to an existing conversation.
|
|
393
|
+
Call after each user message and assistant response.
|
|
394
|
+
User facts are automatically extracted.`,
|
|
395
|
+
{
|
|
396
|
+
conversation_id: z.string().describe('Conversation ID from start_conversation'),
|
|
397
|
+
role: z.enum(['user', 'assistant', 'system']).describe('Message role'),
|
|
398
|
+
content: z.string().describe('Message content'),
|
|
399
|
+
referenced_articles: z.string().optional().describe('Comma-separated article IDs referenced'),
|
|
400
|
+
},
|
|
401
|
+
async (args) => {
|
|
402
|
+
const result = await callRemoteServer('tools/call', {
|
|
403
|
+
name: 'add_message',
|
|
404
|
+
arguments: args,
|
|
405
|
+
}) as { content: Array<{ type: 'text'; text: string }> };
|
|
406
|
+
return result;
|
|
407
|
+
}
|
|
408
|
+
);
|
|
409
|
+
|
|
410
|
+
// Tool 14: Get Conversation
|
|
411
|
+
server.tool(
|
|
412
|
+
'get_conversation',
|
|
413
|
+
'Retrieve a conversation with all its messages by ID.',
|
|
414
|
+
{
|
|
415
|
+
conversation_id: z.string().describe('Conversation ID'),
|
|
416
|
+
},
|
|
417
|
+
async (args) => {
|
|
418
|
+
const result = await callRemoteServer('tools/call', {
|
|
419
|
+
name: 'get_conversation',
|
|
420
|
+
arguments: args,
|
|
421
|
+
}) as { content: Array<{ type: 'text'; text: string }> };
|
|
422
|
+
return result;
|
|
423
|
+
}
|
|
424
|
+
);
|
|
425
|
+
|
|
426
|
+
// Tool 15: List Conversations
|
|
427
|
+
server.tool(
|
|
428
|
+
'list_conversations',
|
|
429
|
+
'List conversations for a session or user. Returns recent first.',
|
|
430
|
+
{
|
|
431
|
+
session_id: z.string().optional().describe('Filter by session ID'),
|
|
432
|
+
user_id: z.string().optional().describe('Filter by user ID'),
|
|
433
|
+
limit: z.number().optional().describe('Max conversations (default: 20)'),
|
|
434
|
+
},
|
|
435
|
+
async (args) => {
|
|
436
|
+
const result = await callRemoteServer('tools/call', {
|
|
437
|
+
name: 'list_conversations',
|
|
438
|
+
arguments: args,
|
|
439
|
+
}) as { content: Array<{ type: 'text'; text: string }> };
|
|
440
|
+
return result;
|
|
441
|
+
}
|
|
442
|
+
);
|
|
443
|
+
|
|
444
|
+
// Tool 16: Get Session Context
|
|
445
|
+
server.tool(
|
|
446
|
+
'get_session_context',
|
|
447
|
+
`Get recent conversation context for a session.
|
|
448
|
+
Call when user refers to prior conversation or asks follow-up questions.`,
|
|
449
|
+
{
|
|
450
|
+
session_id: z.string().describe('Session ID to get context for'),
|
|
451
|
+
message_limit: z.number().optional().describe('Max messages (default: 10)'),
|
|
452
|
+
},
|
|
453
|
+
async (args) => {
|
|
454
|
+
const result = await callRemoteServer('tools/call', {
|
|
455
|
+
name: 'get_session_context',
|
|
456
|
+
arguments: args,
|
|
457
|
+
}) as { content: Array<{ type: 'text'; text: string }> };
|
|
458
|
+
return result;
|
|
459
|
+
}
|
|
460
|
+
);
|
|
461
|
+
|
|
462
|
+
// ============================================================
|
|
463
|
+
// Q&A TOOLS (available in all modes)
|
|
464
|
+
// ============================================================
|
|
465
|
+
|
|
466
|
+
// Tool 17: Add Question
|
|
467
|
+
server.tool(
|
|
468
|
+
'add_question',
|
|
469
|
+
`Add a new question to the Q&A store for later answering.
|
|
470
|
+
Use to capture questions from external channels.`,
|
|
471
|
+
{
|
|
472
|
+
question: z.string().describe('The question text'),
|
|
473
|
+
category: z.string().optional().describe('Category slug'),
|
|
474
|
+
tags: z.string().optional().describe('Comma-separated tags'),
|
|
475
|
+
priority: z.string().optional().describe('Priority: low, medium, high, critical'),
|
|
476
|
+
source: z.string().optional().describe('Source: mcp, email, manual, import'),
|
|
477
|
+
},
|
|
478
|
+
async (args) => {
|
|
479
|
+
const result = await callRemoteServer('tools/call', {
|
|
480
|
+
name: 'add_question',
|
|
481
|
+
arguments: args,
|
|
482
|
+
}) as { content: Array<{ type: 'text'; text: string }> };
|
|
483
|
+
return result;
|
|
484
|
+
}
|
|
485
|
+
);
|
|
486
|
+
|
|
487
|
+
// ============================================================
|
|
488
|
+
// FULL MODE TOOLS (only with --full flag or MCP_MODE=full)
|
|
319
489
|
// ============================================================
|
|
320
490
|
|
|
321
491
|
if (isFullMode) {
|
|
322
|
-
// Tool
|
|
492
|
+
// Tool 18: Add Article
|
|
323
493
|
server.tool(
|
|
324
494
|
'add_article',
|
|
325
|
-
'Create a new article in the knowledge base.
|
|
495
|
+
'Create a new article in the knowledge base. Generates embedding automatically.',
|
|
326
496
|
{
|
|
327
497
|
title: z.string().describe('Article title'),
|
|
328
|
-
content: z.string().describe('Article content in markdown
|
|
329
|
-
summary: z.string().optional().describe('Brief summary (auto-generated if
|
|
330
|
-
category: z.string().optional().describe('Category
|
|
498
|
+
content: z.string().describe('Article content in markdown'),
|
|
499
|
+
summary: z.string().optional().describe('Brief summary (auto-generated if omitted)'),
|
|
500
|
+
category: z.string().optional().describe('Category slug'),
|
|
331
501
|
tags: z.string().optional().describe('Comma-separated tags'),
|
|
332
|
-
source: z.string().optional().describe('Source
|
|
502
|
+
source: z.string().optional().describe('Source (e.g., manual, irs.gov)'),
|
|
333
503
|
},
|
|
334
504
|
async (args) => {
|
|
335
505
|
const result = await callRemoteServer('tools/call', {
|
|
@@ -340,7 +510,7 @@ RECOMMENDED WORKFLOW:
|
|
|
340
510
|
}
|
|
341
511
|
);
|
|
342
512
|
|
|
343
|
-
// Tool
|
|
513
|
+
// Tool 19: Edit Article
|
|
344
514
|
server.tool(
|
|
345
515
|
'edit_article',
|
|
346
516
|
'Update an existing article. Re-generates embedding if content changes.',
|
|
@@ -361,13 +531,13 @@ RECOMMENDED WORKFLOW:
|
|
|
361
531
|
}
|
|
362
532
|
);
|
|
363
533
|
|
|
364
|
-
// Tool
|
|
534
|
+
// Tool 20: Remove Article
|
|
365
535
|
server.tool(
|
|
366
536
|
'remove_article',
|
|
367
|
-
'Soft-delete an article
|
|
537
|
+
'Soft-delete an article. Marked as deleted but not permanently removed.',
|
|
368
538
|
{
|
|
369
539
|
article_id: z.string().describe('Article ID to remove'),
|
|
370
|
-
reason: z.string().optional().describe('Reason for deletion
|
|
540
|
+
reason: z.string().optional().describe('Reason for deletion'),
|
|
371
541
|
},
|
|
372
542
|
async (args) => {
|
|
373
543
|
const result = await callRemoteServer('tools/call', {
|
|
@@ -378,14 +548,14 @@ RECOMMENDED WORKFLOW:
|
|
|
378
548
|
}
|
|
379
549
|
);
|
|
380
550
|
|
|
381
|
-
// Tool
|
|
551
|
+
// Tool 21: Rate Answer
|
|
382
552
|
server.tool(
|
|
383
553
|
'rate_answer',
|
|
384
|
-
'Rate a previous answer as helpful, unhelpful, or neutral.
|
|
554
|
+
'Rate a previous answer as helpful, unhelpful, or neutral.',
|
|
385
555
|
{
|
|
386
|
-
query_id: z.string().describe('Query log ID from
|
|
556
|
+
query_id: z.string().describe('Query log ID from answer_question'),
|
|
387
557
|
rating: z.number().describe('Rating: -1 (unhelpful), 0 (neutral), 1 (helpful)'),
|
|
388
|
-
feedback: z.string().optional().describe('Optional text
|
|
558
|
+
feedback: z.string().optional().describe('Optional feedback text'),
|
|
389
559
|
},
|
|
390
560
|
async (args) => {
|
|
391
561
|
const result = await callRemoteServer('tools/call', {
|