/**
* Skills Library Connector for Bridge MCP
*
* Fetches skills (instruction files) from GitHub repositories.
* Skills are markdown files that teach AI how to perform specific tasks.
*
* Supports multiple skill sources:
* 1. Official Bridge skills library (Amara's curated skills)
* 2. Student's personal/team skills repo
* 3. Community skills
*/
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
// Default skills repository (Amara's official library)
const DEFAULT_SKILLS_REPO = "amara-ai/bridge-skills";
const DEFAULT_BRANCH = "main";
// Helper to fetch from GitHub raw content
async function fetchGitHubFile(
repo: string,
path: string,
branch: string = DEFAULT_BRANCH
): Promise<string> {
const url = `https://raw.githubusercontent.com/${repo}/${branch}/${path}`;
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Failed to fetch skill: ${response.status} - ${url}`);
}
return response.text();
}
// Helper to list files in a GitHub directory
async function listGitHubDirectory(
repo: string,
path: string = "",
branch: string = DEFAULT_BRANCH
): Promise<any[]> {
const url = `https://api.github.com/repos/${repo}/contents/${path}?ref=${branch}`;
const response = await fetch(url, {
headers: {
"Accept": "application/vnd.github.v3+json",
"User-Agent": "Bridge-MCP-Server",
},
});
if (!response.ok) {
throw new Error(`Failed to list directory: ${response.status}`);
}
return response.json();
}
export function registerSkillTools(server: McpServer) {
server.tool(
"bridge_get_skill",
"Fetch a skill (instruction file) from the skills library. Skills teach you how to perform specific tasks consistently. Use this before starting a task to load the right approach.",
{
skill_name: z.string().describe("Name of the skill to fetch (e.g., 'transcript-to-tasks', 'client-onboarding')"),
repo: z.string().optional().describe(`GitHub repo in 'owner/repo' format. Defaults to official Bridge library.`),
branch: z.string().optional().describe("Git branch (default: main)"),
category: z.string().optional().describe("Skill category folder (e.g., 'project-management', 'sales-ops')"),
},
async ({ skill_name, repo = DEFAULT_SKILLS_REPO, branch = DEFAULT_BRANCH, category }) => {
try {
// Build the path to the skill file
let path = "";
if (category) {
path = `${category}/${skill_name}/SKILL.md`;
} else {
// Try to find the skill in common locations
const possiblePaths = [
`${skill_name}/SKILL.md`,
`${skill_name}.md`,
`skills/${skill_name}/SKILL.md`,
`skills/${skill_name}.md`,
];
for (const tryPath of possiblePaths) {
try {
const content = await fetchGitHubFile(repo, tryPath, branch);
return {
content: [{
type: "text",
text: `# Skill: ${skill_name}\n# Source: ${repo}/${tryPath}\n\n${content}`
}]
};
} catch {
continue;
}
}
throw new Error(`Skill '${skill_name}' not found in ${repo}`);
}
const content = await fetchGitHubFile(repo, path, branch);
return {
content: [{
type: "text",
text: `# Skill: ${skill_name}\n# Source: ${repo}/${path}\n\n${content}`
}]
};
} catch (error) {
return {
content: [{
type: "text",
text: `Error fetching skill: ${error instanceof Error ? error.message : 'Unknown error'}`
}]
};
}
}
);
server.tool(
"bridge_list_skills",
"List all available skills in a skills library. Use this to discover what skills are available.",
{
repo: z.string().optional().describe(`GitHub repo in 'owner/repo' format. Defaults to official Bridge library.`),
branch: z.string().optional().describe("Git branch (default: main)"),
category: z.string().optional().describe("Filter by category folder"),
},
async ({ repo = DEFAULT_SKILLS_REPO, branch = DEFAULT_BRANCH, category }) => {
try {
const path = category || "";
const contents = await listGitHubDirectory(repo, path, branch);
// Filter for directories (skills) or markdown files
const skills = contents
.filter((item: any) => item.type === "dir" || item.name.endsWith(".md"))
.map((item: any) => ({
name: item.name.replace(".md", ""),
type: item.type === "dir" ? "skill-folder" : "skill-file",
path: item.path,
}));
const output = {
repo,
branch,
category: category || "(root)",
skills,
count: skills.length,
};
return {
content: [{
type: "text",
text: JSON.stringify(output, null, 2)
}]
};
} catch (error) {
return {
content: [{
type: "text",
text: `Error listing skills: ${error instanceof Error ? error.message : 'Unknown error'}`
}]
};
}
}
);
server.tool(
"bridge_get_skill_categories",
"Get the top-level categories in a skills library. Categories organize skills by use case (e.g., project-management, sales-ops, client-management).",
{
repo: z.string().optional().describe(`GitHub repo in 'owner/repo' format. Defaults to official Bridge library.`),
branch: z.string().optional().describe("Git branch (default: main)"),
},
async ({ repo = DEFAULT_SKILLS_REPO, branch = DEFAULT_BRANCH }) => {
try {
const contents = await listGitHubDirectory(repo, "", branch);
// Filter for directories only (categories)
const categories = contents
.filter((item: any) => item.type === "dir" && !item.name.startsWith("."))
.map((item: any) => item.name);
const output = {
repo,
branch,
categories,
count: categories.length,
};
return {
content: [{
type: "text",
text: JSON.stringify(output, null, 2)
}]
};
} catch (error) {
return {
content: [{
type: "text",
text: `Error fetching categories: ${error instanceof Error ? error.message : 'Unknown error'}`
}]
};
}
}
);
server.tool(
"bridge_search_skills",
"Search for skills by keyword across skill names and descriptions.",
{
query: z.string().describe("Search query (matches against skill names)"),
repo: z.string().optional().describe(`GitHub repo in 'owner/repo' format. Defaults to official Bridge library.`),
branch: z.string().optional().describe("Git branch (default: main)"),
},
async ({ query, repo = DEFAULT_SKILLS_REPO, branch = DEFAULT_BRANCH }) => {
try {
// Get all top-level items
const contents = await listGitHubDirectory(repo, "", branch);
const categories = contents.filter((item: any) => item.type === "dir" && !item.name.startsWith("."));
const matches: any[] = [];
const queryLower = query.toLowerCase();
// Search through each category
for (const category of categories) {
try {
const categoryContents = await listGitHubDirectory(repo, category.name, branch);
const skills = categoryContents.filter((item: any) =>
item.type === "dir" || item.name.endsWith(".md")
);
for (const skill of skills) {
if (skill.name.toLowerCase().includes(queryLower)) {
matches.push({
name: skill.name.replace(".md", ""),
category: category.name,
path: skill.path,
});
}
}
} catch {
// Skip categories we can't read
continue;
}
}
const output = {
query,
repo,
matches,
count: matches.length,
};
return {
content: [{
type: "text",
text: JSON.stringify(output, null, 2)
}]
};
} catch (error) {
return {
content: [{
type: "text",
text: `Error searching skills: ${error instanceof Error ? error.message : 'Unknown error'}`
}]
};
}
}
);
}