Announcing CIMD support for MCP Client registration
Learn more

Integrating Custom and Internal Tools with Hermes Agent

TL;DR

  • Scalekit's connector catalog only lists tools someone built support for in advance. Your internal tools will never be on that list, no matter how big the catalog gets.
  • Scalekit AgentKit has a real, working answer for this: client.actions.providers.create_custom_provider(), which brings an unlisted API or MCP server into the same connected account, authorization, and tool-calling model as a built-in connector.
  • This post builds one against Grafana, a real tool, genuinely absent from Scalekit's 389-provider catalog, checked live rather than assumed.
  • Grafana ships its own official MCP server, so the connector here is an MCP connector, not a REST proxy, and it's tested end to end: 118 real tools discovered, one called live, returning 12 real data sources from a real Grafana Cloud account.
  • REST connectors and MCP connectors are two different shapes, and knowing which one a tool needs before building saves a rebuild later, covered here with a real example.

"Connecting Hermes Agent to Your Tools and APIs" built a single-user Hermes gateway against GitHub, and "Multi-User Access Control for AI Agents on Hermes" proved that the same gateway holds up with more than one identity. Both used GitHub, a connector that was already sitting in Scalekit's catalog. That's the easy case. The moment your team needs Hermes to touch something that isn't in Scalekit's catalog, an internal admin panel, a proprietary system, or in this case, a real, live Grafana Cloud instance, the pattern changes.

This post covers why that gap is permanent, not something a bigger catalog eventually fixes, then builds a real custom connector for Grafana end to end: creating the connector from the dashboard and from the management API, wiring up the connection and authorizing it, discovering the tool's real MCP surface, and calling it for actual data. It also covers the one real decision that matters here, REST connector or MCP connector, and closes with what changes to serve more than one teammate.

What happens when your tool isn't in the connector catalog?

The natural assumption is that this is a waiting game: your tool isn't in the catalog yet, but check back next quarter and it probably will be. For an internal tool, that assumption never pays off. Two things keep this gap permanent:

  • Internal tools stay internal, by definition. An admin panel built for one company, a database wrapper only that team ever touches, a Grafana instance at a URL only that company's DNS resolves. None of these could ever sit in a shared catalog, because there's nothing generic about them to catalog. A bigger catalog doesn't change that.
  • Even a large catalog misses common tools. Checked live against Scalekit's own catalog for this post: 389 providers, genuinely broad, GitHub, Slack, Datadog, Linear, dozens of MCP servers. Grafana, one of the more widely used observability tools around, still wasn't in it.

So the real question isn't "will my tool eventually show up?" It's "what's the actual way to wire up the tools that never will," which is what the rest of this post builds.

How do you pick the right auth type?

Scalekit AgentKit exposes this as client.actions.providers, with four real methods: create_custom_provider(), update_custom_provider(), list_providers(), delete_custom_provider(). Creating a connector this way plugs it into the exact same downstream model as a built-in one: the same connection, connected account, authorization, and tool-calling flow used for GitHub in Connecting Hermes Agent to Your Tools and APIs and Multi-User Access Control for AI Agents on Hermes post.

A custom provider needs, at minimum, a display_name, a proxy_url (the base URL requests get routed to), and one auth_pattern describing how users authenticate. Four auth types exist: OAUTH, BEARER, API_KEY, NO_AUTH. Which one fits depends entirely on how the target tool itself authenticates, not on preference.

Auth type
When it fits
What Scalekit stores
OAUTH
The tool runs its own OAuth authorization server, or is a genuinely MCP-native server that handles its own OAuth exchange
An access token, minted through a real browser authorization flow
BEARER
The tool accepts a static, long-lived token in an Authorization: Bearer header
The token itself, entered once by the user
API_KEY
The tool uses its own API key convention, sometimes a different header name
The key, entered once
NO_AUTH
The tool is genuinely public, no credentials, no per-user data
Nothing, there's no credential field at all

Grafana turned out to be the OAUTH case, for a reason worth explaining before the walkthrough: Grafana Cloud ships its own official MCP server, reachable over HTTPS, that manages its own OAuth exchange. That's different from a plain REST API needing a Bearer token, and picking the right one mattered in practice; more on that below.

How do you build a custom connector?

Grafana is the example running through this section, a real Grafana Cloud instance, not a fictional one. Five real calls get from nothing to a working connector, and the last one is the same call Hermes itself ends up making once it's live:

Confirming the tool genuinely isn't in the catalog first

Worth checking before building anything, on your own account too, since catalogs vary. The fastest way: AgentKit → Connectors in the Scalekit dashboard, and search for the tool by name. For this post, that search for "Grafana" across the full catalog, 389 providers at the time of writing, came back empty.

Scripting the same check is also possible, useful if this needs to run as part of a setup script rather than a one-off search:

from scalekit import ScalekitClient from settings import Settings client = ScalekitClient(Settings.SCALEKIT_ENV_URL, Settings.SCALEKIT_CLIENT_ID, Settings.SCALEKIT_CLIENT_SECRET) pc = client.actions.providers._providers_client names, token = [], None while True: result_tuple = pc.list_providers(page_size=100, page_token=token, provider_type=None, identifier=None) proto_resp = result_tuple[0] names += [p.display_name.lower() for p in proto_resp.providers] token = proto_resp.next_page_token if not token: break print(len(names), "providers checked") print("grafana" in " ".join(names))

This is the output after running the script; it shows that Grafana is not found in the connectors:

389 providers checked False

This script reaches into client.actions.providers._providers_client directly rather than the typed list_providers() wrapper, since one built-in provider on this account (Appcues) has an auth field value the typed wrapper doesn't parse yet. Worth knowing if a scripted catalog scan on your own account needs the same workaround; the dashboard search doesn't hit this issue at all.

Creating the connector

Two ways to do this, and both land on the exact same connector underneath: the Scalekit dashboard's guided form, or the management API directly. The dashboard form is the faster path for an MCP connector specifically, since it only asks for what an MCP connector actually needs.

From the dashboard:

  • Scalekit dashboard → AgentKit → Connectors → Create custom connector
  • Fill in the Add MCP connector form: Display name (Grafana MCP), Description, Server URL (the base URL of Grafana's MCP server, must start with https://), and an optional Icon URL
  • For Auth type, choose OAuth, since Grafana's MCP server manages its own OAuth exchange
  • Save. The connector is ready to use the moment a connection is created against it

From the management API, the same connector, built with the SDK instead of clicking through the form:

from scalekit.actions.models.requests.create_custom_provider_request import CreateCustomProviderRequest from scalekit.actions.models.custom_provider import AuthPattern req = CreateCustomProviderRequest( display_name="Grafana MCP", description="Grafana's own MCP server, for dashboards, datasources, alerting, and incidents", proxy_url="https://your-instance.grafana.net", auth_patterns=[ AuthPattern(type="OAUTH",) ], ) resp = client.actions.providers.create_custom_provider(request=req) print(resp.provider.identifier) # e.g. GRAFANAMCP:20324953475777334

One real constraint worth knowing either way: display_name only accepts letters, digits, and spaces, no parentheses, no punctuation, confirmed live by a 400 validation error the first time this was tried through the API with a name that broke that rule.

Wiring up the connection and authorizing it

Creating the connector alone isn't enough. A Connection, the same object created for GitHub in the Connecting Hermes Agent post, has to exist before any connected account or authorization works. Skipping this step fails with connection not found for the given key, confirmed live.

This step happens in the dashboard, the same UI flow used for GitHub earlier in this series:

  • Scalekit dashboard → AgentKit → Connections → Add Connection
  • "Grafana MCP" now shows up in the connector picker, right alongside the built-in connectors
  • Choose a credential mode: each user connects their own account (per-identifier, the same model from Multi-User Access Control for AI Agents on Hermes) or shares one admin-connected account (one credential, everyone uses it).
  • Save, note the generated connection name (e.g. grafanamcp-rp4kppky)
  • Complete the OAuth authorization from that connection page once by signing in to Grafana; the connection is live.

Once that's done, the connection is live, and the same connected account check used throughout this series confirms it from code:

resp = client.actions.get_or_create_connected_account( connection_name="grafanamcp-rp4kppky", identifier="user_1232", ) print(resp.connected_account.status) # "ACTIVE"

What discovered tools actually look like

This is the payoff of picking a tool that ships its own MCP server: nothing has to be hand-defined. Scalekit discovers every tool the upstream MCP server exposes.

resp = client.actions.list_tools( connection_name="grafanamcp-rp4kppky", identifier="user_1232", ) print(len(resp.tools), "tools discovered")

118 tools are discovered in Grafana's real, published MCP server surface: dashboard search, datasource management, Prometheus and Loki queries, incident management, on-call schedules, alerting rules, and more, all discovered automatically, not manually wired.

Calling one of those tools for real

import json result = client.actions.execute_tool( tool_name="c-grafanamcp_list_datasources", connection_name="grafanamcp-rp4kppky", identifier="user_1232", tool_input={}, ) # MCP tools return their payload as a text block inside result.data['content']; # parse that string to get the actual JSON. payload = json.loads(result.data["content"][0]["text"]) print(json.dumps(payload, indent=2))

This is real output, 12 real datasources, from a real Grafana Cloud instance, named after that instance's own account, trimmed here to three datasources and stripped of each one's numeric id field for readability.

{ "datasources": [ {"uid": "grafanacloud-prom", "name": "grafanacloud-rubyredwood935-prom", "type": "prometheus", "isDefault": true}, {"uid": "grafanacloud-logs", "name": "grafanacloud-rubyredwood935-logs", "type": "loki", "isDefault": false}, {"uid": "grafanacloud-traces", "name": "grafanacloud-rubyredwood935-traces", "type": "tempo", "isDefault": false} ], "total": 12, "hasMore": false }

The same call structure was followed by the Connecting Hermes Agent and Multi-User Access Control for AI Agents posts for GitHub tools: tool_name, connection_name, identifier, tool_input, the mechanics don't change once the connector is wired up correctly. What does differ from those posts: an MCP tool's result comes back as a text block inside result.data, not a ready-made dict, so it needs one json.loads() before it's usable.

How does Hermes actually call this connector?

Everything above ran execute_tool() directly from a Python script, useful for proving the connector works, but Hermes itself is the thing this series is actually about. Scalekit's own docs name three patterns for how Hermes reaches Scalekit at all, and only one of them, Pattern A, hermes-delegated-auth, is the documented default for Hermes. It calls tools with execute_tool(tool_name, connection_name, identifier, tool_input), the same four arguments used throughout this post, and nothing in that call shape is specific to built-in connectors. A custom connector's connection name, grafanamcp-rp4kppky here, works exactly the same way as a built-in one.

In practice, that means installing the skill once:

hermes skills install scalekit-inc/authstack/kits/agentkit/host/hermes-delegated-auth

and then asking Hermes to use the Grafana connector by name, the same way earlier posts in this series asked it to use GitHub:

You: List the Grafana datasources for identifier user_1232.

Hermes resolves user_1232's connected account and calls c-grafanamcp_list_datasources through the skill's own execute_tool wrapper, the identical call this post already ran directly.

The other two patterns the docs describe, Virtual MCP on the host and Virtual MCP in your own app, both mint a Scalekit session token instead. A custom MCP connector like this one works with any of the three, since Scalekit is doing the same connected-account resolution underneath either way, but hermes-delegated-auth is the one built specifically for Hermes and doesn't need a token minted or re-minted at all.

REST connector or MCP connector: which one does your tool need?

Scalekit supports two ways to call a custom connector once it's created: via Tool Proxy (actions.request()) for REST API connectors, or via MCP tool calling for MCP connectors. Both are real, documented, fully supported paths.

What decides between them is whether the target tool ships its own MCP server, not how it authenticates. Scalekit's dashboard makes this explicit: the guided form for MCP connectors asks for "the base URL of the MCP server," and Scalekit connects to stateless MCP servers only, not arbitrary REST APIs run through that form. A plain REST API needs the other shape, even with the exact same auth type.

Before building either one, check:

  • Does the tool document its own MCP server and endpoint? If yes, it's an MCP connector, type="OAUTH" against that endpoint, called through list_tools() and execute_tool().
  • Does it only expose a REST API, with no MCP server anywhere in its docs? It's a REST connector: BEARER, API_KEY, or OAUTH against that REST base URL, called through actions.request().
  • Not sure which? Search the tool's docs for "MCP" before assuming REST. Plenty of tools, Grafana included, ship an MCP server that isn't the first thing you'd find looking for their API reference.

Grafana is the MCP case: Grafana Cloud ships its own official MCP server, reachable over OAuth, exactly the walkthrough above. Getting this check wrong means rebuilding the connector from scratch under the other shape, worth the two minutes to confirm first. To understand more about when MCP and REST APIs each make sense, see the difference between MCP and APIs.

How does this scale to more than one teammate?

Nothing about the mechanism changes; the same pattern from multi-user identity architecture applies directly. Each teammate authorizes their own connected account against the same connection:

for identifier in ["alice@company.com", "bob@company.com"]: client.actions.get_or_create_connected_account( connection_name="grafanamcp-rp4kppky", identifier=identifier, )

Each identity gets its own token, scoped independently, with the same isolation model that per-user JWT claims comparison demonstrates. Choosing per-user credential mode when the connection is created is what makes this possible; the org-wide alternative shares one credential across every identifier, which is the right call for something like a read-only dashboard bot, and the wrong call for anything where individual authorization actually matters. For a deeper look at how credential ownership works across agent tool-calling patterns, that post covers the tradeoffs in detail.

Where this leaves your Hermes setup

The gap this post opened never actually closes, since Scalekit's catalog will never have every internal tool a team wants, and neither will anyone else's.

What changes is that the gap stops being the end of the road. Grafana walked in with zero prior support, a create_custom_provider() call, and a connection; later, it has a connected account, an authorized user, and 118 real tools Hermes can call by name. "Not in the catalog" stops mattering the moment a custom connector exists for it.

Connecting Hermes Agent to Your Tools and APIs got one person talking to one tool safely. Multi-User Access Control for AI Agents on Hermes got a whole team talking to that tool without sharing a credential. This post removed the last restriction: the tool no longer has to be one Scalekit already supports. Build the connector, and Hermes can call it, whatever it is. For teams thinking about agent auth patterns and anti-patterns at scale, that post is the natural next read.

FAQs

Does every internal tool need its own custom connector?

Only if it isn't already in Scalekit's catalog, 389 providers at the time of writing. Worth checking first; the same live check this post opened with.

How do I know whether to build a REST connector or an MCP connector?

Check whether the tool exposes its own MCP server, not its auth type. A plain REST API needs BEARER, API_KEY, or OAUTH, called through client.actions.request(). A tool with its own MCP server, Grafana Cloud being a real example, needs OAUTH against that MCP endpoint, called through list_tools() and execute_tool(). Both are documented, supported paths.

Is this safe to build against a tool holding sensitive internal data?

The same security guidance from Connecting Hermes Agent to Your Tools and APIs and Multi-User Access Control for AI Agents on Hermes applies: credentials live in Scalekit, never in Hermes's own config, and per-user identifiers keep each person's access scoped to their own authorization. For a broader look at secure token management for AI agents, that post covers storage, rotation, and expiry in depth.

Do I need to manually define each tool an MCP connector exposes?

No. client.actions.list_tools() discovers every tool the upstream MCP server publishes automatically, 118 of them for Grafana in this post, with no hand-written tool definitions anywhere in the connector setup.

How do I confirm a custom connector was actually created?

Check the Scalekit dashboard, under AgentKit → Connectors. A new custom connector shows up there alongside the built-in ones. list_providers() in the Python SDK isn't a dependable way to check this today: a connector confirmed live and working through this post's Grafana example never showed up in a full, paginated list_providers() scan, even after real connections and tool calls against it succeeded. The dashboard is the source of truth here, not that call.

No items found.
Agent
Auth Quickstart
On this page
Share this article
Agent
Auth Quickstart

Acquire enterprise customers with
‍zero upfront cost.

Every feature unlocked. No hidden fees.