Complete create command implementation with package arguments and updated documentation

Co-authored-by: toby <83556+toby@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2025-06-27 00:44:45 +00:00 • committed by Toby Padilla
commit 662957fa7f
2 changed files with 183 additions and 10 deletions

View file

@ -18,12 +18,23 @@ The compiled binary will be placed in the `bin` directory.
## Usage
The tool supports two main commands:
### Publishing a server
```bash
# Basic usage
./bin/mcp-publisher -registry-url <REGISTRY_URL> -mcp-file <PATH_TO_MCP_FILE>
./bin/mcp-publisher publish -registry-url <REGISTRY_URL> -mcp-file <PATH_TO_MCP_FILE>
# Force a new login even if a token exists
./bin/mcp-publisher -registry-url <REGISTRY_URL> -mcp-file <PATH_TO_MCP_FILE> -login
./bin/mcp-publisher publish -registry-url <REGISTRY_URL> -mcp-file <PATH_TO_MCP_FILE> -login
```
### Creating a server.json file
```bash
# Create a new server.json file
./bin/mcp-publisher create --name "io.github.owner/repo" --description "My server" --repo-url "https://github.com/owner/repo"
```
### Command-line Arguments
@ -33,6 +44,87 @@ The compiled binary will be placed in the `bin` directory.
- `-login`: Force a new GitHub authentication even if a token already exists (overwrites existing token file)
- `-auth-method`: Authentication method to use (default: github-oauth)
## Creating a server.json file
The tool provides a `create` command to help generate a properly formatted `server.json` file. This command takes various flags to specify the server details and generates a complete server.json file that you can then modify as needed.
### Usage
```bash
./bin/mcp-publisher create [flags]
```
### Create Command Flags
#### Required Flags
- `--name`, `-n`: Server name (e.g., io.github.owner/repo-name)
- `--description`, `-d`: Server description
- `--repo-url`: Repository URL
#### Optional Flags
- `--version`, `-v`: Server version (default: "1.0.0")
- `--repo-source`: Repository source (default: "github")
- `--output`, `-o`: Output file path (default: "server.json")
- `--execute`, `-e`: Command to execute the server (generates runtime arguments)
- `--registry`: Package registry name (default: "npm")
- `--package-name`: Package name (defaults to server name)
- `--package-version`: Package version (defaults to server version)
- `--runtime-hint`: Runtime hint (e.g., "docker")
- `--env-var`: Environment variable in format NAME:DESCRIPTION (can be repeated)
- `--package-arg`: Package argument in format VALUE:DESCRIPTION (can be repeated)
### Create Examples
#### Basic NPX Server
```bash
./bin/mcp-publisher create \
--name "io.github.example/my-server" \
--description "My MCP server" \
--repo-url "https://github.com/example/my-server" \
--execute "npx @example/my-server --verbose" \
--env-var "API_KEY:Your API key for the service"
```
#### Docker Server
```bash
./bin/mcp-publisher create \
--name "io.github.example/docker-server" \
--description "Docker-based MCP server" \
--repo-url "https://github.com/example/docker-server" \
--runtime-hint "docker" \
--execute "docker run --mount type=bind,src=/data,dst=/app/data example/server" \
--env-var "CONFIG_PATH:Path to configuration file"
```
#### Server with Package Arguments
```bash
./bin/mcp-publisher create \
--name "io.github.example/full-server" \
--description "Complete server example" \
--repo-url "https://github.com/example/full-server" \
--execute "npx @example/server" \
--package-arg "-s:Specify services and permissions" \
--package-arg "--config:Configuration file path" \
--env-var "API_KEY:Service API key" \
--env-var "DEBUG:Enable debug mode"
```
The `create` command will generate a `server.json` file with:
- Proper structure and formatting
- Runtime arguments parsed from the `--execute` command
- Environment variables with descriptions
- Package arguments for user configuration
- All necessary metadata
After creation, you may need to manually edit the file to:
- Adjust argument descriptions and requirements
- Set environment variable optionality (`is_required`, `is_secret`)
- Add remote server configurations
- Fine-tune runtime and package arguments
## Authentication
The tool has been simplified to use **GitHub OAuth device flow authentication exclusively**. Previous versions supported multiple authentication methods, but this version focuses solely on GitHub OAuth for better security and user experience.
@ -45,7 +137,9 @@ The tool has been simplified to use **GitHub OAuth device flow authentication ex
**Note**: Authentication is performed via GitHub OAuth App, which you must authorize for the respective resources (e.g., organization access if publishing organization repositories).
## Example
## Publishing Example
To publish an existing server.json file to the registry:
1. Prepare your `server.json` file with your server details:
@ -90,7 +184,7 @@ The tool has been simplified to use **GitHub OAuth device flow authentication ex
2. Run the publisher tool:
```bash
./bin/mcp-publisher --registry-url "https://mcp-registry.example.com" --mcp-file "./server.json"
./bin/mcp-publisher publish --registry-url "https://mcp-registry.example.com" --mcp-file "./server.json"
```
3. Follow the authentication instructions in the terminal if prompted.

View file

@ -47,6 +47,7 @@ type Package struct {
Version string `json:"version"`
RuntimeHint string `json:"runtime_hint,omitempty"`
RuntimeArguments []RuntimeArgument `json:"runtime_arguments,omitempty"`
PackageArguments []RuntimeArgument `json:"package_arguments,omitempty"`
EnvironmentVariables []EnvironmentVariable `json:"environment_variables,omitempty"`
}
@ -170,6 +171,7 @@ func createCommand() {
// Repeatable flags
var envVars []string
var packageArgs []string
createFlags.StringVar(&name, "name", "", "Server name (e.g., io.github.owner/repo-name) (required)")
createFlags.StringVar(&name, "n", "", "Server name (shorthand)")
@ -194,6 +196,12 @@ func createCommand() {
envVars = append(envVars, value)
return nil
})
// Custom flag for package arguments
createFlags.Func("package-arg", "Package argument in format VALUE:DESCRIPTION (can be repeated)", func(value string) error {
packageArgs = append(packageArgs, value)
return nil
})
createFlags.Parse(os.Args[2:])
@ -217,7 +225,7 @@ func createCommand() {
}
// Create server structure
server := createServerStructure(name, description, version, repoURL, repoSource, registryName, packageName, packageVersion, runtimeHint, execute, envVars)
server := createServerStructure(name, description, version, repoURL, repoSource, registryName, packageName, packageVersion, runtimeHint, execute, envVars, packageArgs)
// Convert to JSON
jsonData, err := json.MarshalIndent(server, "", " ")
@ -292,7 +300,7 @@ func publishToRegistry(registryURL string, mcpData []byte, token string) error {
return nil
}
func createServerStructure(name, description, version, repoURL, repoSource, registryName, packageName, packageVersion, runtimeHint, execute string, envVars []string) ServerJSON {
func createServerStructure(name, description, version, repoURL, repoSource, registryName, packageName, packageVersion, runtimeHint, execute string, envVars []string, packageArgs []string) ServerJSON {
// Parse environment variables
var environmentVariables []EnvironmentVariable
for _, envVar := range envVars {
@ -311,16 +319,50 @@ func createServerStructure(name, description, version, repoURL, repoSource, regi
}
}
// Parse package arguments
var packageArguments []RuntimeArgument
for i, pkgArg := range packageArgs {
parts := strings.SplitN(pkgArg, ":", 2)
value := parts[0]
description := fmt.Sprintf("Package argument %d", i+1)
if len(parts) == 2 {
description = parts[1]
}
packageArguments = append(packageArguments, RuntimeArgument{
Description: description,
IsRequired: true, // Package arguments are typically required
Format: "string",
Value: value,
Default: value,
Type: "positional",
ValueHint: value,
})
}
// Parse execute command to create runtime arguments
var runtimeArguments []RuntimeArgument
if execute != "" {
// Split the execute command into parts
parts := strings.Fields(execute)
// Split the execute command into parts, handling quoted strings
parts := smartSplit(execute)
if len(parts) > 1 {
// Add each argument as a runtime argument
// Skip the first part (command) and add each argument as a runtime argument
for i, arg := range parts[1:] {
description := fmt.Sprintf("Runtime argument %d", i+1)
// Try to provide better descriptions based on common patterns
if strings.HasPrefix(arg, "--") {
description = fmt.Sprintf("Command line flag: %s", arg)
} else if strings.HasPrefix(arg, "-") && len(arg) == 2 {
description = fmt.Sprintf("Command line option: %s", arg)
} else if strings.Contains(arg, "=") {
description = fmt.Sprintf("Configuration parameter: %s", arg)
} else if i > 0 && strings.HasPrefix(parts[i], "-") {
description = fmt.Sprintf("Value for %s", parts[i])
}
runtimeArguments = append(runtimeArguments, RuntimeArgument{
Description: fmt.Sprintf("Runtime argument %d", i+1),
Description: description,
IsRequired: false,
Format: "string",
Value: arg,
@ -339,6 +381,7 @@ func createServerStructure(name, description, version, repoURL, repoSource, regi
Version: packageVersion,
RuntimeHint: runtimeHint,
RuntimeArguments: runtimeArguments,
PackageArguments: packageArguments,
EnvironmentVariables: environmentVariables,
}
@ -356,3 +399,39 @@ func createServerStructure(name, description, version, repoURL, repoSource, regi
Packages: []Package{pkg},
}
}
// smartSplit splits a command string into parts, handling quoted strings and common shell patterns
func smartSplit(command string) []string {
var parts []string
var current strings.Builder
var inQuotes bool
var quoteChar rune
for _, char := range command {
switch {
case char == '"' || char == '\'':
if !inQuotes {
inQuotes = true
quoteChar = char
} else if char == quoteChar {
inQuotes = false
quoteChar = 0
} else {
current.WriteRune(char)
}
case char == ' ' && !inQuotes:
if current.Len() > 0 {
parts = append(parts, current.String())
current.Reset()
}
default:
current.WriteRune(char)
}
}
if current.Len() > 0 {
parts = append(parts, current.String())
}
return parts
}