Skip to content

KMCP CLI - #3

Closed
jmhbh wants to merge 60 commits into
mainfrom
buildpacks
Closed

KMCP CLI #3
jmhbh wants to merge 60 commits into
mainfrom
buildpacks

Conversation

@jmhbh

@jmhbh jmhbh commented Jul 17, 2025

Copy link
Copy Markdown
Collaborator
  • Add a CLI kmcp that allows users to create a mcp project and deploy it to the kubernetes cluster
  • Updated e2e test to use mcp server built using kmcp
  • Add kmcp binary to release pipeline

Demo script that will create an mcp server with an echo tool deployed to kind which can be connected to using mcp inspector.

Last updated 7/23/25 -
setup-kind-demo.sh.zip

Comment thread TEMPLATE_REFACTOR.md Outdated
@@ -0,0 +1,676 @@
# Template System Refactoring Plan

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can remove this file; it should now be resolved (for fastmcp)

@jmhbh jmhbh changed the title [WIP] Buildpacks KMCP CLI Jul 22, 2025
@jmhbh
jmhbh marked this pull request as ready for review July 22, 2025 05:29
Comment thread cmd/kmcp/cmd/deploy.go Outdated
Comment on lines +47 to +50
kmcp deploy --deploy-controller # Deploy controller
kmcp deploy --deploy-controller --controller-version 0.0.1 # Deploy controller with specific version
kmcp deploy --deploy-controller --controller-namespace my-namespace # Deploy controller to custom namespace
kmcp deploy --deploy-controller --registry-config ~/.docker/config.json # Specify docker registry config`,

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I considered separating the controller deployment into a separate command but this felt a bit more compact happy to split it up if folks think otherwise.

Comment thread test/e2e/e2e_test.go

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"kagent.dev/kmcp/api/v1alpha1"
"github.com/onsi/ginkgo/v2"

@jmhbh jmhbh Jul 22, 2025

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought we were moving away from ginkgo in our projects but I wanted to check in with folks. I can refactor this to use the go test library

Comment thread cmd/kmcp/cmd/deploy.go Outdated
Comment on lines +102 to +103
// TODO: this var is currently required because the controller img is in a private registry but this may change
deployCmd.Flags().StringVar(&deployRegistryConfig, "registry-config", "", "Path to docker registry config file")

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is currently only needed because our image is in a private registry once we OSS the project this can be removed

@peterj

peterj commented Jul 22, 2025

Copy link
Copy Markdown
Contributor

General feedback:

  • MCP Go is provided as an option, but it's not implemented (it should be removed if it's not implemented yet)
  • I didn't see Typescript/Nodejs as an option
  • these notes that are displayed after the first step don't make sense -- we should display them at the end, once the user provides all information and the project is scaffolded:
FastMCP Python uses dynamic tool loading - no template selection needed!
Tools will be automatically discovered from the src/tools/ directory.
Use 'kmcp add-tool <name>' to add new tools after project creation.
  • this output when adding a tool is redundant/not needed (the next steps are more than enough)
 Edit the file to implement your tool logic
🚀 The tool will be automatically loaded when the server starts
📋 Description: somedescript
  • an invalid tool.py file gets created when you run init:
"""<no value> tool for MCP server.

This tool is automatically loaded by the FastMCP dynamic loading system.
The function name must match the filename for auto-discovery.
"""

from core.server import mcp
from core.utils import get_tool_config, get_env_var

# Import additional dependencies as needed
# import httpx          # For HTTP requests
# import asyncpg        # For PostgreSQL database
# import aiofiles       # For async file operations
# import json           # For JSON processing
# import yaml           # For YAML processing


@mcp.tool()
def <no value>(message: str) -> str:
    """<no value> tool implementation.
    
    This is a template function. Replace this implementation with your tool logic.
    
    Args:
        message: Input message (replace with your actual parameters)
        
    Returns:
        str: Result of the tool operation (replace with your actual return type)
    """
    # Get tool-specific configuration from kmcp.yaml
    config = get_tool_config("<no value>")
    
    # TODO: Replace this basic implementation with your tool logic
    
    # Example: Basic text processing
    prefix = config.get("prefix", "")
    return f"{prefix}{message}"
    
    # Example: HTTP API call
    # api_key = get_env_var(config.get("api_key_env", "API_KEY"))
    # base_url = config.get("base_url", "https://api.example.com")
    # timeout = config.get("timeout", 30)
    # 
    # async with httpx.AsyncClient(timeout=timeout) as client:
    #     headers = {"Authorization": f"Bearer {api_key}"}
    #     response = await client.get(f"{base_url}/endpoint", headers=headers)
    #     return response.json()
    
    # Example: Database operation
    # db_url = get_env_var(config.get("db_url_env", "DATABASE_URL"))
    # 
    # async with asyncpg.connect(db_url) as conn:
    #     result = await conn.fetchrow("SELECT * FROM table WHERE id = $1", message)
    #     return dict(result) if result else None
    
    # Example: File processing
    # file_path = config.get("file_path", "/tmp/data.txt")
    # max_size = config.get("max_file_size", 1024 * 1024)  # 1MB
    # 
    # async with aiofiles.open(file_path, 'r') as f:
    #     content = await f.read(max_size)
    #     return {"content": content, "size": len(content)}
    
    # Example: JSON/YAML processing
    # try:
    #     data = json.loads(message)
    #     # Process the data
    #     return {"processed": True, "data": data}
    # except json.JSONDecodeError:
    #     return {"error": "Invalid JSON format"}
    
    # Example: Multi-step workflow
    # steps = config.get("steps", [])
    # results = []
    # 
    # for step in steps:
    #     step_type = step.get("type")
    #     if step_type == "process":
    #         result = await process_step(message, step)
    #     elif step_type == "validate":
    #         result = await validate_step(message, step)
    #     else:
    #         result = {"error": f"Unknown step type: {step_type}"}
    #     
    #     results.append(result)
    # 
    # return {"workflow_results": results}


# Example: Helper function for complex tools
# async def process_step(data: str, step_config: dict) -> dict:
#     """Process a single step in a workflow."""
#     # Your step processing logic here
#     return {"step": "processed", "data": data}


# Example: Validation helper
# async def validate_step(data: str, step_config: dict) -> dict:
#     """Validate data in a workflow step."""
#     # Your validation logic here
#     return {"valid": True, "data": data} 
  • the notes after init tell me how to run the server (uv run python src/main.py), but I was missing instructions on how to test it out locally (without deploying to k8s)
  • there's a bunch of ruff warnings in the .py files (extra spaces, etc)
  • duplicated/similar output when building:
Building Python MCP server...
Building Docker image for python project...
⠙ Building Docker image...
  • add-secret command fails:
../dist/kmcp secrets add-secret
panic: unable to redefine 'v' shorthand in "add-secret" flagset: it's already used for "value" flag

goroutine 1 [running]:
github.com/spf13/pflag.(*FlagSet).AddFlag(0x140001d9000, 0x1400043ad20)
        /Users/peterj/go/pkg/mod/github.com/spf13/pflag@v1.0.5/flag.go:874 +0x378
github.com/spf13/cobra.(*Command).mergePersistentFlags.(*FlagSet).AddFlagSet.func2(0x1400043ad20)
        /Users/peterj/go/pkg/mod/github.com/spf13/pflag@v1.0.5/flag.go:887 +0x44
github.com/spf13/pflag.(*FlagSet).VisitAll(0x1053edf60?, 0x14000061bc8)
        /Users/peterj/go/pkg/mod/github.com/spf13/pflag@v1.0.5/flag.go:290 +0xdc
  • generate k8s-secrets commands creates a weird output:
ypemeta:
    kind: Secret
    apiversion: v1
objectmeta:
    name: ""
    generatename: ""
    namespace: ""
    selflink: ""
    uid: ""
    resourceversion: ""
    generation: 0
    creationtimestamp: "0001-01-01T00:00:00Z"
    deletiontimestamp: null
    deletiongraceperiodseconds: null
    labels: {}
    annotations: {}
    ownerreferences: []
    finalizers: []
    managedfields: []
immutable: null
data:
    DATABASE_URL:
        - 112
        - 111
        - 115
        - 116
        - 103
        - 114
        - 101
        - 115
        - 113
        - 108
        - 58
        - 47
        - 47
        - 117
        - 115
        - 101
        - 114
        - 58
        - 112
        - 97
        - 115
        - 115
        - 64
        - 104
        - 111
        - 115
        - 116
        - 58
        - 53
        - 52
        - 51
        - 50
        - 47
        - 100
        - 98
    EXAMPLE_API_KEY:
        - 121
        - 111
  • I am not clear on the secrets command -- it talks about environments (there's no way to set/change the environment?); also shows the list of environment variables. Is this still in progress or?

  • the add-tool command automatically assumes Python - also some of the description in the output are perhaps too much:

Each tool is a Python file containing a function decorated with @mcp.tool().
The function should use the @mcp.tool() decorator from FastMCP.
...
The generated tool file will include commented examples for common patterns:
- HTTP API calls
- Database operations
- File processing
- Configuration access
  • are "API tools" a specific concept that's different from the MCP tool, or is there another reason this is called out explicitly:
For API tools, configure environment variables in kmcp.yaml:
  tools:
    weather:
      api_key_env: "WEATHER_API_KEY"
      base_url: "https://api.weather.com"
  • .env.example file is created automatically -- we should also create the .env.local file with the same values as the .env.example, since we're referencing that file in the kmcp.yaml
  • is there a reason we need python dependencies in the kmcp.yaml file?
  • the generated README.md is broken (the ``` sections)
Screenshot 2025-07-22 at 9 16 36 PM

Comment thread cmd/kmcp/cmd/add_tool.go Outdated
Comment thread cmd/kmcp/cmd/add_tool.go Outdated
fmt.Printf("🚀 The tool will be automatically loaded when the server starts\n")

if addToolDescription != "" {
fmt.Printf("📋 Description: %s\n", addToolDescription)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not needed, because the description will be displayed a couple of lines above this one (if provideD)

@jmhbh jmhbh Jul 24, 2025

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Peter, responding here because I am unable to tag you in a comment for some reason but I've addressed your feedback above.

MCP Go is provided as an option, but it's not implemented (it should be removed if it's not implemented yet)

I removed MCP Go as an option for now. We plan to follow up on this but it is currently not implemented cc @ilackarms

I didn't see Typescript/Nodejs as an option

After discussing with @ilackarms we wanted to start with python and go first which is why Typescript/NodeJs are currently not available options.

these notes that are displayed after the first step don't make sense -- we should display them at the end, once the user provides all information and the project is scaffolded:

FastMCP Python uses dynamic tool loading - no template selection needed!
Tools will be automatically discovered from the src/tools/ directory.
Use 'kmcp add-tool <name>' to add new tools after project creation.

Removed the first sentence i feel like its unnecessary and moved the last two lines to the end of the output after project is scaffolded.

an invalid tool.py file gets created when you run init:

Fixed. There was a templating issue. Also fixed a number of python lint issues detected by ruff.

the notes after init tell me how to run the server (uv run python src/main.py), but I was missing instructions on how to test it out locally (without deploying to k8s)

I added some additional instructions on using mcp inspector for local testing purposes after the project is scaffolded.

duplicated/similar output when building:

Deduped the output messages.

add-secret command fails:

I think the secrets implementation was too complicated for the current level of maturity of the CLI. Instead I opted to keep things simple and removed the existing secret commands and replaced it with one command create-k8s-secret-from-env which would create a kubernetes secret from a .env file and allow the user to deploy the mcp-server with the secret values loaded into the container using kmcp deploy mcp -f knowledge-assistant/kmcp.yaml --secrets knowledge-assistant/secrets/.env.yaml

generate k8s-secrets commands creates a weird output:

Yeah the original implementation didn't use yaml marshalling I fixed this.

I am not clear on the secrets command -- it talks about environments (there's no way to set/change the environment?); also shows the list of environment variables. Is this still in progress or?

As mentioned above I felt the secrets commands were a bit confusing. Opted to keep things simple for now as mentioned above.

the add-tool command automatically assumes Python - also some of the description in the output are perhaps too much:

I removed a bunch of the cruft, I haven't gotten to it yet but I think the kmcp.yaml also has a lot of unnecessary content that needs to be cleaned up but I might want to do that in a separate PR instead since this one's getting pretty big.

are "API tools" a specific concept that's different from the MCP tool, or is there another reason this is called out explicitly:

AFAIK its just an MCP Tool, @ilackarms maybe you can chime in here to confirm?

.env.example file is created automatically -- we should also create the .env.local file with the same values as the .env.example, since we're referencing that file in the kmcp.yaml

Added a .env.local file that gets generated by default

is there a reason we need python dependencies in the kmcp.yaml file?

Not exactly sure what the original intention was behind this. I'm guessing informational purposes? but I plan on going through this file and removing anything that isn't needed by the controller.

the generated README.md is broken (the ``` sections)

fixed!

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for fixing all these!! I'll let @ilackarms approve from the code side as he probably has more context there. I'll check the UX later on as well and log additional issues if something looks off.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks Peter!

Comment thread cmd/kmcp/cmd/secrets.go Outdated
Comment on lines +33 to +40

Examples:
kmcp secrets create-k8s-secret-from-env .env.local
kmcp secrets create-k8s-secret-from-env .env.production --name my-app-secrets --namespace production
kmcp secrets create-k8s-secret-from-env .env.staging --output-dir secrets/
kmcp secrets create-k8s-secret-from-env /your-mcp-server/.env --name secret --output-dir your-mcp-server/secrets/`,
Args: cobra.ExactArgs(1),
RunE: runCreateK8SecretFromEnv,

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ilackarms I removed the other add-secret, list-secret commands I found them to be not as useful given the current level of maturity of the CLI. I replaced the commands with create-k8s-secret-from-env that will take a .env and create a kubernetes secret that can be used alongside the kmcp deploy command ie: kmcp deploy mcp -f knowledge-assistant/kmcp.yaml --secrets knowledge-assistant/secrets/.env.yaml to deploy the mcp-server alongside the secret which will be loaded into the mcp-server container via envFrom

@jmhbh
jmhbh force-pushed the buildpacks branch 6 times, most recently from 1762e04 to f9c322b Compare July 28, 2025 03:40
@jmhbh jmhbh mentioned this pull request Jul 28, 2025
EItanya and others added 9 commits July 28, 2025 13:36
* create kubebuilder base + put down api foundation

* create foundation for kmcp translator

* refactor api, support http server

* fix build, regenerate

* wire up controller, add api for targetport

* fixes, get e2e test working

* working e2e test

* address pr feedback

* bump  golint action version

* fix golangci config

* remove unused files

* fix linter, add http path
Signed-off-by: JM Huibonhoa <jm.huibonhoa@solo.io>
Signed-off-by: JM Huibonhoa <jm.huibonhoa@solo.io>
Signed-off-by: JM Huibonhoa <jm.huibonhoa@solo.io>
Signed-off-by: JM Huibonhoa <jm.huibonhoa@solo.io>
Signed-off-by: JM Huibonhoa <jm.huibonhoa@solo.io>
… infrastructure for template refactoring

Signed-off-by: JM Huibonhoa <jm.huibonhoa@solo.io>
Signed-off-by: JM Huibonhoa <jm.huibonhoa@solo.io>
jmhbh and others added 28 commits July 28, 2025 13:43
Signed-off-by: JM Huibonhoa <jm.huibonhoa@solo.io>
Signed-off-by: JM Huibonhoa <jm.huibonhoa@solo.io>
…t from env file

Signed-off-by: JM Huibonhoa <jm.huibonhoa@solo.io>
Signed-off-by: JM Huibonhoa <jm.huibonhoa@solo.io>
Signed-off-by: JM Huibonhoa <jm.huibonhoa@solo.io>
Signed-off-by: JM Huibonhoa <jm.huibonhoa@solo.io>
Signed-off-by: JM Huibonhoa <jm.huibonhoa@solo.io>
Signed-off-by: JM Huibonhoa <jm.huibonhoa@solo.io>
Signed-off-by: JM Huibonhoa <jm.huibonhoa@solo.io>
Signed-off-by: JM Huibonhoa <jm.huibonhoa@solo.io>
Signed-off-by: JM Huibonhoa <jm.huibonhoa@solo.io>
…K8sSecretsCmd now that wecangenerate k8 secrets from .env files

Signed-off-by: JM Huibonhoa <jm.huibonhoa@solo.io>
… secrets cli commands

Signed-off-by: JM Huibonhoa <jm.huibonhoa@solo.io>
Signed-off-by: JM Huibonhoa <jm.huibonhoa@solo.io>
Signed-off-by: JM Huibonhoa <jm.huibonhoa@solo.io>
Signed-off-by: JM Huibonhoa <jm.huibonhoa@solo.io>
Signed-off-by: JM Huibonhoa <jm.huibonhoa@solo.io>
Signed-off-by: JM Huibonhoa <jm.huibonhoa@solo.io>
Signed-off-by: JM Huibonhoa <jm.huibonhoa@solo.io>
Signed-off-by: JM Huibonhoa <jm.huibonhoa@solo.io>
Signed-off-by: JM Huibonhoa <jm.huibonhoa@solo.io>
Signed-off-by: JM Huibonhoa <jm.huibonhoa@solo.io>
Signed-off-by: JM Huibonhoa <jm.huibonhoa@solo.io>
…date e2e tests

Signed-off-by: JM Huibonhoa <jm.huibonhoa@solo.io>
Signed-off-by: JM Huibonhoa <jm.huibonhoa@solo.io>
Signed-off-by: JM Huibonhoa <jm.huibonhoa@solo.io>
Signed-off-by: JM Huibonhoa <jm.huibonhoa@solo.io>
Signed-off-by: JM Huibonhoa <jm.huibonhoa@solo.io>
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.

4 participants