Skip to content
Open
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
14 changes: 14 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Octopus Deploy Configuration for Testing
# Copy this file to .env and fill in your actual values

# Your Octopus Deploy instance URL
OCTOPUS_SERVER_URL=https://your-octopus-instance.octopus.app

# Your Octopus Deploy API key
OCTOPUS_API_KEY=API-XXXXXXXXXXXXXXXXXXXXXXXXXX

# Or use an access token (Bearer token) instead of an API key
# OCTOPUS_ACCESS_TOKEN=your-access-token-here

# Space name to use for testing (optional, defaults to "Default")
TEST_SPACE_NAME=Default
45 changes: 44 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ We are planning to release a native ARM build shortly so that those arguments wi
#### Requirements
- Node.js >= v20.0.0
- Octopus Deploy instance that can be accessed by the MCP server via HTTPS
- Octopus Deploy API Key
- Octopus Deploy API Key or Access Token (see [Authentication](#authentication) below)

#### Configuration

Expand Down Expand Up @@ -118,6 +118,49 @@ Or with configuration supplied via the command line:
npx -y @octopusdeploy/mcp-server --server-url https://your-octopus.com --api-key YOUR_API_KEY
```

### Authentication

The MCP server supports two authentication methods:

#### API Key (recommended for interactive use)

API keys are the standard authentication method for Octopus Deploy. You can generate one from your Octopus Deploy user profile.

```bash
# Via environment variable
OCTOPUS_API_KEY=API-XXXXXXXXXXXXXXXXXXXXXXXXXX

# Via command line argument
npx -y @octopusdeploy/mcp-server --api-key YOUR_API_KEY --server-url https://your-octopus.com
```

#### Access Token / Bearer Token (automated scenarios only)

The server also supports short-lived access tokens (Bearer tokens) as an alternative to API keys. This authentication method is intended **only for automated scenarios** where an external system issues a short-lived token to the MCP server (e.g., CI/CD pipelines, automated orchestration, or machine-to-machine workflows). Do not use long-lived Bearer tokens — use API keys instead for interactive or long-running sessions.

```bash
# Via environment variable
OCTOPUS_ACCESS_TOKEN=your-short-lived-token

# Via command line argument
npx -y @octopusdeploy/mcp-server --access-token YOUR_TOKEN --server-url https://your-octopus.com
```

Full example configuration with an access token:
```json
{
"mcpServers": {
"octopusdeploy": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@octopusdeploy/mcp-server", "--access-token", "YOUR_TOKEN", "--server-url", "https://your-octopus.com"]
}
}
}
```

If both an API key and an access token are provided, the access token takes precedence.

### Configuration Options

The Octopus MCP Server supports several command-line options to customize which tools are available.
Expand Down
2 changes: 1 addition & 1 deletion src/helpers/errorHandling.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export function handleOctopusApiError(
isErrorWithMessage(error, "provide a valid API key")
) {
throw new Error(
"Authentication failed. Ensure OCTOPUS_API_KEY environment variable is set with a valid API key. " +
"Authentication failed. Ensure a valid API key (OCTOPUS_API_KEY) or access token (OCTOPUS_ACCESS_TOKEN) is provided. " +
"You can generate an API key from your Octopus Deploy user profile.",
);
}
Expand Down
23 changes: 21 additions & 2 deletions src/helpers/getClientConfigurationFromEnvironment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const USER_AGENT_NAME = "octopus-mcp-server";
export interface ConfigurationOptions {
instanceURL?: string;
apiKey?: string;
accessToken?: string;
}

function isEmpty(value: string | undefined): value is undefined | "" {
Expand All @@ -22,14 +23,31 @@ function constructUserAgent(): string {
}

function getClientConfiguration(options: ConfigurationOptions = {}): ClientConfiguration {
if (isEmpty(options.instanceURL) || isEmpty(options.apiKey)) {
const hasApiKey = !isEmpty(options.apiKey);
const hasAccessToken = !isEmpty(options.accessToken);

if (isEmpty(options.instanceURL)) {
throw new Error(
"Octopus server URL and API key must be provided either via command line arguments (--server-url, --api-key) or environment variables (OCTOPUS_SERVER_URL, OCTOPUS_API_KEY)."
"Octopus server URL must be provided either via command line argument (--server-url) or environment variable (OCTOPUS_SERVER_URL)."
);
}

if (!hasApiKey && !hasAccessToken) {
throw new Error(
"Octopus authentication must be provided. Supply either an API key (--api-key or OCTOPUS_API_KEY) or an access token (--access-token or OCTOPUS_ACCESS_TOKEN)."
);
}

const userAgent = constructUserAgent();

if (hasAccessToken) {
return {
userAgentApp: userAgent,
instanceURL: options.instanceURL,
accessToken: options.accessToken,
};
}

return {
userAgentApp: userAgent,
instanceURL: options.instanceURL,
Expand All @@ -41,5 +59,6 @@ export function getClientConfigurationFromEnvironment(): ClientConfiguration {
return getClientConfiguration({
instanceURL: env["CLI_SERVER_URL"] || env["OCTOPUS_SERVER_URL"],
apiKey: env["CLI_API_KEY"] || env["OCTOPUS_API_KEY"],
accessToken: env["CLI_ACCESS_TOKEN"] || env["OCTOPUS_ACCESS_TOKEN"],
});
}
4 changes: 4 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ program
.version(SEMVER_VERSION)
.option("-s, --server-url <url>", "Octopus server URL")
.option("-k, --api-key <key>", "Octopus API key")
.option("-t, --access-token <token>", "Octopus access token (Bearer token)")
.option(
"--toolsets <toolsets>",
`Comma-separated list of toolsets to enable, or "all" (default: all). Available toolsets: ${DEFAULT_TOOLSETS.join(", ")}`,
Expand Down Expand Up @@ -90,6 +91,9 @@ if (options.serverUrl) {
if (options.apiKey) {
process.env.CLI_API_KEY = options.apiKey;
}
if (options.accessToken) {
process.env.CLI_ACCESS_TOKEN = options.accessToken;
}

// Set up initialization callback to capture client info
server.server.oninitialized = () => {
Expand Down
5 changes: 3 additions & 2 deletions src/tools/__tests__/testSetup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ config();
export const testConfig = {
octopusServerUrl: process.env.OCTOPUS_SERVER_URL || process.env.CLI_SERVER_URL,
octopusApiKey: process.env.OCTOPUS_API_KEY || process.env.CLI_API_KEY,
octopusAccessToken: process.env.OCTOPUS_ACCESS_TOKEN || process.env.CLI_ACCESS_TOKEN,
testSpaceName: process.env.TEST_SPACE_NAME || "Default",
timeout: 30000, // 30 seconds
};
Expand All @@ -18,8 +19,8 @@ export function validateTestEnvironment(): void {
missing.push("OCTOPUS_SERVER_URL (or CLI_SERVER_URL)");
}

if (!testConfig.octopusApiKey) {
missing.push("OCTOPUS_API_KEY (or CLI_API_KEY)");
if (!testConfig.octopusApiKey && !testConfig.octopusAccessToken) {
missing.push("OCTOPUS_API_KEY (or CLI_API_KEY) or OCTOPUS_ACCESS_TOKEN (or CLI_ACCESS_TOKEN)");
}

if (missing.length > 0) {
Expand Down