Skip to content

MCP and AI agents

6 min read

Every Basehim site has an MCP server at /mcp. MCP (Model Context Protocol) is the standard way for AI assistants to use software through named tools instead of scraping pages. An assistant connected to your site can search and read content, draft posts, tag them, browse media and moderate comments. It can do only what you allow, as the account you approve it with.

Two ways to connect

OAuth 2.1API key
ForChat assistants with "custom connector" supportScripts, agent frameworks, your own MCP client
SetupPaste https://your-site.com/mcp, sign in, approveCreate a key under API → Keys, send it as a bearer token
Acts asThe account that approved itThe account that created the key
ScopesRequested by the client, limited by your roleThe ones you tick on the key

OAuth: an assistant connects itself

  1. In your assistant, add a custom connector with the URL https://your-site.com/mcp.
  2. The assistant discovers the OAuth endpoints and registers itself (dynamic client registration). There's no client id or secret to copy.
  3. A browser window opens your site. Sign in, review the permissions the assistant asks for, and approve.
  4. The assistant receives a token and lists the tools it can use.

Scopes your role doesn't allow are dropped at approval: an author approving a connector never grants users:read. Access tokens last 8 hours and refresh themselves for up to 30 days.

The tutorial Connect an AI assistant to your site walks through it with Claude.

Note: In Basehim 1.2.53 and earlier, the admin has no list of connected assistants. To disconnect one, remove the connector in the assistant; its access then expires within 30 days. To cut it off at once, suspend the account it used.

API key: for your own clients

curl -X POST https://your-site.com/mcp \
  -H "Authorization: Bearer basehim_your_key" \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

Roles still apply

A token can only do what its scopes and its account allow:

  • an author's posts:write edits that author's own posts, never anyone else's;
  • a contributor's new posts go to review instead of being published;
  • comment, user and settings tools require the matching capability (moderate_comments, manage_users, manage_settings);
  • HTML from accounts without unfiltered_html is filtered on save, and the tool result includes a warning;
  • a suspended account's tokens and keys stop working immediately.

Give an agent its own account, with the least powerful role that does the job.

Core tools

ToolScopeArgumentsWhat it does
get_site_infononeTitle, tagline, URL, content counts
search_contentposts:readquery, limitFull-text search of published posts and pages
list_postsposts:readstatus, page, per_pagePosts, newest first
get_postposts:readslug or idOne post with its full content
create_postposts:writetitle, content, content_format, status, excerpt, slugNew post (draft by default)
update_postposts:writeid, title, content, status, excerptChanges only the fields given
trash_postposts:writeidMoves a post or page to the trash
set_post_termsposts:writeid, term_idsReplaces a post's categories and tags
list_pages, get_pageposts:readlike postsPages
create_page, update_pageposts:writelike postsPages
list_taxonomiestaxonomies:readCategories and tags with their ids
create_termtaxonomies:writetaxonomy, name, slug, descriptionNew category or tag
list_media, get_mediamedia:readsearch, type, page / idThe media library, with URLs to embed
regenerate_thumbnailsmedia:writeRebuilds every image size
list_commentscomments:readstatus, search, pageComments, e.g. those waiting for moderation
moderate_commentcomments:writeid, statusApprove, unapprove, spam or trash
get_settingssettings:readgroupA settings group, without secrets
list_widget_areas, get_widget_areasettings:readareaWidget areas and their widgets
list_usersusers:readsearch, per_pageUsers with their roles
list_menusmenus:readNavigation menus
delete_menumenus:writeidDeletes a menu permanently

tools/list returns only the tools the token's scopes allow. The menus:* and media:write scopes can be put on API keys but aren't offered to OAuth clients, so those three tools are API-key only.

Content can be HTML, Markdown or block JSON: set content_format on create. update_post can't change the format label, but the site renders block JSON and Markdown correctly whatever the label says.

Resources

resources/list and resources/read expose the site as URIs: basehim://site/info and basehim://post/{id-or-slug}.

Raw JSON-RPC

Any HTTP client can speak MCP. A session looks like this:

KEY=basehim_your_key; SITE=https://your-site.com
mcp() { curl -s -X POST "$SITE/mcp" -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' -d "$1"; echo; }

mcp '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"1"}}}'
mcp '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'
mcp '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"search_content","arguments":{"query":"release","limit":3}}}'
mcp '{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"create_post","arguments":{"title":"Draft from an agent","content":"## Notes\n\n- one\n- two","content_format":"markdown"}}}'

A tool result is MCP text content, holding JSON:

{"jsonrpc":"2.0","id":4,"result":{"content":[{"type":"text","text":"{\"created\": true, \"type\": \"post\", \"id\": 102, \"slug\": \"draft-from-an-agent\", \"status\": \"draft\", \"url\": \"…\"}"}],"isError":false}}

When a tool fails, isError is true and the text explains why: a missing scope, a missing capability, or an unknown id. GET /mcp returns a small discovery document with the transport, the accepted authentication methods and the OAuth metadata URL.

Apps can add tools

An app registers tools with four filters: mcp.tools (the definition), mcp.call.{name} (the handler), and mcp.scopes with api.scopes (its own scope). For example:

$this->addFilter('mcp.tools', function (array $tools) {
    $tools[] = [
        'name'        => 'helpful_votes_report',
        'description' => 'Posts with the most "Was this helpful?" votes.',
        'inputSchema' => ['type' => 'object', 'properties' => ['limit' => ['type' => 'integer']], 'additionalProperties' => false],
        '_scope'      => 'helpful:read',
    ];
    return $tools;
});
$this->addFilter('mcp.call.helpful_votes_report', fn($r, array $args) => ['posts' => $this->totals((int) ($args['limit'] ?? 10))], 10, 2);

Core tool names always win a clash, so an app can't redefine create_post. The tutorial Give your app an MCP tool builds this step by step, including scopes and consent.

In the browser: WebMCP

/mcp is for agents with credentials. For agents running in a visitor's own browser, Basehim also describes public pages and forms through WebMCP. See AI discovery.

Keep building

More guides, or jump straight into the API reference.

Discussion

Leave a comment