common-qa-mcp 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +96 -0
  3. package/dist/adapters/factory.d.ts +15 -0
  4. package/dist/adapters/factory.d.ts.map +1 -0
  5. package/dist/adapters/factory.js +18 -0
  6. package/dist/adapters/factory.js.map +1 -0
  7. package/dist/adapters/remoteMcp/adapter.d.ts +92 -0
  8. package/dist/adapters/remoteMcp/adapter.d.ts.map +1 -0
  9. package/dist/adapters/remoteMcp/adapter.js +219 -0
  10. package/dist/adapters/remoteMcp/adapter.js.map +1 -0
  11. package/dist/adapters/types.d.ts +77 -0
  12. package/dist/adapters/types.d.ts.map +1 -0
  13. package/dist/adapters/types.js +2 -0
  14. package/dist/adapters/types.js.map +1 -0
  15. package/dist/adapters/walnut/actionTypes.d.ts +16 -0
  16. package/dist/adapters/walnut/actionTypes.d.ts.map +1 -0
  17. package/dist/adapters/walnut/actionTypes.js +141 -0
  18. package/dist/adapters/walnut/actionTypes.js.map +1 -0
  19. package/dist/adapters/walnut/mapper.d.ts +34 -0
  20. package/dist/adapters/walnut/mapper.d.ts.map +1 -0
  21. package/dist/adapters/walnut/mapper.js +228 -0
  22. package/dist/adapters/walnut/mapper.js.map +1 -0
  23. package/dist/cli.d.ts +3 -0
  24. package/dist/cli.d.ts.map +1 -0
  25. package/dist/cli.js +36 -0
  26. package/dist/cli.js.map +1 -0
  27. package/dist/config.d.ts +81 -0
  28. package/dist/config.d.ts.map +1 -0
  29. package/dist/config.js +211 -0
  30. package/dist/config.js.map +1 -0
  31. package/dist/connection.d.ts +101 -0
  32. package/dist/connection.d.ts.map +1 -0
  33. package/dist/connection.js +190 -0
  34. package/dist/connection.js.map +1 -0
  35. package/dist/domain/types.d.ts +279 -0
  36. package/dist/domain/types.d.ts.map +1 -0
  37. package/dist/domain/types.js +2 -0
  38. package/dist/domain/types.js.map +1 -0
  39. package/dist/domain/validate.d.ts +3 -0
  40. package/dist/domain/validate.d.ts.map +1 -0
  41. package/dist/domain/validate.js +29 -0
  42. package/dist/domain/validate.js.map +1 -0
  43. package/dist/drafts/store.d.ts +62 -0
  44. package/dist/drafts/store.d.ts.map +1 -0
  45. package/dist/drafts/store.js +119 -0
  46. package/dist/drafts/store.js.map +1 -0
  47. package/dist/httpMain.d.ts +3 -0
  48. package/dist/httpMain.d.ts.map +1 -0
  49. package/dist/httpMain.js +22 -0
  50. package/dist/httpMain.js.map +1 -0
  51. package/dist/index.d.ts +3 -0
  52. package/dist/index.d.ts.map +1 -0
  53. package/dist/index.js +46 -0
  54. package/dist/index.js.map +1 -0
  55. package/dist/initAgents.d.ts +2 -0
  56. package/dist/initAgents.d.ts.map +1 -0
  57. package/dist/initAgents.js +178 -0
  58. package/dist/initAgents.js.map +1 -0
  59. package/dist/log.d.ts +14 -0
  60. package/dist/log.d.ts.map +1 -0
  61. package/dist/log.js +14 -0
  62. package/dist/log.js.map +1 -0
  63. package/dist/mcpResult.d.ts +20 -0
  64. package/dist/mcpResult.d.ts.map +1 -0
  65. package/dist/mcpResult.js +8 -0
  66. package/dist/mcpResult.js.map +1 -0
  67. package/dist/mount.d.ts +12 -0
  68. package/dist/mount.d.ts.map +1 -0
  69. package/dist/mount.js +126 -0
  70. package/dist/mount.js.map +1 -0
  71. package/dist/projectStore.d.ts +5 -0
  72. package/dist/projectStore.d.ts.map +1 -0
  73. package/dist/projectStore.js +60 -0
  74. package/dist/projectStore.js.map +1 -0
  75. package/dist/requestConnection.d.ts +20 -0
  76. package/dist/requestConnection.d.ts.map +1 -0
  77. package/dist/requestConnection.js +44 -0
  78. package/dist/requestConnection.js.map +1 -0
  79. package/dist/serve.d.ts +10 -0
  80. package/dist/serve.d.ts.map +1 -0
  81. package/dist/serve.js +37 -0
  82. package/dist/serve.js.map +1 -0
  83. package/dist/server.d.ts +29 -0
  84. package/dist/server.d.ts.map +1 -0
  85. package/dist/server.js +76 -0
  86. package/dist/server.js.map +1 -0
  87. package/dist/tools/connection.tools.d.ts +16 -0
  88. package/dist/tools/connection.tools.d.ts.map +1 -0
  89. package/dist/tools/connection.tools.js +106 -0
  90. package/dist/tools/connection.tools.js.map +1 -0
  91. package/dist/tools/discovery.tools.d.ts +16 -0
  92. package/dist/tools/discovery.tools.d.ts.map +1 -0
  93. package/dist/tools/discovery.tools.js +369 -0
  94. package/dist/tools/discovery.tools.js.map +1 -0
  95. package/dist/tools/markdown.d.ts +13 -0
  96. package/dist/tools/markdown.d.ts.map +1 -0
  97. package/dist/tools/markdown.js +65 -0
  98. package/dist/tools/markdown.js.map +1 -0
  99. package/dist/tools/recording.tools.d.ts +16 -0
  100. package/dist/tools/recording.tools.d.ts.map +1 -0
  101. package/dist/tools/recording.tools.js +378 -0
  102. package/dist/tools/recording.tools.js.map +1 -0
  103. package/dist/tools/run.tools.d.ts +13 -0
  104. package/dist/tools/run.tools.d.ts.map +1 -0
  105. package/dist/tools/run.tools.js +230 -0
  106. package/dist/tools/run.tools.js.map +1 -0
  107. package/dist/tools/schemas.d.ts +560 -0
  108. package/dist/tools/schemas.d.ts.map +1 -0
  109. package/dist/tools/schemas.js +112 -0
  110. package/dist/tools/schemas.js.map +1 -0
  111. package/dist/tools/testcase.tools.d.ts +50 -0
  112. package/dist/tools/testcase.tools.d.ts.map +1 -0
  113. package/dist/tools/testcase.tools.js +384 -0
  114. package/dist/tools/testcase.tools.js.map +1 -0
  115. package/dist/vendor/actionSignatures.d.ts +117 -0
  116. package/dist/vendor/actionSignatures.d.ts.map +1 -0
  117. package/dist/vendor/actionSignatures.js +420 -0
  118. package/dist/vendor/actionSignatures.js.map +1 -0
  119. package/dist/vendor/logger.d.ts +38 -0
  120. package/dist/vendor/logger.d.ts.map +1 -0
  121. package/dist/vendor/logger.js +174 -0
  122. package/dist/vendor/logger.js.map +1 -0
  123. package/package.json +57 -0
package/dist/serve.js ADDED
@@ -0,0 +1,37 @@
1
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
2
+ import { buildServer } from './server.js';
3
+ import { QaConnection } from './connection.js';
4
+ import { DraftStore } from './drafts/store.js';
5
+ import { createAdapter } from './adapters/factory.js';
6
+ import { resolveConfig } from './config.js';
7
+ import { log, mask } from './log.js';
8
+ /**
9
+ * The PRODUCT stdio entry — what `common-qa-mcp` (or `common-qa-mcp serve`) runs when an AI client
10
+ * launches it over stdio (the npx path). Mirrors mount.ts's HTTP wiring (a live QaConnection so
11
+ * qa_connect works + a shared DraftStore so drafts survive across a session) but the transport is
12
+ * stdio and the connection is primed from the `QA_MCP_*` env the client passes in its mcp config —
13
+ * there are no HTTP headers over stdio. All logging goes to STDERR (log.ts); stdout is the MCP
14
+ * transport itself and must stay clean.
15
+ */
16
+ export async function startStdioServer() {
17
+ const config = resolveConfig(process.env);
18
+ const connection = new QaConnection(config);
19
+ const server = buildServer({
20
+ adapter: createAdapter(config),
21
+ connection,
22
+ mode: config.mode,
23
+ drafts: new DraftStore(),
24
+ walnutUiOrigin: config.walnutUiOrigin,
25
+ agentUrl: config.agentUrl,
26
+ // No Electron window in the standalone CLI — tc_draft_open just returns the editor link.
27
+ openEditorWindow: async () => false,
28
+ });
29
+ await server.connect(new StdioServerTransport());
30
+ if (config.mode === 'offline') {
31
+ log('stdio server ready — offline (draft authoring only; set QA_MCP_URL / QA_MCP_TOKEN / QA_MCP_PROJECT_ID to connect)');
32
+ }
33
+ else {
34
+ log(`stdio server ready — product=${config.provider ?? 'walnut'} url=${config.url} project=${config.projectId} token=${mask(config.token)}`);
35
+ }
36
+ }
37
+ //# sourceMappingURL=serve.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serve.js","sourceRoot":"","sources":["../src/serve.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAErC;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB;IACpC,MAAM,MAAM,GAAG,aAAa,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAC1C,MAAM,UAAU,GAAG,IAAI,YAAY,CAAC,MAAM,CAAC,CAAC;IAC5C,MAAM,MAAM,GAAG,WAAW,CAAC;QACzB,OAAO,EAAE,aAAa,CAAC,MAAM,CAAC;QAC9B,UAAU;QACV,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,MAAM,EAAE,IAAI,UAAU,EAAE;QACxB,cAAc,EAAE,MAAM,CAAC,cAAc;QACrC,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,yFAAyF;QACzF,gBAAgB,EAAE,KAAK,IAAI,EAAE,CAAC,KAAK;KACpC,CAAC,CAAC;IACH,MAAM,MAAM,CAAC,OAAO,CAAC,IAAI,oBAAoB,EAAE,CAAC,CAAC;IACjD,IAAI,MAAM,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAC9B,GAAG,CAAC,mHAAmH,CAAC,CAAC;IAC3H,CAAC;SAAM,CAAC;QACN,GAAG,CAAC,gCAAgC,MAAM,CAAC,QAAQ,IAAI,QAAQ,QAAQ,MAAM,CAAC,GAAG,YAAY,MAAM,CAAC,SAAS,UAAU,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAC/I,CAAC;AACH,CAAC"}
@@ -0,0 +1,29 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { ProviderAdapter } from './adapters/types.js';
3
+ import type { QaConnection } from './connection.js';
4
+ import { DraftStore } from './drafts/store.js';
5
+ import { ok, err } from './mcpResult.js';
6
+ export { ok, err };
7
+ /**
8
+ * Common QA MCP -- the product-neutral core. This is the STANDALONE build: it carries the API
9
+ * lifecycle surface (connection, projects, discovery, drafts, save-orchestration) that routes to a
10
+ * product adapter -- Walnut over REST, SimplifyQA over its own MCP. The browser-recording + live
11
+ * editor tools are agent-bound (they need a real browser/Electron window) and live in the Walnut
12
+ * agent, which mounts this same core with those extra deps injected.
13
+ *
14
+ * `| undefined` on the optional fields is deliberate (exactOptionalPropertyTypes): callers thread
15
+ * them through from other optional values, e.g. `walnutUiOrigin: config.walnutUiOrigin`.
16
+ */
17
+ export type ServerDeps = {
18
+ adapter: ProviderAdapter | null;
19
+ /** The LIVE connection (mount.ts supplies one); tools read it per call so runtime qa_connect /
20
+ * header-primed connections win over the startup adapter. */
21
+ connection?: QaConnection | undefined;
22
+ mode?: 'connected' | 'offline' | undefined;
23
+ drafts?: DraftStore | undefined;
24
+ walnutUiOrigin?: string | undefined;
25
+ agentUrl?: string | undefined;
26
+ openEditorWindow?: ((url: string, title: string) => Promise<boolean>) | undefined;
27
+ };
28
+ export declare function buildServer(deps: ServerDeps): McpServer;
29
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAM/C,OAAO,EAAE,EAAE,EAAE,GAAG,EAAE,MAAM,gBAAgB,CAAC;AAEzC,OAAO,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC;AAEnB;;;;;;;;;GASG;AACH,MAAM,MAAM,UAAU,GAAG;IACvB,OAAO,EAAE,eAAe,GAAG,IAAI,CAAC;IAChC;kEAC8D;IAC9D,UAAU,CAAC,EAAE,YAAY,GAAG,SAAS,CAAC;IACtC,IAAI,CAAC,EAAE,WAAW,GAAG,SAAS,GAAG,SAAS,CAAC;IAC3C,MAAM,CAAC,EAAE,UAAU,GAAG,SAAS,CAAC;IAChC,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,gBAAgB,CAAC,EAAE,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;CACnF,CAAC;AAIF,wBAAgB,WAAW,CAAC,IAAI,EAAE,UAAU,GAAG,SAAS,CA4EvD"}
package/dist/server.js ADDED
@@ -0,0 +1,76 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { DraftStore } from './drafts/store.js';
3
+ import { registerTestcaseTools } from './tools/testcase.tools.js';
4
+ import { registerConnectionTools } from './tools/connection.tools.js';
5
+ import { registerDiscoveryTools } from './tools/discovery.tools.js';
6
+ import { registerRunTools } from './tools/run.tools.js';
7
+ import { registerRecordingPassthrough } from './tools/recording.tools.js';
8
+ import { ok, err } from './mcpResult.js';
9
+ export { ok, err };
10
+ const OFFLINE_TOOLS = ['actions_catalog', 'tc_draft_create', 'tc_draft_update', 'tc_draft_get', 'tc_draft_discard', 'tc_draft_open'];
11
+ export function buildServer(deps) {
12
+ const server = new McpServer({ name: 'common-qa-mcp', version: '0.1.0' });
13
+ const drafts = deps.drafts ?? new DraftStore();
14
+ server.registerTool('project_info', {
15
+ description: 'Validate the configured connection and return provider, org/project and entity counts.',
16
+ inputSchema: {}, annotations: { readOnlyHint: true },
17
+ }, async () => {
18
+ // The live connection wins over startup state: the user may have connected/disconnected since
19
+ // boot (qa_connect or config headers), so reporting the boot-time mode would be wrong.
20
+ const live = deps.connection?.describe();
21
+ const adapter = deps.connection ? deps.connection.adapter() : deps.adapter;
22
+ const mode = live ? live.mode : deps.mode;
23
+ if (mode === 'offline') {
24
+ return ok('Offline: no backend connection, so nothing is persisted anywhere.\n\n' +
25
+ `Available offline: ${OFFLINE_TOOLS.join(', ')}.\n` +
26
+ 'Author a draft with tc_draft_create / tc_draft_update and open it with tc_draft_open.\n\n' +
27
+ 'To work with EXISTING test cases (list, read, modify, run), connect first:\n' +
28
+ ' qa_connect({ url: "https://<your-product>", token: "<token>", projectId: "<id>", product: "walnut" })', { mode: 'offline', availableTools: OFFLINE_TOOLS, ...(live ? { connection: live } : {}) });
29
+ }
30
+ if (!adapter)
31
+ return err('No provider configured. Use qa_connect, or set QA_MCP_URL/TOKEN/PROJECT_ID.');
32
+ try {
33
+ const info = await adapter.info();
34
+ return ok(`Connected: ${info.provider} · project ${info.projectName} (${info.projectId})`
35
+ + (live?.source === 'runtime-connect' ? ' · via qa_connect' : ' · from startup config'), { info, ...(live ? { connection: live } : {}) });
36
+ }
37
+ catch (e) {
38
+ return err(e instanceof Error ? e.message : String(e));
39
+ }
40
+ });
41
+ // Runtime connect/disconnect/switch -- only when the host wired a live connection (mount.ts does).
42
+ if (deps.connection)
43
+ registerConnectionTools(server, deps.connection);
44
+ registerTestcaseTools(server, {
45
+ drafts,
46
+ walnutUiOrigin: deps.walnutUiOrigin,
47
+ agentUrl: deps.agentUrl,
48
+ openEditorWindow: deps.openEditorWindow,
49
+ // tc_save persists a draft to the product via the current provider adapter (create/update),
50
+ // forwarded across the hop — same live-connection-wins rule as discovery.
51
+ getAdapter: () => (deps.connection ? deps.connection.adapter() : deps.adapter),
52
+ });
53
+ // Read side of the shared lifecycle contract (list_projects / tc_list / tc_get). Reads the current
54
+ // provider adapter per call -- the live connection wins over the startup adapter, like project_info.
55
+ registerDiscoveryTools(server, {
56
+ getAdapter: () => (deps.connection ? deps.connection.adapter() : deps.adapter),
57
+ });
58
+ // Run surface (cloud_runners / tc_run / run_status / run_stop) -- same live-connection-wins rule;
59
+ // the run is forwarded to the product MCP (local dispatches to the agent, cloud to a runner VM).
60
+ registerRunTools(server, {
61
+ getAdapter: () => (deps.connection ? deps.connection.adapter() : deps.adapter),
62
+ });
63
+ // Recording passthrough: mirror the product agent's browser-recording tools onto this server so
64
+ // the client records through the one door. Agent-bound + forwarded (see recording.tools.ts).
65
+ // Async + fire-and-forget: it waits for the connection, then registerTool emits list_changed.
66
+ registerRecordingPassthrough(server, {
67
+ getAdapter: () => (deps.connection ? deps.connection.adapter() : deps.adapter),
68
+ isConnected: () => deps.connection ? deps.connection.describe().mode === 'connected' : deps.mode === 'connected',
69
+ // SimplifyQA-only: gates the granular-verb proxying + real-schema preservation. Walnut unaffected.
70
+ isSimplifyqa: () => deps.connection?.config().provider === 'simplifyqa',
71
+ // Shared with the draft/save tools so a recorded draft bridged here is the SAME store tc_save reads.
72
+ drafts,
73
+ });
74
+ return server;
75
+ }
76
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * `qa_connect` / `qa_disconnect` / `qa_connection_status` -- the URL + token entry point.
3
+ *
4
+ * The product shape is: an AI client connects to this MCP and the USER hands it an app URL and a
5
+ * token, in conversation. Before this, credentials came only from `QA_MCP_URL`/`QA_MCP_TOKEN`/
6
+ * `QA_MCP_PROJECT_ID` read once at boot, so connecting -- or switching project or org -- meant
7
+ * restarting the agent. See recording/../connection.ts for what the holder does and does not do
8
+ * (memory only, never logged, validated before it is adopted).
9
+ *
10
+ * Offline authoring never needs any of this: recording, the live editor and drafts work with no
11
+ * connection at all, and that is the mode this server boots into.
12
+ */
13
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
14
+ import type { QaConnection } from '../connection.js';
15
+ export declare function registerConnectionTools(server: McpServer, connection: QaConnection): void;
16
+ //# sourceMappingURL=connection.tools.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connection.tools.d.ts","sourceRoot":"","sources":["../../src/tools/connection.tools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAGrD,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,YAAY,GAAG,IAAI,CAkIzF"}
@@ -0,0 +1,106 @@
1
+ /**
2
+ * `qa_connect` / `qa_disconnect` / `qa_connection_status` -- the URL + token entry point.
3
+ *
4
+ * The product shape is: an AI client connects to this MCP and the USER hands it an app URL and a
5
+ * token, in conversation. Before this, credentials came only from `QA_MCP_URL`/`QA_MCP_TOKEN`/
6
+ * `QA_MCP_PROJECT_ID` read once at boot, so connecting -- or switching project or org -- meant
7
+ * restarting the agent. See recording/../connection.ts for what the holder does and does not do
8
+ * (memory only, never logged, validated before it is adopted).
9
+ *
10
+ * Offline authoring never needs any of this: recording, the live editor and drafts work with no
11
+ * connection at all, and that is the mode this server boots into.
12
+ */
13
+ import { z } from 'zod';
14
+ import { ok, err } from '../mcpResult.js';
15
+ export function registerConnectionTools(server, connection) {
16
+ server.registerTool('qa_connect', {
17
+ title: 'Connect to a Walnut app',
18
+ description: 'Point this MCP at a Walnut app so it can work with EXISTING test cases -- list them, read ' +
19
+ 'their steps, modify them, and run them. Ask the user for their app URL and a token; do not ' +
20
+ 'guess either. The token is a Walnut session JWT (then `projectId` is required, because the ' +
21
+ 'token alone carries no project context) or an MCP API key (then pass auth:"key" and ' +
22
+ 'projectId is optional -- a key is already project-scoped).\n\n' +
23
+ 'The connection is VALIDATED before it is accepted (it fetches the project), so a wrong URL, ' +
24
+ 'token or project fails here with the reason rather than surfacing later as a confusing ' +
25
+ 'error, and a failed attempt leaves any working connection untouched. It is kept in memory ' +
26
+ 'for as long as the agent runs and is never written to disk or logged.\n\n' +
27
+ 'NOT needed to record a new test case: recording, the live editor and drafts work offline ' +
28
+ 'with nothing configured.',
29
+ inputSchema: {
30
+ url: z.string().min(1).describe('App URL, e.g. "https://app.walnut.ai" or a self-hosted origin. Must start with http:// or https://.'),
31
+ token: z.string().min(1).describe('Bearer token: a Walnut session JWT, or an MCP API key.'),
32
+ projectId: z.string().optional().describe('Project id. REQUIRED with a JWT; optional with an API key.'),
33
+ auth: z.enum(['jwt', 'key']).optional().describe('Token kind. Default "jwt": sends Authorization + x-project-id. "key": sends Authorization only.'),
34
+ product: z.enum(['walnut', 'simplifyqa']).optional().describe('Which product to connect to. Default "walnut". Use "simplifyqa" and pass the SimplifyQA agent MCP\'s /mcp endpoint as `url` — the request is then routed through to the SimplifyQA MCP.'),
35
+ },
36
+ annotations: { readOnlyHint: false },
37
+ }, async ({ url, token, projectId, auth, product }) => {
38
+ const result = await connection.connect({
39
+ url,
40
+ token,
41
+ ...(projectId !== undefined ? { projectId } : {}),
42
+ ...(auth !== undefined ? { auth } : {}),
43
+ ...(product !== undefined ? { product } : {}),
44
+ });
45
+ if (!result.ok) {
46
+ return err(`Could not connect: ${result.error}\n\n` +
47
+ 'Nothing changed — any previous connection is still in place. Check the URL is the APP ' +
48
+ 'origin (not the API path), that the token is current, and that the project id belongs to ' +
49
+ 'the same org as the token.');
50
+ }
51
+ const info = result.info;
52
+ return ok(`Connected to ${connection.describe().url} — project ${info?.projectName ?? '(unnamed)'} ` +
53
+ `(${info?.projectId ?? projectId ?? 'key-scoped'}).\n` +
54
+ 'You can now list, read, modify and run existing test cases. Recording still works as before.', { connection: connection.describe(), info: result.info });
55
+ });
56
+ server.registerTool('qa_switch_project', {
57
+ title: 'Switch the connected project',
58
+ description: 'Switch to a different project on the SAME connection — reuses the current URL + token, so ' +
59
+ 'you do NOT re-enter them. Pass the project id; if the user names a project, call ' +
60
+ 'list_projects first to resolve the name to an id. Validated before it is adopted, so a bad ' +
61
+ 'id leaves the current project in place.',
62
+ inputSchema: {
63
+ projectId: z.string().min(1).describe('The project id to switch to. Use list_projects to find it by name.'),
64
+ },
65
+ annotations: { readOnlyHint: false },
66
+ }, async ({ projectId }) => {
67
+ const result = await connection.switchProject(projectId);
68
+ if (!result.ok) {
69
+ return err(`Could not switch project: ${result.error}\n\nThe current project is unchanged.`);
70
+ }
71
+ const info = result.info;
72
+ return ok(`Switched to project ${info?.projectName ?? '(unnamed)'} (${info?.projectId ?? projectId}). ` +
73
+ 'tc_list / tc_get now operate on this project.', { connection: connection.describe(), info: result.info });
74
+ });
75
+ server.registerTool('qa_connection_status', {
76
+ title: 'Show the current connection',
77
+ description: 'Report whether this MCP is connected to a Walnut app, which URL and project, how the ' +
78
+ 'credentials arrived (startup config vs qa_connect), and whether a token is present. The ' +
79
+ 'token value itself is never returned.',
80
+ inputSchema: {},
81
+ annotations: { readOnlyHint: true },
82
+ }, async () => {
83
+ const d = connection.describe();
84
+ if (d.mode === 'offline') {
85
+ return ok('Offline — no backend connection. Recording, the live editor and drafts all work; ' +
86
+ 'listing, reading, modifying and running EXISTING test cases need qa_connect.', { connection: d });
87
+ }
88
+ return ok(`Connected to ${d.url} · project ${d.projectId || '(key-scoped)'} · auth ${d.auth} · ` +
89
+ `credentials from ${d.source === 'runtime-connect' ? 'qa_connect' : 'startup config'}`, { connection: d });
90
+ });
91
+ server.registerTool('qa_disconnect', {
92
+ title: 'Drop the connection',
93
+ description: 'Forget the runtime URL and token and fall back to whatever the agent started with (usually ' +
94
+ 'nothing). Use it before connecting to a different app or project, or to stop this MCP ' +
95
+ 'reaching a backend at all. Offline authoring is unaffected.',
96
+ inputSchema: {},
97
+ annotations: { readOnlyHint: false },
98
+ }, async () => {
99
+ connection.disconnect();
100
+ const d = connection.describe();
101
+ return ok(d.mode === 'offline'
102
+ ? 'Disconnected. No backend connection now — recording and drafts still work.'
103
+ : `Disconnected the runtime credentials; back to the startup connection (${d.url}).`, { connection: d });
104
+ });
105
+ }
106
+ //# sourceMappingURL=connection.tools.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connection.tools.js","sourceRoot":"","sources":["../../src/tools/connection.tools.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { ProviderAdapter } from '../adapters/types.js';
3
+ /**
4
+ * The read side of the shared, product-neutral lifecycle contract: `tc_list` and
5
+ * `tc_get`. Unlike the offline draft surface (testcase.tools.ts), these need a
6
+ * LIVE backend, so they read the current provider adapter per call via
7
+ * `deps.getAdapter()` -- the same product-routing seam project_info uses: whichever
8
+ * product the session is connected to (walnut today, sqa via its own MCP later)
9
+ * answers, and the tool shape is identical either way.
10
+ */
11
+ export type DiscoveryToolsDeps = {
12
+ /** The live provider adapter for the current connection, or null when offline. */
13
+ getAdapter: () => ProviderAdapter | null;
14
+ };
15
+ export declare function registerDiscoveryTools(server: McpServer, deps: DiscoveryToolsDeps): void;
16
+ //# sourceMappingURL=discovery.tools.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discovery.tools.d.ts","sourceRoot":"","sources":["../../src/tools/discovery.tools.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEzE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAK5D;;;;;;;GAOG;AACH,MAAM,MAAM,kBAAkB,GAAG;IAC/B,kFAAkF;IAClF,UAAU,EAAE,MAAM,eAAe,GAAG,IAAI,CAAC;CAC1C,CAAC;AAeF,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,kBAAkB,GAAG,IAAI,CAkaxF"}