@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 +25 -13
- package/dist/customDatabasePlans.d.ts +6 -0
- package/dist/customDatabasePlans.js +96 -0
- package/dist/index.js +2 -5
- package/dist/mcpState.d.ts +4 -0
- package/dist/mcpState.js +46 -0
- package/dist/mcpSurfaces.d.ts +1 -1
- package/dist/obsidian/config.js +2 -3
- package/dist/obsidian/consent.d.ts +5 -1
- package/dist/obsidian/consent.js +11 -1
- package/dist/obsidian/importer.d.ts +5 -0
- package/dist/obsidian/importer.js +7 -0
- package/dist/server.d.ts +1 -1
- package/dist/server.js +92 -31
- package/dist/tools.d.ts +3 -1
- package/dist/tools.js +34 -21
- package/docs/agent-guide.md +1 -1
- package/docs/obsidian-import.md +6 -4
- package/docs/security.md +7 -4
- package/docs/setup.md +15 -7
- package/docs/tools.md +2 -2
- package/docs/troubleshooting.md +2 -2
- package/package.json +5 -3
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.
|
|
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.
|
|
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
|
-
|
|
115
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
224
|
-
|
|
225
|
-
|
|
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 {
|
|
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
|
-
|
|
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>;
|
package/dist/mcpState.js
ADDED
|
@@ -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
|
+
}
|
package/dist/mcpSurfaces.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { CompleteRequest, CompleteResult, GetPromptResult, ListPromptsResult, ListResourceTemplatesResult, ReadResourceResult } from '@modelcontextprotocol/
|
|
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>;
|
package/dist/obsidian/config.js
CHANGED
|
@@ -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
|
|
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';
|
package/dist/obsidian/consent.js
CHANGED
|
@@ -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/
|
|
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/
|
|
2
|
-
import {
|
|
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
|
-
|
|
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(
|
|
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
|
|
24
|
-
throw new
|
|
49
|
+
throw new ProtocolError(ProtocolErrorCode.InvalidParams, error.message);
|
|
50
|
+
throw new ProtocolError(ProtocolErrorCode.InternalError, toSafeToolError(error).message);
|
|
25
51
|
}
|
|
26
52
|
});
|
|
27
|
-
server.setRequestHandler(
|
|
53
|
+
server.setRequestHandler('tools/call', async (request, ctx) => {
|
|
28
54
|
const { name, arguments: args } = request.params;
|
|
29
|
-
const formElicitation = server
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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(
|
|
101
|
+
server.setRequestHandler('prompts/list', async (_request, ctx) => withCacheHint({
|
|
41
102
|
prompts: getAuroraPromptDefinitions(),
|
|
42
|
-
}));
|
|
43
|
-
server.setRequestHandler(
|
|
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
|
|
110
|
+
throw new ProtocolError(ProtocolErrorCode.InvalidParams, error.message);
|
|
50
111
|
throw error;
|
|
51
112
|
}
|
|
52
113
|
});
|
|
53
|
-
server.setRequestHandler(
|
|
114
|
+
server.setRequestHandler('resources/templates/list', async (_request, ctx) => withCacheHint({
|
|
54
115
|
resourceTemplates: getAuroraResourceTemplates(),
|
|
55
|
-
}));
|
|
56
|
-
server.setRequestHandler(
|
|
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
|
|
123
|
+
throw new ProtocolError(ProtocolErrorCode.InvalidParams, error.message);
|
|
63
124
|
const safeError = toSafeToolError(error);
|
|
64
125
|
const code = safeError.code === 'not_found'
|
|
65
|
-
?
|
|
66
|
-
:
|
|
67
|
-
throw new
|
|
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
|
-
|
|
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, {
|
|
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
|
-
|
|
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 =
|
|
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);
|
package/docs/agent-guide.md
CHANGED
|
@@ -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.
|
|
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
|
package/docs/obsidian-import.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Obsidian import
|
|
2
2
|
|
|
3
|
-
> **Available
|
|
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.
|
|
39
|
-
|
|
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.
|
|
34
|
-
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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` |
|
package/docs/troubleshooting.md
CHANGED
|
@@ -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.
|
|
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.
|
|
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/
|
|
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"
|