Forge
csharp3407750f
1using System.Collections.Frozen;
2using System.Net.Http.Json;
3using System.Text.Json;
4using AgentForge.Abstractions;
5using AgentForge.Credentials;
6using AgentForge.Mcp;
7using ModelContextProtocol.Protocol;
8using ModelContextProtocol.Server;
9using Tool = ModelContextProtocol.Protocol.Tool;
10
11var load = ForgeMcpConfigLoader.Load(args);
12if (load.IsHelp)
13{
14 PrintUsage();
15 return 0;
16}
17
18if (!load.IsSuccess || load.Settings is null)
19{
20 Console.Error.WriteLine(load.Error ?? "Failed to load MCP config.");
21 PrintUsage();
22 return 1;
23}
24
25var settings = load.Settings;
26
27var jsonOptions = new JsonSerializerOptions
28{
29 PropertyNameCaseInsensitive = true,
30 PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
31};
32
33using var http = CreateHttpClient(settings);
34using var runCts = new CancellationTokenSource();
35
36var options = new McpServerOptions
37{
38 ServerInfo = new Implementation { Name = "AgentForgeMcp", Version = "0.4.0" },
39 ServerInstructions = ServerInstructions.Text,
40 ProtocolVersion = "2024-11-05",
41 Capabilities = new ServerCapabilities
42 {
43 Tools = new ToolsCapability { ListChanged = settings.ToolsListChanged },
44 },
45 Handlers = new McpServerHandlers
46 {
47 ListToolsHandler = async (_, cancellationToken) =>
48 {
49 var tools = await ForgeMcpToolLoader.LoadFromCapabilitiesAsync(http, jsonOptions, cancellationToken)
50 .ConfigureAwait(false);
51 ForgeMcpCursorToolDescriptorSync.TrySync(tools, settings);
52 return new ListToolsResult { Tools = tools };
53 },
54 CallToolHandler = async (request, cancellationToken) =>
55 {
56 var name = request.Params?.Name ?? "";
57 var args = request.Params?.Arguments is IReadOnlyDictionary<string, JsonElement> dictionary
58 ? dictionary
59 : FrozenDictionary<string, JsonElement>.Empty;
60
61 try
62 {
63 var payload = new ForgeMcpInvokeRequest
64 {
65 Tool = name,
66 Arguments = args.Count == 0 ? null : args.ToDictionary(static p => p.Key, static p => p.Value),
67 };
68
69 using var response = await http.PostAsJsonAsync("/api/v1/mcp/invoke", payload, jsonOptions, cancellationToken)
70 .ConfigureAwait(false);
71 var body = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
72 var invoke = JsonSerializer.Deserialize<ForgeMcpInvokeResponse>(body, jsonOptions)
73 ?? new ForgeMcpInvokeResponse { Success = false, Body = body };
74
75 return new CallToolResult
76 {
77 Content = [new TextContentBlock { Text = invoke.Body }],
78 IsError = !invoke.Success,
79 };
80 }
81 catch (HttpRequestException ex)
82 {
83 return Error($"Forge unreachable at {settings.BaseUri}: {ex.Message}");
84 }
85 catch (Exception ex)
86 {
87 return Error(ex.Message);
88 }
89 },
90 },
91};
92
93static CallToolResult Error(string message) =>
94 new()
95 {
96 Content = [new TextContentBlock { Text = $"Error: {message}" }],
97 IsError = true,
98 };
99
100static HttpClient CreateHttpClient(ForgeMcpSettings settings)
101{
102 var http = new HttpClient { BaseAddress = settings.BaseUri };
103 http.DefaultRequestHeaders.Accept.ParseAdd("application/json");
104
105 var apiToken = settings.ApiToken
106 ?? new ForgeCredentialStore().TryGetToken(ForgeHostUrl.NormalizeKey(settings.BaseUri));
107 if (string.IsNullOrEmpty(apiToken))
108 Console.Error.WriteLine(
109 $"Forge MCP: no API token for {ForgeHostUrl.NormalizeKey(settings.BaseUri)}. " +
110 "Set [identity].api_token in forge-mcp.toml, FORGE_API_TOKEN env, or run forge auth login.");
111 if (!string.IsNullOrEmpty(apiToken))
112 http.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", apiToken);
113
114 if (!string.IsNullOrEmpty(settings.Actor))
115 http.DefaultRequestHeaders.TryAddWithoutValidation("X-Forge-Actor", settings.Actor);
116
117 return http;
118}
119
120static void PrintUsage()
121{
122 Console.Error.WriteLine(
123 """
124 AgentForge.Mcp — stdio bridge to Agent Forge API.
125
126 Usage:
127 AgentForge.Mcp [--config|-c PATH]
128
129 Config (TOML, recommended):
130 version = 1
131 [forge]
132 base_url = "http://127.0.0.1:8770"
133 [identity]
134 actor = "oar:YourName"
135 [mcp]
136 tools_list_changed = true
137 capabilities_poll_seconds = 120
138 [cursor]
139 sync_tool_descriptors = true
140 tool_descriptors_dir = "C:/Users/you/.cursor/projects/<slug>/mcps/user-forge/tools"
141
142 Env overrides (optional):
143 FORGE_BASE_URL, FORGE_API_TOKEN, FORGE_ACTOR
144 FORGE_MCP_TOOLS_LIST_CHANGED, FORGE_MCP_CAPABILITIES_POLL_SECONDS
145 FORGE_CURSOR_SYNC_TOOLS, FORGE_CURSOR_TOOLS_DIR
146 Token fallback: ~/.forge/credentials.json (forge login)
147 """);
148}
149
150var transport = new StdioServerTransport("AgentForgeMcp");
151await using var server = McpServer.Create(transport, options);
152ForgeMcpCapabilitiesPoller.StartIfEnabled(server, http, settings, jsonOptions, runCts.Token);
153await server.RunAsync(runCts.Token);
154return 0;
155
View only · write via MCP/CIDE