@henrikogard/auroradocs-mcp 0.2.1 → 0.3.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.
package/README.md CHANGED
@@ -6,7 +6,7 @@ authenticated requests to `https://api.auroradocs.eu`.
6
6
 
7
7
  The public package is `@henrikogard/auroradocs-mcp` and the executable is
8
8
  `aurora-mcp`. The latest published package and current source version are
9
- `0.2.1`.
9
+ `0.3.0`.
10
10
 
11
11
  For an end-to-end installation walkthrough, use the dedicated
12
12
  [Setup guide](docs/setup.md). AI assistants and client integrators should start
@@ -19,7 +19,8 @@ also apply the bounded [read-only agent profiles](docs/agent-profiles.md).
19
19
  - an AuroraDocs account with an AuroraCloud-backed workspace
20
20
  - permission to create an MCP token for that workspace
21
21
  - a supported local MCP client: Claude Desktop, Claude Code, Codex, or another
22
- client that can start a stdio server
22
+ client that can start a stdio server. Claude Desktop can also install the
23
+ `.mcpb` bundle from this repository instead of editing JSON.
23
24
 
24
25
  Browser-only workspaces and Local folders workspaces are not AuroraCloud MCP
25
26
  destinations. By default the server does not read a browser tab or local
@@ -88,7 +89,7 @@ in the knowledge-search recipe above.
88
89
  ## Configure a client
89
90
 
90
91
  All examples below use the production AuroraCloud API, a new client credential,
91
- and package version `0.2.1`. Replace `REDACTED` locally. Do not commit the
92
+ and package version `0.3.0`. Replace `REDACTED` locally. Do not commit the
92
93
  resulting configuration. The examples store the token in the client's saved
93
94
  configuration, so protect that file as a credential.
94
95
 
@@ -111,15 +112,22 @@ MCP-token authentication only.
111
112
 
112
113
  ### Claude Desktop
113
114
 
114
- Open Claude Desktop's developer settings and edit its MCP configuration. Add
115
- this server under `mcpServers`, preserving any servers already present:
115
+ For one-click install, pack `auroradocs.mcpb` with `pnpm pack:mcpb` and install
116
+ it from Claude Desktop **Settings Extensions Advanced settings → Install
117
+ Extension…**. The extension asks for the AuroraCloud API URL and the MCP token
118
+ (the token is stored in the OS keychain). Optional fields authorize one Obsidian
119
+ vault and a private plan/journal directory.
120
+
121
+ To configure it by hand, open Claude Desktop's developer settings and edit its
122
+ MCP configuration. Add this server under `mcpServers`, preserving any servers
123
+ already present:
116
124
 
117
125
  ```json
118
126
  {
119
127
  "mcpServers": {
120
128
  "auroradocs": {
121
129
  "command": "npx",
122
- "args": ["-y", "@henrikogard/auroradocs-mcp@0.2.1"],
130
+ "args": ["-y", "@henrikogard/auroradocs-mcp@0.3.0"],
123
131
  "env": {
124
132
  "AURORA_API_URL": "https://api.auroradocs.eu",
125
133
  "AURORA_API_TOKEN": "REDACTED"
@@ -142,7 +150,7 @@ Options must appear before the server name:
142
150
  claude mcp add --transport stdio --scope user \
143
151
  --env AURORA_API_URL=https://api.auroradocs.eu \
144
152
  --env AURORA_API_TOKEN=REDACTED \
145
- auroradocs -- npx -y @henrikogard/auroradocs-mcp@0.2.1
153
+ auroradocs -- npx -y @henrikogard/auroradocs-mcp@0.3.0
146
154
  ```
147
155
 
148
156
  Run `claude mcp get auroradocs` to inspect the saved entry, then use `/mcp` in
@@ -157,7 +165,7 @@ The installed Codex CLI accepts `--env` for local stdio servers:
157
165
  codex mcp add \
158
166
  --env AURORA_API_URL=https://api.auroradocs.eu \
159
167
  --env AURORA_API_TOKEN=REDACTED \
160
- auroradocs -- npx -y @henrikogard/auroradocs-mcp@0.2.1
168
+ auroradocs -- npx -y @henrikogard/auroradocs-mcp@0.3.0
161
169
  ```
162
170
 
163
171
  Run `codex mcp get auroradocs` to inspect the saved entry.
@@ -169,7 +177,7 @@ Use this valid generic JSON shape when a client accepts an MCP server object:
169
177
  ```json
170
178
  {
171
179
  "command": "npx",
172
- "args": ["-y", "@henrikogard/auroradocs-mcp@0.2.1"],
180
+ "args": ["-y", "@henrikogard/auroradocs-mcp@0.3.0"],
173
181
  "env": {
174
182
  "AURORA_API_URL": "https://api.auroradocs.eu",
175
183
  "AURORA_API_TOKEN": "REDACTED"
@@ -220,9 +228,12 @@ E2EE all stop before AuroraDocs writes.
220
228
 
221
229
  Imports run in resume-safe batches, keep private plan metadata plus a
222
230
  content-free progress journal outside the vault, survive MCP process restarts,
223
- and never modify the source. Back up both systems first and start
224
- with a small test workspace. See [Obsidian import](docs/obsidian-import.md) for
225
- configuration, mapping, consent, recovery, and fidelity limits.
231
+ and never modify the source. Custom-database plans use the same private state
232
+ directory. `list_workspaces` re-reads the current grant set from AuroraCloud, so
233
+ a new or revoked grant is visible without restarting the MCP process. Back up
234
+ both systems first and start with a small test workspace. See
235
+ [Obsidian import](docs/obsidian-import.md) for configuration, mapping, consent,
236
+ recovery, and fidelity limits.
226
237
 
227
238
  Analysis rejects more than 256 MiB of eligible Markdown/Canvas source files,
228
239
  and hidden, plugin, Git, cache, trash, and other ignored paths cannot be read as
@@ -262,7 +273,8 @@ the old token. Tokens cannot be extended or recovered.
262
273
  ## Security model
263
274
 
264
275
  - The MCP protocol process is local and stdio-only; AuroraDocs does not provide
265
- a hosted MCP HTTP, SSE, or OAuth endpoint.
276
+ a hosted MCP HTTP, SSE, or OAuth endpoint. The server speaks MCP 2026-07-28
277
+ and still serves 2025-era stdio clients.
266
278
  - AuroraCloud checks workspace membership, token scopes, the member's current
267
279
  role, expiry, revocation, rate limits, and audit events on requests.
268
280
  - E2EE content that is locked or unavailable is reported that way. The server
@@ -0,0 +1,6 @@
1
+ import { type CustomDatabasePlan } from './customDatabases.js';
2
+ export declare function storeCustomDatabasePlan(plan: CustomDatabasePlan): void;
3
+ export declare function getStoredCustomDatabasePlan(workspaceId: string, planId: string): CustomDatabasePlan | null;
4
+ export declare function clearCustomDatabasePlansForTests(): void;
5
+ export declare function writeCustomDatabasePlan(plan: CustomDatabasePlan, env?: Record<string, string | undefined>): Promise<string | null>;
6
+ export declare function readCustomDatabasePlan(workspaceId: string, planId: string, env?: Record<string, string | undefined>): Promise<CustomDatabasePlan | null>;
@@ -0,0 +1,96 @@
1
+ import { lstat, readFile } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { hashCustomDatabasePlan, } from './customDatabases.js';
4
+ import { ToolInputError } from './errors.js';
5
+ import { mcpStatePersistenceEnabled, resolveMcpStateDir, writeMcpStateFile, } from './mcpState.js';
6
+ const PLAN_STORE = new Map();
7
+ const PLAN_MAX_BYTES = 1 * 1024 * 1024;
8
+ const PLAN_STORE_LIMIT = 100;
9
+ function planKey(workspaceId, planId) {
10
+ return `${workspaceId}:${planId}`;
11
+ }
12
+ function isPersistedPlanId(planId) {
13
+ return /^[A-Za-z0-9_-]{1,128}$/.test(planId);
14
+ }
15
+ function planFileName(planId) {
16
+ if (!isPersistedPlanId(planId))
17
+ throw new ToolInputError('Invalid custom database plan ID');
18
+ return `custom-database-plan-${planId}.json`;
19
+ }
20
+ function pruneExpired(now = Date.now()) {
21
+ for (const [key, plan] of PLAN_STORE) {
22
+ if (Date.parse(plan.expiresAt) <= now)
23
+ PLAN_STORE.delete(key);
24
+ }
25
+ while (PLAN_STORE.size >= PLAN_STORE_LIMIT) {
26
+ const oldest = PLAN_STORE.keys().next().value;
27
+ if (!oldest)
28
+ break;
29
+ PLAN_STORE.delete(oldest);
30
+ }
31
+ }
32
+ function assertPersistedPlan(value) {
33
+ if (!value || typeof value !== 'object' || Array.isArray(value))
34
+ throw new Error('Invalid persisted custom database plan');
35
+ const plan = value;
36
+ if (typeof plan.planId !== 'string'
37
+ || typeof plan.planHash !== 'string'
38
+ || typeof plan.workspaceId !== 'string'
39
+ || typeof plan.createdAt !== 'string'
40
+ || typeof plan.expiresAt !== 'string'
41
+ || !Number.isFinite(Date.parse(plan.createdAt))
42
+ || !Number.isFinite(Date.parse(plan.expiresAt))
43
+ || hashCustomDatabasePlan(plan) !== plan.planHash) {
44
+ throw new Error('Invalid persisted custom database plan');
45
+ }
46
+ }
47
+ export function storeCustomDatabasePlan(plan) {
48
+ pruneExpired();
49
+ PLAN_STORE.set(planKey(plan.workspaceId, plan.planId), plan);
50
+ }
51
+ export function getStoredCustomDatabasePlan(workspaceId, planId) {
52
+ pruneExpired();
53
+ return PLAN_STORE.get(planKey(workspaceId, planId)) ?? null;
54
+ }
55
+ export function clearCustomDatabasePlansForTests() {
56
+ if (process.env['NODE_ENV'] !== 'test') {
57
+ throw new Error('clearCustomDatabasePlansForTests is only available in test environments');
58
+ }
59
+ PLAN_STORE.clear();
60
+ }
61
+ export async function writeCustomDatabasePlan(plan, env = process.env) {
62
+ storeCustomDatabasePlan(plan);
63
+ if (!mcpStatePersistenceEnabled(env))
64
+ return null;
65
+ const serialized = `${JSON.stringify(plan, null, 2)}\n`;
66
+ return writeMcpStateFile(resolveMcpStateDir(env), planFileName(plan.planId), serialized, PLAN_MAX_BYTES);
67
+ }
68
+ export async function readCustomDatabasePlan(workspaceId, planId, env = process.env) {
69
+ const memory = getStoredCustomDatabasePlan(workspaceId, planId);
70
+ if (memory)
71
+ return memory;
72
+ if (!isPersistedPlanId(planId))
73
+ return null;
74
+ if (!mcpStatePersistenceEnabled(env) && !env['AURORA_MCP_STATE_DIR']?.trim())
75
+ return null;
76
+ const file = path.join(resolveMcpStateDir(env), planFileName(planId));
77
+ let serialized;
78
+ try {
79
+ const info = await lstat(file);
80
+ if (info.isSymbolicLink() || !info.isFile() || info.size > PLAN_MAX_BYTES) {
81
+ throw new Error('Invalid persisted custom database plan file');
82
+ }
83
+ serialized = await readFile(file, 'utf8');
84
+ }
85
+ catch (error) {
86
+ if (error.code === 'ENOENT')
87
+ return null;
88
+ throw error;
89
+ }
90
+ const value = JSON.parse(serialized);
91
+ assertPersistedPlan(value);
92
+ if (value.planId !== planId || value.workspaceId !== workspaceId)
93
+ return null;
94
+ storeCustomDatabasePlan(value);
95
+ return value;
96
+ }
package/dist/index.js CHANGED
@@ -19,10 +19,9 @@
19
19
  * AURORA_API_EMAIL AuroraCloud user email (legacy/dev fallback)
20
20
  * AURORA_API_PASSWORD AuroraCloud user password (legacy/dev fallback)
21
21
  */
22
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
22
+ import { serveStdio } from '@modelcontextprotocol/server/stdio';
23
23
  import { authenticate } from './auroraClient.js';
24
24
  import { createAuroraMcpServer } from './server.js';
25
- // ── Main ─────────────────────────────────────────────────────────────────────
26
25
  async function main() {
27
26
  let context;
28
27
  try {
@@ -35,9 +34,7 @@ async function main() {
35
34
  process.stderr.write('AuroraDocs MCP authentication failed.\n');
36
35
  process.exit(1);
37
36
  }
38
- const server = createAuroraMcpServer(context);
39
- const transport = new StdioServerTransport();
40
- await server.connect(transport);
37
+ serveStdio(() => createAuroraMcpServer(context));
41
38
  process.stderr.write('AuroraDocs MCP server running.\n');
42
39
  }
43
40
  main().catch(() => {
@@ -0,0 +1,4 @@
1
+ export declare function resolveMcpStateDir(env?: Record<string, string | undefined>): string;
2
+ export declare function mcpStatePersistenceEnabled(env?: Record<string, string | undefined>): boolean;
3
+ export declare function ensureMcpStateDirectory(stateDir: string): Promise<void>;
4
+ export declare function writeMcpStateFile(stateDir: string, fileName: string, serialized: string, maxBytes: number): Promise<string>;
@@ -0,0 +1,46 @@
1
+ import { randomBytes } from 'node:crypto';
2
+ import { chmod, lstat, mkdir, open, rename, unlink } from 'node:fs/promises';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ export function resolveMcpStateDir(env = process.env) {
6
+ const raw = env['AURORA_MCP_STATE_DIR']?.trim();
7
+ return path.resolve(raw || path.join(os.homedir(), '.auroradocs-mcp'));
8
+ }
9
+ export function mcpStatePersistenceEnabled(env = process.env) {
10
+ if (env['AURORA_MCP_STATE_DIR']?.trim())
11
+ return true;
12
+ return env['NODE_ENV'] !== 'test';
13
+ }
14
+ export async function ensureMcpStateDirectory(stateDir) {
15
+ await mkdir(stateDir, { recursive: true, mode: 0o700 });
16
+ const info = await lstat(stateDir);
17
+ if (info.isSymbolicLink() || !info.isDirectory())
18
+ throw new Error('MCP state path must be a real directory');
19
+ await chmod(stateDir, 0o700);
20
+ }
21
+ export async function writeMcpStateFile(stateDir, fileName, serialized, maxBytes) {
22
+ if (!/^[A-Za-z0-9._-]{1,180}$/.test(fileName))
23
+ throw new Error('Invalid MCP state file name');
24
+ await ensureMcpStateDirectory(stateDir);
25
+ const destination = path.join(stateDir, fileName);
26
+ const temporary = path.join(stateDir, `.${fileName}.${randomBytes(8).toString('hex')}.tmp`);
27
+ if (Buffer.byteLength(serialized) > maxBytes)
28
+ throw new Error('MCP state file exceeds its safety limit');
29
+ const handle = await open(temporary, 'wx', 0o600);
30
+ try {
31
+ await handle.writeFile(serialized, 'utf8');
32
+ await handle.sync();
33
+ }
34
+ finally {
35
+ await handle.close();
36
+ }
37
+ try {
38
+ await rename(temporary, destination);
39
+ await chmod(destination, 0o600);
40
+ }
41
+ catch (error) {
42
+ await unlink(temporary).catch(() => { });
43
+ throw error;
44
+ }
45
+ return destination;
46
+ }
@@ -1,4 +1,4 @@
1
- import type { CompleteRequest, CompleteResult, GetPromptResult, ListPromptsResult, ListResourceTemplatesResult, ReadResourceResult } from '@modelcontextprotocol/sdk/types.js';
1
+ import type { CompleteRequest, CompleteResult, GetPromptResult, ListPromptsResult, ListResourceTemplatesResult, ReadResourceResult } from '@modelcontextprotocol/server';
2
2
  import type { AuroraConnectionContext } from './contracts.js';
3
3
  export declare function getAuroraServerInstructions(): string;
4
4
  export declare function completeAuroraArgument(params: CompleteRequest['params'], context: AuroraConnectionContext): Promise<CompleteResult>;
@@ -1,5 +1,5 @@
1
- import os from 'node:os';
2
1
  import path from 'node:path';
2
+ import { resolveMcpStateDir } from '../mcpState.js';
3
3
  function isInside(parent, candidate) {
4
4
  const relative = path.relative(parent, candidate);
5
5
  return relative === '' || (!relative.startsWith(`..${path.sep}`) && relative !== '..' && !path.isAbsolute(relative));
@@ -11,8 +11,7 @@ export function resolveObsidianConfig(env = process.env) {
11
11
  if (!path.isAbsolute(rawRoot))
12
12
  throw new Error('AURORA_OBSIDIAN_VAULT_ROOT must be an absolute path.');
13
13
  const vaultRoot = path.resolve(rawRoot);
14
- const rawStateDir = env['AURORA_MCP_STATE_DIR']?.trim();
15
- const stateDir = path.resolve(rawStateDir || path.join(os.homedir(), '.auroradocs-mcp'));
14
+ const stateDir = resolveMcpStateDir(env);
16
15
  if (isInside(vaultRoot, stateDir)) {
17
16
  throw new Error('AURORA_MCP_STATE_DIR must be outside the authorized Obsidian vault.');
18
17
  }
@@ -1,3 +1,4 @@
1
+ import type { ElicitRequestFormParams } from '@modelcontextprotocol/server';
1
2
  export type ObsidianConsentPreview = {
2
3
  planId: string;
3
4
  planHash: string;
@@ -33,10 +34,13 @@ export type ObsidianConsentDecision = {
33
34
  source: 'tool_input' | 'elicitation';
34
35
  };
35
36
  export type ObsidianConsentRequest = (preview: ObsidianConsentPreview) => Promise<ObsidianConsentResponse>;
37
+ export declare class ObsidianElicitationRequired extends Error {
38
+ readonly preview: ObsidianConsentPreview;
39
+ constructor(preview: ObsidianConsentPreview);
40
+ }
36
41
  export declare function buildObsidianConsentElicitation(preview: ObsidianConsentPreview): ElicitRequestFormParams;
37
42
  export declare function decideObsidianImportConsent(input: {
38
43
  confirmed?: unknown;
39
44
  preview: ObsidianConsentPreview;
40
45
  requestConsent?: ObsidianConsentRequest;
41
46
  }): Promise<ObsidianConsentDecision>;
42
- import type { ElicitRequestFormParams } from '@modelcontextprotocol/sdk/types.js';
@@ -1,3 +1,11 @@
1
+ export class ObsidianElicitationRequired extends Error {
2
+ preview;
3
+ constructor(preview) {
4
+ super('Obsidian import requires form elicitation');
5
+ this.name = 'ObsidianElicitationRequired';
6
+ this.preview = preview;
7
+ }
8
+ }
1
9
  export function buildObsidianConsentElicitation(preview) {
2
10
  return {
3
11
  mode: 'form',
@@ -43,7 +51,9 @@ export async function decideObsidianImportConsent(input) {
43
51
  try {
44
52
  response = await input.requestConsent(input.preview);
45
53
  }
46
- catch {
54
+ catch (error) {
55
+ if (error instanceof ObsidianElicitationRequired)
56
+ throw error;
47
57
  return { outcome: 'cancelled', source: 'elicitation' };
48
58
  }
49
59
  if (response.action === 'decline')
@@ -2,6 +2,10 @@ import type { AuroraAttachmentUpload, AuroraImportCapabilities, AuroraObjectReco
2
2
  import { type ObjectTypeDef, type ObjectTypeSchema, type PropertyValueType } from '../customDatabases.js';
3
3
  import { type StoredObsidianImportPlan } from './importPlan.js';
4
4
  import type { AuthorizedVault } from './vaultAccess.js';
5
+ export type ObsidianImportProgress = {
6
+ completed: number;
7
+ total: number;
8
+ };
5
9
  export type ObsidianImportDependencies = {
6
10
  getCapabilities(workspaceId: string): Promise<AuroraImportCapabilities>;
7
11
  listObjectTypes(workspaceId: string): Promise<ObjectTypeDef[]>;
@@ -49,4 +53,5 @@ export type ObsidianImportBatchResult = {
49
53
  export declare function runObsidianImportBatch(stored: StoredObsidianImportPlan, vault: AuthorizedVault, stateDir: string, options?: {
50
54
  batchSize?: number;
51
55
  dependencies?: ObsidianImportDependencies;
56
+ onProgress?: (progress: ObsidianImportProgress) => void | Promise<void>;
52
57
  }): Promise<ObsidianImportBatchResult>;
@@ -198,6 +198,12 @@ export async function runObsidianImportBatch(stored, vault, stateDir, options =
198
198
  journal.status = 'in_progress';
199
199
  journal.updatedAt = dependencies.now().toISOString();
200
200
  await writeImportJournal(stateDir, journal);
201
+ const total = stored.plan.entries.length;
202
+ const reportProgress = async () => {
203
+ const completed = Object.values(journal.entries).filter((entry) => entry.status === 'complete').length;
204
+ await options.onProgress?.({ completed, total });
205
+ };
206
+ await reportProgress();
201
207
  const warnings = [];
202
208
  const selection = selectPendingEntries(stored.plan.entries, journal, batchSize);
203
209
  const pending = selection.entries;
@@ -456,5 +462,6 @@ export async function runObsidianImportBatch(stored, vault, stateDir, options =
456
462
  const result = resultFromJournal(stored, journal, warnings);
457
463
  journal.updatedAt = dependencies.now().toISOString();
458
464
  await writeImportJournal(stateDir, journal);
465
+ await reportProgress();
459
466
  return result;
460
467
  }
package/dist/server.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- import { Server } from '@modelcontextprotocol/sdk/server/index.js';
1
+ import { Server } from '@modelcontextprotocol/server';
2
2
  import type { AuroraConnectionContext } from './contracts.js';
3
3
  export declare function createAuroraMcpServer(context: AuroraConnectionContext): Server;
package/dist/server.js CHANGED
@@ -1,70 +1,131 @@
1
- import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
- import { CallToolRequestSchema, CompleteRequestSchema, ErrorCode, GetPromptRequestSchema, ListPromptsRequestSchema, ListResourceTemplatesRequestSchema, ListToolsRequestSchema, McpError, ReadResourceRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
1
+ import { inputRequired, inputResponse, ProtocolError, ProtocolErrorCode, Server, } from '@modelcontextprotocol/server';
2
+ import { listGrantedWorkspaces } from './auroraClient.js';
3
3
  import { ToolInputError, toSafeToolError } from './errors.js';
4
4
  import { getAuroraPromptDefinitions, getAuroraPrompt, getAuroraResourceTemplates, getAuroraServerInstructions, completeAuroraArgument, readAuroraResource, } from './mcpSurfaces.js';
5
5
  import { executeToolCall, toMcpToolCallResult } from './tools.js';
6
6
  import { getToolDefinitions } from './toolCatalog.js';
7
7
  import { SERVER_VERSION } from './version.js';
8
- import { buildObsidianConsentElicitation } from './obsidian/consent.js';
8
+ import { buildObsidianConsentElicitation, ObsidianElicitationRequired } from './obsidian/consent.js';
9
+ const LIST_CACHE = { ttlMs: 300_000, cacheScope: 'private' };
10
+ const READ_CACHE = { ttlMs: 0, cacheScope: 'private' };
11
+ function clientSupportsFormElicitation(ctx, server) {
12
+ const envelope = ctx.mcpReq.envelope;
13
+ const elicitation = envelope?.clientCapabilities?.elicitation ?? server.getClientCapabilities()?.elicitation;
14
+ if (!elicitation)
15
+ return false;
16
+ if (Object.hasOwn(elicitation, 'form'))
17
+ return Boolean(elicitation.form);
18
+ return Object.keys(elicitation).length === 0;
19
+ }
20
+ function withCacheHint(result, ctx, hint) {
21
+ if (!ctx.mcpReq.envelope)
22
+ return result;
23
+ return { ...result, ...hint };
24
+ }
9
25
  export function createAuroraMcpServer(context) {
10
26
  const server = new Server({ name: 'auroradocs-mcp', version: SERVER_VERSION }, {
11
27
  capabilities: { tools: {}, prompts: {}, resources: {}, completions: {} },
12
28
  instructions: getAuroraServerInstructions(),
29
+ cacheHints: {
30
+ 'tools/list': LIST_CACHE,
31
+ 'prompts/list': LIST_CACHE,
32
+ 'resources/templates/list': LIST_CACHE,
33
+ 'resources/read': READ_CACHE,
34
+ 'server/discover': LIST_CACHE,
35
+ },
13
36
  });
14
- server.setRequestHandler(ListToolsRequestSchema, async () => ({
37
+ const refreshClientGrants = context.kind === 'client'
38
+ ? () => listGrantedWorkspaces()
39
+ : undefined;
40
+ server.setRequestHandler('tools/list', async (_request, ctx) => withCacheHint({
15
41
  tools: getToolDefinitions(),
16
- }));
17
- server.setRequestHandler(CompleteRequestSchema, async (request) => {
42
+ }, ctx, LIST_CACHE));
43
+ server.setRequestHandler('completion/complete', async (request, _ctx) => {
18
44
  try {
19
45
  return await completeAuroraArgument(request.params, context);
20
46
  }
21
47
  catch (error) {
22
48
  if (error instanceof ToolInputError)
23
- throw new McpError(ErrorCode.InvalidParams, error.message);
24
- throw new McpError(ErrorCode.InternalError, toSafeToolError(error).message);
49
+ throw new ProtocolError(ProtocolErrorCode.InvalidParams, error.message);
50
+ throw new ProtocolError(ProtocolErrorCode.InternalError, toSafeToolError(error).message);
25
51
  }
26
52
  });
27
- server.setRequestHandler(CallToolRequestSchema, async (request) => {
53
+ server.setRequestHandler('tools/call', async (request, ctx) => {
28
54
  const { name, arguments: args } = request.params;
29
- const formElicitation = server.getClientCapabilities()?.elicitation?.form;
30
- const result = await executeToolCall(name, (args ?? {}), context, {
31
- requestObsidianImportConsent: formElicitation
32
- ? async (preview) => {
33
- const response = await server.elicitInput(buildObsidianConsentElicitation(preview));
34
- return { action: response.action, content: response.content };
35
- }
36
- : undefined,
37
- });
38
- return toMcpToolCallResult(result);
55
+ const formElicitation = clientSupportsFormElicitation(ctx, server);
56
+ const progressToken = ctx.mcpReq._meta?.progressToken;
57
+ try {
58
+ const result = await executeToolCall(name, (args ?? {}), context, {
59
+ refreshClientGrants,
60
+ reportProgress: progressToken === undefined
61
+ ? undefined
62
+ : async (progress) => {
63
+ await ctx.mcpReq.notify({
64
+ method: 'notifications/progress',
65
+ params: {
66
+ progressToken,
67
+ progress: progress.completed,
68
+ total: progress.total,
69
+ },
70
+ });
71
+ },
72
+ requestObsidianImportConsent: formElicitation
73
+ ? async (preview) => {
74
+ const view = inputResponse(ctx.mcpReq.inputResponses, 'consent');
75
+ if (view.kind === 'missing')
76
+ throw new ObsidianElicitationRequired(preview);
77
+ if (view.kind === 'elicit') {
78
+ return {
79
+ action: view.action === 'accept' ? 'accept' : view.action === 'decline' ? 'decline' : 'cancel',
80
+ content: view.content,
81
+ };
82
+ }
83
+ throw new ObsidianElicitationRequired(preview);
84
+ }
85
+ : undefined,
86
+ });
87
+ const definition = getToolDefinitions().find((tool) => tool.name === name);
88
+ return server.projectCallToolResult(toMcpToolCallResult(result), definition?.outputSchema);
89
+ }
90
+ catch (error) {
91
+ if (error instanceof ObsidianElicitationRequired) {
92
+ return inputRequired({
93
+ inputRequests: {
94
+ consent: inputRequired.elicit(buildObsidianConsentElicitation(error.preview)),
95
+ },
96
+ });
97
+ }
98
+ throw error;
99
+ }
39
100
  });
40
- server.setRequestHandler(ListPromptsRequestSchema, async () => ({
101
+ server.setRequestHandler('prompts/list', async (_request, ctx) => withCacheHint({
41
102
  prompts: getAuroraPromptDefinitions(),
42
- }));
43
- server.setRequestHandler(GetPromptRequestSchema, async (request) => {
103
+ }, ctx, LIST_CACHE));
104
+ server.setRequestHandler('prompts/get', async (request) => {
44
105
  try {
45
106
  return getAuroraPrompt(request.params.name, request.params.arguments ?? {});
46
107
  }
47
108
  catch (error) {
48
109
  if (error instanceof ToolInputError)
49
- throw new McpError(ErrorCode.InvalidParams, error.message);
110
+ throw new ProtocolError(ProtocolErrorCode.InvalidParams, error.message);
50
111
  throw error;
51
112
  }
52
113
  });
53
- server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => ({
114
+ server.setRequestHandler('resources/templates/list', async (_request, ctx) => withCacheHint({
54
115
  resourceTemplates: getAuroraResourceTemplates(),
55
- }));
56
- server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
116
+ }, ctx, LIST_CACHE));
117
+ server.setRequestHandler('resources/read', async (request, ctx) => {
57
118
  try {
58
- return await readAuroraResource(request.params.uri, context);
119
+ return withCacheHint(await readAuroraResource(request.params.uri, context), ctx, READ_CACHE);
59
120
  }
60
121
  catch (error) {
61
122
  if (error instanceof ToolInputError)
62
- throw new McpError(ErrorCode.InvalidParams, error.message);
123
+ throw new ProtocolError(ProtocolErrorCode.InvalidParams, error.message);
63
124
  const safeError = toSafeToolError(error);
64
125
  const code = safeError.code === 'not_found'
65
- ? ErrorCode.InvalidParams
66
- : ErrorCode.InternalError;
67
- throw new McpError(code, safeError.message);
126
+ ? ProtocolErrorCode.InvalidParams
127
+ : ProtocolErrorCode.InternalError;
128
+ throw new ProtocolError(code, safeError.message);
68
129
  }
69
130
  });
70
131
  return server;
package/dist/tools.d.ts CHANGED
@@ -5,7 +5,7 @@ import type { CustomDatabasePlan, CustomDatabaseRecipe, ObjectTypeDef } from './
5
5
  import { getObsidianImportPlanPage } from './obsidian/importPlan.js';
6
6
  import type { ObsidianImportPlanPreview } from './obsidian/importPlan.js';
7
7
  import { type ObsidianConsentPreview, type ObsidianConsentRequest } from './obsidian/consent.js';
8
- import { type ObsidianImportBatchResult } from './obsidian/importer.js';
8
+ import { type ObsidianImportBatchResult, type ObsidianImportProgress } from './obsidian/importer.js';
9
9
  import { type ObsidianImportStatus } from './obsidian/journal.js';
10
10
  import type { StoredObsidianImportPlan } from './obsidian/importPlan.js';
11
11
  import type { AuroraConnectionContext, Availability, GrantedWorkspace, ProjectChangesResult, ProjectContextResult, ToolErrorResult } from './contracts.js';
@@ -203,6 +203,8 @@ export type ToolExecutionOptions = {
203
203
  requestObsidianImportConsent?: ObsidianConsentRequest;
204
204
  runObsidianImport?: (stored: StoredObsidianImportPlan, batchSize: number) => Promise<ObsidianImportBatchResult>;
205
205
  now?: () => Date;
206
+ refreshClientGrants?: () => Promise<GrantedWorkspace[]>;
207
+ reportProgress?: (progress: ObsidianImportProgress) => void | Promise<void>;
206
208
  };
207
209
  export declare function resolveWorkspace(context: AuroraConnectionContext, input: Record<string, unknown>): string;
208
210
  export declare function executeToolCall(name: string, input: Record<string, unknown>, connection: AuroraConnectionContext | string, options?: ToolExecutionOptions): Promise<ToolResult>;
package/dist/tools.js CHANGED
@@ -16,8 +16,9 @@ import { resolveObsidianConfig } from './obsidian/config.js';
16
16
  import { openAuthorizedVault } from './obsidian/vaultAccess.js';
17
17
  import { analyzeObsidianVault } from './obsidian/analyzer.js';
18
18
  import { assertCurrentObsidianImportPlan, buildObsidianImportPlan, getObsidianImportPlanPage, getStoredObsidianImportPlan, readObsidianImportPlan, storeObsidianImportPlan, summarizeObsidianImportPlan, writeObsidianImportPlan, } from './obsidian/importPlan.js';
19
- import { decideObsidianImportConsent, } from './obsidian/consent.js';
19
+ import { decideObsidianImportConsent, ObsidianElicitationRequired, } from './obsidian/consent.js';
20
20
  import { runObsidianImportBatch } from './obsidian/importer.js';
21
+ import { readCustomDatabasePlan, writeCustomDatabasePlan, } from './customDatabasePlans.js';
21
22
  import { readImportJournal, summarizeImportJournal } from './obsidian/journal.js';
22
23
  export function buildMcpObjectPropertyResult(input) {
23
24
  const computedLabels = new Map();
@@ -93,7 +94,6 @@ function invalidInput(message) {
93
94
  function notFound(message) {
94
95
  return { type: 'error', code: 'not_found', message, retryable: false };
95
96
  }
96
- const CUSTOM_DATABASE_PLANS = new Map();
97
97
  async function loadStoredObsidianImportPlan(workspaceId, planId) {
98
98
  const memory = getStoredObsidianImportPlan(workspaceId, planId);
99
99
  if (memory)
@@ -131,20 +131,6 @@ function cloneRecipe(recipe) {
131
131
  } : {}),
132
132
  };
133
133
  }
134
- function storeCustomDatabasePlan(plan) {
135
- const now = Date.now();
136
- for (const [key, value] of CUSTOM_DATABASE_PLANS) {
137
- if (Date.parse(value.expiresAt) <= now)
138
- CUSTOM_DATABASE_PLANS.delete(key);
139
- }
140
- while (CUSTOM_DATABASE_PLANS.size >= 100) {
141
- const oldest = CUSTOM_DATABASE_PLANS.keys().next().value;
142
- if (!oldest)
143
- break;
144
- CUSTOM_DATABASE_PLANS.delete(oldest);
145
- }
146
- CUSTOM_DATABASE_PLANS.set(`${plan.workspaceId}:${plan.planId}`, plan);
147
- }
148
134
  function optionalPresentation(input, key) {
149
135
  if (!Object.hasOwn(input, key))
150
136
  return undefined;
@@ -401,17 +387,41 @@ export function resolveWorkspace(context, input) {
401
387
  }
402
388
  return matches[0].workspaceId;
403
389
  }
390
+ async function refreshClientGrants(context, options) {
391
+ if (context.kind !== 'client' || !options.refreshClientGrants)
392
+ return;
393
+ const workspaces = await options.refreshClientGrants();
394
+ context.workspaces.splice(0, context.workspaces.length, ...workspaces);
395
+ }
404
396
  export async function executeToolCall(name, input, connection, options = {}) {
405
397
  try {
406
398
  const context = normalizeConnectionContext(connection);
407
- if (name === 'list_workspaces')
399
+ if (name === 'list_workspaces') {
400
+ await refreshClientGrants(context, options);
408
401
  return { type: 'workspaces', workspaces: context.workspaces };
402
+ }
409
403
  if (name === 'get_mcp_tool_coverage' || name === 'get_mcp_workflow_recipes' || name === 'get_custom_database_recipes') {
410
404
  return await executeToolCallUnsafe(name, input, context.kind === 'legacy_workspace' ? context.defaultWorkspaceId : '', options);
411
405
  }
412
- return await executeToolCallUnsafe(name, input, resolveWorkspace(context, input), options);
406
+ try {
407
+ return await executeToolCallUnsafe(name, input, resolveWorkspace(context, input), options);
408
+ }
409
+ catch (error) {
410
+ if (error instanceof ObsidianElicitationRequired)
411
+ throw error;
412
+ if (!(error instanceof ToolInputError)
413
+ || error.message !== 'Workspace selector does not match an available grant'
414
+ || context.kind !== 'client'
415
+ || !options.refreshClientGrants) {
416
+ throw error;
417
+ }
418
+ await refreshClientGrants(context, options);
419
+ return await executeToolCallUnsafe(name, input, resolveWorkspace(context, input), options);
420
+ }
413
421
  }
414
422
  catch (error) {
423
+ if (error instanceof ObsidianElicitationRequired)
424
+ throw error;
415
425
  return toSafeToolError(error);
416
426
  }
417
427
  }
@@ -645,7 +655,10 @@ async function executeToolCallUnsafe(name, input, workspaceId, options) {
645
655
  throw new ToolInputError(error instanceof Error ? error.message : 'Obsidian vault authorization is unavailable');
646
656
  }
647
657
  const vault = await openAuthorizedVault(config);
648
- return runObsidianImportBatch(approved, vault, config.stateDir, { batchSize: size });
658
+ return runObsidianImportBatch(approved, vault, config.stateDir, {
659
+ batchSize: size,
660
+ onProgress: options.reportProgress,
661
+ });
649
662
  });
650
663
  return { type: 'obsidian_import_batch', result: await run(stored, batchSize.value) };
651
664
  }
@@ -700,7 +713,7 @@ async function executeToolCallUnsafe(name, input, workspaceId, options) {
700
713
  existingTypes: objectTypes,
701
714
  assumptions: Array.isArray(input['assumptions']) ? input['assumptions'].map((entry) => String(entry).trim()).filter(Boolean).slice(0, 20) : [],
702
715
  });
703
- storeCustomDatabasePlan(plan);
716
+ await writeCustomDatabasePlan(plan);
704
717
  return { type: 'custom_database_plan', plan, summary: summarizeCustomDatabasePlan(plan) };
705
718
  }
706
719
  case 'apply_custom_database_plan': {
@@ -708,7 +721,7 @@ async function executeToolCallUnsafe(name, input, workspaceId, options) {
708
721
  const planHash = readString(input['plan_hash']);
709
722
  if (!planId || !planHash)
710
723
  return invalidInput('Plan ID and plan hash are required');
711
- const plan = CUSTOM_DATABASE_PLANS.get(`${workspaceId}:${planId}`);
724
+ const plan = await readCustomDatabasePlan(workspaceId, planId);
712
725
  if (!plan || plan.planHash !== planHash)
713
726
  return invalidInput('The custom database plan is missing, expired, or does not match the approved hash');
714
727
  const existingTypes = await listAuroraObjectTypes(workspaceId);
@@ -5,7 +5,7 @@ MCP server. The server also sends a shorter version as MCP initialization
5
5
  instructions, so compatible clients receive the core workspace, approval,
6
6
  citation, and untrusted-content rules when they connect.
7
7
 
8
- This guide describes the published `0.2.1` package, including custom-database,
8
+ This guide describes the published `0.3.0` package, including custom-database,
9
9
  template, Obsidian, and initialization-instruction support.
10
10
 
11
11
  ## Start every session safely
@@ -1,7 +1,7 @@
1
1
  # Obsidian import
2
2
 
3
- > **Available in 0.2.1:** these tools require the published 0.2.1 package or
4
- > newer.
3
+ > **Available since 0.2.1:** these tools require the published `0.3.0` package
4
+ > or newer.
5
5
 
6
6
  The importer converts one explicitly authorized local Obsidian vault into an
7
7
  AuroraCloud-backed AuroraDocs workspace. It is analyze-first, consent-gated,
@@ -35,8 +35,10 @@ Configure the local MCP child process with:
35
35
  `AURORA_OBSIDIAN_VAULT_ROOT` is required and must be an absolute real
36
36
  directory. `AURORA_MCP_STATE_DIR` is optional and must remain outside the
37
37
  vault; by default the server uses a private `.auroradocs-mcp` directory for the
38
- current user. Removing the root variable disables all Obsidian tools while
39
- ordinary AuroraCloud tools keep working.
38
+ current user. That directory also stores reviewable custom-database plans.
39
+ Removing the root variable disables all Obsidian tools while ordinary
40
+ AuroraCloud tools keep working. Import batches report progress on the
41
+ originating tool call when the client sends a progress token.
40
42
 
41
43
  This environment setting authorizes read-only analysis only. The reader never
42
44
  scans parents or siblings, follows symlinks, runs plugins, reads `.git`, trash,
package/docs/security.md CHANGED
@@ -30,8 +30,9 @@ reuse one token across unrelated clients.
30
30
 
31
31
  The package runs locally over stdio. It does not listen on a network port and
32
32
  AuroraDocs does not currently offer a hosted HTTP, SSE, WebSocket, or OAuth MCP
33
- endpoint. `https://api.auroradocs.eu` is the AuroraCloud REST API used by the
34
- local process, not an MCP endpoint.
33
+ endpoint. The local process speaks MCP 2026-07-28 and still serves 2025-era
34
+ stdio clients. `https://api.auroradocs.eu` is the AuroraCloud REST API used by
35
+ the local process, not an MCP endpoint.
35
36
 
36
37
  Do not expose the stdio process through a network proxy or paste its credential
37
38
  into an arbitrary hosted agent service.
@@ -79,7 +80,8 @@ not scanned and cannot be read as attachments. No parser fetches remote URLs and
79
80
  modified.
80
81
 
81
82
  Import requires a separate, exact plan ID/hash acceptance. Form-capable MCP
82
- clients receive an elicitation request; only protocol `accept` with
83
+ clients receive a form elicitation (a 2026 multi-round-trip `input_required`
84
+ result, or a 2025 `elicitation/create` request). Only protocol `accept` with
83
85
  `confirmed: true` and unchanged policy choices proceeds. Decline, cancel,
84
86
  malformed content, changed choices, or an unavailable elicitation client without
85
87
  a later exact confirmation performs zero writes. Plans are workspace-bound,
@@ -92,7 +94,8 @@ uploads use one parent-bound MCP route and stable idempotency keys; the MCP
92
94
  cannot list, replace, or delete arbitrary files through that boundary.
93
95
 
94
96
  The private state lives outside the vault in a 0700 directory with 0600 files
95
- and atomic replacement. The persisted approved plan stores bounded policies,
97
+ and atomic replacement. Custom-database plans use that same directory so apply
98
+ survives an MCP restart. The persisted approved plan stores bounded policies,
96
99
  inferred schemas, planned IDs, warnings, and relative source paths needed for
97
100
  restart revalidation, but no note bodies, frontmatter values, attachment bytes,
98
101
  tokens, credentials, or absolute paths. The separate resume journal stores
package/docs/setup.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # Setup
2
2
 
3
3
  This guide connects a local MCP client to owner-approved AuroraCloud workspace
4
- grants with `@henrikogard/auroradocs-mcp@0.2.1`. The server runs locally over
4
+ grants with `@henrikogard/auroradocs-mcp@0.3.0`. The server runs locally over
5
5
  stdio; it is not a hosted MCP endpoint.
6
6
 
7
7
  ## Requirements
@@ -9,7 +9,8 @@ stdio; it is not a hosted MCP endpoint.
9
9
  - Node.js 20 or newer, with `npx` available to the MCP client
10
10
  - an AuroraDocs account and an AuroraCloud-backed workspace
11
11
  - workspace owner or admin permission to create an MCP key
12
- - Claude Desktop, Claude Code, Codex, or another stdio-capable MCP client
12
+ - Claude Desktop, Claude Code, Codex, or another stdio-capable MCP client.
13
+ Claude Desktop can install the `.mcpb` bundle instead of a JSON server entry.
13
14
 
14
15
  Browser-only and Local folders workspaces are not supported.
15
16
 
@@ -62,7 +63,7 @@ Every new client entry provides these values:
62
63
  | `AURORA_API_URL` | `https://api.auroradocs.eu` |
63
64
  | `AURORA_API_TOKEN` | the one-time `aur_mcp_client_` credential |
64
65
 
65
- For the `0.2.1` Obsidian importer, add these only after backing up the source
66
+ For the `0.3.0` Obsidian importer, add these only after backing up the source
66
67
  vault and choosing a test destination:
67
68
 
68
69
  | Variable | Value |
@@ -80,7 +81,7 @@ Generic stdio configuration:
80
81
  ```json
81
82
  {
82
83
  "command": "npx",
83
- "args": ["-y", "@henrikogard/auroradocs-mcp@0.2.1"],
84
+ "args": ["-y", "@henrikogard/auroradocs-mcp@0.3.0"],
84
85
  "env": {
85
86
  "AURORA_API_URL": "https://api.auroradocs.eu",
86
87
  "AURORA_API_TOKEN": "REDACTED"
@@ -88,6 +89,13 @@ Generic stdio configuration:
88
89
  }
89
90
  ```
90
91
 
92
+ Claude Desktop can install a local `.mcpb` bundle instead of that JSON. From a
93
+ checkout run `pnpm pack:mcpb`, then in Claude Desktop open **Settings →
94
+ Extensions → Advanced settings → Install Extension…** and choose
95
+ `auroradocs.mcpb`. Enter the API URL and MCP token there. The token is stored
96
+ in the OS keychain. Optional fields authorize one Obsidian vault and a private
97
+ state directory.
98
+
91
99
  For Hermes and OpenClaw, restrict exposed tools to the read-only profile in
92
100
  [Agent profiles](agent-profiles.md). Never expose the credential to an agent
93
101
  prompt, logs, or committed configuration.
@@ -156,7 +164,7 @@ servers, then restart Claude Desktop:
156
164
  "mcpServers": {
157
165
  "auroradocs": {
158
166
  "command": "npx",
159
- "args": ["-y", "@henrikogard/auroradocs-mcp@0.2.1"],
167
+ "args": ["-y", "@henrikogard/auroradocs-mcp@0.3.0"],
160
168
  "env": {
161
169
  "AURORA_API_URL": "https://api.auroradocs.eu",
162
170
  "AURORA_WORKSPACE_ID": "WORKSPACE_ID",
@@ -174,7 +182,7 @@ claude mcp add --transport stdio --scope user \
174
182
  --env AURORA_API_URL=https://api.auroradocs.eu \
175
183
  --env AURORA_WORKSPACE_ID=WORKSPACE_ID \
176
184
  --env AURORA_API_TOKEN=REDACTED \
177
- auroradocs -- npx -y @henrikogard/auroradocs-mcp@0.2.1
185
+ auroradocs -- npx -y @henrikogard/auroradocs-mcp@0.3.0
178
186
  ```
179
187
 
180
188
  Run `claude mcp get auroradocs` to inspect the saved entry.
@@ -186,7 +194,7 @@ codex mcp add \
186
194
  --env AURORA_API_URL=https://api.auroradocs.eu \
187
195
  --env AURORA_WORKSPACE_ID=WORKSPACE_ID \
188
196
  --env AURORA_API_TOKEN=REDACTED \
189
- auroradocs -- npx -y @henrikogard/auroradocs-mcp@0.2.1
197
+ auroradocs -- npx -y @henrikogard/auroradocs-mcp@0.3.0
190
198
  ```
191
199
 
192
200
  Run `codex mcp get auroradocs` to inspect the saved entry.
package/docs/tools.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Tools and scopes
2
2
 
3
- AuroraDocs MCP `0.2.1` exposes the tools below to a connected client. An
3
+ AuroraDocs MCP `0.3.0` exposes the tools below to a connected client. An
4
4
  `aur_mcp_client_` credential can discover its independently granted workspaces
5
5
  with `list_workspaces`; every workspace data call then selects one grant with
6
6
  `workspace_id` or an unambiguous `workspace_alias`. A legacy `aur_mcp_` token
@@ -59,7 +59,7 @@ including object lookup and startup membership verification.
59
59
 
60
60
  | Tool | What it does | Scopes |
61
61
  | --- | --- | --- |
62
- | `list_workspaces` | List only the workspaces independently granted to a client credential. | Client credential; no workspace selector |
62
+ | `list_workspaces` | List only the workspaces independently granted to a client credential. Re-reads the live grant set from AuroraCloud. | Client credential; no workspace selector |
63
63
  | `search_objects` | Search object titles, optionally by type. | `read:objects` |
64
64
  | `search` | Alias for `search_objects`. | `read:objects` |
65
65
  | `list_objects` | List object metadata, optionally by type. | `read:objects` |
@@ -12,10 +12,10 @@ logs, screenshots, issues, or support messages. Use the token fingerprint from
12
12
  | Server exits with `AURORA_WORKSPACE_ID environment variable is required` | A legacy `aur_mcp_` token is missing its pinned workspace ID. | Copy it again from MCP Access. Client credentials must use `aur_mcp_client_` and omit this variable. |
13
13
  | Server exits with `AURORA_API_URL environment variable is required` | The API URL is missing. | Set it to `https://api.auroradocs.eu`. Do not add `/mcp`. |
14
14
  | Authentication is missing | `AURORA_API_TOKEN` was not passed to the child process. | Add the MCP token environment variable to the server entry. Do not use email/password authentication. |
15
- | Client shows no tools | The stdio process failed, is still connecting, or the client has stale configuration. | Inspect the client's MCP status, confirm the command is `npx -y @henrikogard/auroradocs-mcp@0.2.1`, then restart the client. |
15
+ | Client shows no tools | The stdio process failed, is still connecting, or the client has stale configuration. | Inspect the client's MCP status, confirm the command is `npx -y @henrikogard/auroradocs-mcp@0.3.0`, then restart the client. |
16
16
  | JSON configuration will not load | Invalid JSON, usually a missing comma or an overwritten outer `mcpServers` object. | Validate the file as JSON and merge the server entry with existing entries. JSON cannot contain comments. |
17
17
  | Works in a terminal but not the desktop client | Desktop apps often use a different PATH and environment. | For client mode, pass the API URL and token; for legacy mode, also pass `AURORA_WORKSPACE_ID`. Restart the desktop app. |
18
- | Client credential reports an ambiguous workspace | The data call omitted a selector or an alias matches more than one grant. | Call `list_workspaces`, then pass one exact `workspace_id` or unambiguous `workspace_alias`. |
18
+ | Client credential reports an ambiguous workspace | The data call omitted a selector or an alias matches more than one grant. | Call `list_workspaces`, then pass one exact `workspace_id` or unambiguous `workspace_alias`. `list_workspaces` re-reads grants from AuroraCloud; a new grant does not require restarting the MCP process. |
19
19
  | Obsidian tools say `AURORA_OBSIDIAN_VAULT_ROOT` is required | Local vault access is intentionally disabled. | Add one absolute vault path to the local MCP process only after backing it up, then restart the client. Normal AuroraCloud tools do not need this variable. |
20
20
  | Vault or state path is rejected | The root is relative/symlinked, changed identity, or the plan/journal directory is inside the vault. | Use a real absolute vault directory and a separate private `AURORA_MCP_STATE_DIR`; never weaken the path checks. |
21
21
  | Vault analysis exceeds its source limit | Eligible Markdown, Canvas, and Templates configuration files total more than 256 MiB. | Split or archive the vault before analysis; ignored plugin/cache/Git data does not count and cannot be imported as attachments. |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@henrikogard/auroradocs-mcp",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "AuroraCloud-first MCP server that exposes AuroraDocs workspace data to Claude Desktop and other AI agents",
@@ -34,7 +34,8 @@
34
34
  "test": "NODE_ENV=test tsx --test src/*.test.ts src/obsidian/*.test.ts",
35
35
  "test:live-smoke-contract": "NODE_ENV=test node --import tsx --test scripts/auroracloudLiveSmoke.test.mjs",
36
36
  "test:docs": "node --test scripts/docsContract.test.mjs",
37
- "test:contracts": "node --test scripts/checkWiring.test.mjs scripts/packageContentsCore.test.mjs",
37
+ "test:contracts": "node --test scripts/checkWiring.test.mjs scripts/packageContentsCore.test.mjs scripts/mcpbManifest.test.mjs",
38
+ "pack:mcpb": "node scripts/packMcpb.mjs",
38
39
  "test:stdio": "pnpm build && tsx --test test/stdio.integration.test.ts",
39
40
  "typecheck": "tsc --noEmit",
40
41
  "build": "tsc -p tsconfig.build.json",
@@ -45,10 +46,11 @@
45
46
  "auroracloud-live-smoke": "tsx scripts/auroracloudLiveSmoke.ts"
46
47
  },
47
48
  "dependencies": {
48
- "@modelcontextprotocol/sdk": "^1.30.0",
49
+ "@modelcontextprotocol/server": "^2.0.0",
49
50
  "yaml": "^2.9.0"
50
51
  },
51
52
  "devDependencies": {
53
+ "@modelcontextprotocol/client": "^2.0.0",
52
54
  "@types/node": "^26.1.1",
53
55
  "tsx": "^4.19.0",
54
56
  "typescript": "~7.0.2"