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
12 changes: 9 additions & 3 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
{
"name": "connector-hub",
"version": "2.0.0",
"tagline": "One hub for every external service — spec-driven, type-safe, auditable.",
"tags": ["connectors", "api", "mcp", "automation", "devops"],
"version": "2.1.0",
"tagline": "One hub for every external service — 24 providers, 301 operations, spec-driven and auditable.",
"tags": [
"connectors",
"api",
"mcp",
"automation",
"devops"
],
"icon": "plug",
"homepage": "https://github.com/CodeWithJuber/connector-hub",
"repository": "https://github.com/CodeWithJuber/connector-hub"
Expand Down
10 changes: 7 additions & 3 deletions .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "connector-hub",
"version": "2.0.0",
"description": "Spec-driven connector orchestration — 21 providers, 278 operations, type-safe execution contract with audit ledger.",
"version": "2.1.0",
"description": "Spec-driven connector orchestration — 24 providers, 301 operations, type-safe execution contract with audit ledger.",
"author": {
"name": "Connector Hub contributors",
"url": "https://github.com/CodeWithJuber"
Expand All @@ -13,7 +13,11 @@
"longDescription": "Runs the Connector Hub MCP server with spec-driven connectors covering AI, email, hosting, cloud, chat, GitHub, and operations — behind one interface with a type-safe execution contract, permission model, and hash-chained audit ledger.",
"developerName": "Connector Hub contributors",
"category": "Developer Tools",
"capabilities": ["Interactive", "Read", "Write"],
"capabilities": [
"Interactive",
"Read",
"Write"
],
"defaultPrompt": [
"List all providers and their operation counts.",
"Search for operations matching a query.",
Expand Down
10 changes: 7 additions & 3 deletions .codex-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "connector-hub",
"version": "2.0.0",
"description": "Spec-driven connector orchestration — 21 providers, 278 operations, type-safe execution contract with audit ledger.",
"version": "2.1.0",
"description": "Spec-driven connector orchestration — 24 providers, 301 operations, type-safe execution contract with audit ledger.",
"author": {
"name": "Connector Hub contributors",
"url": "https://github.com/CodeWithJuber"
Expand All @@ -13,7 +13,11 @@
"longDescription": "Runs the Connector Hub MCP server with spec-driven connectors covering AI, email, hosting, cloud, chat, GitHub, and operations — behind one interface with a type-safe execution contract, permission model, and hash-chained audit ledger.",
"developerName": "Connector Hub contributors",
"category": "Developer Tools",
"capabilities": ["Interactive", "Read", "Write"],
"capabilities": [
"Interactive",
"Read",
"Write"
],
"defaultPrompt": [
"List all providers and their operation counts.",
"Search for operations matching a query.",
Expand Down
13 changes: 13 additions & 0 deletions .mcp.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"mcpServers": {
"connector-hub": {
"command": "${CLAUDE_PLUGIN_ROOT}/crates/target/release/connector-hub",
"args": [
"mcp"
],
"env": {
"CONNECTOR_HUB_SPECS_DIR": "${CLAUDE_PLUGIN_ROOT}/specs"
}
}
}
}
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,18 @@ connector-hub mcp
connector-hub validate
```

### Specs resolution

Provider specs are located in this order:

1. `CONNECTOR_HUB_SPECS_DIR` — explicit override.
2. `./specs` relative to the working directory.
3. `specs/` found by walking up from the executable.

Rule 3 lets an installed binary find its specs without the caller setting a
working directory — MCP hosts launch servers with an arbitrary cwd, so a hub
installed globally would otherwise start with an empty catalogue.

## Architecture

Connectors are **data, not code**. Provider specs (OpenAPI 3.x or Google
Expand Down
50 changes: 44 additions & 6 deletions crates/connector-hub/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,12 +109,15 @@ async fn main() -> anyhow::Result<()> {
println!("Validating installation...\n");

// 1. Check specs directory
let specs_dir = std::path::Path::new("specs");
if !specs_dir.exists() {
errors.push("specs/ directory not found".into());
let specs_dir = specs_dir();
if !specs_dir.is_dir() {
errors.push(format!(
"specs/ directory not found at {}",
specs_dir.display()
));
} else {
let mut spec_count = 0;
for entry in std::fs::read_dir(specs_dir)? {
for entry in std::fs::read_dir(&specs_dir)? {
let entry = entry?;
let path = entry.path();
if path.extension().is_some_and(|e| e == "json") {
Expand Down Expand Up @@ -259,11 +262,46 @@ async fn main() -> anyhow::Result<()> {
Ok(())
}

/// Resolve the provider specs directory.
///
/// Resolution order:
/// 1. `CONNECTOR_HUB_SPECS_DIR` — explicit override.
/// 2. `./specs` relative to the working directory — repo-root invocation.
/// 3. `specs/` found by walking up from the executable — lets an installed
/// binary (e.g. `crates/target/release/connector-hub`) locate the specs
/// without the caller having to set a working directory. MCP hosts launch
/// servers with an arbitrary cwd, so this is the common case.
///
/// Falls back to `./specs` so callers keep a stable "not found" path to report.
fn specs_dir() -> std::path::PathBuf {
if let Ok(dir) = std::env::var("CONNECTOR_HUB_SPECS_DIR") {
return std::path::PathBuf::from(dir);
}

let cwd_specs = std::path::PathBuf::from("specs");
if cwd_specs.is_dir() {
return cwd_specs;
}

if let Ok(exe) = std::env::current_exe() {
let mut ancestor = exe.parent();
while let Some(dir) = ancestor {
let candidate = dir.join("specs");
if candidate.is_dir() {
return candidate;
}
ancestor = dir.parent();
}
}

cwd_specs
}

fn build_catalogue() -> anyhow::Result<hub_core::Catalogue> {
let mut catalogue = hub_core::Catalogue::new();

let specs_dir = std::path::Path::new("specs");
if specs_dir.exists() {
let specs_dir = specs_dir();
if specs_dir.is_dir() {
for entry in std::fs::read_dir(specs_dir)? {
let entry = entry?;
let path = entry.path();
Expand Down
11 changes: 5 additions & 6 deletions crates/hub-net/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,12 @@ impl NetClient {

// For methods that take a body, send args as JSON
match method.to_uppercase().as_str() {
"POST" | "PUT" | "PATCH" => {
if !args.is_null() {
request = request
.header("Content-Type", "application/json")
.json(args);
}
"POST" | "PUT" | "PATCH" if !args.is_null() => {
request = request
.header("Content-Type", "application/json")
.json(args);
}
"POST" | "PUT" | "PATCH" => {}
"GET" | "HEAD" | "DELETE" => {
// GET/HEAD/DELETE: if args has query-like params, append as query string
if let Some(obj) = args.as_object() {
Expand Down
4 changes: 2 additions & 2 deletions kimi.plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "connector-hub",
"version": "2.0.0",
"description": "Spec-driven connector orchestration — 21 providers, 278 operations, type-safe execution contract with audit ledger.",
"version": "2.1.0",
"description": "Spec-driven connector orchestration — 24 providers, 301 operations, type-safe execution contract with audit ledger.",
"author": {
"name": "Connector Hub contributors",
"url": "https://github.com/CodeWithJuber"
Expand Down
82 changes: 82 additions & 0 deletions skills/connector-hub/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
---
name: connector-hub
description: Call external services (AI providers, Gmail, GitHub, Hetzner/Linode/Contabo/OVH VPS, cPanel/WHM/WHMCS panels, Cloudflare, tawk.to, SSH and network ops) through the Connector Hub MCP server. Use whenever a task needs a real API call to one of these providers, when you need to discover which operation exists for a provider, or when a destructive operation needs confirmation. Triggers include "list my servers", "send this email", "create a DNS record", "suspend that hosting account", "reboot the VPS", "what can I do with <provider>".
---

# Connector Hub

One MCP surface over 24 providers and 301 operations. The tool surface is fixed
and small; the reachable API surface is complete. Never guess an operation ID —
discover it.

## The four tools

| Tool | Use it for |
|---|---|
| `list_providers` | What providers exist and how many operations each has |
| `search_operations` | Find the operation ID for an intent — free text, optional `provider` filter |
| `describe_operation` | Exact JSON Schema for one operation before calling it |
| `call_operation` | Execute — `id`, `args`, optional `account`, `dry_run`, confirmation token |

## Workflow

Always in this order:

1. **Search** — `search_operations({query: "delete server", provider: "hetzner"})`.
Search by what you want to do, not by the endpoint name you imagine.
2. **Describe** — `describe_operation({id: "hetzner.servers.delete"})`. Read the
schema. Do not construct `args` from memory of the provider's REST API.
3. **Dry run** — for anything mutating, call with `dry_run: true` first and show
the user the `would_execute` payload and `mutation_class`.
4. **Call** — only after the user has seen what will happen.

## Reading the result

Every result is one of five states. Only one of them means something happened:

- `Succeeded { executed: true, data }` — real output, the only executed state.
- `DryRun { would_execute, mutation_class }` — nothing ran.
- `ConfirmationRequired { provider, operation, token_format }` — destructive op.
Relay the request to the user; pass the token they give back on the retry.
Never mint or guess a confirmation token.
- `ConfigurationRequired { provider, missing }` — credentials absent. Tell the
user exactly which env vars are missing; do not retry, and do not attempt the
same call through curl, a raw HTTP client, or a different tool.
- `PermissionDenied` — policy refused. Report it and stop.

A non-`Succeeded` state never means "probably worked". Do not report success
unless you saw `executed: true`.

## Safety rules

- 32 of the 301 operations are destructive (server deletes, account suspends,
Gmail deletes, SSH commands). Confirm with the user in plain language — name
the resource and the provider — before requesting a confirmation token.
- Credentials live in environment variables or the encrypted store. Never put a
key in `args`, never echo one back to the user, never write one into a file
the user did not ask for.
- `ops_ssh`, `ops_network`, and `ops_security` need a `HUB_SECURITY_POLICY`
capability grant. If they are denied, that is the policy working — surface it
rather than routing around it.
- Every policy decision is appended to a BLAKE3 hash-chained audit ledger.
Verify with `connector-hub audit-verify audit.jsonl`.

## Provider map

- **AI** — `claude`, `openai`, `kimi`
- **Mail** — `gmail` (79 ops), `email` (IMAP/SMTP)
- **Code** — `github` (25 ops)
- **Cloud / VPS** — `hetzner` (72 ops), `linode`, `contabo`, `ovh`,
`oneprovider`, `cloudflare`
- **Hosting panels** — `cpanel`, `whm`, `whmcs`, `ultrahost`
- **Chat** — `tawk`
- **Affiliate** — `iherb_apify`, `iherb_impact`, `iherb_partnerize`
- **Local ops** — `ops_ssh`, `ops_network`, `ops_security`, `ops_browser`

## Troubleshooting

- **Zero providers listed** — the specs directory was not found. Set
`CONNECTOR_HUB_SPECS_DIR` to the repo's `specs/` folder, or launch with the
repo root as the working directory.
- **Provider missing from `list_providers`** — its spec file failed to parse;
run `connector-hub validate`.
Loading