Recently I wanted to discover how to build a custom connector that Muse — Meta’s personal AI agent — could use to reach a remote service of mine. The idea was simple: start with a temporary hello-world API as a proof of concept, prove the full round trip, and only then think about real functionality.
This post is the exact recipe I followed, written so that another builder — or another AI agent — can repeat it end to end. All the code is below.
⚠️ One thing I learned first: Meta has published no official developer documentation, SDK, or protocol spec for building custom Muse connectors. The only official word is a short Help Center article that says “you can ask Muse to create a Custom Connector,” with a warning that Meta does not review custom connectors. So everything below is reverse-engineered from practice, not from docs.
The plan
Five steps, in order:
- Write a tiny HTTP server with a public
/healthendpoint and a Bearer-protected/helloendpoint. - Expose it to the internet so Muse can reach it.
- Register a custom connector in Muse, scoped to that URL, with the API key sent as a Bearer header.
- Scaffold a workspace skill with a small CLI (
hello,health,status) for repeatable calls. - Verify the end-to-end round trip.
Step 1: The server
I wanted zero dependencies — pure Python standard library, so anyone can run it without installing anything. The server has two endpoints:
GET /health— no auth, just a liveness check.GET /hello— requiresAuthorization: Bearer <API_KEY>, returns{"message": "hello world"}.
The API key comes from the HACKATHON_API_KEY environment variable, and the port defaults to 8765.
#!/usr/bin/env python3
"""PoC hello-world API for the Muse custom-connector.
Endpoints:
GET /health - no auth, liveness check
GET /hello - requires Authorization: Bearer <API_KEY>
The API key is read from the HACKATHON_API_KEY env var.
"""
import json
import os
from http.server import BaseHTTPRequestHandler, HTTPServer
API_KEY = os.environ.get("HACKATHON_API_KEY", "")
PORT = int(os.environ.get("PORT", "8765"))
class Handler(BaseHTTPRequestHandler):
def _send(self, status, payload):
body = json.dumps(payload).encode()
self.send_response(status)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
def do_GET(self):
if self.path == "/health":
self._send(200, {"status": "ok"})
return
if self.path == "/hello":
auth = self.headers.get("Authorization", "")
if not API_KEY or auth != f"Bearer {API_KEY}":
self._send(401, {"error": "unauthorized"})
return
self._send(200, {"message": "hello world", "service": "hackathon-hello-poc"})
return
self._send(404, {"error": "not found"})
def log_message(self, *args):
pass
if __name__ == "__main__":
if not API_KEY:
raise SystemExit("HACKATHON_API_KEY env var is required")
HTTPServer(("127.0.0.1", PORT), Handler).serve_forever()
Run it locally:
export HACKATHON_API_KEY="your-secret-key-here"
python3 server.py
Then verify the three cases — health works, /hello without a key is rejected, /hello with the key succeeds:
curl localhost:8765/health
# {"status": "ok"}
curl localhost:8765/hello
# {"error": "unauthorized"}
curl -H "Authorization: Bearer $HACKATHON_API_KEY" localhost:8765/hello
# {"message": "hello world", "service": "hackathon-hello-poc"}
Step 2: Expose it with ngrok
Muse runs in the cloud, so localhost is not enough — the server needs a public HTTPS URL.
My first attempt was a Cloudflare quick tunnel from the VM where Muse runs. That failed because of the VM’s runtime proxy, so I switched plans: I ran both the server and ngrok on my own Mac, which already had ngrok installed.
On the Mac, in one terminal:
export HACKATHON_API_KEY="your-secret-key-here"
python3 server.py
In another:
ngrok http 8765
ngrok prints a forwarding line like:
Forwarding https://<random-subdomain>.ngrok-free.dev -> http://localhost:8765
Copy that public URL. That is the URL you hand to Muse for connector registration.
⚠️ ngrok’s free URLs are ephemeral. Every time you restart ngrok, the subdomain changes, and the connector must be re-registered against the new URL. Plan accordingly — or pay for a reserved domain if you want stability.
Step 3: Register the custom connector in Muse
I asked Muse to create a custom connector. It registered one named custom.hackathon-hello, scoped to the ngrok hostname, configured so the API key is sent as a Bearer header on each request.
The API key itself was never typed into chat. Muse presented a secure credential form; the key went straight into its credential store, and Muse never saw the raw value. If you are doing this yourself, never paste secrets into chat — use the secure entry flow.
Step 4: Scaffold a skill with a CLI
To make the connector reusable (and to give future AI agents a documented interface), we scaffolded a workspace skill — a small folder with a SKILL.md describing the provider and a CLI script exposing three subcommands:
hello—GET /hello(authenticated), prints the JSON response.health—GET /health(no auth), liveness check.status— reports whether the connector credential works.
From then on, testing the connector was a one-liner instead of a curl incantation.
Step 5: Verify the round trip
The moment of truth: Muse called /hello through the connector, from its cloud environment, against my Mac-hosted server, through ngrok. The response:
{"message": "hello world"}
The full round trip worked — the request left Muse’s cloud, traveled through ngrok to my Mac, the server checked the Bearer key, and the JSON came back.
Repeating this yourself: the checklist
- Save the server code above as
server.py. export HACKATHON_API_KEY=<a-long-random-secret>and runpython3 server.py.ngrok http 8765, copy the forwarding URL.- Ask Muse to create a custom connector scoped to that hostname, with the key as a Bearer header — enter the key through the secure credential form, not chat.
- Scaffold a skill/CLI with
hello,health, andstatusfor repeatability. - Call
/hellothrough the connector and confirm you get{"message": "hello world"}. - Remember: restart ngrok → new URL → re-register the connector.
That is the whole PoC. From here, the path is clear: replace the hello-world endpoints with real ones, keep the auth pattern, and the connector keeps working.