Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Nov 5, 2025

Description

Implements a Model Context Protocol (MCP) server that exposes PowerShell with the Dataverse module pre-loaded, enabling AI assistants to execute PowerShell scripts against Dataverse environments with configurable security controls and persistent session support. Packaged as a .NET global tool and published to NuGet.org via CI/CD.

Supports both STDIO (default) and HTTP transports for flexible deployment scenarios. HTTP mode uses the official ModelContextProtocol.AspNetCore package for proper MCP protocol implementation.

Changes Made

New Project: Rnwood.Dataverse.Data.PowerShell.McpServer (.NET 8.0)

  • Uses ModelContextProtocol SDK v0.4.0-preview.3 for MCP protocol implementation
  • Uses ModelContextProtocol.AspNetCore v0.4.0-preview.3 for HTTP transport
  • Hosts PowerShell via Microsoft.PowerShell.SDK v7.4.6 in persistent runspaces
  • Supports STDIO mode (default) for direct process invocation
  • Supports HTTP mode (--http flag) using official ModelContextProtocol.AspNetCore package
  • Defaults to restricted language mode for security (disable with --unrestricted-mode)
  • Disables providers by default (FileSystem, Registry, etc.) - enable with --enable-providers
  • Pre-loads only the Dataverse module
  • Requires a saved named connection for startup
  • Sets connection as default with -SetAsDefault flag for seamless cmdlet usage
  • Validates connection availability with helpful error messages
  • Uses System.CommandLine for proper command-line argument parsing
  • Packaged as .NET global tool with module bundled
  • Published to NuGet.org via CI/CD on releases and main branch builds

MCP Tools

  • GetCmdletList: Returns all available Dataverse cmdlets with their synopsis for discovery
  • GetCmdletHelp: Returns detailed help content (description, parameters, examples) for specific cmdlets
  • CreateSession: Creates a new persistent PowerShell session with pre-loaded module and connection
  • RunScriptInSession: Executes PowerShell scripts in an existing session (variables and state persist)
  • GetScriptOutput: Retrieves output from script execution with optional incremental mode
  • EndSession: Closes and cleans up a persistent session

PowerShellExecutor Service

  • Manages persistent PowerShell sessions with thread-safe ConcurrentDictionary
  • Validates named connection on startup (via CLI arg or DATAVERSE_CONNECTION_NAME env var)
  • Pre-loads default connection as $connection variable in all sessions with -SetAsDefault flag
  • Provides cmdlet discovery and help retrieval capabilities
  • Captures all output streams (stdout, stderr, warnings, verbose) using event handlers with indexed access
  • Auto-discovers module path across Debug/Release builds, packaged tool location, or via DATAVERSE_MODULE_PATH env var
  • Tracks individual script executions, not whole runspace completion
  • Async execution with proper resource disposal

Security Configuration

  • Restricted Language Mode (default): Prevents access to .NET types and methods
  • Providers Disabled (default): No FileSystem, Registry, or other provider access
  • Command-line flags for relaxing restrictions in trusted environments:
    • --unrestricted-mode / -u: Disable language restrictions
    • --enable-providers / -p: Enable all PowerShell providers

Command-Line Interface

  • --connection / -c: Specify saved connection name
  • --http: Enable HTTP mode (default: STDIO)
  • --urls: Specify HTTP binding URLs (supports ASPNETCORE_URLS env var)
  • --unrestricted-mode / -u: Disable restricted language mode
  • --enable-providers / -p: Enable PowerShell providers
  • --help: Display usage information

.NET Global Tool Configuration

  • Package ID: Rnwood.Dataverse.Data.PowerShell.McpServer
  • Tool command: rnwood-dataverse-mcp
  • Module files bundled at tools/net8.0/any/module/ in package
  • Install: dotnet tool install --global Rnwood.Dataverse.Data.PowerShell.McpServer
  • Update: dotnet tool update --global Rnwood.Dataverse.Data.PowerShell.McpServer

CI/CD Pipeline

  • Added build, pack, and publish steps to .github/workflows/publish.yml
  • Builds and packages MCP server on publish matrix runs
  • Publishes to NuGet.org:
    • Stable releases (from GitHub releases)
    • CI builds from main branch (as prereleases)
  • Version automatically determined from tags or conventional commits

E2E Testing

  • Added comprehensive e2e tests in separate files:
    • McpServer.Tests.ps1 - STDIO mode test
    • McpServer-Http.Tests.ps1 - HTTP mode test
  • Tests validate complete MCP server workflow:
    • Creates and saves a test connection
    • Launches MCP server process (STDIO or HTTP)
    • Tests MCP protocol initialization
    • Creates a session and runs scripts (Get-DataverseWhoAmI)
    • Monitors script execution and retrieves output
    • Validates output contains expected data
    • Ends session and cleans up
  • Tests run in parallel with proper isolation (each has own BeforeAll)
  • Integrates with existing e2e test infrastructure

Setup Requirements

  • Must save a named connection before starting the server:
    Get-DataverseConnection -Url https://myorg.crm.dynamics.com -Interactive -Name "MyConnection" -SetAsDefault
  • Server fails with helpful instructions if connection not found

Usage Examples

Install as global tool (recommended):

dotnet tool install --global Rnwood.Dataverse.Data.PowerShell.McpServer

STDIO Mode (default):

# Run in STDIO mode
rnwood-dataverse-mcp --connection MyConnection

# With unrestricted mode and providers
rnwood-dataverse-mcp -c MyConnection --unrestricted-mode --enable-providers

Configure in Claude Desktop claude_desktop_config.json for STDIO mode:

{
  "mcpServers": {
    "dataverse-powershell": {
      "command": "rnwood-dataverse-mcp",
      "args": ["--connection", "MyConnection"]
    }
  }
}

HTTP Mode:

# Run in HTTP mode (default port 5000)
rnwood-dataverse-mcp --connection MyConnection --http

# HTTP mode with custom port
rnwood-dataverse-mcp --connection MyConnection --http --urls "http://localhost:8080"

# Using environment variable
ASPNETCORE_URLS="http://localhost:8080" rnwood-dataverse-mcp -c MyConnection --http

HTTP mode uses the official ModelContextProtocol.AspNetCore package and is suitable for:

  • Web service deployment
  • Multiple concurrent clients
  • Load balancing scenarios
  • Network-based access with appropriate authentication

Persistent Session Workflow:

# 1. Create persistent session
CreateSession

# 2. Run multiple scripts in same session (state persists)
RunScriptInSession -sessionId "abc..." -script '$accounts = Get-DataverseRecord -Connection $connection -TableName account -Top 10'
GetScriptOutput -sessionId "abc..." -scriptExecutionId "xyz..."

RunScriptInSession -sessionId "abc..." -script '$accounts | Select-Object name, accountnumber'
GetScriptOutput -sessionId "abc..." -scriptExecutionId "def..."

# 3. End session when done
EndSession -sessionId "abc..."

Testing

  • ✅ Build succeeds without errors
  • ✅ All code review feedback addressed
  • ✅ Connection validation logic tested
  • ✅ Cmdlet discovery functionality verified
  • ✅ Command-line help displays correctly
  • ✅ Security modes configurable via CLI flags
  • ✅ Persistent session management implemented
  • ✅ Package creation and module bundling verified
  • ✅ Module path resolution works in packaged tool
  • ✅ Tool command name verified in package
  • ✅ E2E tests pass for both STDIO and HTTP modes with real connection
  • ✅ Connection set as default in sessions validated
  • ✅ HTTP mode with official ModelContextProtocol.AspNetCore package tested and validated

Checklist

  • Code follows the project's coding conventions
  • Documentation updated (README with setup instructions, security options, persistent sessions, HTTP/STDIO modes, and global tool installation)
  • Tests added/updated (E2E tests for both STDIO and HTTP modes)
  • PR title uses conventional commit format (REQUIRED)
Original prompt

Create a STDIO mcp provider (.net8 only) using https://github.com/modelcontextprotocol/csharp-sdk

It should host powershell using the PowerShell SDK and allow the caller to execute arbitrary PowerShell with the module (and no others) pre-loaded. The file system provider, registry provider etc should all be disabled.

The provider should allow callers to start a script (string), return an ID that represents it and expose another action to get the full or just new output since last call. Follow existing patterns in other similar tools like the ones you have access to.


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Copilot AI and others added 4 commits November 5, 2025 18:26
Co-authored-by: rnwood <1327895+rnwood@users.noreply.github.com>
Co-authored-by: rnwood <1327895+rnwood@users.noreply.github.com>
Co-authored-by: rnwood <1327895+rnwood@users.noreply.github.com>
Co-authored-by: rnwood <1327895+rnwood@users.noreply.github.com>
Copilot AI changed the title [WIP] Create STDIO MCP provider using .NET 8 and PowerShell SDK feat: add MCP server for PowerShell execution with Dataverse module Nov 5, 2025
Copilot AI requested a review from rnwood November 5, 2025 18:42
Copilot finished work on behalf of rnwood November 5, 2025 18:42
@rnwood
Copy link
Owner

rnwood commented Nov 5, 2025

@copilot Add actions to get list of cmdlets with a summary of what they do (from help) and another to return the full help content for a cmdlet.

The default connection should be set as part of the MCP startup referencing a named connection that Get-DataverseConnection has saved. If no name is provided startup should fail with good instructions on how to save the required connection.

- Add GetCmdletList tool to list all cmdlets with synopsis
- Add GetCmdletHelp tool to get detailed help for specific cmdlets
- Add connection name parameter (CLI arg or env var DATAVERSE_CONNECTION_NAME)
- Validate connection on startup with helpful error messages
- Pre-load $connection variable in script sessions
- Update README with setup instructions and new tool documentation

Co-authored-by: rnwood <1327895+rnwood@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Nov 5, 2025

@copilot Add actions to get list of cmdlets with a summary of what they do (from help) and another to return the full help content for a cmdlet.

The default connection should be set as part of the ...

Implemented in commit 2d55d70:

New MCP Tools:

  • GetCmdletList - Returns all Dataverse cmdlets with their synopsis
  • GetCmdletHelp - Returns full help content (description, parameters, examples) for any cmdlet

Default Connection:

  • Connection name now required via CLI argument or DATAVERSE_CONNECTION_NAME env var
  • Server validates connection on startup and fails with helpful instructions if not found
  • $connection variable automatically pre-loaded in all script sessions

Example usage:

dotnet run --project McpServer.csproj MyConnection

Or with environment variable:

export DATAVERSE_CONNECTION_NAME=MyConnection
dotnet run --project McpServer.csproj

Copilot finished work on behalf of rnwood November 5, 2025 19:08
@rnwood
Copy link
Owner

rnwood commented Nov 5, 2025

@copilot default to PowerShell restricted language mode, but provide a cmd line option to disable it. Also allow the filesystem provider and other restrictions to be turned off.

Also, it would be good to allow a session to be started and then for multiple scripts to be run in the same session one after the other if needed. An end session would be good too. So run script will need to track if the supplied script has exited, not the whole runspace.

@rnwood
Copy link
Owner

rnwood commented Nov 5, 2025

@copilot set up CI so that the MCP provider is packaged and pushed as .net global tool. It should bundle the module with it.

…strictions

- Add System.CommandLine for proper command-line parsing
- Implement persistent sessions with CreateSession/RunScriptInSession/EndSession
- Add --unrestricted-mode flag to disable restricted language mode (default: restricted)
- Add --enable-providers flag to enable FileSystem/Registry providers (default: disabled)
- Track individual script executions, not whole runspace completion
- Scripts in same session share variables and state
- Update README with new session model and security options
- Maintain backward compatibility with session-based execution

Co-authored-by: rnwood <1327895+rnwood@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Nov 5, 2025

@copilot default to PowerShell restricted language mode, but provide a cmd line option to disable it. Also allow the filesystem provider and other restrictions to be turned off.

Also, it would be goo...

Implemented in commit 16ba421:

PowerShell Restricted Language Mode

  • Now defaults to restricted language mode for security
  • Use --unrestricted-mode or -u to disable restrictions

Provider Control

  • Providers (FileSystem, Registry, etc.) now disabled by default
  • Use --enable-providers or -p to enable them

Persistent Sessions

  • New CreateSession tool creates a persistent runspace
  • RunScriptInSession executes multiple scripts in same session
  • Variables and state persist between scripts
  • EndSession cleans up resources
  • Tracks individual script completion, not runspace completion

Command-Line Library

  • Added System.CommandLine for proper argument parsing
  • Full --help support with descriptions
  • Options: -c/--connection, -u/--unrestricted-mode, -p/--enable-providers

Example usage:

# Restricted mode (default)
dotnet run -- -c MyConnection

# Full access
dotnet run -- -c MyConnection --unrestricted-mode --enable-providers

Copilot AI changed the title feat: add MCP server for PowerShell execution with Dataverse module feat: add MCP server with persistent sessions and configurable security Nov 5, 2025
Copilot finished work on behalf of rnwood November 5, 2025 22:15
- Configure project as .NET global tool (command: dataverse-mcp)
- Bundle Dataverse PowerShell module in package at tools/net8.0/any/module/
- Update module path resolution to check packaged location first
- Add CI workflow steps to build, package and publish to NuGet.org
- Publish on stable releases and CI builds from main branch
- Update README with installation and usage instructions for global tool
- Update Claude Desktop configuration examples

Co-authored-by: rnwood <1327895+rnwood@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Nov 5, 2025

@copilot set up CI so that the MCP provider is packaged and pushed as .net global tool. It should bundle the module with it.

Implemented in commit ee0d35d:

. NET Global Tool Configuration

  • Configured as packaged tool with command name dataverse-mcp
  • Module files bundled in package at tools/net8.0/any/module/
  • Install with: dotnet tool install --global Rnwood.Dataverse.Data.PowerShell.McpServer

CI/CD Workflow

  • Added build, pack and publish steps to .github/workflows/publish.yml
  • Builds and packages on all publish matrix runs
  • Publishes to NuGet.org on:
    • Stable releases (from GitHub releases)
    • CI builds from main branch (as prereleases)
  • Version automatically determined from tags or conventional commits

Usage

# Install
dotnet tool install --global Rnwood.Dataverse.Data.PowerShell.McpServer

# Run
dataverse-mcp --connection MyConnection

# Update
dotnet tool update --global Rnwood.Dataverse.Data.PowerShell.McpServer

Module is automatically included in the tool package - no separate installation needed.

Copilot AI changed the title feat: add MCP server with persistent sessions and configurable security feat: add MCP server as .NET global tool with persistent sessions and configurable security Nov 5, 2025
Copilot finished work on behalf of rnwood November 5, 2025 22:24
@rnwood
Copy link
Owner

rnwood commented Nov 6, 2025

@copilot prefix the dotnet tool name with rnwood-

Change tool command from 'dataverse-mcp' to 'rnwood-dataverse-mcp' for better namespacing and to avoid potential conflicts with other tools.

Co-authored-by: rnwood <1327895+rnwood@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Nov 6, 2025

@copilot prefix the dotnet tool name with rnwood-

Updated in commit 3d27407:

Tool command name changed from dataverse-mcp to rnwood-dataverse-mcp.

Installation:

dotnet tool install --global Rnwood.Dataverse.Data.PowerShell.McpServer

Usage:

rnwood-dataverse-mcp --connection MyConnection

Updated README and Claude Desktop configuration examples.

Copilot finished work on behalf of rnwood November 6, 2025 05:47
@rnwood
Copy link
Owner

rnwood commented Nov 6, 2025

@copilot in the MCP session, the named connection should be -setasdefault

add an e2e test for the MCP server as part of the existing e2e tests. It should create and save a connectioj, launch the mcp server and test that it can run and monitor a script that uses the connection

- Add -SetAsDefault flag when loading named connection in MCP sessions
- Add comprehensive e2e test for MCP server that:
  - Creates and saves a test connection
  - Launches the MCP server process
  - Tests MCP protocol initialization
  - Creates a session
  - Runs a script (Get-DataverseWhoAmI)
  - Monitors script execution and retrieves output
  - Ends the session and cleans up
- Test validates full MCP server workflow with real connection

Co-authored-by: rnwood <1327895+rnwood@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Nov 6, 2025

@copilot in the MCP session, the named connection should be -setasdefault

add an e2e test for the MCP server as part of the existing e2e tests. It should create and save a connectioj, launch the mcp...

Implemented in commit 9c36b68:

Connection SetAsDefault:

  • Added -SetAsDefault flag when loading named connection in MCP sessions
  • Connection is now set as the default connection for seamless cmdlet usage

E2E Test:

  • Added comprehensive e2e test in e2e-tests/Module.Tests.ps1
  • Test workflow:
    1. Creates and saves a test connection with unique name
    2. Launches MCP server process with the connection
    3. Tests MCP protocol initialization (JSON-RPC)
    4. Creates a persistent session
    5. Runs a script (Get-DataverseWhoAmI)
    6. Monitors script execution and retrieves output
    7. Validates output contains expected data
    8. Ends session and cleans up process
  • Test validates full MCP server workflow with real Dataverse connection
  • Runs as part of existing e2e test suite

Copilot finished work on behalf of rnwood November 6, 2025 06:13
@rnwood
Copy link
Owner

rnwood commented Nov 6, 2025

@copilot please split the test into another file and make sure the tests run all in parallel like the main non e2e tests

- Create new McpServer.Tests.ps1 in e2e-tests directory
- Move MCP server test from Module.Tests.ps1 to McpServer.Tests.ps1
- Each test file has its own BeforeAll setup for isolation
- Tests can now run in parallel like other test files
- No functional changes to test logic

Co-authored-by: rnwood <1327895+rnwood@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Nov 6, 2025

@copilot please split the test into another file and make sure the tests run all in parallel like the main non e2e tests

Implemented in commit b7839c1:

Test File Split:

  • Created new e2e-tests/McpServer.Tests.ps1 for MCP server tests
  • Removed MCP test from Module.Tests.ps1
  • Each file has independent BeforeAll setup for proper isolation

Parallel Execution:

  • Tests now run in parallel like main non-e2e tests
  • Each test file sets up its own temp module folder
  • No shared state between test files
  • Test structure matches existing e2e test patterns

Copilot finished work on behalf of rnwood November 6, 2025 06:45
@rnwood
Copy link
Owner

rnwood commented Nov 6, 2025

@copilot Add HTTP mode triggered by a switch. It should use the standard ASPNET env vars and CMD line args for the bindings. Add another e2e test for this.

- Add --http flag to run server in HTTP mode instead of STDIO
- HTTP mode uses ASP.NET Core with JSON-RPC endpoints at /mcp
- Supports standard ASP.NET environment variables (ASPNETCORE_URLS, etc.)
- Add Microsoft.AspNetCore.App package reference
- Implement JSON-RPC request handler for HTTP mode
- Update README with HTTP mode documentation and examples
- Add comprehensive e2e test for HTTP mode (McpServer-Http.Tests.ps1)
- Test validates full HTTP workflow including initialize, session creation, script execution, and cleanup

Co-authored-by: rnwood <1327895+rnwood@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Nov 6, 2025

@copilot Add HTTP mode triggered by a switch. It should use the standard ASPNET env vars and CMD line args for the bindings. Add another e2e test for this.

Implemented in commit a94b7b7:

HTTP Mode:

  • Added --http flag (alias -h) to run server in HTTP mode
  • Uses ASP.NET Core with JSON-RPC endpoint at /mcp
  • Supports standard ASP.NET environment variables:
    • ASPNETCORE_URLS or --urls for binding addresses
    • ASPNETCORE_ENVIRONMENT for environment settings
  • Default binding: http://localhost:5000

E2E Test:

  • Created McpServer-Http.Tests.ps1 for HTTP mode testing
  • Test workflow:
    1. Starts server on random port (5000-6000)
    2. Tests JSON-RPC initialization via HTTP POST
    3. Creates session, runs script, retrieves output
    4. Validates full HTTP workflow
    5. Cleans up process
  • Runs in parallel with other e2e tests

Usage Examples:

# Default HTTP mode
rnwood-dataverse-mcp --connection MyConnection --http

# Custom port
rnwood-dataverse-mcp --connection MyConnection --http --urls "http://localhost:8080"

# Environment variable
ASPNETCORE_URLS="http://0.0.0.0:5000" rnwood-dataverse-mcp --connection MyConnection --http

Documentation updated in README with HTTP mode section.

Copilot AI changed the title feat: add MCP server as .NET global tool with persistent sessions and configurable security feat: add MCP server as .NET global tool with STDIO and HTTP transports Nov 6, 2025
Copilot finished work on behalf of rnwood November 6, 2025 07:22
@rnwood
Copy link
Owner

rnwood commented Nov 6, 2025

@copilot Try again after reading this:
@modelcontextprotocol/csharp-sdk/files/src/ModelContextProtocol.AspNetCore/README.md

- Add ModelContextProtocol.AspNetCore package (v0.4.0-preview.3)
- Remove Microsoft.AspNetCore.App reference (provided by AspNetCore package)
- Simplify HTTP mode to use AddMcpServer().WithHttpTransport() and MapMcp()
- Remove manual JSON-RPC request handling code
- Update README to mention official ASP.NET Core package
- HTTP endpoints now managed by ModelContextProtocol framework

Co-authored-by: rnwood <1327895+rnwood@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Nov 6, 2025

@copilot Try again after reading this:
@modelcontextprotocol/csharp-sdk/files/src/ModelContextProtocol.AspNetCore/README.md

Refactored in commit 8acc736:

HTTP Mode Now Uses Official Package:

  • Added ModelContextProtocol.AspNetCore package (v0.4.0-preview.3)
  • HTTP mode now uses AddMcpServer().WithHttpTransport() and MapMcp()
  • Removed manual JSON-RPC request handling (80+ lines of code eliminated)
  • Framework automatically handles MCP protocol over HTTP
  • Cleaner, more maintainable implementation

Implementation:

builder.Services.AddMcpServer()
    .WithHttpTransport()
    .WithTools<PowerShellTools>();

var app = builder.Build();
app.MapMcp();

The ModelContextProtocol.AspNetCore package handles all protocol details, endpoint routing, and JSON-RPC communication automatically.

Copilot finished work on behalf of rnwood November 6, 2025 07:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants