Skip to content

Commit 1c21b0a

Browse files
committed
docs: improve MCP tool definition quality
1 parent e8525b5 commit 1c21b0a

3 files changed

Lines changed: 69 additions & 46 deletions

File tree

server/README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -120,11 +120,12 @@ Add to `settings.json`:
120120

121121
| Tool | Description |
122122
|------|-------------|
123-
| `search_knowledge` | Semantic search across notes |
123+
| `search_knowledge` | Semantic search across notes when you need relevance-ranked matches |
124124
| `get_note` | Get full note content (summary, conclusions, code snippets) |
125-
| `list_notes` | Browse notes by tag or keyword |
125+
| `list_notes` | Browse paginated note summaries by tag or title/summary keyword |
126126
| `get_relations` | Get related notes with relationship types |
127-
| `recall_for_task` | Recall project-first memories for a coding task |
127+
| `recall_for_task` | Read-only task recall that loads project memories first, then optional global lessons |
128+
| `validate_task_memory` | Dry-run validation before writing a task memory; returns acceptance, reasons, warnings, and note fields |
128129
| `write_task_memory` | Persist reusable task memories after meaningful work |
129130

130131
## Configuration

server/src/cli/mcp/server.ts

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,28 @@ import {
99
WriteTaskMemoryRequestShape,
1010
} from '../../services/memory/schemas.js';
1111

12+
const LIST_NOTES_DESCRIPTION = [
13+
'List note summaries for browsing and narrowing the ChatCrystal knowledge base.',
14+
'Use this when you need paginated notes filtered by tag or title/summary keyword.',
15+
'Use search_knowledge instead for semantic relevance ranking, and get_note when you already have an id and need the full note body.',
16+
'Returns note metadata and summaries, not full note content.',
17+
].join(' ');
18+
19+
const RECALL_FOR_TASK_DESCRIPTION = [
20+
'Retrieve reusable task memories before starting substantive coding work.',
21+
'Use this at the beginning of implementation, debugging, migration, configuration, investigation, refactor, or optimization tasks to load project-scoped memories first and optional global lessons second.',
22+
'Use mode="debug" when the user reports an error, failing command, regression, or incident; include error_signatures and related_files when available.',
23+
'Use search_knowledge instead for ad hoc semantic note search that is not tied to the current task.',
24+
'This tool is read-only and returns ranked memories plus optional related-note context without writing anything.',
25+
].join(' ');
26+
27+
const VALIDATE_TASK_MEMORY_DESCRIPTION = [
28+
'Dry-run validation for a candidate task memory before calling write_task_memory.',
29+
'Use this after meaningful work and before persisting a lesson to check whether the candidate is durable, specific, reusable, and shaped like a high-quality ChatCrystal note.',
30+
'It has no side effects and never writes to the knowledge base.',
31+
'Returns acceptance, rejection reason, warnings, and materialized note fields so agents can revise the candidate or skip weak work logs.',
32+
].join(' ');
33+
1234
export async function startMcpServer(options?: string | CrystalClientOptions) {
1335
const client = new CrystalClient(options);
1436
const status = await client.status();
@@ -58,11 +80,11 @@ export async function startMcpServer(options?: string | CrystalClientOptions) {
5880
// Tool 3: list_notes
5981
server.tool(
6082
'list_notes',
61-
'Browse notes in the knowledge base. Filter by tag or keyword search.',
83+
LIST_NOTES_DESCRIPTION,
6284
{
63-
tag: z.string().optional().describe('Filter by tag name'),
64-
search: z.string().optional().describe('Filter by keyword in title/summary'),
65-
page: z.number().optional().default(1).describe('Page number'),
85+
tag: z.string().optional().describe('Exact tag name to filter notes by, for example "mcp" or "cursor".'),
86+
search: z.string().optional().describe('Literal keyword filter applied to note title and summary; not semantic search.'),
87+
page: z.number().optional().default(1).describe('1-based page number for paginated note summaries. Each page returns up to 20 notes.'),
6688
},
6789
async ({ tag, search, page }) => {
6890
const limit = 20;
@@ -97,7 +119,7 @@ export async function startMcpServer(options?: string | CrystalClientOptions) {
97119

98120
server.tool(
99121
'recall_for_task',
100-
'Recall project-first and global-supplement memories for a task.',
122+
RECALL_FOR_TASK_DESCRIPTION,
101123
RecallForTaskRequestShape,
102124
async (input) => {
103125
const data = await client.recallForTask(input);
@@ -112,7 +134,7 @@ export async function startMcpServer(options?: string | CrystalClientOptions) {
112134

113135
server.tool(
114136
'validate_task_memory',
115-
'Preflight a task memory candidate without side effects. Use this before write_task_memory when available. Returns materialized ChatCrystal note fields, acceptance, reason, and warnings.',
137+
VALIDATE_TASK_MEMORY_DESCRIPTION,
116138
ValidateTaskMemoryRequestShape,
117139
async (input) => {
118140
const data = await client.validateTaskMemory(input);

server/src/services/memory/schemas.ts

Lines changed: 37 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ export const SourceAgentSchema = z.enum([
77
'cursor',
88
'trae',
99
'unknown',
10-
]);
10+
]).describe('AI coding tool or agent that is calling ChatCrystal; use unknown when unsure.');
1111

1212
const TaskBaseSchema = z.object({
13-
goal: z.string().min(1),
13+
goal: z.string().min(1).describe('Plain-language task goal or user request. Include enough context to retrieve relevant memories.'),
1414
task_kind: z.enum([
1515
'debug',
1616
'implement',
@@ -19,70 +19,70 @@ const TaskBaseSchema = z.object({
1919
'config',
2020
'investigate',
2121
'optimization',
22-
]),
23-
project_key: z.string().optional(),
24-
project_dir: z.string().optional(),
25-
cwd: z.string().optional(),
26-
branch: z.string().optional(),
27-
files_touched: z.array(z.string()).optional(),
28-
error_signatures: z.array(z.string()).optional(),
22+
]).describe('Kind of work being performed. Use debug for failures; choose the closest non-debug category for planned work.'),
23+
project_key: z.string().optional().describe('Stable project identifier used to prioritize project-scoped memories, such as a repository or workspace key.'),
24+
project_dir: z.string().optional().describe('Absolute project directory when known; helps ChatCrystal match memories to the right local workspace.'),
25+
cwd: z.string().optional().describe('Current working directory of the agent session.'),
26+
branch: z.string().optional().describe('Current VCS branch when relevant to the task.'),
27+
files_touched: z.array(z.string()).optional().describe('Files already touched or expected to be touched; improves project memory matching.'),
28+
error_signatures: z.array(z.string()).optional().describe('Concrete errors, stack traces, failing commands, or symptoms. Most useful with debug tasks.'),
2929
source_agent: SourceAgentSchema.optional(),
3030
});
3131

3232
const RecallTaskSchema = TaskBaseSchema.extend({
33-
related_files: z.array(z.string()).optional(),
33+
related_files: z.array(z.string()).optional().describe('Additional files related to the task but not necessarily modified.'),
3434
});
3535

3636
const NormalizedTaskSchema = TaskBaseSchema.transform((task) => ({
3737
...task,
3838
source_agent: task.source_agent ?? 'unknown',
39-
}));
39+
})).describe('Current task context used to scope, rank, and store memories.');
4040

4141
export const RecallForTaskOptionsShape = {
42-
project_limit: z.number().int().nonnegative().default(5),
43-
global_limit: z.number().int().nonnegative().default(3),
44-
include_relations: z.boolean().default(true),
42+
project_limit: z.number().int().nonnegative().default(5).describe('Maximum number of project-scoped memories to return first.'),
43+
global_limit: z.number().int().nonnegative().default(3).describe('Maximum number of cross-project/global lessons to append after project memories.'),
44+
include_relations: z.boolean().default(true).describe('Whether to include related-note context for returned memories.'),
4545
} as const;
4646

4747
export const RecallForTaskRequestShape = {
48-
mode: z.enum(['task', 'debug']).default('task'),
48+
mode: z.enum(['task', 'debug']).default('task').describe('Use task for normal work and debug when the task starts from an error, failing test, or incident.'),
4949
task: RecallTaskSchema.transform((task) => ({
5050
...task,
5151
source_agent: task.source_agent ?? 'unknown',
52-
})),
53-
options: z.object(RecallForTaskOptionsShape).optional(),
52+
})).describe('Current task context used to retrieve relevant project and global memories.'),
53+
options: z.object(RecallForTaskOptionsShape).optional().describe('Optional limits and relation expansion controls for recall results.'),
5454
} as const;
5555

5656
export const RecallForTaskRequestSchema = z.object(RecallForTaskRequestShape);
5757

5858
export const WriteTaskMemoryPayloadShape = {
59-
title: z.string().optional(),
60-
summary: z.string().min(1),
61-
outcome_type: z.enum(['pitfall', 'fix', 'pattern', 'decision']),
62-
pitfalls: z.array(z.string()).optional(),
63-
root_cause: z.string().optional(),
64-
resolution: z.string().optional(),
65-
reusable_patterns: z.array(z.string()).optional(),
66-
decisions: z.array(z.string()).optional(),
67-
key_conclusions: z.array(z.string()).optional(),
59+
title: z.string().optional().describe('Specific note title. Prefer the durable lesson over a generic task name.'),
60+
summary: z.string().min(1).describe('Concrete summary of what was learned or decided, written so it remains useful in a later session.'),
61+
outcome_type: z.enum(['pitfall', 'fix', 'pattern', 'decision']).describe('Primary kind of reusable memory being saved.'),
62+
pitfalls: z.array(z.string()).optional().describe('Mistakes, traps, or failure modes future agents should avoid.'),
63+
root_cause: z.string().optional().describe('Underlying cause of the problem when the memory is about a fix or pitfall.'),
64+
resolution: z.string().optional().describe('Specific fix or action that resolved the issue.'),
65+
reusable_patterns: z.array(z.string()).optional().describe('Generalizable implementation, debugging, migration, or configuration patterns.'),
66+
decisions: z.array(z.string()).optional().describe('Durable design, product, architecture, or process decisions made during the task.'),
67+
key_conclusions: z.array(z.string()).optional().describe('Important takeaways that should be recalled before similar future work.'),
6868
code_snippets: z.array(
6969
z.object({
70-
language: z.string(),
71-
code: z.string(),
72-
description: z.string(),
70+
language: z.string().describe('Programming or markup language for the snippet.'),
71+
code: z.string().describe('Minimal code, command, config, or query that illustrates the reusable lesson.'),
72+
description: z.string().describe('Why this snippet matters and when to reuse it.'),
7373
}),
74-
).optional(),
75-
files_touched: z.array(z.string()).optional(),
76-
error_signatures: z.array(z.string()).optional(),
77-
tags: z.array(z.string()).optional(),
74+
).optional().describe('Small snippets that make the memory actionable without copying large files.'),
75+
files_touched: z.array(z.string()).optional().describe('Files that provide useful provenance for the memory.'),
76+
error_signatures: z.array(z.string()).optional().describe('Exact errors or symptoms that should trigger this memory in future debug recall.'),
77+
tags: z.array(z.string()).optional().describe('Short tags for retrieval, such as framework, subsystem, source tool, or failure type.'),
7878
} as const;
7979

8080
export const WriteTaskMemoryRequestShape = {
81-
mode: z.enum(['auto', 'manual']),
82-
source_run_key: z.string().optional(),
83-
scope: z.enum(['project', 'global']).optional(),
81+
mode: z.enum(['auto', 'manual']).describe('Use auto for agent-generated writebacks and manual for explicit user-curated memories.'),
82+
source_run_key: z.string().optional().describe('Idempotency key for auto writebacks; required in auto mode to avoid duplicate memory receipts.'),
83+
scope: z.enum(['project', 'global']).optional().describe('Store as project memory by default; global is reserved for broadly reusable manual lessons.'),
8484
task: NormalizedTaskSchema,
85-
memory: z.object(WriteTaskMemoryPayloadShape),
85+
memory: z.object(WriteTaskMemoryPayloadShape).describe('Candidate ChatCrystal note content to validate or persist as reusable task memory.'),
8686
} as const;
8787

8888
export const WriteTaskMemoryRequestSchema = z

0 commit comments

Comments
 (0)