@agntn/harnesses 0.0.1

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 (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +142 -0
  3. package/dist/THIRD-PARTY-LICENSES.md +36 -0
  4. package/dist/_chunks/agents-sync.d.mts +417 -0
  5. package/dist/_chunks/agents-sync.d.mts.map +1 -0
  6. package/dist/_chunks/agents-sync.mjs +2719 -0
  7. package/dist/_chunks/agents-sync.mjs.map +1 -0
  8. package/dist/_chunks/agents.mjs +62 -0
  9. package/dist/_chunks/agents.mjs.map +1 -0
  10. package/dist/_chunks/libs/typebox.mjs +5512 -0
  11. package/dist/_chunks/libs/typebox.mjs.map +1 -0
  12. package/dist/_chunks/mcp-servers.mjs +149 -0
  13. package/dist/_chunks/mcp-servers.mjs.map +1 -0
  14. package/dist/_chunks/mcp.mjs +164 -0
  15. package/dist/_chunks/mcp.mjs.map +1 -0
  16. package/dist/_chunks/mcp2.mjs +17 -0
  17. package/dist/_chunks/mcp2.mjs.map +1 -0
  18. package/dist/_chunks/tool-operations.mjs +361 -0
  19. package/dist/_chunks/tool-operations.mjs.map +1 -0
  20. package/dist/_chunks/tool-schemas.d.mts +94 -0
  21. package/dist/_chunks/tool-schemas.d.mts.map +1 -0
  22. package/dist/_chunks/tool-schemas.mjs +112 -0
  23. package/dist/_chunks/tool-schemas.mjs.map +1 -0
  24. package/dist/_chunks/types.mjs +4 -0
  25. package/dist/_chunks/types.mjs.map +1 -0
  26. package/dist/cli.d.mts +1 -0
  27. package/dist/cli.mjs +350 -0
  28. package/dist/cli.mjs.map +1 -0
  29. package/dist/index.d.mts +300 -0
  30. package/dist/index.d.mts.map +1 -0
  31. package/dist/index.mjs +6 -0
  32. package/dist/index.mjs.map +1 -0
  33. package/dist/mcp.d.mts +15 -0
  34. package/dist/mcp.d.mts.map +1 -0
  35. package/dist/mcp.mjs +2 -0
  36. package/dist/tool-operations.d.mts +222 -0
  37. package/dist/tool-operations.d.mts.map +1 -0
  38. package/dist/tool-operations.mjs +3 -0
  39. package/dist/tool-schemas.d.mts +2 -0
  40. package/dist/tool-schemas.mjs +2 -0
  41. package/package.json +102 -0
  42. package/packages/omp/extensions/AGENTS.md +7 -0
  43. package/packages/omp/extensions/harnesses.ts +253 -0
  44. package/packages/pi/extensions/AGENTS.md +7 -0
  45. package/packages/pi/extensions/harnesses.ts +265 -0
  46. package/packages/shared/AGENTS.md +7 -0
  47. package/packages/shared/tui.ts +346 -0
@@ -0,0 +1,149 @@
1
+ import { isHarnessId, listHarnesses } from "./agents-sync.mjs";
2
+ import { mcpAdd, mcpList, mcpRemove, mcpSync } from "./tool-operations.mjs";
3
+ import { stripVTControlCharacters } from "node:util";
4
+ import { defineCommand } from "citty";
5
+ import { consola } from "consola";
6
+ import { encode } from "@toon-format/toon";
7
+ const formatArgs = {
8
+ json: {
9
+ type: "boolean",
10
+ description: "Output as JSON"
11
+ },
12
+ toon: {
13
+ type: "boolean",
14
+ description: "Output as TOON"
15
+ }
16
+ };
17
+ const UNSAFE_TERMINAL_CONTROLS = /[\u0000-\u0008\u000B-\u001F\u007F-\u009F]/g;
18
+ function sanitize(text) {
19
+ return stripVTControlCharacters(text ?? "").replace(UNSAFE_TERMINAL_CONTROLS, " ");
20
+ }
21
+ function report(result, args) {
22
+ if (result.isError) {
23
+ consola.error(sanitize(result.content[0]?.text));
24
+ process.exit(1);
25
+ }
26
+ if (args.json) {
27
+ console.log(JSON.stringify(result.details, null, 2));
28
+ return;
29
+ }
30
+ if (args.toon) {
31
+ console.log(encode(result.details));
32
+ return;
33
+ }
34
+ console.log(sanitize(result.content[0]?.text));
35
+ }
36
+ function requireHarnessId(id) {
37
+ if (!isHarnessId(id)) {
38
+ consola.error(`Unknown harness: ${id}\nKnown: ${listHarnesses().join(", ")}`);
39
+ process.exit(1);
40
+ }
41
+ return id;
42
+ }
43
+ var mcp_servers_default = defineCommand({
44
+ meta: { description: "Manage MCP servers across harness configs" },
45
+ subCommands: {
46
+ list: defineCommand({
47
+ meta: { description: "List configured MCP servers, normalized" },
48
+ args: {
49
+ id: {
50
+ type: "positional",
51
+ description: "Harness id",
52
+ required: false
53
+ },
54
+ ...formatArgs
55
+ },
56
+ run({ args }) {
57
+ const id = args.id === void 0 ? void 0 : requireHarnessId(args.id);
58
+ report(mcpList(id), args);
59
+ }
60
+ }),
61
+ add: defineCommand({
62
+ meta: { description: "Add or replace one MCP server in a harness config" },
63
+ args: {
64
+ id: {
65
+ type: "positional",
66
+ description: "Harness id",
67
+ required: true
68
+ },
69
+ name: {
70
+ type: "positional",
71
+ description: "Server name",
72
+ required: true
73
+ },
74
+ command: {
75
+ type: "string",
76
+ description: "Binary for a stdio server"
77
+ },
78
+ args: {
79
+ type: "string",
80
+ description: "Space-separated arguments for a stdio server"
81
+ },
82
+ env: {
83
+ type: "string",
84
+ description: "Comma-separated K=V environment variables"
85
+ },
86
+ url: {
87
+ type: "string",
88
+ description: "URL for an http/sse server"
89
+ },
90
+ scope: {
91
+ type: "string",
92
+ description: "user (default) or project"
93
+ },
94
+ ...formatArgs
95
+ },
96
+ run({ args }) {
97
+ const env = args.env ? Object.fromEntries(args.env.split(",").map((pair) => pair.split(/=(.*)/s)).filter((pair) => pair[0]).map((pair) => [pair[0], pair[1] ?? ""])) : void 0;
98
+ report(mcpAdd(requireHarnessId(args.id), {
99
+ name: args.name,
100
+ ...args.command ? { command: args.command } : {},
101
+ ...args.args ? { args: args.args.split(" ").filter(Boolean) } : {},
102
+ ...env ? { env } : {},
103
+ ...args.url ? { url: args.url } : {}
104
+ }, args.scope === "project" ? "project" : "user"), args);
105
+ }
106
+ }),
107
+ sync: defineCommand({
108
+ meta: { description: "Reset harness configs to the master list from ~/.config/agntn/mcp.jsonc" },
109
+ args: {
110
+ id: {
111
+ type: "positional",
112
+ description: "Harness id",
113
+ required: false
114
+ },
115
+ ...formatArgs
116
+ },
117
+ run({ args }) {
118
+ const id = args.id === void 0 ? void 0 : requireHarnessId(args.id);
119
+ report(mcpSync(id), args);
120
+ }
121
+ }),
122
+ remove: defineCommand({
123
+ meta: { description: "Remove one MCP server from a harness config" },
124
+ args: {
125
+ id: {
126
+ type: "positional",
127
+ description: "Harness id",
128
+ required: true
129
+ },
130
+ name: {
131
+ type: "positional",
132
+ description: "Server name",
133
+ required: true
134
+ },
135
+ scope: {
136
+ type: "string",
137
+ description: "user (default) or project"
138
+ },
139
+ ...formatArgs
140
+ },
141
+ run({ args }) {
142
+ report(mcpRemove(requireHarnessId(args.id), args.name, args.scope === "project" ? "project" : "user"), args);
143
+ }
144
+ })
145
+ }
146
+ });
147
+ export { mcp_servers_default as default };
148
+
149
+ //# sourceMappingURL=mcp-servers.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-servers.mjs","names":["toToon"],"sources":["../../src/commands/mcp-servers.ts"],"sourcesContent":["import { stripVTControlCharacters } from \"node:util\";\nimport { defineCommand } from \"citty\";\nimport { consola } from \"consola\";\nimport { encode as toToon } from \"@toon-format/toon\";\nimport { isHarnessId, listHarnesses } from \"../registry.ts\";\nimport { mcpAdd, mcpList, mcpRemove, mcpSync, type ToolResult } from \"../tool-operations.ts\";\n\nconst formatArgs = {\n json: { type: \"boolean\" as const, description: \"Output as JSON\" },\n toon: { type: \"boolean\" as const, description: \"Output as TOON\" },\n};\n\n// MCP listings echo values read from project-controlled config files, so a\n// malicious server name could smuggle ANSI/OSC sequences into the terminal.\n// oxlint-disable-next-line no-control-regex -- Terminal control bytes are precisely what this boundary removes.\nconst UNSAFE_TERMINAL_CONTROLS = /[\\u0000-\\u0008\\u000B-\\u001F\\u007F-\\u009F]/g;\n\nfunction sanitize(text: string | undefined): string {\n return stripVTControlCharacters(text ?? \"\").replace(UNSAFE_TERMINAL_CONTROLS, \" \");\n}\n\n/**\n * Emits one tool result: machine format when asked, sanitized text otherwise.\n *\n * @param result - Tool result to emit.\n * @param args - Selected output-format flags.\n */\nfunction report(\n result: ToolResult<unknown>,\n args: Readonly<{ json?: boolean; toon?: boolean }>,\n): void {\n if (result.isError) {\n consola.error(sanitize(result.content[0]?.text));\n process.exit(1);\n }\n if (args.json) {\n console.log(JSON.stringify(result.details, null, 2));\n return;\n }\n if (args.toon) {\n console.log(toToon(result.details));\n return;\n }\n console.log(sanitize(result.content[0]?.text));\n}\n\nfunction requireHarnessId(id: string): string {\n if (!isHarnessId(id)) {\n consola.error(`Unknown harness: ${id}\\nKnown: ${listHarnesses().join(\", \")}`);\n process.exit(1);\n }\n return id;\n}\n\nexport default defineCommand({\n meta: { description: \"Manage MCP servers across harness configs\" },\n subCommands: {\n list: defineCommand({\n meta: { description: \"List configured MCP servers, normalized\" },\n args: {\n id: { type: \"positional\" as const, description: \"Harness id\", required: false },\n ...formatArgs,\n },\n run({ args }) {\n const id = args.id === undefined ? undefined : requireHarnessId(args.id as string);\n report(mcpList(id), args);\n },\n }),\n add: defineCommand({\n meta: { description: \"Add or replace one MCP server in a harness config\" },\n args: {\n id: { type: \"positional\" as const, description: \"Harness id\", required: true },\n name: { type: \"positional\" as const, description: \"Server name\", required: true },\n command: { type: \"string\" as const, description: \"Binary for a stdio server\" },\n args: {\n type: \"string\" as const,\n description: \"Space-separated arguments for a stdio server\",\n },\n env: { type: \"string\" as const, description: \"Comma-separated K=V environment variables\" },\n url: { type: \"string\" as const, description: \"URL for an http/sse server\" },\n scope: { type: \"string\" as const, description: \"user (default) or project\" },\n ...formatArgs,\n },\n run({ args }) {\n const env = args.env\n ? Object.fromEntries(\n args.env\n .split(\",\")\n .map((pair) => pair.split(/=(.*)/s))\n .filter((pair) => pair[0])\n .map((pair) => [pair[0] as string, pair[1] ?? \"\"]),\n )\n : undefined;\n const result = mcpAdd(\n requireHarnessId(args.id as string),\n {\n name: args.name as string,\n ...(args.command ? { command: args.command } : {}),\n ...(args.args ? { args: args.args.split(\" \").filter(Boolean) } : {}),\n ...(env ? { env } : {}),\n ...(args.url ? { url: args.url } : {}),\n },\n args.scope === \"project\" ? \"project\" : \"user\",\n );\n report(result, args);\n },\n }),\n sync: defineCommand({\n meta: {\n description: \"Reset harness configs to the master list from ~/.config/agntn/mcp.jsonc\",\n },\n args: {\n id: { type: \"positional\" as const, description: \"Harness id\", required: false },\n ...formatArgs,\n },\n run({ args }) {\n const id = args.id === undefined ? undefined : requireHarnessId(args.id as string);\n report(mcpSync(id), args);\n },\n }),\n remove: defineCommand({\n meta: { description: \"Remove one MCP server from a harness config\" },\n args: {\n id: { type: \"positional\" as const, description: \"Harness id\", required: true },\n name: { type: \"positional\" as const, description: \"Server name\", required: true },\n scope: { type: \"string\" as const, description: \"user (default) or project\" },\n ...formatArgs,\n },\n run({ args }) {\n report(\n mcpRemove(\n requireHarnessId(args.id as string),\n args.name as string,\n args.scope === \"project\" ? \"project\" : \"user\",\n ),\n args,\n );\n },\n }),\n },\n});\n"],"mappings":"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"}
@@ -0,0 +1,164 @@
1
+ import { Check, Errors, typebox_exports } from "./libs/typebox.mjs";
2
+ import { version } from "./types.mjs";
3
+ import { harnessToolSchemas } from "./tool-schemas.mjs";
4
+ import { agentsSync, detectHarnesses, harnessInfo, listHarnessModels, mcpAdd, mcpList, mcpRemove, mcpSync, runHarness } from "./tool-operations.mjs";
5
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
6
+ import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
7
+ const READ_ONLY = {
8
+ readOnlyHint: true,
9
+ destructiveHint: false,
10
+ idempotentHint: true,
11
+ openWorldHint: false
12
+ };
13
+ const EXEC_READ = {
14
+ readOnlyHint: false,
15
+ destructiveHint: false,
16
+ idempotentHint: false,
17
+ openWorldHint: true
18
+ };
19
+ const CONFIG_WRITE = {
20
+ readOnlyHint: false,
21
+ destructiveHint: true,
22
+ idempotentHint: true,
23
+ openWorldHint: false
24
+ };
25
+ const schemas = harnessToolSchemas(typebox_exports);
26
+ const tools = [
27
+ {
28
+ name: "harnesses_detect",
29
+ title: "Harnesses Detect",
30
+ description: "List every known AI coding harness with its install state and version, scanned from the binaries on PATH.",
31
+ inputSchema: schemas.detect,
32
+ annotations: READ_ONLY,
33
+ execute: () => detectHarnesses()
34
+ },
35
+ {
36
+ name: "harnesses_info",
37
+ title: "Harnesses Info",
38
+ description: "Full metadata for one or more AI coding harnesses, including supported invocation and model operations, configuration, sessions, instructions, skills, commands, hooks, and resolved paths.",
39
+ inputSchema: schemas.info,
40
+ annotations: READ_ONLY,
41
+ execute: (args) => harnessInfo(args.id)
42
+ },
43
+ {
44
+ name: "harnesses_models",
45
+ title: "Harnesses Models",
46
+ description: "List the models currently available to one AI coding harness through its native CLI, normalized across providers. An optional search filter is passed to harnesses that support it. The command may load project configuration and extensions.",
47
+ inputSchema: schemas.models,
48
+ annotations: EXEC_READ,
49
+ execute: (args) => listHarnessModels(args.id, {
50
+ search: args.search,
51
+ cwd: args.cwd,
52
+ timeoutSeconds: args.timeoutSeconds
53
+ })
54
+ },
55
+ {
56
+ name: "harnesses_run",
57
+ title: "Harnesses Run",
58
+ description: "Run one prompt through an AI coding harness's normalized non-interactive invocation and return its output. Pass tools=true whenever the task needs harness tools, including Grok native X search. Add readOnly=true to require native read-only tool enforcement. Pass tools=false only for an advisor without tools. Unsupported modes never widen access.",
59
+ inputSchema: schemas.run,
60
+ annotations: {
61
+ readOnlyHint: false,
62
+ destructiveHint: true,
63
+ idempotentHint: false,
64
+ openWorldHint: true
65
+ },
66
+ execute: (args) => runHarness(args.id, args.prompt, {
67
+ cwd: args.cwd,
68
+ model: args.model,
69
+ timeoutSeconds: args.timeoutSeconds,
70
+ structured: args.structured,
71
+ tools: args.tools,
72
+ readOnly: args.readOnly
73
+ })
74
+ },
75
+ {
76
+ name: "harnesses_mcp_list",
77
+ title: "Harnesses MCP List",
78
+ description: "List the MCP servers configured in each harness's config files, normalized across dialects. Omit id to scan every harness.",
79
+ inputSchema: schemas.mcpList,
80
+ annotations: READ_ONLY,
81
+ execute: (args) => mcpList(args.id)
82
+ },
83
+ {
84
+ name: "harnesses_mcp_add",
85
+ title: "Harnesses MCP Add",
86
+ description: "Add or replace one MCP server in a harness config. JSON configs are rewritten; TOML configs get a surgical, comment-preserving edit.",
87
+ inputSchema: schemas.mcpAdd,
88
+ annotations: CONFIG_WRITE,
89
+ execute: (args) => mcpAdd(args.id, args, args.scope ?? "user")
90
+ },
91
+ {
92
+ name: "harnesses_mcp_sync",
93
+ title: "Harnesses MCP Sync",
94
+ description: "Reset every harness's user-scope MCP config to exactly the master list from ~/.config/agntn/mcp.jsonc: servers are added, replaced, and extras removed. Excluded harnesses keep their own servers, but master-listed names are withdrawn from them.",
95
+ inputSchema: schemas.mcpSync,
96
+ annotations: CONFIG_WRITE,
97
+ execute: (args) => mcpSync(args.id)
98
+ },
99
+ {
100
+ name: "harnesses_agents_sync",
101
+ title: "Harnesses Agents Sync",
102
+ description: "Link every harness's global instructions file and declared companions to the master bundle, so edits made through any harness land in one place. Diverged copies are backed up and relinked. Pass check to only report.",
103
+ inputSchema: schemas.agentsSync,
104
+ annotations: CONFIG_WRITE,
105
+ execute: (args) => agentsSync(args.id, args.check === true)
106
+ },
107
+ {
108
+ name: "harnesses_mcp_remove",
109
+ title: "Harnesses MCP Remove",
110
+ description: "Remove one MCP server from a harness config. JSON configs are rewritten; TOML configs get a surgical, comment-preserving edit.",
111
+ inputSchema: schemas.mcpRemove,
112
+ annotations: CONFIG_WRITE,
113
+ execute: (args) => mcpRemove(args.id, args.name, args.scope ?? "user")
114
+ }
115
+ ];
116
+ function validationError(schema, value) {
117
+ const first = Errors(schema, value)[0];
118
+ if (!first) return "Invalid arguments";
119
+ return `Invalid arguments at ${first.instancePath || "/"}: ${first.message}`;
120
+ }
121
+ function errorResult(text) {
122
+ return {
123
+ content: [{
124
+ type: "text",
125
+ text: text.replaceAll(/\p{Cc}/gu, " ")
126
+ }],
127
+ isError: true
128
+ };
129
+ }
130
+ function toCallToolResult(result) {
131
+ return {
132
+ content: result.content,
133
+ ...result.isError === void 0 ? {} : { isError: result.isError }
134
+ };
135
+ }
136
+ function createMcpServer() {
137
+ const toolsByName = new Map(tools.map((tool) => [tool.name, tool]));
138
+ const server = new Server({
139
+ name: "harnesses",
140
+ version
141
+ }, { capabilities: { tools: {} } });
142
+ server.setRequestHandler(ListToolsRequestSchema, () => ({ tools: tools.map((tool) => ({
143
+ name: tool.name,
144
+ title: tool.title,
145
+ description: tool.description,
146
+ inputSchema: tool.inputSchema,
147
+ annotations: tool.annotations
148
+ })) }));
149
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
150
+ const tool = toolsByName.get(request.params.name);
151
+ if (!tool) return errorResult(`Unknown harnesses tool: ${JSON.stringify(request.params.name)}`);
152
+ const args = request.params.arguments ?? {};
153
+ if (!Check(tool.inputSchema, args)) return errorResult(validationError(tool.inputSchema, args));
154
+ try {
155
+ return toCallToolResult(await tool.execute(args));
156
+ } catch (error) {
157
+ return errorResult(`${tool.name} failed: ${error instanceof Error ? error.message : String(error)}`);
158
+ }
159
+ });
160
+ return server;
161
+ }
162
+ export { createMcpServer };
163
+
164
+ //# sourceMappingURL=mcp.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp.mjs","names":["Type","Value.Errors","Value.Check"],"sources":["../../src/mcp.ts"],"sourcesContent":["import { Server } from \"@modelcontextprotocol/sdk/server/index.js\";\nimport {\n CallToolRequestSchema,\n ListToolsRequestSchema,\n type CallToolResult,\n type Tool,\n} from \"@modelcontextprotocol/sdk/types.js\";\nimport { Type, type TSchema } from \"typebox\";\nimport { Value } from \"typebox/value\";\nimport {\n detectHarnesses,\n harnessInfo,\n listHarnessModels,\n mcpAdd,\n mcpList,\n mcpRemove,\n mcpSync,\n agentsSync,\n runHarness,\n type McpServerParams,\n type ToolResult,\n} from \"./tool-operations.ts\";\nimport { harnessToolSchemas } from \"./tool-schemas.ts\";\nimport { version } from \"./types.ts\";\n\ninterface ToolDefinition {\n name: string;\n title: string;\n description: string;\n inputSchema: TSchema;\n annotations: Tool[\"annotations\"];\n execute(\n args: Readonly<Record<string, unknown>>,\n ): ToolResult<unknown> | Promise<ToolResult<unknown>>;\n}\n\nconst READ_ONLY: Tool[\"annotations\"] = {\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n};\n\nconst EXEC_READ: Tool[\"annotations\"] = {\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: false,\n openWorldHint: true,\n};\n\nconst CONFIG_WRITE: Tool[\"annotations\"] = {\n readOnlyHint: false,\n destructiveHint: true,\n idempotentHint: true,\n openWorldHint: false,\n};\n\nconst schemas = harnessToolSchemas<TSchema, TSchema>(Type);\n\nconst tools: ToolDefinition[] = [\n {\n name: \"harnesses_detect\",\n title: \"Harnesses Detect\",\n description:\n \"List every known AI coding harness with its install state and version, scanned from the binaries on PATH.\",\n inputSchema: schemas.detect,\n annotations: READ_ONLY,\n execute: () => detectHarnesses(),\n },\n {\n name: \"harnesses_info\",\n title: \"Harnesses Info\",\n description:\n \"Full metadata for one or more AI coding harnesses, including supported invocation and model operations, configuration, sessions, instructions, skills, commands, hooks, and resolved paths.\",\n inputSchema: schemas.info,\n annotations: READ_ONLY,\n execute: (args) => harnessInfo(args.id as string | string[]),\n },\n {\n name: \"harnesses_models\",\n title: \"Harnesses Models\",\n description:\n \"List the models currently available to one AI coding harness through its native CLI, normalized across providers. An optional search filter is passed to harnesses that support it. The command may load project configuration and extensions.\",\n inputSchema: schemas.models,\n annotations: EXEC_READ,\n execute: (args) =>\n listHarnessModels(args.id as string, {\n search: args.search as string | undefined,\n cwd: args.cwd as string | undefined,\n timeoutSeconds: args.timeoutSeconds as number | undefined,\n }),\n },\n {\n name: \"harnesses_run\",\n title: \"Harnesses Run\",\n description:\n \"Run one prompt through an AI coding harness's normalized non-interactive invocation and return its output. Pass tools=true whenever the task needs harness tools, including Grok native X search. Add readOnly=true to require native read-only tool enforcement. Pass tools=false only for an advisor without tools. Unsupported modes never widen access.\",\n inputSchema: schemas.run,\n annotations: {\n readOnlyHint: false,\n destructiveHint: true,\n idempotentHint: false,\n openWorldHint: true,\n },\n execute: (args) =>\n runHarness(args.id as string, args.prompt as string, {\n cwd: args.cwd as string | undefined,\n model: args.model as string | undefined,\n timeoutSeconds: args.timeoutSeconds as number | undefined,\n structured: args.structured as boolean | undefined,\n tools: args.tools as boolean | undefined,\n readOnly: args.readOnly as boolean | undefined,\n }),\n },\n {\n name: \"harnesses_mcp_list\",\n title: \"Harnesses MCP List\",\n description:\n \"List the MCP servers configured in each harness's config files, normalized across dialects. Omit id to scan every harness.\",\n inputSchema: schemas.mcpList,\n annotations: READ_ONLY,\n execute: (args) => mcpList(args.id as string | undefined),\n },\n {\n name: \"harnesses_mcp_add\",\n title: \"Harnesses MCP Add\",\n description:\n \"Add or replace one MCP server in a harness config. JSON configs are rewritten; TOML configs get a surgical, comment-preserving edit.\",\n inputSchema: schemas.mcpAdd,\n annotations: CONFIG_WRITE,\n execute: (args) =>\n mcpAdd(\n args.id as string,\n args as unknown as McpServerParams,\n (args.scope as \"user\" | \"project\") ?? \"user\",\n ),\n },\n {\n name: \"harnesses_mcp_sync\",\n title: \"Harnesses MCP Sync\",\n description:\n \"Reset every harness's user-scope MCP config to exactly the master list from ~/.config/agntn/mcp.jsonc: servers are added, replaced, and extras removed. Excluded harnesses keep their own servers, but master-listed names are withdrawn from them.\",\n inputSchema: schemas.mcpSync,\n annotations: CONFIG_WRITE,\n execute: (args) => mcpSync(args.id as string | undefined),\n },\n {\n name: \"harnesses_agents_sync\",\n title: \"Harnesses Agents Sync\",\n description:\n \"Link every harness's global instructions file and declared companions to the master bundle, so edits made through any harness land in one place. Diverged copies are backed up and relinked. Pass check to only report.\",\n inputSchema: schemas.agentsSync,\n annotations: CONFIG_WRITE,\n execute: (args) => agentsSync(args.id as string | undefined, args.check === true),\n },\n {\n name: \"harnesses_mcp_remove\",\n title: \"Harnesses MCP Remove\",\n description:\n \"Remove one MCP server from a harness config. JSON configs are rewritten; TOML configs get a surgical, comment-preserving edit.\",\n inputSchema: schemas.mcpRemove,\n annotations: CONFIG_WRITE,\n execute: (args) =>\n mcpRemove(\n args.id as string,\n args.name as string,\n (args.scope as \"user\" | \"project\") ?? \"user\",\n ),\n },\n];\n\n/**\n * Formats the first TypeBox validation failure for an MCP client.\n *\n * @param schema - Schema that rejected the value.\n * @param value - Rejected input value.\n * @returns {string} A client-facing validation message.\n */\nfunction validationError(schema: TSchema, value: unknown): string {\n const first = Value.Errors(schema, value)[0];\n if (!first) return \"Invalid arguments\";\n return `Invalid arguments at ${first.instancePath || \"/\"}: ${first.message}`;\n}\n\n/**\n * Wraps error text for the MCP client, replacing control bytes with spaces.\n *\n * Every error branch goes through here because parts of these messages echo\n * client-controlled values (a tool name, an argument) or downstream error\n * messages: one raw newline or escape byte would forge extra lines that read\n * as the server's own answer.\n *\n * @param text - Untrusted error text.\n * @returns {CallToolResult} A sanitized MCP error result.\n */\nfunction errorResult(text: string): CallToolResult {\n return {\n content: [{ type: \"text\", text: text.replaceAll(/\\p{Cc}/gu, \" \") }],\n isError: true,\n };\n}\n\n/**\n * Converts a shared tool result to the MCP text-result contract.\n *\n * `details` is dropped and `structuredContent` is never set: clients that see\n * structured output prefer it over `content` and would hide the readable answer.\n *\n * @param result - Shared tool result to adapt.\n * @returns {CallToolResult} The MCP text-result envelope.\n */\nfunction toCallToolResult(result: ToolResult<unknown>): CallToolResult {\n return {\n content: result.content,\n ...(result.isError === undefined ? {} : { isError: result.isError }),\n };\n}\n\n/**\n * Creates an unconnected MCP server exposing the harness registry tools.\n *\n * Built on the low-level `Server` even though the SDK marks it `@deprecated`,\n * because `McpServer.registerTool` accepts Standard Schema (Zod) only. TypeBox 1.x\n * does not implement Standard Schema, and this package's tool schemas are TypeBox,\n * shared with the Pi and OMP extensions. The high-level API would force a second\n * definition of every parameter.\n *\n * @returns {Server} An unconnected MCP server.\n */\nexport function createMcpServer(): Server {\n const toolsByName = new Map(tools.map((tool) => [tool.name, tool]));\n const server = new Server({ name: \"harnesses\", version }, { capabilities: { tools: {} } });\n\n server.setRequestHandler(ListToolsRequestSchema, () => ({\n tools: tools.map((tool): Tool => ({\n name: tool.name,\n title: tool.title,\n description: tool.description,\n inputSchema: tool.inputSchema as Tool[\"inputSchema\"],\n annotations: tool.annotations,\n })),\n }));\n\n server.setRequestHandler(CallToolRequestSchema, async (request) => {\n const tool = toolsByName.get(request.params.name);\n if (!tool) {\n return errorResult(`Unknown harnesses tool: ${JSON.stringify(request.params.name)}`);\n }\n\n const args = request.params.arguments ?? {};\n if (!Value.Check(tool.inputSchema, args)) {\n return errorResult(validationError(tool.inputSchema, args));\n }\n\n try {\n return toCallToolResult(await tool.execute(args));\n } catch (error) {\n return errorResult(\n `${tool.name} failed: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n });\n\n return server;\n}\n"],"mappings":";;;;;;AAoCA,MAAM,YAAiC;CACrC,cAAc;CACd,iBAAiB;CACjB,gBAAgB;CAChB,eAAe;AACjB;AAEA,MAAM,YAAiC;CACrC,cAAc;CACd,iBAAiB;CACjB,gBAAgB;CAChB,eAAe;AACjB;AAEA,MAAM,eAAoC;CACxC,cAAc;CACd,iBAAiB;CACjB,gBAAgB;CAChB,eAAe;AACjB;AAEA,MAAM,UAAU,mBAAqCA,eAAI;AAEzD,MAAM,QAA0B;CAC9B;EACE,MAAM;EACN,OAAO;EACP,aACE;EACF,aAAa,QAAQ;EACrB,aAAa;EACb,eAAe,gBAAgB;CACjC;CACA;EACE,MAAM;EACN,OAAO;EACP,aACE;EACF,aAAa,QAAQ;EACrB,aAAa;EACb,UAAU,SAAS,YAAY,KAAK,EAAuB;CAC7D;CACA;EACE,MAAM;EACN,OAAO;EACP,aACE;EACF,aAAa,QAAQ;EACrB,aAAa;EACb,UAAU,SACR,kBAAkB,KAAK,IAAc;GACnC,QAAQ,KAAK;GACb,KAAK,KAAK;GACV,gBAAgB,KAAK;EACvB,CAAC;CACL;CACA;EACE,MAAM;EACN,OAAO;EACP,aACE;EACF,aAAa,QAAQ;EACrB,aAAa;GACX,cAAc;GACd,iBAAiB;GACjB,gBAAgB;GAChB,eAAe;EACjB;EACA,UAAU,SACR,WAAW,KAAK,IAAc,KAAK,QAAkB;GACnD,KAAK,KAAK;GACV,OAAO,KAAK;GACZ,gBAAgB,KAAK;GACrB,YAAY,KAAK;GACjB,OAAO,KAAK;GACZ,UAAU,KAAK;EACjB,CAAC;CACL;CACA;EACE,MAAM;EACN,OAAO;EACP,aACE;EACF,aAAa,QAAQ;EACrB,aAAa;EACb,UAAU,SAAS,QAAQ,KAAK,EAAwB;CAC1D;CACA;EACE,MAAM;EACN,OAAO;EACP,aACE;EACF,aAAa,QAAQ;EACrB,aAAa;EACb,UAAU,SACR,OACE,KAAK,IACL,MACC,KAAK,SAAgC,MACxC;CACJ;CACA;EACE,MAAM;EACN,OAAO;EACP,aACE;EACF,aAAa,QAAQ;EACrB,aAAa;EACb,UAAU,SAAS,QAAQ,KAAK,EAAwB;CAC1D;CACA;EACE,MAAM;EACN,OAAO;EACP,aACE;EACF,aAAa,QAAQ;EACrB,aAAa;EACb,UAAU,SAAS,WAAW,KAAK,IAA0B,KAAK,UAAU,IAAI;CAClF;CACA;EACE,MAAM;EACN,OAAO;EACP,aACE;EACF,aAAa,QAAQ;EACrB,aAAa;EACb,UAAU,SACR,UACE,KAAK,IACL,KAAK,MACJ,KAAK,SAAgC,MACxC;CACJ;AACF;;;;;;AASA,SAAS,YAAA,MAAgB;CACvB,OAAM;EACN,SAAK,CAAA;GACL,MAAO;GACT,MAAA,KAAA,WAAA,YAAA,GAAA;;;;;;;;;;;AAcE,SAAO,kBAAA;CACL,MAAA,cAAU,IAAA,IAAA,MAAA,KAAA,SAAA,CAAA,KAAA,MAAA,IAAA,CAAA,CAAA;CAAE,MAAA,SAAM,IAAA,OAAA;EAAQ,MAAA;EAAuC;CACjE,GAAA,EAAA,cAAS,EAAA,OAAA,CAAA,EAAA,EAAA,CAAA;CACX,OAAA,kBAAA,+BAAA,EAAA,OAAA,MAAA,KAAA,UAAA;EACF,MAAA,KAAA;;;;;;;;;;EAWA,IAAA,CAAA,MAAS,KAAA,aAAiB,IAA6C,GAAA,OAAA,YAAA,gBAAA,KAAA,aAAA,IAAA,CAAA;EACrE,IAAA;GACE,OAAA,iBAAgB,MAAA,KAAA,QAAA,IAAA,CAAA;EAChB,SAAI,OAAO;GACb,OAAA,YAAA,GAAA,KAAA,KAAA,WAAA,iBAAA,QAAA,MAAA,UAAA,OAAA,KAAA,GAAA;EACF"}
@@ -0,0 +1,17 @@
1
+ import { createMcpServer } from "./mcp.mjs";
2
+ import { defineCommand } from "citty";
3
+ import { LogLevels, consola } from "consola";
4
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
+ var mcp_default = defineCommand({
6
+ meta: {
7
+ name: "mcp",
8
+ description: "Run the harnesses MCP server over stdio"
9
+ },
10
+ async run() {
11
+ consola.level = LogLevels.warn;
12
+ await createMcpServer().connect(new StdioServerTransport());
13
+ }
14
+ });
15
+ export { mcp_default as default };
16
+
17
+ //# sourceMappingURL=mcp2.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp2.mjs","names":[],"sources":["../../src/commands/mcp.ts"],"sourcesContent":["import { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport { defineCommand } from \"citty\";\nimport { consola, LogLevels } from \"consola\";\nimport { createMcpServer } from \"../mcp.ts\";\n\nexport default defineCommand({\n meta: {\n name: \"mcp\",\n description: \"Run the harnesses MCP server over stdio\",\n },\n /**\n * stdout carries the JSON-RPC frames. consola's default reporter sends\n * anything at log level or below to that same descriptor, and `DEBUG` in the\n * environment raises the level on import, so one stray line would corrupt\n * the stream. Warnings and errors still reach stderr.\n */\n async run() {\n consola.level = LogLevels.warn;\n\n await createMcpServer().connect(new StdioServerTransport());\n },\n});\n"],"mappings":";;;;AAKA,IAAA,cAAe,cAAc;CAC3B,MAAM;EACJ,MAAM;EACN,aAAa;CACf;;;;;;AAQE,SAAA,eAAgB"}