#
tokens: 4693/50000 7/7 files
lines: on (toggle) GitHub
raw markdown copy reset
# Directory Structure

```
├── .github
│   └── workflows
│       └── publish.yml
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── package-lock.json
├── package.json
├── README.md
├── src
│   └── index.ts
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
1 | node_modules/
2 | build/
3 | *.log
4 | .env*
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
  1 | # MCP Function App Tester
  2 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
  3 | 
  4 | A TypeScript-based MCP server that enables testing of Azure Function Apps through Cline. This tool allows you to test and interact with Function App endpoints directly from your development environment.
  5 | 
  6 | <a href="https://glama.ai/mcp/servers/la0u86zue0">
  7 |   <img width="380" height="200" src="https://glama.ai/mcp/servers/la0u86zue0/badge" />
  8 | </a>
  9 | 
 10 | ## Installation
 11 | 
 12 | ```bash
 13 | npm install dkmaker-mcp-function-app-tester
 14 | ```
 15 | 
 16 | ## Features
 17 | 
 18 | - Test Function App endpoints with different HTTP methods
 19 | - Support for GET, POST, PUT, and DELETE requests
 20 | - Detailed response information
 21 | - Custom header support
 22 | - Request body handling for POST/PUT methods
 23 | - Authentication support:
 24 |   - Basic Authentication (username/password)
 25 |   - Bearer Token Authentication
 26 |   - API Key Authentication (custom header)
 27 | 
 28 | ## Authentication
 29 | 
 30 | The server supports two authentication methods that can be configured via environment variables:
 31 | 
 32 | ### Basic Authentication
 33 | Set both environment variables to enable Basic Authentication:
 34 | ```bash
 35 | AUTH_BASIC_USERNAME=your-username
 36 | AUTH_BASIC_PASSWORD=your-password
 37 | ```
 38 | 
 39 | ### Bearer Token
 40 | Set this environment variable to enable Bearer Token authentication:
 41 | ```bash
 42 | AUTH_BEARER=your-token
 43 | ```
 44 | 
 45 | ### API Key
 46 | Set both environment variables to enable API Key authentication:
 47 | ```bash
 48 | AUTH_APIKEY_HEADER_NAME=X-API-Key  # The header name to use (e.g., X-API-Key, api-key, etc.)
 49 | AUTH_APIKEY_VALUE=your-api-key     # The actual API key value
 50 | ```
 51 | 
 52 | Note: Authentication precedence order:
 53 | 1. Basic Authentication (if username and password are set)
 54 | 2. Bearer Token (if token is set and Basic Auth is not configured)
 55 | 3. API Key (if header name and value are set, and no other auth is configured)
 56 | 
 57 | ## Usage
 58 | 
 59 | Once installed, you can use the Function App Tester through Cline. The server provides tools to test endpoints at the base URL: `http://localhost:7071/api`
 60 | 
 61 | Example usage:
 62 | 
 63 | ```typescript
 64 | // Test a GET endpoint
 65 | {
 66 |   "method": "GET",
 67 |   "endpoint": "/users"
 68 | }
 69 | 
 70 | // Test a POST endpoint with body
 71 | {
 72 |   "method": "POST",
 73 |   "endpoint": "/users",
 74 |   "body": {
 75 |     "name": "John Doe",
 76 |     "email": "[email protected]"
 77 |   }
 78 | }
 79 | 
 80 | // Test with custom headers
 81 | {
 82 |   "method": "GET",
 83 |   "endpoint": "/secure/data",
 84 |   "headers": {
 85 |     "Authorization": "Bearer token123"
 86 |   }
 87 | }
 88 | ```
 89 | 
 90 | ## Development
 91 | 
 92 | 1. Clone the repository:
 93 | ```bash
 94 | git clone https://github.com/dkmaker/mcp-function-app-tester.git
 95 | cd mcp-function-app-tester
 96 | ```
 97 | 
 98 | 2. Install dependencies:
 99 | ```bash
100 | npm install
101 | ```
102 | 
103 | 3. Build the project:
104 | ```bash
105 | npm run build
106 | ```
107 | 
108 | For development with auto-rebuild:
109 | ```bash
110 | npm run watch
111 | ```
112 | 
113 | ## License
114 | 
115 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
116 | 
```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "compilerOptions": {
 3 |     "target": "ES2022",
 4 |     "module": "Node16",
 5 |     "moduleResolution": "Node16",
 6 |     "outDir": "./build",
 7 |     "rootDir": "./src",
 8 |     "strict": true,
 9 |     "esModuleInterop": true,
10 |     "skipLibCheck": true,
11 |     "forceConsistentCasingInFileNames": true
12 |   },
13 |   "include": ["src/**/*"],
14 |   "exclude": ["node_modules"]
15 | }
16 | 
```

--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------

```markdown
 1 | ## [0.2.0](https://github.com/zenturacp/mcp-function-app-tester/compare/v0.1.0...v0.2.0) (2024-12-21)
 2 | 
 3 | 
 4 | ### Features
 5 | 
 6 | * add API key authentication support ([7882ae7](https://github.com/zenturacp/mcp-function-app-tester/commit/7882ae7ca09e5a0c066c6e9de256eeabdf536893))
 7 | 
 8 | ## [0.1.0](https://github.com/zenturacp/mcp-function-app-tester/compare/b71da08b67cf0fa61523f2205cd1ba37a2cb4e30...v0.1.0) (2024-12-21)
 9 | 
10 | 
11 | ### Features
12 | 
13 | * add automated publishing workflow and version management ([b71da08](https://github.com/zenturacp/mcp-function-app-tester/commit/b71da08b67cf0fa61523f2205cd1ba37a2cb4e30))
14 | 
15 | 
16 | ### Bug Fixes
17 | 
18 | * update GitHub Actions workflow configuration ([5e31b8e](https://github.com/zenturacp/mcp-function-app-tester/commit/5e31b8e61a0be542891584bb15ffe08e20587aa0))
19 | 
20 | 
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "name": "dkmaker-mcp-function-app-tester",
 3 |   "version": "0.2.0",
 4 |   "description": "A test of responses from a Function App",
 5 |   "license": "MIT",
 6 |   "type": "module",
 7 |   "bin": {
 8 |     "function-app-tester": "./build/index.js"
 9 |   },
10 |   "files": [
11 |     "build",
12 |     "README.md",
13 |     "LICENSE"
14 |   ],
15 |   "scripts": {
16 |     "build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"",
17 |     "prepare": "npm run build",
18 |     "watch": "tsc --watch",
19 |     "inspector": "npx @modelcontextprotocol/inspector build/index.js"
20 |   },
21 |   "dependencies": {
22 |     "@modelcontextprotocol/sdk": "^1.0.4",
23 |     "axios": "^1.7.9"
24 |   },
25 |   "devDependencies": {
26 |     "@types/node": "^20.11.24",
27 |     "typescript": "^5.3.3"
28 |   },
29 |   "keywords": [
30 |     "mcp",
31 |     "azure",
32 |     "cline",
33 |     "sonnet",
34 |     "assistant",
35 |     "development",
36 |     "typescript"
37 |   ],
38 |   "author": "dkmaker",
39 |   "repository": {
40 |     "type": "git",
41 |     "url": "git+https://github.com/dkmaker/mcp-function-app-tester.git"
42 |   },
43 |   "bugs": {
44 |     "url": "https://github.com/dkmaker/mcp-function-app-tester/issues"
45 |   },
46 |   "homepage": "https://github.com/dkmaker/mcp-function-app-tester#readme",
47 |   "engines": {
48 |     "node": ">=18.0.0"
49 |   }
50 | }
51 | 
```

--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------

```yaml
 1 | name: Publish Package
 2 | 
 3 | on:
 4 |   push:
 5 |     branches: [ main ]
 6 |   workflow_dispatch:
 7 | 
 8 | permissions:
 9 |   contents: write
10 |   pull-requests: write
11 | 
12 | jobs:
13 |   publish:
14 |     runs-on: ubuntu-latest
15 |     steps:
16 |       - uses: actions/checkout@v4
17 |         with:
18 |           fetch-depth: 0
19 |       
20 |       - name: Setup Node.js
21 |         uses: actions/setup-node@v4
22 |         with:
23 |           node-version: '18.x'
24 |           registry-url: 'https://registry.npmjs.org'
25 | 
26 |       - name: Install dependencies
27 |         run: npm ci
28 | 
29 |       - name: Conventional Changelog Action
30 |         id: changelog
31 |         uses: TriPSs/conventional-changelog-action@v6
32 |         with:
33 |           github-token: ${{ secrets.GITHUB_TOKEN }}
34 |           git-message: 'chore(release): {version}'
35 |           preset: 'conventionalcommits'
36 |           tag-prefix: 'v'
37 |           output-file: 'CHANGELOG.md'
38 |           skip-version-file: false
39 |           skip-commit: false
40 | 
41 |       - name: Build
42 |         run: npm run build
43 | 
44 |       - name: Create Release
45 |         if: steps.changelog.outputs.skipped == 'false'
46 |         env:
47 |           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
48 |         run: |
49 |           gh release create v${{ steps.changelog.outputs.version }} \
50 |             --title "Release v${{ steps.changelog.outputs.version }}" \
51 |             --notes "${{ steps.changelog.outputs.clean_changelog }}"
52 | 
53 |       - name: Publish to NPM
54 |         if: steps.changelog.outputs.skipped == 'false'
55 |         run: npm publish
56 |         env:
57 |           NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
58 | 
59 |       - name: Push changes
60 |         if: steps.changelog.outputs.skipped == 'false'
61 |         env:
62 |           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
63 |         run: |
64 |           git config --global user.name 'github-actions[bot]'
65 |           git config --global user.email 'github-actions[bot]@users.noreply.github.com'
66 |           git add package.json CHANGELOG.md
67 |           git commit -m "chore(release): v${{ steps.changelog.outputs.version }}"
68 |           git push "https://[email protected]/$GITHUB_REPOSITORY.git" HEAD:main
69 | 
```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
  1 | #!/usr/bin/env node
  2 | import { Server } from '@modelcontextprotocol/sdk/server/index.js';
  3 | import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
  4 | import {
  5 |   CallToolRequestSchema,
  6 |   ErrorCode,
  7 |   ListToolsRequestSchema,
  8 |   McpError,
  9 | } from '@modelcontextprotocol/sdk/types.js';
 10 | import axios, { AxiosRequestConfig, Method } from 'axios';
 11 | 
 12 | const BASE_URL = process.env.FUNCTION_APP_BASE_URL || 'http://localhost:7071/api';
 13 | const AUTH_BASIC_USERNAME = process.env.AUTH_BASIC_USERNAME;
 14 | const AUTH_BASIC_PASSWORD = process.env.AUTH_BASIC_PASSWORD;
 15 | const AUTH_BEARER = process.env.AUTH_BEARER;
 16 | const AUTH_APIKEY_HEADER_NAME = process.env.AUTH_APIKEY_HEADER_NAME;
 17 | const AUTH_APIKEY_VALUE = process.env.AUTH_APIKEY_VALUE;
 18 | 
 19 | interface TestEndpointArgs {
 20 |   method: 'GET' | 'POST' | 'PUT' | 'DELETE';
 21 |   endpoint: string;
 22 |   body?: any;
 23 |   headers?: Record<string, string>;
 24 | }
 25 | 
 26 | const isValidTestEndpointArgs = (args: any): args is TestEndpointArgs => {
 27 |   if (typeof args !== 'object' || args === null) return false;
 28 |   if (!['GET', 'POST', 'PUT', 'DELETE'].includes(args.method)) return false;
 29 |   if (typeof args.endpoint !== 'string') return false;
 30 |   if (args.headers !== undefined && typeof args.headers !== 'object') return false;
 31 |   return true;
 32 | };
 33 | 
 34 | const hasBasicAuth = () => AUTH_BASIC_USERNAME && AUTH_BASIC_PASSWORD;
 35 | const hasBearerAuth = () => !!AUTH_BEARER;
 36 | const hasApiKeyAuth = () => AUTH_APIKEY_HEADER_NAME && AUTH_APIKEY_VALUE;
 37 | 
 38 | class FunctionAppTester {
 39 |   private server: Server;
 40 |   private axiosInstance;
 41 | 
 42 |   constructor() {
 43 |     this.server = new Server(
 44 |       {
 45 |         name: 'function-app-tester',
 46 |         version: '0.1.0',
 47 |       },
 48 |       {
 49 |         capabilities: {
 50 |           tools: {},
 51 |         },
 52 |       }
 53 |     );
 54 | 
 55 |     this.axiosInstance = axios.create({
 56 |       baseURL: BASE_URL,
 57 |       validateStatus: () => true, // Allow any status code
 58 |     });
 59 | 
 60 |     this.setupToolHandlers();
 61 |     
 62 |     this.server.onerror = (error) => console.error('[MCP Error]', error);
 63 |     process.on('SIGINT', async () => {
 64 |       await this.server.close();
 65 |       process.exit(0);
 66 |     });
 67 |   }
 68 | 
 69 |   private setupToolHandlers() {
 70 |     this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
 71 |       tools: [
 72 |         {
 73 |           name: 'test_endpoint',
 74 |           description: `Test a Function App endpoint and get detailed response information. The endpoint will be prepended to the base url which is: ${BASE_URL}`,
 75 |           inputSchema: {
 76 |             type: 'object',
 77 |             properties: {
 78 |               method: {
 79 |                 type: 'string',
 80 |                 enum: ['GET', 'POST', 'PUT', 'DELETE'],
 81 |                 description: 'HTTP method to use',
 82 |               },
 83 |               endpoint: {
 84 |                 type: 'string',
 85 |                 description: 'Endpoint path (e.g. "/users"). Will be appended to base URL.',
 86 |               },
 87 |               body: {
 88 |                 type: 'object',
 89 |                 description: 'Optional request body for POST/PUT requests',
 90 |               },
 91 |               headers: {
 92 |                 type: 'object',
 93 |                 description: 'Optional request headers',
 94 |                 additionalProperties: {
 95 |                   type: 'string',
 96 |                 },
 97 |               },
 98 |             },
 99 |             required: ['method', 'endpoint'],
100 |           },
101 |         },
102 |       ],
103 |     }));
104 | 
105 |     this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
106 |       if (request.params.name !== 'test_endpoint') {
107 |         throw new McpError(
108 |           ErrorCode.MethodNotFound,
109 |           `Unknown tool: ${request.params.name}`
110 |         );
111 |       }
112 | 
113 |       if (!isValidTestEndpointArgs(request.params.arguments)) {
114 |         throw new McpError(
115 |           ErrorCode.InvalidParams,
116 |           'Invalid test endpoint arguments'
117 |         );
118 |       }
119 | 
120 |       try {
121 |         const config: AxiosRequestConfig = {
122 |           method: request.params.arguments.method as Method,
123 |           url: request.params.arguments.endpoint,
124 |           headers: request.params.arguments.headers || {},
125 |         };
126 | 
127 |         if (['POST', 'PUT'].includes(request.params.arguments.method) && request.params.arguments.body) {
128 |           config.data = request.params.arguments.body;
129 |         }
130 | 
131 |         // Handle authentication based on environment variables
132 |         if (hasBasicAuth()) {
133 |           const base64Credentials = Buffer.from(`${AUTH_BASIC_USERNAME}:${AUTH_BASIC_PASSWORD}`).toString('base64');
134 |           config.headers = {
135 |             ...config.headers,
136 |             'Authorization': `Basic ${base64Credentials}`
137 |           };
138 |         } else if (hasBearerAuth()) {
139 |           config.headers = {
140 |             ...config.headers,
141 |             'Authorization': `Bearer ${AUTH_BEARER}`
142 |           };
143 |         } else if (hasApiKeyAuth()) {
144 |           config.headers = {
145 |             ...config.headers,
146 |             [AUTH_APIKEY_HEADER_NAME as string]: AUTH_APIKEY_VALUE
147 |           };
148 |         }
149 | 
150 |         // Ensure endpoint starts with / and remove any trailing slashes
151 |         const normalizedEndpoint = `/${request.params.arguments.endpoint.replace(/^\/+|\/+$/g, '')}`;
152 |         config.url = normalizedEndpoint;
153 | 
154 |         const response = await this.axiosInstance.request(config);
155 |         const fullUrl = `${BASE_URL}${normalizedEndpoint}`;
156 | 
157 |         return {
158 |           content: [
159 |             {
160 |               type: 'text',
161 |               text: JSON.stringify({
162 |                 url: fullUrl, // Include the actual URL called
163 |                 statusCode: response.status,
164 |                 statusText: response.statusText,
165 |                 headers: response.headers,
166 |                 body: response.data,
167 |               }, null, 2),
168 |             },
169 |           ],
170 |         };
171 |       } catch (error) {
172 |         if (axios.isAxiosError(error)) {
173 |           return {
174 |             content: [
175 |               {
176 |                 type: 'text',
177 |                 text: `Request failed: ${error.message}`,
178 |               },
179 |             ],
180 |             isError: true,
181 |           };
182 |         }
183 |         throw error;
184 |       }
185 |     });
186 |   }
187 | 
188 |   async run() {
189 |     const transport = new StdioServerTransport();
190 |     await this.server.connect(transport);
191 |     console.error('Function App Tester MCP server running on stdio');
192 |   }
193 | }
194 | 
195 | const server = new FunctionAppTester();
196 | server.run().catch(console.error);
197 | 
```