{"id":28942419,"url":"https://github.com/darren277/relayops","last_synced_at":"2026-04-02T18:52:32.176Z","repository":{"id":64795355,"uuid":"400165494","full_name":"darren277/RelayOps","owner":"darren277","description":"Model Context Protocol for DevOps","archived":false,"fork":false,"pushed_at":"2025-06-13T11:48:48.000Z","size":225,"stargazers_count":0,"open_issues_count":7,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-06-13T12:40:10.809Z","etag":null,"topics":["abstract-factory-pattern","devops","flask","github","llm","mcp","slack","webhooks"],"latest_commit_sha":null,"homepage":"https://relayops.apphosting.services","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/darren277.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2021-08-26T12:44:43.000Z","updated_at":"2025-06-13T12:03:37.000Z","dependencies_parsed_at":"2025-02-16T14:35:33.160Z","dependency_job_id":"8545451e-4e6f-4832-8754-be7c2b671a35","html_url":"https://github.com/darren277/RelayOps","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/darren277/RelayOps","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/darren277%2FRelayOps","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/darren277%2FRelayOps/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/darren277%2FRelayOps/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/darren277%2FRelayOps/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/darren277","download_url":"https://codeload.github.com/darren277/RelayOps/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/darren277%2FRelayOps/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":261407221,"owners_count":23153918,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["abstract-factory-pattern","devops","flask","github","llm","mcp","slack","webhooks"],"created_at":"2025-06-23T03:44:23.425Z","updated_at":"2025-12-30T22:07:02.335Z","avatar_url":"https://github.com/darren277.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# About\n\nThis is a relay station for webhooks, currently predominantly GitHub and Slack.\n\nThis is also my first attempt at using an abstract factory pattern for easier extensibility.\n\nThis is a work in progress.\n\n**Please note:** LLM tool calls are very experimental at the moment.\n\n## MCP\n\nCheck out the very early draft of the **Model Context Protocol (MCP)** that I am using to define the envelope for all messages exchanged through this relay station: [docs/MCP.md](docs/MCP.md).\n\n# Usage\n\n1. Configure webhook for a GitHub repository (see Notes below).\n![](RepoWebhooks.png)\n\n2. Configure webhook for a Slack channel.\nCheck out the following resource for details:\nhttps://api.slack.com/messaging/webhooks\n\n3. Clone repository.\n\n4. Create a `.env` file for:\n   - `SLACK_RELAY_ENDPOINT`: The endpoint for all Slack messages (ex: \"https://hooks.slack.com/services/{STRING1}/{STRING2}/{STRING3}\").\n   - `GITHUB_REPO`: Repository to monitor for changes (ex: \"https://github.com/darren277/githubtoslack/\").\n   - `PORT`: Flask port.\n\n5. Create a virtual environment.\n   - For example, on Ubuntu:\n   1. `python3 -m venv venv`.\n   2. `source venv/bin/activate`.\n\n6. Install dependencies.\n   1. `pip3 install -r requirements.txt`.\n\n7. Run `python3 app.py`.\n\n## Slack to GitHub\n\nProvision a personal access token from GitHub Developer Settings.\n\nConfigure the slash command from https://api.slack.com/apps/\u003cAPP_ID\u003e/slash-commands.\n\n`/githubissue Fix user login bug`.\n\n## LLM Endpoint\n\nDon't forget to install RabbitMQ:\n\n```shell\nsudo apt-get install rabbitmq-server\nsudo systemctl start rabbitmq-server\nsudo systemctl enable rabbitmq-server\n```\n\nTo troubleshoot, check the logs:\n\n```shell\ncelery -A tasks.celery worker --loglevel=info\n```\n\n# Notes\n\nMine is deployed on an AWS EC2 instance so that GitHub has a remote URL to send to.\n\nSafely testing locally would almost require a tunneling service like ngrok.\n\n## LLM Use Cases\n\n### Structured Outputs\n\nI encountered an interesting scenario today. I was originally intending to use `pydantic` to define a structured model for OpenProject `WorkPackage` objects (i.e. Tasks, etc) that would be passed directly into the OpenAI chat completions API call. What occured instead, however, was I discovered a certain complication due to having multiple fundamentally different kinds of tool calls available, making structured output definitions trickier.\n\nSo what happened instead, was that I wound up creating a third tool call option, wherein I simply use a `pydantic` builtin method to convert the input to the desired structured output. It is experimental for the time being, so we'll see how it works.\n\n### Multiple Tools\n\nIt turns out that incorporating numerous fundamentally different kinds of tool calls can make things a bit messy.\n\nFor example, I found that the LLM was using tools that I really did not see as all that relevant for the given query.\n\n### Example Task Creation\n\nAs of right now, it unfortunately needs a pretty precise definition of some attributes in order to behave consistently. This is something I will experiment with as I go.\n\nSlack slash command example: `/llm create the following task: Title: \"Fix User Sign-Up Issue\". Start date: 2025-02-20. Due date: 2025-02-28. Estimated time: 5 hours.`.\n\n# TODO\n\n- Programmatically list endpoints.\n- Proper exception handling and logging.\n- Add more webhook types.\n\n\n## OpenProject API\n\nConsider validating attributes before sending request for a cleaner error experience.\n\n```json\n{\n   \"_type\":\"Error\",\n   \"errorIdentifier\":\"urn:openproject-org:api:v3:errors:MultipleErrors\",\n   \"message\":\"Multiple field constraints have been violated.\",\n   \"_embedded\":\n    {\n       \"errors\":\n        [\n           {\n              \"_type\":\"Error\",\n              \"errorIdentifier\":\"urn:openproject-org:api:v3:errors:PropertyConstraintViolation\",\n              \"message\":\"Project can't be blank.\",\n              \"_embedded\":{\"details\":{\"attribute\":\"project\"}}\n           },\n           {\n              \"_type\":\"Error\",\n              \"errorIdentifier\":\"urn:openproject-org:api:v3:errors:PropertyConstraintViolation\",\n              \"message\":\"Type can't be blank.\",\n              \"_embedded\":{\"details\":{\"attribute\":\"type\"}}\n           }\n        ]\n    }\n}\n```\n\n## SurrealDB\n\nTo run: `docker run --rm --pull always -p $(EXT_PORT):$(INT_PORT) surrealdb/surrealdb:latest start`.\n\nI will be using `EXT_PORT=8011` and `INT_PORT=8011` for both the external and internal port, but you can change this to whatever you like.\n\nTo run (hardcoded port): `docker run --rm --pull always -p 8011:8011 surrealdb/surrealdb:latest start --bind 0.0.0.0:8011 --user root --pass root`.\n\n### Migrations\n\nTo migrate the Wiki data (for vector-based RAG search):\n\nStep 1: Export Wiki as an HTML file from OpenProject.\n\nStep 2: Run the migration script (HTML filename is currently hardcoded. Will convert to a CLI argument later).\n\n```shell\npython3 migrations.py\n```\n\nNote that you have to use the same embedding model for both the migration and the search queries.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdarren277%2Frelayops","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdarren277%2Frelayops","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdarren277%2Frelayops/lists"}