Skip to main content
Glama
TCSoftInc

TestCollab MCP Server

by TCSoftInc

get_project_context

Retrieve essential project metadata to resolve human-readable names to numeric IDs and understand project structure before using other TestCollab tools.

Instructions

Get project context including project name, description, application type, suite tree, tags, test_case_custom_fields, test_plan_custom_fields, requirements, test plan folders, releases, and project users. Returns the metadata needed to resolve human-readable names (e.g. suite titles, tag names, folder titles, release titles, user names) to numeric IDs used by other tools. Also returns the project description and app_type (web_app, mobile_app, api, desktop_app, other) which should inform the style of test steps you generate.

IMPORTANT: Call this tool at the start of every conversation before using any other TestCollab tool. This avoids errors from unresolved suite names, tag names, or custom field references.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
project_idNoProject ID (optional — uses default project if omitted)

Implementation Reference

  • The handler function `handleProjectContext` in `src/resources/project-context.ts` implements the core logic for the "get_project_context" tool. It fetches project metadata (suites, tags, custom fields, etc.) and returns them as a JSON response.
    export async function handleProjectContext(
      projectId: number
    ): Promise<{ contents: Array<{ uri: string; mimeType: string; text: string }> }> {
      const now = Date.now();
      const cacheKey = cacheTtlMs > 0 ? getCacheKey(projectId) : undefined;
    
      if (cacheKey) {
        const cached = contextCache.get(cacheKey);
        if (cached && cached.expiresAt > now) {
          if (!hasEntityScopedCustomFields(cached.payload)) {
            console.log(
              `${logPrefix} Project context cache invalidated for project ${projectId} due to legacy payload shape`
            );
            contextCache.delete(cacheKey);
          } else {
            console.log(
              `${logPrefix} Project context cache hit for project ${projectId}`
            );
            return {
              contents: [
                {
                  uri: `testcollab://project/${projectId}/context`,
                  mimeType: "application/json",
                  text: JSON.stringify(cached.payload, null, 2),
                },
              ],
            };
          }
        }
      }
    
      const startTime = Date.now();
      console.log(`${logPrefix} Building project context for ${projectId}`);
    
      try {
        const client = getApiClient();
    
        let companyId: number | undefined;
        let projectName: string | undefined;
        let projectDescription: string | undefined;
        let appType: string | undefined;
        let appTypeOther: string | undefined;
        try {
          console.log(
            `${apiLogPrefix} GET /projects/{id} params: ${JSON.stringify({
              projectId,
            })}`
          );
          const project = await client.getProject(projectId);
          companyId = getCompanyIdFromProject(project);
          projectName = normalizeString(getField<string>(project, "name"));
          projectDescription = normalizeString(getField<string>(project, "description"));
          appType = normalizeString(getField<string>(project, "app_type"));
          appTypeOther = normalizeString(getField<string>(project, "app_type_other"));
        } catch (error) {
          console.warn(
            `${logPrefix} Failed to fetch project ${projectId} for company ID`,
            error
          );
        }
    
        console.log(
          `${apiLogPrefix} GET /suites params: ${JSON.stringify({
            projectId,
          })}`
        );
        console.log(
          `${apiLogPrefix} GET /tags params: ${JSON.stringify({
            projectId,
            // companyId,
          })}`
        );
        console.log(
          `${apiLogPrefix} GET /requirements params: ${JSON.stringify({
            projectId,
            companyId,
          })}`
        );
        console.log(
          `${apiLogPrefix} GET /customfields params: ${JSON.stringify({
            projectId,
            companyId,
            entity: "TestCase",
          })}`
        );
        console.log(
          `${apiLogPrefix} GET /customfields params: ${JSON.stringify({
            projectId,
            companyId,
            entity: "TestPlan",
          })}`
        );
        console.log(
          `${apiLogPrefix} GET /testplanfolders params: ${JSON.stringify({
            projectId,
          })}`
        );
        console.log(
          `${apiLogPrefix} GET /releases params: ${JSON.stringify({
            projectId,
          })}`
        );
        console.log(
          `${apiLogPrefix} GET /projectusers params: ${JSON.stringify({
            projectId,
          })}`
        );
    
        const [
          suitesList,
          tagsList,
          requirementsList,
          testCaseCustomFieldsList,
          testPlanCustomFieldsList,
          testPlanFoldersList,
          releasesList,
          projectUsersList,
        ] = await Promise.all([
          client.listSuites(projectId).catch((error) => {
            console.warn(
              `${logPrefix} Failed to fetch suites for ${projectId}`,
              error
            );
            return [];
          }),
          client.listTags(projectId).catch((error) => {
            console.warn(
              `${logPrefix} Failed to fetch tags for ${projectId}`,
              error
            );
            return [];
          }),
          client.listRequirements(projectId).catch((error) => {
            console.warn(
              `${logPrefix} Failed to fetch requirements for ${projectId}`,
              error
            );
            return [];
          }),
          companyId
            ? client
                .listProjectCustomFields(projectId, companyId, "TestCase")
                .catch((error) => {
                  console.warn(
                    `${logPrefix} Failed to fetch TestCase custom fields for ${projectId}`,
                    error
                  );
                  return [];
                })
            : Promise.resolve([]),
          companyId
            ? client
                .listProjectCustomFields(projectId, companyId, "TestPlan")
                .catch((error) => {
                  console.warn(
                    `${logPrefix} Failed to fetch TestPlan custom fields for ${projectId}`,
                    error
                  );
                  return [];
                })
            : Promise.resolve([]),
          client.listTestPlanFolders(projectId).catch((error) => {
            console.warn(
              `${logPrefix} Failed to fetch test plan folders for ${projectId}`,
              error
            );
            return [];
          }),
          client.listReleases(projectId).catch((error) => {
            console.warn(`${logPrefix} Failed to fetch releases for ${projectId}`, error);
            return [];
          }),
          client.listProjectUsers(projectId).catch((error) => {
            console.warn(
              `${logPrefix} Failed to fetch project users for ${projectId}`,
              error
            );
            return [];
          }),
        ]);
    
        const suites = buildSuiteTree(Array.isArray(suitesList) ? suitesList : []);
        const tags = mapTags(Array.isArray(tagsList) ? tagsList : []);
        const requirements = mapRequirements(
          Array.isArray(requirementsList) ? requirementsList : []
        );
        const test_case_custom_fields = mapCustomFields(
          Array.isArray(testCaseCustomFieldsList) ? testCaseCustomFieldsList : [],
          "TestCase"
        );
        const test_plan_custom_fields = mapCustomFields(
          Array.isArray(testPlanCustomFieldsList) ? testPlanCustomFieldsList : [],
          "TestPlan"
        );
        // Backward-compatibility alias retained for existing consumers.
        const custom_fields = test_case_custom_fields;
        const test_plan_folders = mapTestPlanFolders(
          Array.isArray(testPlanFoldersList) ? testPlanFoldersList : []
        );
        const releases = mapReleases(Array.isArray(releasesList) ? releasesList : []);
        const users = mapProjectUsers(
          Array.isArray(projectUsersList) ? projectUsersList : []
        );
    
        const payload: ProjectContextPayload = {
          project_id: projectId,
          ...(projectName ? { project_name: projectName } : {}),
          ...(projectDescription ? { project_description: projectDescription } : {}),
          ...(appType ? { app_type: appType } : {}),
          ...(appType === "other" && appTypeOther ? { app_type_other: appTypeOther } : {}),
          suites,
          tags,
          test_case_custom_fields,
          test_plan_custom_fields,
          custom_fields,
          requirements,
          test_plan_folders,
          releases,
          users,
        };
    
        if (cacheKey) {
          contextCache.set(cacheKey, {
            expiresAt: now + cacheTtlMs,
            payload,
          });
        }
    
        const durationMs = Date.now() - startTime;
        console.log(
          `${logPrefix} Project context ready for ${projectId} in ${durationMs}ms (suites: ${suites.length}, tags: ${tags.length}, test_case_custom_fields: ${test_case_custom_fields.length}, test_plan_custom_fields: ${test_plan_custom_fields.length}, requirements: ${requirements.length}, test_plan_folders: ${test_plan_folders.length}, releases: ${releases.length}, users: ${users.length})`
        );
    
        return {
          contents: [
            {
              uri: `testcollab://project/${projectId}/context`,
              mimeType: "application/json",
              text: JSON.stringify(payload, null, 2),
            },
          ],
        };
      } catch (error) {
        console.error(
          `${logPrefix} Failed to build project context for ${projectId}:`,
          error
        );
    
        return {
          contents: [
            {
              uri: `testcollab://project/${projectId}/context`,
              mimeType: "application/json",
              text: JSON.stringify(
                {
                  error: "PROJECT_CONTEXT_FETCH_FAILED",
                  message:
                    error instanceof Error ? error.message : "Unknown error",
                },
                null,
                2
              ),
            },
          ],
        };
      }
    }
  • The registration for the "get_project_context" tool occurs in `src/tools/index.ts`. It invokes `handleProjectContext` defined in `src/resources/project-context.ts`.
      server.tool(
        "get_project_context",
        `Get project context including project name, description, application type, suite tree, tags, test_case_custom_fields, test_plan_custom_fields, requirements, test plan folders, releases, and project users.
    Returns the metadata needed to resolve human-readable names (e.g. suite titles, tag names, folder titles, release titles, user names) to numeric IDs used by other tools.
    Also returns the project description and app_type (web_app, mobile_app, api, desktop_app, other) which should inform the style of test steps you generate.
    
    IMPORTANT: Call this tool at the start of every conversation before using any other TestCollab tool.
    This avoids errors from unresolved suite names, tag names, or custom field references.`,
        {
          project_id: z.number().optional().describe("Project ID (optional — uses default project if omitted)"),
        },
        async (args) => {
          const projectId = resolveProjectId(args.project_id);
          if (!projectId) {
            return {
              content: [
                {
                  type: "text" as const,
                  text: JSON.stringify({ error: "No project_id provided and no default project configured." }),
                },
              ],
            };
          }
    
          const result = await handleProjectContext(projectId);
          const text = result.contents[0]?.text ?? JSON.stringify({ error: "No context returned" });
    
          return {
            content: [
              {
                type: "text" as const,
                text,
              },
            ],
          };
        }
      );
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Without annotations, the description carries the full burden and successfully explains the tool's behavioral role as a name-to-ID resolution service and its impact on test step generation style. However, it omits performance characteristics, caching behavior, or error handling details for invalid project IDs.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Information-dense with no wasted words: opening sentence lists return values, second explains ID resolution purpose, third covers test generation implications, and the IMPORTANT block delivers critical workflow guidance. Efficiently front-loaded with the 'what' before the 'when'.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Despite lacking an output schema and annotations, the description comprehensively compensates by enumerating all returned metadata fields (custom fields, releases, users, etc.) and explaining their consumption pattern, providing sufficient context for an AI to utilize the tool effectively.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

With 100% schema description coverage for the single optional project_id parameter, the baseline is appropriately met. The description implies the parameter through 'project context' references but does not add syntax details or usage examples beyond the schema's explanation of the default project fallback.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool retrieves project metadata (name, description, app_type, suite tree, etc.) and distinguishes itself from sibling CRUD tools by emphasizing its role in resolving 'human-readable names to numeric IDs' for other operations.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly mandates 'Call this tool at the start of every conversation before using any other TestCollab tool' and explains the consequence of not doing so ('avoids errors from unresolved suite names'), providing clear temporal and prerequisite guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/TCSoftInc/testcollab-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server