@aexol/spectral 0.2.11 → 0.2.13

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.
@@ -24,13 +24,8 @@ async function attemptAutoAuth(state, serverName) {
24
24
  return { status: "skipped" };
25
25
  }
26
26
  const oauthConfig = definition.oauth;
27
- const grantType = oauthConfig?.grantType ?? "authorization_code";
28
- if (!state.ui && grantType !== "client_credentials") {
29
- return {
30
- status: "failed",
31
- message: getAuthRequiredMessage(state, serverName, `Server "${serverName}" requires OAuth authentication. Run /mcp-auth ${serverName} in an interactive session.`),
32
- };
33
- }
27
+ // OAuth flow works even without TUI — open() uses the system browser and the
28
+ // callback server handles the redirect.
34
29
  try {
35
30
  await authenticate(serverName, definition.url, definition);
36
31
  return { status: "success" };
@@ -43,33 +43,33 @@ export class McpServerManager {
43
43
  async createConnection(name, definition) {
44
44
  const client = this.createClient(name);
45
45
  let transport;
46
- if (definition.command) {
47
- let command = definition.command;
48
- let args = definition.args ?? [];
49
- if (command === "npx" || command === "npm") {
50
- const resolved = await resolveNpxBinary(command, args);
51
- if (resolved) {
52
- command = resolved.isJs ? "node" : resolved.binPath;
53
- args = resolved.isJs ? [resolved.binPath, ...resolved.extraArgs] : resolved.extraArgs;
54
- logger.debug(`${name} resolved to ${resolved.binPath} (skipping npm parent)`);
46
+ try {
47
+ if (definition.command) {
48
+ let command = definition.command;
49
+ let args = definition.args ?? [];
50
+ if (command === "npx" || command === "npm") {
51
+ const resolved = await resolveNpxBinary(command, args);
52
+ if (resolved) {
53
+ command = resolved.isJs ? "node" : resolved.binPath;
54
+ args = resolved.isJs ? [resolved.binPath, ...resolved.extraArgs] : resolved.extraArgs;
55
+ logger.debug(`${name} resolved to ${resolved.binPath} (skipping npm parent)`);
56
+ }
55
57
  }
58
+ transport = new StdioClientTransport({
59
+ command,
60
+ args,
61
+ env: resolveEnv(definition.env),
62
+ cwd: resolveConfigPath(definition.cwd),
63
+ stderr: definition.debug ? "inherit" : "ignore",
64
+ });
65
+ }
66
+ else if (definition.url) {
67
+ // HTTP transport with fallback
68
+ transport = await this.createHttpTransport(definition, name);
69
+ }
70
+ else {
71
+ throw new Error(`Server ${name} has no command or url`);
56
72
  }
57
- transport = new StdioClientTransport({
58
- command,
59
- args,
60
- env: resolveEnv(definition.env),
61
- cwd: resolveConfigPath(definition.cwd),
62
- stderr: definition.debug ? "inherit" : "ignore",
63
- });
64
- }
65
- else if (definition.url) {
66
- // HTTP transport with fallback
67
- transport = await this.createHttpTransport(definition, name);
68
- }
69
- else {
70
- throw new Error(`Server ${name} has no command or url`);
71
- }
72
- try {
73
73
  await client.connect(transport);
74
74
  this.attachAdapterNotificationHandlers(name, client);
75
75
  // Discover tools and resources
@@ -89,14 +89,17 @@ export class McpServerManager {
89
89
  };
90
90
  }
91
91
  catch (error) {
92
- // Check for UnauthorizedError - server requires OAuth
93
- if (error instanceof UnauthorizedError && supportsOAuth(definition)) {
94
- // Clean up both client and transport before reporting needs-auth.
95
- await client.close().catch(() => { });
92
+ // Clean up client and transport on any error.
93
+ await client.close().catch(() => { });
94
+ if (transport) {
96
95
  await transport.close().catch(() => { });
96
+ }
97
+ // Check for UnauthorizedError — server requires OAuth.
98
+ // This can fire from createHttpTransport probe or client.connect.
99
+ if (error instanceof UnauthorizedError && supportsOAuth(definition)) {
97
100
  return {
98
101
  client,
99
- transport,
102
+ transport: transport,
100
103
  definition,
101
104
  tools: [],
102
105
  resources: [],
@@ -105,9 +108,6 @@ export class McpServerManager {
105
108
  status: "needs-auth",
106
109
  };
107
110
  }
108
- // Clean up both client and transport on any error
109
- await client.close().catch(() => { });
110
- await transport.close().catch(() => { });
111
111
  throw error;
112
112
  }
113
113
  }
@@ -140,6 +140,7 @@ export class McpServerManager {
140
140
  clientId: definition.oauth?.clientId,
141
141
  clientSecret: definition.oauth?.clientSecret,
142
142
  scope: definition.oauth?.scope,
143
+ redirectUri: definition.oauth?.redirectUri,
143
144
  };
144
145
  authProvider = new McpOAuthProvider(serverName, definition.url, oauthConfig, {
145
146
  onRedirect: async (_authUrl) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aexol/spectral",
3
- "version": "0.2.11",
3
+ "version": "0.2.13",
4
4
  "description": "Always-on coding agent for Aexol — branded pi wrapper with relay-based browser access.",
5
5
  "type": "module",
6
6
  "private": false,