tool call logging, centralized temp dir
This commit is contained in:
+4
-21
@@ -2,9 +2,9 @@
|
||||
* Simple MCP configuration helper for adding our minimal GitHub comment server
|
||||
*/
|
||||
|
||||
import type { McpServerConfig, McpStdioServerConfig } from "@anthropic-ai/claude-agent-sdk";
|
||||
import type { McpStdioServerConfig } from "@anthropic-ai/claude-agent-sdk";
|
||||
import { fromHere } from "@ark/fs";
|
||||
import { log } from "../utils/cli.ts";
|
||||
import type { Mode } from "../modes.ts";
|
||||
import { parseRepoContext } from "../utils/github.ts";
|
||||
import { ghPullfrogMcpName } from "./index.ts";
|
||||
|
||||
@@ -12,7 +12,7 @@ export type McpName = typeof ghPullfrogMcpName;
|
||||
|
||||
export type McpConfigs = Record<McpName, McpStdioServerConfig>;
|
||||
|
||||
export function createMcpConfigs(githubInstallationToken: string): McpConfigs {
|
||||
export function createMcpConfigs(githubInstallationToken: string, modes: Mode[]): McpConfigs {
|
||||
const repoContext = parseRepoContext();
|
||||
const githubRepository = `${repoContext.owner}/${repoContext.name}`;
|
||||
|
||||
@@ -27,25 +27,8 @@ export function createMcpConfigs(githubInstallationToken: string): McpConfigs {
|
||||
env: {
|
||||
GITHUB_INSTALLATION_TOKEN: githubInstallationToken,
|
||||
GITHUB_REPOSITORY: githubRepository,
|
||||
PULLFROG_MODES: JSON.stringify(modes),
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterate through MCP servers and call the provided handler for each stdio server
|
||||
* Shared logic to avoid duplication across agents
|
||||
*/
|
||||
export function forEachStdioMcpServer(
|
||||
mcpServers: Record<string, McpServerConfig>,
|
||||
handler: (serverName: string, serverConfig: McpStdioServerConfig) => void
|
||||
): void {
|
||||
for (const [serverName, serverConfig] of Object.entries(mcpServers)) {
|
||||
// Only configure stdio servers (CLIs support stdio MCP servers)
|
||||
if (!("command" in serverConfig)) {
|
||||
log.warning(`MCP server '${serverName}' is not a stdio server, skipping...`);
|
||||
continue;
|
||||
}
|
||||
handler(serverName, serverConfig);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import { type } from "arktype";
|
||||
import type { Mode } from "../modes.ts";
|
||||
import { contextualize, tool } from "./shared.ts";
|
||||
|
||||
// Get modes from environment variable (set by createMcpConfigs)
|
||||
function getModes(): Mode[] {
|
||||
const modesJson = process.env.PULLFROG_MODES;
|
||||
if (modesJson) {
|
||||
try {
|
||||
return JSON.parse(modesJson);
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
export const SelectMode = type({
|
||||
modeName: type.string.describe(
|
||||
"the name of the mode to select (e.g., 'Plan', 'Build', 'Review', 'Prompt')"
|
||||
),
|
||||
});
|
||||
|
||||
export const SelectModeTool = tool({
|
||||
name: "select_mode",
|
||||
description:
|
||||
"Select a mode and get its detailed prompt instructions. Call this first to determine which mode to use based on the request.",
|
||||
parameters: SelectMode,
|
||||
execute: contextualize(async ({ modeName }) => {
|
||||
const allModes = getModes();
|
||||
|
||||
if (allModes.length === 0) {
|
||||
return {
|
||||
error:
|
||||
"No modes available. Modes must be provided via PULLFROG_MODES environment variable.",
|
||||
};
|
||||
}
|
||||
|
||||
const selectedMode = allModes.find((m) => m.name.toLowerCase() === modeName.toLowerCase());
|
||||
|
||||
if (!selectedMode) {
|
||||
const availableModes = allModes.map((m) => m.name).join(", ");
|
||||
return {
|
||||
error: `Mode "${modeName}" not found. Available modes: ${availableModes}`,
|
||||
availableModes: allModes.map((m) => ({ name: m.name, description: m.description })),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
modeName: selectedMode.name,
|
||||
description: selectedMode.description,
|
||||
prompt: selectedMode.prompt,
|
||||
};
|
||||
}),
|
||||
});
|
||||
+2
-2
@@ -1,5 +1,3 @@
|
||||
#!/usr/bin/env node
|
||||
// Minimal GitHub Issue Comment MCP Server
|
||||
import { FastMCP } from "fastmcp";
|
||||
import {
|
||||
CreateCommentTool,
|
||||
@@ -11,6 +9,7 @@ import { IssueTool } from "./issue.ts";
|
||||
import { PullRequestTool } from "./pr.ts";
|
||||
import { PullRequestInfoTool } from "./prInfo.ts";
|
||||
import { ReviewTool } from "./review.ts";
|
||||
import { SelectModeTool } from "./selectMode.ts";
|
||||
import { addTools } from "./shared.ts";
|
||||
|
||||
const server = new FastMCP({
|
||||
@@ -19,6 +18,7 @@ const server = new FastMCP({
|
||||
});
|
||||
|
||||
addTools(server, [
|
||||
SelectModeTool,
|
||||
CreateCommentTool,
|
||||
EditCommentTool,
|
||||
CreateWorkingCommentTool,
|
||||
|
||||
+15
-48
@@ -1,9 +1,8 @@
|
||||
import { appendFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { cached } from "@ark/util";
|
||||
import { Octokit } from "@octokit/rest";
|
||||
import type { StandardSchemaV1 } from "@standard-schema/spec";
|
||||
import type { FastMCP, Tool } from "fastmcp";
|
||||
import { log } from "../utils/cli.ts";
|
||||
import { parseRepoContext, type RepoContext } from "../utils/github.ts";
|
||||
|
||||
export interface ToolResult {
|
||||
@@ -32,49 +31,6 @@ export interface McpContext extends RepoContext {
|
||||
octokit: Octokit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the log file path
|
||||
*/
|
||||
function getLogPath(): string {
|
||||
return join(process.cwd(), "log.txt");
|
||||
}
|
||||
|
||||
/**
|
||||
* Log MCP tool call information to log.txt
|
||||
*/
|
||||
function logToolCall({
|
||||
toolName,
|
||||
request,
|
||||
error,
|
||||
success,
|
||||
}: {
|
||||
toolName: string;
|
||||
request: unknown;
|
||||
error?: unknown;
|
||||
success?: boolean;
|
||||
}) {
|
||||
const logPath = getLogPath();
|
||||
const timestamp = new Date().toISOString();
|
||||
const requestStr = JSON.stringify(request, null, 2);
|
||||
|
||||
let logEntry = `[${timestamp}] Tool: ${toolName}\nRequest: ${requestStr}\n`;
|
||||
|
||||
if (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
const errorStack = error instanceof Error ? error.stack : undefined;
|
||||
logEntry += `Error: ${errorMessage}\n`;
|
||||
if (errorStack) {
|
||||
logEntry += `Stack: ${errorStack}\n`;
|
||||
}
|
||||
logEntry += `Status: FAILED\n`;
|
||||
} else if (success !== undefined) {
|
||||
logEntry += `Status: ${success ? "SUCCESS" : "FAILED"}\n`;
|
||||
}
|
||||
|
||||
logEntry += `${"=".repeat(80)}\n\n`;
|
||||
appendFileSync(logPath, logEntry, "utf-8");
|
||||
}
|
||||
|
||||
export const tool = <const params>(toolDef: Tool<any, StandardSchemaV1<params>>) => {
|
||||
// Wrap the execute function to add logging with the tool name
|
||||
const toolName = toolDef.name;
|
||||
@@ -82,15 +38,26 @@ export const tool = <const params>(toolDef: Tool<any, StandardSchemaV1<params>>)
|
||||
|
||||
toolDef.execute = async (args: params, context: any) => {
|
||||
try {
|
||||
logToolCall({ toolName, request: args });
|
||||
const result = await originalExecute(args, context);
|
||||
// Check if result is a ToolResult with isError property
|
||||
const isError =
|
||||
result && typeof result === "object" && "isError" in result && result.isError === true;
|
||||
logToolCall({ toolName, request: args, success: !isError });
|
||||
const resultData =
|
||||
result && typeof result === "object" && "content" in result
|
||||
? (result as ToolResult).content[0]?.text
|
||||
: undefined;
|
||||
|
||||
if (isError && resultData) {
|
||||
log.toolCall({ toolName, request: args, error: resultData });
|
||||
} else if (resultData) {
|
||||
log.toolCall({ toolName, request: args, result: resultData });
|
||||
} else {
|
||||
log.toolCall({ toolName, request: args });
|
||||
}
|
||||
return result;
|
||||
} catch (error) {
|
||||
logToolCall({ toolName, request: args, error });
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
log.toolCall({ toolName, request: args, error: errorMessage });
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user