@agents-at-scale/ark 0.1.63 → 0.1.64

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.
@@ -0,0 +1,37 @@
1
+ export interface AuthStartResponse {
2
+ auth_id: string;
3
+ authorization_url: string;
4
+ flow_expires_at: string;
5
+ }
6
+ export interface AuthStatusResponse {
7
+ state: 'pending' | 'authorized' | 'failed' | 'expired';
8
+ expires_at?: string | null;
9
+ message?: string | null;
10
+ controller_state?: string | null;
11
+ controller_message?: string | null;
12
+ }
13
+ export interface AuthLogoutResponse {
14
+ cleared_keys?: string[];
15
+ deleted?: boolean;
16
+ noop?: boolean;
17
+ }
18
+ export interface AuthStartBody {
19
+ force?: boolean;
20
+ scope?: string[];
21
+ }
22
+ export interface AuthLogoutBody {
23
+ keep_client?: boolean;
24
+ delete_secret?: boolean;
25
+ }
26
+ export declare class AuthHttpError extends Error {
27
+ status: number;
28
+ body: string;
29
+ constructor(status: number, body: string);
30
+ }
31
+ export declare class McpAuthClient {
32
+ private baseUrl;
33
+ constructor(baseUrl: string);
34
+ start(name: string, namespace: string, body: AuthStartBody): Promise<AuthStartResponse>;
35
+ status(name: string, namespace: string, authId: string): Promise<AuthStatusResponse>;
36
+ logout(name: string, namespace: string, body: AuthLogoutBody): Promise<AuthLogoutResponse>;
37
+ }
@@ -0,0 +1,47 @@
1
+ export class AuthHttpError extends Error {
2
+ status;
3
+ body;
4
+ constructor(status, body) {
5
+ super(`HTTP ${status}: ${body}`);
6
+ this.status = status;
7
+ this.body = body;
8
+ }
9
+ }
10
+ export class McpAuthClient {
11
+ baseUrl;
12
+ constructor(baseUrl) {
13
+ this.baseUrl = baseUrl;
14
+ }
15
+ async start(name, namespace, body) {
16
+ const url = `${this.baseUrl}/v1/mcp-servers/${encodeURIComponent(name)}/auth/start?namespace=${encodeURIComponent(namespace)}`;
17
+ const response = await fetch(url, {
18
+ method: 'POST',
19
+ headers: { 'Content-Type': 'application/json' },
20
+ body: JSON.stringify(body),
21
+ });
22
+ if (!response.ok) {
23
+ throw new AuthHttpError(response.status, await response.text());
24
+ }
25
+ return (await response.json());
26
+ }
27
+ async status(name, namespace, authId) {
28
+ const url = `${this.baseUrl}/v1/mcp-servers/${encodeURIComponent(name)}/auth/status?namespace=${encodeURIComponent(namespace)}&auth_id=${encodeURIComponent(authId)}`;
29
+ const response = await fetch(url);
30
+ if (!response.ok) {
31
+ throw new AuthHttpError(response.status, await response.text());
32
+ }
33
+ return (await response.json());
34
+ }
35
+ async logout(name, namespace, body) {
36
+ const url = `${this.baseUrl}/v1/mcp-servers/${encodeURIComponent(name)}/auth/logout?namespace=${encodeURIComponent(namespace)}`;
37
+ const response = await fetch(url, {
38
+ method: 'POST',
39
+ headers: { 'Content-Type': 'application/json' },
40
+ body: JSON.stringify(body),
41
+ });
42
+ if (!response.ok) {
43
+ throw new AuthHttpError(response.status, await response.text());
44
+ }
45
+ return (await response.json());
46
+ }
47
+ }
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ import type { ArkConfig } from '../../lib/config.js';
3
+ export declare function createMcpCommand(_config: ArkConfig): Command;
@@ -0,0 +1,43 @@
1
+ import { Command } from 'commander';
2
+ import { runLogin } from './login.js';
3
+ import { runLogout } from './logout.js';
4
+ export function createMcpCommand(_config) {
5
+ const mcp = new Command('mcp');
6
+ mcp.description('manage MCP servers');
7
+ const auth = new Command('auth');
8
+ auth.description('manage MCP server authorization');
9
+ auth
10
+ .command('login <server-name>')
11
+ .description('start an OAuth flow for an MCPServer via ark-api')
12
+ .option('-n, --namespace <namespace>', 'namespace of the MCPServer')
13
+ .option('--force', 'bypass the Authorized preflight and force fresh client registration')
14
+ .option('--no-open', 'do not open a browser; print the URL only')
15
+ .option('--timeout <duration>', 'how long to wait for authorization to complete (e.g. 60s, 5m, 1h)')
16
+ .option('--scope <scope>', 'space- or comma-separated list of OAuth scopes to request')
17
+ .action(async (serverName, options) => {
18
+ const exitCode = await runLogin(serverName, {
19
+ namespace: options.namespace,
20
+ force: options.force,
21
+ open: options.open,
22
+ timeout: options.timeout,
23
+ scope: options.scope,
24
+ });
25
+ process.exit(exitCode);
26
+ });
27
+ auth
28
+ .command('logout <server-name>')
29
+ .description('clear OAuth state for an MCPServer via ark-api')
30
+ .option('-n, --namespace <namespace>', 'namespace of the MCPServer')
31
+ .option('--keep-client', 'preserve cached client_id/client_secret in the Secret')
32
+ .option('--delete-secret', 'delete the entire token Secret')
33
+ .action(async (serverName, options) => {
34
+ const exitCode = await runLogout(serverName, {
35
+ namespace: options.namespace,
36
+ keepClient: options.keepClient,
37
+ deleteSecret: options.deleteSecret,
38
+ });
39
+ process.exit(exitCode);
40
+ });
41
+ mcp.addCommand(auth);
42
+ return mcp;
43
+ }
@@ -0,0 +1,21 @@
1
+ import { McpAuthClient } from './authClient.js';
2
+ export interface LoginOptions {
3
+ namespace?: string;
4
+ force?: boolean;
5
+ open?: boolean;
6
+ timeout?: string;
7
+ scope?: string;
8
+ }
9
+ export interface LoginDeps {
10
+ buildClient: (baseUrl: string) => McpAuthClient;
11
+ openBrowser: (url: string) => Promise<unknown>;
12
+ resolveNs: (explicit?: string) => string;
13
+ startProxy: () => Promise<{
14
+ baseUrl: string;
15
+ stop: () => void;
16
+ }>;
17
+ sleep: (ms: number) => Promise<void>;
18
+ now: () => number;
19
+ }
20
+ export declare const defaultDeps: LoginDeps;
21
+ export declare function runLogin(serverName: string, options: LoginOptions, deps?: LoginDeps): Promise<number>;
@@ -0,0 +1,106 @@
1
+ import open from 'open';
2
+ import output from '../../lib/output.js';
3
+ import { ArkApiProxy } from '../../lib/arkApiProxy.js';
4
+ import { loadConfig } from '../../lib/config.js';
5
+ import { resolveNamespace } from './namespace.js';
6
+ import { parseTimeoutDuration } from './timeout.js';
7
+ import { AuthHttpError, McpAuthClient } from './authClient.js';
8
+ export const defaultDeps = {
9
+ buildClient: (baseUrl) => new McpAuthClient(baseUrl),
10
+ openBrowser: (url) => open(url),
11
+ resolveNs: (explicit) => resolveNamespace(explicit),
12
+ startProxy: async () => {
13
+ const config = loadConfig();
14
+ const proxy = new ArkApiProxy(undefined, config.services?.reusePortForwards ?? false);
15
+ const client = await proxy.start();
16
+ return {
17
+ baseUrl: client.getBaseUrl(),
18
+ stop: () => proxy.stop(),
19
+ };
20
+ },
21
+ sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
22
+ now: () => Date.now(),
23
+ };
24
+ const POLL_INTERVAL_MS = 2000;
25
+ const DEFAULT_TIMEOUT_MS = 5 * 60 * 1000;
26
+ export async function runLogin(serverName, options, deps = defaultDeps) {
27
+ let timeoutMs = DEFAULT_TIMEOUT_MS;
28
+ if (options.timeout !== undefined) {
29
+ try {
30
+ timeoutMs = parseTimeoutDuration(options.timeout);
31
+ }
32
+ catch (err) {
33
+ output.error('mcp auth failed:', err.message);
34
+ return 1;
35
+ }
36
+ }
37
+ const namespace = deps.resolveNs(options.namespace);
38
+ const body = {};
39
+ if (options.force)
40
+ body.force = true;
41
+ if (options.scope) {
42
+ body.scope = options.scope.split(/[\s,]+/).filter((s) => s.length > 0);
43
+ }
44
+ const proxy = await deps.startProxy();
45
+ try {
46
+ const client = deps.buildClient(proxy.baseUrl);
47
+ let startResponse;
48
+ try {
49
+ startResponse = await client.start(serverName, namespace, body);
50
+ }
51
+ catch (err) {
52
+ if (err instanceof AuthHttpError) {
53
+ output.error('mcp auth failed:', err.body || `HTTP ${err.status}`);
54
+ }
55
+ else {
56
+ output.error('mcp auth failed:', err.message);
57
+ }
58
+ return 1;
59
+ }
60
+ console.log(`Authorization URL: ${startResponse.authorization_url}`);
61
+ if (options.open !== false) {
62
+ try {
63
+ await deps.openBrowser(startResponse.authorization_url);
64
+ }
65
+ catch {
66
+ // ignore — URL is already printed
67
+ }
68
+ }
69
+ const deadline = deps.now() + timeoutMs;
70
+ while (deps.now() < deadline) {
71
+ let status;
72
+ try {
73
+ status = await client.status(serverName, namespace, startResponse.auth_id);
74
+ }
75
+ catch (err) {
76
+ if (err instanceof AuthHttpError) {
77
+ output.error('mcp auth failed:', err.body || `HTTP ${err.status}`);
78
+ }
79
+ else {
80
+ output.error('mcp auth failed:', err.message);
81
+ }
82
+ return 1;
83
+ }
84
+ if (status.state === 'authorized') {
85
+ if (status.expires_at) {
86
+ output.success(`authorized (token expires at ${status.expires_at})`);
87
+ }
88
+ else {
89
+ output.success('authorized');
90
+ }
91
+ return 0;
92
+ }
93
+ if (status.state === 'failed' || status.state === 'expired') {
94
+ const reason = status.message || status.state;
95
+ output.error('mcp auth failed:', reason);
96
+ return 1;
97
+ }
98
+ await deps.sleep(POLL_INTERVAL_MS);
99
+ }
100
+ output.error('mcp auth failed:', 'timed out waiting for authorization');
101
+ return 1;
102
+ }
103
+ finally {
104
+ proxy.stop();
105
+ }
106
+ }
@@ -0,0 +1,16 @@
1
+ import { McpAuthClient } from './authClient.js';
2
+ export interface LogoutOptions {
3
+ namespace?: string;
4
+ keepClient?: boolean;
5
+ deleteSecret?: boolean;
6
+ }
7
+ export interface LogoutDeps {
8
+ buildClient: (baseUrl: string) => McpAuthClient;
9
+ resolveNs: (explicit?: string) => string;
10
+ startProxy: () => Promise<{
11
+ baseUrl: string;
12
+ stop: () => void;
13
+ }>;
14
+ }
15
+ export declare const defaultLogoutDeps: LogoutDeps;
16
+ export declare function runLogout(serverName: string, options: LogoutOptions, deps?: LogoutDeps): Promise<number>;
@@ -0,0 +1,65 @@
1
+ import output from '../../lib/output.js';
2
+ import { ArkApiProxy } from '../../lib/arkApiProxy.js';
3
+ import { loadConfig } from '../../lib/config.js';
4
+ import { resolveNamespace } from './namespace.js';
5
+ import { AuthHttpError, McpAuthClient } from './authClient.js';
6
+ export const defaultLogoutDeps = {
7
+ buildClient: (baseUrl) => new McpAuthClient(baseUrl),
8
+ resolveNs: (explicit) => resolveNamespace(explicit),
9
+ startProxy: async () => {
10
+ const config = loadConfig();
11
+ const proxy = new ArkApiProxy(undefined, config.services?.reusePortForwards ?? false);
12
+ const client = await proxy.start();
13
+ return {
14
+ baseUrl: client.getBaseUrl(),
15
+ stop: () => proxy.stop(),
16
+ };
17
+ },
18
+ };
19
+ export async function runLogout(serverName, options, deps = defaultLogoutDeps) {
20
+ if (options.keepClient && options.deleteSecret) {
21
+ output.error('mcp auth failed:', '--keep-client and --delete-secret are mutually exclusive');
22
+ return 1;
23
+ }
24
+ const namespace = deps.resolveNs(options.namespace);
25
+ const body = {};
26
+ if (options.keepClient)
27
+ body.keep_client = true;
28
+ if (options.deleteSecret)
29
+ body.delete_secret = true;
30
+ const proxy = await deps.startProxy();
31
+ try {
32
+ const client = deps.buildClient(proxy.baseUrl);
33
+ let response;
34
+ try {
35
+ response = await client.logout(serverName, namespace, body);
36
+ }
37
+ catch (err) {
38
+ if (err instanceof AuthHttpError) {
39
+ if (err.status === 404) {
40
+ output.error('mcp auth failed:', 'MCPServer not found');
41
+ }
42
+ else {
43
+ output.error('mcp auth failed:', err.body || `HTTP ${err.status}`);
44
+ }
45
+ return 1;
46
+ }
47
+ output.error('mcp auth failed:', err.message);
48
+ return 1;
49
+ }
50
+ if (response.noop) {
51
+ output.success('logout: nothing to clear');
52
+ }
53
+ else if (response.deleted) {
54
+ output.success('logout: secret deleted');
55
+ }
56
+ else {
57
+ const keys = response.cleared_keys ?? [];
58
+ output.success(`logout: cleared ${keys.length} key(s)`);
59
+ }
60
+ return 0;
61
+ }
62
+ finally {
63
+ proxy.stop();
64
+ }
65
+ }
@@ -0,0 +1 @@
1
+ export declare function resolveNamespace(explicit?: string): string;
@@ -0,0 +1,17 @@
1
+ import { KubeConfig } from '@kubernetes/client-node';
2
+ export function resolveNamespace(explicit) {
3
+ if (explicit && explicit.length > 0) {
4
+ return explicit;
5
+ }
6
+ const kc = new KubeConfig();
7
+ kc.loadFromDefault();
8
+ const contextName = kc.getCurrentContext();
9
+ if (!contextName) {
10
+ return 'default';
11
+ }
12
+ const ctx = kc.getContextObject(contextName);
13
+ if (ctx && ctx.namespace && ctx.namespace.length > 0) {
14
+ return ctx.namespace;
15
+ }
16
+ return 'default';
17
+ }
@@ -0,0 +1 @@
1
+ export declare function parseTimeoutDuration(input: string): number;
@@ -0,0 +1,23 @@
1
+ export function parseTimeoutDuration(input) {
2
+ const match = input.match(/^(\d+(?:\.\d+)?)(ms|s|m|h)$/);
3
+ if (!match) {
4
+ throw new Error(`invalid --timeout value: ${input} (expected Go duration such as 60s, 5m, 1h)`);
5
+ }
6
+ const value = parseFloat(match[1]);
7
+ if (!isFinite(value) || value <= 0) {
8
+ throw new Error(`invalid --timeout value: ${input} (must be a positive duration)`);
9
+ }
10
+ const unit = match[2];
11
+ switch (unit) {
12
+ case 'ms':
13
+ return value;
14
+ case 's':
15
+ return value * 1000;
16
+ case 'm':
17
+ return value * 60 * 1000;
18
+ case 'h':
19
+ return value * 60 * 60 * 1000;
20
+ default:
21
+ throw new Error(`invalid --timeout unit: ${unit}`);
22
+ }
23
+ }
package/dist/index.js CHANGED
@@ -18,6 +18,7 @@ import { createGenerateCommand } from './commands/generate/index.js';
18
18
  import { createImportCommand } from './commands/import/index.js';
19
19
  import { createInstallCommand } from './commands/install/index.js';
20
20
  import { createMarketplaceCommand } from './commands/marketplace/index.js';
21
+ import { createMcpCommand } from './commands/mcp/index.js';
21
22
  import { createMemoryCommand } from './commands/memory/index.js';
22
23
  import { createModelsCommand } from './commands/models/index.js';
23
24
  import { createQueryCommand } from './commands/query/index.js';
@@ -53,6 +54,7 @@ async function main() {
53
54
  program.addCommand(createImportCommand(config));
54
55
  program.addCommand(createInstallCommand(config));
55
56
  program.addCommand(createMarketplaceCommand(config));
57
+ program.addCommand(createMcpCommand(config));
56
58
  program.addCommand(createMemoryCommand(config));
57
59
  program.addCommand(createModelsCommand(config));
58
60
  program.addCommand(createQueryCommand(config));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agents-at-scale/ark",
3
- "version": "0.1.63",
3
+ "version": "0.1.64",
4
4
  "description": "Ark CLI - Interactive terminal interface for ARK agents",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -72,19 +72,20 @@
72
72
  "@types/react": "^19.1.13",
73
73
  "@typescript-eslint/eslint-plugin": "^8.56.0",
74
74
  "@typescript-eslint/parser": "^8.56.0",
75
- "@vitest/coverage-v8": "^3.2.4",
75
+ "@vitest/coverage-v8": "^4.1.8",
76
76
  "eslint": "^10.0.0",
77
77
  "globals": "^16.2.0",
78
78
  "prettier": "^3.6.2",
79
79
  "ts-node": "^10.9.2",
80
80
  "tsx": "^4.20.5",
81
81
  "typescript": "^5.7.2",
82
- "vitest": "^3.2.4"
82
+ "vitest": "^4.1.8"
83
83
  },
84
84
  "engines": {
85
85
  "node": ">=18.0.0"
86
86
  },
87
87
  "overrides": {
88
+ "brace-expansion": ">=5.0.6",
88
89
  "minimatch": "^10.2.3",
89
90
  "rollup": "4.59.0",
90
91
  "hono": "^4.12.18",
@@ -92,6 +93,7 @@
92
93
  "flatted": "^3.4.2",
93
94
  "tar": "^7.5.11",
94
95
  "express-rate-limit": "^8.3.0",
95
- "fast-uri": "^3.1.1"
96
+ "fast-uri": "^3.1.1",
97
+ "ws": "^8.20.1"
96
98
  }
97
99
  }
@@ -63,7 +63,7 @@ ENTRYPOINT [ "/bin/bash", "-c", "mcp-proxy --debug --port 8080 --host 0.0.0.0 --
63
63
  {{- end }}
64
64
 
65
65
  {{- else if eq .Values.technology "go" }}
66
- FROM golang:latest AS go-builder
66
+ FROM golang:1.26.3-alpine AS go-builder
67
67
  {{- if .Values.packageSource }}
68
68
  {{- if eq .Values.packageSource "go-install" }}
69
69
  RUN go install {{ .Values.packageName }}@latest
@@ -48,14 +48,14 @@ wheels = [
48
48
 
49
49
  [[package]]
50
50
  name = "authlib"
51
- version = "1.6.11"
51
+ version = "1.6.12"
52
52
  source = { registry = "https://pypi.org/simple" }
53
53
  dependencies = [
54
54
  { name = "cryptography" },
55
55
  ]
56
- sdist = { url = "https://files.pythonhosted.org/packages/28/10/b325d58ffe86815b399334a101e63bc6fa4e1953921cb23703b48a0a0220/authlib-1.6.11.tar.gz", hash = "sha256:64db35b9b01aeccb4715a6c9a6613a06f2bd7be2ab9d2eb89edd1dfc7580a38f", size = 165359, upload-time = "2026-04-16T07:22:50.279Z" }
56
+ sdist = { url = "https://files.pythonhosted.org/packages/d3/30/6691fdc63b35f54a5a65e04fa1e59d827f4d4e8f4a39678ba7d3088ce0c8/authlib-1.6.12.tar.gz", hash = "sha256:0656d8482f28fc8221929d5f35b2bde5d13e10555ebc06b4561b0d622e83b1bd", size = 165368, upload-time = "2026-05-04T08:11:31.826Z" }
57
57
  wheels = [
58
- { url = "https://files.pythonhosted.org/packages/57/2f/55fca558f925a51db046e5b929deb317ddb05afed74b22d89f4eca578980/authlib-1.6.11-py2.py3-none-any.whl", hash = "sha256:c8687a9a26451c51a34a06fa17bb97cb15bba46a6a626755e2d7f50da8bff3e3", size = 244469, upload-time = "2026-04-16T07:22:48.413Z" },
58
+ { url = "https://files.pythonhosted.org/packages/cd/51/9b0b5cd4cf683a02db937a6f9bbebcdc9c56558a7bb3763ce7d3512103c3/authlib-1.6.12-py2.py3-none-any.whl", hash = "sha256:e9229ad7fde610b139dd12f5edbe97eab9ee78bfb85691247e767727850b99ab", size = 244473, upload-time = "2026-05-04T08:11:30.354Z" },
59
59
  ]
60
60
 
61
61
  [[package]]
@@ -384,11 +384,11 @@ wheels = [
384
384
 
385
385
  [[package]]
386
386
  name = "idna"
387
- version = "3.10"
387
+ version = "3.15"
388
388
  source = { registry = "https://pypi.org/simple" }
389
- sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" }
389
+ sdist = { url = "https://files.pythonhosted.org/packages/82/77/7b3966d0b9d1d31a36ddf1746926a11dface89a83409bf1483f0237aa758/idna-3.15.tar.gz", hash = "sha256:ca962446ea538f7092a95e057da437618e886f4d349216d2b1e294abfdb65fdc", size = 199245, upload-time = "2026-05-12T22:45:57.011Z" }
390
390
  wheels = [
391
- { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" },
391
+ { url = "https://files.pythonhosted.org/packages/d2/23/408243171aa9aaba178d3e2559159c24c1171a641aa83b67bdd3394ead8e/idna-3.15-py3-none-any.whl", hash = "sha256:048adeaf8c2d788c40fee287673ccaa74c24ffd8dcf09ffa555a2fbb59f10ac8", size = 72340, upload-time = "2026-05-12T22:45:55.733Z" },
392
392
  ]
393
393
 
394
394
  [[package]]