Add Email (IMAP/SMTP) and SSH Remote Access connectors
- CLAUDE.md: developer guide for the marketplace repo - email/: generic IMAP+SMTP connector (stdlib only, config via env) - env[] array, auth.type: password, verify.py (IMAP+SMTP probe) - ssh/: SSH Remote Access connector ported from Skald, modified - ALIASES_FILE → ~/.ssh_aliases.json (was ./secrets/...) - optional env[] (TTL/timeout tunables) - auth.type: none (per-alias auth at runtime via elicitation) - tavily/: restructured with env[] (tavilyApiKey), verify.py, auth block - connectors.json: added email, ssh, and tavily verify.py entries - SKALD.md: documented env[], verify, password auth, SSH connector
This commit is contained in:
@@ -4,12 +4,27 @@
|
||||
"version": "1.0.0",
|
||||
"type": "mcp_remote",
|
||||
"mcp_config": {
|
||||
"url": "https://mcp.tavily.com/mcp/?tavilyApiKey={key}",
|
||||
"url": "https://mcp.tavily.com/mcp/?tavilyApiKey={SECRET:tavilyApiKey}",
|
||||
"transport": "streamable-http"
|
||||
},
|
||||
"requires": [
|
||||
"API_KEY"
|
||||
"API_KEY",
|
||||
"ENV"
|
||||
],
|
||||
"env": [
|
||||
{
|
||||
"name": "tavilyApiKey",
|
||||
"label": "Tavily API key",
|
||||
"description": "Your Tavily API key (find it at https://app.tavily.com). Used both as the MCP URL query parameter and for the verification request.",
|
||||
"required": true,
|
||||
"secret": true,
|
||||
"example": "tvly-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
}
|
||||
],
|
||||
"verify": {
|
||||
"command": "python3 verify.py",
|
||||
"timeout_secs": 15
|
||||
},
|
||||
"docs": [
|
||||
{
|
||||
"lang": "en",
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Verify the Tavily API key by making a minimal search request.
|
||||
|
||||
Reads TAVILY_API_KEY from the environment (the key skald collected from the
|
||||
user and substituted into the {SECRET:tavilyApiKey} placeholder), POSTs a
|
||||
minimal search to https://api.tavily.com/search, and prints a single JSON
|
||||
object on stdout:
|
||||
|
||||
{"ok": true, "message": "Tavily API key is valid"}
|
||||
{"ok": false, "message": "Tavily API key is invalid or unauthorized"}
|
||||
|
||||
Exit code is 0 on success, 1 on any failure. The API key is never printed.
|
||||
|
||||
stdlib only (urllib).
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
|
||||
|
||||
def _result(ok, message):
|
||||
print(json.dumps({"ok": ok, "message": message}))
|
||||
sys.exit(0 if ok else 1)
|
||||
|
||||
|
||||
def main():
|
||||
api_key = os.environ.get("tavilyApiKey", "").strip()
|
||||
if not api_key:
|
||||
# Also accept the UPPER_SNAKE form some users may type.
|
||||
api_key = os.environ.get("TAVILY_API_KEY", "").strip()
|
||||
if not api_key:
|
||||
_result(False, "No Tavily API key provided (tavilyApiKey env var is empty)")
|
||||
|
||||
body = json.dumps({
|
||||
"api_key": api_key,
|
||||
"query": "skald connectivity check",
|
||||
"max_results": 1,
|
||||
"search_depth": "basic",
|
||||
}).encode("utf-8")
|
||||
|
||||
req = urllib.request.Request(
|
||||
"https://api.tavily.com/search",
|
||||
data=body,
|
||||
headers={"Content-Type": "application/json"},
|
||||
method="POST",
|
||||
)
|
||||
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=15) as resp:
|
||||
if 200 <= resp.status < 300:
|
||||
_result(True, "Tavily API key is valid")
|
||||
_result(False, f"Tavily returned HTTP {resp.status}")
|
||||
except urllib.error.HTTPError as e:
|
||||
if e.code in (401, 403):
|
||||
_result(False, "Tavily API key is invalid or unauthorized "
|
||||
f"(HTTP {e.code})")
|
||||
_result(False, f"Tavily returned HTTP {e.code}: {e.reason}")
|
||||
except Exception as e:
|
||||
_result(False, f"Tavily verify request failed: {e}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user