Skip to content
Merged
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
31 changes: 30 additions & 1 deletion .github/workflows/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,11 @@ jobs:
"pagesType": str,
"hasCI": bool,
}
OPTIONAL = {"npm": str}
OPTIONAL = {"npm": str, "archetype": str}
VALID_TYPES = {"cursor-plugin", "mcp-server"}
VALID_STATUS = {"experimental", "beta", "active", "maintenance", "deprecated", "archived"}
VALID_PAGES = {"static", "mkdocs", "none"}
VALID_ARCHETYPES = {"deployed-service"}
errors = []
for i, tool in enumerate(data):
label = "Entry %d (%s)" % (i, tool.get("name", "?"))
Expand All @@ -69,6 +70,19 @@ jobs:
pass
elif not all(isinstance(t, str) for t in tool.get("topics", [])):
errors.append("%s: topics must be array of strings" % label)
archetype = tool.get("archetype")
if "archetype" in tool:
if archetype not in VALID_ARCHETYPES:
errors.append("%s: archetype must be one of %s" % (label, VALID_ARCHETYPES))
if tool.get("type") != "mcp-server":
errors.append("%s: archetype is only valid for mcp-server entries" % label)
if tool.get("type") == "mcp-server":
npm = tool.get("npm")
if archetype == "deployed-service":
if "npm" in tool:
errors.append("%s: deployed-service must omit npm" % label)
elif not (isinstance(npm, str) and npm.strip()):
errors.append("%s: mcp-server entries must have a non-empty npm field" % label)
if errors:
for e in errors:
print("::error::" + e, file=sys.stderr)
Expand Down Expand Up @@ -128,6 +142,21 @@ jobs:
test -f /tmp/scaffold-test/ci-test-plugin/mcp-server/server.py
echo "Scaffold test passed"

python3 scaffold/create-tool.py \
--name "CI Deployed Service" \
--description "Automated deployed-service test" \
--type mcp-server \
--deployed-service \
--no-register \
--output /tmp/scaffold-service

test -f /tmp/scaffold-service/ci-deployed-service/.drift-check.json
test ! -f /tmp/scaffold-service/ci-deployed-service/.github/workflows/publish.yml
test -f /tmp/scaffold-service/ci-deployed-service/.github/workflows/release.yml
grep -q '"private": true' /tmp/scaffold-service/ci-deployed-service/package.json
grep -qv 'gh workflow run publish.yml' /tmp/scaffold-service/ci-deployed-service/.github/workflows/release.yml
echo "Deployed-service scaffold test passed"

- name: Scaffold YAML well-formed
run: |
# Catches Jinja2 trim_blocks bugs that collapse adjacent expression lines.
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,11 @@ flowchart LR
| **Monday Cursor Plugin** | Plugin | 21 | 8 | 45 | [![Repo](https://img.shields.io/badge/repo-blue?logo=github)](https://github.com/TMHSDigital/Monday-Cursor-Plugin) [![Docs](https://img.shields.io/badge/docs-7c3aed)](https://tmhsdigital.github.io/Monday-Cursor-Plugin/) |
| **Steam Cursor Plugin** | Plugin | 30 | 9 | 25 | [![Repo](https://img.shields.io/badge/repo-blue?logo=github)](https://github.com/TMHSDigital/Steam-Cursor-Plugin) [![Docs](https://img.shields.io/badge/docs-7c3aed)](https://tmhsdigital.github.io/Steam-Cursor-Plugin/) |
| **Steam MCP Server** | MCP Server | - | - | 25 | [![Repo](https://img.shields.io/badge/repo-blue?logo=github)](https://github.com/TMHSDigital/steam-mcp) [![npm](https://img.shields.io/badge/npm-cb3837?logo=npm&logoColor=white)](https://www.npmjs.com/package/@tmhs/steam-mcp) |
| **Developer Tools MCP** | MCP Server | - | - | 7 | [![Repo](https://img.shields.io/badge/repo-blue?logo=github)](https://github.com/TMHSDigital/Developer-Tools-MCP) [![Docs](https://img.shields.io/badge/docs-7c3aed)](https://tmhsdigital.github.io/Developer-Tools-MCP/) |
| **CFX MCP** | MCP Server | - | - | 0 | [![Repo](https://img.shields.io/badge/repo-blue?logo=github)](https://github.com/TMHSDigital/cfx-mcp) |
| **Developer Tools MCP** | MCP Server | - | - | 7 | [![Repo](https://img.shields.io/badge/repo-blue?logo=github)](https://github.com/TMHSDigital/Developer-Tools-MCP) [![Docs](https://img.shields.io/badge/docs-7c3aed)](https://tmhsdigital.github.io/Developer-Tools-MCP/) [![npm](https://img.shields.io/badge/npm-cb3837?logo=npm&logoColor=white)](https://www.npmjs.com/package/@tmhs/devtools-mcp) |
| **CFX MCP** | MCP Server | - | - | 0 | [![Repo](https://img.shields.io/badge/repo-blue?logo=github)](https://github.com/TMHSDigital/cfx-mcp) [![npm](https://img.shields.io/badge/npm-cb3837?logo=npm&logoColor=white)](https://www.npmjs.com/package/@tmhsdigital/cfx-mcp) |
| **Blender Developer Tools** | Plugin | 12 | 6 | 0 | [![Repo](https://img.shields.io/badge/repo-blue?logo=github)](https://github.com/TMHSDigital/Blender-Developer-Tools) |
| **Local AI MCP** | MCP Server | - | - | 16 | [![Repo](https://img.shields.io/badge/repo-blue?logo=github)](https://github.com/TMHSDigital/local-ai-mcp) [![Docs](https://img.shields.io/badge/docs-7c3aed)](https://tmhsdigital.github.io/local-ai-mcp/) [![npm](https://img.shields.io/badge/npm-cb3837?logo=npm&logoColor=white)](https://www.npmjs.com/package/@tmhs/local-ai-mcp) |
| **Screencast MCP** | MCP Server | - | - | 25 | [![Repo](https://img.shields.io/badge/repo-blue?logo=github)](https://github.com/TMHSDigital/screencast-mcp) [![Docs](https://img.shields.io/badge/docs-7c3aed)](https://tmhsdigital.github.io/screencast-mcp/) |
| **Screencast MCP** | MCP Server | - | - | 25 | [![Repo](https://img.shields.io/badge/repo-blue?logo=github)](https://github.com/TMHSDigital/screencast-mcp) [![Docs](https://img.shields.io/badge/docs-7c3aed)](https://tmhsdigital.github.io/screencast-mcp/) [![npm](https://img.shields.io/badge/npm-cb3837?logo=npm&logoColor=white)](https://www.npmjs.com/package/@tmhs/screencast-mcp) |
| **Tailscale MCP** | MCP Server | - | - | 6 | [![Repo](https://img.shields.io/badge/repo-blue?logo=github)](https://github.com/TMHSDigital/tailscale-mcp) [![Docs](https://img.shields.io/badge/docs-7c3aed)](https://tmhsdigital.github.io/tailscale-mcp/) [![npm](https://img.shields.io/badge/npm-cb3837?logo=npm&logoColor=white)](https://www.npmjs.com/package/@tmhs/tailscale-mcp) |
| **Godot Correctness MCP** | MCP Server | - | - | 10 | [![Repo](https://img.shields.io/badge/repo-blue?logo=github)](https://github.com/TMHSDigital/godot-correctness-mcp) [![Docs](https://img.shields.io/badge/docs-7c3aed)](https://tmhsdigital.github.io/godot-correctness-mcp/) [![npm](https://img.shields.io/badge/npm-cb3837?logo=npm&logoColor=white)](https://www.npmjs.com/package/@tmhs/godot-correctness-mcp) |
| **Bounty Radar MCP** | MCP Server | - | - | 1 | [![Repo](https://img.shields.io/badge/repo-blue?logo=github)](https://github.com/TMHSDigital/bounty-radar-mcp) [![Docs](https://img.shields.io/badge/docs-7c3aed)](https://tmhsdigital.github.io/bounty-radar-mcp/) |
Expand Down
2 changes: 1 addition & 1 deletion STANDARDS_VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.10.0
1.10.1
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.18.0
1.19.0
6 changes: 3 additions & 3 deletions docs/index.html

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/search-index.json

Large diffs are not rendered by default.

12 changes: 8 additions & 4 deletions registry.json
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,8 @@
"language": "TypeScript",
"license": "CC-BY-NC-ND-4.0",
"pagesType": "static",
"hasCI": true
"hasCI": true,
"npm": "@tmhs/devtools-mcp"
},
{
"name": "CFX MCP",
Expand All @@ -267,7 +268,8 @@
"language": "TypeScript",
"license": "CC-BY-NC-ND-4.0",
"pagesType": "none",
"hasCI": true
"hasCI": true,
"npm": "@tmhsdigital/cfx-mcp"
},
{
"name": "Blender Developer Tools",
Expand Down Expand Up @@ -343,7 +345,8 @@
"language": "TypeScript",
"license": "CC-BY-NC-ND-4.0",
"pagesType": "static",
"hasCI": true
"hasCI": true,
"npm": "@tmhs/screencast-mcp"
},
{
"name": "Tailscale MCP",
Expand Down Expand Up @@ -417,6 +420,7 @@
"language": "TypeScript",
"license": "CC-BY-NC-ND-4.0",
"pagesType": "static",
"hasCI": true
"hasCI": true,
"archetype": "deployed-service"
}
]
10 changes: 10 additions & 0 deletions scaffold/create-tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ def parse_args():
Examples:
python create-tool.py --name "Unreal Developer Tools" --description "Cursor plugin for Unreal Engine"
python create-tool.py --name "AWS MCP Server" --type mcp-server --mcp-server
python create-tool.py --name "Hosted Radar" --type mcp-server --deployed-service
python create-tool.py --name "K8s Developer Tools" --mcp-server --skills 5 --rules 3
python create-tool.py --name "Throwaway" --description test --no-register
""",
Expand All @@ -55,6 +56,14 @@ def parse_args():
help="Repository type (default: cursor-plugin)",
)
parser.add_argument("--mcp-server", action="store_true", help="Include MCP server scaffold")
parser.add_argument(
"--deployed-service",
action="store_true",
help=(
"mcp-server archetype: deployed HTTP service. Omits publish.yml and "
"the registry npm field; writes .drift-check.json. Requires --type mcp-server."
),
)
parser.add_argument("--skills", type=int, default=0, help="Number of placeholder skill directories to create")
parser.add_argument("--rules", type=int, default=0, help="Number of placeholder rule files to create")
parser.add_argument(
Expand Down Expand Up @@ -115,6 +124,7 @@ def main() -> int:
author_email=args.author_email,
register=not args.no_register,
registry_root=Path(args.registry_root) if args.registry_root else None,
deployed_service=args.deployed_service,
)
except ScaffoldError as exc:
print(f"Error: {exc}", file=sys.stderr)
Expand Down
26 changes: 24 additions & 2 deletions scaffold/generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ def build_context(
license_key: str,
author_name: str,
author_email: str,
deployed_service: bool = False,
) -> dict[str, Any]:
standards_version = read_standards_version()
meta_major, meta_minor, meta_patch = read_meta_version()
Expand All @@ -153,6 +154,7 @@ def build_context(
"meta_patch": meta_patch,
"meta_version": f"{meta_major}.{meta_minor}.{meta_patch}",
"year": datetime.datetime.now(datetime.timezone.utc).year,
"deployed_service": deployed_service,
}


Expand Down Expand Up @@ -191,7 +193,15 @@ def _render_repo(output_dir: Path, ctx: dict, *, verbose: bool) -> None:
# plus the two optional-for-both workflows (pages, label-sync). mcp-server
# repos do NOT get cursor-plugin-specific validate.yml / release.yml.
if repo_type == "mcp-server":
write_file(output_dir, ".github/workflows/publish.yml", render_template(env, "publish.yml.j2", ctx), verbose=verbose)
if ctx.get("deployed_service"):
write_file(
output_dir,
".drift-check.json",
json.dumps({"archetype": "deployed-service"}, indent=2) + "\n",
verbose=verbose,
)
else:
write_file(output_dir, ".github/workflows/publish.yml", render_template(env, "publish.yml.j2", ctx), verbose=verbose)
write_file(output_dir, ".github/workflows/ci.yml", render_template(env, "ci.yml.j2", ctx), verbose=verbose)
write_file(output_dir, ".github/workflows/release.yml", render_template(env, "release.mcp.yml.j2", ctx), verbose=verbose)
write_file(output_dir, ".github/workflows/pages.yml", render_template(env, "pages.mcp.yml.j2", ctx), verbose=verbose)
Expand Down Expand Up @@ -279,6 +289,7 @@ def build_registry_entry(
skill_count: int,
rule_count: int,
license_key: str,
deployed_service: bool = False,
) -> dict[str, Any]:
"""Build a schema-valid registry.json entry for a generated repo.

Expand All @@ -287,7 +298,7 @@ def build_registry_entry(
a fresh repo has no MCP tools yet so ``mcpTools`` is 0.
"""
homepage = f"https://{REPO_OWNER.lower()}.github.io/{slug}/"
return {
entry: dict[str, Any] = {
"name": name,
"repo": f"{REPO_OWNER}/{slug}",
"slug": slug,
Expand All @@ -305,6 +316,12 @@ def build_registry_entry(
"pagesType": "static",
"hasCI": True,
}
if repo_type == "mcp-server":
if deployed_service:
entry["archetype"] = "deployed-service"
else:
entry["npm"] = f"@tmhs/{slug}"
return entry


def register_in_registry(
Expand Down Expand Up @@ -375,6 +392,7 @@ def generate_repo(
register: bool = True,
registry_root: Optional[Path] = None,
verbose: bool = True,
deployed_service: bool = False,
) -> Path:
"""Render a complete, standards-compliant repo and (by default) register
it in the meta catalog. Returns the path to the generated repo.
Expand All @@ -385,6 +403,8 @@ def generate_repo(
"""
if repo_type not in ("cursor-plugin", "mcp-server"):
raise ScaffoldError(f"unknown repo type: {repo_type!r}")
if deployed_service and repo_type != "mcp-server":
raise ScaffoldError("--deployed-service requires --type mcp-server")
if license_key not in LICENSE_FILES:
raise ScaffoldError(f"unknown license: {license_key!r}")

Expand All @@ -410,6 +430,7 @@ def generate_repo(
license_key=license_key,
author_name=author_name,
author_email=author_email,
deployed_service=deployed_service,
)

if verbose:
Expand All @@ -432,6 +453,7 @@ def generate_repo(
skill_count=len(skill_names),
rule_count=len(rule_names),
license_key=license_key,
deployed_service=deployed_service,
)
register_in_registry(Path(root), entry, run_sync=True, verbose=verbose)

Expand Down
4 changes: 4 additions & 0 deletions scaffold/templates/README.md.j2
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,14 @@ pip install -r requirements.txt

## Installation

{% if deployed_service %}
This is a deployed HTTP service, not an npm package. Run it from source or a container; there is no `npm install`.
{% else %}
```bash
npm install {{ slug }}
```
{% endif %}
{% endif %}

## Project Structure

Expand Down
11 changes: 9 additions & 2 deletions scaffold/templates/package.json.j2
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
{
{% if deployed_service %}
"name": "{{ slug }}",
"private": true,
{% else %}
"name": "@tmhs/{{ slug }}",
{% endif %}
"version": "0.1.0",
"description": "{{ description }}",
"type": "module",
"main": "dist/index.js",
{% if not deployed_service %}
"bin": {"{{ slug }}": "dist/index.js"},
"files": ["dist", "README.md", "LICENSE"],
{% endif %}
"scripts": {
"build": "echo 'No build target yet'",
"test": "echo 'No tests yet' && exit 0",
"prepublishOnly": "npm run build"
"test": "echo 'No tests yet' && exit 0"{% if not deployed_service %},
"prepublishOnly": "npm run build"{% endif %}
},
"author": "{{ author_name }}",
"license": "{{ license_spdx }}",
Expand Down
15 changes: 13 additions & 2 deletions scaffold/templates/release.mcp.yml.j2
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,17 @@ jobs:
name: Tag and release
runs-on: ubuntu-latest
steps:
{% endraw %}
{% if deployed_service %}
# Deployed services have no npm artifact; tagging is the release.
# main is protected and is NEVER written to by CI; bump the version in your PR.
{% else %}
# The default GITHUB_TOKEN (contents: write) pushes tags and creates the
# release. main is protected and is NEVER written to by CI; bump the
# version in your PR. A GITHUB_TOKEN release does not trigger publish.yml,
# so this job dispatches it explicitly afterward.
{% endif %}
{% raw %}
- uses: actions/checkout@v6
with:
fetch-depth: 0
Expand All @@ -43,7 +50,9 @@ jobs:
echo "skip=false" >> "$GITHUB_OUTPUT"
fi

- name: Tag, release, and trigger publish
{% endraw %}
- name: {% if deployed_service %}Tag and release{% else %}Tag, release, and trigger publish{% endif %}
{% raw %}
if: steps.check.outputs.skip == 'false'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Expand All @@ -60,7 +69,9 @@ jobs:
git push origin "v$major" --force
git push origin "v$major.$minor" --force
gh release create "v$v" --title "v$v" --generate-notes
{% endraw %}
{% if not deployed_service %}
# publish.yml is idempotent (skips an already-published version), so a
# duplicate dispatch is harmless.
gh workflow run publish.yml --ref main
{% endraw %}
{% endif %}
6 changes: 6 additions & 0 deletions scaffold/templates/site.json.j2
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,16 @@
"Open Cursor IDE and go to <code>Settings</code> > <code>Extensions</code>",
"Search for <code>{{ name }}</code>",
"Click <code>Install</code> and reload"
{% else %}
{% if deployed_service %}
"Deploy the HTTP service and point your MCP client at its /mcp endpoint",
"Add the remote URL to your Claude Desktop or Cursor mcpServers config",
"Restart your MCP client"
{% else %}
"Install with <code>npx -y @tmhs/{{ slug }}</code>",
"Add to your Claude Desktop or Cursor mcpServers config",
"Restart your MCP client"
{% endif %}
{% endif %}
]
}
2 changes: 1 addition & 1 deletion scripts/drift_check/checks/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Session A shipped ``version_signal``. Session B adds ``broken_refs``,
``required_refs``, and ``stale_counts``. ``required_workflows`` was added
in v1.10.0.
in v1.10.0. Deployed-service archetype support landed in v1.11.0.
"""
from .broken_refs import BrokenRefsCheck
from .required_refs import RequiredRefsCheck
Expand Down
Loading
Loading