Sokket is built to be extended through exactly one seam: a filter that registers tools. Everything else — transport, auth, permissions, audit — is written once and applies to your tools identically.
Text domain: sokket-site-connector-for-mcp · Prefix: sokket_ / SOKKET_ · Namespace: SOKKET\ (PSR-4 → includes/).
Registering a tool
add_filter( 'sokket_register_tools', function ( array $tools ) {
$tools[] = array(
'name' => 'get_order_summary',
'description' => 'Get a summary of a WooCommerce order by id.',
'capability' => 'edit_shop_orders',
'is_write' => false,
'group' => 'other',
'input_schema' => array(
'type' => 'object',
'required' => array( 'order_id' ),
'properties' => array(
'order_id' => array(
'type' => 'integer',
'description' => 'The order id.',
),
),
),
'callback' => function ( array $args ) {
$order = wc_get_order( absint( $args['order_id'] ) );
if ( ! $order ) {
return new WP_Error( 'not_found', 'No such order.' );
}
return array(
'id' => $order->get_id(),
'status' => $order->get_status(),
'total' => $order->get_total(),
);
},
);
return $tools;
} );
Your tool immediately appears in the token allowlist on Settings → Access, in tools/list for connections that may call it, and in the audit log.
Tool definition schema
| Key | Type | Required | Meaning |
|---|---|---|---|
name | string | ✔ | Unique key matching ^[a-z0-9_]{1,64}$. |
description | string | ✔ | Shown to the AI client — this is the model’s only instruction on when to use the tool, so write it for a reader who can’t see your code. |
capability | string | ✔ | Checked with current_user_can() before the callback runs. |
is_write | bool | ✔ | Must be a real boolean. true means the read-only ceiling blocks it. |
is_destructive | bool | — | true when the tool removes content. Implies is_write, sends the MCP destructiveHint annotation, and shows a delete badge in the token allowlist. Defaults to false. |
callback | callable | ✔ | function ( array $args ): array|WP_Error. |
input_schema | array | — | JSON Schema for the arguments object. Omit for a tool with no arguments. |
group | string | — | Admin grouping key. Defaults to other. |
Invalid definitions are silently discarded, and the first registration of a name wins — so a filter cannot overwrite a built-in tool by re-registering its name.
Groups
Built-in group keys and labels: site (Site & diagnostics), content (Content), media (Media), taxonomy (Taxonomies & terms), comments (Comments), and other (Other). A tool declaring an unknown group falls into the trailing Other bucket in the admin.
Writing a good callback
- Return an array on success — it is JSON-encoded into the tool result. Return a
WP_Erroron failure and Sokket turns it into a proper JSON-RPC error. - Re-check per-object permissions inside the callback. The definition’s
capabilityis only the outer gate. If your tool acts on a specific object, callcurrent_user_can( 'edit_post', $id )(or the equivalent) yourself — the built-in post, term, and comment tools all do. - Sanitize every argument. Arguments come from a remote client. Never pass one into a query, a path, or an option name unfiltered.
- Set
is_writehonestly. It is what the read-only ceiling keys off. A tool that changes anything and declaresfalsesilently defeats a security control the site owner is relying on. - Don’t make outbound requests. The free plugin’s "no outbound calls" guarantee is per-codebase; if your add-on calls out, say so in your own documentation rather than quietly borrowing Sokket’s claim.
- Throw nothing. Uncaught
Throwables are caught and converted into a generic internal error, so exceptions cost you your error message.
Every hook
Tools
sokket_register_tools(filter) — the tool definition array. The only extension seam.sokket_settings_read_allowlist(filter) — the optionsget_site_settingsmay read, asgroup => array( option names ). Anything you add here is exposed to every connection allowed to call that tool.
Uploads
sokket_allowed_upload_mimes(filter) — allowed upload MIME types. Defaults to JPEG, PNG, GIF, WebP, and AVIF. SVG is refused by the type-sniffing layer regardless of what you return here.sokket_max_upload_size(filter) — maximum bytes per file. Default 8 MB.sokket_max_uploads_per_hour(filter) — file count cap per user per hour. Default 20.sokket_max_upload_bytes_per_hour(filter) — byte cap per user per hour. Default 25 MB.
Auth & OAuth
sokket_rate_limit_per_minute(filter) — requests per minute per credential. Default 120; return0or less to disable.sokket_oauth_user_can_authorize(filter) — whether the current user may approve an OAuth connection. Defaults tocurrent_user_can( 'read' ).
// Only editors and above may approve MCP connections.
add_filter( 'sokket_oauth_user_can_authorize', function () {
return current_user_can( 'edit_others_posts' );
} );
Audit
sokket_audit_max_rows(filter) — audit log row cap. Default 500, minimum 10.
Admin
sokket_screen_ids(filter) — the plugin’s admin screen ids.sokket_admin_header_nav(filter) — the header nav items.sokket_before_sidebar_content/sokket_after_sidebar_content(actions) — render into the admin sidebar.sokket_admin_page_after_content(action) — render after the main admin content.
The transport contract
One REST route: POST /wp-json/sokket/v1/mcp, Streamable HTTP carrying JSON-RPC 2.0.
Methods: initialize, ping, tools/list, tools/call, and the notifications/initialized no-op. Anything else returns method-not-found.
Protocol versions: 2025-11-25, 2025-06-18, 2025-03-26. initialize echoes a supported requested version, or replies with the newest one when the request names something else.
Capabilities: the handshake advertises tools with listChanged: false. The server never pushes a tool-list-changed notification, so clients refresh their tool list by re-initializing.
Sessions: a successful initialize returns an Mcp-Session-Id header, backed by a transient with a 15-minute sliding expiry. Send it back on later requests. An unknown or expired id gets 404 so the client re-initializes. DELETE with the header ends the session.
Other verbs: GET returns 405 with Allow: POST, DELETE — there is no server-initiated stream.
Batching: JSON-RPC batching is accepted only for a session negotiated at 2025-03-26, the last protocol version that allowed it, and is size-capped. Newer sessions get an invalid-request error.
Auth precedence: an Authorization: Bearer header prefixed sokket_at_ is treated as an OAuth access token; one prefixed sokket_ is a manual token. Any other Bearer value is left alone for other plugins’ auth handlers. With no bearer header, Sokket falls back to whatever user WordPress already authenticated — the Application Password path.
Permission callback order: master toggle → origin validation → authentication. A disabled server rejects before anything is parsed.
OAuth endpoints
| Purpose | Route |
|---|---|
| Discovery (RFC 9728) | /.well-known/oauth-protected-resource |
| Discovery (RFC 8414) | /.well-known/oauth-authorization-server |
| Authorization | /sokket-oauth/authorize |
| Token | /wp-json/sokket/v1/oauth/token |
| Registration | /wp-json/sokket/v1/oauth/register |
| Revocation | /wp-json/sokket/v1/oauth/revoke |
Grants: authorization_code and refresh_token. PKCE S256 required. Scopes: mcp:read, mcp:write. Lifetimes: code 5 minutes, access token 1 hour, refresh token 30 days. The two discovery documents are matched on init rather than registered as REST routes, because they must live at the host root above the REST namespace.
Note that OAuth contexts carry no per-tool allowlist — a grant may call every tool its user has the capability for, bounded by its read-only scope. Per-tool control is a bearer-token feature.
Database schema
| Table | Holds |
|---|---|
{prefix}sokket_tokens | Bearer tokens (SHA-256), bound user, allowlist JSON, read-only flag, active flag, timestamps. |
{prefix}sokket_audit_log | Capped tool-call log: token id, user id, tool name, outcome, object id, timestamp. |
{prefix}sokket_oauth_clients | Registered clients, redirect URIs, hashed secrets. |
{prefix}sokket_oauth_codes | Short-lived authorization codes. |
{prefix}sokket_oauth_tokens | Hashed access and refresh tokens with their scope. |
Options: sokket_settings, sokket_version. Transients are prefixed sokket_. A scheduled sokket_oauth_cleanup task prunes expired codes and tokens. uninstall.php removes all of it.
Building an add-on
The Pro plugin uses nothing but sokket_register_tools — no forks, no patches, no private APIs. If you’re building filesystem, database, or deployment tooling on top of Sokket, that filter is the whole integration surface, and everything you register inherits the permission model, the audit log, and the transport for free.