Skip to main content
Glama
adama007

testlink-mcp-server

by adama007

testlink-mcp-server

MCP (Model Context Protocol) server exposing the TestLink XML-RPC API as typed tools for LLM agents (Claude Desktop, Claude Code, etc.).

Stack

Concern

Choice

Runtime

Node.js ≥ 18, TypeScript, ESM

MCP transport

@modelcontextprotocol/sdk over stdio

TestLink transport

xmlrpc client, devKey injected on every call

Schema validation

zod (per-tool input schemas)

Tests

vitest, XML-RPC client fully mocked

Project structure

testlink-mcp-server/
├── src/
│   ├── index.ts            # MCP server entry point (registers all tool groups, stdio transport)
│   ├── client.ts            # TestLinkClient: XML-RPC wrapper, retry/timeout, error normalization
│   ├── types.ts              # TestLinkClientConfig, TestLinkError
│   ├── mcp-helpers.ts         # toToolResult(): uniform success/error → MCP content mapping
│   ├── xmlrpc.d.ts            # minimal ambient types for the untyped `xmlrpc` package
│   └── tools/
│       ├── projects.ts        # list_projects, create_project
│       ├── testsuites.ts       # list/create/update/delete_test_suite
│       ├── testcases.ts         # read/create/update/delete_test_case, list_test_cases_in_suite
│       ├── customfields.ts       # get/update_custom_field_value, list_custom_fields_for_project
│       ├── requirements.ts        # list/create/get_requirement, create_requirement_specification, assign_requirements
│       ├── testplans.ts            # list/create/delete_test_plan, add_test_case_to_test_plan, get_test_cases_for_test_plan
│       ├── builds.ts                # create/list/close_build
│       ├── executions.ts             # create/read_test_execution
│       └── diagnostics.ts             # list_available_api_methods (system.listMethods introspection)
├── test/client.test.ts
├── package.json
├── tsconfig.json
├── vitest.config.ts
└── .env.example

Installation

npm install
npm run build

Configuration

Copy .env.example to .env and fill in your instance's details:

cp .env.example .env

Variable

Required

Description

TESTLINK_URL

yes

Full XML-RPC endpoint, e.g. http://host/testlink/lib/api/xmlrpc/v1/xmlrpc.php

TESTLINK_API_KEY

yes

Personal API key from TestLink → My Settings → API interface

TESTLINK_TIMEOUT_MS

no

Per-call network timeout (default 15000)

TESTLINK_RETRIES

no

Retry attempts on transient network errors only (default 2)

TESTLINK_RETRY_DELAY_MS

no

Base backoff delay, doubled per attempt (default 500)

TESTLINK_API_KEY is a credential — keep it out of source control (.env is already git-ignored) and out of shared MCP configs where other users could read it.

Running standalone

npm run dev     # ts-node style, via tsx
# or
npm run build && npm start

The server speaks MCP over stdio — it has no meaningful output when run directly in a terminal; it's meant to be launched by an MCP host.

Claude Desktop configuration

Add a block like this to claude_desktop_config.json (Windows: %APPDATA%\Claude\claude_desktop_config.json; macOS: ~/Library/Application Support/Claude/claude_desktop_config.json):

{
  "mcpServers": {
    "testlink": {
      "command": "node",
      "args": ["C:/data/mcp-server/testlink/dist/index.js"],
      "env": {
        "TESTLINK_URL": "http://your-testlink-host/testlink/lib/api/xmlrpc/v1/xmlrpc.php",
        "TESTLINK_API_KEY": "your-devkey-here"
      }
    }
  }
}

Restart Claude Desktop after editing the config. Run npm run build first — the config points at the compiled dist/index.js, not the TypeScript source.

Tools exposed

Tool

TestLink API method

list_projects

tl.getProjects

create_project

tl.createTestProject

list_test_suites

tl.getFirstLevelTestSuitesForTestProject / tl.getTestSuitesForTestSuite

create_test_suite

tl.createTestSuite

update_test_suite

tl.updateTestSuite ⚠️

delete_test_suite

tl.deleteTestSuite ⚠️

read_test_case

tl.getTestCase

create_test_case

tl.createTestCase

update_test_case

tl.updateTestCase

delete_test_case

tl.deleteTestCase ⚠️

list_test_cases_in_suite

tl.getTestCasesForTestSuite

get_custom_field_value

tl.getTestCaseCustomFieldDesignValue

update_custom_field_value

tl.updateTestCase (customfields param)

list_custom_fields_for_project

best-effort probe, see caveat below ⚠️

list_requirements

tl.getRequirements

create_requirement_specification

tl.createRequirementSpecification

create_requirement

tl.createRequirement

assign_requirements

tl.assignRequirements

get_requirement

client-side filter over tl.getRequirements ⚠️

list_test_plans

tl.getProjectTestPlans

create_test_plan

tl.createTestPlan

add_test_case_to_test_plan

tl.addTestCaseToTestPlan

get_test_cases_for_test_plan

tl.getTestCasesForTestPlan

delete_test_plan

tl.deleteTestPlan ⚠️

create_build

tl.createBuild

list_builds

tl.getBuildsForTestPlan

close_build

tl.closeBuild ⚠️

create_test_execution

tl.reportTCResult

read_test_execution

tl.getLastExecutionResult

list_available_api_methods

system.listMethods (introspection)

Known API limitations (⚠️ above)

TestLink's official XML-RPC surface is oriented around CI reporting (create projects/suites/cases/plans/builds, report results) and historically has no stock endpoints for update/delete on test suites, test cases, or test plans, nor for closing a build or listing custom field definitions. This server implements those tools anyway, calling the conventionally-named method (tl.updateTestSuite, tl.deleteTestSuite, tl.deleteTestCase, tl.deleteTestPlan, tl.closeBuild), because some installations add them via patches or newer releases.

Before relying on any ⚠️-marked tool, call list_available_api_methods (wraps the standard XML-RPC system.listMethods introspection call) to confirm your TestLink server actually exposes it. If it doesn't, the call fails as a clear XML-RPC "unknown method" fault surfaced through this server's normal error path — not a silent no-op.

  • get_requirement: TestLink has no single-requirement lookup; this fetches the full project requirement list via tl.getRequirements and filters client-side by requirement id or doc id. Fine for small/medium requirement sets; not paginated.

  • list_custom_fields_for_project: TestLink's API can only read a named custom field's value on a specific test case — it cannot enumerate a project's custom field definitions. This tool takes a representative test case plus a list of candidate field names (visible in TestLink Admin → Custom Fields) and probes each one, returning only the fields that resolved to a non-empty value. It cannot discover field names you don't already supply.

Error handling

TestLink signals failures two ways, both normalized into a single TestLinkError:

  1. XML-RPC faults — transport-level errors (bad method, malformed params).

  2. In-band error payloads — TestLink often returns HTTP 200 with a body like [{code: 200, message: "..."}], or {status: false, message: "..."} for write operations. TestLinkClient inspects every response and throws TestLinkError for both cases, so callers never have to special-case a "successful" HTTP response that's actually a failure.

Every tool handler wraps its call in toToolResult(), so failures come back as an MCP tool result with isError: true and a JSON body {error: true, method, code, message} — never an uncaught exception that kills the server process.

Network-level errors (ECONNRESET, ETIMEDOUT, ECONNREFUSED, ENOTFOUND, EAI_AGAIN, EPIPE) are retried with exponential backoff up to TESTLINK_RETRIES times; logical TestLink API errors are never retried, since retrying a duplicate-name or bad-devKey error just repeats the same failure.

Tests

npm test

test/client.test.ts mocks the xmlrpc module entirely and covers: devKey injection, both TestLink in-band error shapes, the retry path on transient network errors, no-retry on logical errors, system.* calls skipping devKey, and the client-side requirement lookup's not-found path.

Security notes

  • TESTLINK_API_KEY grants full API access under whichever TestLink user issued it — treat it like any other credential.

  • If TESTLINK_URL is https://, the client uses xmlrpc.createSecureClient; prefer HTTPS whenever your TestLink instance supports it, since the XML-RPC API otherwise sends the devKey in plaintext.

  • All tool inputs are validated against explicit zod schemas before reaching the TestLink client — malformed input is rejected by the MCP layer rather than forwarded to the XML-RPC endpoint.