Managing custom tools in Splunk MCP Server
Custom tools allow external Splunk applications to register their own tools with the Splunk MCP server. Once registered, these tools can be enabled and made available to AI assistants that interact with your Splunk environment.
| Action | HTTP Method | Description |
|---|---|---|
| Create a custom tool | POST |
Register a single new tool. |
| Batch replace tool | POST |
Atomically replace all tools for an app. |
| Enable or disable a tool | POST |
Toggle whether a tool is active. |
| List or get tools | GET |
View registered tools. |
| Update a tool | PUT |
Edit an existing tool's definition. |
| Delete tools | DELETE |
Remove one tool or all tools for an app. |
Key concepts
See the following table for some key concepts on MCP Server custom tools:
| Concept | Description |
|---|---|
| Tool ID |
A globally unique identifier in the format |
| External App ID |
The Splunk app that owns the tool for example |
| Enabled tools | A tool must be explicitly enabled before it becomes available to the MCP server. Enabling a tool also checks for name collisions with other active tools. |
| Built-in tools | Tools shipped with the MCP server app itself. They cannot be modified or deleted through the API call. |
| Execution type |
A tool can execute an |
Authentication
All requests are authenticated using a Splunk platform authentication token belonging to a user that has the mcp_tool_admin capability. The token is passed as a bearer token in the Authorization header.
Authorization: Bearer <splunk_token>
Content-Type: application/json
Base URL
All examples provided assume the following base URL. Replace <splunk_host> and <port> with your Splunk instance values:
https://<splunk_host>:<port>/services/mcp_tools
Create a custom tool
Register a single new custom tool.
-
Method:
POST -
Content type:
application/json
Request body
See the following table for request body field information:
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Tool name. Alphanumeric but may include underscores. Must start with a letter. Used as part of the Tool ID. |
title |
string | Yes | Human-readable display name shown to AI assistants. |
description |
string | Yes | Explains what the tool does. Good descriptions improve AI tool selection. |
inputSchema |
object | Yes | JSON Schema (type: "object") describing the tool's parameters. See Input Schema. |
_meta |
object | Yes | Execution configuration, tags, examples, and app metadata. See Meta Reference. |
enabled |
boolean | No | Set true to enable the tool immediately after creation. Default: false. |
override |
boolean | No | When true and enabled: true, overrides any currently-enabled tool with the same name. Default: false. |
Example: SPL execution tool
This example registers a tool whose execution type is SPL ("_meta.execution.type": "spl").
When invoked, the MCP server substitutes the {{...}} placeholders into the SPL template and runs the resulting query against Splunk.
curl -k -X POST \
"https://<splunk_host>:8089/services/mcp_tools" \
-H "Authorization: Bearer <splunk_token>" \
-H "Content-Type: application/json" \
-d '{
"name": "search_error_logs",
"title": "Search Error Logs",
"description": "Searches Splunk for error log events matching a keyword within a given index.",
"inputSchema": {
"type": "object",
"properties": {
"keyword": {
"type": "string",
"description": "The error keyword to search for.",
"examples": ["OOM", "timeout", "connection refused"]
},
"index": {
"type": "string",
"description": "Splunk index to search.",
"default": "main"
},
"max_results": {
"type": "integer",
"description": "Maximum number of results to return.",
"default": 20,
"minimum": 1,
"maximum": 500
}
},
"required": ["keyword"]
},
"_meta": {
"external_app_id": "my_monitoring_app",
"execution": {
"type": "spl",
"template": "index={{index}} {{keyword}} | head {{max_results}}",
"row_limiter": true,
"time_range": true
},
"tags": ["logs", "errors", "search"],
"examples": [
{
"name": "Search for OOM errors",
"description": "Find out-of-memory errors in the last hour",
"expected_use": "When a user asks about memory issues",
"arguments": {"keyword": "OOM", "index": "main"}
}
]
},
"enabled": true
}'
SPL execution fields
template, row_limiter, time_range, and guardrails must not be set for API execution tool.
| Field | Required | Description |
|---|---|---|
template |
Yes | SPL query template. Use {{param_name}} placeholders that map to inputSchema properties. |
row_limiter |
No, default = true | When true, automatically appends a row_limit argument to cap result size and prevent runaway queries. |
time_range |
No, default = true | When true, automatically adds earliest_time / latest_time arguments for time-bounded searches. |
guardrails |
No, default = false | Enables additional safety checks on the generated SPL. |
Example: API execution tool
This example registers a tool whose execution type is API ("_meta.execution.type": "api").
When invoked, the MCP server substitutes the {{...}} placeholders into the endpoint, headers, params, and body fields, and issues an HTTP request against Splunk's REST API.
curl -k -X POST \
"https://<splunk_host>:8089/services/mcp_tools" \
-H "Authorization: Bearer <splunk_token>" \
-H "Content-Type: application/json" \
-d '{
"name": "create_saved_search",
"title": "Create Saved Search",
"description": "Creates a new saved search in the given Splunk app namespace.",
"inputSchema": {
"type": "object",
"properties": {
"app": {
"type": "string",
"description": "Splunk app namespace that will own the saved search.",
"default": "search"
},
"search_name": {
"type": "string",
"description": "Name to give the new saved search."
},
"search": {
"type": "string",
"description": "SPL query that the saved search will run."
},
"description": {
"type": "string",
"description": "Human-readable description of the saved search.",
"default": ""
}
},
"required": ["search_name", "search"]
},
"_meta": {
"external_app_id": "my_monitoring_app",
"execution": {
"type": "api",
"method": "POST",
"endpoint": "/servicesNS/nobody/{{app}}/saved/searches",
"headers": {
"Accept": "application/json",
"Content-Type": "application/x-www-form-urlencoded"
},
"params": {
"output_mode": "json"
},
"body": {
"name": "{{search_name}}",
"search": "{{search}}",
"description": "{{description}}"
}
},
"tags": ["saved-search", "rest"],
"examples": [
{
"name": "Create an errors saved search",
"description": "Save an SPL query that finds error events in the last 24 hours",
"expected_use": "When a user asks to save a search for repeated reuse",
"arguments": {
"app": "search",
"search_name": "Errors in the last 24 hours",
"search": "index=main error earliest=-24h",
"description": "All error events from the last day"
}
}
]
},
"enabled": true
}
API execution fields
See the following table of API execution fields:
method, endpoint, headers, params, and body must not be set for SPL execution tool
| Field | Required | Description |
|---|---|---|
method |
Yes | HTTP method: GET, POST, PUT, PATCH, or DELETE. |
endpoint |
Yes | Target path or URL. Splunk REST paths (e.g. /services/...) are routed through the local splunkd instance. |
headers, params, body |
No | Use headers to add HTTP headers, params to include query parameters in the URL, and body to define the request payload. To insert user-provided values dynamically, use {{param_name}} inside any string value.
Note: The parameter name must match a property defined in inputSchema field.
|
Success response: 201 created
{
"tool_id": "my_monitoring_app:get_saved_search"
}
Error responses
See the following table for error response information:
| Status | Code | Reason |
|---|---|---|
| 400 Bad Request | validation_error |
One or more fields failed validation. The details array lists every problem. |
| 409 Conflict | name_conflict |
A custom tool with the same name already exists. |
{
"error": true,
"code": "validation_error",
"message": "Tool definition failed validation.",
"details": [
"'name' must start with a letter and contain only alphanumeric characters and underscores.",
"'description' is required and must be a non-empty string."
]
}
Batch replace tools for an app
Atomically replace all tools registered under an external_app_id. Inserts new, updates changed, and deletes missing tools in one call with rollback on failure.
-
Method:
POST -
Content-Type:
application/json
Request body
See the following table for request body field information:
| Field | Type | Required | Description |
|---|---|---|---|
external_app_id |
string | Yes | The app namespace to replace tools for. |
tools |
array<object> | Yes | Array of tool definitions. Pass [] to delete all tools for the app. |
Example
Replace all tools associated with an app. The following request deletes all existing tools for my_monitoring_app and creates a new set of tools specified in the payload:
curl -k -X POST \
"https://<splunk_host>:8089/services/mcp_tools" \
-H "Authorization: Bearer <splunk_token>" \
-H "Content-Type: application/json" \
-d '{
"external_app_id": "my_monitoring_app",
"tools": [
{
"name": "search_error_logs",
"title": "Search Error Logs",
"description": "Searches Splunk for error log events matching a keyword.",
"inputSchema": {
"type": "object",
"properties": {
"keyword": {"type": "string", "description": "Error keyword to search for."}
},
"required": ["keyword"]
},
"_meta": {
"external_app_id": "my_monitoring_app",
"execution": {
"type": "spl",
"template": "index=main {{keyword}} | head 20"
},
"tags": ["logs", "errors"]
}
},
{
"name": "get_host_metrics",
"title": "Get Host Metrics",
"description": "Retrieves CPU and memory metrics for a specific host.",
"inputSchema": {
"type": "object",
"properties": {
"hostname": {"type": "string", "description": "Target host name."}
},
"required": ["hostname"]
},
"_meta": {
"external_app_id": "my_monitoring_app",
"execution": {
"type": "spl",
"template": "index=metrics host={{hostname}} | stats avg(cpu_pct), avg(mem_pct) by host"
},
"tags": ["metrics", "performance"]
}
}
]
}'
Success response: 200 OK
See the following table for details:
| Field | Description |
|---|---|
registered_count |
Number of tools created or updated. |
deleted_count |
Number of stale tools removed. |
skipped_built_in |
Built-in tools that were skipped. Cannot be replaced. |
failed_deletes |
Tools that could not be deleted. Partial failure. All new or updated tools are still committed. |
{
"external_app_id": "my_monitoring_app",
"message": "Replaced tools successfully.",
"registered_count": 2,
"deleted_count": 1,
"skipped_built_in": [],
"failed_deletes": []
}
Error responses
See the following table for error response information:
| Status | Code | Reason |
|---|---|---|
| 400 Bad Request | missing_external_app_id |
The external_app_id is missing or empty. |
| 400 Bad Request | invalid_tools |
Tools is not an array. |
| 400 Bad Request | validation_error |
One or more tool definitions failed validation. |
| 400 Bad Request | duplicate_tools |
The payload contains two tools with the same name under the same app. |
| 500 Internal Server Error | kvstore_error |
Upsert failed. All changes have been rolled back automatically. |
Enable or disable a tool
Toggle a registered tool on or off. Only enabled tools are exposed via the MCP server.
-
Method:
POST -
Content type:
application/json
pass override: true to take over when needed.
Request body
See the following table for request body field information:
| Field | Type | Required | Description |
|---|---|---|---|
tool_id |
string | Yes | Full Tool ID : <external_app_id>:<tool_name> |
enabled |
boolean | Yes | Use true to enable, false to disable. |
override |
boolean | No | When true, disables any conflicting tool that currently holds this name (enable only). |
Examples
curl -k -X POST \
"https://<splunk_host>:8089/services/mcp_tools" \
-H "Authorization: Bearer <splunk_token>" \
-H "Content-Type: application/json" \
-d '{
"tool_id": "my_monitoring_app:search_error_logs",
"enabled": true
}'
curl -k -X POST \
"https://<splunk_host>:8089/services/mcp_tools" \
-H "Authorization: Bearer <splunk_token>" \
-H "Content-Type: application/json" \
-d '{
"tool_id": "my_monitoring_app:search_error_logs",
"enabled": true,
"override": true
}'
curl -k -X POST \
"https://<splunk_host>:8089/services/mcp_tools" \
-H "Authorization: Bearer <splunk_token>" \
-H "Content-Type: application/json" \
-d '{
"tool_id": "my_monitoring_app:search_error_logs",
"enabled": false
}'
Success response: 200 OK
{
"tool_id": "my_monitoring_app:search_error_logs",
"enabled": true,
"message": "Tool enabled successfully."
}
Error responses
See the following table for error response information:
| Status | Code | Reason |
|---|---|---|
| 400 Bad Request | missing_tool_id |
The tool_id field is missing or empty. |
| 400 Bad Request | missing_enabled_flag |
The enabled field is missing or not a boolean. |
| 400 Bad Request | missing_tool_name |
The tool_name could not be inferred from tool_id; provide it explicitly. |
| 404 Not Found | tool_not_found |
No tool found with the given tool_id. |
| 409 Conflict | tool_name_conflict |
Another tool with the same short name is already enabled. Use "override": true to take over. |
| 409 Conflict | required_app_missing |
The tool requires a Splunk app that is not installed. |
| 500 Internal Server Error | tool_enable_failed |
Unexpected error while enabling the tool. |
collision_ids lists other registered tools that share the same short name but are not active due to the conflict.
List or get tools
Retrieve registered tools, optionally filtered by various criteria.
-
Method:
GET
Query parameters
See the following table for list of get tools parameters:
| Parameter | Type | Description |
|---|---|---|
tool_id |
string | Fetch a single tool by its full Tool ID. |
name |
string | Filter by tool short name. |
external_app_id |
string | Filter tools belonging to a specific app. |
tags |
string | Comma-separated list of tags to filter by (e.g., logs,errors). |
enabled_tools |
boolean | Pass true to get the list of currently enabled tools only. |
Examples
curl -k -X GET \
"https://<splunk_host>:8089/services/mcp_tools" \
-H "Authorization: Bearer <splunk_token>"
curl -k -X GET \
"https://<splunk_host>:8089/services/mcp_tools?tool_id=my_monitoring_app:search_error_logs" \
-H "Authorization: Bearer <splunk_token>"
curl -k -X GET \
"https://<splunk_host>:8089/services/mcp_tools?external_app_id=my_monitoring_app" \
-H "Authorization: Bearer <splunk_token>"
curl -k -X GET \
"https://<splunk_host>:8089/services/mcp_tools?enabled_tools=true" \
-H "Authorization: Bearer <splunk_token>"
Success responses
{
"tool": {
"name": "my_monitoring_app_search_error_logs",
"title": "Search Error Logs",
"description": "Searches Splunk for error log events matching a keyword.",
"inputSchema": {
"type": "object",
"properties": {
"keyword": {"type": "string", "description": "Error keyword to search for."}
},
"required": ["keyword"]
},
"_meta": {
"external_app_id": "my_monitoring_app",
"execution": {"type": "spl", "template": "index=main {{keyword}} | head 20"},
"tags": ["logs", "errors"],
"built_in": false
}
}
}
{
"tools": [
{ "name": "...", "title": "...", "description": "...", "inputSchema": {...}, "_meta": {...} },
{ "name": "...", "title": "...", "description": "...", "inputSchema": {...}, "_meta": {...} }
],
"total": 2
}
{
"enabled_tools": [
{
"tool_name": "search_error_logs",
"tool_id": "my_monitoring_app:search_error_logs",
"collision_ids": []
}
]
}
collision_ids lists other registered tools that share the same short name but are currently not active due to the conflict.
Update a tool
Modify an existing tool's definition. Only supplied fields are updated. Omitted fields remain unchanged.
-
Method:
PUT -
Content-Type:
application/json
external_app_id are immutable after creation. Built-in tools cannot be updated
Request body
See the following table for request body field information:
| Field | Type | Required | Description |
|---|---|---|---|
tool_id |
string | Yes | The full Tool ID of the tool to update. |
title |
string | No | New display title. |
description |
string | No | New description. |
inputSchema |
object | No | Replacement input schema. |
Example
curl -k -X PUT \
"https://<splunk_host>:8089/services/mcp_tools" \
-H "Authorization: Bearer <splunk_token>" \
-H "Content-Type: application/json" \
-d '{
"tool_id": "my_monitoring_app:search_error_logs",
"description": "Searches Splunk for error log events. Supports index and keyword filters.",
"_meta": {
"tags": ["logs", "errors", "monitoring"],
"execution": {
"type": "spl",
"template": "index={{index}} {{keyword}} | head {{max_results}}",
"row_limiter": true,
"time_range": true
}
}
}'
Success response: 200 OK
{
"tool_id": "my_monitoring_app:search_error_logs",
"tool": {
"name": "my_monitoring_app_search_error_logs",
"title": "Search Error Logs",
"description": "Searches Splunk for error log events. Supports index and keyword filters.",
"inputSchema": {...},
"_meta": {...}
}
}
Error responses
See the following table for error response information:
| Status | Code | Reason |
|---|---|---|
| 400 Bad Request | invalid_payload | Request body is not a JSON object. |
| 400 Bad Request | missing_tool_id | The tool_id field is missing or empty. |
| 400 Bad Request | no_updates | No updatable fields were provided in the body. |
| 400 Bad Request | invalid_meta | The _meta field is not an object. |
| 404 Not Found | tool_not_found | No tool found with the given tool_id. |
| 409 Conflict | immutable_field | Attempted to change name or external_app_id. |
| 409 Conflict | built_in_tool | Built-in tools cannot be updated. |
| 502 Bad Gateway | kvstore_response_error | KV Store returned a malformed response. |
Delete tools
You can delete a single tool by specifying tool_id or delete all tools linked to an app.
-
Method:
DELETE -
Content type:
application/json
Request body
See the following table for request body field information to delete a single tool:
| Field | Type | Required | Description |
|---|---|---|---|
tool_id |
string | One of | The full Tool ID of the tool to delete. |
external_app_id |
string | One of | Delete all tools linked to this app. |
Examples
curl -k -X DELETE \
"https://<splunk_host>:8089/services/mcp_tools" \
-H "Authorization: Bearer <splunk_token>" \
-H "Content-Type: application/json" \
-d '{
"tool_id": "my_monitoring_app:search_error_logs"
}'
curl -k -X DELETE \
"https://<splunk_host>:8089/services/mcp_tools" \
-H "Authorization: Bearer <splunk_token>" \
-H "Content-Type: application/json" \
-d '{
"external_app_id": "my_monitoring_app"
}'
Success response: 200 OK
{
"deleted": true,
"tool_id": "my_monitoring_app:search_error_logs",
"message": "Tool deleted successfully."
}
Error responses
See the following table for error response information:
| Status | Code | Reason |
|---|---|---|
| 400 Bad Request | invalid_payload |
Request body is not a JSON object. |
| 400 Bad Request | missing_identifier |
Neither tool_id nor external_app_id was provided. |
| 404 Not Found | tool_not_found |
No tool found with the given tool_id. |
| 409 Conflict | built_in_tool |
Attempted to delete a built-in tool. |
| 502 Bad Gateway | kvstore_response_error |
KV Store returned a malformed response. |