diff --git a/.gitignore b/.gitignore index cda6c8e..5b1a783 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,7 @@ .env dist/* node_modules/ -.vscode/ \ No newline at end of file +/.vscode/* +!/.vscode/mcp.json + +tsconfig.tsbuildinfo diff --git a/.vscode/mcp.json b/.vscode/mcp.json new file mode 100644 index 0000000..3da8eb2 --- /dev/null +++ b/.vscode/mcp.json @@ -0,0 +1,10 @@ +{ + "servers": { + "my-dynatrace-mcp-server": { + "command": "node", + "cwd": "${workspaceFolder}", + "args": ["${workspaceFolder}/dist/index.js"], + "envFile": "${workspaceFolder}/.env" + } + } +} diff --git a/src/dynatrace-clients.ts b/src/authentication/dynatrace-clients.ts similarity index 75% rename from src/dynatrace-clients.ts rename to src/authentication/dynatrace-clients.ts index e398b11..c5046b9 100644 --- a/src/dynatrace-clients.ts +++ b/src/authentication/dynatrace-clients.ts @@ -5,36 +5,24 @@ import { RequestBodyTypes, } from '@dynatrace-sdk/http-client'; import { getSSOUrl } from 'dt-app'; -import { version as VERSION } from '../package.json'; - -// Define the OAuthTokenResponse interface to match the expected structure of the response -export interface OAuthTokenResponse { - scope?: string; - token_type?: string; - expires_in?: number; - access_token?: string; - errorCode?: number; - message?: string; - issueId?: string; - error?: string; - error_description?: string; -} +import { version as VERSION } from '../../package.json'; +import { OAuthTokenResponse } from './types'; /** - * Uses the provided oauth Client ID and Secret and requests a token + * Uses the provided oauth Client ID and Secret and requests a token via client-credentials flow * @param clientId - OAuth Client ID for Dynatrace * @param clientSecret - Oauth Client Secret for Dynatrace - * @param authUrl - SSO Authentication URL + * @param ssoAuthUrl - SSO Authentication URL * @param scopes - List of requested scopes * @returns */ const requestToken = async ( clientId: string, clientSecret: string, - authUrl: string, + ssoAuthUrl: string, scopes: string[], ): Promise => { - const res = await fetch(authUrl, { + const res = await fetch(ssoAuthUrl, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', @@ -83,7 +71,9 @@ export class ExtendedOauthClient extends _OAuthHttpClient { } } -/** Create an Oauth Client based on clientId, clientSecret, environmentUrl and scopes */ +/** Create an Oauth Client based on clientId, clientSecret, environmentUrl and scopes + * This uses a client-credentials flow to request a token from the SSO endpoint. + */ export const createOAuthClient = async ( clientId: string, clientSecret: string, @@ -130,28 +120,3 @@ export const createOAuthClient = async ( userAgent, ); }; - -/** Helper function to call an app-function via platform-api */ -export const callAppFunction = async ( - dtClient: _OAuthHttpClient, - appId: string, - functionName: string, - payload: any, -) => { - console.error(`Sending payload ${JSON.stringify(payload)}`); - - const response = await dtClient.send({ - url: `/platform/app-engine/app-functions/v1/apps/${appId}/api/${functionName}`, - method: 'POST', - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json', - }, - body: payload, - statusValidator: (status: number) => { - return [200].includes(status); - }, - }); - - return await response.body('json'); -}; diff --git a/src/authentication/types.ts b/src/authentication/types.ts new file mode 100644 index 0000000..9f47a74 --- /dev/null +++ b/src/authentication/types.ts @@ -0,0 +1,12 @@ +// Define the OAuthTokenResponse interface to match the expected structure of the response +export interface OAuthTokenResponse { + scope?: string; + token_type?: string; + expires_in?: number; + access_token?: string; + errorCode?: number; + message?: string; + issueId?: string; + error?: string; + error_description?: string; +} diff --git a/src/capabilities/call-app-function.ts b/src/capabilities/call-app-function.ts new file mode 100644 index 0000000..116da95 --- /dev/null +++ b/src/capabilities/call-app-function.ts @@ -0,0 +1,26 @@ +import { _OAuthHttpClient } from '@dynatrace-sdk/http-client'; + +/** Helper function to call an app-function via platform-api */ +export const callAppFunction = async ( + dtClient: _OAuthHttpClient, + appId: string, + functionName: string, + payload: any, +) => { + console.error(`Sending payload ${JSON.stringify(payload)}`); + + const response = await dtClient.send({ + url: `/platform/app-engine/app-functions/v1/apps/${appId}/api/${functionName}`, + method: 'POST', + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + }, + body: payload, + statusValidator: (status: number) => { + return [200].includes(status); + }, + }); + + return await response.body('json'); +}; diff --git a/src/capabilities/get-ownership-information.ts b/src/capabilities/get-ownership-information.ts index 5eb4d27..67d3d41 100644 --- a/src/capabilities/get-ownership-information.ts +++ b/src/capabilities/get-ownership-information.ts @@ -1,5 +1,5 @@ import { _OAuthHttpClient } from '@dynatrace-sdk/http-client'; -import { callAppFunction } from '../dynatrace-clients'; +import { callAppFunction } from './call-app-function'; export const getOwnershipInformation = async (dtClient: _OAuthHttpClient, entityIds: string) => { const ownershipResponse = await callAppFunction(dtClient, 'dynatrace.ownership', 'get-ownership-from-entity', { diff --git a/src/capabilities/send-slack-message.ts b/src/capabilities/send-slack-message.ts index 07d0a29..ef08fa7 100644 --- a/src/capabilities/send-slack-message.ts +++ b/src/capabilities/send-slack-message.ts @@ -1,5 +1,5 @@ import { _OAuthHttpClient } from '@dynatrace-sdk/http-client'; -import { callAppFunction } from '../dynatrace-clients'; +import { callAppFunction } from './call-app-function'; export const sendSlackMessage = async ( dtClient: _OAuthHttpClient, diff --git a/src/index.ts b/src/index.ts index 740ee73..6fed5be 100644 --- a/src/index.ts +++ b/src/index.ts @@ -20,7 +20,7 @@ import { config } from 'dotenv'; import { z, ZodRawShape, ZodTypeAny } from 'zod'; import { version as VERSION } from '../package.json'; -import { createOAuthClient } from './dynatrace-clients'; +import { createOAuthClient } from './authentication/dynatrace-clients'; import { listVulnerabilities } from './capabilities/list-vulnerabilities'; import { listProblems } from './capabilities/list-problems'; import { getProblemDetails } from './capabilities/get-problem-details';