@nowline/mcp 0.8.6 → 0.10.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/dist/capabilities.d.ts.map +1 -1
- package/dist/capabilities.js +2 -9
- package/dist/capabilities.js.map +1 -1
- package/dist/diagnostics.d.ts +43 -15
- package/dist/diagnostics.d.ts.map +1 -1
- package/dist/diagnostics.js +77 -25
- package/dist/diagnostics.js.map +1 -1
- package/dist/generated/resources.d.ts.map +1 -1
- package/dist/generated/resources.js +15 -3
- package/dist/generated/resources.js.map +1 -1
- package/dist/generated/ui-bundle.d.ts.map +1 -1
- package/dist/generated/ui-bundle.js +2 -2
- package/dist/generated/ui-bundle.js.map +1 -1
- package/dist/http.d.ts +4 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +23 -0
- package/dist/http.js.map +1 -0
- package/dist/index.js +8 -13
- package/dist/index.js.map +1 -1
- package/dist/options.d.ts +26 -0
- package/dist/options.d.ts.map +1 -0
- package/dist/options.js +55 -0
- package/dist/options.js.map +1 -0
- package/dist/reference-cheatsheet.d.ts +1 -1
- package/dist/reference-cheatsheet.d.ts.map +1 -1
- package/dist/reference-cheatsheet.js +27 -2
- package/dist/reference-cheatsheet.js.map +1 -1
- package/dist/schema-vocab.d.ts +4 -2
- package/dist/schema-vocab.d.ts.map +1 -1
- package/dist/schema-vocab.js +9 -0
- package/dist/schema-vocab.js.map +1 -1
- package/dist/schemas.d.ts +2 -0
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +2 -0
- package/dist/schemas.js.map +1 -1
- package/dist/server.d.ts +4 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +172 -82
- package/dist/server.js.map +1 -1
- package/dist/ui/entry.js +5 -12
- package/dist/ui/entry.js.map +1 -1
- package/dist/ui/payload.d.ts +14 -0
- package/dist/ui/payload.d.ts.map +1 -1
- package/dist/ui/payload.js +23 -0
- package/dist/ui/payload.js.map +1 -1
- package/dist/workspace-roots.d.ts +23 -0
- package/dist/workspace-roots.d.ts.map +1 -0
- package/dist/workspace-roots.js +85 -0
- package/dist/workspace-roots.js.map +1 -0
- package/package.json +21 -16
- package/src/capabilities.ts +4 -2
- package/src/diagnostics.ts +99 -39
- package/src/generated/resources.ts +15 -3
- package/src/generated/ui-bundle.ts +2 -2
- package/src/http.ts +28 -0
- package/src/index.ts +8 -14
- package/src/options.ts +79 -0
- package/src/reference-cheatsheet.ts +27 -2
- package/src/schema-vocab.ts +9 -0
- package/src/schemas.ts +2 -0
- package/src/server.ts +229 -84
- package/src/ui/entry.ts +6 -11
- package/src/ui/payload.ts +26 -0
- package/src/workspace-roots.ts +99 -0
package/src/ui/payload.ts
CHANGED
|
@@ -13,6 +13,8 @@ export interface PreviewPayload {
|
|
|
13
13
|
theme?: string;
|
|
14
14
|
now?: string;
|
|
15
15
|
width?: number;
|
|
16
|
+
/** The render-time non-working display; absent lets the file's own key apply. */
|
|
17
|
+
nonWorking?: 'hide' | 'show';
|
|
16
18
|
locale?: string;
|
|
17
19
|
showLinks?: boolean;
|
|
18
20
|
showMinimap?: boolean;
|
|
@@ -53,11 +55,35 @@ export function parsePreviewFromArguments(
|
|
|
53
55
|
args: Record<string, unknown> | undefined,
|
|
54
56
|
): PreviewPayload | undefined {
|
|
55
57
|
if (!args || typeof args.source !== 'string') return undefined;
|
|
58
|
+
// A `timezone` without a `now` names a date only the server can resolve;
|
|
59
|
+
// wait for the result payload, which carries it.
|
|
60
|
+
if (typeof args.timezone === 'string' && typeof args.now !== 'string') return undefined;
|
|
56
61
|
return {
|
|
57
62
|
source: args.source,
|
|
58
63
|
theme: typeof args.theme === 'string' ? args.theme : undefined,
|
|
59
64
|
now: typeof args.now === 'string' ? args.now : undefined,
|
|
60
65
|
width: typeof args.width === 'number' ? args.width : undefined,
|
|
66
|
+
nonWorking:
|
|
67
|
+
args.nonWorking === 'hide' || args.nonWorking === 'show' ? args.nonWorking : undefined,
|
|
61
68
|
locale: 'en-US',
|
|
62
69
|
};
|
|
63
70
|
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The shell's non-working dropdown value for a payload. A payload with no
|
|
74
|
+
* `nonWorking` maps to `'file'` (no override), so the file's own key applies;
|
|
75
|
+
* anything other than `hide` or `show` is treated as unset.
|
|
76
|
+
*/
|
|
77
|
+
export function toNonWorkingOverride(nonWorking: unknown): 'file' | 'hide' | 'show' {
|
|
78
|
+
return nonWorking === 'hide' || nonWorking === 'show' ? nonWorking : 'file';
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* The shell's now-line value for a payload's `now`. Omitted means today; `-`
|
|
83
|
+
* (the CLI's `--now -` sentinel, also accepted by the `render` tool) hides the
|
|
84
|
+
* now-line; a date passes through.
|
|
85
|
+
*/
|
|
86
|
+
export function toNowOverride(now: unknown): string {
|
|
87
|
+
if (now === '-') return 'hide';
|
|
88
|
+
return typeof now === 'string' && now.length > 0 ? now : 'today';
|
|
89
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// Where the server's file tools resolve paths.
|
|
2
|
+
//
|
|
3
|
+
// An explicit root (`--root`, or `allowedRoot` from an embedder) always wins
|
|
4
|
+
// and the client is never asked. Without one, the server asks a client that
|
|
5
|
+
// advertises the MCP `roots` capability for its roots, because a harness may
|
|
6
|
+
// start the server somewhere other than the project (a plugin cache, the app
|
|
7
|
+
// bundle). A client without roots, an empty or non-`file://` list, or a failed
|
|
8
|
+
// `roots/list` falls back to `process.cwd()`.
|
|
9
|
+
//
|
|
10
|
+
// Multiple roots: every root is allowed, and relative paths resolve against
|
|
11
|
+
// the first. The answer is cached until the client sends
|
|
12
|
+
// `notifications/roots/list_changed`.
|
|
13
|
+
//
|
|
14
|
+
// Spec: specs/mcp.md "Shared tool contract".
|
|
15
|
+
|
|
16
|
+
import * as path from 'node:path';
|
|
17
|
+
import { fileURLToPath } from 'node:url';
|
|
18
|
+
import type { McpServer } from '@modelcontextprotocol/server';
|
|
19
|
+
|
|
20
|
+
export interface WorkspaceRoots {
|
|
21
|
+
/** Relative paths resolve against this root; `list` and default export output use it. */
|
|
22
|
+
readonly primary: string;
|
|
23
|
+
/** Every root a path may land in. Always contains `primary`. */
|
|
24
|
+
readonly allowed: readonly string[];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Bounds a `roots/list` round trip so a client that never answers can't hang a tool call. */
|
|
28
|
+
const LIST_ROOTS_TIMEOUT_MS = 5_000;
|
|
29
|
+
|
|
30
|
+
export function staticRoots(root: string): WorkspaceRoots {
|
|
31
|
+
const abs = path.resolve(root);
|
|
32
|
+
return { primary: abs, allowed: [abs] };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Converts a `roots/list` result to filesystem roots. Non-`file://` and
|
|
37
|
+
* malformed URIs are skipped; duplicates collapse. Returns `undefined` when
|
|
38
|
+
* nothing usable remains.
|
|
39
|
+
*/
|
|
40
|
+
export function rootsFromUris(uris: readonly string[]): WorkspaceRoots | undefined {
|
|
41
|
+
const paths: string[] = [];
|
|
42
|
+
for (const uri of uris) {
|
|
43
|
+
let abs: string;
|
|
44
|
+
try {
|
|
45
|
+
if (!uri.startsWith('file:')) continue;
|
|
46
|
+
abs = path.resolve(fileURLToPath(uri));
|
|
47
|
+
} catch {
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (!paths.includes(abs)) paths.push(abs);
|
|
51
|
+
}
|
|
52
|
+
return paths.length > 0 ? { primary: paths[0], allowed: paths } : undefined;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Returns a function yielding the current roots for `server`. With
|
|
57
|
+
* `configuredRoot` set the result is fixed; otherwise it follows the client's
|
|
58
|
+
* roots as described above. Never rejects.
|
|
59
|
+
*/
|
|
60
|
+
export function createWorkspaceRoots(
|
|
61
|
+
server: McpServer,
|
|
62
|
+
configuredRoot: string | undefined,
|
|
63
|
+
): () => Promise<WorkspaceRoots> {
|
|
64
|
+
if (configuredRoot !== undefined) {
|
|
65
|
+
const fixed = staticRoots(configuredRoot);
|
|
66
|
+
return async () => fixed;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
let cached: Promise<WorkspaceRoots> | undefined;
|
|
70
|
+
server.server.setNotificationHandler('notifications/roots/list_changed', () => {
|
|
71
|
+
cached = undefined;
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
async function fetchRoots(): Promise<WorkspaceRoots> {
|
|
75
|
+
const fallback = staticRoots(process.cwd());
|
|
76
|
+
if (!server.server.getClientCapabilities()?.roots) return fallback;
|
|
77
|
+
try {
|
|
78
|
+
const { roots } = await server.server.listRoots(undefined, {
|
|
79
|
+
timeout: LIST_ROOTS_TIMEOUT_MS,
|
|
80
|
+
});
|
|
81
|
+
return rootsFromUris(roots.map((r) => r.uri)) ?? fallback;
|
|
82
|
+
} catch {
|
|
83
|
+
return fallback;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return () => {
|
|
88
|
+
cached ??= fetchRoots();
|
|
89
|
+
return cached;
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Whether the absolute path `abs` is one of the allowed roots or inside one. */
|
|
94
|
+
export function isInsideRoots(abs: string, roots: WorkspaceRoots): boolean {
|
|
95
|
+
return roots.allowed.some((root) => {
|
|
96
|
+
const prefix = root.endsWith(path.sep) ? root : root + path.sep;
|
|
97
|
+
return abs === root || abs.startsWith(prefix);
|
|
98
|
+
});
|
|
99
|
+
}
|