@zvndev/circular-mcp 0.1.1 → 0.1.2

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/lib/server.mjs CHANGED
@@ -21,7 +21,7 @@ const SUPPORTED_PROTOCOL_VERSIONS = new Set([
21
21
  "2024-11-05",
22
22
  ]);
23
23
 
24
- export const SERVER_INFO = { name: "circular-mcp", version: "0.1.1" };
24
+ export const SERVER_INFO = { name: "circular-mcp", version: "0.1.2" };
25
25
 
26
26
  /**
27
27
  * The briefing every client sees on connect. This is the only onboarding an
@@ -99,11 +99,13 @@ export async function apiRequest(
99
99
 
100
100
  let response = await send(config, method, url, body);
101
101
 
102
- // One retry, and only when the file genuinely holds a different key. The
103
- // config object is mutated so the rest of the session uses the live key too:
102
+ // Legacy unbound sessions get one retry when the file holds a different key.
103
+ // Bound sessions cannot prove that a global key represents the same agent
104
+ // and human, so they must reconnect instead of silently changing identity.
105
+ // The config object is mutated so the session uses the live key afterward:
104
106
  // recovering one request and leaving the next twenty to fail would be worse
105
107
  // than not recovering at all, because the failure would look intermittent.
106
- if (response.status === 401) {
108
+ if (response.status === 401 && !config.agentParticipantId) {
107
109
  const rotated = rotatedKey(config.apiKey, readFile);
108
110
  if (rotated) {
109
111
  config.apiKey = rotated;
@@ -120,6 +122,13 @@ export async function apiRequest(
120
122
  }
121
123
 
122
124
  if (!response.ok) {
125
+ if (response.status === 401 && config.agentParticipantId) {
126
+ throw new ApiError(
127
+ `${method} ${path} failed (401): This agent session credential expired or was revoked. Reconnect the session in Circular to authorize the same agent again; the global account key was not used.`,
128
+ response.status,
129
+ parsed,
130
+ );
131
+ }
123
132
  const detail =
124
133
  parsed && typeof parsed === "object" && parsed.error ? parsed.error : response.statusText;
125
134
  throw new ApiError(`${method} ${path} failed (${response.status}): ${detail}`, response.status, parsed);
@@ -42,20 +42,25 @@ export function readConfigFile(path = configFilePath()) {
42
42
  * CLI flags; `env` defaults to process.env; `file` is the parsed config file.
43
43
  */
44
44
  export function resolveConfig(flags = {}, env = process.env, file = {}) {
45
+ const agentParticipantId = env.CIRCULAR_AGENT_PARTICIPANT_ID?.trim();
46
+ const authorizingUserId = env.CIRCULAR_AUTHORIZING_USER_ID?.trim();
45
47
  const pick = (flagKey, envKey, fileKey, fallback) => {
46
48
  if (flags[flagKey] !== undefined && flags[flagKey] !== true) return flags[flagKey];
47
49
  if (env[envKey]) return env[envKey];
48
- if (file[fileKey]) return file[fileKey];
50
+ if (file[fileKey] && !(agentParticipantId && fileKey === "apiKey")) return file[fileKey];
49
51
  return fallback;
50
52
  };
51
53
 
52
54
  return {
55
+ // A bound session cannot borrow the operator's global credential, including
56
+ // when its delegated key is missing rather than rejected by the server.
53
57
  apiKey: pick("api-key", "CIRCULAR_API_KEY", "apiKey", undefined),
54
58
  baseUrl: stripTrailingSlash(
55
59
  pick("base-url", "CIRCULAR_BASE_URL", "baseUrl", DEFAULT_BASE_URL)
56
60
  ),
57
61
  workspaceId: pick("workspace", "CIRCULAR_WORKSPACE_ID", "workspaceId", undefined),
58
62
  teamId: pick("team", "CIRCULAR_TEAM_ID", "teamId", undefined),
63
+ ...(agentParticipantId ? { agentParticipantId, ...(authorizingUserId ? { authorizingUserId } : {}) } : {}),
59
64
  };
60
65
  }
61
66
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zvndev/circular-mcp",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Dependency-free stdio MCP server for the Circular Agent API — plan work into Circular as tracked tasks and subtasks.",
5
5
  "keywords": [
6
6
  "circular",