From 9a79fc8e4c29e83007e17a9eb5f4d71c67483f86 Mon Sep 17 00:00:00 2001 From: Andreas Hilbig Date: Sat, 31 Jan 2026 03:31:40 +0100 Subject: [PATCH] feat: add MCP tools, refined recipe steps for schema extension verification and update Docker requirements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add mcp/operations/importHosts.graphql for flexible, name-based host provisioning. - Split schema verification operations into createVerificationHost.graphql and verifySchemaExtension.graphql to support Apollo MCP server requirements. - Improve mcp/operations/createHost.graphql with better type mapping and error message retrieval. - Fix syntax error in importHosts.graphql by replacing unsupported triple-quote docstrings with standard comments. - Add src/test/mcp_operations_validation.test.ts to automatically validate MCP operations against the GraphQL schema. - Update docs/howtos/cookbook.md with 🤖 AI/MCP guidance and refined recipe steps for schema extension verification. - Update README.md, docs/howtos/mcp.md, and .junie/guidelines.md to: - Add Docker (v27+) and Docker Compose (v2.29+) version requirements to the Tech Stack. - Enforce the use of docker compose (without hyphen) for all commands in the Environment guidelines. - Replace legacy docker-compose references across all documentation with the new command format. --- .junie/guidelines.md | 4 +-- README.md | 3 +- docs/howtos/cookbook.md | 25 ++++++++++++----- docs/howtos/mcp.md | 2 +- mcp/operations/createHost.graphql | 6 ++-- mcp/operations/createVerificationHost.graphql | 13 +++++++++ mcp/operations/importHosts.graphql | 11 ++++++++ mcp/operations/verifySchemaExtension.graphql | 13 +++++++++ src/test/mcp_operations_validation.test.ts | 28 +++++++++++++++++++ 9 files changed, 92 insertions(+), 13 deletions(-) create mode 100644 mcp/operations/createVerificationHost.graphql create mode 100644 mcp/operations/importHosts.graphql create mode 100644 mcp/operations/verifySchemaExtension.graphql create mode 100644 src/test/mcp_operations_validation.test.ts diff --git a/.junie/guidelines.md b/.junie/guidelines.md index a4af39f..b2940a4 100644 --- a/.junie/guidelines.md +++ b/.junie/guidelines.md @@ -7,7 +7,7 @@ The [Roadmap](../roadmap.md) is to be considered as outlook giving constraints o ### Environment - **Operating System**: Windows with WSL + Ubuntu installed. -- **Commands**: Always execute Linux commands (e.g. use `ls` instead of `dir`). +- **Commands**: Always execute Linux commands (e.g. use `ls` instead of `dir`) and use `docker compose` (without hyphen) instead of `docker-compose`. ## Tech Stack @@ -15,7 +15,7 @@ The [Roadmap](../roadmap.md) is to be considered as outlook giving constraints o - **Language**: TypeScript (ESM) - **API**: GraphQL (Apollo Server 4) - **Testing**: Jest -- **Deployment**: Docker +- **Deployment**: Docker (v27+) and Docker Compose (v2.29+) ## Project Structure - `src/api/`: GraphQL server configuration, schema loading, and root resolvers (see `createResolvers` in `resolvers.ts`). diff --git a/README.md b/README.md index f17a842..1f2973d 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ The Zabbix GraphQL API acts as a wrapper and enhancer for the native Zabbix JSON - *Reference*: `codegen.ts`, `src/schema/generated/graphql.ts`, `tsconfig.json`, `package.json` (devDependencies for GraphQL Codegen) - **AI Agent & MCP Enablement**: Native support for Model Context Protocol (MCP) and AI-driven automation. GraphQL's strongly-typed, introspectable nature provides a superior interface for AI agents compared to traditional REST APIs. - - *Reference*: `docs/howtos/mcp.md`, `.ai/mcp/mcp.json` (Sample Config), `mcp-config.yaml`, `docker-compose.yml` (MCP service) + - *Reference*: `docs/howtos/mcp.md`, `.ai/mcp/mcp.json` (Sample Config), `mcp-config.yaml`, `docker compose` (MCP service) > **Planned features**: For an overview of achieved milestones and planned enhancements have a look at the [**Roadmap**](./roadmap.md). @@ -50,6 +50,7 @@ See the [How-To Overview](./docs/howtos/README.md) for a complete list of docume Before you begin, ensure you have met the following requirements: - **Node.js**: Version 24 (LTS) or higher recommended. +- **Docker**: Version 27 or higher and **Docker Compose** v2.29 or higher (use `docker compose` instead of `docker-compose`). - **Zabbix**: A running Zabbix instance (compatible with Zabbix 6.0+) with API access. - **Zabbix Super Admin Token** (for full functionality / privilege escalation). - **Zabbix User Access** (groups and roles depending on your use case). diff --git a/docs/howtos/cookbook.md b/docs/howtos/cookbook.md index e792eb7..7dc3877 100644 --- a/docs/howtos/cookbook.md +++ b/docs/howtos/cookbook.md @@ -48,6 +48,7 @@ type DistanceTrackerValues { timeFrom: Time timeUntil: Time count: Int + # The distances are modelled using a type which is already defined in location_tracker_commons.graphql distances: [SensorDistanceValue!] } ``` @@ -57,7 +58,7 @@ type DistanceTrackerValues { ### ⚙️ Step 2: Configure Environment Variables Add the new schema and resolver to your `.env` file: ```env -ADDITIONAL_SCHEMAS=./schema/extensions/distance_tracker.graphql +ADDITIONAL_SCHEMAS=./schema/extensions/distance_tracker.graphql,./schema/extensions/location_tracker_commons.graphql ADDITIONAL_RESOLVERS=DistanceTrackerDevice ``` Restart the API server. @@ -66,12 +67,12 @@ Restart the API server. #### Method A: Manual Creation in Zabbix If you prefer to configure Zabbix manually: -1. **Create Template**: Create a template named `Distance Tracker Device`. -2. **Create Items**: Add items with keys that match the GraphQL fields: +1. **Create Template**: Create a template named `DISTANCE_TRACKER`. +2. **Create Items**: Add items with keys that match the GraphQL fields (using hierarchical mapping): * `state.current.timeFrom` * `state.current.timeUntil` * `state.current.count` - * `state.current.json_distances` (maps to `distances` array) + * `state.current.json_distances` (maps to `distances` array via `json_` prefix) 3. **Add Tag**: Add a host tag to the template with name `deviceType` and value `DistanceTrackerDevice`. #### Method B: Automated Import @@ -82,7 +83,8 @@ Execute the `importTemplates` mutation to create the template and items automati Verify that the new type is available and correctly mapped by creating a test host and querying it. #### 1. Create a Test Host -Use the `importHosts` mutation to create a host and explicitly set its `deviceType` to `DistanceTrackerDevice`. +Use the `importHosts` mutation (or `createHost` if IDs are already known) to create a host and explicitly set its `deviceType` to `DistanceTrackerDevice`. + ```graphql mutation CreateTestDistanceTracker($host: String!, $groupNames: [String!]!) { importHosts(hosts: [{ @@ -97,7 +99,8 @@ mutation CreateTestDistanceTracker($host: String!, $groupNames: [String!]!) { ``` #### 2. Query the Device -Query the newly created device. Use the `tag_deviceType: ["DistanceTrackerDevice"]` argument to ensure you only retrieve devices of this specific type and prevent showing all devices. +Query the newly created device. Use the `tag_deviceType: ["DistanceTrackerDevice"]` argument to ensure you only retrieve devices of this specific type and prevent showing all devices of any type. + ```graphql query VerifyNewDeviceType { # 1. Check if the type exists in the schema @@ -126,6 +129,11 @@ query VerifyNewDeviceType { > **Reference**: See how items map to fields in the [Zabbix to GraphQL Mapping](../../README.md#zabbix-to-graphql-mapping). +### 🤖 AI/MCP +AI agents can use the generalized `verifySchemaExtension.graphql` operations to automate this step: +- **CreateVerificationHost**: Automatically imports a test host with the correct `deviceType`. +- **VerifySchemaExtension**: Queries the newly created device to verify it is correctly mapped to the new type. + --- ## 🍳 Recipe: Provisioning a New Host @@ -140,6 +148,9 @@ Define the host name, groups, and templates to link. ### 🚀 Step 2: Execute `createHost` Mutation For more details on the input fields, see the [Reference: createHost](../../schema/mutations.graphql). +### 🤖 AI/MCP +AI agents should prefer using the `importHosts` MCP tool for provisioning as it allows using names for host groups instead of IDs. + ```graphql mutation CreateNewHost($host: String!, $groups: [Int!]!, $templates: [Int!]!) { createHost(host: $host, hostgroupids: $groups, templateids: $templates) { @@ -303,7 +314,7 @@ Confirm that the agent can successfully interact with the API: - The agent should use the `apiVersion` query and respond with the version number. ### 💡 Alternative: Using Pre-running MCP Server -If you already have the MCP server running locally (e.g. via `docker-compose.yml`), you can use a simpler configuration. +If you already have the MCP server running locally (e.g. via `docker compose`), you can use a simpler configuration. - **Sample Configuration**: See [.ai/mcp/mcp.json](../../.ai/mcp/mcp.json) for a sample that connects to a running MCP server via HTTP. - **Usage**: Use this `url`-based configuration in your Claude Desktop or IDE settings instead of the `command`-based setup if you prefer to manage the MCP server lifecycle separately. diff --git a/docs/howtos/mcp.md b/docs/howtos/mcp.md index 3d2794b..24382b8 100644 --- a/docs/howtos/mcp.md +++ b/docs/howtos/mcp.md @@ -20,7 +20,7 @@ You can start both the Zabbix GraphQL API and the Apollo MCP Server using Docker ``` - **Start Services**: ```bash - docker-compose up -d + docker compose up -d ``` This will: diff --git a/mcp/operations/createHost.graphql b/mcp/operations/createHost.graphql index c078a0f..a01c372 100644 --- a/mcp/operations/createHost.graphql +++ b/mcp/operations/createHost.graphql @@ -1,6 +1,8 @@ -mutation CreateHost($host: String!, $hostgroupids: [Int!]!, $templateids: [Int!]) { +mutation CreateHost($host: String!, $hostgroupids: [Int!]!, $templateids: [Int!]!) { createHost(host: $host, hostgroupids: $hostgroupids, templateids: $templateids) { hostids - error + error { + message + } } } diff --git a/mcp/operations/createVerificationHost.graphql b/mcp/operations/createVerificationHost.graphql new file mode 100644 index 0000000..e583b91 --- /dev/null +++ b/mcp/operations/createVerificationHost.graphql @@ -0,0 +1,13 @@ +mutation CreateVerificationHost($deviceKey: String!, $deviceType: String!, $groupNames: [String!]!) { + importHosts(hosts: [{ + deviceKey: $deviceKey, + deviceType: $deviceType, + groupNames: $groupNames + }]) { + hostid + message + error { + message + } + } +} diff --git a/mcp/operations/importHosts.graphql b/mcp/operations/importHosts.graphql new file mode 100644 index 0000000..4dba3df --- /dev/null +++ b/mcp/operations/importHosts.graphql @@ -0,0 +1,11 @@ +# Import multiple hosts/devices into Zabbix. +# This is a powerful tool for bulk provisioning of hosts using their names and types. +mutation ImportHosts($hosts: [CreateHost!]!) { + importHosts(hosts: $hosts) { + hostid + message + error { + message + } + } +} diff --git a/mcp/operations/verifySchemaExtension.graphql b/mcp/operations/verifySchemaExtension.graphql new file mode 100644 index 0000000..a2d1005 --- /dev/null +++ b/mcp/operations/verifySchemaExtension.graphql @@ -0,0 +1,13 @@ +query VerifySchemaExtension($typeName: String!, $deviceKey: String) { + allDevices(tag_deviceType: [$typeName], filter_host: $deviceKey) { + hostid + host + name + deviceType + state { + operational { + timestamp + } + } + } +} diff --git a/src/test/mcp_operations_validation.test.ts b/src/test/mcp_operations_validation.test.ts new file mode 100644 index 0000000..0b1fc59 --- /dev/null +++ b/src/test/mcp_operations_validation.test.ts @@ -0,0 +1,28 @@ +import {schema_loader} from "../api/schema.js"; +import {readdirSync, readFileSync} from "node:fs"; +import {parse, validate} from "graphql"; +import path from "node:path"; + +describe("MCP Operations Validation", () => { + let schema: any; + + beforeAll(async () => { + schema = await schema_loader(); + }); + + const operationsDir = "./mcp/operations"; + const files = readdirSync(operationsDir).filter(f => f.endsWith(".graphql")); + + test.each(files)("Operation file %s should be valid against schema", (file) => { + const filePath = path.join(operationsDir, file); + const content = readFileSync(filePath, "utf-8"); + const document = parse(content); + const errors = validate(schema, document); + + if (errors.length > 0) { + console.error(`Validation errors in ${file}:`, errors.map(e => e.message)); + } + + expect(errors).toHaveLength(0); + }); +});