| name | unserializable-type-handling |
| description | Handle PowerShell commands whose parameter types cannot be JSON-serialized by applying three-tier graceful fallback: skip optional parameters, skip unviable parameter sets, or skip entire commands — preserving server startup. WHEN: adding MCP tool schema generation, encountering PSObject/ScriptBlock/Stream parameters, debugging server startup failures due to unserializable types, or extending IsUnserializableType checks. |
| domain | api-design |
| confidence | high |
| source | earned |
Context
The MCP protocol requires tool schemas to be JSON-serializable. PowerShell commands may have parameters with types that cannot be represented in JSON Schema (e.g., PSObject, ScriptBlock, PowerShell instances). Rather than failing to expose a command, implement three-tier fallback logic: skip optional parameters, skip parameter sets, or skip the entire command if all sets are affected.
Patterns
- Tier 1: Optional unserializable parameter: If a parameter is optional and unserializable, drop it silently from the MCP schema. The command remains usable without that parameter.
- Tier 2: Mandatory unserializable parameter (single parameter set): If all mandatory parameters in a parameter set are unserializable, skip that entire parameter set. Other sets remain available.
- Tier 3: All parameter sets skipped: If all parameter sets are skipped (no viable alternative remains), the command receives no MCP tool. Log a warning; the command is inaccessible via MCP but doesn't break the server.
- Unserializable type set includes:
PSObject, ScriptBlock, System.Object (too generic)
IntPtr, UIntPtr, and pointer/by-ref types
- Delegate-derived types
- Stream-derived types
- WaitHandle-derived types
System.Reflection.Assembly
System.Management.Automation.PowerShell
- All
Runspaces.* types
- Arrays of any unserializable type
- Check location:
PowerShellParameterUtils.IsUnserializableType(Type) performs the check after common parameter exclusion (e.g., -Verbose, -ErrorAction).
- Return value semantics:
GenerateMethodForCommand() returns bool (false = skipped). Do not throw exceptions; emit warnings and continue.
Examples
Parameter set handling in schema generation:
if (parameter.IsMandatory)
{
if (PowerShellParameterUtils.IsUnserializableType(parameter.ParameterType))
{
skipThisParameterSet = true;
}
}
else
{
if (PowerShellParameterUtils.IsUnserializableType(parameter.ParameterType))
{
continue;
}
}
Command-level skipping:
public bool GenerateMethodForCommand(string commandName)
{
var parameterSets = GetParameterSets(commandName);
var viableParameterSets = new List<IMcpToolParameterSet>();
foreach (var set in parameterSets)
{
if (IsParameterSetViable(set))
{
viableParameterSets.Add(set);
}
}
if (viableParameterSets.Count == 0)
{
_logger.LogWarning($"Command '{commandName}' has no serializable parameter sets; skipping MCP tool.");
return false;
}
return true;
}
Detecting unserializable types:
private static bool IsUnserializableType(Type type)
{
var unserializableTypes = new[]
{
typeof(PSObject),
typeof(ScriptBlock),
typeof(System.Object),
typeof(IntPtr),
typeof(UIntPtr),
};
if (unserializableTypes.Contains(type))
return true;
if (type.IsArray && IsUnserializableType(type.GetElementType()!))
return true;
if (typeof(Stream).IsAssignableFrom(type) ||
typeof(WaitHandle).IsAssignableFrom(type) ||
typeof(Delegate).IsAssignableFrom(type))
return true;
return false;
}
Anti-Patterns
- ❌ Throwing an exception when an unserializable type is encountered (crashes server initialization)
- ❌ Attempting to force-serialize unserializable types (results in invalid JSON schemas)
- ❌ Silently skipping entire commands without logging a warning (leaves no trace for debugging)
- ❌ Including mandatory unserializable parameters in the schema with placeholder types
- ❌ Forgetting to check array element types (arrays of unserializable types are also unserializable)