Skip to content

Commit fe953a0

Browse files
The MCP server is amcli mcp (#1677)
The server from asc-community/AngouriMathMCP becomes a library in Sources/MCP, and the terminal serves it with `amcli mcp`, so an MCP client's configuration names the `amcli` users already install. It builds against the library here rather than a released package, and McpUnitTests drives it as a client would: the protocol, one call to each tool on the behaviour its design rests on, the use cases its README describes, its selftest, and its knowledge of the grammar against every function token in AngouriMath.g. The selftest fails there on a documented library defect that no longer reproduces as well as on an identity that fails. Whether a name is a function is asked of the parser, by parsing name(x), rather than kept in a list, so the unknown-function warning follows the grammar. The server's documents say what this library does: integrals answered with Ei, erfi, Si and li, the functions the grammar has gained, and union and intersection, which are indexed. A tool's answer is no longer escaped for HTML, so a model reads `+` and `α` as written. Part of #1649. Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent fb520e2 commit fe953a0

33 files changed

Lines changed: 5237 additions & 4 deletions

‎.github/workflows/InteractiveTest.yml‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ jobs:
3131
run: |
3232
dotnet build Sources/Tests/InteractiveWrapperUnitTests -c Release
3333
dotnet build Sources/Tests/TerminalUnitTests -c Release
34+
dotnet build Sources/Tests/McpUnitTests -c Release
3435
3536
- name: Test Interactive Wrapper
3637
if: ${{ matrix.os != 'ubuntu-latest' }}
@@ -42,11 +43,20 @@ jobs:
4243
run: |
4344
dotnet test "Sources/Tests/TerminalUnitTests" -c Release
4445
46+
# The server amcli mcp runs. It builds against the library in this repository, so a change
47+
# that moves an answer its documents describe, or a function the grammar gains or loses,
48+
# fails here rather than after a release.
49+
- name: Test MCP server
50+
if: ${{ matrix.os != 'ubuntu-latest' }}
51+
run: |
52+
dotnet test "Sources/Tests/McpUnitTests" -c Release
53+
4554
- name: Test with coverage
4655
if: ${{ matrix.os == 'ubuntu-latest' }}
4756
run: |
4857
dotnet test "Sources/Tests/InteractiveWrapperUnitTests" -c Release --collect:"XPlat Code Coverage" --results-directory ./coverage -- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=opencover
4958
dotnet test "Sources/Tests/TerminalUnitTests" -c Release --collect:"XPlat Code Coverage" --results-directory ./coverage -- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=opencover
59+
dotnet test "Sources/Tests/McpUnitTests" -c Release --collect:"XPlat Code Coverage" --results-directory ./coverage -- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=opencover
5060
5161
- name: 'Send to codecov'
5262
if: ${{ matrix.os == 'ubuntu-latest' }}

‎AGENTS.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -821,6 +821,7 @@ are short, and a stale one is worse than none — if you change what a file desc
821821
| [`Contributing/Packaging.md`](Sources/AngouriMath/Docs/Contributing/Packaging.md) | which capabilities ship in the kernel package and which ship separately, and the four checkable clauses that decide. Read it before anything large lands in the kernel |
822822
| [`WhatsNew/version_performance_control.md`](Sources/AngouriMath/Docs/WhatsNew/version_performance_control.md) | the inter-version performance table, how to add a column, and when the CI performance baseline may be updated |
823823
| `Sources/Analyzers/` | the custom analyzers, including the static-field one behind `[ConstantField]` |
824+
| [`Sources/MCP/README.md`](Sources/MCP/README.md) | the MCP server `amcli mcp` runs: what it offers an agent, and the invariants that look arbitrary in it and are not |
824825

825826
Anything added for the library's own purposes is not `public` — see
826827
[`Contributing/coding_rules.md`](Sources/AngouriMath/Docs/Contributing/coding_rules.md). Nothing

‎Sources/.editorconfig‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -720,6 +720,12 @@ file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed
720720
[AngouriMath/Numerics/*.cs]
721721
file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n
722722

723+
[MCP/**.cs]
724+
file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n
725+
726+
[Tests/McpUnitTests/**.cs]
727+
file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n
728+
723729
[Tests/UnitTests/Core/IntervalEvaluationTest.cs]
724730
file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n
725731

‎Sources/AngouriMath.sln‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,12 @@ Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "AngouriMath.Terminal", "Ter
5353
EndProject
5454
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "AngouriMath.Terminal.Lib", "Terminal\AngouriMath.Terminal.Lib\AngouriMath.Terminal.Lib.fsproj", "{405E214B-17A6-83E0-90A1-4F8B7213954F}"
5555
EndProject
56+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "MCP", "MCP", "{A3D7217B-225C-4020-980F-EBB8EAECADBA}"
57+
EndProject
58+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AngouriMath.Mcp", "MCP\AngouriMath.Mcp\AngouriMath.Mcp.csproj", "{50C3DF3D-BB3E-49D8-BC88-4C7BE573B51A}"
59+
EndProject
60+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "McpUnitTests", "Tests\McpUnitTests\McpUnitTests.csproj", "{0C73C9BD-C99C-486D-A058-EE269F02DB77}"
61+
EndProject
5662
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Utils", "Utils", "{DC3A3E46-854E-403D-8FA5-561DF66277D4}"
5763
ProjectSection(SolutionItems) = preProject
5864
Utils\antlr_rerun.bat = Utils\antlr_rerun.bat
@@ -153,6 +159,14 @@ Global
153159
{405E214B-17A6-83E0-90A1-4F8B7213954F}.Debug|Any CPU.Build.0 = Debug|Any CPU
154160
{405E214B-17A6-83E0-90A1-4F8B7213954F}.Release|Any CPU.ActiveCfg = Release|Any CPU
155161
{405E214B-17A6-83E0-90A1-4F8B7213954F}.Release|Any CPU.Build.0 = Release|Any CPU
162+
{50C3DF3D-BB3E-49D8-BC88-4C7BE573B51A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
163+
{50C3DF3D-BB3E-49D8-BC88-4C7BE573B51A}.Debug|Any CPU.Build.0 = Debug|Any CPU
164+
{50C3DF3D-BB3E-49D8-BC88-4C7BE573B51A}.Release|Any CPU.ActiveCfg = Release|Any CPU
165+
{50C3DF3D-BB3E-49D8-BC88-4C7BE573B51A}.Release|Any CPU.Build.0 = Release|Any CPU
166+
{0C73C9BD-C99C-486D-A058-EE269F02DB77}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
167+
{0C73C9BD-C99C-486D-A058-EE269F02DB77}.Debug|Any CPU.Build.0 = Debug|Any CPU
168+
{0C73C9BD-C99C-486D-A058-EE269F02DB77}.Release|Any CPU.ActiveCfg = Release|Any CPU
169+
{0C73C9BD-C99C-486D-A058-EE269F02DB77}.Release|Any CPU.Build.0 = Release|Any CPU
156170
{9E733914-18F8-CD3A-EDB8-F32FFEF4A3DA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
157171
{9E733914-18F8-CD3A-EDB8-F32FFEF4A3DA}.Debug|Any CPU.Build.0 = Debug|Any CPU
158172
{9E733914-18F8-CD3A-EDB8-F32FFEF4A3DA}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -197,6 +211,8 @@ Global
197211
{13409571-0ADC-B9C6-2EE7-44DF0B4AD43C} = {822E64E5-9BB9-4702-B704-18129D0BC9C7}
198212
{E34FC29D-1046-0B02-E647-858434BFD9E0} = {7FCB69C7-EC31-4F61-BDD9-43B9DEED8750}
199213
{405E214B-17A6-83E0-90A1-4F8B7213954F} = {7FCB69C7-EC31-4F61-BDD9-43B9DEED8750}
214+
{50C3DF3D-BB3E-49D8-BC88-4C7BE573B51A} = {A3D7217B-225C-4020-980F-EBB8EAECADBA}
215+
{0C73C9BD-C99C-486D-A058-EE269F02DB77} = {9869C55D-A753-4DE2-9C22-CCF768C6EC53}
200216
{9E733914-18F8-CD3A-EDB8-F32FFEF4A3DA} = {DC3A3E46-854E-403D-8FA5-561DF66277D4}
201217
{FD30A551-F0D8-071A-9356-0BF0CDFB2FCC} = {D8022D2F-1DCE-4CE1-98DF-70B00D719D59}
202218
{ABAD0619-30C7-242D-670F-2ECC908074A2} = {D8022D2F-1DCE-4CE1-98DF-70B00D719D59}
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
//
2+
// Copyright (c) 2019-2026 Angouri.
3+
// AngouriMath is licensed under MIT.
4+
// Details: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.
5+
// Website: https://am.angouri.org.
6+
//
7+
8+
using AngouriMath;
9+
using AngouriMath.Core;
10+
using static AngouriMath.Entity;
11+
12+
namespace AngouriMath.Mcp;
13+
14+
/// <summary>
15+
/// Answers two questions the raw library does not: "what should I watch out for in this
16+
/// expression?" and "is this answer real, or merely correct?".
17+
///
18+
/// AngouriMath has no step engine — there is no derivation output anywhere in the library —
19+
/// so the honest substitute is to check a chain of working the caller supplies, and say
20+
/// which step broke. See <see cref="CheckSteps"/>.
21+
/// </summary>
22+
internal static class Analysis
23+
{
24+
// Spread across the real line, including negatives and a point near zero, because the
25+
// whole point here is to FIND the places an expression stops being real or defined.
26+
private static readonly Entity[] Probes =
27+
["-2.7", "-1.3", "-0.4", "0.6", "1.9", "3.2"];
28+
29+
public sealed record Caution(string Where, string Risk);
30+
31+
/// <summary>
32+
/// Structural hazards, found by walking the tree. These are the classic ways an
33+
/// expression means less than it appears to.
34+
/// </summary>
35+
public static List<Caution> Hazards(Entity e)
36+
{
37+
var found = new List<Caution>();
38+
39+
foreach (var node in e.Nodes)
40+
{
41+
switch (node)
42+
{
43+
case Divf:
44+
var divisor = node.DirectChildren.Count > 1
45+
? node.DirectChildren[1].Stringize()
46+
: "?";
47+
found.Add(new Caution(node.Stringize(),
48+
$"division: undefined where {divisor} = 0. Any cancellation of this " +
49+
"term silently widens the domain unless a `provided` guard survives."));
50+
break;
51+
52+
case Logf:
53+
found.Add(new Caution(node.Stringize(),
54+
"logarithm: real only for a positive argument. Combining " +
55+
"ln(a)+ln(b) into ln(a*b) widens the domain and is a documented " +
56+
"source of extraneous roots."));
57+
break;
58+
59+
case Powf pow when pow.Exponent is not Number.Integer:
60+
found.Add(new Caution(node.Stringize(),
61+
"fractional or symbolic power: the principal branch is taken, so " +
62+
"(-8)^(1/3) is a complex number, not -2. sqrt(x^2) is abs(x), not x."));
63+
break;
64+
65+
case Arcsinf or Arccosf:
66+
found.Add(new Caution(node.Stringize(),
67+
"inverse trig: real only for an argument in [-1, 1], and the " +
68+
"principal branch is returned — the full solution family is periodic."));
69+
break;
70+
71+
case Factorialf:
72+
found.Add(new Caution(node.Stringize(),
73+
"factorial: defined via the gamma function, so it returns values for " +
74+
"non-integers and poles at the negative integers."));
75+
break;
76+
77+
case Absf:
78+
found.Add(new Caution(node.Stringize(),
79+
"absolute value: not differentiable at 0, and it hides a sign change " +
80+
"that matters when solving."));
81+
break;
82+
}
83+
}
84+
85+
return found;
86+
}
87+
88+
/// <summary>Domain guards AngouriMath itself worked out, pulled out of the tree.</summary>
89+
public static List<string> Conditions(Entity simplified) =>
90+
simplified.Nodes
91+
.OfType<Providedf>()
92+
.Select(p => p.Predicate.Stringize())
93+
.Distinct()
94+
.ToList();
95+
96+
public sealed record RealityReport(
97+
List<string> ComplexAt, List<string> UndefinedAt, int Sampled);
98+
99+
/// <summary>
100+
/// Where does this expression stop being a real number? A result can be perfectly
101+
/// correct and still be useless as a length, a resistance or a time.
102+
/// </summary>
103+
public static RealityReport Reality(Entity e)
104+
{
105+
var complexAt = new List<string>();
106+
var undefinedAt = new List<string>();
107+
var variables = e.Vars.ToArray();
108+
var sampled = 0;
109+
110+
// With no free variables there is a single value to inspect.
111+
if (variables.Length == 0)
112+
{
113+
Inspect(e, "(constant)");
114+
return new RealityReport(complexAt, undefinedAt, 1);
115+
}
116+
117+
foreach (var probe in Probes)
118+
{
119+
var substituted = e;
120+
foreach (var variable in variables)
121+
substituted = substituted.Substitute(variable, probe);
122+
123+
sampled++;
124+
Inspect(substituted, string.Join(" = ", variables.Select(v => v.Stringize())) +
125+
" = " + probe.Stringize());
126+
}
127+
128+
return new RealityReport(complexAt, undefinedAt, sampled);
129+
130+
void Inspect(Entity at, string label)
131+
{
132+
try
133+
{
134+
if (!at.EvaluableNumerical) { undefinedAt.Add(label); return; }
135+
136+
var value = at.EvalNumerical();
137+
var real = value.RealPart.EDecimal.ToDouble();
138+
var imaginary = value.ImaginaryPart.EDecimal.ToDouble();
139+
140+
if (double.IsNaN(real) || double.IsNaN(imaginary)) undefinedAt.Add(label);
141+
else if (Math.Abs(imaginary) > 1e-12) complexAt.Add(label);
142+
}
143+
catch
144+
{
145+
undefinedAt.Add(label);
146+
}
147+
}
148+
}
149+
150+
/// <summary>Force an expression to be real-valued: anything complex becomes NaN.</summary>
151+
public static Entity AsReal(Entity e) =>
152+
e.Replace(node => node.WithCodomain(Domain.Real));
153+
154+
public sealed record StepCheck(
155+
int Index, string From, string To, bool? Equal, string Method, string? Difference);
156+
157+
/// <summary>
158+
/// Check a chain of working, one step at a time, and report the first step that changes
159+
/// the value.
160+
///
161+
/// This is the useful form of "show me the steps". The library cannot produce a
162+
/// derivation — but a model is perfectly good at PROPOSING one and unreliable at
163+
/// EXECUTING it, so having it write the steps and having this check each transition
164+
/// puts each side on the task it is actually good at. A wrong step is located, not just
165+
/// detected.
166+
/// </summary>
167+
public static List<StepCheck> CheckSteps(List<Entity> steps)
168+
{
169+
var checks = new List<StepCheck>();
170+
171+
for (var i = 1; i < steps.Count; i++)
172+
{
173+
var from = steps[i - 1];
174+
var to = steps[i];
175+
176+
Entity difference;
177+
try { difference = (from - to).Simplify(); }
178+
catch { difference = from - to; }
179+
180+
if (Numeric.IsZero(difference))
181+
{
182+
checks.Add(new StepCheck(i, from.Stringize(), to.Stringize(),
183+
true, "exact", null));
184+
continue;
185+
}
186+
187+
var numerically = Numeric.Equal(difference, 0);
188+
checks.Add(new StepCheck(
189+
i, from.Stringize(), to.Stringize(),
190+
numerically,
191+
numerically is null ? "inconclusive" : "numeric",
192+
numerically == true ? null : difference.Stringize()));
193+
}
194+
195+
return checks;
196+
}
197+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<!--
4+
The Model Context Protocol server that `amcli mcp` runs: newline-delimited JSON-RPC over stdio,
5+
exposing the library to an agent as tools, prompts and resources. A library rather than a
6+
program so that the terminal's tool is the one thing a user installs; Server.Run is the entry.
7+
It builds against the library in this repository, so a change to the parser or to an answer
8+
the server's selftest or its tests rely on fails here rather than after a release.
9+
https://github.com/asc-community/AngouriMath/issues/1649
10+
-->
11+
12+
<PropertyGroup>
13+
<TargetFramework>net10.0</TargetFramework>
14+
<ImplicitUsings>enable</ImplicitUsings>
15+
<RootNamespace>AngouriMath.Mcp</RootNamespace>
16+
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
17+
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
18+
<GenerateDocumentationFile>true</GenerateDocumentationFile>
19+
<!-- Shipped inside the terminal's tool package, not as a package of its own. -->
20+
<IsPackable>false</IsPackable>
21+
</PropertyGroup>
22+
23+
<ItemGroup>
24+
<InternalsVisibleTo Include="McpUnitTests" />
25+
</ItemGroup>
26+
27+
<ItemGroup>
28+
<ProjectReference Include="../../AngouriMath/AngouriMath.csproj" />
29+
</ItemGroup>
30+
31+
</Project>

0 commit comments

Comments
 (0)