Skip to content

Commit 97ac369

Browse files
committed
feat(mcp): Improve documentation rendering
1 parent c5a7383 commit 97ac369

File tree

4 files changed

+53
-43
lines changed

4 files changed

+53
-43
lines changed

mcp/docs.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -74,18 +74,21 @@ export async function ensureDocs(
7474
return await extractBuiltinFunctions(zigVersion, isMcpMode);
7575
}
7676

77-
export async function downloadSourcesTar(zigVersion: string): Promise<Uint8Array> {
77+
export async function downloadSourcesTar(
78+
zigVersion: string,
79+
isMcpMode: boolean = false,
80+
): Promise<Uint8Array> {
7881
const paths = envPaths("zig-docs-mcp", { suffix: "" });
7982
const versionCacheDir = path.join(paths.cache, zigVersion);
8083
const sourcesPath = path.join(versionCacheDir, "sources.tar");
8184

8285
if (fs.existsSync(sourcesPath)) {
83-
console.log(`Using cached sources.tar from ${sourcesPath}`);
86+
if (!isMcpMode) console.log(`Using cached sources.tar from ${sourcesPath}`);
8487
return new Uint8Array(fs.readFileSync(sourcesPath));
8588
}
8689

8790
const url = `https://ziglang.org/documentation/${zigVersion}/std/sources.tar`;
88-
console.log(`Downloading sources.tar from: ${url}`);
91+
if (!isMcpMode) console.log(`Downloading sources.tar from: ${url}`);
8992

9093
const response = await fetch(url);
9194
if (!response.ok) {
@@ -102,7 +105,7 @@ export async function downloadSourcesTar(zigVersion: string): Promise<Uint8Array
102105
}
103106

104107
fs.writeFileSync(sourcesPath, uint8Array);
105-
console.log(`Downloaded sources.tar to ${sourcesPath}`);
108+
if (!isMcpMode) console.log(`Downloaded sources.tar to ${sourcesPath}`);
106109

107110
return uint8Array;
108111
}
@@ -117,7 +120,7 @@ async function downloadSourcesTarPath(zigVersion: string): Promise<string> {
117120
return sourcesPath;
118121
}
119122

120-
await downloadSourcesTar(zigVersion);
123+
await downloadSourcesTar(zigVersion, false);
121124
return sourcesPath;
122125
}
123126

mcp/mcp.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ async function main() {
8888
}
8989

9090
const builtinFunctions = await ensureDocs(options.version, options.updatePolicy, true);
91-
const stdSources = await downloadSourcesTar(options.version);
91+
const stdSources = await downloadSourcesTar(options.version, true);
9292

9393
const mcpServer = new McpServer({
9494
name: "ZigDocs",

mcp/std.ts

Lines changed: 39 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -187,9 +187,6 @@ function renderSource(path: any) {
187187
function renderNamespacePage(decl_index: any) {
188188
let markdown = "";
189189

190-
// Add navigation breadcrumb
191-
markdown += renderNavMarkdown(decl_index);
192-
193190
// Add title
194191
const name = unwrapString(wasm_exports.decl_category_name(decl_index));
195192
markdown += "# " + name + "\n\n";
@@ -212,32 +209,29 @@ function renderNamespacePage(decl_index: any) {
212209
function renderFunction(decl_index: any) {
213210
let markdown = "";
214211

215-
// Add navigation breadcrumb
216-
markdown += renderNavMarkdown(decl_index);
217-
218212
// Add title
219213
const name = unwrapString(wasm_exports.decl_category_name(decl_index));
220-
markdown += "# " + name + "\n\n";
214+
markdown += "# " + name + "\n";
221215

222216
// Add documentation
223217
const docs = unwrapString(wasm_exports.decl_docs_html(decl_index, false));
224218
if (docs.length > 0) {
225-
markdown += docs + "\n\n";
219+
markdown += "\n" + docs;
226220
}
227221

228222
// Add function prototype
229223
const proto = unwrapString(wasm_exports.decl_fn_proto_html(decl_index, false));
230224
if (proto.length > 0) {
231-
markdown += "## Function Signature\n\n" + proto + "\n\n";
225+
markdown += "\n\n## Function Signature\n\n" + proto;
232226
}
233227

234228
// Add parameters
235229
const params = declParams(decl_index).slice();
236230
if (params.length > 0) {
237-
markdown += "## Parameters\n\n";
231+
markdown += "\n\n## Parameters\n";
238232
for (let i = 0; i < params.length; i++) {
239233
const param_html = unwrapString(wasm_exports.decl_param_html(decl_index, params[i]));
240-
markdown += param_html + "\n\n";
234+
markdown += "\n" + param_html;
241235
}
242236
}
243237

@@ -247,24 +241,24 @@ function renderFunction(decl_index: any) {
247241
const base_decl = wasm_exports.fn_error_set_decl(decl_index, errorSetNode);
248242
const errorList = errorSetNodeList(decl_index, errorSetNode);
249243
if (errorList != null && errorList.length > 0) {
250-
markdown += "## Errors\n\n";
244+
markdown += "\n\n## Errors\n";
251245
for (let i = 0; i < errorList.length; i++) {
252246
const error_html = unwrapString(wasm_exports.error_html(base_decl, errorList[i]));
253-
markdown += error_html + "\n\n";
247+
markdown += "\n" + error_html;
254248
}
255249
}
256250
}
257251

258252
// Add doctest
259253
const doctest = unwrapString(wasm_exports.decl_doctest_html(decl_index));
260254
if (doctest.length > 0) {
261-
markdown += "## Example Usage\n\n" + doctest + "\n\n";
255+
markdown += "\n\n## Example Usage\n\n" + doctest;
262256
}
263257

264258
// Add source code
265259
const source = unwrapString(wasm_exports.decl_source_html(decl_index));
266260
if (source.length > 0) {
267-
markdown += "## Source Code\n\n" + source + "\n\n";
261+
markdown += "\n\n## Source Code\n\n" + source;
268262
}
269263

270264
if (domContent) domContent.textContent = markdown;
@@ -274,9 +268,6 @@ function renderFunction(decl_index: any) {
274268
function renderGlobal(decl_index: any) {
275269
let markdown = "";
276270

277-
// Add navigation breadcrumb
278-
markdown += renderNavMarkdown(decl_index);
279-
280271
// Add title
281272
const name = unwrapString(wasm_exports.decl_category_name(decl_index));
282273
markdown += "# " + name + "\n\n";
@@ -300,9 +291,6 @@ function renderGlobal(decl_index: any) {
300291
function renderTypeFunction(decl_index: any) {
301292
let markdown = "";
302293

303-
// Add navigation breadcrumb
304-
markdown += renderNavMarkdown(decl_index);
305-
306294
// Add title
307295
const name = unwrapString(wasm_exports.decl_category_name(decl_index));
308296
markdown += "# " + name + "\n\n";
@@ -348,9 +336,6 @@ function renderTypeFunction(decl_index: any) {
348336
function renderErrorSetPage(decl_index: any) {
349337
let markdown = "";
350338

351-
// Add navigation breadcrumb
352-
markdown += renderNavMarkdown(decl_index);
353-
354339
// Add title
355340
const name = unwrapString(wasm_exports.decl_category_name(decl_index));
356341
markdown += "# " + name + "\n\n";
@@ -864,7 +849,7 @@ export async function getStdLibItem(
864849
wasmPath: string,
865850
stdSources: Uint8Array<ArrayBuffer>,
866851
name: string,
867-
getSourceCode: boolean = false,
852+
getSourceFile: boolean = false,
868853
): Promise<string> {
869854
const fs = await import("node:fs");
870855
const wasmBytes = fs.readFileSync(wasmPath);
@@ -893,13 +878,21 @@ export async function getStdLibItem(
893878
return `# Error\n\nDeclaration "${name}" not found.`;
894879
}
895880

896-
if (getSourceCode) {
897-
const source = unwrapString(exports.decl_source_html(decl_index));
898-
if (source.length > 0) {
899-
return `# ${name} - Source Code\n\n${source}`;
900-
} else {
901-
return `# ${name}\n\nNo source code available.`;
881+
if (getSourceFile) {
882+
// Get the source file using decl_source_html for the file root
883+
// We need to find the file that contains this declaration
884+
const fqn = fullyQualifiedName(decl_index);
885+
const filePath = getFilePathFromFqn(fqn);
886+
if (filePath) {
887+
const fileDecl = findFileRoot(filePath);
888+
if (fileDecl !== null) {
889+
let markdown = "";
890+
markdown += "# " + filePath + "\n\n";
891+
markdown += unwrapString(wasm_exports.decl_source_html(fileDecl));
892+
return markdown;
893+
}
902894
}
895+
return `# Error\n\nCould not find source file for "${name}".`;
903896
}
904897

905898
const category = exports.categorize_decl(decl_index, 0);
@@ -924,9 +917,23 @@ export async function getStdLibItem(
924917
wasmPath,
925918
stdSources,
926919
fullyQualifiedName(exports.get_aliasee()),
927-
getSourceCode,
920+
getSourceFile,
928921
);
929922
default:
930923
return `# Error\n\nUnrecognized category ${category} for "${name}".`;
931924
}
932925
}
926+
927+
function getFilePathFromFqn(fqn: string): string | null {
928+
// Convert fully qualified name to file path
929+
const parts = fqn.split(".");
930+
if (parts.length < 2) return null;
931+
932+
if (parts[0] === "std" && parts.length >= 2) {
933+
return parts[0] + "/" + parts[1] + ".zig";
934+
}
935+
936+
const pathParts = parts.slice(0, -1);
937+
const filePath = pathParts.join("/") + ".zig";
938+
return filePath;
939+
}

mcp/tools.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -164,23 +164,23 @@ function getStdLibItemTool(wasmPath: string, stdSources: Uint8Array<ArrayBuffer>
164164
.describe(
165165
"Fully qualified name of the standard library item (e.g., 'std.ArrayList', 'std.debug.print', 'std.mem.Allocator')",
166166
),
167-
get_source_code: z
167+
get_source_file: z
168168
.boolean()
169169
.default(false)
170170
.describe(
171-
"Include source code only in the response (default: false - shows detailed documentation only)",
171+
"Return the entire source file where this item is implemented (default: false - shows detailed documentation with item source code only)",
172172
),
173173
},
174174
},
175175
handler: async ({
176176
name,
177-
get_source_code = false,
177+
get_source_file = false,
178178
}: {
179179
name: string;
180-
get_source_code: boolean;
180+
get_source_file: boolean;
181181
}) => {
182182
try {
183-
const markdown = await getStdLibItem(wasmPath, stdSources, name, get_source_code);
183+
const markdown = await getStdLibItem(wasmPath, stdSources, name, get_source_file);
184184
return {
185185
content: [
186186
{

0 commit comments

Comments
 (0)