MCP Server Tutorial#

This tutorial walks you through setting up scafctl’s Model Context Protocol (MCP) server so that AI agents — GitHub Copilot, Claude, Cursor, Windsurf, and others — can discover and invoke scafctl tools programmatically.

Prerequisites#

  • scafctl installed and on your $PATH (scafctl version should work)
  • An MCP-compatible AI client (VS Code with Copilot, Claude Desktop, Cursor, or Windsurf)

What Is the MCP Server?#

The MCP server is a local process that translates between the AI agent’s JSON-RPC protocol and scafctl’s Go library functions. When an AI agent decides to use a tool (e.g., “lint this solution”), it sends a structured request to the MCP server, which calls the same code that the CLI commands use and returns structured JSON results.

Most tools exposed by the MCP server are read-only — they inspect, validate, evaluate, and list. A few tools (preview_resolvers, run_solution_tests, render_solution) execute solution code which may have side effects depending on the providers used (e.g., exec, http).

AI Agent (Copilot, Claude, etc.)
    │  JSON-RPC 2.0 over stdio
scafctl mcp serve
    │  Direct Go function calls
scafctl libraries (solution, provider, CEL, catalog, auth)

1. Getting Started#

Verify the MCP Server#

Run the --info flag to confirm the server is built and can list its tools:

scafctl mcp serve --info
scafctl mcp serve --info

You should see JSON output listing all available tools:

{
  "name": "scafctl",
  "version": "dev",
  "tools": [
    { "name": "auth_status", "description": "Report which auth handlers (e.g. entra, gcp, github) are configured..." },
    { "name": "catalog_inspect", "description": "Get detailed metadata for a specific catalog artifact..." },
    { "name": "catalog_list", "description": "List entries in the local catalog..." },
    { "name": "diff_solution", "description": "Compare two solution files structurally..." },
    { "name": "diff_snapshots", "description": "Diff two resolver snapshots..." },
    { "name": "evaluate_cel", "description": "Evaluate a CEL expression..." },
    { "name": "evaluate_go_template", "description": "Evaluate a Go template against provided data..." },
    { "name": "explain_concepts", "description": "Look up and explain scafctl concepts..." },
    { "name": "extract_resolver_refs", "description": "Find all resolver cross-references in a solution..." },
    { "name": "generate_test_scaffold", "description": "Generate functional test cases for a solution..." },
    { "name": "get_config_paths", "description": "List all XDG-compliant paths used by scafctl..." },
    { "name": "get_version", "description": "Return scafctl version, commit, and build time..." },
    { "name": "inspect_solution", "description": "Get full solution metadata..." },
    { "name": "lint_solution", "description": "Validate a solution file..." },
    { "name": "list_auth_handlers", "description": "List all registered auth handlers..." },
    { "name": "list_cel_functions", "description": "List all available CEL functions..." },
    { "name": "list_go_template_functions", "description": "List all available Go template extension functions..." },
    { "name": "list_examples", "description": "List available scafctl example files..." },
    { "name": "list_providers", "description": "List all available providers..." },
    { "name": "list_solutions", "description": "List available solutions from the local catalog..." },
    { "name": "list_tests", "description": "List functional tests defined in a solution..." },
    { "name": "render_solution", "description": "Render a solution's action graph..." },
    { "name": "show_snapshot", "description": "Show the contents of a resolver snapshot..." },
    { "name": "validate_expressions", "description": "Validate CEL expressions or Go templates..." }
  ]
}

If this works, the MCP server is ready.

Start the Server Manually (Optional)#

You can start the server interactively for testing:

scafctl mcp serve
scafctl mcp serve

The server listens on stdin/stdout for JSON-RPC 2.0 messages. Press Ctrl+C to stop. Normally, your AI client starts and stops the server automatically.

2. VS Code / GitHub Copilot Setup#

Create .vscode/mcp.json in your project root:

{
  "servers": {
    "scafctl": {
      "type": "stdio",
      "command": "scafctl",
      "args": ["mcp", "serve"]
    }
  }
}

Option B: User-Level Configuration#

Add to your VS Code settings.json (Cmd+Shift+P → “Preferences: Open User Settings (JSON)”):

{
  "mcp": {
    "servers": {
      "scafctl": {
        "type": "stdio",
        "command": "scafctl",
        "args": ["mcp", "serve"]
      }
    }
  }
}

Verify in VS Code#

  1. Reload the VS Code window (Cmd+Shift+P → “Developer: Reload Window”)
  2. Open the Copilot chat panel
  3. The scafctl tools should appear in the tool picker (click the tool icon in the chat input)
  4. Try asking Copilot: “List my available providers”

3. Claude Desktop Setup#

Add to your Claude Desktop configuration file:

  • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
  • Windows: %APPDATA%\Claude\claude_desktop_config.json
{
  "mcpServers": {
    "scafctl": {
      "command": "scafctl",
      "args": ["mcp", "serve"]
    }
  }
}

Restart Claude Desktop. The scafctl tools will appear in the tool menu (hammer icon).

4. Cursor Setup#

Create .cursor/mcp.json in your project root:

{
  "mcpServers": {
    "scafctl": {
      "command": "scafctl",
      "args": ["mcp", "serve"]
    }
  }
}

Restart Cursor. The tools will be available in the AI chat.

5. Windsurf Setup#

Create .windsurf/mcp.json in your project root:

{
  "mcpServers": {
    "scafctl": {
      "command": "scafctl",
      "args": ["mcp", "serve"]
    }
  }
}

Restart Windsurf to pick up the configuration.

6. Using the Tools#

Once connected, your AI agent can use scafctl tools through natural conversation. This section walks through each tool with a concrete example you can follow along with.

Setup: Create a Sample Solution#

Most examples below reference a solution file. Create mcp-demo/solution.yaml in your project:

mkdir -p mcp-demo

You can copy the pre-built example or create it from scratch:

# Option A: Copy the example file
cp examples/mcp/tutorial-solution.yaml mcp-demo/solution.yaml

# Option B: Create it manually (paste the YAML below)

Paste this into mcp-demo/solution.yaml:

apiVersion: scafctl.io/v1
kind: Solution

metadata:
  name: greeting-service
  version: 1.0.0
  description: A simple greeting service that demonstrates resolvers, validation, and actions.
  maintainers:
    - name: Tutorial User
      email: tutorial@example.com
  tags:
    - tutorial
    - demo

spec:
  resolvers:
    username:
      description: Name of the person to greet
      type: string
      resolve:
        with:
          - provider: parameter
            inputs:
              key: username
          - provider: static
            inputs:
              value: World
      validate:
        with:
          - provider: validation
            inputs:
              match: '^[A-Za-z ]{1,50}$'
            message: "Username must be 1-50 alphabetic characters"

    style:
      description: Greeting style
      type: string
      resolve:
        with:
          - provider: parameter
            inputs:
              key: style
          - provider: static
            inputs:
              value: friendly
      transform:
        with:
          - provider: cel
            inputs:
              expression: '__self.lowerAscii()'
      validate:
        with:
          - provider: validation
            inputs:
              expression: '__self in ["friendly", "formal", "casual"]'
            message: "Style must be friendly, formal, or casual"

    message:
      description: Composed greeting message
      type: string
      dependsOn:
        - username
        - style
      resolve:
        with:
          - provider: cel
            inputs:
              expression: |
                _.style == "formal" ? "Good day, " + _.username + "."
                : _.style == "casual" ? "Hey " + _.username + "!"
                : "Hello, " + _.username + "! Welcome!"

  workflow:
    actions:
      greet:
        description: Print the greeting to the console
        provider: exec
        inputs:
          command:
            expr: '"echo " + _.message'

      log:
        description: Log the greeting event
        provider: static
        dependsOn:
          - greet
        inputs:
          value:
            event: greeting_sent
            user:
              rslvr: username
            style:
              rslvr: style

List Available Providers#

No files needed — this tool lists the built-in providers.

You: “What solution providers are available?”

The AI calls list_providers and returns something like:

Here are the available solution providers:

| Name        | Category    | Capabilities       |
|-------------|-------------|--------------------|
| cel         | transform   | from, transform    |
| debug       | utility     | action             |
| directory   | data        | from               |
| env         | data        | from               |
| exec        | action      | from, action       |
| file        | data        | from               |
| git         | data        | from               |
| go-template | transform   | from, transform    |
| hcl         | data        | from, transform    |
| http        | data        | from, action       |
| identity    | auth        | from               |
| parameter   | data        | from               |
| secret      | data        | from               |
| sleep       | utility     | action             |
| static      | data        | from, action       |
| validation  | validation  | validation         |

16 providers available. Use `get_provider_schema` with a specific
provider name to see its full input schema and examples.

Inspect a Solution#

Uses the mcp-demo/solution.yaml file created above.

You: “Explain what the greeting-service solution does”

The AI calls inspect_solution with path: "mcp-demo/solution.yaml" and returns something like:

## greeting-service v1.0.0

**Description:** A simple greeting service that demonstrates resolvers,
validation, and actions.

**Maintainers:** Tutorial User <tutorial@example.com>
**Tags:** tutorial, demo

### Resolvers (3)

| Resolver | Type   | Description                  | Providers              |
|----------|--------|------------------------------|------------------------|
| username | string | Name of the person to greet  | parameter  static     |
| style    | string | Greeting style               | parameter  static     |
| message  | string | Composed greeting message    | cel                    |

- `message` depends on: username, style
- `username` validates via regex: `^[A-Za-z ]{1,50}$`
- `style` validates via CEL: `__self in ["friendly", "formal", "casual"]`

### Actions (2)

| Action | Provider | Description                      | Depends On |
|--------|----------|----------------------------------|------------|
| greet  | exec     | Print the greeting to the console|            |
| log    | static   | Log the greeting event           | greet      |

Validate a Solution#

Uses the mcp-demo/solution.yaml file created above.

You: “Validate my mcp-demo/solution.yaml file”

The AI calls lint_solution with file: "mcp-demo/solution.yaml" and returns something like:

✅ Validation passed — no errors or warnings found in mcp-demo/solution.yaml.

The solution "greeting-service" (v1.0.0) is well-formed with 3 resolvers
and 2 actions.

To see what lint findings look like, you can also try validating the intentionally broken example:

You: “Lint the file examples/solutions/bad-solution-yaml/solution.yaml”

The AI calls lint_solution and returns structured findings with severity, location, and descriptions for each issue.

Evaluate a CEL Expression#

No files needed — you provide the expression and data inline.

You: “Evaluate this CEL expression with sample data: _.items.filter(i, i.enabled).map(i, i.name)

Use this data: {"items": [{"name": "alpha", "enabled": true}, {"name": "beta", "enabled": false}, {"name": "gamma", "enabled": true}]}

The AI calls evaluate_cel with the expression and data, and returns something like:

Expression: _.items.filter(i, i.enabled).map(i, i.name)

Result: ["alpha", "gamma"]
Type:   list(string)

You can also test expressions used in your solution:

You: “Evaluate this: _.style == "formal" ? "Good day, " + _.username + "." : "Hello, " + _.username + "!" with data {"style": "formal", "username": "Alice"}

Result: "Good day, Alice."
Type:   string

Discover CEL Functions#

No files needed.

You: “What custom CEL functions can I use for string manipulation?”

The AI calls list_cel_functions with custom_only: true and returns something like:

Here are the custom scafctl CEL functions for string manipulation:

- **lowerAscii(string)  string**  Convert to lowercase
- **upperAscii(string)  string**  Convert to uppercase
- **trim(string)  string**  Remove leading/trailing whitespace
- **contains(string, string)  bool**  Check substring
- **replace(string, string, string)  string**  Replace occurrences
- **split(string, string)  list(string)**  Split by delimiter
- **join(list(string), string)  string**  Join with delimiter
...

Use `evaluate_cel` to test any of these interactively.

Discover Go Template Functions#

No files needed.

You: “What Go template functions are available for string manipulation?”

The AI calls list_go_template_functions and returns something like:

Here are some Go template functions for string manipulation:

**Sprig Functions:**
- **upper**  Convert to uppercase
- **lower**  Convert to lowercase
- **title**  Convert to title case
- **trim** / **trimAll** / **trimPrefix** / **trimSuffix**  Trim whitespace or characters
- **replace**  Replace occurrences of a substring
- **camelcase** / **snakecase** / **kebabcase**  Case conversions
- **contains** / **hasPrefix** / **hasSuffix**  String tests

**Custom Functions:**
- **toHcl**  Convert a Go object to HCL format
- **toYaml**  Encode a Go value as a YAML string
- **fromYaml**  Decode a YAML string into a map
- **mustToYaml**  Same as toYaml (errors propagate in Go templates)
- **mustFromYaml**  Same as fromYaml (errors propagate in Go templates)

Use `evaluate_go_template` to test any of these interactively.

You can also filter to specific categories:

You: “List only the custom Go template functions”

The AI calls list_go_template_functions with custom_only: true and returns the custom scafctl functions.

Browse the Catalog#

This tool queries your local catalog. If you haven’t added solutions to the catalog yet, the result will be empty.

You: “What solutions are in my local catalog?”

The AI calls catalog_list with kind: "solution" and returns something like:

Your local catalog contains 0 solutions.

To add solutions to the catalog, see the Catalog Tutorial.

Or, if you have entries:

Found 2 solutions in your local catalog:

| Name             | Version | Description                     |
|------------------|---------|---------------------------------|
| greeting-service | 1.0.0   | A simple greeting service       |
| deploy-gcp       | 2.1.0   | Deploy services to GCP          |

Check Authentication#

No files needed — this inspects your current auth state.

You: “Am I authenticated for any services?”

The AI calls auth_status and returns something like:

You have 2 authenticated auth handlers:

 gcp (Google Cloud Platform)
   Identity: user | Expires: May 18, 2026
   Flows: interactive, service_principal, workload_identity, metadata

 github (GitHub)
   Identity: user | Username: kcloutie
   Flows: device_code, pat

 entra (Microsoft Entra ID)
   Not authenticated
   Flows: device_code, service_principal, workload_identity

Run `scafctl auth login <handler>` to authenticate.

Render a Solution Graph#

Uses the mcp-demo/solution.yaml file created above.

You: “Show me the action graph for mcp-demo/solution.yaml”

The AI calls render_solution with path: "mcp-demo/solution.yaml" and graph_type: "action" and returns something like:

Action execution graph for greeting-service:

  greet
    └── log

Execution order:
  1. greet (exec) — Print the greeting to the console
  2. log (static) — Log the greeting event [depends on: greet]

You can also request the resolver dependency graph:

You: “Show me the resolver dependency graph for mcp-demo/solution.yaml”

Resolver dependency graph:

  username ──┐
             ├──► message
  style ─────┘

Resolution order:
  Phase 1: username, style (parallel — no dependencies)
  Phase 2: message (depends on: username, style)

Get Provider Schema#

No files needed — you specify the provider by name.

You: “What inputs does the exec provider accept?”

The AI calls get_provider_schema with name: "exec" and returns structured JSON with:

  • Input schema — each property has type, description, required (true/false), default, example, and enum where applicable
  • Output schemas — per capability (action, from, transform)
  • Examples — YAML usage examples for common patterns
  • CLI usage — auto-generated scafctl run provider commands
{
  "name": "exec",
  "description": "Executes shell commands using an embedded cross-platform POSIX shell interpreter...",
  "capabilities": ["action", "from", "transform"],
  "schema": {
    "properties": {
      "command": { "type": "string", "required": true, "description": "Command to execute...", "example": "echo hello | tr a-z A-Z" },
      "shell":   { "type": "string", "default": "auto", "enum": ["auto", "sh", "bash", "pwsh", "cmd"] },
      "timeout": { "type": "integer", "description": "Timeout in seconds (0 or omit for no timeout)" },
      "workingDir": { "type": "string", "description": "Working directory for command execution" },
      "env":     { "type": "", "description": "Environment variables to set (key-value pairs)" },
      "args":    { "type": "array", "description": "Additional arguments appended to the command" },
      "stdin":   { "type": "string", "description": "Standard input to provide to the command" }
    }
  },
  "outputSchemas": { "action": { "properties": { "stdout": {...}, "stderr": {...}, "exitCode": {...}, "success": {...} } } },
  "examples": [{ "name": "Simple command execution", "yaml": "..." }],
  "cliUsage": ["scafctl run provider exec --input command=echo hello | tr a-z A-Z"]
}

The required: true field on each property makes it immediately clear which inputs are mandatory — the AI doesn’t need to cross-reference a separate required array. The provider://reference resource also provides a compact overview of all providers’ required inputs at once.

Example: Schema and Examples (AI-Assisted Authoring)#

These tools help AI agents write correct solution YAML by giving them access to the full schema and real examples.

Get the Solution Schema#

You: “What fields are valid in a solution YAML file?”

The AI calls get_solution_schema and receives the full JSON Schema for the solution format — every field, type, validation rule, and description. This prevents the AI from inventing fields that don’t exist.

You can also ask about a specific section:

You: “What goes in the metadata section of a solution?”

The AI calls get_solution_schema with field: "metadata" and returns just the metadata schema, showing fields like name, version, description, tags, etc. with their validation constraints.

Explain a Kind#

You: “What fields does a resolver have?”

The AI calls explain_kind with kind: "resolver" and returns a structured breakdown of all resolver fields, including nested types, documentation, and validation tags.

Browse Examples#

You: “Show me example action configurations”

The AI calls list_examples with category: "actions" to see all available action examples, then calls get_example with a specific path to read the example content. This gives it real, tested patterns to reference when writing new YAML.

You: “Show me how to set up a solution with parameters and validation”

The AI calls get_example with path: "solutions/email-notifier/solution.yaml" to get a practical reference, then uses get_solution_schema to verify the structure.

Explain a Concept#

You: “What is a resolver and how does it work?”

The AI calls explain_concepts with name: "resolver" and returns a detailed explanation including the concept summary, category, examples, and related concepts.

You: “What testing concepts does scafctl support?”

The AI calls explain_concepts with category: "testing" and gets summaries for all testing-related concepts — functional testing, test sandbox, test scaffold, and test assertions.

Inspect Solution File Dependencies#

You: “What files does my solution depend on?”

The AI calls inspect_solution with path: "solution.yaml" and the response includes a fileDependencies array listing all external files referenced by providers (e.g., template files loaded via go-template, HCL files via hcl), along with which resolver references each file.

7. Available Tools Reference#

ToolDescription
auth_statusCheck auth handler status (configured, token validity, expiry). Auth handlers manage authentication identity — they are not solution providers
catalog_listList catalog entries filtered by kind (solution, provider, auth-handler) and name
diff_solutionCompare two solution files structurally — shows added, removed, and changed resolvers, actions, metadata, and tests
evaluate_celEvaluate a CEL expression with inline data, variables, or file-based context
evaluate_go_templateEvaluate a Go template against provided data, returning rendered output and referenced fields
explain_errorExplain a scafctl error message — root cause, resolution steps, related tools, and example fixes
explain_kindExplain any registered kind (solution, resolver, action, etc.) — shows all fields, types, descriptions, and validation tags
explain_lint_ruleGet a detailed explanation of a lint rule — description, severity, category, why it matters, how to fix it, and examples
get_exampleRead the contents of a scafctl example file. Use list_examples first to find available examples
get_provider_schemaGet comprehensive provider info: input schema (with per-property required), output schemas, examples, CLI usage
get_provider_output_shapeGet the output schema for a provider. Optionally filter by capability (from, transform, action)
get_solution_schemaGet the full JSON Schema for the solution YAML file format. Optionally drill into a specific field (e.g., metadata, spec)
get_run_commandGet the exact CLI command to run a solution (determines run solution vs run resolver)
explain_conceptsLook up and explain scafctl concepts (resolvers, providers, testing, CEL, etc.). Use without arguments to list all, or provide a name/query/category
inspect_solutionFull solution metadata — resolvers, actions, tags, links, maintainers, and file dependencies
lint_solutionValidate a solution YAML file and return structured findings
list_cel_functionsList CEL functions — custom scafctl functions, built-in, or by name. Use category to filter (e.g., strings, collections, encoding)
list_go_template_functionsList Go template extension functions — Sprig, custom, or by name
list_examplesList available scafctl example files with category filtering (solutions, resolvers, actions, providers, etc.)
list_providersList all providers with capability and category filtering
list_solutionsList solutions from the local catalog with name filtering
preview_actionPreview what each action in a workflow would do WITHOUT executing — shows materialized inputs, deferred values, phases, dependencies, and cross-section references (crossSectionRefs) for finally actions reading main-section results
preview_resolversExecute a solution’s resolver chain and return each resolver’s resolved value, output schema, and source positions. Use resolver param to focus on a single resolver and its dependencies
render_solutionRender action, resolver, or action-deps graphs as structured JSON
run_solution_testsExecute functional tests defined in a solution and return structured results. Use verbose=true for full assertion details
scaffold_solutionGenerate a complete skeleton solution YAML from parameters — name, description, features, and providers
validate_expressionSyntax-check a CEL expression or Go template without executing it — returns validity, errors, and referenced fields
catalog_inspectGet detailed metadata for a specific catalog artifact — version, kind, digest, created timestamp, and dependency list
extract_resolver_refsFind all resolver cross-references (_.resolverName) in a solution — shows which resolvers reference which others
generate_test_scaffoldGenerate functional test case scaffolding for a solution — creates test YAML with assertions, tags, auto-populated file dependencies, and sandbox guidance
list_testsList functional tests defined in a solution’s spec.testing.cases — shows test names, descriptions, assertions, and tags
show_snapshotDisplay the contents of a resolver execution snapshot file — resolver values, timing, status, and errors
diff_snapshotsCompare two resolver snapshots and return structured diffs — added, removed, modified, and unchanged resolvers
list_auth_handlersList all registered authentication handlers with their configuration status and token expiry
get_config_pathsList all XDG-compliant paths used by scafctl — config, data, cache, state, secrets, plugins, and runtime directories
validate_expressionsBatch-validate multiple CEL expressions or Go templates — returns validity, errors, and referenced fields for each
get_versionReturn scafctl version, commit SHA, and build timestamp
dry_run_solutionDry-run a solution without executing action providers. Resolvers run normally; each action gets a provider-generated WhatIf description of what it would do

8. Available Resources#

MCP resources are read-only data endpoints that AI agents can fetch on demand:

Resource URIDescription
solution://{name}Raw YAML content of a solution
solution://{name}/schemaJSON Schema for a solution’s input parameters
solution://{name}/graphResolver dependency graph with execution tiers, ASCII diagram, and Mermaid diagram
provider://{name}Detailed provider info: input schema (with required/optional per property), output schemas, examples, CLI usage
provider://referenceCompact reference of all providers with required/optional inputs, capabilities, and descriptions
solution://{name}/testsFunctional test cases defined in a solution’s spec.testing.cases — test names, descriptions, assertions, tags, and configuration

9. Available Prompts#

MCP prompts are predefined templates that guide AI agents through common workflows:

PromptDescriptionRequired Arguments
create_solutionStep-by-step guide for creating a new solution YAML file. Instructs the AI to fetch the schema and examples before writing any YAML.name, description
debug_solutionSystematic debugging workflow that inspects, lints, checks schema, and renders dependency graphs.path
add_resolverGuide for adding a resolver using a specific provider. Fetches provider schema and resolver examples.provider
add_actionGuide for adding an action to a solution’s workflow. Shows all action features (retry, forEach, when, etc.).provider
update_solutionGuide for modifying an existing solution. Follows inspect → edit → lint → preview → test workflow.path, change
add_testsGuide for writing functional tests for a solution. Covers test schema, assertions, and test patterns.path
compose_solutionGuide for designing a multi-file composed solution with partial YAML files that get merged at build time.path, goal
analyze_executionAnalyze solution execution results — diagnose failures, performance bottlenecks, and unexpected values.path
migrate_solutionGuide for migrating a solution — add composition, extract templates, split into multiple files, add tests, or upgrade patterns.path, migration
optimize_solutionAnalyze a solution for optimization opportunities — performance, readability, testing coverage, or all areas.path

Using Prompts#

In most AI clients, prompts appear as suggested conversation starters or can be invoked explicitly. For example, in VS Code with Copilot, you might see a prompt suggestion like “Create a new scafctl solution” that fills in the context automatically.

The prompts instruct the AI to:

  1. Fetch the schema first — ensuring it uses correct field names and structure
  2. Check available providers — so it picks real providers, not invented ones
  3. Reference examples — for practical patterns and best practices
  4. Follow validation rules — respecting field constraints like maxLength, pattern, etc.

10. Debugging#

Check Server Capabilities#

Print the full tool list and verify the server starts correctly:

scafctl mcp serve --info
scafctl mcp serve --info

Enable File Logging#

When the MCP server runs over stdio, application logs go to stderr by default. For persistent logs, use --log-file:

scafctl mcp serve --log-file /tmp/scafctl-mcp.log
scafctl mcp serve --log-file /tmp/scafctl-mcp.log

Then tail the log in another terminal:

tail -f /tmp/scafctl-mcp.log
tail -f /tmp/scafctl-mcp.log

Test with Raw JSON-RPC#

You can test the server manually by sending JSON-RPC messages:

echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}' | scafctl mcp serve
Write-Output '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}' | scafctl mcp serve

Debug Logging#

Enable verbose logging to see tool calls and responses:

scafctl mcp serve --log-level debug --log-file /tmp/scafctl-mcp-debug.log
scafctl mcp serve --log-level debug --log-file /tmp/scafctl-mcp-debug.log

11. Troubleshooting#

“scafctl: command not found”#

The AI client cannot find the scafctl binary. Ensure it’s on your $PATH:

which scafctl
which scafctl

If using a non-standard install location, use the full path in your MCP configuration:

{
  "servers": {
    "scafctl": {
      "type": "stdio",
      "command": "/usr/local/bin/scafctl",
      "args": ["mcp", "serve"]
    }
  }
}

Tools Not Appearing in AI Client#

  1. Verify the server starts correctly: scafctl mcp serve --info
  2. Check your MCP config file syntax (valid JSON, correct file location)
  3. Reload/restart the AI client after configuration changes
  4. Check the AI client’s output panel for MCP connection errors

“solution has no workflow defined”#

If you see this error when running scafctl run solution:

Error: solution "my-solution" has no workflow defined; use 'scafctl run resolver' to execute resolvers without actions

This means the solution contains only resolvers and no spec.workflow section. Use scafctl run resolver instead:

# For solutions with ONLY resolvers (no spec.workflow):
scafctl run resolver -f ./my-solution.yaml -r key=value

# For solutions WITH actions (spec.workflow.actions):
scafctl run solution -f ./my-solution.yaml -r key=value
# For solutions with ONLY resolvers (no spec.workflow):
scafctl run resolver -f ./my-solution.yaml -r key=value

# For solutions WITH actions (spec.workflow.actions):
scafctl run solution -f ./my-solution.yaml -r key=value

The create_solution MCP prompt instructs the AI to suggest the correct command based on whether the generated solution includes a workflow.

Authentication Errors#

If a tool returns an auth error, set up authentication before starting the server:

# Authenticate first
scafctl auth login <provider>

# Then start the server (or let the AI client start it)
scafctl mcp serve
# Authenticate first
scafctl auth login <provider>

# Then start the server (or let the AI client start it)
scafctl mcp serve

The MCP server inherits the same auth context as the CLI. If scafctl run solution works with your credentials, the MCP server will too.

Config Not Found#

The MCP server uses the same config file as the CLI (~/.scafctl/config.yaml by default). If you use a custom config location:

{
  "servers": {
    "scafctl": {
      "type": "stdio",
      "command": "scafctl",
      "args": ["mcp", "serve", "--config", "/path/to/config.yaml"]
    }
  }
}

Proxy Configuration#

If you are behind a corporate proxy, ensure proxy environment variables are available to the MCP server process. In VS Code, you can pass environment variables in the MCP configuration:

{
  "servers": {
    "scafctl": {
      "type": "stdio",
      "command": "scafctl",
      "args": ["mcp", "serve"],
      "env": {
        "HTTP_PROXY": "http://proxy.example.com:8080",
        "HTTPS_PROXY": "http://proxy.example.com:8080",
        "NO_PROXY": "localhost,127.0.0.1,.local"
      }
    }
  }
}

Advanced Features#

Transport Protocols#

The MCP server supports three transport protocols:

# Default: stdio (JSON-RPC 2.0 over stdin/stdout)
scafctl mcp serve

# SSE: Server-Sent Events over HTTP
scafctl mcp serve --transport sse --addr :8080

# HTTP: Streamable HTTP transport
scafctl mcp serve --transport http --addr :8080
# Default: stdio (JSON-RPC 2.0 over stdin/stdout)
scafctl mcp serve

# SSE: Server-Sent Events over HTTP
scafctl mcp serve --transport sse --addr :8080

# HTTP: Streamable HTTP transport
scafctl mcp serve --transport http --addr :8080

SSE and HTTP transports are useful for remote or multi-client scenarios where stdio is not available.

Structured Error Responses#

All tool errors return structured JSON with machine-readable context:

{
  "code": "INVALID_INPUT",
  "message": "'path' is required",
  "field": "path",
  "suggestion": "Provide the path to a solution file",
  "related": ["list_solutions", "scaffold_solution"]
}

Error codes include:

  • INVALID_INPUT — Bad or missing input arguments
  • NOT_FOUND — Requested resource doesn’t exist
  • VALIDATION_ERROR — Content failed validation
  • LOAD_FAILED — Could not load or parse a file
  • EXECUTION_FAILED — Runtime execution error
  • AUTH_REQUIRED — Authentication needed
  • CONFIG_ERROR — Configuration issue

Observability Hooks#

The server includes built-in observability via hooks and middleware:

  • Request timing: Every tool and resource call is timed and logged
  • Session tracking: Client connections and disconnections are logged
  • Error tracking: Failures include timing and context information

Enable detailed logging with:

scafctl mcp serve --log-file /tmp/scafctl-mcp.log
scafctl mcp serve --log-file /tmp/scafctl-mcp.log

Auto-Completion#

MCP clients that support argument completion will receive suggestions for:

  • Provider names: When a prompt or resource expects a provider name
  • Migration types: composition, templates, split, tests, upgrade
  • Solution features: resolvers, actions, transforms, validation, etc.
  • Solution names: From the local catalog for resource template URIs
  • Lint rule names: Available lint rules for the explain_lint_rule tool

Contextual Tool Filtering#

The server dynamically filters tools based on available capabilities:

  • Auth tools (e.g., auth_status) are hidden when no auth handlers are configured
  • Catalog tools (e.g., catalog_list) are hidden when no catalogs are configured
  • Provider tools (e.g., list_providers) are hidden when no registry is available

This reduces noise and prevents AI agents from invoking tools that will always fail.

Log Streaming#

The server can stream structured log messages to connected clients in real-time during tool execution. This uses the MCP logging notification protocol, respecting the client’s configured log level.

Workspace Roots#

For clients that support the MCP roots capability, the server can discover workspace root directories, enabling workspace-aware file operations.

Working Directory (cwd) Parameter#

All tools that accept file paths support an optional cwd parameter. This sets the working directory for path resolution without changing the server process’s CWD:

{
  "tool": "render_solution",
  "arguments": {
    "path": "solution.yaml",
    "cwd": "/Users/me/projects/my-app"
  }
}

This is the MCP equivalent of the CLI --cwd (-C) flag. It allows AI agents to specify the project context when the MCP server runs from a different directory. See the cwd design doc for which tools support it and design details.

Sampling and Elicitation#

The server supports two client interaction capabilities:

  • Sampling: Request the client’s LLM to generate text (e.g., for intelligent defaults)
  • Elicitation: Request structured input from the user during tool execution

Stdio Tuning#

For high-throughput scenarios, you can tune the stdio transport:

scafctl mcp serve --worker-pool-size 4 --queue-size 200
scafctl mcp serve --worker-pool-size 4 --queue-size 200

10. Protocol Features#

The MCP server leverages advanced protocol features from the MCP spec (2025-06-18) for a richer experience.

Progress Notifications#

Long-running tools (preview_resolvers, run_solution_tests, dry_run_solution) send real-time progress notifications. Clients that pass a progressToken in the request metadata will receive notifications/progress messages with step count, total, and human-readable descriptions.

Icons#

All tools, prompts, and resources include SVG icons via data URIs. MCP clients that support the icons field (VS Code, Claude Desktop, Cursor) display these alongside tool names for quick visual identification.

Output Schemas#

Key tools declare their output JSON Schema via outputSchema, enabling clients to validate and render structured output. This applies to list_solutions, inspect_solution, preview_resolvers, get_version, evaluate_cel, auth_status, get_config, get_config_paths, lint_solution, render_solution, and dry_run_solution.

Tool results include ResourceLink items that point to related MCP resources:

  • inspect_solution → links to the solution’s YAML, schema, and dependency graph resources
  • list_providers → link to the provider quick reference resource
  • preview_resolvers → links to the solution and its dependency graph

Content Annotations#

Some tool results annotate content items with audience hints (user vs assistant) and priority values. For example, get_run_command marks the command text for the assistant and the explanation for the user.

Deferred Loading#

Rarely-used tools (show_snapshot, diff_snapshots, diff_solution, extract_resolver_refs, explain_lint_rule) use deferred loading to reduce initial tool list size. They are still discoverable but load lazily.

Elicitation#

When preview_resolvers encounters parameter-type resolvers without provided values, it uses MCP elicitation to prompt the user for input interactively. This works with clients that support the elicitation capability.

Roots Discovery#

The list_solutions tool uses MCP roots to discover solution files in workspace directories when the catalog returns no results, enabling workspace-aware behavior.

Resource Recovery#

All resource handlers have automatic panic recovery (WithResourceRecovery), ensuring a panic in one resource handler doesn’t crash the server.

Next Steps#