ServiceNow MCP
Use this integration to connect securely with a ServiceNow Model Context Protocol (MCP) server and access its tools in real time.
Case Management · ServiceNow MCP MCP
Details
| ID | ServiceNow MCP |
|---|---|
| Provider | ServiceNow |
| Category | Case Management |
| From Version | 8.13.0 |
| Docker Image | demisto/mcp:1.0.0.10120494 |
| Supported Modules | Attack Surface Management Exposure Management Agentix Cortex Cloud Cloud Runtime Security Cloud Posture Security XSIAM EDR |
Connection settings
| Parameter | Name | Type | Required | Default |
|---|---|---|---|---|
| ServiceNow Instance URL | instance_url |
Short text | Yes | — |
| MCP Server Name | server_name |
Short text | Yes | sn_mcp_server_default |
| Client ID | oauth_credentials |
Credentials | Yes | — |
| Authorization code | auth_code |
Credentials | No | — |
| Redirect URI | redirect_uri |
Short text | No | https://oproxy.demisto.ninja/authcode |
| Trust any certificate (not secure) | insecure |
Boolean | No | — |
Commands (4)
An MCP server publishes no fixed command set: list-tools and
call-tool are the protocol plumbing, and the tools they reach are
discovered from the remote server when the instance connects. The pack cannot name them ahead of time, so
neither can this page.
-
call-toolCalls a specific tool on the MCP server with optional input parameters.
-
list-toolsRetrieves a list of available tools in the ServiceNow MCP server.
-
servicenow-mcp-auth-testTest the authentication configuration with the MCP server.
-
servicenow-mcp-generate-login-urlGenerate an authentication login URL.
import pytest from pytest_mock import MockerFixture import demistomock as demisto from ServiceNowMCP import main, build_servicenow_urls, validate_required_params, DEFAULT_MCP_SERVER_NAME VALID_PARAMS = { "instance_url": "https://dev12345.service-now.com", "server_name": "sn_mcp_server_default", "oauth_credentials": {"identifier": "test_client_id", "password": "test_client_secret"}, "auth_code": {"password": "test_auth_code"}, "redirect_uri": "https://oproxy.demisto.ninja/authcode", "insecure": False, } class TestBuildServiceNowUrls: """Unit tests for build_servicenow_urls.""" def test_build_urls_with_explicit_server_name(self): """Given: A full ServiceNow instance URL and explicit server name. When: build_servicenow_urls is called. Then: Correct base_url, authorization_endpoint, and token_endpoint are returned. """ base_url, auth_endpoint, token_endpoint = build_servicenow_urls("https://dev12345.service-now.com", "my_server") assert base_url == "https://dev12345.service-now.com/sncapps/mcp-server/mcp/my_server" assert auth_endpoint == "https://dev12345.service-now.com/oauth_auth.do" assert token_endpoint == "https://dev12345.service-now.com/oauth_token.do" def test_build_urls_with_empty_server_name_uses_default(self): """Given: A full ServiceNow instance URL and an empty server name. When: build_servicenow_urls is called. Then: The default Quickstart server name is used in the base URL. """ base_url, _, _ = build_servicenow_urls("https://dev12345.service-now.com", "") assert base_url == f"https://dev12345.service-now.com/sncapps/mcp-server/mcp/{DEFAULT_MCP_SERVER_NAME}" def test_build_urls_strips_whitespace_and_trailing_slash(self): """Given: An instance URL with surrounding whitespace and a trailing slash. When: build_servicenow_urls is called. Then: The URLs are built with the cleaned instance URL. """ base_url, auth_endpoint, token_endpoint = build_servicenow_urls( " https://dev12345.service-now.com/ ", " custom_server " ) assert base_url == "https://dev12345.service-now.com/sncapps/mcp-server/mcp/custom_server" assert auth_endpoint == "https://dev12345.service-now.com/oauth_auth.do" assert token_endpoint == "https://dev12345.service-now.com/oauth_token.do" class TestValidateRequiredParams: """Unit tests for validate_required_params.""" def test_valid_params_passes(self): """Given: All required parameters are provided. When: validate_required_params is called. Then: No exception is raised. """ validate_required_params("https://dev12345.service-now.com", "cid", "csecret") def test_missing_instance_raises(self): """Given: The instance URL is missing. When: validate_required_params is called. Then: A ValueError is raised mentioning the instance requirement. """ with pytest.raises(ValueError, match="ServiceNow Instance URL"): validate_required_params("", "cid", "csecret") def test_missing_client_id_raises(self): """Given: The Client ID is missing. When: validate_required_params is called. Then: A ValueError is raised mentioning OAuth credentials. """ with pytest.raises(ValueError, match="Client ID and Client Secret"): validate_required_params("https://dev12345.service-now.com", "", "csecret") def test_missing_client_secret_raises(self): """Given: The Client Secret is missing. When: validate_required_params is called. Then: A ValueError is raised mentioning OAuth credentials. """ with pytest.raises(ValueError, match="Client ID and Client Secret"): validate_required_params("https://dev12345.service-now.com", "cid", "") class TestMain: """Unit tests for the main function of ServiceNowMCP.""" @pytest.mark.asyncio async def test_test_module_command(self, mocker: MockerFixture): """Given: The test-module command is called for an OAuth integration. When: Main function processes the command. Then: return_error is called with a message directing the user to the auth-test command. """ async def mock_close(): pass mock_client = mocker.MagicMock() mock_client.close = mock_close mocker.patch("ServiceNowMCP.Client", return_value=mock_client) mocker.patch.object(demisto, "params", return_value=VALID_PARAMS) mocker.patch.object(demisto, "args", return_value={}) mocker.patch.object(demisto, "command", return_value="test-module") mock_return_error = mocker.patch("ServiceNowMCP.return_error") await main() mock_return_error.assert_called_once() error_call = mock_return_error.call_args[0][0] assert "Test module is unavailable for this integration" in error_call assert "servicenow-mcp-auth-test" in error_call @pytest.mark.asyncio async def test_list_tools_command(self, mocker: MockerFixture): """Given: The list-tools command is called with valid params. When: Main function processes the command. Then: The client's list_tools method is called and results are returned. """ async def mock_list_tools(server_name): return {"tools": []} async def mock_close(): pass mock_client = mocker.MagicMock() mock_client.list_tools = mock_list_tools mock_client.close = mock_close mocker.patch("ServiceNowMCP.Client", return_value=mock_client) mocker.patch.object(demisto, "params", return_value=VALID_PARAMS) mocker.patch.object(demisto, "args", return_value={}) mocker.patch.object(demisto, "command", return_value="list-tools") mock_return_results = mocker.patch("ServiceNowMCP.return_results") await main() mock_return_results.assert_called_once_with({"tools": []}) @pytest.mark.asyncio async def test_call_tool_command_with_arguments(self, mocker: MockerFixture): """Given: The call-tool command is called with a tool name and arguments. When: Main function processes the command. Then: The client's call_tool method is called with the provided parameters. """ captured: dict = {} async def mock_call_tool(name, arguments): captured["name"] = name captured["arguments"] = arguments return {"result": "success"} async def mock_close(): pass mock_client = mocker.MagicMock() mock_client.call_tool = mock_call_tool mock_client.close = mock_close mocker.patch("ServiceNowMCP.Client", return_value=mock_client) mocker.patch.object(demisto, "params", return_value=VALID_PARAMS) mocker.patch.object(demisto, "args", return_value={"name": "lookup_incident", "arguments": '{"number": "INC0010001"}'}) mocker.patch.object(demisto, "command", return_value="call-tool") mock_return_results = mocker.patch("ServiceNowMCP.return_results") await main() assert captured == {"name": "lookup_incident", "arguments": '{"number": "INC0010001"}'} mock_return_results.assert_called_once_with({"result": "success"}) @pytest.mark.asyncio async def test_call_tool_command_without_arguments(self, mocker: MockerFixture): """Given: The call-tool command is called with only a tool name (no arguments). When: Main function processes the command. Then: The client's call_tool method is called with an empty arguments string. """ captured: dict = {} async def mock_call_tool(name, arguments): captured["name"] = name captured["arguments"] = arguments return {"result": "ok"} async def mock_close(): pass mock_client = mocker.MagicMock() mock_client.call_tool = mock_call_tool mock_client.close = mock_close mocker.patch("ServiceNowMCP.Client", return_value=mock_client) mocker.patch.object(demisto, "params", return_value=VALID_PARAMS) mocker.patch.object(demisto, "args", return_value={"name": "list_incidents"}) mocker.patch.object(demisto, "command", return_value="call-tool") mock_return_results = mocker.patch("ServiceNowMCP.return_results") await main() assert captured == {"name": "list_incidents", "arguments": ""} mock_return_results.assert_called_once_with({"result": "ok"}) @pytest.mark.asyncio async def test_auth_test_command(self, mocker: MockerFixture): """Given: The servicenow-mcp-auth-test command is called. When: Main function processes the command. Then: The client's test_connection method is called with auth_test=True. """ captured: dict = {} async def mock_test_connection(auth_test=False): captured["auth_test"] = auth_test return {"status": "authenticated"} async def mock_close(): pass mock_client = mocker.MagicMock() mock_client.test_connection = mock_test_connection mock_client.close = mock_close mocker.patch("ServiceNowMCP.Client", return_value=mock_client) mocker.patch.object(demisto, "params", return_value=VALID_PARAMS) mocker.patch.object(demisto, "args", return_value={}) mocker.patch.object(demisto, "command", return_value="servicenow-mcp-auth-test") mock_return_results = mocker.patch("ServiceNowMCP.return_results") await main() assert captured == {"auth_test": True} mock_return_results.assert_called_once_with({"status": "authenticated"}) @pytest.mark.asyncio async def test_generate_login_url_command(self, mocker: MockerFixture): """Given: The servicenow-mcp-generate-login-url command is called. When: Main function processes the command. Then: generate_login_url is called with the ServiceNow authorization endpoint and troubleshooting enabled. """ captured_kwargs: dict = {} async def mock_generate_login_url(*args, **kwargs): captured_kwargs.update(kwargs) return {"login_url": "https://dev12345.service-now.com/oauth_auth.do?..."} async def mock_close(): pass mock_client = mocker.MagicMock() mock_client.close = mock_close mocker.patch("ServiceNowMCP.Client", return_value=mock_client) mocker.patch("ServiceNowMCP.generate_login_url", side_effect=mock_generate_login_url) mocker.patch.object(demisto, "params", return_value=VALID_PARAMS) mocker.patch.object(demisto, "args", return_value={}) mocker.patch.object(demisto, "command", return_value="servicenow-mcp-generate-login-url") mock_return_results = mocker.patch("ServiceNowMCP.return_results") await main() assert captured_kwargs["authorization_endpoint"] == "https://dev12345.service-now.com/oauth_auth.do" assert captured_kwargs["redirect_uri"] == VALID_PARAMS["redirect_uri"] assert captured_kwargs["troubleshooting_redirect"] is True mock_return_results.assert_called_once_with({"login_url": "https://dev12345.service-now.com/oauth_auth.do?..."}) @pytest.mark.asyncio async def test_unknown_command(self, mocker: MockerFixture): """Given: An unknown command is called. When: Main function processes the command. Then: A NotImplementedError is raised and return_error is called. """ async def mock_close(): pass mock_client = mocker.MagicMock() mock_client.close = mock_close mocker.patch("ServiceNowMCP.Client", return_value=mock_client) mocker.patch.object(demisto, "params", return_value=VALID_PARAMS) mocker.patch.object(demisto, "args", return_value={}) mocker.patch.object(demisto, "command", return_value="unknown-command") mock_return_error = mocker.patch("ServiceNowMCP.return_error") await main() mock_return_error.assert_called_once() error_call = mock_return_error.call_args[0][0] assert "Command unknown-command is not implemented" in error_call @pytest.mark.asyncio async def test_exception_handling(self, mocker: MockerFixture): """Given: An exception occurs during command processing. When: Main function processes the command. Then: The exception is caught and return_error is called with the error message. """ async def mock_list_tools_with_error(server_name): raise Exception("Connection failed") async def mock_close(): pass mock_client = mocker.MagicMock() mock_client.list_tools = mock_list_tools_with_error mock_client.close = mock_close mocker.patch("ServiceNowMCP.Client", return_value=mock_client) mocker.patch.object(demisto, "params", return_value=VALID_PARAMS) mocker.patch.object(demisto, "args", return_value={}) mocker.patch.object(demisto, "command", return_value="list-tools") mock_return_error = mocker.patch("ServiceNowMCP.return_error") await main() mock_return_error.assert_called_once() error_call = mock_return_error.call_args[0][0] assert "Failed to execute list-tools command" in error_call assert "Connection failed" in error_call @pytest.mark.asyncio async def test_validation_failure_missing_instance(self, mocker: MockerFixture): """Given: The integration is configured without a ServiceNow instance. When: Main function processes any command. Then: validate_required_params raises ValueError and return_error is called. """ params = dict(VALID_PARAMS) params["instance_url"] = "" mocker.patch.object(demisto, "params", return_value=params) mocker.patch.object(demisto, "args", return_value={}) mocker.patch.object(demisto, "command", return_value="list-tools") mock_return_error = mocker.patch("ServiceNowMCP.return_error") await main() mock_return_error.assert_called_once() error_call = mock_return_error.call_args[0][0] assert "ServiceNow Instance" in error_call @pytest.mark.asyncio async def test_validation_failure_missing_oauth_credentials(self, mocker: MockerFixture): """Given: The integration is configured without OAuth credentials. When: Main function processes any command. Then: validate_required_params raises ValueError and return_error is called. """ params = dict(VALID_PARAMS) params["oauth_credentials"] = {"identifier": "", "password": ""} mocker.patch.object(demisto, "params", return_value=params) mocker.patch.object(demisto, "args", return_value={}) mocker.patch.object(demisto, "command", return_value="list-tools") mock_return_error = mocker.patch("ServiceNowMCP.return_error") await main() mock_return_error.assert_called_once() error_call = mock_return_error.call_args[0][0] assert "Client ID and Client Secret" in error_call