Registered in source/DevTools.Mcp.Server/Hosting/McpEngine.cs. The daemon does not
project host tools/resources into tools/list / resources/list.
ListChanged is advertised as false for tools, prompts, and resources.
| Tool | Assembly | Description |
|---|---|---|
list_host_instances |
DevTools.Daemon |
Lists connected instances + discovered MCP pipes |
launch_host |
DevTools.Daemon |
Launches host (optional model file at startup; host inferred from extension when filePath is set) |
read_file_info |
DevTools.Daemon |
On-disk Revit/DWG metadata reader |
list_machines |
DevTools.Daemon |
Queries Gateway for connected devices (requires auth) |
| Tool | Assembly | Backing store | Purpose |
|---|---|---|---|
search_dynamic |
DevTools.Mcp.Server |
ConnectedHostCatalog |
In-memory search across tool, resource, resource_template; returns opaque capabilityId locators |
invoke_dynamic |
DevTools.Mcp.Server |
HostBroker SDK session |
Resolve one capabilityId (or batch reads[] of resource locators) against the current catalog/session |
Both groups are registered together in McpEngine.CreateLocalTools (single external surface).
Infrastructure tools stay in DevTools.Daemon; typed dynamic handlers live in DevTools.Mcp.Server.
search_dynamic never opens a host pipe. It accepts query, optional hostInstanceId,
kinds, limit (1–32, default 12), and detail (summary default | schema).
Search normalizes whitespace / _ / -, ranks all-token matches before partial matches,
and returns bounded items plus hasMore. Each hit includes capabilityId, kind, target,
host routing, short description, requiredArgs, and argsHint; only detail=schema
includes a tool inputSchema.
invoke_dynamic accepts either capabilityId + optional arguments, or
reads: [{ capabilityId, arguments? }, ...] (mutually exclusive). Batch mode is
read-only (resources/templates); tools in reads are validation errors. Stale
locators return retryable stale_capability with research_then_reinvoke.
Agent payload knobs (daemon):
| Tool | Parameter | Default | Purpose |
|---|---|---|---|
search_dynamic |
detail |
summary |
schema includes per-hit inputSchema |
search_dynamic |
limit |
12 (max 32) |
Bound catalog hits; response may set hasMore |
search_dynamic |
(response) | — | Opaque capabilityId + argsHint / requiredArgs |
invoke_dynamic |
reads |
— | Batch read-only resource/template reads by capabilityId |
read_file_info |
detail |
summary |
summary = version/title/link names; full = complete on-disk metadata |
invoke_dynamic resource reads serialize with compact McpJsonUtilities.DefaultOptions
(not indented). Tool descriptions warn against parallel mutating calls on the same PID.
| Name | Arguments | Description |
|---|---|---|
revit_code |
task, optional mode |
Generate IExternalCommand C# for Revit |
acad_code |
task, optional mode |
Generate AutoCAD .NET command C# |
These are registered via SDK PromptCollection and answered entirely in-process.
HostBroker connects to each DevToolsMcp_* pipe with an SDK McpClient, then
calls ListToolsAsync / ListResourcesAsync / ListResourceTemplatesAsync once
and stores an immutable HostCatalogEntry keyed by HostKey(machineId, processId).
On host list_changed notifications, only that host’s entry is re-listed and
replaced. Disconnect removes the entry. External daemon collections are untouched.
Search ranking (ConnectedHostCatalog.Search): empty query returns the catalog
(kind/target order). Non-empty queries normalize whitespace / _ / - and rank
all-token matches before partial matches on target name/URI, resource name, or
description. Invalid kinds are validation errors (no silent broaden).
Python dynamic toolsets (samples/PythonDemo/mcp_toolset): tool function
parameters use snake_case wire names only (no Field(alias=) on params). Do not
register Python and C# toolsets that expose the same tool names simultaneously.
C# sample templates (samples/RevitMcpToolSet): ElementResources registers
resource_template entries revit://element/{elementId} (application/json) and
revit://schedule/{scheduleId}/preview (text/csv). Agents discover them via
search_dynamic with kinds=["resource_template"] and read via invoke_dynamic
(single arguments or batch reads[]).
Registered via DI in host projects and exposed on the host spec wire handler
(HostMcpPipeServer + McpHandler). Host prompts are not registered.
| Tool | Host | Description |
|---|---|---|
execute_csharp_code |
Revit + AutoCAD | Compile and run C# with host API context |
execute_python_code |
Revit + AutoCAD | Execute inline Python with PEP 723 deps |
open_document |
Revit + AutoCAD | Open model file via IDocumentBridge |
navigate_history |
Revit + AutoCAD | Undo/redo navigation |
view_screenshot |
Revit + AutoCAD | Active viewport PNG (1280 px / 1280×720) as MCP image content |
| URI | Host | Description |
|---|---|---|
revit://csharp-cheatsheet |
Revit | C# API patterns |
revit://python-cheatsheet |
Revit | Python API patterns |
revit://model/context |
Revit | Live model state |
revit://model/warnings |
Revit | Active warnings |
revit://version |
Revit | Host/runtime version |
acad://csharp-cheatsheet |
AutoCAD | C# API patterns |
acad://python-cheatsheet |
AutoCAD | Python API patterns |
Loaded from user-configured paths via McpCatalogStore:
- .NET assemblies —
[McpTool]viaDotnetMcpAssemblyParser - Python toolsets —
PythonToolsetParser/ToolParser.py
| Surface | How InputSchema is produced |
|---|---|
| Daemon tools | SDK McpServerTool.Create(handler) — schema from method signature + [Description] |
Host built-in tools (IBuiltInMcpTool) |
DescriptorFactory + wire list from McpHandler |
Discovered .NET assemblies (McpAssemblyParser) |
McpSchemaBuilder.FromClrType over MetadataLoadContext parameters |
JsonSchemaObject / JsonSchemaProperty under DevTools.Mcp.Catalog are for parsing schemas (UI only).
MCP call observability is always-on at protocol boundaries — not optional and not a separate tool.
Serialization uses McpJsonUtilities.DefaultOptions (same as McpPrimitiveDispatcher,
PythonToolsetParser, and the rest of the stack). There is no McpCallLog / redact /
preview pipeline.
| Surface | Attachment | Logger category |
|---|---|---|
Daemon tools/call |
SDK CallToolFilters via McpServerConfigurator |
DevTools.Mcp.ToolCall |
Host tools/call / resources/read |
McpHandler + McpLogFilters |
DevTools.Mcp.ToolCall / DevTools.Mcp.ResourceRead |
Each call emits a single compact ZLogger line. Dynamic daemon handlers use method string
tools/call (the protocol operation they represent), not a separate dynamic.* prefix:
tools/call ok target=execute_csharp_code durationMs=718 args={"code":"..."} result={...}
resources/read ok target=revit://model/context durationMs=42 result={...}
tools/call ok target=search_dynamic durationMs=3 args={"query":"wall",...} result={...}
| Token | Meaning |
|---|---|
tools/call / resources/read |
MCP method (daemon dynamic tools log as tools/call) |
ok / error / timeout |
Outcome |
target |
Tool name or resource URI |
args |
Protocol JSON arguments (JsonElement dictionary via McpJsonUtilities) |
result |
Serialized CallToolResult / ReadResourceResult via McpJsonUtilities |
durationMs |
Wall time |
Binary content: when a result includes image/audio blocks, monitor lines omit base64
Data and log type, mimeType, and length only (SDK DebuggerDisplay spirit).
search_dynamic monitor lines log count and compact {kind,target,hostInstanceId}
hits — not the full catalog JSON returned to the client.
HostBroker catalog refresh uses structured ZLogger scopes (counts + duration) — no custom
ActivitySource. Protocol MCP spans stay with the SDK.
Host SDK protocol chatter (ModelContextProtocol.*) is filtered to Warning so call logs stay visible.
AddMcp() registers WithTasks; McpTaskExecutionSelector.Select reads
Tool.Meta[McpTaskExecutionMeta.MetaKey] and maps to SDK McpTaskExecutionMode via
McpTaskExecutionMeta.ParseMode. Tools declare mode with
[McpMeta(McpTaskExecutionMeta.MetaKey, McpTaskExecutionMeta.Mode.Optional)] where
Mode.* are nameof(McpTaskExecutionMode.*) from package
ModelContextProtocol.Extensions.Tasks. MetaKey (tasks.executionMode) is the only host
convention bridging [McpMeta] to ExecutionModeSelector — not an MCP wire field.
Unset meta defaults to Synchronous. Today only execute_csharp_code and
execute_python_code set Optional.
Clients opt in per tools/call via _meta extension io.modelcontextprotocol/tasks.
Daemon-to-host routing uses synchronous HostSession.CallToolPassthroughAsync (task polling is client ↔ daemon only).
Host and catalog tool responses may include SDK ResourceLinkBlock content (URI + metadata,
no inline payload). The adapter maps these to McpResourceLinkContent and round-trips them
through invoke_dynamic without auto-fetching the linked resource (Option A). Clients that
support resource links can resolve URIs themselves; unsupported clients skip the block.
Daemon envelope tools (search_dynamic, list_host_instances, read_file_info) emit
StructuredContent manually via DynamicToolCallResults and do not set
UseStructuredContent on tools/list yet — auto outputSchema from JsonElement
members breaks strict clients (Cursor drops the entire tool list). Host toolsets may
use UseStructuredContent where schemas are stable. invoke_dynamic pass-through
preserves host StructuredContent on success paths.
| Role | Serializer |
|---|---|
| Protocol wire, logs, discovery, tool result text | ToolHelpers.Serialize / ToolHelpers.ToElement (wraps McpJsonUtilities.DefaultOptions) |
See MCP README for dual-server vocabulary and DI lifecycle.
Element counts use per-category FilteredElementCollector.GetElementCount() — native
count-only queries that do not hydrate elements. ElementMulticategoryFilter + iterate
is reserved for cases that need element data, not count-only snapshots.
There is no TTL cache on this resource: counts must reflect live document state after
mutations. Latency targets are validated via docs/agents/mcp-integration-test.md
(3× sequential read on Snowdon Towers; warm durationMs < 500).
Implementation: source/DevTools.Agents.Revit/Resources/RevitModelContext.cs.
%APPDATA%\RevitDevTool\mcp-server\log_{pid}_{yyyyMMddHH}_{seq}.log — hourly ZLogger rolling, 30-day cleanup at startup. Tray and stdio share the folder; PID separates processes.
| Area | Path |
|---|---|
| Daemon infra + dynamic tools | source/DevTools.Mcp.Server/Tools/, Hosting/McpEngine.cs |
| Daemon file logging | source/DevTools.Daemon/Hosting/McpServerFileLogging.cs |
| Fixed prompts | source/DevTools.Mcp.Server/Prompts/ |
| Call-log payload helpers | source/DevTools.Mcp.Server/Hosting/McpLogPayload.cs |
| Shared MCP hosting (Tasks, filters) | source/DevTools.Mcp.Server/Hosting/McpServerConfigurator.cs |
| Tool result helpers | source/DevTools.Mcp.Core/Utils/ToolHelpers.cs |
| Host wire handler + logging | source/DevTools.Mcp.Adapter/Host/McpHandler.cs, Hosting/McpLogFilters.cs |
| Schema builder (discovery) | source/DevTools.Mcp.Catalog/Discovery/McpSchemaBuilder.cs |
| Schema parse models (UI) | source/DevTools.Mcp.Catalog/JsonSchemaModels.cs |
| Connected host catalog | source/DevTools.Mcp.Client/ConnectedHostCatalog.cs |
| In-host registry | source/DevTools.Mcp.Catalog/McpCatalogStore.cs |
| Built-in tools | source/DevTools.Execution/External/Mcp/BuiltIn/ |