@@ -633,51 +633,102 @@ async def process_skill_dir(directory, category):
633633 return f"Error: Failed to list skills: { str (e )} "
634634
635635
636- @mcp .tool ()
637- async def get_skill_info (skill_name : str ) -> str :
636+ async def _read_skill_file (skill_name : str , filename : str ) -> tuple [str , str , str ]:
638637 """
639- Retrieves the documentation (SKILL.md) for a specific skill.
638+ Helper function to read a file from a skill's directory .
640639
641640 Args:
642- skill_name: The name of the skill (e.g., 'pdf-text-replace', 'image-crop-rotate')
641+ skill_name: The name of the skill
642+ filename: The name of the file to read (e.g., 'SKILL.md', 'EXAMPLES.md')
643643
644644 Returns:
645- The content of the skill's SKILL.md file with usage instructions and examples.
645+ A tuple of (content, skill_type, error_message)
646+ If successful, error_message is None
647+ If failed, content and skill_type are None
646648 """
647649 try :
648650 # Check public skills first
649- public_skill_path = PUBLIC_SKILLS_DIR / skill_name / "SKILL.md"
650- user_skill_path = USER_SKILLS_DIR / skill_name / "SKILL.md"
651+ public_skill_file = PUBLIC_SKILLS_DIR / skill_name / filename
652+ user_skill_file = USER_SKILLS_DIR / skill_name / filename
651653
652- skill_path = None
654+ skill_file_path = None
653655 skill_type = None
654656
655- if public_skill_path .exists ():
656- skill_path = public_skill_path
657+ if public_skill_file .exists ():
658+ skill_file_path = public_skill_file
657659 skill_type = "public"
658- elif user_skill_path .exists ():
659- skill_path = user_skill_path
660+ elif user_skill_file .exists ():
661+ skill_file_path = user_skill_file
660662 skill_type = "user"
661663 else :
662- return f"Error: Skill '{ skill_name } ' not found. Use list_skills() to see available skills."
664+ return None , None , f"Error: File '{ filename } ' not found in skill ' { skill_name } ' . Use list_skills() to see available skills."
663665
664- # Read the SKILL.md content
665- async with aiofiles .open (skill_path , mode = 'r' ) as f :
666+ # Read the file content
667+ async with aiofiles .open (skill_file_path , mode = 'r' ) as f :
666668 content = await f .read ()
667669
668670 # Replace all occurrences of /mnt/user-data with /app/uploads
669671 content = content .replace ('/mnt/user-data' , '/app/uploads' )
670672
671- # Add header with skill type
672- header = f"Skill: { skill_name } ({ skill_type } )\n "
673- header += f"Location: /app/uploads/skills/{ skill_type } /{ skill_name } /\n "
674- header += "=" * 80 + "\n \n "
675-
676- return header + content
673+ return content , skill_type , None
677674
678675 except Exception as e :
679- logger .error (f"Failed to get skill info for '{ skill_name } ': { e } " )
680- return f"Error: Failed to get skill info: { str (e )} "
676+ logger .error (f"Failed to read file '{ filename } ' from skill '{ skill_name } ': { e } " )
677+ return None , None , f"Error: Failed to read file: { str (e )} "
678+
679+
680+ @mcp .tool ()
681+ async def get_skill_info (skill_name : str ) -> str :
682+ """
683+ Retrieves the documentation (SKILL.md) for a specific skill.
684+
685+ Args:
686+ skill_name: The name of the skill (e.g., 'pdf-text-replace', 'image-crop-rotate')
687+
688+ Returns:
689+ The content of the skill's SKILL.md file with usage instructions and examples.
690+ """
691+ content , skill_type , error = await _read_skill_file (skill_name , "SKILL.md" )
692+
693+ if error :
694+ return error
695+
696+ # Add header with skill type
697+ header = f"Skill: { skill_name } ({ skill_type } )\n "
698+ header += f"Location: /app/uploads/skills/{ skill_type } /{ skill_name } /\n "
699+ header += "=" * 80 + "\n \n "
700+
701+ return header + content
702+
703+
704+ @mcp .tool ()
705+ async def get_skill_file (skill_name : str , filename : str ) -> str :
706+ """
707+ Retrieves any markdown file from a skill's directory.
708+ This is useful when SKILL.md references other documentation files like EXAMPLES.md, API.md, etc.
709+
710+ Args:
711+ skill_name: The name of the skill (e.g., 'pdf-text-replace', 'image-crop-rotate')
712+ filename: The name of the markdown file to read (e.g., 'EXAMPLES.md', 'API.md', 'README.md')
713+
714+ Returns:
715+ The content of the requested file with /mnt/user-data paths replaced with /app/uploads.
716+
717+ Example:
718+ get_skill_file('pdf-text-replace', 'EXAMPLES.md')
719+ """
720+ content , skill_type , error = await _read_skill_file (skill_name , filename )
721+
722+ if error :
723+ return error
724+
725+ # Add header with file info
726+ header = f"Skill: { skill_name } ({ skill_type } )\n "
727+ header += f"File: { filename } \n "
728+ header += f"Location: /app/uploads/skills/{ skill_type } /{ skill_name } /{ filename } \n "
729+ header += "=" * 80 + "\n \n "
730+
731+ return header + content
681732
682733
683734# Use the streamable_http_app as it's the modern standard
0 commit comments