my-personal-code-mcp 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +26 -0
- package/README.md +166 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +62 -0
- package/dist/index.js.map +1 -0
- package/dist/services/github.d.ts +19 -0
- package/dist/services/github.d.ts.map +1 -0
- package/dist/services/github.js +92 -0
- package/dist/services/github.js.map +1 -0
- package/dist/tools/get-skill.d.ts +14 -0
- package/dist/tools/get-skill.d.ts.map +1 -0
- package/dist/tools/get-skill.js +32 -0
- package/dist/tools/get-skill.js.map +1 -0
- package/dist/tools/index.d.ts +3 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +3 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/list-skills.d.ts +7 -0
- package/dist/tools/list-skills.d.ts.map +1 -0
- package/dist/tools/list-skills.js +16 -0
- package/dist/tools/list-skills.js.map +1 -0
- package/package.json +42 -0
- package/src/index.ts +78 -0
- package/src/services/github.ts +142 -0
- package/src/tools/get-skill.ts +47 -0
- package/src/tools/index.ts +12 -0
- package/src/tools/list-skills.ts +23 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"permissions": {
|
|
3
|
+
"allow": [
|
|
4
|
+
"Bash(gh issue create:*)",
|
|
5
|
+
"mcp__github__get_me",
|
|
6
|
+
"Bash(git remote:*)",
|
|
7
|
+
"mcp__github__issue_write",
|
|
8
|
+
"Bash(npm init:*)",
|
|
9
|
+
"Bash(npm install:*)",
|
|
10
|
+
"Bash(npm run build:*)",
|
|
11
|
+
"Bash(set SKILLS_REPO_OWNER=lfdantoni)",
|
|
12
|
+
"Bash(set SKILLS_REPO_NAME=my-skills-repo:*)",
|
|
13
|
+
"Bash(set SKILLS_PATH=skills)",
|
|
14
|
+
"Bash(npx tsx:*)",
|
|
15
|
+
"Bash($env:SKILLS_REPO_OWNER=\"lfdantoni\")",
|
|
16
|
+
"Bash($env:SKILLS_REPO_NAME=\"my-skills-repo\")",
|
|
17
|
+
"Bash($env:SKILLS_PATH=\"skills\")",
|
|
18
|
+
"Bash(export SKILLS_REPO_OWNER=lfdantoni)",
|
|
19
|
+
"Bash(export SKILLS_REPO_NAME=my-skills-repo)",
|
|
20
|
+
"Bash(export SKILLS_PATH=skills)",
|
|
21
|
+
"mcp__github__get_file_contents",
|
|
22
|
+
"Bash(git add:*)",
|
|
23
|
+
"Bash(git commit -m \"$\\(cat <<''EOF''\nImplement MCP server for skills from GitHub repository\n\n- Add MCP server with stdio transport using @modelcontextprotocol/sdk\n- Implement list_skills tool to fetch skill names from GitHub repo\n- Implement get_skill tool to retrieve skill content by name\n- Add GitHubService with error handling for API calls\n- Configure TypeScript build with ES modules\n- Add documentation with Cursor, Claude Desktop, and CLI examples\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>\nEOF\n\\)\")"
|
|
24
|
+
]
|
|
25
|
+
}
|
|
26
|
+
}
|
package/README.md
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# my-personal-code-mcp
|
|
2
|
+
|
|
3
|
+
An MCP (Model Context Protocol) server that provides AI assistants with access to best practice skills stored as markdown files in a GitHub repository.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **list_skills**: Returns a list of all available skill names from the configured repository
|
|
8
|
+
- **get_skill**: Retrieves the content of a specific skill by name
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
# Clone the repository
|
|
14
|
+
git clone https://github.com/lfdantoni/my-personal-code-mcp.git
|
|
15
|
+
cd my-personal-code-mcp
|
|
16
|
+
|
|
17
|
+
# Install dependencies
|
|
18
|
+
npm install
|
|
19
|
+
|
|
20
|
+
# Build
|
|
21
|
+
npm run build
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Configuration
|
|
25
|
+
|
|
26
|
+
### Environment Variables
|
|
27
|
+
|
|
28
|
+
| Variable | Description | Required |
|
|
29
|
+
|----------|-------------|----------|
|
|
30
|
+
| `SKILLS_REPO_OWNER` | GitHub repository owner (username or organization) | Yes |
|
|
31
|
+
| `SKILLS_REPO_NAME` | GitHub repository name | Yes |
|
|
32
|
+
| `SKILLS_PATH` | Subdirectory containing skill files (default: root) | No |
|
|
33
|
+
| `GITHUB_TOKEN` | GitHub personal access token (required for private repos) | No |
|
|
34
|
+
|
|
35
|
+
### Skills Repository Structure
|
|
36
|
+
|
|
37
|
+
Your skills repository should contain markdown files (`.md`) with best practices:
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
your-skills-repo/
|
|
41
|
+
├── typescript-best-practices.md
|
|
42
|
+
├── react-patterns.md
|
|
43
|
+
├── testing-guidelines.md
|
|
44
|
+
└── ...
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Or with a subdirectory:
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
your-skills-repo/
|
|
51
|
+
└── skills/
|
|
52
|
+
├── typescript-best-practices.md
|
|
53
|
+
├── react-patterns.md
|
|
54
|
+
└── ...
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## MCP Client Configuration
|
|
58
|
+
|
|
59
|
+
### Cursor
|
|
60
|
+
|
|
61
|
+
Add the following to your Cursor MCP settings file (`~/.cursor/mcp.json` or via Settings > MCP):
|
|
62
|
+
|
|
63
|
+
```json
|
|
64
|
+
{
|
|
65
|
+
"mcpServers": {
|
|
66
|
+
"my-personal-code-mcp": {
|
|
67
|
+
"command": "node",
|
|
68
|
+
"args": ["C:/path/to/my-personal-code-mcp/dist/index.js"],
|
|
69
|
+
"env": {
|
|
70
|
+
"SKILLS_REPO_OWNER": "your-github-username",
|
|
71
|
+
"SKILLS_REPO_NAME": "your-skills-repo",
|
|
72
|
+
"SKILLS_PATH": "",
|
|
73
|
+
"GITHUB_TOKEN": "ghp_your_token_here"
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Claude Desktop
|
|
81
|
+
|
|
82
|
+
Add the following to your Claude Desktop configuration file:
|
|
83
|
+
|
|
84
|
+
**Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
|
|
85
|
+
**macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
86
|
+
|
|
87
|
+
```json
|
|
88
|
+
{
|
|
89
|
+
"mcpServers": {
|
|
90
|
+
"my-personal-code-mcp": {
|
|
91
|
+
"command": "node",
|
|
92
|
+
"args": ["C:/path/to/my-personal-code-mcp/dist/index.js"],
|
|
93
|
+
"env": {
|
|
94
|
+
"SKILLS_REPO_OWNER": "your-github-username",
|
|
95
|
+
"SKILLS_REPO_NAME": "your-skills-repo",
|
|
96
|
+
"SKILLS_PATH": "",
|
|
97
|
+
"GITHUB_TOKEN": "ghp_your_token_here"
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Claude Code CLI
|
|
105
|
+
|
|
106
|
+
Add to your Claude Code settings (`~/.claude/settings.json`):
|
|
107
|
+
|
|
108
|
+
```json
|
|
109
|
+
{
|
|
110
|
+
"mcpServers": {
|
|
111
|
+
"my-personal-code-mcp": {
|
|
112
|
+
"command": "node",
|
|
113
|
+
"args": ["/path/to/my-personal-code-mcp/dist/index.js"],
|
|
114
|
+
"env": {
|
|
115
|
+
"SKILLS_REPO_OWNER": "your-github-username",
|
|
116
|
+
"SKILLS_REPO_NAME": "your-skills-repo"
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Available Tools
|
|
124
|
+
|
|
125
|
+
### list_skills
|
|
126
|
+
|
|
127
|
+
Returns a list of all available skill names.
|
|
128
|
+
|
|
129
|
+
**Input**: None
|
|
130
|
+
|
|
131
|
+
**Output**:
|
|
132
|
+
```json
|
|
133
|
+
{
|
|
134
|
+
"skills": ["typescript-best-practices", "react-patterns", "testing-guidelines"]
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### get_skill
|
|
139
|
+
|
|
140
|
+
Returns the content of a specific skill.
|
|
141
|
+
|
|
142
|
+
**Input**:
|
|
143
|
+
```json
|
|
144
|
+
{
|
|
145
|
+
"skill_name": "typescript-best-practices"
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
**Output**: The markdown content of the skill file.
|
|
150
|
+
|
|
151
|
+
## Development
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
# Build
|
|
155
|
+
npm run build
|
|
156
|
+
|
|
157
|
+
# Watch mode
|
|
158
|
+
npm run dev
|
|
159
|
+
|
|
160
|
+
# Run the server
|
|
161
|
+
npm start
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## License
|
|
165
|
+
|
|
166
|
+
ISC
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { GitHubService } from "./services/github.js";
|
|
5
|
+
import { handleListSkills, getSkillSchema, handleGetSkill, } from "./tools/index.js";
|
|
6
|
+
const server = new McpServer({
|
|
7
|
+
name: "my-personal-code-mcp",
|
|
8
|
+
version: "1.0.0",
|
|
9
|
+
description: "MCP server for AI best practices skills from GitHub repository",
|
|
10
|
+
});
|
|
11
|
+
let githubService;
|
|
12
|
+
try {
|
|
13
|
+
githubService = new GitHubService();
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
console.error("Failed to initialize GitHub service:", error);
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
server.registerTool("list_skills", {
|
|
20
|
+
description: "Returns a list of all available best practice skills",
|
|
21
|
+
}, async () => {
|
|
22
|
+
try {
|
|
23
|
+
const result = await handleListSkills(githubService);
|
|
24
|
+
return {
|
|
25
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
30
|
+
return {
|
|
31
|
+
content: [{ type: "text", text: `Error: ${message}` }],
|
|
32
|
+
isError: true,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
server.registerTool("get_skill", {
|
|
37
|
+
description: "Returns the content of a specific best practice skill",
|
|
38
|
+
inputSchema: getSkillSchema,
|
|
39
|
+
}, async (input) => {
|
|
40
|
+
try {
|
|
41
|
+
const result = await handleGetSkill(githubService, input);
|
|
42
|
+
return {
|
|
43
|
+
content: [{ type: "text", text: result.content }],
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
48
|
+
return {
|
|
49
|
+
content: [{ type: "text", text: `Error: ${message}` }],
|
|
50
|
+
isError: true,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
async function main() {
|
|
55
|
+
const transport = new StdioServerTransport();
|
|
56
|
+
await server.connect(transport);
|
|
57
|
+
}
|
|
58
|
+
main().catch((error) => {
|
|
59
|
+
console.error("Server error:", error);
|
|
60
|
+
process.exit(1);
|
|
61
|
+
});
|
|
62
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EACL,gBAAgB,EAChB,cAAc,EACd,cAAc,GACf,MAAM,kBAAkB,CAAC;AAE1B,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;IAC3B,IAAI,EAAE,sBAAsB;IAC5B,OAAO,EAAE,OAAO;IAChB,WAAW,EAAE,gEAAgE;CAC9E,CAAC,CAAC;AAEH,IAAI,aAA4B,CAAC;AAEjC,IAAI,CAAC;IACH,aAAa,GAAG,IAAI,aAAa,EAAE,CAAC;AACtC,CAAC;AAAC,OAAO,KAAK,EAAE,CAAC;IACf,OAAO,CAAC,KAAK,CAAC,sCAAsC,EAAE,KAAK,CAAC,CAAC;IAC7D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,MAAM,CAAC,YAAY,CACjB,aAAa,EACb;IACE,WAAW,EAAE,sDAAsD;CACpE,EACD,KAAK,IAAI,EAAE;IACT,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,gBAAgB,CAAC,aAAa,CAAC,CAAC;QACrD,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;SACnE,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC;QACzE,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,OAAO,EAAE,EAAE,CAAC;YACtD,OAAO,EAAE,IAAI;SACd,CAAC;IACJ,CAAC;AACH,CAAC,CACF,CAAC;AAEF,MAAM,CAAC,YAAY,CACjB,WAAW,EACX;IACE,WAAW,EAAE,uDAAuD;IACpE,WAAW,EAAE,cAAc;CAC5B,EACD,KAAK,EAAE,KAAK,EAAE,EAAE;IACd,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC,aAAa,EAAE,KAAK,CAAC,CAAC;QAC1D,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC;SAClD,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC;QACzE,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,OAAO,EAAE,EAAE,CAAC;YACtD,OAAO,EAAE,IAAI;SACd,CAAC;IACJ,CAAC;AACH,CAAC,CACF,CAAC;AAEF,KAAK,UAAU,IAAI;IACjB,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;AAClC,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;IACrB,OAAO,CAAC,KAAK,CAAC,eAAe,EAAE,KAAK,CAAC,CAAC;IACtC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface SkillFile {
|
|
2
|
+
name: string;
|
|
3
|
+
path: string;
|
|
4
|
+
}
|
|
5
|
+
export declare class GitHubServiceError extends Error {
|
|
6
|
+
readonly code: "NOT_FOUND" | "RATE_LIMIT" | "AUTH_ERROR" | "UNKNOWN";
|
|
7
|
+
constructor(message: string, code: "NOT_FOUND" | "RATE_LIMIT" | "AUTH_ERROR" | "UNKNOWN");
|
|
8
|
+
}
|
|
9
|
+
export declare class GitHubService {
|
|
10
|
+
private octokit;
|
|
11
|
+
private owner;
|
|
12
|
+
private repo;
|
|
13
|
+
private skillsPath;
|
|
14
|
+
constructor();
|
|
15
|
+
listSkillFiles(): Promise<SkillFile[]>;
|
|
16
|
+
getSkillContent(skillName: string): Promise<string>;
|
|
17
|
+
private handleError;
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=github.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github.d.ts","sourceRoot":"","sources":["../../src/services/github.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,qBAAa,kBAAmB,SAAQ,KAAK;aAGzB,IAAI,EAAE,WAAW,GAAG,YAAY,GAAG,YAAY,GAAG,SAAS;gBAD3E,OAAO,EAAE,MAAM,EACC,IAAI,EAAE,WAAW,GAAG,YAAY,GAAG,YAAY,GAAG,SAAS;CAK9E;AAED,qBAAa,aAAa;IACxB,OAAO,CAAC,OAAO,CAAU;IACzB,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,UAAU,CAAS;;IAiBrB,cAAc,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;IA0BtC,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAmCzD,OAAO,CAAC,WAAW;CA0CpB"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { Octokit } from "octokit";
|
|
2
|
+
export class GitHubServiceError extends Error {
|
|
3
|
+
code;
|
|
4
|
+
constructor(message, code) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.code = code;
|
|
7
|
+
this.name = "GitHubServiceError";
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
export class GitHubService {
|
|
11
|
+
octokit;
|
|
12
|
+
owner;
|
|
13
|
+
repo;
|
|
14
|
+
skillsPath;
|
|
15
|
+
constructor() {
|
|
16
|
+
const token = process.env.GITHUB_TOKEN;
|
|
17
|
+
this.owner = process.env.SKILLS_REPO_OWNER || "";
|
|
18
|
+
this.repo = process.env.SKILLS_REPO_NAME || "";
|
|
19
|
+
this.skillsPath = process.env.SKILLS_PATH || "";
|
|
20
|
+
if (!this.owner || !this.repo) {
|
|
21
|
+
throw new Error("SKILLS_REPO_OWNER and SKILLS_REPO_NAME environment variables are required");
|
|
22
|
+
}
|
|
23
|
+
this.octokit = new Octokit({ auth: token });
|
|
24
|
+
}
|
|
25
|
+
async listSkillFiles() {
|
|
26
|
+
try {
|
|
27
|
+
const response = await this.octokit.rest.repos.getContent({
|
|
28
|
+
owner: this.owner,
|
|
29
|
+
repo: this.repo,
|
|
30
|
+
path: this.skillsPath,
|
|
31
|
+
});
|
|
32
|
+
if (!Array.isArray(response.data)) {
|
|
33
|
+
throw new GitHubServiceError("Expected directory but found file", "UNKNOWN");
|
|
34
|
+
}
|
|
35
|
+
return response.data
|
|
36
|
+
.filter((item) => item.type === "file" && item.name.endsWith(".md"))
|
|
37
|
+
.map((item) => ({
|
|
38
|
+
name: item.name.replace(/\.md$/, ""),
|
|
39
|
+
path: item.path,
|
|
40
|
+
}));
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
throw this.handleError(error, "listing skills");
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
async getSkillContent(skillName) {
|
|
47
|
+
const filePath = this.skillsPath
|
|
48
|
+
? `${this.skillsPath}/${skillName}.md`
|
|
49
|
+
: `${skillName}.md`;
|
|
50
|
+
try {
|
|
51
|
+
const response = await this.octokit.rest.repos.getContent({
|
|
52
|
+
owner: this.owner,
|
|
53
|
+
repo: this.repo,
|
|
54
|
+
path: filePath,
|
|
55
|
+
});
|
|
56
|
+
if (Array.isArray(response.data)) {
|
|
57
|
+
throw new GitHubServiceError(`"${skillName}" is a directory, not a skill file`, "NOT_FOUND");
|
|
58
|
+
}
|
|
59
|
+
if (response.data.type !== "file" || !("content" in response.data)) {
|
|
60
|
+
throw new GitHubServiceError(`"${skillName}" is not a valid skill file`, "NOT_FOUND");
|
|
61
|
+
}
|
|
62
|
+
const content = Buffer.from(response.data.content, "base64").toString("utf-8");
|
|
63
|
+
return content;
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
throw this.handleError(error, `fetching skill "${skillName}"`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
handleError(error, context) {
|
|
70
|
+
if (error instanceof GitHubServiceError) {
|
|
71
|
+
return error;
|
|
72
|
+
}
|
|
73
|
+
if (error && typeof error === "object" && "status" in error) {
|
|
74
|
+
const status = error.status;
|
|
75
|
+
switch (status) {
|
|
76
|
+
case 404:
|
|
77
|
+
return new GitHubServiceError(`Resource not found while ${context}`, "NOT_FOUND");
|
|
78
|
+
case 403:
|
|
79
|
+
const message = "message" in error ? String(error.message) : "Rate limit exceeded";
|
|
80
|
+
if (message.toLowerCase().includes("rate limit")) {
|
|
81
|
+
return new GitHubServiceError(`GitHub API rate limit exceeded while ${context}`, "RATE_LIMIT");
|
|
82
|
+
}
|
|
83
|
+
return new GitHubServiceError(`Access forbidden while ${context}. Check GITHUB_TOKEN.`, "AUTH_ERROR");
|
|
84
|
+
case 401:
|
|
85
|
+
return new GitHubServiceError(`Authentication failed while ${context}. Check GITHUB_TOKEN.`, "AUTH_ERROR");
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
89
|
+
return new GitHubServiceError(`Error ${context}: ${errorMessage}`, "UNKNOWN");
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=github.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github.js","sourceRoot":"","sources":["../../src/services/github.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { GitHubService } from "../services/github.js";
|
|
3
|
+
export declare const getSkillSchema: {
|
|
4
|
+
skill_name: z.ZodString;
|
|
5
|
+
};
|
|
6
|
+
export interface GetSkillInput {
|
|
7
|
+
skill_name: string;
|
|
8
|
+
}
|
|
9
|
+
export interface GetSkillResult {
|
|
10
|
+
skill_name: string;
|
|
11
|
+
content: string;
|
|
12
|
+
}
|
|
13
|
+
export declare function handleGetSkill(githubService: GitHubService, input: GetSkillInput): Promise<GetSkillResult>;
|
|
14
|
+
//# sourceMappingURL=get-skill.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get-skill.d.ts","sourceRoot":"","sources":["../../src/tools/get-skill.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,aAAa,EAAsB,MAAM,uBAAuB,CAAC;AAE1E,eAAO,MAAM,cAAc;;CAE1B,CAAC;AAEF,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,wBAAsB,cAAc,CAClC,aAAa,EAAE,aAAa,EAC5B,KAAK,EAAE,aAAa,GACnB,OAAO,CAAC,cAAc,CAAC,CA2BzB"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { GitHubServiceError } from "../services/github.js";
|
|
3
|
+
export const getSkillSchema = {
|
|
4
|
+
skill_name: z.string().describe("The name of the skill to retrieve"),
|
|
5
|
+
};
|
|
6
|
+
export async function handleGetSkill(githubService, input) {
|
|
7
|
+
const { skill_name } = input;
|
|
8
|
+
if (!skill_name || typeof skill_name !== "string") {
|
|
9
|
+
throw new Error("skill_name is required and must be a string");
|
|
10
|
+
}
|
|
11
|
+
const trimmedName = skill_name.trim();
|
|
12
|
+
if (trimmedName.length === 0) {
|
|
13
|
+
throw new Error("skill_name cannot be empty");
|
|
14
|
+
}
|
|
15
|
+
try {
|
|
16
|
+
const content = await githubService.getSkillContent(trimmedName);
|
|
17
|
+
return {
|
|
18
|
+
skill_name: trimmedName,
|
|
19
|
+
content,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
if (error instanceof GitHubServiceError) {
|
|
24
|
+
if (error.code === "NOT_FOUND") {
|
|
25
|
+
throw new Error(`Skill "${trimmedName}" not found`);
|
|
26
|
+
}
|
|
27
|
+
throw new Error(`Failed to get skill: ${error.message}`);
|
|
28
|
+
}
|
|
29
|
+
throw error;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=get-skill.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get-skill.js","sourceRoot":"","sources":["../../src/tools/get-skill.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAiB,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAE1E,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,mCAAmC,CAAC;CACrE,CAAC;AAWF,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,aAA4B,EAC5B,KAAoB;IAEpB,MAAM,EAAE,UAAU,EAAE,GAAG,KAAK,CAAC;IAE7B,IAAI,CAAC,UAAU,IAAI,OAAO,UAAU,KAAK,QAAQ,EAAE,CAAC;QAClD,MAAM,IAAI,KAAK,CAAC,6CAA6C,CAAC,CAAC;IACjE,CAAC;IAED,MAAM,WAAW,GAAG,UAAU,CAAC,IAAI,EAAE,CAAC;IACtC,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC7B,MAAM,IAAI,KAAK,CAAC,4BAA4B,CAAC,CAAC;IAChD,CAAC;IAED,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,aAAa,CAAC,eAAe,CAAC,WAAW,CAAC,CAAC;QACjE,OAAO;YACL,UAAU,EAAE,WAAW;YACvB,OAAO;SACR,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,kBAAkB,EAAE,CAAC;YACxC,IAAI,KAAK,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;gBAC/B,MAAM,IAAI,KAAK,CAAC,UAAU,WAAW,aAAa,CAAC,CAAC;YACtD,CAAC;YACD,MAAM,IAAI,KAAK,CAAC,wBAAwB,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;QAC3D,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,gBAAgB,EAChB,KAAK,gBAAgB,GACtB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACL,cAAc,EACd,cAAc,EACd,KAAK,aAAa,EAClB,KAAK,cAAc,GACpB,MAAM,gBAAgB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,gBAAgB,GAEjB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACL,cAAc,EACd,cAAc,GAGf,MAAM,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { GitHubService } from "../services/github.js";
|
|
2
|
+
export declare const listSkillsSchema: {};
|
|
3
|
+
export interface ListSkillsResult {
|
|
4
|
+
skills: string[];
|
|
5
|
+
}
|
|
6
|
+
export declare function handleListSkills(githubService: GitHubService): Promise<ListSkillsResult>;
|
|
7
|
+
//# sourceMappingURL=list-skills.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list-skills.d.ts","sourceRoot":"","sources":["../../src/tools/list-skills.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAsB,MAAM,uBAAuB,CAAC;AAE1E,eAAO,MAAM,gBAAgB,IAAK,CAAC;AAEnC,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,wBAAsB,gBAAgB,CACpC,aAAa,EAAE,aAAa,GAC3B,OAAO,CAAC,gBAAgB,CAAC,CAW3B"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { GitHubServiceError } from "../services/github.js";
|
|
2
|
+
export const listSkillsSchema = {};
|
|
3
|
+
export async function handleListSkills(githubService) {
|
|
4
|
+
try {
|
|
5
|
+
const files = await githubService.listSkillFiles();
|
|
6
|
+
const skills = files.map((file) => file.name);
|
|
7
|
+
return { skills };
|
|
8
|
+
}
|
|
9
|
+
catch (error) {
|
|
10
|
+
if (error instanceof GitHubServiceError) {
|
|
11
|
+
throw new Error(`Failed to list skills: ${error.message}`);
|
|
12
|
+
}
|
|
13
|
+
throw error;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=list-skills.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list-skills.js","sourceRoot":"","sources":["../../src/tools/list-skills.ts"],"names":[],"mappings":"AACA,OAAO,EAAiB,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAE1E,MAAM,CAAC,MAAM,gBAAgB,GAAG,EAAE,CAAC;AAMnC,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,aAA4B;IAE5B,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,MAAM,aAAa,CAAC,cAAc,EAAE,CAAC;QACnD,MAAM,MAAM,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC9C,OAAO,EAAE,MAAM,EAAE,CAAC;IACpB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,kBAAkB,EAAE,CAAC;YACxC,MAAM,IAAI,KAAK,CAAC,0BAA0B,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;QAC7D,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "my-personal-code-mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MCP server for AI best practices skills",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"my-personal-code-mcp": "dist/index.js"
|
|
10
|
+
},
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "tsc",
|
|
13
|
+
"dev": "tsc --watch",
|
|
14
|
+
"start": "node dist/index.js",
|
|
15
|
+
"prepublishOnly": "npm run build"
|
|
16
|
+
},
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/lfdantoni/my-personal-code-mcp.git"
|
|
20
|
+
},
|
|
21
|
+
"keywords": [
|
|
22
|
+
"mcp",
|
|
23
|
+
"model-context-protocol",
|
|
24
|
+
"ai",
|
|
25
|
+
"skills"
|
|
26
|
+
],
|
|
27
|
+
"author": "",
|
|
28
|
+
"license": "ISC",
|
|
29
|
+
"bugs": {
|
|
30
|
+
"url": "https://github.com/lfdantoni/my-personal-code-mcp/issues"
|
|
31
|
+
},
|
|
32
|
+
"homepage": "https://github.com/lfdantoni/my-personal-code-mcp#readme",
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@modelcontextprotocol/sdk": "^1.25.2",
|
|
35
|
+
"octokit": "^5.0.5",
|
|
36
|
+
"zod": "^4.3.5"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@types/node": "^25.0.6",
|
|
40
|
+
"typescript": "^5.9.3"
|
|
41
|
+
}
|
|
42
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
|
+
import { GitHubService } from "./services/github.js";
|
|
6
|
+
import {
|
|
7
|
+
handleListSkills,
|
|
8
|
+
getSkillSchema,
|
|
9
|
+
handleGetSkill,
|
|
10
|
+
} from "./tools/index.js";
|
|
11
|
+
|
|
12
|
+
const server = new McpServer({
|
|
13
|
+
name: "my-personal-code-mcp",
|
|
14
|
+
version: "1.0.0",
|
|
15
|
+
description: "MCP server for AI best practices skills from GitHub repository",
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
let githubService: GitHubService;
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
githubService = new GitHubService();
|
|
22
|
+
} catch (error) {
|
|
23
|
+
console.error("Failed to initialize GitHub service:", error);
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
server.registerTool(
|
|
28
|
+
"list_skills",
|
|
29
|
+
{
|
|
30
|
+
description: "Returns a list of all available best practice skills",
|
|
31
|
+
},
|
|
32
|
+
async () => {
|
|
33
|
+
try {
|
|
34
|
+
const result = await handleListSkills(githubService);
|
|
35
|
+
return {
|
|
36
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
37
|
+
};
|
|
38
|
+
} catch (error) {
|
|
39
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
40
|
+
return {
|
|
41
|
+
content: [{ type: "text", text: `Error: ${message}` }],
|
|
42
|
+
isError: true,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
server.registerTool(
|
|
49
|
+
"get_skill",
|
|
50
|
+
{
|
|
51
|
+
description: "Returns the content of a specific best practice skill",
|
|
52
|
+
inputSchema: getSkillSchema,
|
|
53
|
+
},
|
|
54
|
+
async (input) => {
|
|
55
|
+
try {
|
|
56
|
+
const result = await handleGetSkill(githubService, input);
|
|
57
|
+
return {
|
|
58
|
+
content: [{ type: "text", text: result.content }],
|
|
59
|
+
};
|
|
60
|
+
} catch (error) {
|
|
61
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
62
|
+
return {
|
|
63
|
+
content: [{ type: "text", text: `Error: ${message}` }],
|
|
64
|
+
isError: true,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
async function main() {
|
|
71
|
+
const transport = new StdioServerTransport();
|
|
72
|
+
await server.connect(transport);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
main().catch((error) => {
|
|
76
|
+
console.error("Server error:", error);
|
|
77
|
+
process.exit(1);
|
|
78
|
+
});
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { Octokit } from "octokit";
|
|
2
|
+
|
|
3
|
+
export interface SkillFile {
|
|
4
|
+
name: string;
|
|
5
|
+
path: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export class GitHubServiceError extends Error {
|
|
9
|
+
constructor(
|
|
10
|
+
message: string,
|
|
11
|
+
public readonly code: "NOT_FOUND" | "RATE_LIMIT" | "AUTH_ERROR" | "UNKNOWN"
|
|
12
|
+
) {
|
|
13
|
+
super(message);
|
|
14
|
+
this.name = "GitHubServiceError";
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export class GitHubService {
|
|
19
|
+
private octokit: Octokit;
|
|
20
|
+
private owner: string;
|
|
21
|
+
private repo: string;
|
|
22
|
+
private skillsPath: string;
|
|
23
|
+
|
|
24
|
+
constructor() {
|
|
25
|
+
const token = process.env.GITHUB_TOKEN;
|
|
26
|
+
this.owner = process.env.SKILLS_REPO_OWNER || "";
|
|
27
|
+
this.repo = process.env.SKILLS_REPO_NAME || "";
|
|
28
|
+
this.skillsPath = process.env.SKILLS_PATH || "";
|
|
29
|
+
|
|
30
|
+
if (!this.owner || !this.repo) {
|
|
31
|
+
throw new Error(
|
|
32
|
+
"SKILLS_REPO_OWNER and SKILLS_REPO_NAME environment variables are required"
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
this.octokit = new Octokit({ auth: token });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async listSkillFiles(): Promise<SkillFile[]> {
|
|
40
|
+
try {
|
|
41
|
+
const response = await this.octokit.rest.repos.getContent({
|
|
42
|
+
owner: this.owner,
|
|
43
|
+
repo: this.repo,
|
|
44
|
+
path: this.skillsPath,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
if (!Array.isArray(response.data)) {
|
|
48
|
+
throw new GitHubServiceError(
|
|
49
|
+
"Expected directory but found file",
|
|
50
|
+
"UNKNOWN"
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return response.data
|
|
55
|
+
.filter((item) => item.type === "file" && item.name.endsWith(".md"))
|
|
56
|
+
.map((item) => ({
|
|
57
|
+
name: item.name.replace(/\.md$/, ""),
|
|
58
|
+
path: item.path,
|
|
59
|
+
}));
|
|
60
|
+
} catch (error) {
|
|
61
|
+
throw this.handleError(error, "listing skills");
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async getSkillContent(skillName: string): Promise<string> {
|
|
66
|
+
const filePath = this.skillsPath
|
|
67
|
+
? `${this.skillsPath}/${skillName}.md`
|
|
68
|
+
: `${skillName}.md`;
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
const response = await this.octokit.rest.repos.getContent({
|
|
72
|
+
owner: this.owner,
|
|
73
|
+
repo: this.repo,
|
|
74
|
+
path: filePath,
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
if (Array.isArray(response.data)) {
|
|
78
|
+
throw new GitHubServiceError(
|
|
79
|
+
`"${skillName}" is a directory, not a skill file`,
|
|
80
|
+
"NOT_FOUND"
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (response.data.type !== "file" || !("content" in response.data)) {
|
|
85
|
+
throw new GitHubServiceError(
|
|
86
|
+
`"${skillName}" is not a valid skill file`,
|
|
87
|
+
"NOT_FOUND"
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const content = Buffer.from(response.data.content, "base64").toString(
|
|
92
|
+
"utf-8"
|
|
93
|
+
);
|
|
94
|
+
return content;
|
|
95
|
+
} catch (error) {
|
|
96
|
+
throw this.handleError(error, `fetching skill "${skillName}"`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
private handleError(error: unknown, context: string): GitHubServiceError {
|
|
101
|
+
if (error instanceof GitHubServiceError) {
|
|
102
|
+
return error;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (error && typeof error === "object" && "status" in error) {
|
|
106
|
+
const status = (error as { status: number }).status;
|
|
107
|
+
|
|
108
|
+
switch (status) {
|
|
109
|
+
case 404:
|
|
110
|
+
return new GitHubServiceError(
|
|
111
|
+
`Resource not found while ${context}`,
|
|
112
|
+
"NOT_FOUND"
|
|
113
|
+
);
|
|
114
|
+
case 403:
|
|
115
|
+
const message =
|
|
116
|
+
"message" in error ? String(error.message) : "Rate limit exceeded";
|
|
117
|
+
if (message.toLowerCase().includes("rate limit")) {
|
|
118
|
+
return new GitHubServiceError(
|
|
119
|
+
`GitHub API rate limit exceeded while ${context}`,
|
|
120
|
+
"RATE_LIMIT"
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
return new GitHubServiceError(
|
|
124
|
+
`Access forbidden while ${context}. Check GITHUB_TOKEN.`,
|
|
125
|
+
"AUTH_ERROR"
|
|
126
|
+
);
|
|
127
|
+
case 401:
|
|
128
|
+
return new GitHubServiceError(
|
|
129
|
+
`Authentication failed while ${context}. Check GITHUB_TOKEN.`,
|
|
130
|
+
"AUTH_ERROR"
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const errorMessage =
|
|
136
|
+
error instanceof Error ? error.message : "Unknown error";
|
|
137
|
+
return new GitHubServiceError(
|
|
138
|
+
`Error ${context}: ${errorMessage}`,
|
|
139
|
+
"UNKNOWN"
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { GitHubService, GitHubServiceError } from "../services/github.js";
|
|
3
|
+
|
|
4
|
+
export const getSkillSchema = {
|
|
5
|
+
skill_name: z.string().describe("The name of the skill to retrieve"),
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export interface GetSkillInput {
|
|
9
|
+
skill_name: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface GetSkillResult {
|
|
13
|
+
skill_name: string;
|
|
14
|
+
content: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function handleGetSkill(
|
|
18
|
+
githubService: GitHubService,
|
|
19
|
+
input: GetSkillInput
|
|
20
|
+
): Promise<GetSkillResult> {
|
|
21
|
+
const { skill_name } = input;
|
|
22
|
+
|
|
23
|
+
if (!skill_name || typeof skill_name !== "string") {
|
|
24
|
+
throw new Error("skill_name is required and must be a string");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const trimmedName = skill_name.trim();
|
|
28
|
+
if (trimmedName.length === 0) {
|
|
29
|
+
throw new Error("skill_name cannot be empty");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
const content = await githubService.getSkillContent(trimmedName);
|
|
34
|
+
return {
|
|
35
|
+
skill_name: trimmedName,
|
|
36
|
+
content,
|
|
37
|
+
};
|
|
38
|
+
} catch (error) {
|
|
39
|
+
if (error instanceof GitHubServiceError) {
|
|
40
|
+
if (error.code === "NOT_FOUND") {
|
|
41
|
+
throw new Error(`Skill "${trimmedName}" not found`);
|
|
42
|
+
}
|
|
43
|
+
throw new Error(`Failed to get skill: ${error.message}`);
|
|
44
|
+
}
|
|
45
|
+
throw error;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { GitHubService, GitHubServiceError } from "../services/github.js";
|
|
3
|
+
|
|
4
|
+
export const listSkillsSchema = {};
|
|
5
|
+
|
|
6
|
+
export interface ListSkillsResult {
|
|
7
|
+
skills: string[];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export async function handleListSkills(
|
|
11
|
+
githubService: GitHubService
|
|
12
|
+
): Promise<ListSkillsResult> {
|
|
13
|
+
try {
|
|
14
|
+
const files = await githubService.listSkillFiles();
|
|
15
|
+
const skills = files.map((file) => file.name);
|
|
16
|
+
return { skills };
|
|
17
|
+
} catch (error) {
|
|
18
|
+
if (error instanceof GitHubServiceError) {
|
|
19
|
+
throw new Error(`Failed to list skills: ${error.message}`);
|
|
20
|
+
}
|
|
21
|
+
throw error;
|
|
22
|
+
}
|
|
23
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"outDir": "./dist",
|
|
7
|
+
"rootDir": "./src",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true,
|
|
12
|
+
"declaration": true,
|
|
13
|
+
"declarationMap": true,
|
|
14
|
+
"sourceMap": true,
|
|
15
|
+
"resolveJsonModule": true
|
|
16
|
+
},
|
|
17
|
+
"include": ["src/**/*"],
|
|
18
|
+
"exclude": ["node_modules", "dist"]
|
|
19
|
+
}
|