Wick · Custom Connector

Custom Connector — Add From cURL / MCP / Form

Three import paths into the same generic executor: paste a cURL, register an MCP server, or build by hand. Built-in connectors stay canonical; custom defs live in DB rows and ride the same encrypted-fields, tags, audit, and MCP dispatch layers.

Paired with design.md. Update keduanya barengan kalau layout / kontrak berubah.

⓪ Connectors index — "+ New connector" dropdown

Entry point. Tambah satu tombol di kanan-atas list. Dropdown reveals three sources. Existing search + filter chrome tetap.

Home / Connectors
/
🐙

GitHub

Built-in

Read repos, issues, and pull requests on GitHub.

5 operations · 1 active

💳

Stripe (custom)

Custom

Payments — imported from cURL on 2026-06-05.

3 operations · 1 active · needs reload

🧰

Internal Tools MCP

Custom · MCP ● Connected

Live proxy of internal-tools mcp — 7 tools (1 excluded).

7 operations · 7 active

① Flow A — paste · cURL or AI-assisted parser

Two parsers behind one paste box. cURL parser is deterministic (regex grammar, fast, no LLM call) — pick this when you have a real cURL string. AI parser handles anything else: raw API docs, fetch() snippet, JS axios call, Postman export blob. Falls back to LLM extraction.

Connectors / New custom / From paste

cURL parser — deterministic, $0, sync

Wick reads -X, -H, -d, -u, the URL, query string, and basic Bash escaping. Tokens that look like secrets are auto-flagged.

Tip: copy from browser DevTools → Network → Copy as cURL. Bash and PowerShell variants both supported.

More cURL shapes you can paste (click to try)

GET · query string → 2 inputs
curl 'https://api.github.com/repos/anthropics/claude-code/issues?state=open&per_page=10' \
  -H 'Authorization: Bearer ghp_xxxxxxxxxxxx' \
  -H 'Accept: application/vnd.github+json'

Extracts to state + per_page Inputs; auth_value auto-secret.

POST · JSON body → 3 inputs
curl -X POST https://api.openai.com/v1/chat/completions \
  -H "Authorization: Bearer sk-proj-xxx" \
  -H "Content-Type: application/json" \
  -d '{"model":"gpt-4","messages":[{"role":"user","content":"hi"}],"temperature":0.7}'

Body keys flattened: model, messages (json), temperature.

PUT · path variable → 1 path input
curl -X PUT 'https://api.example.com/v1/users/42' \
  -H 'X-API-Key: abc123' \
  -d '{"name":"Andrian","role":"admin"}'

Numeric path segment /42user_id input (heuristic).

Basic auth (-u) → user + pass
curl -u 'admin:hunter2' \
  https://elastic.internal:9200/_cluster/health \
  -H 'Accept: application/json'

-u → split jadi basic_user + basic_pass (pass auto-secret).

✨ AI parser tab — preview

Same paste box, LLM extracts. Same review form. Tab hidden if no provider configured. Raw paste not persisted.

1 LLM call · async

Inputs that AI parser accepts (cURL parser tolak semua ini)

JS fetch() snippet browser DevTools "Copy as fetch"
fetch("https://api.stripe.com/v1/charges", {
  method: "POST",
  headers: {
    "Authorization": "Bearer sk_test_xxx",
    "Content-Type": "application/x-www-form-urlencoded"
  },
  body: new URLSearchParams({
    amount: "2000",
    currency: "usd",
    customer: "cus_123"
  })
})
axios call code from SDK doc
axios.post('https://api.example.com/api/v3/messages', {
  room_id: 12345,
  message: 'hello',
  type: 'text'
}, {
  headers: {
    'ID': 'my-app',
    'Key': 'sk_xxx'
  }
})
Raw API doc paragraph prose from README
POST https://api.internal/v2/tickets

Headers:
  Authorization: Bearer <your-token>
  X-Workspace-Id: required

Body (JSON):
  title       (string, required)
  priority    (low|medium|high)
  assignee_id (uuid, optional)

Returns the created ticket object.
Plain English description last-resort fallback
Saya mau call POST ke api.notion.com/v1/pages,
auth pakai Bearer token, body JSON dengan field
"parent" (database id), "properties" (object berisi
title dan tags). Header "Notion-Version: 2022-06-28"
wajib.

Apa pun input di atas, AI parser keluarin shape ini → review form

{
  "method": "POST",
  "url": "https://api.notion.com/v1/pages",
  "headers": [
    { "key": "Authorization", "value": "Bearer …", "secret": true },
    { "key": "Notion-Version", "value": "2022-06-28" }
  ],
  "body": { "content_type": "application/json", "keys": ["parent", "properties"] },
  "suggested_op_name": "create_page",
  "suggested_inputs": [
    { "key": "parent", "widget": "text", "required": true },
    { "key": "properties", "widget": "textarea", "required": true }
  ]
}
Show failure / fallback state
✗ AI parser couldn't extract a clean HTTP call
Common causes: paste too short / too long (8 KB cap) / multiple endpoints mixed in / non-HTTP content.
→ Switch to cURL parser tab, or paste only ONE endpoint at a time.
← Cancel

② Flow A — review · Configs vs Inputs split

Left: extracted fields with widget pickers and secret toggles. Right: live JSON preview of what gets written to custom_connectors (one row — ops embedded in the ops JSON column). Admin sets Meta (Key / Name / Icon) at the top before saving.

Used in MCP tool_id. Lowercase, kebab/snake.

Configs (per-instance, stable)
Secret

Auto-flagged: header value matched Bearer …. Encrypted-at-rest + masked in MCP responses.

Operation · post_charges Destructive
Method URL
Inputs (per-call, LLM provides)

Whitelist funcs: urlquery, js, printf, default, lower, upper. No shell, no file IO.

custom_connectors row preview
// one row in custom_connectors — ops embedded as JSON array
{
  "key": "stripe",
  "name": "Stripe (custom)",
  "icon": "💳",
  "source": "curl",
  "source_meta": { "category": "Development", "health_op": "get_balance", "health_expect": "\"object\":\"balance\"" },
  "configs": [
    { "key": "base_url", "widget": "url", "required": true },
    { "key": "auth_value", "widget": "secret", "secret": true, "required": true }
  ],
  "ops": [
    {
      "key": "post_charges",
      "name": "Create charge",
      "destructive": true,
      "inputs": [
        { "key": "amount", "widget": "number", "required": true },
        { "key": "currency", "widget": "text", "default": "usd" },
        { "key": "customer", "widget": "text", "required": true }
      ],
      "request": {
        "method": "POST",
        "url_template": "{{.cfg.base_url}}/charges",
        "headers": { "Authorization": "Bearer {{.cfg.auth_value}}" },
        "body_template": "amount={{.in.amount}}&…",
        "content_type": "application/x-www-form-urlencoded"
      }
    }
  ]
}
Access (tags)

Wick will auto-create the filter tag custom:stripe on save. By default only admins see this connector — non-admins can't see or call it. You can open it up later under /admin/tags.

⚠ Default = admin-only. Auto-created tag is IsFilter=true, IsSystem=false. No user carries it at save, so the filter rule hides the connector from every non-admin /manager surface and MCP wick_list. Grant access in two ways: (a) assign tag to user groups at /admin/tags, or (b) remove the filter tag entirely to open to all approved users.
← Back

③ Flow B — MCP server registration (HTTP forwarder)

Register an MCP server by URL + auth headers. Wick is a forwarder — no process spawn, no lifecycle to babysit. Per-call outbound JSON-RPC over HTTP. Stdio MCPs are out of scope for v1; expose them via a sidecar (mcp-proxy / supergateway) if needed.

Connectors / New custom / From MCP server

Display name — becomes the connector's name (and its key, slugified).

Streamable-HTTP endpoint. Wick POSTs JSON-RPC (initialize, tools/list, tools/call) here per request.

OAuth = standard MCP authorization (server replies 401 invalid_token): discovery + dynamic client registration + PKCE popup login on Test now. Accounts are per instance — save attaches the signed-in account as the first instance; more accounts connect from each instance page ("Connect account →"). Tokens stored encrypted per instance, auto-refreshed.

Each scheme shows different inputs below. Switching schemes preserves any extra Headers row you've added.

All four auth panels (one is rendered at runtime depending on radio)

Auth = None no inputs
MCP server is open / network-restricted. Wick sends JSON-RPC without any auth header beyond the default Content-Type and Accept.
POST /v1
Content-Type: application/json
Accept: application/json

Recommended only inside a private network or behind a service mesh that handles auth.

Auth = Bearer token selected 1 secret

Stored as wick_enc_…. Decrypted server-side per request, masked in audit logs.

POST /v1
Authorization: Bearer ••••••••
Content-Type: application/json

Most common shared-secret case (OAuth access token, API key in Bearer form, etc).

Auth = Custom header N secrets

Define arbitrary header KV pairs. Toggle Sec per row — secret values encrypt at rest.

Sec
POST /v1
X-API-Key: ••••••••
X-Tenant-Id: example-tenant

Use for APIs that don't fit Bearer (e.g. Azure AAD Ocp-Apim-Subscription-Key, AWS sigv4 isn't covered here, providers with paired ID + secret headers).

Auth = SSO (forward caller) zero secret

Wick mints a short-lived (5-min) ED25519-signed JWT representing the user who triggered the MCP call and forwards it as X-Wick-User. MCP server validates against wick's pubkey at /.well-known/wick-pubkey.pem.

"sub": "user.id (uuid)",
"email": "user.email",
"name": "user.display_name",
"groups": "user.tag_ids[]",
"aud": "mcp.internal.example.com",
"iss": "https://wick.example.com",
"iat": 1717564800,
"exp": 1717565100

Defaults to MCP URL host. MCP server should validate this.

Re-minted per request — short TTL OK.

POST /v1
X-Wick-User: eyJhbGciOi… // signed, per-caller, 5-min
Content-Type: application/json

✓ Why SSO: no shared secret stored in wick; MCP can do per-user RBAC + audit; revoking a wick user revokes downstream access instantly (no token rotation needed).

⚠ Server requirement: MCP server must implement JWT validation against /.well-known/wick-pubkey.pem. Not supported by stock open-source MCP servers — typically only your in-house ones.

Extra headers (any scheme — appended on top)

For routing / tenancy headers that aren't auth. Each row is independently markable as secret.

Test connection

Fires initialize + tools/list with the current URL + headers. Save is blocked until at least one successful test.

✓ Connected · 12 tools discovered · 142ms
lookup_user · disable_user · audit_query · grant_role · list_tenants · …
Show failure example
✗ 401 Unauthorized
POST https://mcp.internal.example.com/v1 — auth header invalid. Check token + scopes.
← Cancel

④ Flow B — tools exclude-list (part of the server form)

After a successful test, the form shows the live tools/list catalog. Every tool is exposed automatically — ticking a row excludes it. Ops are never persisted: each module build (boot / re-sync / save) re-probes, so new server-side tools appear on their own. JSON Schema maps to wick widgets at build time.

Internal Tools MCP connected · 12 tools
1 excluded
Excl. Tool Description Inputs Destructive?
lookup_user Find a user by email or id. email?, id? no
disable_user Disable a user account. user_id Destructive
audit_query Query the audit log with time-range + filter. since, until, action? no
delete_records Delete N records matching a filter. filter, limit Destructive

One server = one connector. Saving creates it immediately; re-sync from the connector page picks up upstream tool changes.

⑤ Flow C — manual builder

Three-step stepper for greenfield definitions. Each step persists draft state; admin can leave + resume.

1 Meta
2 Configs
3 Operations

Step 2 · Configs

Define per-instance fields shared across every operation. Use secret for credentials — wick auto-encrypts at rest and masks in MCP responses.

⑥ Custom connector detail (same chrome as built-in + reload banner)

Same pages as built-in — list page (/manager/connectors/<key>) and instance page (…/<id>): instance Configs editor, per-op toggles, history. Multi-instance by default: no row is auto-seeded at save — admins click + New row (and Duplicate) like any connector, each row with its own credentials; "Single instance only" is an opt-in checkbox on the review form. Custom additions in the header: "Custom · <source>" badge, a status chip for MCP defs (● Connected / ● Disconnected from the last probe — stays until a re-sync, reconnect, or disable; ● Disabled wins for any source; cURL/manual defs show no connection chip), "Edit definition" link, "↻ Re-sync tools" (MCP), a "needs reload" banner when dirty, and a definition danger zone with Disable (zero ops served, pages stay) and Delete.

Definition updated — click Reload to apply changes to live MCP dispatch. Current in-memory executor still serves until reload.
💳

Stripe (custom)

Custom · cURL Edit definition →

Payments — imported from cURL on 2026-06-05 by yoga@example.com. (cURL source — no connection chip; MCP defs show ● Connected / ● Disconnected here.)

Configs (this instance)

Stored as wick_enc_…. Decryption happens server-side per call.

Operations
post_charges
Create charge
Destructive
get_charge
Retrieve charge by id
list_charges
List charges
Access (tags) — who can see & call this connector
custom:stripe filter Connector Internal APIs

custom:stripe is the per-def auto-tag — its filter flag is what makes this connector admin-only by default. Remove it to open to all approved users.

Currently visible to: admins only
0 user groups carry custom:stripe. Assign at /admin/tags/custom:stripe, or open to all (removes the filter tag from this connector).
post_charges
get_charge
list_charges

Operation call passes only when: row tags allow AND op enabled AND (admin OR not admin-only). Three independent off-switches.

Token legend (design-system compliance)

Custom Custom-def origin (green-200 / green-700)
Built-in RegisterBuiltins (white-300 / black-800)
Destructive OpDestructive (neg-100 / neg-400)
Secret Encrypted-field (cau-100 / cau-400)
Active filter green-200 + green-500 border
Idle filter white-300 + white-400 border

Spacing follows 8-grid; cards use rounded-xl (12px); inputs rounded-lg (8px); chips rounded-full. Dark-mode pair: navy-700 surfaces with white-100 text and navy-600 borders.