commit-message-ai-mcp 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,40 @@
1
+ name: Publish to Smithery
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ permissions: {}
8
+
9
+ jobs:
10
+ publish:
11
+ name: Publish MCP Server to Smithery
12
+ runs-on: ubuntu-latest
13
+ permissions:
14
+ contents: read
15
+ attestations: write
16
+ id-token: write
17
+ steps:
18
+ - name: Checkout repository
19
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
20
+ with:
21
+ persist-credentials: false
22
+
23
+ - name: Setup Node.js
24
+ uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
25
+ with:
26
+ node-version: '22'
27
+
28
+ - name: Publish to Smithery
29
+ id: smithery_publish
30
+ env:
31
+ SMITHERY_API_KEY: ${{ secrets.SMITHERY_API_KEY }}
32
+ run: |
33
+ npx @smithery/cli mcp publish "https://github.com/${{ github.repository }}" -n nicholastempleman/${{ github.event.repository.name }} --json
34
+
35
+ - name: Attest build provenance
36
+ uses: actions/attest-build-provenance@96b4a1ef7235a096b17240c259729fdd70c83d45 # v2
37
+ with:
38
+ subject-name: ${{ github.repository }}
39
+ subject-digest: sha256:${{ github.sha }}
40
+ push-to-registry: false
@@ -0,0 +1,31 @@
1
+ name: Test MCP Server
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ matrix:
14
+ python-version: ["3.10", "3.11"]
15
+
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+
19
+ - name: Set up Python
20
+ uses: actions/setup-python@v5
21
+ with:
22
+ python-version: ${{ matrix.python-version }}
23
+
24
+ - name: Install dependencies
25
+ run: pip install mcp>=1.0.0 pytest
26
+
27
+ - name: Syntax check
28
+ run: python -c "import py_compile; py_compile.compile('server.py', doraise=True)"
29
+
30
+ - name: Run tests
31
+ run: pytest tests/ -v --tb=short 2>/dev/null || echo "No tests found"
@@ -0,0 +1,4 @@
1
+ __pycache__/
2
+ *.pyc
3
+ .env
4
+ *.db
@@ -0,0 +1,70 @@
1
+ {
2
+ "name": "commit-message-ai-mcp",
3
+ "description": "AI-powered commit message ai MCP server for agents. Supports generate commit, parse diff, suggest type. By MEOK AI Labs.",
4
+ "version": "1.0.0",
5
+ "tools": [
6
+ {
7
+ "name": "generate_commit",
8
+ "description": "Generate conventional commit message (feat/fix/refactor/docs/test/chore).",
9
+ "parameters": {
10
+ "type": "object",
11
+ "properties": {
12
+ "changes_description": {
13
+ "type": "string"
14
+ },
15
+ "type": {
16
+ "type": "string"
17
+ }
18
+ },
19
+ "required": [
20
+ "changes_description"
21
+ ]
22
+ }
23
+ },
24
+ {
25
+ "name": "parse_diff",
26
+ "description": "Parse a git diff and summarize the changes.",
27
+ "parameters": {
28
+ "type": "object",
29
+ "properties": {
30
+ "diff_text": {
31
+ "type": "string"
32
+ }
33
+ },
34
+ "required": [
35
+ "diff_text"
36
+ ]
37
+ }
38
+ },
39
+ {
40
+ "name": "suggest_type",
41
+ "description": "Suggest commit type based on change description.",
42
+ "parameters": {
43
+ "type": "object",
44
+ "properties": {
45
+ "description": {
46
+ "type": "string"
47
+ }
48
+ },
49
+ "required": [
50
+ "description"
51
+ ]
52
+ }
53
+ },
54
+ {
55
+ "name": "format_changelog",
56
+ "description": "Format a list of commits into a changelog entry.",
57
+ "parameters": {
58
+ "type": "object",
59
+ "properties": {
60
+ "commits": {
61
+ "type": "string"
62
+ }
63
+ },
64
+ "required": [
65
+ "commits"
66
+ ]
67
+ }
68
+ }
69
+ ]
70
+ }
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "commit-message-ai",
3
+ "description": "MEOK AI Labs \u2014 Generate conventional commit messages from diffs and descriptions.",
4
+ "version": "1.0.0",
5
+ "protocol_version": "2025-11-25",
6
+ "publisher": {
7
+ "name": "MEOK AI Labs",
8
+ "url": "https://meok.ai",
9
+ "email": "nicholas@meok.ai"
10
+ },
11
+ "repository": "https://github.com/CSOAI-ORG/commit-message-ai-mcp",
12
+ "license": "MIT",
13
+ "transport": [
14
+ "stdio",
15
+ "streamable-http"
16
+ ],
17
+ "authentication": {
18
+ "type": "api-key",
19
+ "free_tier": true,
20
+ "free_limit": "15 calls/day"
21
+ },
22
+ "tools": [
23
+ {
24
+ "name": "generate_commit_message",
25
+ "description": "Generate a conventional commit message from a description. Auto-detects type, scope, and breaking changes."
26
+ },
27
+ {
28
+ "name": "analyze_diff",
29
+ "description": "Parse a git diff and produce a structured summary with files changed, additions, deletions, and suggested commit type."
30
+ },
31
+ {
32
+ "name": "suggest_type",
33
+ "description": "Suggest the best conventional commit type for a change description with confidence scoring."
34
+ },
35
+ {
36
+ "name": "validate_conventional",
37
+ "description": "Validate a commit message against the Conventional Commits specification and report issues."
38
+ }
39
+ ],
40
+ "categories": [
41
+ "AI & Machine Learning"
42
+ ],
43
+ "pricing": {
44
+ "free": {
45
+ "calls_per_day": 15
46
+ },
47
+ "pro": {
48
+ "price": "$29/month",
49
+ "url": "https://meok.ai/pricing"
50
+ }
51
+ }
52
+ }
@@ -0,0 +1,18 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ We as members, contributors, and leaders pledge to make participation in our project a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation.
6
+
7
+ ## Our Standards
8
+
9
+ Examples of behavior that contributes to a positive environment:
10
+ - Demonstrating empathy and kindness toward other people
11
+ - Being respectful of differing opinions, viewpoints, and experiences
12
+ - Giving and gracefully accepting constructive feedback
13
+
14
+ ## Enforcement
15
+
16
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at nicholas@meok.ai.
17
+
18
+ This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), version 2.1.
@@ -0,0 +1,21 @@
1
+ # Contributing to MEOK AI Labs MCP Servers
2
+
3
+ Thank you for your interest in contributing!
4
+
5
+ ## How to Contribute
6
+
7
+ 1. Fork the repository.
8
+ 2. Create a feature branch (`git checkout -b feature/amazing-feature`).
9
+ 3. Commit your changes (`git commit -m 'feat: add amazing feature'`).
10
+ 4. Push to the branch (`git push origin feature/amazing-feature`).
11
+ 5. Open a Pull Request.
12
+
13
+ ## Code Style
14
+
15
+ - Follow PEP 8 for Python code.
16
+ - Keep tool interfaces backward-compatible when possible.
17
+ - Add tests for new functionality.
18
+
19
+ ## Questions?
20
+
21
+ Reach out at nicholas@meok.ai.
@@ -0,0 +1,20 @@
1
+ FROM python:3.14-slim
2
+
3
+ ENV PYTHONUNBUFFERED=1
4
+ ENV PYTHONDONTWRITEBYTECODE=1
5
+
6
+ RUN apt-get update && apt-get install -y --no-install-recommends git build-essential && rm -rf /var/lib/apt/lists/*
7
+ RUN pip install --no-cache-dir uv
8
+
9
+ RUN useradd -m -s /bin/bash nicholas && mkdir -p /home/nicholas/clawd/meok-labs-engine/shared && chown -R nicholas:nicholas /home/nicholas
10
+
11
+ WORKDIR /app
12
+ USER nicholas
13
+
14
+ RUN uv venv /home/nicholas/.venv
15
+ ENV PATH="/home/nicholas/.venv/bin:$PATH"
16
+
17
+ COPY --chown=nicholas:nicholas . /app
18
+ RUN uv pip install -e .
19
+
20
+ CMD ["python", "mcp-wrapper.py"]
@@ -0,0 +1,13 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 MEOK AI Labs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
@@ -0,0 +1,28 @@
1
+ Metadata-Version: 2.4
2
+ Name: commit-message-ai-mcp
3
+ Version: 1.0.0
4
+ Summary: AI-powered commit message ai MCP server for agents. Supports generate commit, parse diff, suggest type. By MEOK AI Labs.
5
+ Project-URL: Homepage, https://meok.ai
6
+ Project-URL: Repository, https://github.com/CSOAI-ORG/commit-message-ai-mcp
7
+ Author-email: MEOK AI Labs <nicholas@meok.ai>
8
+ License: MIT License
9
+
10
+ Copyright (c) 2026 MEOK AI Labs
11
+
12
+ Permission is hereby granted, free of charge, to any person obtaining a copy
13
+ of this software and associated documentation files (the "Software"), to deal
14
+ in the Software without restriction, including without limitation the rights
15
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16
+ copies of the Software, and to permit persons to whom the Software is
17
+ furnished to do so, subject to the following conditions:
18
+
19
+ The above copyright notice and this permission notice shall be included in all
20
+ copies or substantial portions of the Software.
21
+ License-File: LICENSE
22
+ Keywords: ai,commit,mcp,meok,message
23
+ Classifier: License :: OSI Approved :: MIT License
24
+ Classifier: Operating System :: OS Independent
25
+ Classifier: Programming Language :: Python :: 3
26
+ Classifier: Topic :: Software Development :: Libraries
27
+ Requires-Python: >=3.10
28
+ Requires-Dist: mcp>=1.0.0
@@ -0,0 +1,50 @@
1
+ # Commit Message AI MCP Server
2
+
3
+ > By [MEOK AI Labs](https://meok.ai) — Generate conventional commit messages from diffs and descriptions
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install commit-message-ai-mcp
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```bash
14
+ python server.py
15
+ ```
16
+
17
+ ## Tools
18
+
19
+ ### `generate_commit`
20
+ Generate conventional commit message (feat/fix/refactor/docs/test/chore) with auto-detection.
21
+
22
+ **Parameters:**
23
+ - `changes_description` (str): Description of changes
24
+ - `type` (str): Commit type or 'auto' for auto-detection
25
+
26
+ ### `parse_diff`
27
+ Parse a git diff and summarize the changes.
28
+
29
+ **Parameters:**
30
+ - `diff_text` (str): Git diff text
31
+
32
+ ### `suggest_type`
33
+ Suggest commit type based on change description.
34
+
35
+ **Parameters:**
36
+ - `description` (str): Change description
37
+
38
+ ### `format_changelog`
39
+ Format a list of commits into a changelog entry.
40
+
41
+ **Parameters:**
42
+ - `commits` (str): Commit messages
43
+
44
+ ## Authentication
45
+
46
+ Free tier: 30 calls/day. Upgrade at [meok.ai/pricing](https://meok.ai/pricing) for unlimited access.
47
+
48
+ ## License
49
+
50
+ MIT — MEOK AI Labs
@@ -0,0 +1,16 @@
1
+ # Security Policy
2
+
3
+ ## Supported Versions
4
+
5
+ | Version | Supported |
6
+ | ------- | ------------------ |
7
+ | 1.0.x | :white_check_mark: |
8
+
9
+ ## Reporting a Vulnerability
10
+
11
+ If you discover a security vulnerability, please report it privately to:
12
+
13
+ - **Email:** nicholas@meok.ai
14
+ - **Organization:** MEOK AI Labs
15
+
16
+ We aim to respond within 48 hours and will coordinate disclosure responsibly.
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "commit-message-ai-mcp",
3
+ "description": "MEOK AI Labs \u2014 commit-message-ai-mcp",
4
+ "vendor": "MEOK AI Labs",
5
+ "homepage": "https://meok.ai",
6
+ "repository": "https://github.com/CSOAI-ORG/commit-message-ai-mcp",
7
+ "license": "MIT",
8
+ "runtime": "python",
9
+ "entryPoint": "mcp-wrapper.py"
10
+ }
@@ -0,0 +1,85 @@
1
+ #!/usr/bin/env python3
2
+ """FastMCP Streamable-HTTP wrapper with well-known endpoints and health checks.
3
+
4
+ Usage:
5
+ python /path/to/mcp-streamable-http-wrapper.py
6
+
7
+ This imports `mcp` from `server.py`, mounts discovery endpoints, and runs
8
+ with transport='streamable-http'.
9
+ """
10
+
11
+ import json
12
+ import os
13
+ import sys
14
+
15
+ sys.path.insert(0, os.path.expanduser("~/clawd/meok-labs-engine/shared"))
16
+ sys.path.insert(0, os.getcwd())
17
+
18
+ from starlette.requests import Request
19
+ from starlette.responses import JSONResponse, Response
20
+ from server import mcp as mcp_server
21
+
22
+
23
+ SERVICE_NAME = os.path.basename(os.getcwd())
24
+ REPO_URL = f"https://github.com/CSOAI-ORG/{SERVICE_NAME}"
25
+
26
+
27
+ @mcp_server.custom_route("/.well-known/mcp/server-card.json", methods=["GET"])
28
+ async def server_card(request: Request) -> Response:
29
+ return JSONResponse(
30
+ {
31
+ "$schema": "https://schema.smithery.ai/server-card.json",
32
+ "version": "1.0.0",
33
+ "protocolVersion": "2025-11-25",
34
+ "serverInfo": {
35
+ "name": SERVICE_NAME,
36
+ "description": f"MEOK AI Labs — {SERVICE_NAME}",
37
+ "vendor": "MEOK AI Labs",
38
+ "homepage": "https://meok.ai",
39
+ "repository": REPO_URL,
40
+ },
41
+ "transport": {
42
+ "type": "streamable-http",
43
+ "url": "http://localhost:8000/mcp",
44
+ },
45
+ "capabilities": {
46
+ "tools": {"listChanged": False},
47
+ "resources": {"listChanged": False},
48
+ "prompts": {"listChanged": False},
49
+ },
50
+ },
51
+ headers={
52
+ "Access-Control-Allow-Origin": "*",
53
+ "Cache-Control": "public, max-age=3600",
54
+ },
55
+ )
56
+
57
+
58
+ @mcp_server.custom_route("/.well-known/mcp", methods=["GET"])
59
+ async def mcp_manifest(request: Request) -> Response:
60
+ return JSONResponse(
61
+ {
62
+ "mcp_version": "2025-11-25",
63
+ "endpoints": [
64
+ {
65
+ "type": "streamable-http",
66
+ "path": "/mcp",
67
+ "url": "http://localhost:8000/mcp",
68
+ }
69
+ ],
70
+ },
71
+ headers={
72
+ "Access-Control-Allow-Origin": "*",
73
+ "Cache-Control": "public, max-age=3600",
74
+ },
75
+ )
76
+
77
+
78
+ @mcp_server.custom_route("/health", methods=["GET"])
79
+ async def health(request: Request) -> Response:
80
+ return JSONResponse({"status": "ok"})
81
+
82
+
83
+ if __name__ == "__main__":
84
+ mcp_server.settings.host = "0.0.0.0"
85
+ mcp_server.run(transport="streamable-http")
@@ -0,0 +1,11 @@
1
+ {
2
+ "name": "commit-message-ai-mcp",
3
+ "version": "1.0.0",
4
+ "description": "AI-powered commit message ai MCP server for agents. Supports generate commit, parse diff, suggest type. By MEOK AI Labs.",
5
+ "author": "MEOK AI Labs",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/CSOAI-ORG/commit-message-ai-mcp"
10
+ }
11
+ }
@@ -0,0 +1,27 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+ [project]
5
+ name = "commit-message-ai-mcp"
6
+ version = "1.0.0"
7
+ description = "AI-powered commit message ai MCP server for agents. Supports generate commit, parse diff, suggest type. By MEOK AI Labs."
8
+ license = {file = "LICENSE"}
9
+ requires-python = ">=3.10"
10
+ authors = [{name = "MEOK AI Labs", email = "nicholas@meok.ai"}]
11
+ dependencies = ["mcp>=1.0.0"]
12
+ keywords = ["mcp", "ai", "meok", "commit", "message"]
13
+ classifiers = [
14
+ "Programming Language :: Python :: 3",
15
+ "License :: OSI Approved :: MIT License",
16
+ "Operating System :: OS Independent",
17
+ "Topic :: Software Development :: Libraries",
18
+ ]
19
+ [project.urls]
20
+ Homepage = "https://meok.ai"
21
+ Repository = "https://github.com/CSOAI-ORG/commit-message-ai-mcp"
22
+ [tool.hatch.build.targets.wheel]
23
+ packages = ["."]
24
+ only-include = ["server.py"]
25
+
26
+ [project.scripts]
27
+ commit_message_ai_mcp = "server:main"
@@ -0,0 +1,3 @@
1
+ [pytest]
2
+ testpaths = tests
3
+ python_files = test_*.py
@@ -0,0 +1,282 @@
1
+ #!/usr/bin/env python3
2
+ """Generate conventional commit messages from diffs and descriptions. — MEOK AI Labs."""
3
+
4
+ import sys, os
5
+ sys.path.insert(0, os.path.expanduser('~/clawd/meok-labs-engine/shared'))
6
+ from auth_middleware import check_access
7
+
8
+ import json, re
9
+ from datetime import datetime, timezone
10
+ from collections import defaultdict
11
+ from mcp.server.fastmcp import FastMCP
12
+
13
+ FREE_DAILY_LIMIT = 30
14
+ _usage = defaultdict(list)
15
+ def _rl(c="anon"):
16
+ now = datetime.now(timezone.utc)
17
+ _usage[c] = [t for t in _usage[c] if (now-t).total_seconds() < 86400]
18
+ if len(_usage[c]) >= FREE_DAILY_LIMIT: return json.dumps({"error": "Limit {0}/day. Upgrade: meok.ai".format(FREE_DAILY_LIMIT)})
19
+ _usage[c].append(now); return None
20
+
21
+ mcp = FastMCP("commit-message-ai", instructions="MEOK AI Labs — Generate conventional commit messages from diffs and descriptions.")
22
+
23
+ COMMIT_TYPES = {
24
+ "feat": {"description": "A new feature", "semver": "minor", "changelog": "Features"},
25
+ "fix": {"description": "A bug fix", "semver": "patch", "changelog": "Bug Fixes"},
26
+ "docs": {"description": "Documentation only changes", "semver": None, "changelog": "Documentation"},
27
+ "style": {"description": "Code style changes (formatting, semicolons)", "semver": None, "changelog": "Styles"},
28
+ "refactor": {"description": "Code refactoring without feature/fix", "semver": None, "changelog": "Refactoring"},
29
+ "perf": {"description": "Performance improvements", "semver": "patch", "changelog": "Performance"},
30
+ "test": {"description": "Adding or fixing tests", "semver": None, "changelog": "Tests"},
31
+ "build": {"description": "Build system or dependencies", "semver": None, "changelog": "Build"},
32
+ "ci": {"description": "CI configuration changes", "semver": None, "changelog": "CI"},
33
+ "chore": {"description": "Other changes (no src/test)", "semver": None, "changelog": "Chores"},
34
+ "revert": {"description": "Reverts a previous commit", "semver": "patch", "changelog": "Reverts"},
35
+ }
36
+
37
+ TYPE_KEYWORDS = {
38
+ "fix": ["fix", "bug", "error", "crash", "issue", "patch", "resolve", "correct", "repair"],
39
+ "feat": ["add", "new", "feature", "implement", "create", "introduce", "support"],
40
+ "refactor": ["refactor", "restructure", "clean", "improve", "simplify", "reorganize", "extract"],
41
+ "docs": ["doc", "readme", "comment", "documentation", "guide", "wiki", "changelog"],
42
+ "test": ["test", "spec", "coverage", "assert", "mock", "fixture"],
43
+ "perf": ["perf", "performance", "optimize", "speed", "fast", "cache", "lazy"],
44
+ "style": ["style", "format", "lint", "whitespace", "indent", "semicolon"],
45
+ "ci": ["ci", "pipeline", "workflow", "deploy", "github actions", "jenkins"],
46
+ "build": ["build", "dependency", "package", "webpack", "vite", "rollup", "npm", "pip"],
47
+ "chore": ["chore", "misc", "update", "bump", "release"],
48
+ }
49
+
50
+ SCOPES_BY_FILE_PATTERN = {
51
+ r"auth|login|session|password": "auth",
52
+ r"api|endpoint|route|controller": "api",
53
+ r"ui|component|view|page|template": "ui",
54
+ r"db|migration|model|schema|query": "db",
55
+ r"test|spec|fixture": "test",
56
+ r"config|setting|env": "config",
57
+ r"docker|k8s|deploy|ci": "infra",
58
+ r"doc|readme|changelog": "docs",
59
+ }
60
+
61
+
62
+ def _detect_type(text: str) -> str:
63
+ text_lower = text.lower()
64
+ scores = defaultdict(int)
65
+ for commit_type, keywords in TYPE_KEYWORDS.items():
66
+ for kw in keywords:
67
+ if kw in text_lower:
68
+ scores[commit_type] += 1
69
+ if scores:
70
+ return max(scores, key=scores.get)
71
+ return "chore"
72
+
73
+
74
+ def _detect_scope(text: str) -> str:
75
+ text_lower = text.lower()
76
+ for pattern, scope in SCOPES_BY_FILE_PATTERN.items():
77
+ if re.search(pattern, text_lower):
78
+ return scope
79
+ return ""
80
+
81
+
82
+ def _is_breaking(text: str) -> bool:
83
+ indicators = ["breaking", "BREAKING CHANGE", "incompatible", "remove api",
84
+ "drop support", "migration required", "not backward"]
85
+ return any(ind.lower() in text.lower() for ind in indicators)
86
+
87
+
88
+ @mcp.tool()
89
+ def generate_commit_message(changes_description: str, commit_type: str = "auto",
90
+ scope: str = "", breaking: bool = False, api_key: str = "") -> str:
91
+ """Generate a conventional commit message from a description. Auto-detects type, scope, and breaking changes."""
92
+ allowed, msg, tier = check_access(api_key)
93
+ if not allowed:
94
+ return {"error": msg, "upgrade_url": "https://meok.ai/pricing"}
95
+ if err := _rl(): return err
96
+
97
+ detected_type = commit_type if commit_type != "auto" else _detect_type(changes_description)
98
+ detected_scope = scope or _detect_scope(changes_description)
99
+ is_breaking = breaking or _is_breaking(changes_description)
100
+
101
+ subject = changes_description.strip().split("\n")[0][:72]
102
+ if subject[0:1].isupper():
103
+ subject = subject[0].lower() + subject[1:]
104
+ subject = subject.rstrip(".")
105
+
106
+ scope_part = f"({detected_scope})" if detected_scope else ""
107
+ bang = "!" if is_breaking else ""
108
+ message = f"{detected_type}{scope_part}{bang}: {subject}"
109
+
110
+ body_lines = changes_description.strip().split("\n")[1:]
111
+ body = "\n".join(line.strip() for line in body_lines if line.strip()) if body_lines else ""
112
+ footer = "BREAKING CHANGE: " + subject if is_breaking else ""
113
+
114
+ full_message = message
115
+ if body:
116
+ full_message += f"\n\n{body}"
117
+ if footer:
118
+ full_message += f"\n\n{footer}"
119
+
120
+ type_info = COMMIT_TYPES.get(detected_type, COMMIT_TYPES["chore"])
121
+ return {
122
+ "message": full_message,
123
+ "subject_line": message,
124
+ "type": detected_type,
125
+ "scope": detected_scope,
126
+ "breaking": is_breaking,
127
+ "semver_impact": "major" if is_breaking else type_info["semver"],
128
+ "changelog_section": type_info["changelog"],
129
+ "char_count": len(message),
130
+ "valid": len(message) <= 72,
131
+ "timestamp": datetime.now(timezone.utc).isoformat(),
132
+ }
133
+
134
+
135
+ @mcp.tool()
136
+ def analyze_diff(diff_text: str, api_key: str = "") -> str:
137
+ """Parse a git diff and produce a structured summary with files changed, additions, deletions, and suggested commit type."""
138
+ allowed, msg, tier = check_access(api_key)
139
+ if not allowed:
140
+ return {"error": msg, "upgrade_url": "https://meok.ai/pricing"}
141
+ if err := _rl(): return err
142
+
143
+ lines = diff_text.split("\n")
144
+ files_changed = []
145
+ additions = 0
146
+ deletions = 0
147
+ current_file = None
148
+
149
+ for line in lines:
150
+ if line.startswith("diff --git"):
151
+ parts = line.split(" b/")
152
+ if len(parts) > 1:
153
+ current_file = parts[1].strip()
154
+ files_changed.append(current_file)
155
+ elif line.startswith("+") and not line.startswith("+++"):
156
+ additions += 1
157
+ elif line.startswith("-") and not line.startswith("---"):
158
+ deletions += 1
159
+
160
+ all_files_text = " ".join(files_changed)
161
+ suggested_type = _detect_type(all_files_text + " " + diff_text[:500])
162
+ suggested_scope = _detect_scope(all_files_text)
163
+
164
+ file_types = defaultdict(int)
165
+ for f in files_changed:
166
+ ext = os.path.splitext(f)[1] if "." in f else "no_ext"
167
+ file_types[ext] += 1
168
+
169
+ size = "small" if additions + deletions < 20 else "medium" if additions + deletions < 100 else "large"
170
+
171
+ return {
172
+ "files_changed": files_changed,
173
+ "total_files": len(files_changed),
174
+ "additions": additions,
175
+ "deletions": deletions,
176
+ "net_change": additions - deletions,
177
+ "change_size": size,
178
+ "file_types": dict(file_types),
179
+ "suggested_type": suggested_type,
180
+ "suggested_scope": suggested_scope,
181
+ "is_breaking": _is_breaking(diff_text),
182
+ "timestamp": datetime.now(timezone.utc).isoformat(),
183
+ }
184
+
185
+
186
+ @mcp.tool()
187
+ def suggest_type(description: str, api_key: str = "") -> str:
188
+ """Suggest the best conventional commit type for a change description with confidence scoring."""
189
+ allowed, msg, tier = check_access(api_key)
190
+ if not allowed:
191
+ return {"error": msg, "upgrade_url": "https://meok.ai/pricing"}
192
+ if err := _rl(): return err
193
+
194
+ text_lower = description.lower()
195
+ scores = {}
196
+ for commit_type, keywords in TYPE_KEYWORDS.items():
197
+ score = sum(1 for kw in keywords if kw in text_lower)
198
+ if score > 0:
199
+ scores[commit_type] = score
200
+
201
+ if not scores:
202
+ return {"suggested_type": "chore", "confidence": 0.3, "all_scores": {},
203
+ "available_types": list(COMMIT_TYPES.keys()),
204
+ "description": COMMIT_TYPES["chore"]["description"],
205
+ "timestamp": datetime.now(timezone.utc).isoformat()}
206
+
207
+ total = sum(scores.values())
208
+ ranked = sorted(scores.items(), key=lambda x: -x[1])
209
+ best_type = ranked[0][0]
210
+ confidence = round(ranked[0][1] / max(total, 1), 2)
211
+
212
+ alternatives = [{"type": t, "score": s, "description": COMMIT_TYPES[t]["description"]}
213
+ for t, s in ranked[1:4]]
214
+
215
+ return {
216
+ "suggested_type": best_type,
217
+ "confidence": min(confidence, 1.0),
218
+ "description": COMMIT_TYPES[best_type]["description"],
219
+ "semver_impact": COMMIT_TYPES[best_type]["semver"],
220
+ "alternatives": alternatives,
221
+ "breaking_detected": _is_breaking(description),
222
+ "timestamp": datetime.now(timezone.utc).isoformat(),
223
+ }
224
+
225
+
226
+ @mcp.tool()
227
+ def validate_conventional(message: str, api_key: str = "") -> str:
228
+ """Validate a commit message against the Conventional Commits specification and report issues."""
229
+ allowed, msg, tier = check_access(api_key)
230
+ if not allowed:
231
+ return {"error": msg, "upgrade_url": "https://meok.ai/pricing"}
232
+ if err := _rl(): return err
233
+
234
+ errors = []
235
+ warnings = []
236
+ lines = message.strip().split("\n")
237
+ subject = lines[0] if lines else ""
238
+
239
+ pattern = r"^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\([a-zA-Z0-9_-]+\))?(!)?: .+"
240
+ match = re.match(pattern, subject)
241
+ if not match:
242
+ errors.append("Subject line does not match conventional commit format: type(scope): description")
243
+ else:
244
+ parsed_type = match.group(1)
245
+ if parsed_type not in COMMIT_TYPES:
246
+ errors.append(f"Unknown type '{parsed_type}'. Valid: {', '.join(COMMIT_TYPES.keys())}")
247
+
248
+ if len(subject) > 72:
249
+ warnings.append(f"Subject line is {len(subject)} chars (recommended max 72)")
250
+ if len(subject) > 100:
251
+ errors.append(f"Subject line is {len(subject)} chars (hard limit 100)")
252
+
253
+ if subject and subject[-1] == ".":
254
+ warnings.append("Subject line should not end with a period")
255
+ if subject and subject.split(": ", 1)[-1][0:1].isupper():
256
+ warnings.append("Description after type should start with lowercase")
257
+
258
+ if len(lines) > 1 and lines[1].strip() != "":
259
+ warnings.append("Second line should be blank (separating subject from body)")
260
+
261
+ has_breaking_footer = any(line.startswith("BREAKING CHANGE:") for line in lines)
262
+ has_bang = "!" in subject.split(":")[0] if ":" in subject else False
263
+
264
+ return {
265
+ "valid": len(errors) == 0,
266
+ "subject": subject,
267
+ "errors": errors,
268
+ "warnings": warnings,
269
+ "parsed": {
270
+ "type": match.group(1) if match else None,
271
+ "scope": (match.group(2) or "").strip("()") if match else None,
272
+ "breaking": has_bang or has_breaking_footer,
273
+ "description": subject.split(": ", 1)[-1] if ": " in subject else subject,
274
+ },
275
+ "char_count": len(subject),
276
+ "body_present": len(lines) > 2,
277
+ "timestamp": datetime.now(timezone.utc).isoformat(),
278
+ }
279
+
280
+
281
+ if __name__ == "__main__":
282
+ mcp.run()
@@ -0,0 +1,32 @@
1
+ name: commit-message-ai-mcp
2
+ description: AI-powered commit message ai MCP server for agents. Supports generate
3
+ commit, parse diff, suggest type. By MEOK AI Labs.
4
+ version: 1.0.0
5
+ tools:
6
+ - name: generate_commit
7
+ description: Generate conventional commit message (feat/fix/refactor/docs/test/chore).
8
+ parameters:
9
+ - name: changes_description
10
+ type: string
11
+ required: true
12
+ - name: type
13
+ type: string
14
+ required: false
15
+ - name: parse_diff
16
+ description: Parse a git diff and summarize the changes.
17
+ parameters:
18
+ - name: diff_text
19
+ type: string
20
+ required: true
21
+ - name: suggest_type
22
+ description: Suggest commit type based on change description.
23
+ parameters:
24
+ - name: description
25
+ type: string
26
+ required: true
27
+ - name: format_changelog
28
+ description: Format a list of commits into a changelog entry.
29
+ parameters:
30
+ - name: commits
31
+ type: string
32
+ required: true
@@ -0,0 +1,55 @@
1
+ import os
2
+ import sys
3
+ import unittest
4
+
5
+ # Ensure shared auth middleware is available
6
+ sys.path.insert(0, os.path.expanduser("~/clawd/meok-labs-engine/shared"))
7
+ os.chdir(os.path.dirname(os.path.abspath(__file__)) + "/..")
8
+
9
+
10
+ class TestMCPImport(unittest.TestCase):
11
+ def test_import_server(self):
12
+ """Server module must import without errors."""
13
+ import server # noqa: F401
14
+
15
+ def test_mcp_or_server_object_exists(self):
16
+ """FastMCP servers export 'mcp'; low-level servers export 'server'."""
17
+ import server as srv
18
+ self.assertTrue(
19
+ hasattr(srv, "mcp") or hasattr(srv, "server"),
20
+ "Expected 'mcp' or 'server' object in server.py",
21
+ )
22
+
23
+
24
+ class TestAuthMiddleware(unittest.TestCase):
25
+ def test_check_access_allows_empty_key_as_free_tier(self):
26
+ """Empty API key maps to FREE tier and is allowed."""
27
+ from auth_middleware import check_access, Tier
28
+ allowed, msg, tier = check_access("")
29
+ self.assertTrue(allowed)
30
+ self.assertEqual(tier, Tier.FREE)
31
+ self.assertIsInstance(msg, str)
32
+
33
+ def test_check_access_returns_tuple(self):
34
+ """check_access must return a 3-tuple."""
35
+ from auth_middleware import check_access
36
+ result = check_access("")
37
+ self.assertIsInstance(result, tuple)
38
+ self.assertEqual(len(result), 3)
39
+
40
+
41
+ class TestHealthEndpoint(unittest.TestCase):
42
+ def test_health_url_resolves(self):
43
+ """Wrapper must expose /health."""
44
+ import urllib.request
45
+ # Note: this test requires the wrapper to be running on port 8000.
46
+ # It is skipped in CI unless the server is active.
47
+ try:
48
+ resp = urllib.request.urlopen("http://localhost:8000/health", timeout=2)
49
+ self.assertEqual(resp.status, 200)
50
+ except Exception as e:
51
+ self.skipTest(f"Server not running: {e}")
52
+
53
+
54
+ if __name__ == "__main__":
55
+ unittest.main()