Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 43 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,10 +127,12 @@ The plugin provides the following built-in tools for interacting with Jenkins:
}
```
Note on Parameters:
- Only built-in parameter types in core Jenkins are fully supported.
- Unsupported parameter types will be ignored and set as null in the pipeline.
- File parameters are not currently supported.
- If you encounter a parameter type from a Jenkins plugin that is not supported, please create an issue in our repository.
- **Core Jenkins Parameters**: Fully supported (String, Boolean, Choice, Text, Password, Run)
- **Plugin Parameters**: Automatically detected and handled using reflection
- **File Parameters**: Not supported via MCP (require file uploads)
- **Multi-select Parameters**: Supported as arrays or lists
- **Custom Plugin Parameters**: Automatically attempted using reflection-based detection
- **Fallback Behavior**: Unsupported parameters fall back to default values with logging
#### Build Information
- `getBuild`: Retrieve a specific build or the last build of a Jenkins job.
- `updateBuild`: Update build display name and/or description.
Expand All @@ -150,6 +152,43 @@ The plugin provides the following built-in tools for interacting with Jenkins:
Each tool accepts specific parameters to customize its behavior. For detailed usage instructions and parameter descriptions, refer to the API documentation or use the MCP introspection capabilities.

To use these tools, connect to the MCP server endpoint and make tool calls using your MCP client implementation.
### Enhanced Parameter Support

The MCP Server Plugin now provides comprehensive support for Jenkins parameters:

#### Supported Parameter Types

- **String Parameters**: Text input with default values
- **Boolean Parameters**: True/false values with automatic type conversion
- **Choice Parameters**: Dropdown selections with validation
- **Text Parameters**: Multi-line text input
- **Password Parameters**: Secure input with Secret handling
- **Run Parameters**: Build number references
- **Plugin Parameters**: Automatically detected and handled

#### Parameter Handling Features

- **Type Conversion**: Automatic conversion between JSON types and Jenkins parameter types
- **Validation**: Choice parameters validate input against available options
- **Fallback**: Unsupported parameters gracefully fall back to defaults
- **Reflection**: Plugin parameter types automatically detected and handled
- **Logging**: Comprehensive logging for debugging parameter issues

#### Example Usage

```json
{
"jobFullName": "my-parameterized-job",
"parameters": {
"BRANCH": "main",
"DEBUG_MODE": true,
"ENVIRONMENT": "production",
"FEATURES": ["feature1", "feature2"],
"NOTES": "Build triggered via MCP"
}
}
```

### Extending MCP Capabilities

To add new MCP tools or functionalities:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
package io.jenkins.plugins.mcp.server.extensions;

import static io.jenkins.plugins.mcp.server.extensions.util.JenkinsUtil.getBuildByNumberOrLast;
import static io.jenkins.plugins.mcp.server.extensions.util.ParameterValueFactory.createParameterValue;

import hudson.Extension;
import hudson.model.AbstractItem;
Expand All @@ -38,7 +39,6 @@
import hudson.model.ParametersAction;
import hudson.model.ParametersDefinitionProperty;
import hudson.model.Run;
import hudson.model.SimpleParameterDefinition;
import hudson.model.User;
import hudson.slaves.Cloud;
import io.jenkins.plugins.mcp.server.McpServerExtension;
Expand Down Expand Up @@ -93,23 +93,21 @@ public boolean triggerBuild(
(ParametersDefinitionProperty) j.getProperty(ParametersDefinitionProperty.class);
var parameterValues = parametersDefinition.getParameterDefinitions().stream()
.map(param -> {
if (param instanceof SimpleParameterDefinition sd) {
if (parameters != null && parameters.containsKey(param.getName())) {
var value = parameters.get(param.getName());
return sd.createValue(String.valueOf(value));
} else {
return sd.getDefaultParameterValue();
}
if (parameters != null && parameters.containsKey(param.getName())) {
var value = parameters.get(param.getName());
return createParameterValue(param, value);
} else {
log.warn(
"Unsupported parameter type: {}",
param.getClass().getName());
return null;
return param.getDefaultParameterValue();
}
})
.filter(Objects::nonNull)
.toList();
job.scheduleBuild2(0, new ParametersAction(parameterValues));

if (!parameterValues.isEmpty()) {
job.scheduleBuild2(0, new ParametersAction(parameterValues));
} else {
job.scheduleBuild2(0);
}
} else {
job.scheduleBuild2(0);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
/*
*
* The MIT License
*
* Copyright (c) 2025, Ognyan Marinov.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is furnished
* to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/

package io.jenkins.plugins.mcp.server.extensions.util;

import hudson.model.BooleanParameterDefinition;
import hudson.model.ChoiceParameterDefinition;
import hudson.model.FileParameterDefinition;
import hudson.model.ParameterDefinition;
import hudson.model.ParameterValue;
import hudson.model.PasswordParameterDefinition;
import hudson.model.RunParameterDefinition;
import hudson.model.StringParameterDefinition;
import hudson.model.TextParameterDefinition;
import java.util.List;
import lombok.extern.slf4j.Slf4j;

/**
* Factory class for creating parameter values from different parameter definitions.
* Supports both core Jenkins parameter types and common plugin parameter types.
*/
@Slf4j
public final class ParameterValueFactory {

/**
* Creates a parameter value from a parameter definition and input value.
* This method supports both core Jenkins parameter types and plugin-specific types.
*
* @param param the parameter definition
* @param inputValue the input value from the user
* @return the created parameter value, or null if creation fails
*/
public static ParameterValue createParameterValue(ParameterDefinition param, Object inputValue) {
try {
if (param instanceof StringParameterDefinition) {
return createStringParameterValue((StringParameterDefinition) param, inputValue);
} else if (param instanceof BooleanParameterDefinition) {
return createBooleanParameterValue((BooleanParameterDefinition) param, inputValue);
} else if (param instanceof ChoiceParameterDefinition) {
return createChoiceParameterValue((ChoiceParameterDefinition) param, inputValue);
} else if (param instanceof TextParameterDefinition) {
return createTextParameterValue((TextParameterDefinition) param, inputValue);
} else if (param instanceof PasswordParameterDefinition) {
return createPasswordParameterValue((PasswordParameterDefinition) param, inputValue);
} else if (param instanceof RunParameterDefinition) {
return createRunParameterValue((RunParameterDefinition) param, inputValue);
} else if (param instanceof FileParameterDefinition) {
return createFileParameterValue((FileParameterDefinition) param, inputValue);
} else {
// Try to use reflection for plugin parameter types
return createPluginParameterValue(param, inputValue);
}
} catch (Exception e) {
log.warn(
"Failed to create parameter value for {}: {}",
param.getClass().getSimpleName(),
e.getMessage());
return null;
}
}

private static ParameterValue createStringParameterValue(StringParameterDefinition param, Object inputValue) {
if (inputValue != null) {
return param.createValue(String.valueOf(inputValue));
}
return param.getDefaultParameterValue();
}

private static ParameterValue createBooleanParameterValue(BooleanParameterDefinition param, Object inputValue) {
if (inputValue != null) {
boolean value;
if (inputValue instanceof Boolean) {
value = (Boolean) inputValue;
} else if (inputValue instanceof String) {
value = Boolean.parseBoolean((String) inputValue);
} else {
value = Boolean.parseBoolean(String.valueOf(inputValue));
}
return param.createValue(String.valueOf(value));
}
return param.getDefaultParameterValue();
}

private static ParameterValue createChoiceParameterValue(ChoiceParameterDefinition param, Object inputValue) {
if (inputValue != null) {
String value = String.valueOf(inputValue);
// Validate that the choice is valid
if (param.getChoices().contains(value)) {
return param.createValue(value);
} else {
log.warn(
"Invalid choice '{}' for parameter '{}'. Valid choices: {}",
value,
param.getName(),
param.getChoices());
return param.getDefaultParameterValue();
}
}
return param.getDefaultParameterValue();
}

private static ParameterValue createTextParameterValue(TextParameterDefinition param, Object inputValue) {
if (inputValue != null) {
return param.createValue(String.valueOf(inputValue));
}
return param.getDefaultParameterValue();
}

private static ParameterValue createPasswordParameterValue(PasswordParameterDefinition param, Object inputValue) {
if (inputValue != null) {
String value = String.valueOf(inputValue);
return param.createValue(value);
}
return param.getDefaultParameterValue();
}

private static ParameterValue createRunParameterValue(RunParameterDefinition param, Object inputValue) {
if (inputValue != null) {
String value = String.valueOf(inputValue);
return param.createValue(value);
}
return param.getDefaultParameterValue();
}

private static ParameterValue createFileParameterValue(FileParameterDefinition param, Object inputValue) {
// File parameters require special handling and are not fully supported via MCP
// For now, we'll log a warning and return null
log.warn(
"File parameter '{}' is not supported via MCP. File parameters require file uploads.", param.getName());
return null;
}

/**
* Attempts to create a parameter value for plugin parameter types using reflection.
* This allows support for custom parameter types without hardcoding them.
*/
private static ParameterValue createPluginParameterValue(ParameterDefinition param, Object inputValue) {
try {
// Try to use the createValue method if it exists
if (inputValue != null) {
// First try with the input value
try {
var method = param.getClass().getMethod("createValue", Object.class);
if (method != null) {
return (ParameterValue) method.invoke(param, inputValue);
}
} catch (NoSuchMethodException e) {
// Method doesn't exist, continue to next approach
}

// Try with String parameter
try {
var method = param.getClass().getMethod("createValue", String.class);
if (method != null) {
return (ParameterValue) method.invoke(param, String.valueOf(inputValue));
}
} catch (NoSuchMethodException e) {
// Method doesn't exist, continue to next approach
}

// Try with boolean parameter
if (inputValue instanceof Boolean) {
try {
var method = param.getClass().getMethod("createValue", boolean.class);
if (method != null) {
return (ParameterValue) method.invoke(param, inputValue);
}
} catch (NoSuchMethodException e) {
// Method doesn't exist, continue to next approach
}
}

// Try with List parameter (for multi-select parameters)
if (inputValue instanceof List) {
try {
var method = param.getClass().getMethod("createValue", List.class);
if (method != null) {
return (ParameterValue) method.invoke(param, inputValue);
}
} catch (NoSuchMethodException e) {
// Method doesn't exist, continue to next approach
}
}

// Try with String[] parameter (for multi-select parameters)
if (inputValue instanceof List) {
try {
List<?> list = (List<?>) inputValue;
String[] array = list.stream().map(String::valueOf).toArray(String[]::new);
var method = param.getClass().getMethod("createValue", String[].class);
if (method != null) {
return (ParameterValue) method.invoke(param, (Object) array);
}
} catch (NoSuchMethodException e) {
// Method doesn't exist, continue to next approach
}
}
}

// If all else fails, try to get the default value
try {
var method = param.getClass().getMethod("getDefaultParameterValue");
if (method != null) {
return (ParameterValue) method.invoke(param);
}
} catch (NoSuchMethodException e) {
// Method doesn't exist
}

log.warn(
"Could not create parameter value for plugin parameter type: {}",
param.getClass().getName());
return null;

} catch (Exception e) {
log.warn(
"Failed to create plugin parameter value for {}: {}",
param.getClass().getName(),
e.getMessage());
return null;
}
}
}
Loading