McpActions.Core
0.2.0
dotnet add package McpActions.Core --version 0.2.0
NuGet\Install-Package McpActions.Core -Version 0.2.0
<PackageReference Include="McpActions.Core" Version="0.2.0" />
<PackageVersion Include="McpActions.Core" Version="0.2.0" />
<PackageReference Include="McpActions.Core" />
paket add McpActions.Core --version 0.2.0
#r "nuget: McpActions.Core, 0.2.0"
#:package McpActions.Core@0.2.0
#addin nuget:?package=McpActions.Core&version=0.2.0
#tool nuget:?package=McpActions.Core&version=0.2.0
MCP Toolbox for Actions
MCP Toolbox for Actions (mcp-actions) turns configured actions into MCP tools. Users compose their own toolbox with mcp-actions.yml, then run mcp-actions.exe -c mcp-actions.yml to serve MCP over stdio.
The project separates low-level providers from product plugins:
- HTTP requests
- CLI commands (can invoke PowerShell / Python / Node without a shell wrapper)
- Built-in script providers for PowerShell (
pwsh), Node.js (node), and Python (python) - Providers such as SQLite (shipped as a NuGet package, loaded dynamically)
- Product plugins such as PocketBase, GitHub, SQL Server, Kubernetes (shipped as NuGet packages, loaded dynamically)
See REGISTRY.md for the list of available providers and plugins and instructions on how to register new ones. See docs/architecture.md for the runtime contract (external ActionRequest/ActionResponse and internal InternalRequest/InternalResponse protocols, Primitive type set, broker model, and SQLite row/column result conventions).
mcp-actions.exe is a framework-dependent single-file executable. Providers and plugins are .nupkg packages loaded at runtime via AssemblyLoadContext, so AOT is intentionally not used.
Project layout
src/
core/
McpActions.Core/ Shared contracts (IActionProvider / IActionPlugin) + discovery attributes
runtime/
McpActions/ Framework-dependent single-file console app
McpActions.Runtime/ Runtime: config, template, dispatcher, package loader, MCP bridge
McpActions.Provider.Http/ Built-in HTTP provider (compiled into host)
McpActions.Provider.Cli/ Built-in local process provider (compiled into host)
providers/
McpActions.Provider.Sqlite/ SQLite provider; packs a nupkg
plugins/
McpActions.Plugin.PocketBase/ PocketBase plugin; packs a nupkg
examples/
mcp-actions.yml
pocketbase-mcp-actions.yml
pwsh-mcp-actions.yml
test/
McpActions.Tests/ In-process unit tests
McpActions.IntegrationTests/ End-to-end tests that drive dist/mcp-actions[.exe]
Built-in providers (http, cli, pwsh, node, python) are compiled into the host. Any other provider or plugin is a .nupkg dropped into dist/providers/ or dist/plugins/.
Commands
dotnet build .\McpActions.slnx
dotnet pack .\McpActions.slnx -c Release
dotnet publish .\src\runtime\McpActions -c Release -r win-x64 --self-contained false -p:PublishSingleFile=true
dotnet run --project .\src\runtime\McpActions -- --help
dotnet run --project .\src\runtime\McpActions -- -c .\examples\mcp-actions.yml
dotnet run --project .\src\runtime\McpActions -- -c .\examples\pocketbase-mcp-actions.yml
Debugging with MCP Inspector
Run the command from the dist directory:
npx @modelcontextprotocol/inspector ./mcp-actions.exe -c ./examples/pocketbase-mcp-actions.yml
On Windows, use / in paths passed through Inspector. Inspector treats \ as an argument escape character, so a value such as .\examples\pocketbase-mcp-actions.yml is parsed incorrectly.
Testing
Tests live under test/ and are split into two layers:
test/McpActions.Tests— in-process unit tests for the runtime (TemplateEngine,YamlConfigLoader,PackageRef,InputSchemaBuilder, …).test/McpActions.IntegrationTests— end-to-end tests that spawn the packagedmcp-actionsexecutable and drive it as an MCP client over stdio against every YAML file inexamples/.
Both layers use xUnit and FluentAssertions. Integration tests read from dist/, so build it first:
./tools/build.ps1 -Clean
dotnet test ./McpActions.slnx
To run just one layer:
dotnet test ./test/McpActions.Tests
dotnet test ./test/McpActions.IntegrationTests
Dist layout
dist/
mcp-actions.exe
providers/
McpActions.Provider.Sqlite.0.1.0.nupkg
plugins/
McpActions.Plugin.PocketBase.0.1.0.nupkg
examples/
mcp-actions.yml
pocketbase-mcp-actions.yml
Provider and plugin loading
Package entry-point types are marked with attributes so the loader can discover them without full assembly scans:
[McpActionsProvider(Name = "sqlite")]
public sealed class SqliteProvider : IActionProvider { ... }
[McpActionsPlugin(Name = "pocketbase", RequiredProviders = new[] { "sqlite" })]
public sealed class PocketBasePlugin : IActionPlugin { ... }
Each package is loaded into its own AssemblyLoadContext named {id}@{version} (via PackageLoader in McpActions.Runtime). Because contexts are keyed on both id and version, two configurations can pull different versions of the same package side by side without conflict. The contract assembly McpActions.Core is pinned to the host's default load context so plugins and the host see the same Type identities for IActionProvider, IActionPlugin, etc.
Package extraction rejects paths outside the package cache directory. Package providers may include managed and native runtime dependencies in the nupkg; the SQLite package is self-contained this way.
Package references
Third-party providers and plugins are declared with a pinned {Id}@{Version} reference:
providers:
- name: sqlite
package: McpActions.Provider.Sqlite@0.1.0
plugins:
- name: pocketbase
package: McpActions.Plugin.PocketBase@0.1.0
providers: [sqlite]
Built-in providers (http, cli, pwsh, node, python) ship with the host and never carry a package: field. They also do not need to be declared under providers: — the runtime auto-registers any built-in provider referenced by an action. Only add an explicit providers: entry for a built-in when you need to pass it per-instance config (currently unused by the shipped built-ins).
Package resolution order
For each {id}@{version}, PackageLoader looks for {id}.{version}.nupkg in this order:
<config-dir>/providers/or<config-dir>/plugins/<exe-dir>/providers/or<exe-dir>/plugins/- Local user cache
%LocalAppData%\mcp-actions\packages\{id}\{version}\{id}.{version}.nupkg— only whennuget.commandis configured - Invoke
<nuget-command> install {id} -Version {version} -OutputDirectory <cache>— only whennuget.commandis configured
Steps 3 and 4 are gated on the nuget.command value in the yml:
nuget:
command: nuget # or an absolute path like C:\tools\nuget.exe
Use an absolute path when the CLI is not on PATH. The command is invoked with install <id> -Version <version> -OutputDirectory <cache> -DirectDownload -PackageSaveMode nupkg -DependencyVersion Ignore -NonInteractive; feed selection, credentials, and proxy settings are inherited from the user's nuget.config.
Configuration
Templates support tool arguments, variables, and environment variables:
{{name}} MCP tool call parameter (bare, no namespace)
{{vars.name}} value from the top-level vars mapping
{{envs.NAME}} environment variable
If a complete YAML scalar is one template expression, its resolved type is preserved. Templates embedded in longer strings are rendered as text.
Relative database paths and CLI working directories are resolved from the directory containing mcp-actions.yml.
Action anatomy
Each action's YAML has two halves that serve different purposes:
| Section | Fields | Role |
|---|---|---|
| ① MCP tool facet (outward) | name / description / parameters |
Populates the tool metadata returned by tools/list — the name, description, and inputSchema that MCP clients see. |
| ② Execution facet (inward) | provider + its execution fields (script / scriptFile / args / env / command / request / sql / mode / timeoutSeconds / ...) |
Describes how the tool actually runs when a tools/call arrives — spawn a process, make an HTTP request, run a SQLite query, etc. |
Templates are the bridge: {{name}} binds a parameter declared in ① into a concrete position of the execution fields in ②.
- name: download_desktop_images_to_target_dry_run # ① tool name
description: Preview copying... # ① tool description
parameters: # ① tool inputSchema
targetPath:
type: string
required: true
description: Target folder path.
# ---------- boundary ----------
provider: pwsh # ② which provider
scriptFile: '{{vars.toolsDir}}\...ps1' # ② execution detail
args: ["-TargetPath", "{{targetPath}}", "-DryRun"] # ② + template bridge
timeoutSeconds: 30 # ② execution detail
Parameters
Each action declares its MCP tool inputs under parameters:. Two shapes are accepted:
parameters:
# Shorthand: just the JSON schema type. Implies required=true.
limit: integer
# Full form: any JSON schema keywords plus the extra `required` flag.
targetPath:
type: string
required: true # optional, defaults to true
description: Target folder path.
Any extra keys (description, enum, minimum, pattern, ...) pass through verbatim into the tool's inputSchema.
HTTP
- name: get_issue
provider: http
request:
method: GET
url: "https://api.github.com/repos/{{owner}}/{{repo}}/issues/{{number}}"
timeoutSeconds: 30
headers:
Accept: application/vnd.github+json
response:
parse: json # json | text | binary
CLI
command.args is passed through ProcessStartInfo.ArgumentList; it is not concatenated into a shell command.
- name: dotnet_info
provider: cli
command:
file: dotnet
args: ["--info"]
workingDirectory: .
timeoutSeconds: 30
successExitCodes: [0]
env:
DOTNET_CLI_UI_LANGUAGE: en-US
Built-in script providers
The pwsh, node, and python providers run either inline script content or a script file. Use exactly one of:
script: inline script content.scriptFile: script path relative to the directory containingmcp-actions.yml, or an absolute path.
Invocation arguments are exposed to the script as environment variables:
MCP_ARGS_JSON: JSON object containing all tool arguments.MCP_ARG_<NAME>: one variable per argument, with the name upper-cased and non-alphanumeric characters replaced with_.
Common options are args, workingDirectory, env, timeoutSeconds, successExitCodes, and executable. args are appended after the generated script-file argument.
Default working directory (when workingDirectory: is not set):
scriptFile:— the directory of the script file (so the script can reference sibling files by relative path).- inline
script:— the directory of the yml file (the temp file's location is not meaningful).
- name: pwsh_echo
description: Echo text with PowerShell.
parameters:
text: string
provider: pwsh
script: |
Write-Output "PowerShell: $env:MCP_ARG_TEXT"
- name: node_echo
description: Echo text with Node.js.
parameters:
text: string
provider: node
script: |
console.log(`Node.js: ${process.env.MCP_ARG_TEXT}`);
- name: python_echo
description: Echo text with Python.
parameters:
text: string
provider: python
script: |
import os
print(f"Python: {os.environ['MCP_ARG_TEXT']}")
- name: run_existing_script
description: Run a script file with extra arguments.
provider: pwsh
scriptFile: ./scripts/report.ps1
args: ["--format", "json"]
timeoutSeconds: 60
SQLite
providers:
- name: sqlite
package: McpActions.Provider.Sqlite@0.1.0
actions:
- name: list_messages
description: List messages, optionally capped by a limit.
parameters:
limit: integer
provider: sqlite
database: ./messages.db
sql: SELECT * FROM messages LIMIT @limit
mode: query # query | scalar | nonQuery
SQLite binds tool arguments referenced as @name, :name, or $name. When mode is omitted, SELECT, WITH, and PRAGMA are treated as queries.
PocketBase
The PocketBase plugin requires the SQLite provider and implements listCollections, describeCollection, and listRecords. See examples/pocketbase-mcp-actions.yml.
Implemented
- YAML loading and structural validation
- Recursive template rendering with type preservation
- Dynamic nupkg loading in isolated
AssemblyLoadContextinstances - MCP stdio
tools/listandtools/call - HTTP and CLI built-in providers
- SQLite package provider
- PocketBase package plugin
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.