@sammorrowdrums/mcpi-ext 0.2.0 → 1.0.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 (145) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +137 -47
  3. package/dist/code-mode/eligibility.d.ts +35 -0
  4. package/dist/code-mode/eligibility.js +64 -0
  5. package/dist/code-mode/executor.d.ts +53 -0
  6. package/dist/code-mode/executor.js +185 -0
  7. package/dist/code-mode/index.d.ts +107 -0
  8. package/dist/code-mode/index.js +267 -0
  9. package/dist/code-mode/isolated-vm.d.ts +65 -0
  10. package/dist/code-mode/isolated-vm.js +116 -0
  11. package/dist/code-mode/tools.d.ts +48 -0
  12. package/dist/code-mode/tools.js +73 -0
  13. package/dist/code-mode/type-hints.d.ts +20 -0
  14. package/dist/code-mode/type-hints.js +205 -0
  15. package/dist/docker-e2e.d.ts +54 -0
  16. package/dist/docker-e2e.js +234 -0
  17. package/dist/index.d.ts +11 -0
  18. package/dist/index.js +227 -0
  19. package/dist/mcp/call-tool-result.d.ts +10 -0
  20. package/dist/mcp/call-tool-result.js +70 -0
  21. package/dist/mcp/client-factory.d.ts +59 -0
  22. package/dist/mcp/client-factory.js +76 -0
  23. package/dist/mcp/client-manager.d.ts +88 -0
  24. package/dist/mcp/client-manager.js +307 -0
  25. package/dist/mcp/config-loader.d.ts +7 -0
  26. package/dist/mcp/config-loader.js +42 -0
  27. package/dist/mcp/config.d.ts +56 -0
  28. package/dist/mcp/config.js +42 -0
  29. package/dist/mcp/gateway-defaults.d.ts +14 -0
  30. package/dist/mcp/gateway-defaults.js +21 -0
  31. package/dist/mcp/host-approval.d.ts +16 -0
  32. package/dist/mcp/host-approval.js +19 -0
  33. package/dist/mcp/host-elicitation.d.ts +19 -0
  34. package/dist/mcp/host-elicitation.js +94 -0
  35. package/dist/mcp/index.d.ts +8 -0
  36. package/dist/mcp/index.js +7 -0
  37. package/dist/mcp/policy.d.ts +314 -0
  38. package/dist/mcp/policy.js +848 -0
  39. package/dist/routing/facilities.d.ts +109 -0
  40. package/dist/routing/facilities.js +175 -0
  41. package/dist/routing/format.d.ts +24 -0
  42. package/dist/routing/format.js +64 -0
  43. package/dist/routing/index.d.ts +4 -0
  44. package/dist/routing/index.js +4 -0
  45. package/dist/routing/seam.d.ts +35 -0
  46. package/dist/routing/seam.js +28 -0
  47. package/dist/routing/tripwire.d.ts +42 -0
  48. package/dist/routing/tripwire.js +80 -0
  49. package/dist/skills/discover.d.ts +12 -0
  50. package/dist/skills/discover.js +85 -0
  51. package/dist/skills/format.d.ts +13 -0
  52. package/dist/skills/format.js +42 -0
  53. package/dist/skills/index.d.ts +6 -0
  54. package/dist/skills/index.js +6 -0
  55. package/dist/skills/load-skill-tool.d.ts +56 -0
  56. package/dist/skills/load-skill-tool.js +183 -0
  57. package/dist/skills/mcp-tool-proxy.d.ts +7 -0
  58. package/dist/skills/mcp-tool-proxy.js +41 -0
  59. package/dist/skills/sep2640/client.d.ts +91 -0
  60. package/dist/skills/sep2640/client.js +160 -0
  61. package/dist/skills/sep2640/discover.d.ts +33 -0
  62. package/dist/skills/sep2640/discover.js +82 -0
  63. package/dist/skills/sep2640/index.d.ts +13 -0
  64. package/dist/skills/sep2640/index.js +13 -0
  65. package/dist/skills/sep2640/integrity.d.ts +58 -0
  66. package/dist/skills/sep2640/integrity.js +185 -0
  67. package/dist/skills/sep2640/load.d.ts +61 -0
  68. package/dist/skills/sep2640/load.js +90 -0
  69. package/dist/skills/sep2640/protocol.d.ts +146 -0
  70. package/dist/skills/sep2640/protocol.js +214 -0
  71. package/dist/skills/sep2640/spec.d.ts +76 -0
  72. package/dist/skills/sep2640/spec.js +88 -0
  73. package/dist/skills/skill-registry.d.ts +85 -0
  74. package/dist/skills/skill-registry.js +85 -0
  75. package/dist/tool-cli/bridge.d.ts +14 -0
  76. package/dist/tool-cli/bridge.js +196 -0
  77. package/dist/tool-cli/format.d.ts +20 -0
  78. package/dist/tool-cli/format.js +73 -0
  79. package/dist/tool-cli/index.d.ts +7 -0
  80. package/dist/tool-cli/index.js +5 -0
  81. package/dist/tool-cli/provider.d.ts +21 -0
  82. package/dist/tool-cli/provider.js +114 -0
  83. package/dist/tool-cli/startup.d.ts +22 -0
  84. package/dist/tool-cli/startup.js +47 -0
  85. package/package.json +78 -26
  86. package/.dockerignore +0 -6
  87. package/.github/dependabot.yml +0 -25
  88. package/.github/workflows/ci.yml +0 -50
  89. package/.hadolint.yaml +0 -6
  90. package/.prettierignore +0 -4
  91. package/.prettierrc +0 -7
  92. package/AGENTS.md +0 -118
  93. package/DECISIONS.md +0 -99
  94. package/Dockerfile +0 -39
  95. package/aube-lock.yaml +0 -4146
  96. package/docs/code-mode.md +0 -38
  97. package/docs/skills.md +0 -57
  98. package/docs/tool-cli.md +0 -54
  99. package/eslint.config.js +0 -27
  100. package/images/banner.webp +0 -0
  101. package/images/code-c-maude.webp +0 -0
  102. package/images/code-mode-histogram.png +0 -0
  103. package/images/nuclear-mcp-football.webp +0 -0
  104. package/images/skills-enabling-mcp-tools.png +0 -0
  105. package/images/the-skill-dealer.webp +0 -0
  106. package/images/tool-cli-grep.png +0 -0
  107. package/mise.toml +0 -53
  108. package/scripts/docker-entrypoint.sh +0 -29
  109. package/scripts/test-echo-server.mjs +0 -28
  110. package/scripts/test-mcp-integration.mjs +0 -59
  111. package/scripts/test-tool-cli-smoke.mjs +0 -93
  112. package/src/code-mode/code-mode-integration.test.ts +0 -135
  113. package/src/code-mode/eligibility.test.ts +0 -60
  114. package/src/code-mode/eligibility.ts +0 -16
  115. package/src/code-mode/executor.test.ts +0 -168
  116. package/src/code-mode/executor.ts +0 -163
  117. package/src/code-mode/index.ts +0 -208
  118. package/src/code-mode/tools.ts +0 -110
  119. package/src/code-mode/type-hints.test.ts +0 -187
  120. package/src/code-mode/type-hints.ts +0 -249
  121. package/src/docker-e2e.test.ts +0 -195
  122. package/src/docker-e2e.ts +0 -277
  123. package/src/index.test.ts +0 -8
  124. package/src/index.ts +0 -179
  125. package/src/mcp/client-manager.test.ts +0 -250
  126. package/src/mcp/client-manager.ts +0 -199
  127. package/src/mcp/config-loader.test.ts +0 -115
  128. package/src/mcp/config-loader.ts +0 -46
  129. package/src/mcp/config.ts +0 -40
  130. package/src/mcp/index.ts +0 -4
  131. package/src/skills/discover.ts +0 -81
  132. package/src/skills/format.test.ts +0 -70
  133. package/src/skills/format.ts +0 -42
  134. package/src/skills/index.ts +0 -9
  135. package/src/skills/load-skill-tool.ts +0 -136
  136. package/src/skills/mcp-tool-proxy.ts +0 -143
  137. package/src/skills/skill-integration.test.ts +0 -139
  138. package/src/skills/skill-registry.test.ts +0 -83
  139. package/src/skills/skill-registry.ts +0 -69
  140. package/src/test-servers/weather-server.ts +0 -171
  141. package/src/test-servers/weather-stdio.ts +0 -11
  142. package/src/tool-cli/format.test.ts +0 -33
  143. package/src/tool-cli/format.ts +0 -56
  144. package/src/tool-cli/index.ts +0 -3
  145. package/tsconfig.json +0 -21
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Lazy adapter for the optional `isolated-vm` native addon.
3
+ *
4
+ * Code Mode is the only feature that needs a real V8 isolate. `isolated-vm` is a
5
+ * native addon, so it can legitimately be absent: an unsupported platform, a
6
+ * missing prebuild with no toolchain to compile one, or a deliberate
7
+ * `--omit=optional` install. When that happens Code Mode must report itself
8
+ * unavailable and the rest of the extension — skills, tool-cli, routing — must
9
+ * keep working.
10
+ *
11
+ * Two rules govern this module:
12
+ *
13
+ * 1. **No top-level import.** The specifier is resolved through a dynamic
14
+ * `import()` behind a variable so neither Node nor TypeScript binds to the
15
+ * addon at module-evaluation time. Loading the extension never throws
16
+ * because a native addon is missing.
17
+ * 2. **Never fall back to Node's `vm`.** `node:vm` shares the host heap and
18
+ * realm; it is a code-organisation tool, not a security boundary. Silently
19
+ * downgrading to it would turn "sandboxed execution" into a false claim.
20
+ * If the addon is absent, Code Mode is unavailable — full stop.
21
+ */
22
+ /**
23
+ * Resolved through a variable rather than a string literal so that TypeScript
24
+ * does not require `isolated-vm` types to be present to build, and so no
25
+ * bundler or loader statically links the addon into the module graph.
26
+ */
27
+ const ISOLATED_VM_SPECIFIER = "isolated-vm";
28
+ const MISSING_MODULE_CODES = new Set([
29
+ "ERR_MODULE_NOT_FOUND",
30
+ "MODULE_NOT_FOUND",
31
+ "ERR_DLOPEN_FAILED",
32
+ ]);
33
+ let cached;
34
+ let inFlight;
35
+ /**
36
+ * Load the native addon at most once per process.
37
+ *
38
+ * The result — success *or* failure — is cached. A failed load is not retried:
39
+ * a missing or broken native addon does not become present later in the same
40
+ * process, and retrying would repeat the cost on every Code Mode call.
41
+ */
42
+ export async function loadIsolatedVm() {
43
+ if (cached)
44
+ return cached;
45
+ inFlight ??= attemptLoad().then((result) => {
46
+ cached = result;
47
+ inFlight = undefined;
48
+ return result;
49
+ });
50
+ return inFlight;
51
+ }
52
+ /** Synchronously report a previously resolved load, if one has happened. */
53
+ export function peekIsolatedVm() {
54
+ return cached;
55
+ }
56
+ /** Reset the memoised state. Test-only. */
57
+ export function resetIsolatedVmCacheForTests() {
58
+ cached = undefined;
59
+ inFlight = undefined;
60
+ }
61
+ /** Seed the memoised state. Test-only — lets tests simulate an absent addon. */
62
+ export function setIsolatedVmForTests(load) {
63
+ cached = load;
64
+ inFlight = undefined;
65
+ }
66
+ async function attemptLoad() {
67
+ try {
68
+ const imported = await import(ISOLATED_VM_SPECIFIER);
69
+ const candidate = unwrapDefault(imported);
70
+ if (!isIsolatedVmModule(candidate)) {
71
+ return {
72
+ available: false,
73
+ reason: "the optional isolated-vm addon loaded but did not expose the expected Isolate/Callback/Reference API",
74
+ };
75
+ }
76
+ return { available: true, module: candidate };
77
+ }
78
+ catch (error) {
79
+ return { available: false, reason: describeLoadFailure(error) };
80
+ }
81
+ }
82
+ function unwrapDefault(imported) {
83
+ if (typeof imported !== "object" || imported === null)
84
+ return imported;
85
+ const namespace = imported;
86
+ return namespace.default ?? imported;
87
+ }
88
+ function isIsolatedVmModule(value) {
89
+ if (typeof value !== "object" || value === null)
90
+ return false;
91
+ const candidate = value;
92
+ return (typeof candidate.Isolate === "function" &&
93
+ typeof candidate.Callback === "function" &&
94
+ typeof candidate.Reference === "function");
95
+ }
96
+ /**
97
+ * Turn a load failure into a reason a human can act on.
98
+ *
99
+ * The two failure shapes are meaningfully different: "not installed" is fixed
100
+ * by installing it, while "installed but failed to load" points at an ABI or
101
+ * platform mismatch and needs a rebuild.
102
+ */
103
+ function describeLoadFailure(error) {
104
+ const code = getErrorCode(error);
105
+ const detail = error instanceof Error ? error.message : String(error);
106
+ if (code && MISSING_MODULE_CODES.has(code)) {
107
+ return `the optional isolated-vm native addon is not installed or failed to load (${code}: ${detail})`;
108
+ }
109
+ return `the optional isolated-vm native addon could not be loaded (${detail})`;
110
+ }
111
+ function getErrorCode(error) {
112
+ if (typeof error !== "object" || error === null)
113
+ return undefined;
114
+ const code = error.code;
115
+ return typeof code === "string" ? code : undefined;
116
+ }
@@ -0,0 +1,48 @@
1
+ import type { AgentToolResult, ExtensionContext } from "@sammorrowdrums/mcpi";
2
+ import { Type, type Static } from "typebox";
3
+ import type { CodeModeManager } from "./index.js";
4
+ declare const CodeInput: Type.TObject<{
5
+ code: Type.TString;
6
+ }>;
7
+ type CodeInputType = Static<typeof CodeInput>;
8
+ export interface CodeModeToolDetails {
9
+ executionMs: number;
10
+ logs: string[];
11
+ error?: string;
12
+ message?: string;
13
+ alternatives?: string[];
14
+ toolName?: string;
15
+ reason?: string;
16
+ }
17
+ /**
18
+ * Create the `code_search` tool for querying the tool catalog.
19
+ *
20
+ * The model writes JavaScript to discover and filter available tools.
21
+ * The sandbox provides `codemode.listTools()` and `codemode.describeTools(names)`.
22
+ */
23
+ export declare function createCodeSearchTool(manager: CodeModeManager): {
24
+ name: string;
25
+ label: string;
26
+ description: string;
27
+ parameters: Type.TObject<{
28
+ code: Type.TString;
29
+ }>;
30
+ execute(_toolCallId: string, params: CodeInputType, _signal: AbortSignal | undefined, _onUpdate: undefined, _ctx: ExtensionContext): Promise<AgentToolResult<CodeModeToolDetails>>;
31
+ };
32
+ /**
33
+ * Create the `code_execute` tool for chaining tool calls.
34
+ *
35
+ * The model writes JavaScript that calls read-only MCP tools via
36
+ * the `codemode` namespace (e.g. `codemode.search_docs({ query: 'test' })`).
37
+ * Code runs in a sandbox with no access to filesystem, network, or Node.js APIs.
38
+ */
39
+ export declare function createCodeExecuteTool(manager: CodeModeManager): {
40
+ name: string;
41
+ label: string;
42
+ description: string;
43
+ parameters: Type.TObject<{
44
+ code: Type.TString;
45
+ }>;
46
+ execute(_toolCallId: string, params: CodeInputType, _signal: AbortSignal | undefined, _onUpdate: undefined, _ctx: ExtensionContext): Promise<AgentToolResult<CodeModeToolDetails>>;
47
+ };
48
+ export {};
@@ -0,0 +1,73 @@
1
+ import { Type } from "typebox";
2
+ const CodeInput = Type.Object({
3
+ code: Type.String({
4
+ description: "JavaScript code to execute. Use the `codemode` namespace to call tools (e.g. `codemode.search_docs({ query: 'test' })`). Always `return` your final result. Code runs in a sandbox with no access to filesystem, network, or Node.js APIs.",
5
+ }),
6
+ });
7
+ /** Format an ExecuteResult into a tool response. Falls back to logs if result is undefined. */
8
+ function formatResult(result, executionMs, errorPrefix) {
9
+ if (result.error) {
10
+ const errorDetails = result.errorDetails ?? {
11
+ error: "execution_failed",
12
+ message: result.error,
13
+ };
14
+ return {
15
+ content: [{ type: "text", text: `${errorPrefix}: ${result.error}` }],
16
+ details: { executionMs, logs: result.logs, ...errorDetails },
17
+ };
18
+ }
19
+ // If code didn't return a value, fall back to captured console output
20
+ const output = result.result !== undefined
21
+ ? typeof result.result === "string"
22
+ ? result.result
23
+ : JSON.stringify(result.result, null, 2)
24
+ : result.logs.length > 0
25
+ ? result.logs.join("\n")
26
+ : "(no return value)";
27
+ const logsSection = result.result !== undefined && result.logs.length > 0
28
+ ? `\n\nLogs:\n${result.logs.join("\n")}`
29
+ : "";
30
+ return {
31
+ content: [{ type: "text", text: output + logsSection }],
32
+ details: { executionMs, logs: result.logs },
33
+ };
34
+ }
35
+ /**
36
+ * Create the `code_search` tool for querying the tool catalog.
37
+ *
38
+ * The model writes JavaScript to discover and filter available tools.
39
+ * The sandbox provides `codemode.listTools()` and `codemode.describeTools(names)`.
40
+ */
41
+ export function createCodeSearchTool(manager) {
42
+ return {
43
+ name: "code_search",
44
+ label: "Code Search",
45
+ description: "Discover all available MCP tools by writing JavaScript. Use `codemode.listTools()` to list tools and `codemode.describeTools(names)` for type info. Type hints identify which tools Code Mode can call; non-read-only tools remain discovery-only.",
46
+ parameters: CodeInput,
47
+ async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
48
+ const start = performance.now();
49
+ const result = await manager.searchTools(params.code);
50
+ return formatResult(result, Math.round(performance.now() - start), "Code search error");
51
+ },
52
+ };
53
+ }
54
+ /**
55
+ * Create the `code_execute` tool for chaining tool calls.
56
+ *
57
+ * The model writes JavaScript that calls read-only MCP tools via
58
+ * the `codemode` namespace (e.g. `codemode.search_docs({ query: 'test' })`).
59
+ * Code runs in a sandbox with no access to filesystem, network, or Node.js APIs.
60
+ */
61
+ export function createCodeExecuteTool(manager) {
62
+ return {
63
+ name: "code_execute",
64
+ label: "Code Execute",
65
+ description: "Execute JavaScript that chains read-only MCP tool calls for computation over data. Use when you need to aggregate, filter, loop, or transform results across multiple tool calls. Access tools via `codemode.toolName(args)`. Runs in a sandbox — no filesystem, network, or Node.js API access.",
66
+ parameters: CodeInput,
67
+ async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
68
+ const start = performance.now();
69
+ const result = await manager.executeCode(params.code);
70
+ return formatResult(result, Math.round(performance.now() - start), "Code execution error");
71
+ },
72
+ };
73
+ }
@@ -0,0 +1,20 @@
1
+ import type { McpTool } from "../mcp/index.js";
2
+ import { type CodeModeTool } from "./eligibility.js";
3
+ type JsonSchema = Record<string, unknown>;
4
+ /**
5
+ * Convert a JSON Schema to a TypeScript type string.
6
+ *
7
+ * Handles objects, arrays, primitives, enums, anyOf/oneOf/allOf, $ref,
8
+ * and circular references (depth guard at 20).
9
+ */
10
+ export declare function jsonSchemaToTypeString(schema: JsonSchema, definitions?: Record<string, JsonSchema>, depth?: number, seen?: Set<JsonSchema>): string;
11
+ /** Sanitize a tool name to be a valid JS identifier. */
12
+ export declare function sanitizeToolName(name: string): string;
13
+ /**
14
+ * Generate TypeScript type declarations for a set of MCP tools.
15
+ *
16
+ * Produces a `declare const codemode: { ... }` block with type-safe
17
+ * method signatures the model can use when writing code.
18
+ */
19
+ export declare function generateTypeHints(tools: readonly (McpTool | CodeModeTool)[]): string;
20
+ export {};
@@ -0,0 +1,205 @@
1
+ import { getCodeModeDiagnostics, toCodeModeTool } from "./eligibility.js";
2
+ /**
3
+ * Convert a JSON Schema to a TypeScript type string.
4
+ *
5
+ * Handles objects, arrays, primitives, enums, anyOf/oneOf/allOf, $ref,
6
+ * and circular references (depth guard at 20).
7
+ */
8
+ export function jsonSchemaToTypeString(schema, definitions, depth = 0, seen = new Set()) {
9
+ if (depth > 20)
10
+ return "unknown";
11
+ if (seen.has(schema))
12
+ return "unknown";
13
+ seen.add(schema);
14
+ // Handle $ref
15
+ if (typeof schema.$ref === "string") {
16
+ const refPath = schema.$ref;
17
+ const refName = refPath.replace(/^#\/(definitions|components\/schemas|\\$defs)\//, "");
18
+ const resolved = definitions?.[refName];
19
+ if (resolved) {
20
+ return jsonSchemaToTypeString(resolved, definitions, depth + 1, seen);
21
+ }
22
+ return "unknown";
23
+ }
24
+ // Handle enum
25
+ if (Array.isArray(schema.enum)) {
26
+ return schema.enum.map((v) => JSON.stringify(v)).join(" | ");
27
+ }
28
+ // Handle const
29
+ if ("const" in schema) {
30
+ return JSON.stringify(schema.const);
31
+ }
32
+ // Handle anyOf / oneOf
33
+ const unionKey = schema.anyOf ? "anyOf" : schema.oneOf ? "oneOf" : null;
34
+ if (unionKey && Array.isArray(schema[unionKey])) {
35
+ const variants = schema[unionKey].map((s) => jsonSchemaToTypeString(s, definitions, depth + 1, seen));
36
+ return variants.join(" | ");
37
+ }
38
+ // Handle allOf
39
+ if (Array.isArray(schema.allOf)) {
40
+ const parts = schema.allOf.map((s) => jsonSchemaToTypeString(s, definitions, depth + 1, seen));
41
+ return parts.join(" & ");
42
+ }
43
+ const type = schema.type;
44
+ // Handle type arrays (e.g. ["string", "null"])
45
+ if (Array.isArray(type)) {
46
+ const types = type.map((t) => primitiveToTs(t));
47
+ return types.join(" | ");
48
+ }
49
+ switch (type) {
50
+ case "object":
51
+ return objectToTs(schema, definitions, depth, seen);
52
+ case "array":
53
+ return arrayToTs(schema, definitions, depth, seen);
54
+ case "string":
55
+ case "number":
56
+ case "integer":
57
+ case "boolean":
58
+ case "null":
59
+ return primitiveToTs(type);
60
+ default:
61
+ // No type specified — try to infer from properties
62
+ if (schema.properties) {
63
+ return objectToTs(schema, definitions, depth, seen);
64
+ }
65
+ return "unknown";
66
+ }
67
+ }
68
+ function primitiveToTs(type) {
69
+ switch (type) {
70
+ case "string":
71
+ return "string";
72
+ case "number":
73
+ case "integer":
74
+ return "number";
75
+ case "boolean":
76
+ return "boolean";
77
+ case "null":
78
+ return "null";
79
+ default:
80
+ return "unknown";
81
+ }
82
+ }
83
+ function objectToTs(schema, definitions, depth, seen) {
84
+ const properties = schema.properties;
85
+ if (!properties || Object.keys(properties).length === 0) {
86
+ return "Record<string, unknown>";
87
+ }
88
+ const required = new Set(Array.isArray(schema.required) ? schema.required : []);
89
+ const lines = [];
90
+ for (const [key, propSchema] of Object.entries(properties)) {
91
+ const desc = propSchema.description;
92
+ if (desc) {
93
+ lines.push(` /** ${desc} */`);
94
+ }
95
+ const optional = required.has(key) ? "" : "?";
96
+ const typeStr = jsonSchemaToTypeString(propSchema, definitions, depth + 1, seen);
97
+ lines.push(` ${safeName(key)}${optional}: ${typeStr};`);
98
+ }
99
+ return `{\n${lines.join("\n")}\n}`;
100
+ }
101
+ function arrayToTs(schema, definitions, depth, seen) {
102
+ const items = schema.items;
103
+ if (!items)
104
+ return "unknown[]";
105
+ // Tuple form
106
+ if (Array.isArray(items)) {
107
+ const tupleTypes = items.map((s) => jsonSchemaToTypeString(s, definitions, depth + 1, seen));
108
+ return `[${tupleTypes.join(", ")}]`;
109
+ }
110
+ const itemType = jsonSchemaToTypeString(items, definitions, depth + 1, seen);
111
+ return `${itemType}[]`;
112
+ }
113
+ /** Ensure property name is a valid JS identifier, quote otherwise. */
114
+ function safeName(name) {
115
+ return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name) ? name : JSON.stringify(name);
116
+ }
117
+ /** Sanitize a tool name to be a valid JS identifier. */
118
+ export function sanitizeToolName(name) {
119
+ return name.replace(/[^a-zA-Z0-9_$]/g, "_");
120
+ }
121
+ /**
122
+ * Generate TypeScript type declarations for a set of MCP tools.
123
+ *
124
+ * Produces a `declare const codemode: { ... }` block with type-safe
125
+ * method signatures the model can use when writing code.
126
+ */
127
+ export function generateTypeHints(tools) {
128
+ const codeModeTools = tools.map(normalizeCodeModeTool);
129
+ const diagnostics = getCodeModeDiagnostics(codeModeTools);
130
+ const methods = [];
131
+ for (const codeModeTool of codeModeTools) {
132
+ const tool = codeModeTool.tool;
133
+ const safeName = sanitizeToolName(tool.name);
134
+ const inputSchema = tool.inputSchema;
135
+ const outputSchema = codeModeTool.outputSchema;
136
+ const definitions = (inputSchema.$defs ??
137
+ inputSchema.definitions ??
138
+ outputSchema?.$defs ??
139
+ outputSchema?.definitions);
140
+ // Build input type
141
+ const inputType = generateInputType(safeName, inputSchema, definitions);
142
+ // Build output type
143
+ const outputType = outputSchema ? jsonSchemaToTypeString(outputSchema, definitions) : "unknown";
144
+ // Build JSDoc
145
+ const jsdoc = buildJsDoc(codeModeTool, inputSchema);
146
+ methods.push(`${jsdoc} ${safeName}: (input: ${inputType}) => Promise<${outputType}>;`);
147
+ }
148
+ const toolListType = codeModeTools.length > 0
149
+ ? codeModeTools.map((entry) => `"${escapeStr(entry.tool.name)}"`).join(" | ")
150
+ : "never";
151
+ return [
152
+ "// Code mode type hints — auto-generated from MCP tool schemas",
153
+ "// Available tools are accessed via the `codemode` namespace",
154
+ `// MCP catalog: ${diagnostics.totalTools} tool(s); ${diagnostics.callableTools} callable, ${diagnostics.refusedTools} dispatch-refused`,
155
+ `// Output schemas: ${diagnostics.declaredOutputSchemas} declared, ${diagnostics.synthesizedOutputSchemas} synthesized, ${diagnostics.unavailableOutputSchemas} unavailable`,
156
+ "",
157
+ `declare const codemode: {`,
158
+ ` /** List all available code mode tool names. */`,
159
+ ` listTools: () => Promise<(${toolListType})[]>;`,
160
+ ` /** Get full type information for specific tools. */`,
161
+ ` describeTools: (names: string[]) => Promise<string>;`,
162
+ ...methods.map((m) => m),
163
+ `};`,
164
+ ].join("\n");
165
+ }
166
+ function generateInputType(_toolSafeName, inputSchema, definitions) {
167
+ const properties = inputSchema.properties;
168
+ if (!properties || Object.keys(properties).length === 0) {
169
+ return "Record<string, never>";
170
+ }
171
+ // Always inline the type — avoids emitting unreferenced named type aliases
172
+ const typeStr = jsonSchemaToTypeString(inputSchema, definitions);
173
+ return typeStr;
174
+ }
175
+ function buildJsDoc(codeModeTool, inputSchema) {
176
+ const tool = codeModeTool.tool;
177
+ const lines = [" /**"];
178
+ if (tool.description) {
179
+ lines.push(` * ${tool.description}`);
180
+ }
181
+ if (codeModeTool.callable) {
182
+ lines.push(" * Code Mode dispatch: callable (explicitly read-only and non-destructive).");
183
+ }
184
+ else {
185
+ lines.push(" * Code Mode dispatch: refused. Use a permission-aware non-Code-Mode path.");
186
+ }
187
+ lines.push(` * Output schema provenance: ${codeModeTool.outputSchemaProvenance}.`);
188
+ const properties = inputSchema.properties;
189
+ if (properties) {
190
+ for (const [key, propSchema] of Object.entries(properties)) {
191
+ const desc = propSchema.description;
192
+ if (desc) {
193
+ lines.push(` * @param input.${key} - ${desc}`);
194
+ }
195
+ }
196
+ }
197
+ lines.push(" */");
198
+ return lines.join("\n") + "\n";
199
+ }
200
+ function escapeStr(s) {
201
+ return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, " ");
202
+ }
203
+ function normalizeCodeModeTool(tool) {
204
+ return "tool" in tool && "outputSchemaProvenance" in tool ? tool : toCodeModeTool(tool);
205
+ }
@@ -0,0 +1,54 @@
1
+ import type { AgentToolResult, AgentToolUpdateCallback, ExtensionContext } from "@sammorrowdrums/mcpi";
2
+ import { Type, type Static } from "typebox";
3
+ declare const DockerE2EParams: Type.TObject<{
4
+ task: Type.TString;
5
+ token: Type.TString;
6
+ model: Type.TOptional<Type.TString>;
7
+ timeout: Type.TOptional<Type.TNumber>;
8
+ }>;
9
+ type DockerE2EInput = Static<typeof DockerE2EParams>;
10
+ interface RunLogEntry {
11
+ type: string;
12
+ timestamp: number;
13
+ raw: string;
14
+ }
15
+ export interface DockerE2EDetails {
16
+ exitCode: number | null;
17
+ log: RunLogEntry[];
18
+ duration: number;
19
+ model?: string;
20
+ usage?: {
21
+ input: number;
22
+ output: number;
23
+ cacheRead: number;
24
+ cacheWrite: number;
25
+ cost: number;
26
+ turns: number;
27
+ };
28
+ }
29
+ export declare function parseJsonlLine(line: string): RunLogEntry | null;
30
+ export declare function extractAssistantText(log: RunLogEntry[]): string;
31
+ export declare function extractUsage(log: RunLogEntry[]): {
32
+ input: number;
33
+ output: number;
34
+ cacheRead: number;
35
+ cacheWrite: number;
36
+ cost: number;
37
+ turns: number;
38
+ };
39
+ export declare function extractModel(log: RunLogEntry[]): string | undefined;
40
+ export declare function buildDockerArgs(image: string, params: DockerE2EInput): string[];
41
+ export declare const dockerE2ETool: {
42
+ name: string;
43
+ label: string;
44
+ description: string;
45
+ promptSnippet: string;
46
+ parameters: Type.TObject<{
47
+ task: Type.TString;
48
+ token: Type.TString;
49
+ model: Type.TOptional<Type.TString>;
50
+ timeout: Type.TOptional<Type.TNumber>;
51
+ }>;
52
+ execute(_toolCallId: string, params: DockerE2EInput, signal: AbortSignal | undefined, onUpdate: AgentToolUpdateCallback<DockerE2EDetails> | undefined, _ctx: ExtensionContext): Promise<AgentToolResult<DockerE2EDetails>>;
53
+ };
54
+ export {};