|  | 
| 10 | 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 | 11 |     "\n", | 
| 12 | 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" | 
|  | 13 | +    "  - [ ] Capture e.g. tool_name, description, inputs, outputs\n" | 
|  | 14 | +   ] | 
|  | 15 | +  }, | 
|  | 16 | +  { | 
|  | 17 | +   "cell_type": "code", | 
|  | 18 | +   "execution_count": null, | 
|  | 19 | +   "id": "18ab5550", | 
|  | 20 | +   "metadata": {}, | 
|  | 21 | +   "outputs": [], | 
|  | 22 | +   "source": [] | 
|  | 23 | +  }, | 
|  | 24 | +  { | 
|  | 25 | +   "cell_type": "markdown", | 
|  | 26 | +   "id": "363201e8", | 
|  | 27 | +   "metadata": {}, | 
|  | 28 | +   "source": [ | 
|  | 29 | +    "- MCP JSONSchema can (and most probably should) be used for tool registration in Splunk" | 
| 15 | 30 |    ] | 
| 16 | 31 |   }, | 
| 17 | 32 |   { | 
|  | 
| 98 | 113 |     "  - [ ] Call MCP registry with credentials (from App Manager)\n", | 
| 99 | 114 |     "  - [ ] Log success/fail to MCP audit\n" | 
| 100 | 115 |    ] | 
| 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 | 116 |   } | 
| 213 | 117 |  ], | 
| 214 | 118 |  "metadata": { | 
|  | 
0 commit comments