diff --git a/README.md b/README.md index 1ebd7d5212..d934feeb63 100644 --- a/README.md +++ b/README.md @@ -378,6 +378,11 @@ These are high-level frameworks that make it easier to build MCP servers or clie * **[codemirror-mcp](https://github.com/marimo-team/codemirror-mcp)** - CodeMirror extension that implements the Model Context Protocol (MCP) for resource mentions and prompt commands +## Tooling + +* **[mcp-autotest](https://github.com/strowk/mcp-autotest)** - Autotest MCP servers in a language-agnostic way by **[strowk](https://github.com/strowk)** + * check example tests for [SQLite MCP Server](https://github.com/modelcontextprotocol/servers/tree/main/src/sqlite/testdata) + ## 📚 Resources Additional resources on MCP. diff --git a/src/sqlite/.gitignore b/src/sqlite/.gitignore new file mode 100644 index 0000000000..eeeceb19d7 --- /dev/null +++ b/src/sqlite/.gitignore @@ -0,0 +1,2 @@ +# ignore database file created during tests +sqlite_mcp_server.db \ No newline at end of file diff --git a/src/sqlite/README.md b/src/sqlite/README.md index e194c6bf5b..8321aeff72 100644 --- a/src/sqlite/README.md +++ b/src/sqlite/README.md @@ -111,6 +111,22 @@ Docker: docker build -t mcp/sqlite . ``` +## Autotesting + +If you have installed `uv` and `npx` (Node.js) installed, you can run tests like this: + +```bash +npx mcp-autotest run testdata uv run mcp-server-sqlite +``` + +Repeating this run, would give you error `Database error: table t1 already exists`, because tests assume that database is cleared before running. +So if you want to repeat run: + +```bash +rm sqlite_mcp_server.db ; npx mcp-autotest run testdata uv run mcp-server-sqlite +``` + + ## License This MCP server is licensed under the MIT License. This means you are free to use, modify, and distribute the software, subject to the terms and conditions of the MIT License. For more details, please see the LICENSE file in the project repository. diff --git a/src/sqlite/testdata/01_lifecycle_test.yaml b/src/sqlite/testdata/01_lifecycle_test.yaml new file mode 100644 index 0000000000..188f217c7c --- /dev/null +++ b/src/sqlite/testdata/01_lifecycle_test.yaml @@ -0,0 +1,40 @@ +case: Initialize + +# Client requesting initialization +in_request_to_initialize: + { + "jsonrpc": "2.0", + "id": 1, + "method": "initialize", + "params": + { + "protocolVersion": "2024-11-05", + "capabilities": {}, + "clientInfo": { "name": "ExampleClient", "version": "1.0.0" }, + }, + } + +# Server responding to initialization +out: + { + "id": 1, + "jsonrpc": "2.0", + "result": + { + "capabilities": + { + "experimental": {}, + "prompts": { "listChanged": false }, + "resources": { "listChanged": false, "subscribe": false }, + "tools": { "listChanged": false }, + }, + "protocolVersion": "2024-11-05", + "serverInfo": { "name": "sqlite", "version": "0.1.0" }, + }, + } + +--- + +# Finish initialization +in_finished: + { "jsonrpc": "2.0", "method": "notifications/initialized", "params": {} } \ No newline at end of file diff --git a/src/sqlite/testdata/02_list_tools_test.yaml b/src/sqlite/testdata/02_list_tools_test.yaml new file mode 100644 index 0000000000..56d0c3b4e1 --- /dev/null +++ b/src/sqlite/testdata/02_list_tools_test.yaml @@ -0,0 +1,137 @@ +case: Initialize + +# Client requesting initialization +in_request_to_initialize: + { + "jsonrpc": "2.0", + "id": 1, + "method": "initialize", + "params": + { + "protocolVersion": "2024-11-05", + "capabilities": {}, + "clientInfo": { "name": "ExampleClient", "version": "1.0.0" }, + }, + } + +# Server responding to initialization, ignore this, since we are not testing it +out: {} + +--- + +case: Finish initialization + +# Finish initialization +in_finished: + { "jsonrpc": "2.0", "method": "notifications/initialized", "params": {} } + +--- + +case: List tools + +# Client requesting list of tools +in: { "jsonrpc": "2.0", "method": "tools/list", "params": {}, "id": 2 } + +# Server responding with list of tools +out: + { + "jsonrpc": "2.0", + "id": 2, + "result": + { + "tools": + [ + { + "name": "read_query", + "description": "Execute a SELECT query on the SQLite database", + "inputSchema": + { + "type": "object", + "properties": + { + "query": + { + "type": "string", + "description": "SELECT SQL query to execute", + }, + }, + "required": ["query"], + }, + }, + { + "name": "write_query", + "description": "Execute an INSERT, UPDATE, or DELETE query on the SQLite database", + "inputSchema": + { + "type": "object", + "properties": + { + "query": + { + "type": "string", + "description": "SQL query to execute", + }, + }, + "required": ["query"], + }, + }, + { + "name": "create_table", + "description": "Create a new table in the SQLite database", + "inputSchema": + { + "type": "object", + "properties": + { + "query": + { + "type": "string", + "description": "CREATE TABLE SQL statement", + }, + }, + "required": ["query"], + }, + }, + { + "name": "list_tables", + "description": "List all tables in the SQLite database", + "inputSchema": { "type": "object", "properties": {} }, + }, + { + "name": "describe_table", + "description": "Get the schema information for a specific table", + "inputSchema": + { + "type": "object", + "properties": + { + "table_name": + { + "type": "string", + "description": "Name of the table to describe", + }, + }, + "required": ["table_name"], + }, + }, + { + "name": "append_insight", + "description": "Add a business insight to the memo", + "inputSchema": + { + "type": "object", + "properties": + { + "insight": + { + "type": "string", + "description": "Business insight discovered from data analysis", + }, + }, + "required": ["insight"], + }, + }, + ], + }, + } + diff --git a/src/sqlite/testdata/03_call_list_tables_tool_test.yaml b/src/sqlite/testdata/03_call_list_tables_tool_test.yaml new file mode 100644 index 0000000000..e714302e22 --- /dev/null +++ b/src/sqlite/testdata/03_call_list_tables_tool_test.yaml @@ -0,0 +1,46 @@ +case: Initialize + +# Client requesting initialization +in_request_to_initialize: + { + "jsonrpc": "2.0", + "id": 1, + "method": "initialize", + "params": + { + "protocolVersion": "2024-11-05", + "capabilities": {}, + "clientInfo": { "name": "ExampleClient", "version": "1.0.0" }, + }, + } + +# Server responding to initialization, ignore this, since we are not testing it +out: {} + +--- + +case: Finish initialization + +# Finish initialization +in_finished: + { "jsonrpc": "2.0", "method": "notifications/initialized", "params": {} } + +--- + +case: Call list_table tool + +in: + { + "jsonrpc": "2.0", + "method": "tools/call", + "params": { "name": "list_tables", "args": {} }, + "id": 2, + } + +out: + { + "jsonrpc": "2.0", + "id": 2, + "result": + { "content": [{ "type": "text", "text": "[]" }], "isError": false }, + } diff --git a/src/sqlite/testdata/04_call_create_table_tool_test.yaml b/src/sqlite/testdata/04_call_create_table_tool_test.yaml new file mode 100644 index 0000000000..d15bc2688b --- /dev/null +++ b/src/sqlite/testdata/04_call_create_table_tool_test.yaml @@ -0,0 +1,96 @@ +case: Initialize + +# Client requesting initialization +in_request_to_initialize: + { + "jsonrpc": "2.0", + "id": 1, + "method": "initialize", + "params": + { + "protocolVersion": "2024-11-05", + "capabilities": {}, + "clientInfo": { "name": "ExampleClient", "version": "1.0.0" }, + }, + } + +# Server responding to initialization, ignore this, since we are not testing it +out: {} + +--- +case: Finish initialization + +# Finish initialization +in_finished: + { "jsonrpc": "2.0", "method": "notifications/initialized", "params": {} } + +--- +case: Fail calling create_table tool + +in: { "jsonrpc": "2.0", "method": "tools/call", "params": { + "name": "create_table", + "arguments": {}, + }, "id": 2 } # query argument is missing + +out: + { + "id": 2, + "jsonrpc": "2.0", + "result": + { + "content": [{ "text": "Error: Missing arguments", "type": "text" }], + "isError": false, + }, + } + +--- + +case: Success call create_table tool + +in: + { + "jsonrpc": "2.0", + "method": "tools/call", + "params": + { + "name": "create_table", + "arguments": { "query": "CREATE TABLE t1(x INT CHECK( x>3 ))" }, + }, + "id": 2, + } + +out: + { + "id": 2, + "jsonrpc": "2.0", + "result": + { + "content": [{ "text": "Table created successfully", "type": "text" }], + "isError": false, + }, + } + +--- + +# Now confirm that created table is visible for list_tables tool + +case: Call list_table tool to confirm table creation + +in: + { + "jsonrpc": "2.0", + "method": "tools/call", + "params": { "name": "list_tables", "args": {} }, + "id": 2, + } + +out: + { + "id": 2, + "jsonrpc": "2.0", + "result": + { + "content": [{ "text": "[{'name': 't1'}]", "type": "text" }], + "isError": false, + }, + }