# Directory Structure
```
├── .github
│ └── workflows
│ └── publish.yml
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── package-lock.json
├── package.json
├── README.md
├── src
│ └── index.ts
└── tsconfig.json
```
# Files
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
```
node_modules/
build/
*.log
.env*
```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
# MCP Function App Tester
[](https://opensource.org/licenses/MIT)
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.
<a href="https://glama.ai/mcp/servers/la0u86zue0">
<img width="380" height="200" src="https://glama.ai/mcp/servers/la0u86zue0/badge" />
</a>
## Installation
```bash
npm install dkmaker-mcp-function-app-tester
```
## Features
- Test Function App endpoints with different HTTP methods
- Support for GET, POST, PUT, and DELETE requests
- Detailed response information
- Custom header support
- Request body handling for POST/PUT methods
- Authentication support:
- Basic Authentication (username/password)
- Bearer Token Authentication
- API Key Authentication (custom header)
## Authentication
The server supports two authentication methods that can be configured via environment variables:
### Basic Authentication
Set both environment variables to enable Basic Authentication:
```bash
AUTH_BASIC_USERNAME=your-username
AUTH_BASIC_PASSWORD=your-password
```
### Bearer Token
Set this environment variable to enable Bearer Token authentication:
```bash
AUTH_BEARER=your-token
```
### API Key
Set both environment variables to enable API Key authentication:
```bash
AUTH_APIKEY_HEADER_NAME=X-API-Key # The header name to use (e.g., X-API-Key, api-key, etc.)
AUTH_APIKEY_VALUE=your-api-key # The actual API key value
```
Note: Authentication precedence order:
1. Basic Authentication (if username and password are set)
2. Bearer Token (if token is set and Basic Auth is not configured)
3. API Key (if header name and value are set, and no other auth is configured)
## Usage
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`
Example usage:
```typescript
// Test a GET endpoint
{
"method": "GET",
"endpoint": "/users"
}
// Test a POST endpoint with body
{
"method": "POST",
"endpoint": "/users",
"body": {
"name": "John Doe",
"email": "[email protected]"
}
}
// Test with custom headers
{
"method": "GET",
"endpoint": "/secure/data",
"headers": {
"Authorization": "Bearer token123"
}
}
```
## Development
1. Clone the repository:
```bash
git clone https://github.com/dkmaker/mcp-function-app-tester.git
cd mcp-function-app-tester
```
2. Install dependencies:
```bash
npm install
```
3. Build the project:
```bash
npm run build
```
For development with auto-rebuild:
```bash
npm run watch
```
## License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
```
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"outDir": "./build",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
```
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
```markdown
## [0.2.0](https://github.com/zenturacp/mcp-function-app-tester/compare/v0.1.0...v0.2.0) (2024-12-21)
### Features
* add API key authentication support ([7882ae7](https://github.com/zenturacp/mcp-function-app-tester/commit/7882ae7ca09e5a0c066c6e9de256eeabdf536893))
## [0.1.0](https://github.com/zenturacp/mcp-function-app-tester/compare/b71da08b67cf0fa61523f2205cd1ba37a2cb4e30...v0.1.0) (2024-12-21)
### Features
* add automated publishing workflow and version management ([b71da08](https://github.com/zenturacp/mcp-function-app-tester/commit/b71da08b67cf0fa61523f2205cd1ba37a2cb4e30))
### Bug Fixes
* update GitHub Actions workflow configuration ([5e31b8e](https://github.com/zenturacp/mcp-function-app-tester/commit/5e31b8e61a0be542891584bb15ffe08e20587aa0))
```
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "dkmaker-mcp-function-app-tester",
"version": "0.2.0",
"description": "A test of responses from a Function App",
"license": "MIT",
"type": "module",
"bin": {
"function-app-tester": "./build/index.js"
},
"files": [
"build",
"README.md",
"LICENSE"
],
"scripts": {
"build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"",
"prepare": "npm run build",
"watch": "tsc --watch",
"inspector": "npx @modelcontextprotocol/inspector build/index.js"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.0.4",
"axios": "^1.7.9"
},
"devDependencies": {
"@types/node": "^20.11.24",
"typescript": "^5.3.3"
},
"keywords": [
"mcp",
"azure",
"cline",
"sonnet",
"assistant",
"development",
"typescript"
],
"author": "dkmaker",
"repository": {
"type": "git",
"url": "git+https://github.com/dkmaker/mcp-function-app-tester.git"
},
"bugs": {
"url": "https://github.com/dkmaker/mcp-function-app-tester/issues"
},
"homepage": "https://github.com/dkmaker/mcp-function-app-tester#readme",
"engines": {
"node": ">=18.0.0"
}
}
```
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
```yaml
name: Publish Package
on:
push:
branches: [ main ]
workflow_dispatch:
permissions:
contents: write
pull-requests: write
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18.x'
registry-url: 'https://registry.npmjs.org'
- name: Install dependencies
run: npm ci
- name: Conventional Changelog Action
id: changelog
uses: TriPSs/conventional-changelog-action@v6
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
git-message: 'chore(release): {version}'
preset: 'conventionalcommits'
tag-prefix: 'v'
output-file: 'CHANGELOG.md'
skip-version-file: false
skip-commit: false
- name: Build
run: npm run build
- name: Create Release
if: steps.changelog.outputs.skipped == 'false'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release create v${{ steps.changelog.outputs.version }} \
--title "Release v${{ steps.changelog.outputs.version }}" \
--notes "${{ steps.changelog.outputs.clean_changelog }}"
- name: Publish to NPM
if: steps.changelog.outputs.skipped == 'false'
run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Push changes
if: steps.changelog.outputs.skipped == 'false'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
git config --global user.name 'github-actions[bot]'
git config --global user.email 'github-actions[bot]@users.noreply.github.com'
git add package.json CHANGELOG.md
git commit -m "chore(release): v${{ steps.changelog.outputs.version }}"
git push "https://[email protected]/$GITHUB_REPOSITORY.git" HEAD:main
```
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
```typescript
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ErrorCode,
ListToolsRequestSchema,
McpError,
} from '@modelcontextprotocol/sdk/types.js';
import axios, { AxiosRequestConfig, Method } from 'axios';
const BASE_URL = process.env.FUNCTION_APP_BASE_URL || 'http://localhost:7071/api';
const AUTH_BASIC_USERNAME = process.env.AUTH_BASIC_USERNAME;
const AUTH_BASIC_PASSWORD = process.env.AUTH_BASIC_PASSWORD;
const AUTH_BEARER = process.env.AUTH_BEARER;
const AUTH_APIKEY_HEADER_NAME = process.env.AUTH_APIKEY_HEADER_NAME;
const AUTH_APIKEY_VALUE = process.env.AUTH_APIKEY_VALUE;
interface TestEndpointArgs {
method: 'GET' | 'POST' | 'PUT' | 'DELETE';
endpoint: string;
body?: any;
headers?: Record<string, string>;
}
const isValidTestEndpointArgs = (args: any): args is TestEndpointArgs => {
if (typeof args !== 'object' || args === null) return false;
if (!['GET', 'POST', 'PUT', 'DELETE'].includes(args.method)) return false;
if (typeof args.endpoint !== 'string') return false;
if (args.headers !== undefined && typeof args.headers !== 'object') return false;
return true;
};
const hasBasicAuth = () => AUTH_BASIC_USERNAME && AUTH_BASIC_PASSWORD;
const hasBearerAuth = () => !!AUTH_BEARER;
const hasApiKeyAuth = () => AUTH_APIKEY_HEADER_NAME && AUTH_APIKEY_VALUE;
class FunctionAppTester {
private server: Server;
private axiosInstance;
constructor() {
this.server = new Server(
{
name: 'function-app-tester',
version: '0.1.0',
},
{
capabilities: {
tools: {},
},
}
);
this.axiosInstance = axios.create({
baseURL: BASE_URL,
validateStatus: () => true, // Allow any status code
});
this.setupToolHandlers();
this.server.onerror = (error) => console.error('[MCP Error]', error);
process.on('SIGINT', async () => {
await this.server.close();
process.exit(0);
});
}
private setupToolHandlers() {
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'test_endpoint',
description: `Test a Function App endpoint and get detailed response information. The endpoint will be prepended to the base url which is: ${BASE_URL}`,
inputSchema: {
type: 'object',
properties: {
method: {
type: 'string',
enum: ['GET', 'POST', 'PUT', 'DELETE'],
description: 'HTTP method to use',
},
endpoint: {
type: 'string',
description: 'Endpoint path (e.g. "/users"). Will be appended to base URL.',
},
body: {
type: 'object',
description: 'Optional request body for POST/PUT requests',
},
headers: {
type: 'object',
description: 'Optional request headers',
additionalProperties: {
type: 'string',
},
},
},
required: ['method', 'endpoint'],
},
},
],
}));
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
if (request.params.name !== 'test_endpoint') {
throw new McpError(
ErrorCode.MethodNotFound,
`Unknown tool: ${request.params.name}`
);
}
if (!isValidTestEndpointArgs(request.params.arguments)) {
throw new McpError(
ErrorCode.InvalidParams,
'Invalid test endpoint arguments'
);
}
try {
const config: AxiosRequestConfig = {
method: request.params.arguments.method as Method,
url: request.params.arguments.endpoint,
headers: request.params.arguments.headers || {},
};
if (['POST', 'PUT'].includes(request.params.arguments.method) && request.params.arguments.body) {
config.data = request.params.arguments.body;
}
// Handle authentication based on environment variables
if (hasBasicAuth()) {
const base64Credentials = Buffer.from(`${AUTH_BASIC_USERNAME}:${AUTH_BASIC_PASSWORD}`).toString('base64');
config.headers = {
...config.headers,
'Authorization': `Basic ${base64Credentials}`
};
} else if (hasBearerAuth()) {
config.headers = {
...config.headers,
'Authorization': `Bearer ${AUTH_BEARER}`
};
} else if (hasApiKeyAuth()) {
config.headers = {
...config.headers,
[AUTH_APIKEY_HEADER_NAME as string]: AUTH_APIKEY_VALUE
};
}
// Ensure endpoint starts with / and remove any trailing slashes
const normalizedEndpoint = `/${request.params.arguments.endpoint.replace(/^\/+|\/+$/g, '')}`;
config.url = normalizedEndpoint;
const response = await this.axiosInstance.request(config);
const fullUrl = `${BASE_URL}${normalizedEndpoint}`;
return {
content: [
{
type: 'text',
text: JSON.stringify({
url: fullUrl, // Include the actual URL called
statusCode: response.status,
statusText: response.statusText,
headers: response.headers,
body: response.data,
}, null, 2),
},
],
};
} catch (error) {
if (axios.isAxiosError(error)) {
return {
content: [
{
type: 'text',
text: `Request failed: ${error.message}`,
},
],
isError: true,
};
}
throw error;
}
});
}
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error('Function App Tester MCP server running on stdio');
}
}
const server = new FunctionAppTester();
server.run().catch(console.error);
```