Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions tools/FEx.McpServer/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using FEx.McpServer.Services;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using ModelContextProtocol.Server;

var repoPath = Environment.GetEnvironmentVariable("FEX_REPO_PATH")
Expand All @@ -19,6 +20,10 @@

var builder = Host.CreateApplicationBuilder(args);

// Redirect all Console logging to stderr so stdout stays clean for JSON-RPC (stdio transport).
// Without this, info-level log lines corrupt the newline-delimited JSON stream.
builder.Logging.AddConsole(o => o.LogToStandardErrorThreshold = LogLevel.Trace);

builder.Services.AddSingleton(new ApiSurfaceConfig(apiSurfacePath, repoPath));
builder.Services.AddSingleton<TomlApiSurfaceReader>();
builder.Services.AddMcpServer()
Expand Down
79 changes: 62 additions & 17 deletions tools/FEx.McpServer/Tools/FExApiTools.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using FEx.McpServer.Services;
Expand Down Expand Up @@ -131,29 +131,74 @@ public static string CheckFreshness(
var tomlCommit = reader.GitCommit;
var generated = reader.Generated;

var headCommit = TryReadGitHeadShort(config.RepoPath);
if (string.IsNullOrEmpty(headCommit))
return $"Cannot check git HEAD: failed to read .git/HEAD. TOML commit: {tomlCommit}, generated: {generated}";

var isFresh = string.Equals(tomlCommit, headCommit, StringComparison.OrdinalIgnoreCase);

return isFresh
? $"API surface is UP TO DATE. Commit: {tomlCommit}, generated: {generated}"
: $"API surface is OUTDATED. TOML: {tomlCommit}, HEAD: {headCommit}. Run Generate-ApiSurface.ps1 to update.";
}

// Reads the short (7-char) SHA for HEAD directly from .git files, avoiding a git subprocess
// that can hang when the process has inherited pipe handles (e.g. under an MCP stdio host).
private static string TryReadGitHeadShort(string repoPath)
{
try
{
var psi = new ProcessStartInfo("git", "rev-parse --short HEAD")
{
WorkingDirectory = config.RepoPath,
RedirectStandardOutput = true,
UseShellExecute = false,
CreateNoWindow = true
};
var gitDir = Path.Combine(repoPath, ".git");
var headContent = File.ReadAllText(Path.Combine(gitDir, "HEAD")).Trim();

using var proc = Process.Start(psi);
var headCommit = proc?.StandardOutput.ReadToEnd().Trim() ?? "unknown";
proc?.WaitForExit();
string fullSha;

if (headContent.StartsWith("ref: ", StringComparison.Ordinal))
{
var refPath = headContent[5..]; // e.g. "refs/heads/develop"
var refFile = Path.Combine(gitDir, refPath.Replace('/', Path.DirectorySeparatorChar));

var isFresh = string.Equals(tomlCommit, headCommit, StringComparison.OrdinalIgnoreCase);
if (File.Exists(refFile))
{
fullSha = File.ReadAllText(refFile).Trim();
}
else
{
// Ref has been packed — search packed-refs
var packedRefs = Path.Combine(gitDir, "packed-refs");
if (!File.Exists(packedRefs))
return "";

string foundSha = "";
foreach (var line in File.ReadLines(packedRefs))
{
if (line.Length == 0 || line[0] == '#' || line[0] == '^')
continue;
var space = line.IndexOf(' ');
if (space > 0 && line[(space + 1)..] == refPath)
{
foundSha = line[..space];
break;
}
}

if (string.IsNullOrEmpty(foundSha))
return "";

fullSha = foundSha;
}
}
else
{
// Detached HEAD — content is already the full SHA
fullSha = headContent;
}

return isFresh
? $"API surface is UP TO DATE. Commit: {tomlCommit}, generated: {generated}"
: $"API surface is OUTDATED. TOML: {tomlCommit}, HEAD: {headCommit}. Run Generate-ApiSurface.ps1 to update.";
return fullSha.Length >= 7 ? fullSha[..7] : fullSha;
}
catch (Exception ex)
catch
{
return $"Cannot check git HEAD: {ex.Message}. TOML commit: {tomlCommit}, generated: {generated}";
return "";
}
}
}
Loading