diff --git a/mcp/comment.ts b/mcp/comment.ts new file mode 100644 index 0000000..a83787c --- /dev/null +++ b/mcp/comment.ts @@ -0,0 +1,66 @@ +import { Octokit } from "@octokit/rest"; +import { type } from "arktype"; +import { resolveRepoContext } from "../utils/repo-context.ts"; +import { tool } from "./shared.ts"; + +export const Comment = type({ + issueNumber: type.number.describe("the issue number to comment on"), + body: type.string.describe("the comment body content"), +}); + +export const CommentTool = tool({ + name: "create_issue_comment", + description: "Create a comment on a GitHub issue", + parameters: Comment, + execute: async ({ issueNumber, body }: { issueNumber: number; body: string }) => { + try { + const githubInstallationToken = process.env.GITHUB_INSTALLATION_TOKEN; + if (!githubInstallationToken) { + throw new Error("GITHUB_INSTALLATION_TOKEN environment variable is required"); + } + + const repoContext = resolveRepoContext(); + + const octokit = new Octokit({ + auth: githubInstallationToken, + }); + + const result = await octokit.rest.issues.createComment({ + owner: repoContext.owner, + repo: repoContext.name, + issue_number: issueNumber, + body: body, + }); + + return { + content: [ + { + type: "text", + text: JSON.stringify( + { + success: true, + commentId: result.data.id, + url: result.data.html_url, + body: result.data.body, + }, + null, + 2 + ), + }, + ], + }; + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + return { + content: [ + { + type: "text", + text: `Error creating comment: ${errorMessage}`, + }, + ], + error: errorMessage, + isError: true, + }; + } + }, +}); diff --git a/mcp/config.ts b/mcp/config.ts index 8e1e192..9ecfdd9 100644 --- a/mcp/config.ts +++ b/mcp/config.ts @@ -1,8 +1,6 @@ /** * Simple MCP configuration helper for adding our minimal GitHub comment server */ -// const actionPath = process.env.GITHUB_ACTION_PATH || process.cwd(); - import { fromHere } from "@ark/fs"; const actionPath = fromHere(".."); diff --git a/mcp/server.ts b/mcp/server.ts index b1f5f3a..360f61c 100644 --- a/mcp/server.ts +++ b/mcp/server.ts @@ -1,92 +1,14 @@ #!/usr/bin/env node // Minimal GitHub Issue Comment MCP Server -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; -import { Octokit } from "@octokit/rest"; -import { type } from "arktype"; -import { z } from "zod"; -import { resolveRepoContext } from "../utils/repo-context.ts"; +import { FastMCP } from "fastmcp"; +import { CommentTool } from "./comment.ts"; +import { addTools } from "./shared.ts"; -const server = new McpServer({ +const server = new FastMCP({ name: "Minimal GitHub Issue Comment Server", version: "0.0.1", }); -// Define the schema for creating issue comments -const Comment = type({ - issueNumber: type.number.describe("the issue number to comment on"), - body: type.string.describe("the comment body content"), -}); +addTools(server, [CommentTool]); -server.tool( - "create_issue_comment", - "Create a comment on a GitHub issue", - { - issueNumber: z.number().describe("the issue number to comment on"), - body: z.string().describe("the comment body content"), - }, - async ({ issueNumber, body }) => { - try { - Comment.assert({ issueNumber, body }); - - const githubInstallationToken = process.env.GITHUB_INSTALLATION_TOKEN; - if (!githubInstallationToken) { - throw new Error("GITHUB_INSTALLATION_TOKEN environment variable is required"); - } - - // Resolve repository context from environment - const repoContext = resolveRepoContext(); - - const octokit = new Octokit({ - auth: githubInstallationToken, - }); - - const result = await octokit.rest.issues.createComment({ - owner: repoContext.owner, - repo: repoContext.name, - issue_number: issueNumber, - body: body, - }); - - return { - content: [ - { - type: "text", - text: JSON.stringify( - { - success: true, - commentId: result.data.id, - url: result.data.html_url, - body: result.data.body, - }, - null, - 2 - ), - }, - ], - }; - } catch (error) { - const errorMessage = error instanceof Error ? error.message : String(error); - return { - content: [ - { - type: "text", - text: `Error creating comment: ${errorMessage}`, - }, - ], - error: errorMessage, - isError: true, - }; - } - } -); - -async function runServer() { - const transport = new StdioServerTransport(); - await server.connect(transport); - process.on("exit", () => { - server.close(); - }); -} - -await runServer(); +server.start(); diff --git a/mcp/shared.ts b/mcp/shared.ts new file mode 100644 index 0000000..59cfee9 --- /dev/null +++ b/mcp/shared.ts @@ -0,0 +1,11 @@ +import type { StandardSchemaV1 } from "@standard-schema/spec"; +import type { FastMCP, Tool } from "fastmcp"; + +export const tool = (tool: Tool<{}, StandardSchemaV1>) => tool; + +export const addTools = (server: FastMCP, tools: Tool[]) => { + for (const tool of tools) { + server.addTool(tool); + } + return server; +}; diff --git a/package.json b/package.json index 9cdf6a4..dea2062 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@pullfrog/action", - "version": "0.0.47", + "version": "0.0.48", "type": "module", "files": [ "index.js", @@ -22,12 +22,13 @@ "dependencies": { "@actions/core": "^1.11.1", "@ark/fs": "0.49.0", - "@modelcontextprotocol/sdk": "^1.20.0", "@octokit/rest": "^22.0.0", "@octokit/webhooks-types": "^7.6.1", - "arktype": "^2.1.22", + "@standard-schema/spec": "1.0.0", + "arktype": "^2.1.23", "dotenv": "^17.2.3", "execa": "^9.6.0", + "fastmcp": "^3.20.0", "table": "^6.9.0", "zod": "^3.24.4" },