mtech-ai-local-runner 0.1.25 → 0.1.27

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.
package/README.md CHANGED
@@ -10,6 +10,24 @@ The package stores safe runner configuration locally and keeps OAuth refresh cre
10
10
 
11
11
  Public npm installation requires no npm account or publishing OTP. Setup requires Gateway OAuth login, and local Claude Code must already be authenticated (`claude auth status`).
12
12
 
13
+ ## 0.1.27: explicit job MCP exposure
14
+
15
+ The job-local MCP omits deferred and Code Mode surfaces so the inspected ordinary
16
+ MCP path keeps its tools directly available. Server authentication, enabled tools
17
+ and scoped read-only/private-path checks remain unchanged. Exact-binary no-model
18
+ inspection accepted the configuration and registered four synthetic tools;
19
+ actual model selection and document reads still require separate acceptance.
20
+ Foreground installation does not run Codex or update the daemon.
21
+
22
+ ## 0.1.26: compiled private-file denial compatibility
23
+
24
+ The compiled `.env.*` blocked-path rule now denies that private-file family
25
+ without rejecting unrelated scoped reads. Private files at all depths,
26
+ read-only access and unsupported-pattern rejection remain enforced. Shared
27
+ Claude/Codex policy and actual MCP fake-data regressions use the full compiled
28
+ path combination. Foreground installation does not rerun Codex or update the
29
+ daemon; actual document acceptance still requires a separately approved run.
30
+
13
31
  ## 0.1.25: scoped reads and blocked diagnostics
14
32
 
15
33
  Codex receives explicit file-scoped ai_local read/search guidance; launch and
@@ -6,6 +6,9 @@
6
6
  export const CODEX_ISOLATION_EXEC_FLAGS = Object.freeze([
7
7
  "--ignore-user-config", "--ignore-rules", "--ephemeral", "--strict-config", "--json",
8
8
  ]);
9
+ // Job MCP tools must be in the initial model catalog, not discoverable only via
10
+ // tool_search or nested Code Mode. This changes exposure, not tool authority.
11
+ export const CODEX_JOB_MCP_OMIT_SURFACES = Object.freeze(['code_mode', 'deferred']);
9
12
  export const CODEX_ISOLATION_CONFIG_OVERRIDES = Object.freeze([
10
13
  "analytics.enabled=false",
11
14
  "project_doc_max_bytes=0",
@@ -1,5 +1,5 @@
1
1
  import { isAbsolute } from "node:path";
2
- import { CODEX_ISOLATION_EXEC_FLAGS, CODEX_ISOLATION_CONFIG_OVERRIDES } from "./compatibility.js";
2
+ import { CODEX_ISOLATION_EXEC_FLAGS, CODEX_ISOLATION_CONFIG_OVERRIDES, CODEX_JOB_MCP_OMIT_SURFACES } from "./compatibility.js";
3
3
  import { codexControlDenials } from './scratch.js';
4
4
  /** Candidate invocation; execution still requires independent compatibility admission. */
5
5
  export function buildCodexInvocation(input) {
@@ -22,7 +22,7 @@ export function buildCodexInvocation(input) {
22
22
  `permissions.local_job.filesystem={":minimal"="read",${deniedFiles}}`,
23
23
  "permissions.local_job.network.enabled=false",
24
24
  // Only this job server is preauthorized: every operation is checked by Runner policy.
25
- `mcp_servers={ai_local={url=${JSON.stringify(endpoint.toString())},bearer_token_env_var="AI_GATEWAY_JOB_MCP_TOKEN",required=true,default_tools_approval_mode="approve",enabled_tools=["local_read","local_search","local_edit","local_test"]}}`,
25
+ `mcp_servers={ai_local={url=${JSON.stringify(endpoint.toString())},bearer_token_env_var="AI_GATEWAY_JOB_MCP_TOKEN",required=true,default_tools_approval_mode="approve",enabled_tools=["local_read","local_search","local_edit","local_test"],omit_tools_from=${JSON.stringify(CODEX_JOB_MCP_OMIT_SURFACES)}}}`,
26
26
  ];
27
27
  const env = { HOME: input.env.HOME, AI_GATEWAY_JOB_MCP_TOKEN: input.endpoint.token };
28
28
  for (const key of ["PATH", "USER", "LOGNAME", "LANG", "LC_ALL", "TMPDIR", "CODEX_HOME"]) {
@@ -1,6 +1,6 @@
1
1
  import { createHash } from 'node:crypto';
2
2
  import { record } from './stdio-rpc.js';
3
- import { CODEX_ISOLATION_CONFIG_OVERRIDES } from './compatibility.js';
3
+ import { CODEX_ISOLATION_CONFIG_OVERRIDES, CODEX_JOB_MCP_OMIT_SURFACES } from './compatibility.js';
4
4
  import { localToolInventory } from '../../safety/local-tool-server.js';
5
5
  import { codexControlDenials } from './scratch.js';
6
6
  export const canonical = (value) => JSON.stringify(normalize(value));
@@ -102,10 +102,11 @@ export function validateNativeConfig(value, requirements, pin) {
102
102
  const server = record(servers.ai_local);
103
103
  if (server.url !== pin.endpoint || server.bearer_token_env_var !== 'AI_GATEWAY_JOB_MCP_TOKEN'
104
104
  || server.required !== true || server.enabled !== true || server.default_tools_approval_mode !== 'approve'
105
+ || canonical(server.omit_tools_from) !== canonical(CODEX_JOB_MCP_OMIT_SURFACES)
105
106
  || canonical([...(server.enabled_tools ?? [])].sort()) !== canonical(localToolInventory().map(t => t.name).sort()))
106
107
  reject();
107
108
  for (const k of Object.keys(server))
108
- if (!['url', 'bearer_token_env_var', 'environment_id', 'enabled', 'required', 'tool_timeout_sec', 'default_tools_approval_mode', 'enabled_tools'].includes(k))
109
+ if (!['url', 'bearer_token_env_var', 'environment_id', 'enabled', 'required', 'tool_timeout_sec', 'default_tools_approval_mode', 'enabled_tools', 'omit_tools_from'].includes(k))
109
110
  reject();
110
111
  return fingerprint({ config: c, requirements: req, ...(pin.accountSources ? { accountSources: pin.accountSources.fingerprint } : {}) });
111
112
  }
@@ -15,6 +15,13 @@ function scopePath(root, scope) {
15
15
  throw new Error('DENIED_UNSUPPORTED_SCOPE');
16
16
  return path.resolve(root, directory);
17
17
  }
18
+ function blockedScopeContains(root, scope, file) {
19
+ // The compiled repository policy includes this private-file family. Handle
20
+ // it only as a denial, never as a wildcard grant or an ignored block.
21
+ if (scope === '.env.*')
22
+ return file.startsWith(path.join(root, '.env.'));
23
+ return contains(scopePath(root, scope), file);
24
+ }
18
25
  export class LocalToolPolicy {
19
26
  options;
20
27
  abort = new AbortController();
@@ -51,7 +58,7 @@ export class LocalToolPolicy {
51
58
  if (typeof value !== 'string' || !value || value.includes('\0') || path.isAbsolute(value) || value.split(/[\\/]/).some(p => p === '..' || sensitive(p)))
52
59
  throw new Error('DENIED_PATH');
53
60
  const root = path.resolve(this.options.worktreeRoot), file = path.resolve(root, value);
54
- if (!contains(root, file) || !this.options.allowedPaths.some(scope => contains(scopePath(root, scope), file)) || this.options.blockedPaths.some(scope => contains(scopePath(root, scope), file)))
61
+ if (!contains(root, file) || !this.options.allowedPaths.some(scope => contains(scopePath(root, scope), file)) || this.options.blockedPaths.some(scope => blockedScopeContains(root, scope, file)))
55
62
  throw new Error('DENIED_SCOPE');
56
63
  if (await realpath(root) !== root)
57
64
  throw new Error('DENIED_ROOT');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mtech-ai-local-runner",
3
- "version": "0.1.25",
3
+ "version": "0.1.27",
4
4
  "private": false,
5
5
  "license": "UNLICENSED",
6
6
  "bin": {