Mars mcp server
Build MCP (Model Context Protocol) servers in Delphi with MARS-Curiosity, so AI agents (Claude, ChatGPT, Open WebUI/Ollama, MCP Inspector) can discover and call Delphi code as tools, read MCP resources and use MCP prompts. Use this skill whenever the user wants to expose Delphi/MARS functionality to an AI agent or LLM, mentions MCP, MCP server, MCP tools, resources, prompts, tool calling, function calling, AI agents, connectors, or wants Claude/ChatGPT/a local model to query their Delphi application or database; also when working with MARS.MCP.* units, TMCPResource, [MCPTool], [MCPResource], [MCPPrompt], TMCPOAuthServer, or debugging an MCP client that cannot connect or authenticate to a MARS server.From its SKILL.md
npx -y skills add andrea-magni/MARS --skill mars-mcp-serverAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
SKILL.md
8.0 KB, as published. Nobody here has run it
Build an MCP server with MARS-Curiosity
MARS ships native MCP support (units MARS.MCP.* in Source/): derive a resource from TMCPResource, mark methods with [MCPTool], and any MARS server becomes an MCP server speaking the Streamable HTTP transport (single endpoint, JSON-RPC 2.0 over POST). Tool list and JSON Schema are generated from RTTI — no protocol code to write.
The complete working example is Demos/MCPServer in the MARS repository (public tools + FireDAC-backed authenticated tools + OAuth).
Minimal MCP server
unit Server.Resources.MCP;
interface
uses
SysUtils, Classes
, MARS.Core.Attributes, MARS.Core.MediaType
, MARS.MCP.Resource, MARS.MCP.Attributes;
type
TCalculationResult = record
operation: string;
value: Double;
end;
[Path('mcp')
, MCPServerInfo('My MCP Server', '1.0.0'
, 'Optional instructions the AI agent reads on connection.')]
TMyMCPResource = class(TMCPResource)
public
[MCPTool('say_hello', 'Returns a friendly greeting for the given name')]
function SayHello(
[MCPParam('name', 'Name of the person to greet')] const AName: string): string;
[MCPTool('add_numbers', 'Adds two numbers and returns a structured result')]
function AddNumbers(
[MCPParam('a', 'First operand')] const A: Double;
[MCPParam('b', 'Second operand')] const B: Double): TCalculationResult;
end;
implementation
uses
MARS.Core.Registry;
function TMyMCPResource.SayHello(const AName: string): string;
begin
Result := 'Hello, ' + AName + '!';
end;
function TMyMCPResource.AddNumbers(const A, B: Double): TCalculationResult;
begin
Result.operation := 'add';
Result.value := A + B;
end;
initialization
MARSRegister([TMyMCPResource]);
end.
This is a regular MARS resource: register the unit mask in Server.Ignition.pas as usual (AddApplication('DefaultApp', '/default', ['Server.Resources.*'])). The MCP endpoint is the composed URL, e.g. http://localhost:8080/rest/default/mcp. Everything the mars-development skill says about resources, [Context] injection and configuration applies here too.
A complete starting unit is available in assets/Server.Resources.MCP.pas.template.
What the attributes do
| Attribute | Where | Effect |
|---|---|---|
MCPServerInfo(name, version, instructions) | resource class | identity returned by initialize; instructions are read by the agent |
MCPTool(description) / MCPTool(name, description) | public method | exposes the method as a tool (name defaults to the method name) |
MCPParam(description) / MCPParam(name, description) | parameter | documents and optionally renames the parameter in the JSON Schema (rename it: Delphi convention names like AValue leak otherwise) |
MCPResource(uri, [name,] description [, mimeType]) | public method | exposes readable content by URI; {param} placeholders make it a template — see references/resources-prompts.md |
MCPPrompt([name,] description) | public method | exposes a reusable prompt template; parameters become the prompt arguments — see references/resources-prompts.md |
RolesAllowed('...') / DenyAll | tool method | per-tool authorization — see references/authorization.md |
MCPOAuth | resource class | 401 + OAuth discovery for unauthenticated requests — see references/authorization.md |
Tool results: a string becomes a text content block; records (and arrays, numbers...) are serialized with the MARS JSON serializer and records also become structuredContent. Exceptions raised inside a tool are reported as tool execution errors (isError: true), not protocol errors. TDataSet results need TMCPDataResource — see references/database-tools.md.
Parameter/field types map to JSON Schema automatically: strings → string, integers → integer, floats → number, Boolean → boolean, enums → string + enum values, TDateTime → string/date-time, dynamic arrays → array, records → nested object. All parameters are listed as required — model optional inputs as explicit values (e.g. empty string) and document them in the description.
Protocol facts worth knowing
- Supported JSON-RPC methods:
initialize(protocol versions 2025-06-18, 2025-03-26, 2024-11-05),ping,tools/list,tools/call,resources/list,resources/templates/list,resources/read,prompts/list,prompts/get. Notifications answer202with no body;GET/DELETEanswer405(stateless server, allowed by spec). Theresources/promptscapabilities are advertised only when the class declares any. ExtendTMCPDispatcher.DispatchRequestfor additional methods. - Tool discovery is cached per resource class (thread-safe, RTTI scanned once). The authorization filter is evaluated per request, so caching never leaks role-protected tools.
- The dispatcher class is pluggable: override
GetDispatcherClasson the resource (that is howTMCPDataResourceadds dataset support).
When to read the references
- Any authentication requirement — Bearer tokens, roles, hiding tools per user, OAuth login window for Claude/ChatGPT/Open WebUI: read
references/authorization.mdfirst, it also lists client-specific pitfalls that look like server bugs. - Tools that query a database (FireDAC, TFDQuery, TMARSFireDAC): read
references/database-tools.md— dataset ownership rules matter. - MCP resources or prompts (attachable content like DB schemas, URI templates, reusable prompt templates): read
references/resources-prompts.md.
Verify the server
With curl (initialize / list / call):
curl -X POST http://localhost:8080/rest/default/mcp -H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
curl -X POST http://localhost:8080/rest/default/mcp -H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"add_numbers","arguments":{"a":2,"b":3}}}'
With the official MCP Inspector (needs Node.js), which performs a real client handshake:
npx @modelcontextprotocol/inspector --cli http://localhost:8080/rest/default/mcp --transport http --method tools/list
Connect real clients: claude mcp add --transport http my-server http://localhost:8080/rest/default/mcp, or in Open WebUI add an External Tools connection of type MCP (Streamable HTTP) (requires Open WebUI ≥ 0.6.31). Remember: from another machine/container, use the server's LAN IP, not localhost.
Troubleshooting quick table
| Symptom | Likely cause |
|---|---|
| Client says no tools / "no such tool" | tools not enabled for the chat/model on the client side; or tool hidden by a role filter (by design it answers "Unknown tool") |
| 404 on the endpoint | wrong composed URL — check engine BasePath (/rest), app path, resource [Path] |
| 403 instead of OAuth login | class-level [RolesAllowed] fires before MCP: use [MCPOAuth] for OAuth-protected endpoints (see references/authorization.md) |
| Client aborts during OAuth discovery | a /.well-known/* probe returns 500/garbage — wire TMCPOAuthMetadata.HandleWellKnownRequest in BeforeHandleRequest (it also answers clean 404s and serves the openid-configuration alias) |
| Works with curl, not from Docker/other host | server bound/reached via localhost only — use the LAN IP; verify with curl from the client's network |