Skip to content

Commit 5acec74

Browse files
committed
Add files from group programming sessions
1 parent 94ce5d4 commit 5acec74

File tree

12 files changed

+1763
-2370
lines changed

12 files changed

+1763
-2370
lines changed

docker-compose.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,10 @@ services:
2222
- "./tests/system/test_apps/generating_app:/opt/splunk/etc/apps/generating_app"
2323
- "./tests/system/test_apps/reporting_app:/opt/splunk/etc/apps/reporting_app"
2424
- "./tests/system/test_apps/streaming_app:/opt/splunk/etc/apps/streaming_app"
25-
- "./tests/system/test_apps/modularinput_app:/opt/splunk/etc/apps/modularinput_app"
25+
- "./tests/system/test_apps/mcp_enabled_app:/opt/splunk/etc/apps/mcp_enabled_app"
2626
- "./splunklib:/opt/splunk/etc/apps/eventing_app/bin/splunklib"
2727
- "./splunklib:/opt/splunk/etc/apps/generating_app/bin/splunklib"
2828
- "./splunklib:/opt/splunk/etc/apps/reporting_app/bin/splunklib"
2929
- "./splunklib:/opt/splunk/etc/apps/streaming_app/bin/splunklib"
3030
- "./splunklib:/opt/splunk/etc/apps/modularinput_app/bin/splunklib"
31+
- "./splunklib:/opt/splunk/etc/apps/mcp_enabled_app/bin/splunklib"

pyproject.toml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ name = "splunk-sdk"
1111
dynamic = ["version"]
1212
description = "Splunk Software Development Kit for Python"
1313
readme = "README.md"
14-
requires-python = ">=3.7"
14+
requires-python = ">=3.10"
1515
license = { text = "Apache-2.0" }
1616
authors = [{ name = "Splunk, Inc.", email = "[email protected]" }]
1717
keywords = ["splunk", "sdk"]
@@ -29,7 +29,10 @@ classifiers = [
2929
"Topic :: Software Development :: Libraries :: Application Frameworks",
3030
]
3131

32-
dependencies = ["python-dotenv>=0.21.1"]
32+
dependencies = [
33+
"fastmcp>=2.12.4",
34+
"python-dotenv>=0.21.1",
35+
]
3336
optional-dependencies = { compat = ["six>=1.17.0"] }
3437

3538
[dependency-groups]
@@ -71,3 +74,6 @@ select = [
7174
"ANN", # flake8 type annotations
7275
"RUF", # ruff-specific rules
7376
]
77+
78+
[tool.uv.workspace]
79+
members = ["tests/system/test_apps/mcp_enabled_app"]

tests/system/test_apps/mcp_app/bin/mcp_tool.py

Lines changed: 0 additions & 41 deletions
This file was deleted.

tests/system/test_apps/mcp_app/default/app.conf

Lines changed: 0 additions & 16 deletions
This file was deleted.

tests/system/test_apps/mcp_app/default/commands.conf

Lines changed: 0 additions & 4 deletions
This file was deleted.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# splunk-mcp-poc
2+
3+
This example aspires to verify the points listed in [POC - AI with Splunk Apps](https://cisco-my.sharepoint.com/:w:/r/personal/hbalacha_cisco_com/Documents/POC%20-%20AI%20with%20Splunk%20Apps.docx?d=w2776e089011943abbd84c0fa30a53f34&csf=1&web=1&e=RxvShR)
4+
5+
## Pre-requirements
6+
7+
- Python 3.10+
8+
- `uv`
9+
- A way of launching Jupyter notebooks
10+
11+
## Getting started
12+
13+
- Run `uv sync`
14+
- `source .venv/bin/activate`
15+
- Run the code blocks `bin/mcp_enabled_app.ipynb`
Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"id": "f7f79ace",
6+
"metadata": {},
7+
"source": [
8+
"# POC – MCP Tool Registration\n",
9+
"\n",
10+
"This example aspires to verify the points listed in [POC - AI with Splunk Apps](https://cisco-my.sharepoint.com/:w:/r/personal/hbalacha_cisco_com/Documents/POC%20-%20AI%20with%20Splunk%20Apps.docx?d=w2776e089011943abbd84c0fa30a53f34&csf=1&web=1&e=RxvShR)\n",
11+
"\n",
12+
"- Develop @tool Decorator\n",
13+
" - [ ] Capture e.g. tool_name, description, inputs, outputs\n",
14+
"- MCP JSONSchema can (and most probably should) be used for tool registration in Splunk\n"
15+
]
16+
},
17+
{
18+
"cell_type": "code",
19+
"execution_count": null,
20+
"id": "c349a502",
21+
"metadata": {},
22+
"outputs": [],
23+
"source": [
24+
"from fastmcp import Client\n",
25+
"from mcp.types import Tool\n",
26+
"\n",
27+
"MCP_SERVER_HOST: str = \"0.0.0.0\"\n",
28+
"MCP_SERVER_PORT: int = 2137\n",
29+
"\n",
30+
"\n",
31+
"conn_str = f\"http://{MCP_SERVER_HOST}:{MCP_SERVER_PORT}/mcp\"\n",
32+
"mcp_client = Client(\"./tools.py\")\n",
33+
"\n",
34+
"\n",
35+
"async def get_tools() -> list[Tool]:\n",
36+
" tools = []\n",
37+
" async with mcp_client:\n",
38+
" tools = await mcp_client.list_tools()\n",
39+
"\n",
40+
" return tools\n",
41+
"\n",
42+
"\n",
43+
"call_tool_result = await get_tools()\n",
44+
"for tool in call_tool_result:\n",
45+
" print(tool.name)\n",
46+
" print(tool.description)\n",
47+
" print(tool.inputSchema)\n",
48+
" print(tool.outputSchema)\n",
49+
" print(tool.meta)"
50+
]
51+
},
52+
{
53+
"cell_type": "markdown",
54+
"id": "cd1da846",
55+
"metadata": {},
56+
"source": [
57+
"- execution_mode (external_http)\n",
58+
" - Is this what MCP calls `transports`?\n",
59+
"- execution_metadata (endpoint URL)\n",
60+
" - Isn't that just `/execute_tool` or `tool/call`?"
61+
]
62+
},
63+
{
64+
"cell_type": "code",
65+
"execution_count": null,
66+
"id": "931ff98f",
67+
"metadata": {},
68+
"outputs": [],
69+
"source": [
70+
"mcp_client = Client(\"tools.py\")\n",
71+
"\n",
72+
"\n",
73+
"async def call_tool(tool_name: str, arguments: dict[str, int]):\n",
74+
" result = None\n",
75+
" async with mcp_client:\n",
76+
" result = await mcp_client.call_tool(tool_name, arguments)\n",
77+
"\n",
78+
" return result\n",
79+
"\n",
80+
"\n",
81+
"call_tool_result = await call_tool(\"generating_csc\", {\"count\": 10})\n",
82+
"\n",
83+
"[print(data) for data in call_tool_result.data]\n"
84+
]
85+
},
86+
{
87+
"cell_type": "markdown",
88+
"id": "2da04c9f",
89+
"metadata": {},
90+
"source": [
91+
"- [ ] Support YAML v2 Tool Definition\n",
92+
"- [ ] Merge decorator + YAML for complete metadata\n",
93+
"\n",
94+
"- Implement Post-Install Script (sdk_post_install.py):\n",
95+
" - [ ] Load app modules → decorators populate RegisteredTools\n",
96+
" - [ ] Merge YAML/decorator metadata\n",
97+
" - [ ] Build MCP /tools/register payload\n",
98+
" - [ ] Call MCP registry with credentials (from App Manager)\n",
99+
" - [ ] Log success/fail to MCP audit\n"
100+
]
101+
},
102+
{
103+
"cell_type": "code",
104+
"execution_count": null,
105+
"id": "2e594272",
106+
"metadata": {},
107+
"outputs": [
108+
{
109+
"name": "stdout",
110+
"output_type": "stream",
111+
"text": [
112+
"/Users/bjedreck/Projects/spl-mcp-tool/bin/../default/app.conf\n",
113+
"name='aws_logs_search' title=None description='Execute SPL queries against AWS logs' inputSchema={'type': 'object', 'properties': {}, 'required': []} outputSchema={'type': 'object', 'properties': {}, 'required': []} icons=None annotations=None meta={'permissions': ['role:search_admin', 'role:aws_analyst'], 'tool_type': 'search', 'schema_version': '1.0'}\n"
114+
]
115+
}
116+
],
117+
"source": [
118+
"import configparser\n",
119+
"import os\n",
120+
"from dataclasses import asdict, dataclass, field\n",
121+
"from typing import Any, Literal\n",
122+
"\n",
123+
"from mcp.types import Tool\n",
124+
"\n",
125+
"\n",
126+
"@dataclass\n",
127+
"class SplunkMeta:\n",
128+
" permissions: list[str]\n",
129+
" tool_type: str\n",
130+
" schema_version: str\n",
131+
"\n",
132+
"\n",
133+
"@dataclass\n",
134+
"class McpInputOutputSchema:\n",
135+
" type: Literal[\"object\"] = \"object\"\n",
136+
" properties: dict[str, Any] = field(default_factory=lambda: {}) # pyright: ignore[reportExplicitAny]\n",
137+
" required: list[str] = field(default_factory=lambda: [])\n",
138+
"\n",
139+
"\n",
140+
"tool_reg_prefix = \"app:mcp_tool\"\n",
141+
"\n",
142+
"\n",
143+
"def filter_sections(section_name: str):\n",
144+
" return section_name.startswith(tool_reg_prefix)\n",
145+
"\n",
146+
"\n",
147+
"def match_input_schema(input: Literal[\"query_string\"] | Literal[\"other\"]):\n",
148+
" match input:\n",
149+
" case \"query_string\":\n",
150+
" return {\n",
151+
" \"type\": \"object\",\n",
152+
" \"properties\": {\n",
153+
" \"query_string\": {\n",
154+
" \"type\": \"string\",\n",
155+
" \"description\": \"SPL2 query string\",\n",
156+
" }\n",
157+
" },\n",
158+
" }\n",
159+
" case _:\n",
160+
" raise NotImplementedError(\"We don't know what to put here lol\")\n",
161+
"\n",
162+
"\n",
163+
"def parse_app_conf_tool_registrations(file_path: str) -> list[Tool]:\n",
164+
" config = configparser.ConfigParser()\n",
165+
" all_sections_len = config.read(file_path)\n",
166+
" if len(all_sections_len) == 0:\n",
167+
" return []\n",
168+
"\n",
169+
" tool_reg_sections: list[str] = list(filter(filter_sections, config.sections()))\n",
170+
" if len(tool_reg_sections) == 0:\n",
171+
" return []\n",
172+
"\n",
173+
" ini_tools: list[Tool] = []\n",
174+
" for reg_section in tool_reg_sections:\n",
175+
" reg_section_data = config[reg_section]\n",
176+
"\n",
177+
" name: str = reg_section.split(\":\")[2]\n",
178+
" description = reg_section_data[\"description\"]\n",
179+
" # https://modelcontextprotocol.io/specification/2025-06-18/schema#tool\n",
180+
" inputSchema = McpInputOutputSchema(properties={}, required=[])\n",
181+
" outputSchema = McpInputOutputSchema(properties={}, required=[])\n",
182+
" meta = SplunkMeta(\n",
183+
" permissions=[\n",
184+
" perm.strip()\n",
185+
" for perm in reg_section_data[\"permissions\"].strip().split(\",\")\n",
186+
" ],\n",
187+
" tool_type=\"search\",\n",
188+
" schema_version=reg_section_data[\"schema_version\"].strip(),\n",
189+
" )\n",
190+
"\n",
191+
" ini_tool = Tool(\n",
192+
" name=name,\n",
193+
" description=description,\n",
194+
" inputSchema=asdict(inputSchema),\n",
195+
" outputSchema=asdict(outputSchema),\n",
196+
" _meta=asdict(meta),\n",
197+
" )\n",
198+
" ini_tools.append(ini_tool)\n",
199+
"\n",
200+
" return ini_tools\n",
201+
"\n",
202+
"\n",
203+
"async def post_install():\n",
204+
" curr_path = os.path.join(os.getcwd(), \"..\", \"default\", \"app.conf\")\n",
205+
" print(curr_path)\n",
206+
" yaml_tool_registrations: list[Tool] = parse_app_conf_tool_registrations(curr_path)\n",
207+
" [print(toolReg) for toolReg in yaml_tool_registrations]\n",
208+
"\n",
209+
"\n",
210+
"await post_install()\n"
211+
]
212+
}
213+
],
214+
"metadata": {
215+
"kernelspec": {
216+
"display_name": ".venv",
217+
"language": "python",
218+
"name": "python3"
219+
},
220+
"language_info": {
221+
"codemirror_mode": {
222+
"name": "ipython",
223+
"version": 3
224+
},
225+
"file_extension": ".py",
226+
"mimetype": "text/x-python",
227+
"name": "python",
228+
"nbconvert_exporter": "python",
229+
"pygments_lexer": "ipython3",
230+
"version": "3.13.7"
231+
}
232+
},
233+
"nbformat": 4,
234+
"nbformat_minor": 5
235+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Splunk app configuration file
2+
3+
[install]
4+
is_configured = 0
5+
6+
[ui]
7+
is_visible = true
8+
label = [PoC] MCP-Enabled App
9+
10+
[launcher]
11+
author = Splunk Inc.
12+
description = Trying to verify the implementation plan for MCP Tool registration
13+
version = 0.0.1
14+
15+
# Example MCP registration in app.conf
16+
[app:mcp_tool:generate_csc]
17+
description = Generate
18+
tool_type = search
19+
inputs = count
20+
outputs = results
21+
permissions = role:search_admin, role:mcp_tool
22+
schema_version = 1.0
23+
mcp_enabled = true

tests/system/test_apps/mcp_enabled_app/local/app.conf

Whitespace-only changes.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
[]
2+
access = read : [ * ], write : [ admin, power ]
3+
export = none
4+
5+
[tags]
6+
export = system
7+
8+
[props]
9+
export = system
10+
11+
[transforms]
12+
export = system
13+
14+
[lookups]
15+
export = system

0 commit comments

Comments
 (0)