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
37 changes: 32 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,15 @@ go install github.com/notque/openstack-mcp-server/cmd/openstack-mcp-server@lates
# 2. Store your password in the system keychain (macOS)
security add-generic-password -a your-user -s openstack -w "your-password"

# 3. Add to ~/.claude/settings.json (see Configuration below)
# 3. Register as an MCP server (see Configuration below)
claude mcp add openstack openstack-mcp-server \
-e OS_AUTH_URL=https://identity-3.eu-de-1.cloud.sap/v3 \
-e OS_USERNAME=your-user \
-e OS_PW_CMD="security find-generic-password -a your-user -s openstack -w" \
-e OS_USER_DOMAIN_NAME=your-domain \
-e OS_PROJECT_NAME=your-project \
-e OS_PROJECT_DOMAIN_NAME=your-domain \
-e OS_REGION_NAME=eu-de-1

# 4. Try asking Claude:
# "List my servers and their status"
Expand Down Expand Up @@ -56,9 +64,28 @@ security add-generic-password -a your-user -s openstack -w "your-password"

## Configuration

### Claude Code / Cursor
### Claude Code

Add to your `~/.claude/settings.json`:
Register the MCP server using the CLI:

```bash
claude mcp add openstack openstack-mcp-server \
-e OS_AUTH_URL=https://identity-3.eu-de-1.cloud.sap/v3 \
-e OS_USERNAME=your-user \
-e OS_PW_CMD="security find-generic-password -a your-user -s openstack -w" \
-e OS_USER_DOMAIN_NAME=your-domain \
-e OS_PROJECT_NAME=your-project \
-e OS_PROJECT_DOMAIN_NAME=your-domain \
-e OS_REGION_NAME=eu-de-1
```

This writes to `~/.claude.json` which Claude Code reads at session start. Use `--scope project` to scope to a single repo (writes to `.mcp.json`).

To verify: `claude mcp list`

### Cursor / Other MCP Clients

Add to your MCP client's configuration file (e.g., `.cursor/mcp.json`):

```json
{
Expand All @@ -67,8 +94,8 @@ Add to your `~/.claude/settings.json`:
"command": "openstack-mcp-server",
"env": {
"OS_AUTH_URL": "https://identity-3.eu-de-1.cloud.sap/v3",
"OS_USERNAME": "I-number",
"OS_PW_CMD": "security find-generic-password -a I-number -s openstack -w",
"OS_USERNAME": "your-user",
"OS_PW_CMD": "security find-generic-password -a your-user -s openstack -w",
"OS_USER_DOMAIN_NAME": "your-domain",
"OS_PROJECT_NAME": "your-project",
"OS_PROJECT_DOMAIN_NAME": "your-domain",
Expand Down
4 changes: 3 additions & 1 deletion internal/tools/shared/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ import (
)

// uuidPattern validates that a string is a proper UUID format.
// Accepts both hyphenated (8-4-4-4-12) and non-hyphenated (32 hex chars) formats,
// because OpenStack Keystone commonly returns IDs without hyphens.
// Used to prevent path traversal attacks via ID parameters in URL construction.
var uuidPattern = regexp.MustCompile(`(?i)^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$`)
var uuidPattern = regexp.MustCompile(`(?i)^([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}|[0-9a-f]{32})$`)

// safePathSegmentPattern validates that a string is a safe URL path segment.
// Allows alphanumeric, hyphens, underscores, dots, and forward slashes (for repo paths).
Expand Down
5 changes: 5 additions & 0 deletions internal/tools/shared/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ func TestValidateUUID_ValidUUIDs(t *testing.T) {
"6ba7b810-9dad-11d1-80b4-00c04fd430c8",
"00000000-0000-0000-0000-000000000000",
"AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE",
// OpenStack Keystone returns UUIDs without hyphens
"a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6",
"f6e5d4c3b2a1f6e5d4c3b2a1f6e5d4c3",
}
for _, uuid := range validUUIDs {
if result := ValidateUUID(uuid, "test_id"); result != nil {
Expand All @@ -34,6 +37,8 @@ func TestValidateUUID_InvalidUUIDs(t *testing.T) {
{"empty", ""},
{"with spaces", "550e8400 e29b 41d4 a716 446655440000"},
{"newlines", "550e8400-e29b-41d4-a716\n-446655440000"},
{"partial hyphens", "550e8400-e29b41d4a716446655440000"},
{"mixed hyphens", "550e8400e29b-41d4-a716-446655440000"},
}
for _, tt := range invalidUUIDs {
t.Run(tt.name, func(t *testing.T) {
Expand Down
Loading