Research: MCP Tool Operations¶
Feature: 010-mcp-tool-operations Date: 2025-11-28
Research Questions Resolved¶
1. Semantic Kernel MCP Module Integration¶
Decision: Use Semantic Kernel's MCP plugins directly via composition/wrapping
Rationale: - Semantic Kernel (already a dependency at v1.37.1) provides complete MCP implementation - Four plugin types available: MCPStdioPlugin, MCPSsePlugin, MCPWebsocketPlugin, MCPStreamableHttpPlugin - Plugins handle protocol lifecycle, tool discovery, content conversion - Avoids reimplementing MCP protocol from scratch
Alternatives Considered: - Direct mcp-sdk usage: Rejected - would duplicate work already done in SK - Custom MCP implementation: Rejected - unnecessary complexity, maintenance burden
2. MCP Protocol Version Compatibility¶
Decision: Target MCP protocol version supported by Semantic Kernel (2024-11-05 spec)
Rationale: - SK maintains compatibility with official MCP specification - Protocol includes: initialize, tools/list, tools/call, prompts/list, prompts/get, notifications - Content types: TextContent, ImageContent, AudioContent, EmbeddedResource, ResourceLink
Alternatives Considered: - Pin specific MCP version: Rejected - let SK handle version management
3. Transport Type Detection¶
Decision: Explicit transport field in configuration (not auto-detection)
Rationale:
- Clear user intent
- No ambiguity between http and sse transports
- Follows VS Code mcp.json pattern
- Default to stdio when not specified
Transport Mapping: | transport value | SK Plugin Class | |-----------------|-----------------| | stdio (default) | MCPStdioPlugin | | sse | MCPSsePlugin | | websocket | MCPWebsocketPlugin | | http | MCPStreamableHttpPlugin |
3.1 Stdio Command Restrictions (Security)¶
Decision: Only allow npx, uvx, or docker as stdio commands
Rationale: - Security: Prevents command injection attacks via arbitrary shell commands - Consistency: All MCP servers run through known package managers or containers - Auditability: Easy to understand what's being executed - Ecosystem alignment: Most MCP servers are distributed via npm (npx) or Python (uvx)
Supported Commands:
| Command | Runtime | Package Source |
|---------|---------|----------------|
| npx | Node.js | npm registry |
| uvx | Python | PyPI via uv |
| docker | Docker | Container registries |
Alternatives Rejected: - Arbitrary command execution: Security risk (shell injection) - Python/node direct: Less portable, harder to manage dependencies - Allowlist expansion: Can be added later if needed with security review
4. Environment Variable Resolution Strategy¶
Decision: Fail-fast at configuration load time, reusing existing env_loader.py
Rationale:
- Per clarification session decision
- Prevents runtime surprises
- Security benefit: no empty credentials passed accidentally
- Clear error messages help debugging
- DRY: Existing substitute_env_vars() already implements fail-fast behavior
Implementation (REUSE existing code):
- REUSE: holodeck.config.env_loader.substitute_env_vars() for ${VAR} pattern
- REUSE: holodeck.config.env_loader.load_env_file() for env_file loading
- DO NOT: Create new env resolution logic - use existing functions
Existing Functions in env_loader.py:
substitute_env_vars(text: str) -> str # Already fails-fast on missing vars
load_env_file(path: str) -> dict[str, str] # Already loads .env files
get_env_var(key: str, default: Any) -> Any # Get single var with default
5. Tool Name Normalization Pattern¶
Decision: Replace invalid characters with "-" (per Semantic Kernel pattern)
Rationale:
- Consistent with SK MCPPluginBase implementation
- Pattern: re.sub(r"[^a-zA-Z0-9_]", "-", tool_name)
- Ensures tool names are valid Python identifiers
6. Content Type Conversion Strategy¶
Decision: Map MCP content types to HoloDeck internal representation
Rationale: - HoloDeck already has content models for LLM integration - Conversion ensures consistent handling across tool types
Mapping: | MCP Type | HoloDeck Type | |----------|---------------| | TextContent | str (plain text) | | ImageContent | dict with base64/url | | AudioContent | dict with base64/url | | EmbeddedResource | bytes (binary) | | ResourceLink | str (URI reference) |
7. Error Handling Strategy¶
Decision: Custom exception hierarchy extending HoloDeckError
Rationale:
- Consistent with existing error patterns in lib/errors.py
- Clear categorization: config errors vs runtime errors vs protocol errors
Exception Types:
- MCPConfigError: Invalid MCP configuration (extends ConfigError)
- MCPConnectionError: Server connection failures (extends ToolError)
- MCPProtocolError: Protocol-level errors (extends ToolError)
- MCPTimeoutError: Request timeout (extends MCPConnectionError)
8. Async Context Management¶
Decision: Use async context managers for MCP server lifecycle
Rationale:
- SK plugins already implement __aenter__/__aexit__
- Ensures proper cleanup on agent shutdown
- Supports multiple concurrent MCP connections
Pattern:
async with mcp_plugin:
result = await mcp_plugin.call_tool(tool_name, **kwargs)
9. Concurrent Call Handling¶
Decision: Allow parallel calls; server handles concurrency
Rationale: - Per clarification session decision - MCP servers designed to handle concurrent requests - No client-side queuing needed - Simplifies implementation
10. Default Timeout Configuration¶
Decision: 60 seconds default request_timeout
Rationale:
- Per clarification session decision
- Generous timeout for complex operations (file processing, database queries)
- Configurable per-tool via request_timeout field
Technology Best Practices¶
Semantic Kernel MCP Best Practices¶
- Plugin Lifecycle: Always use async context manager
- Tool Discovery: Set
load_tools=True(default), callconnect()to discover - Prompt Discovery: Set
load_prompts=Trueif needed - Kernel Reference: Pass kernel for sampling callback support
- Logging: Set log level matching Python logger configuration
YAML Configuration Best Practices¶
- Transport Selection: Always explicit, default to stdio
- Environment Variables: Use
${VAR}for process env vars - Timeouts: Configure realistic timeouts based on expected operation duration
- Server References: Use npm package names for stdio (e.g.,
@modelcontextprotocol/server-filesystem)
DRY Compliance: Reuse Existing Code¶
Existing Code to Reuse¶
| Module | Function | Purpose | MCP Usage |
|---|---|---|---|
holodeck.config.env_loader |
substitute_env_vars(text) |
${VAR} substitution with fail-fast |
Resolve env vars in MCP config |
holodeck.config.env_loader |
load_env_file(path) |
Load .env file to dict | Handle env_file config option |
holodeck.config.env_loader |
get_env_var(key, default) |
Get single env var | Optional fallback lookups |
holodeck.lib.errors |
ConfigError |
Configuration errors | Base for MCPConfigError |
holodeck.lib.errors |
HoloDeckError |
Base exception | Base for MCPError |
holodeck.lib.errors |
ExecutionError |
Runtime execution errors | Pattern for MCP runtime errors |
Code NOT to Duplicate¶
- ❌ Do NOT create new regex for
${VAR}pattern - usesubstitute_env_vars() - ❌ Do NOT create new .env parser - use
load_env_file() - ❌ Do NOT create new error base classes - extend existing
ConfigError/ToolError
New Code Required¶
| Module | Function/Class | Purpose |
|---|---|---|
holodeck.tools.mcp.errors |
MCPConfigError(ConfigError) |
MCP-specific config errors |
holodeck.tools.mcp.errors |
MCPError(HoloDeckError) |
MCP runtime errors base |
holodeck.tools.mcp.errors |
MCPConnectionError(MCPError) |
Server connection failures |
holodeck.tools.mcp.errors |
MCPTimeoutError(MCPConnectionError) |
Timeout errors |
holodeck.tools.mcp.errors |
MCPProtocolError(MCPError) |
Protocol-level errors |
holodeck.tools.mcp.factory |
create_mcp_plugin(config) |
Factory for SK plugins |
Note: Do NOT create UnresolvedEnvVarError - let substitute_env_vars() raise
its existing ConfigError and propagate it unchanged.
Dependencies Analysis¶
Required (Already Installed)¶
semantic-kernel>=1.37.1- MCP plugin implementationspydantic>=2.0.0- Configuration modelspython-dotenv>=1.0.0- Environment file loading
Transitive (via semantic-kernel)¶
mcp- MCP protocol SDKhttpx/httpx-sse- HTTP/SSE transportswebsockets- WebSocket transport
No Additional Dependencies Required¶
All MCP functionality available through existing semantic-kernel dependency.
Risks and Mitigations¶
| Risk | Likelihood | Impact | Mitigation |
|---|---|---|---|
| SK MCP API changes | Low | Medium | Pin SK version, test on upgrades |
| MCP server compatibility | Medium | Low | Test with official MCP servers |
| Async complexity | Medium | Medium | Comprehensive async tests |
| Environment variable leaks | Low | High | Fail-fast validation, no logging of values |