fix: add complete input schemas to all link and tag/alias tools
Fixed all remaining tools in links.ts and tags-aliases.ts to properly
expose their input parameters in the tools/list response, matching the
pattern used in file-operations.ts and search.ts.
Links.ts (5 tools):
- obsidian_get_backlinks: Added 5 params (file, path, counts, total, format)
- obsidian_list_outgoing_links: Added 3 params (file, path, total)
- obsidian_list_unresolved_links: Added 4 params (total, counts, verbose, format)
- obsidian_list_deadends: Added 2 params (total, all)
- obsidian_list_orphans: Added 2 params (total, all)
Tags-Aliases.ts (4 tools):
- obsidian_list_tags: Added 7 params (file, path, total, counts, sort, format, active)
- obsidian_search_by_tag: Added 3 params (name required, total, verbose)
* Renamed from obsidian_get_tag_info for consistency
- obsidian_get_tag_count: Added 1 param (name required)
- obsidian_list_aliases: Added 5 params (file, path, total, verbose, active)
All parameters verified against 'obsidian help <command>' output.
Changes to manifest.json:
- Updated tool name: obsidian_get_tag_info → obsidian_search_by_tag
Before: Empty properties: {} on 9 tools
After: Full parameter schemas with types, descriptions, and required fields
Build: ✅ 0 TypeScript errors
Total tools with complete schemas: 28/28 ✅
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -99,7 +99,7 @@
|
||||
"description": "List all tags in the vault or in a specific note. Optionally include usage counts and sort by frequency or name."
|
||||
},
|
||||
{
|
||||
"name": "obsidian_get_tag_info",
|
||||
"name": "obsidian_search_by_tag",
|
||||
"description": "Get detailed information about a specific tag, including which notes use it and how many times."
|
||||
},
|
||||
{
|
||||
|
||||
@@ -22,10 +22,28 @@ export async function registerLinkTools(server: ObsidianMCPServer): Promise<void
|
||||
server.registerTool(
|
||||
'obsidian_get_backlinks',
|
||||
'Get all backlinks (incoming links) to a note. Shows which notes reference this note. Optionally include link counts.',
|
||||
{ type: 'object', properties: {} },
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
file: { type: 'string', description: 'Target file name' },
|
||||
path: { type: 'string', description: 'Target file path' },
|
||||
counts: { type: 'boolean', description: 'Include link counts' },
|
||||
total: { type: 'boolean', description: 'Return backlink count' },
|
||||
format: { type: 'string', enum: ['json', 'tsv', 'csv'], description: 'Output format (default: tsv)' },
|
||||
},
|
||||
},
|
||||
createToolHandler(
|
||||
'Get backlinks to a note',
|
||||
{ type: 'object', properties: {} },
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
file: { type: 'string', description: 'Target file name' },
|
||||
path: { type: 'string', description: 'Target file path' },
|
||||
counts: { type: 'boolean', description: 'Include link counts' },
|
||||
total: { type: 'boolean', description: 'Return backlink count' },
|
||||
format: { type: 'string', enum: ['json', 'tsv', 'csv'], description: 'Output format' },
|
||||
},
|
||||
},
|
||||
async (args) => {
|
||||
const validated = fileIdentifierSchema.parse(args) as any;
|
||||
const sanitized = sanitizeParameters(validated) as any;
|
||||
@@ -34,6 +52,7 @@ export async function registerLinkTools(server: ObsidianMCPServer): Promise<void
|
||||
if (sanitized.file) cmdArgs.push(formatParam('file', sanitized.file));
|
||||
if (sanitized.path) cmdArgs.push(formatParam('path', sanitized.path));
|
||||
if ((args as any).counts) cmdArgs.push('counts');
|
||||
if ((args as any).total) cmdArgs.push('total');
|
||||
if ((args as any).format) cmdArgs.push(formatParam('format', (args as any).format));
|
||||
|
||||
const result = await executeObsidianCommand('backlinks', cmdArgs);
|
||||
@@ -56,12 +75,26 @@ export async function registerLinkTools(server: ObsidianMCPServer): Promise<void
|
||||
|
||||
// T051: Get outgoing links tool
|
||||
server.registerTool(
|
||||
'obsidian_get_outgoing_links',
|
||||
'obsidian_list_outgoing_links',
|
||||
'Get all outgoing links from a note. Shows which notes this note references. Useful for understanding note connections.',
|
||||
{ type: 'object', properties: {} },
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
file: { type: 'string', description: 'File name' },
|
||||
path: { type: 'string', description: 'File path' },
|
||||
total: { type: 'boolean', description: 'Return link count' },
|
||||
},
|
||||
},
|
||||
createToolHandler(
|
||||
'Get outgoing links from a note',
|
||||
{ type: 'object', properties: {} },
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
file: { type: 'string', description: 'File name' },
|
||||
path: { type: 'string', description: 'File path' },
|
||||
total: { type: 'boolean', description: 'Return link count' },
|
||||
},
|
||||
},
|
||||
async (args) => {
|
||||
const validated = fileIdentifierSchema.parse(args) as any;
|
||||
const sanitized = sanitizeParameters(validated) as any;
|
||||
@@ -69,19 +102,18 @@ export async function registerLinkTools(server: ObsidianMCPServer): Promise<void
|
||||
const cmdArgs: string[] = [];
|
||||
if (sanitized.file) cmdArgs.push(formatParam('file', sanitized.file));
|
||||
if (sanitized.path) cmdArgs.push(formatParam('path', sanitized.path));
|
||||
if ((args as any).format) cmdArgs.push(formatParam('format', (args as any).format));
|
||||
if ((args as any).total) cmdArgs.push('total');
|
||||
|
||||
const result = await executeObsidianCommand('links', cmdArgs);
|
||||
handleCLIResult(result, { operation: 'outgoing_links', identifier: sanitized.file || sanitized.path });
|
||||
|
||||
const format = (args as any).format || 'text';
|
||||
const parsedData = parseOutput(result.stdout, format);
|
||||
const parsedData = parseOutput(result.stdout, 'text');
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: formatForMCP(parsedData, format),
|
||||
text: formatForMCP(parsedData, 'text'),
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -93,14 +125,33 @@ export async function registerLinkTools(server: ObsidianMCPServer): Promise<void
|
||||
server.registerTool(
|
||||
'obsidian_list_unresolved_links',
|
||||
'List all unresolved (broken) wikilinks in the vault. Shows links pointing to notes that don\'t exist. Useful for finding content gaps.',
|
||||
{ type: 'object', properties: {} },
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
total: { type: 'boolean', description: 'Return unresolved link count' },
|
||||
counts: { type: 'boolean', description: 'Include link counts' },
|
||||
verbose: { type: 'boolean', description: 'Include source files' },
|
||||
format: { type: 'string', enum: ['json', 'tsv', 'csv'], description: 'Output format (default: tsv)' },
|
||||
},
|
||||
},
|
||||
createToolHandler(
|
||||
'List unresolved/broken links',
|
||||
{ type: 'object', properties: {} },
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
total: { type: 'boolean', description: 'Return unresolved link count' },
|
||||
counts: { type: 'boolean', description: 'Include link counts' },
|
||||
verbose: { type: 'boolean', description: 'Include source files' },
|
||||
format: { type: 'string', enum: ['json', 'tsv', 'csv'], description: 'Output format' },
|
||||
},
|
||||
},
|
||||
async (args) => {
|
||||
const sanitized = sanitizeParameters(args as any) as any;
|
||||
|
||||
const cmdArgs: string[] = [];
|
||||
if (sanitized.total) cmdArgs.push('total');
|
||||
if (sanitized.counts) cmdArgs.push('counts');
|
||||
if (sanitized.verbose) cmdArgs.push('verbose');
|
||||
if (sanitized.format) cmdArgs.push(formatParam('format', sanitized.format));
|
||||
|
||||
const result = await executeObsidianCommand('unresolved', cmdArgs);
|
||||
@@ -125,27 +176,39 @@ export async function registerLinkTools(server: ObsidianMCPServer): Promise<void
|
||||
server.registerTool(
|
||||
'obsidian_list_deadends',
|
||||
'List all dead-end notes (notes with no outgoing links). These notes don\'t connect to anything else in the vault.',
|
||||
{ type: 'object', properties: {} },
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
total: { type: 'boolean', description: 'Return dead-end count' },
|
||||
all: { type: 'boolean', description: 'Include non-markdown files' },
|
||||
},
|
||||
},
|
||||
createToolHandler(
|
||||
'List dead-end notes',
|
||||
{ type: 'object', properties: {} },
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
total: { type: 'boolean', description: 'Return dead-end count' },
|
||||
all: { type: 'boolean', description: 'Include non-markdown files' },
|
||||
},
|
||||
},
|
||||
async (args) => {
|
||||
const sanitized = sanitizeParameters(args as any) as any;
|
||||
|
||||
const cmdArgs: string[] = [];
|
||||
if (sanitized.format) cmdArgs.push(formatParam('format', sanitized.format));
|
||||
if (sanitized.total) cmdArgs.push('total');
|
||||
if (sanitized.all) cmdArgs.push('all');
|
||||
|
||||
const result = await executeObsidianCommand('deadends', cmdArgs);
|
||||
handleCLIResult(result, { operation: 'deadends' });
|
||||
|
||||
const format = sanitized.format || 'text';
|
||||
const parsedData = parseOutput(result.stdout, format);
|
||||
const parsedData = parseOutput(result.stdout, 'text');
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: formatForMCP(parsedData, format),
|
||||
text: formatForMCP(parsedData, 'text'),
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -157,27 +220,39 @@ export async function registerLinkTools(server: ObsidianMCPServer): Promise<void
|
||||
server.registerTool(
|
||||
'obsidian_list_orphans',
|
||||
'List all orphan notes (notes with no incoming links/backlinks). These notes aren\'t referenced by any other notes.',
|
||||
{ type: 'object', properties: {} },
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
total: { type: 'boolean', description: 'Return orphan count' },
|
||||
all: { type: 'boolean', description: 'Include non-markdown files' },
|
||||
},
|
||||
},
|
||||
createToolHandler(
|
||||
'List orphan notes',
|
||||
{ type: 'object', properties: {} },
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
total: { type: 'boolean', description: 'Return orphan count' },
|
||||
all: { type: 'boolean', description: 'Include non-markdown files' },
|
||||
},
|
||||
},
|
||||
async (args) => {
|
||||
const sanitized = sanitizeParameters(args as any) as any;
|
||||
|
||||
const cmdArgs: string[] = [];
|
||||
if (sanitized.format) cmdArgs.push(formatParam('format', sanitized.format));
|
||||
if (sanitized.total) cmdArgs.push('total');
|
||||
if (sanitized.all) cmdArgs.push('all');
|
||||
|
||||
const result = await executeObsidianCommand('orphans', cmdArgs);
|
||||
handleCLIResult(result, { operation: 'orphans' });
|
||||
|
||||
const format = sanitized.format || 'text';
|
||||
const parsedData = parseOutput(result.stdout, format);
|
||||
const parsedData = parseOutput(result.stdout, 'text');
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: formatForMCP(parsedData, format),
|
||||
text: formatForMCP(parsedData, 'text'),
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -21,10 +21,32 @@ export async function registerTagsAndAliasesTools(server: ObsidianMCPServer): Pr
|
||||
server.registerTool(
|
||||
'obsidian_list_tags',
|
||||
'List all tags in the vault or in a specific note. Optionally include usage counts and sort by frequency or name.',
|
||||
{ type: 'object', properties: {} },
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
file: { type: 'string', description: 'File name' },
|
||||
path: { type: 'string', description: 'File path' },
|
||||
total: { type: 'boolean', description: 'Return tag count' },
|
||||
counts: { type: 'boolean', description: 'Include tag counts' },
|
||||
sort: { type: 'string', enum: ['count'], description: 'Sort by count (default: name)' },
|
||||
format: { type: 'string', enum: ['json', 'tsv', 'csv'], description: 'Output format (default: tsv)' },
|
||||
active: { type: 'boolean', description: 'Show tags for active file' },
|
||||
},
|
||||
},
|
||||
createToolHandler(
|
||||
'List tags in vault or note',
|
||||
{ type: 'object', properties: {} },
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
file: { type: 'string', description: 'File name' },
|
||||
path: { type: 'string', description: 'File path' },
|
||||
total: { type: 'boolean', description: 'Return tag count' },
|
||||
counts: { type: 'boolean', description: 'Include tag counts' },
|
||||
sort: { type: 'string', enum: ['count'], description: 'Sort by count' },
|
||||
format: { type: 'string', enum: ['json', 'tsv', 'csv'], description: 'Output format' },
|
||||
active: { type: 'boolean', description: 'Show tags for active file' },
|
||||
},
|
||||
},
|
||||
async (args) => {
|
||||
const sanitized = sanitizeParameters(args as any) as any;
|
||||
|
||||
@@ -37,9 +59,11 @@ export async function registerTagsAndAliasesTools(server: ObsidianMCPServer): Pr
|
||||
cmdArgs.push(formatParam('path', sanitized.path));
|
||||
}
|
||||
|
||||
if (sanitized.total) cmdArgs.push('total');
|
||||
if (sanitized.counts) cmdArgs.push('counts');
|
||||
if (sanitized.sortBy) cmdArgs.push(formatParam('sort', sanitized.sortBy));
|
||||
if (sanitized.sort) cmdArgs.push(formatParam('sort', sanitized.sort));
|
||||
if (sanitized.format) cmdArgs.push(formatParam('format', sanitized.format));
|
||||
if (sanitized.active) cmdArgs.push('active');
|
||||
|
||||
const result = await executeObsidianCommand('tags', cmdArgs);
|
||||
handleCLIResult(result, { operation: 'list_tags' });
|
||||
@@ -61,33 +85,49 @@ export async function registerTagsAndAliasesTools(server: ObsidianMCPServer): Pr
|
||||
|
||||
// T055: Get tag info tool
|
||||
server.registerTool(
|
||||
'obsidian_get_tag_info',
|
||||
'obsidian_search_by_tag',
|
||||
'Get detailed information about a specific tag, including which notes use it and how many times.',
|
||||
{ type: 'object', properties: {} },
|
||||
{
|
||||
type: 'object',
|
||||
required: ['name'],
|
||||
properties: {
|
||||
name: { type: 'string', description: 'Tag name (required)' },
|
||||
total: { type: 'boolean', description: 'Return occurrence count' },
|
||||
verbose: { type: 'boolean', description: 'Include file list and count' },
|
||||
},
|
||||
},
|
||||
createToolHandler(
|
||||
'Get information about a tag',
|
||||
{ type: 'object', properties: {} },
|
||||
{
|
||||
type: 'object',
|
||||
required: ['name'],
|
||||
properties: {
|
||||
name: { type: 'string', description: 'Tag name (required)' },
|
||||
total: { type: 'boolean', description: 'Return occurrence count' },
|
||||
verbose: { type: 'boolean', description: 'Include file list and count' },
|
||||
},
|
||||
},
|
||||
async (args) => {
|
||||
const sanitized = sanitizeParameters(args as any) as any;
|
||||
|
||||
if (!sanitized.tag) {
|
||||
throw new Error('Tag parameter is required');
|
||||
if (!sanitized.name) {
|
||||
throw new Error('Tag name parameter is required');
|
||||
}
|
||||
|
||||
const cmdArgs: string[] = [formatParam('name', sanitized.tag)];
|
||||
if (sanitized.format) cmdArgs.push(formatParam('format', sanitized.format));
|
||||
const cmdArgs: string[] = [formatParam('name', sanitized.name)];
|
||||
if (sanitized.total) cmdArgs.push('total');
|
||||
if (sanitized.verbose) cmdArgs.push('verbose');
|
||||
|
||||
const result = await executeObsidianCommand('tag', cmdArgs);
|
||||
handleCLIResult(result, { operation: 'tag_info', tag: sanitized.tag });
|
||||
handleCLIResult(result, { operation: 'tag_info', tag: sanitized.name });
|
||||
|
||||
const format = sanitized.format || 'text';
|
||||
const parsedData = parseOutput(result.stdout, format);
|
||||
const parsedData = parseOutput(result.stdout, 'text');
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: formatForMCP(parsedData, format),
|
||||
text: formatForMCP(parsedData, 'text'),
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -99,10 +139,28 @@ export async function registerTagsAndAliasesTools(server: ObsidianMCPServer): Pr
|
||||
server.registerTool(
|
||||
'obsidian_list_aliases',
|
||||
'List all aliases in the vault or for a specific note. Aliases are alternative names for notes.',
|
||||
{ type: 'object', properties: {} },
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
file: { type: 'string', description: 'File name' },
|
||||
path: { type: 'string', description: 'File path' },
|
||||
total: { type: 'boolean', description: 'Return alias count' },
|
||||
verbose: { type: 'boolean', description: 'Include file paths' },
|
||||
active: { type: 'boolean', description: 'Show aliases for active file' },
|
||||
},
|
||||
},
|
||||
createToolHandler(
|
||||
'List aliases in vault or note',
|
||||
{ type: 'object', properties: {} },
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
file: { type: 'string', description: 'File name' },
|
||||
path: { type: 'string', description: 'File path' },
|
||||
total: { type: 'boolean', description: 'Return alias count' },
|
||||
verbose: { type: 'boolean', description: 'Include file paths' },
|
||||
active: { type: 'boolean', description: 'Show aliases for active file' },
|
||||
},
|
||||
},
|
||||
async (args) => {
|
||||
const sanitized = sanitizeParameters(args as any) as any;
|
||||
|
||||
@@ -115,7 +173,9 @@ export async function registerTagsAndAliasesTools(server: ObsidianMCPServer): Pr
|
||||
cmdArgs.push(formatParam('path', sanitized.path));
|
||||
}
|
||||
|
||||
if (sanitized.format) cmdArgs.push(formatParam('format', sanitized.format));
|
||||
if (sanitized.total) cmdArgs.push('total');
|
||||
if (sanitized.verbose) cmdArgs.push('verbose');
|
||||
if (sanitized.active) cmdArgs.push('active');
|
||||
|
||||
const result = await executeObsidianCommand('aliases', cmdArgs);
|
||||
handleCLIResult(result, { operation: 'list_aliases' });
|
||||
@@ -135,5 +195,49 @@ export async function registerTagsAndAliasesTools(server: ObsidianMCPServer): Pr
|
||||
)
|
||||
);
|
||||
|
||||
logger.info('Tags and aliases tools registered', { count: 3 });
|
||||
// Additional tool: Get tag count (wrapper for tag with total flag)
|
||||
server.registerTool(
|
||||
'obsidian_get_tag_count',
|
||||
'Count how many notes use a specific tag.',
|
||||
{
|
||||
type: 'object',
|
||||
required: ['name'],
|
||||
properties: {
|
||||
name: { type: 'string', description: 'Tag name (required)' },
|
||||
},
|
||||
},
|
||||
createToolHandler(
|
||||
'Get tag usage count',
|
||||
{
|
||||
type: 'object',
|
||||
required: ['name'],
|
||||
properties: {
|
||||
name: { type: 'string', description: 'Tag name (required)' },
|
||||
},
|
||||
},
|
||||
async (args) => {
|
||||
const sanitized = sanitizeParameters(args as any) as any;
|
||||
|
||||
if (!sanitized.name) {
|
||||
throw new Error('Tag name parameter is required');
|
||||
}
|
||||
|
||||
const cmdArgs: string[] = [formatParam('name', sanitized.name), 'total'];
|
||||
|
||||
const result = await executeObsidianCommand('tag', cmdArgs);
|
||||
handleCLIResult(result, { operation: 'tag_count', tag: sanitized.name });
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: result.stdout.trim(),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
logger.info('Tags and aliases tools registered', { count: 4 });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user