Skip to content

Commit da29087

Browse files
cliffhallclaude
andcommitted
feat(memory): describe the top-level array parameters of four tools
create_entities.entities, create_relations.relations, add_observations.observations and delete_observations.deletions had no description in their input schemas, unlike delete_entities.entityNames. Add one to each, in delete_entities' style, and a protocol-level test that every top-level input property of every tool in tools/list carries a non-empty description. Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: cliffhall <[email protected]>
1 parent 281b167 commit da29087

4 files changed

Lines changed: 123 additions & 24 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@modelcontextprotocol/server-memory": patch
3+
---
4+
5+
Describe the top-level array parameters of `create_entities`, `create_relations`, `add_observations` and `delete_observations` in their input schemas, so `tools/list` documents every top-level input property.
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// Asserts that every top-level input property of every memory tool carries a
2+
// non-empty description in tools/list (#5416). Clients and models see only the
3+
// property name when a description is missing, so a new tool or parameter that
4+
// omits one fails here rather than shipping undocumented. Driven through an SDK
5+
// Client over an in-memory transport, so it checks what a client receives.
6+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
7+
import { connect, makeTempGraph } from "./helpers.js";
8+
import type { Connection } from "./helpers.js";
9+
10+
describe("tool input property descriptions", () => {
11+
let connection: Connection;
12+
let cleanup: () => Promise<void>;
13+
14+
beforeEach(async () => {
15+
const graph = await makeTempGraph();
16+
cleanup = graph.cleanup;
17+
connection = await connect(graph.filePath);
18+
});
19+
20+
afterEach(async () => {
21+
await connection.close();
22+
await cleanup();
23+
});
24+
25+
it("describes every top-level input property of every tool (#5416)", async () => {
26+
const { tools } = await connection.client.listTools();
27+
expect(tools.length).toBeGreaterThan(0);
28+
29+
const undescribed: string[] = [];
30+
for (const tool of tools) {
31+
for (const [name, schema] of Object.entries(
32+
tool.inputSchema.properties ?? {},
33+
)) {
34+
const description: unknown =
35+
typeof schema === "object" && schema !== null
36+
? (schema as { description?: unknown }).description
37+
: undefined;
38+
if (typeof description !== "string" || description.trim() === "") {
39+
undescribed.push(`${tool.name}.${name}`);
40+
}
41+
}
42+
}
43+
expect(undescribed).toEqual([]);
44+
});
45+
46+
it("describes the four top-level array parameters named in #5416", async () => {
47+
const { tools } = await connection.client.listTools();
48+
const descriptionOf = (tool: string, property: string): unknown => {
49+
const schema = tools.find((t) => t.name === tool)?.inputSchema
50+
.properties?.[property];
51+
return typeof schema === "object" && schema !== null
52+
? (schema as { description?: unknown }).description
53+
: undefined;
54+
};
55+
expect(descriptionOf("create_entities", "entities")).toBe(
56+
"An array of entities to create",
57+
);
58+
expect(descriptionOf("create_relations", "relations")).toBe(
59+
"An array of relations to create",
60+
);
61+
expect(descriptionOf("add_observations", "observations")).toBe(
62+
"An array of entities and the observations to add to each",
63+
);
64+
expect(descriptionOf("delete_observations", "deletions")).toBe(
65+
"An array of entities and the observations to delete from each",
66+
);
67+
});
68+
});

‎src/memory/__tests__/tools.test.ts‎

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,13 @@ const expectedTools = [
134134
"Create multiple new entities in the knowledge graph. An entity whose name already exists, or repeats an earlier entity in the same call, is skipped and its observations are not added; the result lists the skipped names in `skipped`. Use add_observations to add observations to an existing entity.",
135135
inputSchema: {
136136
type: "object",
137-
properties: { entities: { type: "array", items: entityItem(false) } },
137+
properties: {
138+
entities: {
139+
type: "array",
140+
items: entityItem(false),
141+
description: "An array of entities to create",
142+
},
143+
},
138144
required: ["entities"],
139145
$schema: JSON_SCHEMA_2020_12,
140146
},
@@ -158,7 +164,13 @@ const expectedTools = [
158164
"Create multiple new relations between entities in the knowledge graph. Relations should be in active voice",
159165
inputSchema: {
160166
type: "object",
161-
properties: { relations: { type: "array", items: relationItem(false) } },
167+
properties: {
168+
relations: {
169+
type: "array",
170+
items: relationItem(false),
171+
description: "An array of relations to create",
172+
},
173+
},
162174
required: ["relations"],
163175
$schema: JSON_SCHEMA_2020_12,
164176
},
@@ -198,6 +210,8 @@ const expectedTools = [
198210
},
199211
required: ["entityName", "contents"],
200212
},
213+
description:
214+
"An array of entities and the observations to add to each",
201215
},
202216
},
203217
required: ["observations"],
@@ -273,6 +287,8 @@ const expectedTools = [
273287
},
274288
required: ["entityName", "observations"],
275289
},
290+
description:
291+
"An array of entities and the observations to delete from each",
276292
},
277293
},
278294
required: ["deletions"],

‎src/memory/index.ts‎

Lines changed: 32 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -984,7 +984,9 @@ export function createServer(
984984
description:
985985
"Create multiple new entities in the knowledge graph. An entity whose name already exists, or repeats an earlier entity in the same call, is skipped and its observations are not added; the result lists the skipped names in `skipped`. Use add_observations to add observations to an existing entity.",
986986
inputSchema: z.object({
987-
entities: z.array(EntitySchema),
987+
entities: z
988+
.array(EntitySchema)
989+
.describe("An array of entities to create"),
988990
}),
989991
outputSchema: z.object({
990992
entities: z.array(EntitySchema),
@@ -1031,7 +1033,9 @@ export function createServer(
10311033
description:
10321034
"Create multiple new relations between entities in the knowledge graph. Relations should be in active voice",
10331035
inputSchema: z.object({
1034-
relations: z.array(RelationSchema),
1036+
relations: z
1037+
.array(RelationSchema)
1038+
.describe("An array of relations to create"),
10351039
}),
10361040
outputSchema: z.object({
10371041
relations: z.array(RelationSchema),
@@ -1063,16 +1067,18 @@ export function createServer(
10631067
description:
10641068
"Add new observations to existing entities in the knowledge graph",
10651069
inputSchema: z.object({
1066-
observations: z.array(
1067-
z.object({
1068-
entityName: z
1069-
.string()
1070-
.describe("The name of the entity to add the observations to"),
1071-
contents: z
1072-
.array(z.string())
1073-
.describe("An array of observation contents to add"),
1074-
}),
1075-
),
1070+
observations: z
1071+
.array(
1072+
z.object({
1073+
entityName: z
1074+
.string()
1075+
.describe("The name of the entity to add the observations to"),
1076+
contents: z
1077+
.array(z.string())
1078+
.describe("An array of observation contents to add"),
1079+
}),
1080+
)
1081+
.describe("An array of entities and the observations to add to each"),
10761082
}),
10771083
outputSchema: z.object({
10781084
results: z.array(
@@ -1147,16 +1153,20 @@ export function createServer(
11471153
description:
11481154
"Delete specific observations from entities in the knowledge graph",
11491155
inputSchema: z.object({
1150-
deletions: z.array(
1151-
z.object({
1152-
entityName: z
1153-
.string()
1154-
.describe("The name of the entity containing the observations"),
1155-
observations: z
1156-
.array(z.string())
1157-
.describe("An array of observations to delete"),
1158-
}),
1159-
),
1156+
deletions: z
1157+
.array(
1158+
z.object({
1159+
entityName: z
1160+
.string()
1161+
.describe("The name of the entity containing the observations"),
1162+
observations: z
1163+
.array(z.string())
1164+
.describe("An array of observations to delete"),
1165+
}),
1166+
)
1167+
.describe(
1168+
"An array of entities and the observations to delete from each",
1169+
),
11601170
}),
11611171
outputSchema: z.object({
11621172
success: z.boolean(),

0 commit comments

Comments
 (0)