getSectionContent - allow multiple sectionids

This commit is contained in:
2026-04-20 18:32:59 +05:30
parent a26f90cef9
commit 0334c5836b
5 changed files with 159 additions and 30 deletions
+67 -15
View File
@@ -14,9 +14,18 @@ const sectionContentSchema = z.object({
partIndex: z.number().optional(),
});
const getSectionContentOutputSchema = z.object({
const sectionDataSchema = z.object({
sectionId: z.string(),
content: z.string(),
sectionCount: z.number(),
found: z.boolean(),
error: z.string().optional(),
sectionTitle: z.string().optional(),
partIndex: z.number().optional(),
totalParts: z.number().optional(),
});
const getSectionContentOutputSchema = z.object({
sections: z.array(sectionDataSchema),
});
// #endregion
@@ -25,20 +34,21 @@ const getSectionContentOutputSchema = z.object({
export const toolMetadata = {
description:
"Retrieves all text chunks from a specific specification section by sectionid. " +
"Supports recursive fetching - if recursive=true and the section has children, it will fetch all descendants. " +
"Retrieves all text chunks from one or more specification sections by section IDs. " +
"Supports recursive fetching - if recursive=true and a section has children, it will fetch all descendants. " +
"Use this to get complete content when you see 'Subsection available' or 'partial section' references.",
args: {
sectionId: "The section ID (e.g., 'sec-if-statement') to fetch chunks for",
sectionIds:
"Array of section IDs (e.g., ['sec-if-statement', 'sec-for-statement']) to fetch chunks for",
recursive:
"If true, recursively fetches content from all child sections and their descendants. " +
"If false, only returns content from the specified section itself. " +
"Use false when you only need the specific section's content without subsections.",
"If false, only returns content from the specified sections themselves. " +
"Use false when you only need the specific sections' content without subsections.",
},
};
export const inputSchema = z.object({
sectionId: z.string().describe(toolMetadata.args.sectionId),
sectionIds: z.array(z.string()).describe(toolMetadata.args.sectionIds),
recursive: z.boolean().default(true).describe(toolMetadata.args.recursive),
});
@@ -69,11 +79,19 @@ export type GetSectionContentInput = z.infer<typeof inputSchema>;
*/
export function createGetSectionContentTool(table: Table) {
return async ({
sectionId,
sectionIds,
recursive,
}: GetSectionContentInput): Promise<GetSectionContentOutput> => {
const allDocs: string[] = [];
const queue: string[] = [sectionId];
const sectionsData = new Map<
string,
{
content: string[];
title?: string;
partIndex?: number;
totalParts?: number;
}
>();
const queue: string[] = [...sectionIds];
const visited = new Set<string>();
while (queue.length > 0) {
@@ -84,7 +102,7 @@ export function createGetSectionContentTool(table: Table) {
const results = await table
.query()
.where(`sectionid = '${currentId}'`)
.limit(100)
.limit(10)
.toArray();
// Sort by partindex to maintain order (nulls last for single-part sections)
@@ -99,10 +117,23 @@ export function createGetSectionContentTool(table: Table) {
text?: string;
childrensectionids?: string[];
sectiontitle?: string;
partindex?: number;
totalparts?: number;
};
if (typedResult.text) {
allDocs.push(typedResult.text);
// Get or create section data
let section = sectionsData.get(currentId);
if (!section) {
section = {
content: [],
title: typedResult.sectiontitle,
partIndex: typedResult.partindex ?? undefined,
totalParts: typedResult.totalparts ?? undefined,
};
sectionsData.set(currentId, section);
}
section.content.push(typedResult.text);
}
// Add children to queue for recursive fetching only if recursive is true
@@ -116,9 +147,30 @@ export function createGetSectionContentTool(table: Table) {
}
}
// Build output array from all requested sections
// Missing sections are included with found: false and error message
const sections = sectionIds.map((id) => {
const data = sectionsData.get(id);
if (data) {
return {
sectionId: id,
content: data.content.join("\n\n"),
found: true,
sectionTitle: data.title,
partIndex: data.partIndex,
totalParts: data.totalParts,
};
}
return {
sectionId: id,
content: "",
found: false,
error: `Section '${id}' not found in database`,
};
});
return {
content: allDocs.join("\n\n---\n\n"),
sectionCount: visited.size,
sections,
};
};
}
+10 -6
View File
@@ -113,13 +113,17 @@ async function createMcpServer() {
openWorldHint: false,
},
},
async ({ sectionId, recursive }) => {
async ({ sectionIds, recursive }) => {
console.log(
`[TOOL] ${sectionContentToolName}: sectionId="${sectionId}" recursive=${recursive}`,
`[TOOL] ${sectionContentToolName}: sectionIds=[${sectionIds.map((id: string) => `"${id}"`).join(", ")}] recursive=${recursive}`,
);
const result = await getSectionContentTool({ sectionIds, recursive });
const totalContentLength = result.sections.reduce(
(sum: number, s: { content: string }) => sum + s.content.length,
0,
);
const result = await getSectionContentTool({ sectionId, recursive });
console.log(
`[TOOL] ${sectionContentToolName}: ${result.content.length} chars, ${result.sectionCount} sections`,
`[TOOL] ${sectionContentToolName}: ${totalContentLength} chars, ${result.sections.length} sections`,
);
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
@@ -200,14 +204,14 @@ I'll use one of these orchestration patterns:
PATTERN 1 - For "What happens when I run this code?" questions:
- Use ask262Debug.startImportant() and ask262Debug.stopImportant() in the code to mark only important sections.
- STEP 1: ask262_evaluate_in_engine262(code: markedCode)
- STEP 2: ask262_get_section_content(sectionId: importantSections[0], recursive: true)
- STEP 2: ask262_get_section_content(sectionIds: ["sec-1", "sec-2"], recursive: true)
- Explain which spec sections were hit and why
PATTERN 2 - For "How does X work?" questions (e.g., "${question}"):
- Flow A: Generate a specific code example and follow Pattern 1
- Flow B: If no code example possible, search broadly:
* STEP 1: ask262_search_spec_sections(query: relevant keywords from "${question}")
* STEP 2: ask262_get_section_content(sectionId: foundSectionId, recursive: true)
* STEP 2: ask262_get_section_content(sectionIds: ["sec-1", "sec-2"], recursive: true)
I prefer Pattern 1 when possible as it provides exact spec sections through execution.
+4 -4
View File
@@ -147,10 +147,10 @@ export async function main() {
},
},
async ({
sectionId,
sectionIds,
recursive,
}: GetSectionContentMCPInput): Promise<GetSectionContentMCPOutput> => {
const result = await getSectionContentTool({ sectionId, recursive });
const result = await getSectionContentTool({ sectionIds, recursive });
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
structuredContent: result,
@@ -222,14 +222,14 @@ I'll use one of these orchestration patterns:
PATTERN 1 - For "What happens when I run this code?" questions:
- Use ask262Debug.startImportant() and ask262Debug.stopImportant() in the code to mark only important sections.
- STEP 1: ask262_evaluate_in_engine262(code: markedCode)
- STEP 2: ask262_get_section_content(sectionId: importantSections[0], recursive: true)
- STEP 2: ask262_get_section_content(sectionIds: [importantSections[0]], recursive: true)
- Explain which spec sections were hit and why
PATTERN 2 - For "How does X work?" questions (e.g., "${question}"):
- Flow A: Generate a specific code example and follow Pattern 1
- Flow B: If no code example possible, search broadly:
* STEP 1: ask262_search_spec_sections(query: relevant keywords from "${question}")
* STEP 2: ask262_get_section_content(sectionId: foundSectionId, recursive: true)
* STEP 2: ask262_get_section_content(sectionIds: [foundSectionId], recursive: true)
I prefer Pattern 1 when possible as it provides exact spec sections through execution.
+8 -5
View File
@@ -71,7 +71,7 @@ async function testMCPServer() {
const contentResult = (await client.callTool({
name: "ask262_get_section_content",
arguments: {
sectionId: "sec-array.prototype.map",
sectionIds: ["sec-array.prototype.map"],
recursive: false,
},
})) as GetSectionContentMCPOutput;
@@ -79,10 +79,13 @@ async function testMCPServer() {
throw new Error(`Get section failed: ${contentResult.content[0]?.text}`);
}
const contentData = contentResult.structuredContent;
console.log(
`Content length: ${contentData.content?.length ?? 0} characters`,
);
console.log(`Sections visited: ${contentData.sectionCount ?? 0}`);
const totalContentLength =
contentData.sections?.reduce(
(sum: number, s: { content: string }) => sum + s.content.length,
0,
) ?? 0;
console.log(`Content length: ${totalContentLength} characters`);
console.log(`Sections visited: ${contentData.sections?.length ?? 0}`);
console.log("✓ Section content retrieved (isError: false)\n");
// Test 4: Evaluate in engine262 - success case
@@ -0,0 +1,70 @@
#!/usr/bin/env bun
/**
* Test to verify non-existing section IDs return found: false
*/
import * as lancedbSdk from "@lancedb/lancedb";
import { createGetSectionContentTool } from "../../agent-tools/getSectionContent.js";
import { STORAGE_DIR } from "../../constants.js";
async function main() {
console.log("Testing getSectionContent with non-existing section IDs...\n");
try {
const db = await lancedbSdk.connect(STORAGE_DIR);
const table = await db.openTable("spec_vectors");
const getSectionContentTool = createGetSectionContentTool(table);
// Test with mix of existing and non-existing section IDs
const result = await getSectionContentTool({
sectionIds: [
"sec-non-existent-12345", // Should not exist
"sec-also-fake-99999", // Should not exist
],
recursive: false,
});
console.log("=== RESULT ===");
console.log(JSON.stringify(result, null, 2));
console.log("\n=== VERIFICATION ===");
// Verify all sections are returned
if (result.sections.length !== 2) {
console.error(`❌ Expected 2 sections, got ${result.sections.length}`);
process.exit(1);
}
// Verify non-existing sections have found: false
for (const section of result.sections) {
if (section.found !== false) {
console.error(
`❌ Section ${section.sectionId} should have found: false`,
);
process.exit(1);
}
if (!section.error) {
console.error(
`❌ Section ${section.sectionId} should have error message`,
);
process.exit(1);
}
if (section.content !== "") {
console.error(
`❌ Section ${section.sectionId} should have empty content`,
);
process.exit(1);
}
console.log(
`${section.sectionId}: found=false, error="${section.error}"`,
);
}
console.log("\n✅ All non-existing sections correctly return found: false");
} catch (error) {
console.error("❌ Test failed:", error);
process.exit(1);
}
}
main().catch(console.error);