@nowline/mcp 0.8.5 → 0.9.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.
Files changed (67) hide show
  1. package/dist/branding.d.ts +3 -3
  2. package/dist/branding.js +3 -3
  3. package/dist/capabilities.d.ts.map +1 -1
  4. package/dist/capabilities.js +2 -9
  5. package/dist/capabilities.js.map +1 -1
  6. package/dist/diagnostics.d.ts +46 -18
  7. package/dist/diagnostics.d.ts.map +1 -1
  8. package/dist/diagnostics.js +77 -25
  9. package/dist/diagnostics.js.map +1 -1
  10. package/dist/generated/resources.d.ts.map +1 -1
  11. package/dist/generated/resources.js +15 -3
  12. package/dist/generated/resources.js.map +1 -1
  13. package/dist/generated/ui-bundle.d.ts.map +1 -1
  14. package/dist/generated/ui-bundle.js +2 -2
  15. package/dist/generated/ui-bundle.js.map +1 -1
  16. package/dist/http.d.ts +4 -0
  17. package/dist/http.d.ts.map +1 -0
  18. package/dist/http.js +23 -0
  19. package/dist/http.js.map +1 -0
  20. package/dist/index.js +7 -13
  21. package/dist/index.js.map +1 -1
  22. package/dist/options.d.ts +26 -0
  23. package/dist/options.d.ts.map +1 -0
  24. package/dist/options.js +55 -0
  25. package/dist/options.js.map +1 -0
  26. package/dist/prompts.d.ts +1 -1
  27. package/dist/prompts.d.ts.map +1 -1
  28. package/dist/prompts.js +6 -6
  29. package/dist/prompts.js.map +1 -1
  30. package/dist/reference-cheatsheet.d.ts +1 -1
  31. package/dist/reference-cheatsheet.d.ts.map +1 -1
  32. package/dist/reference-cheatsheet.js +27 -2
  33. package/dist/reference-cheatsheet.js.map +1 -1
  34. package/dist/schema-vocab.d.ts +5 -3
  35. package/dist/schema-vocab.d.ts.map +1 -1
  36. package/dist/schema-vocab.js +9 -0
  37. package/dist/schema-vocab.js.map +1 -1
  38. package/dist/schemas.d.ts +9 -7
  39. package/dist/schemas.d.ts.map +1 -1
  40. package/dist/schemas.js +2 -0
  41. package/dist/schemas.js.map +1 -1
  42. package/dist/server.d.ts +1 -1
  43. package/dist/server.d.ts.map +1 -1
  44. package/dist/server.js +151 -59
  45. package/dist/server.js.map +1 -1
  46. package/dist/ui/entry.js +5 -12
  47. package/dist/ui/entry.js.map +1 -1
  48. package/dist/ui/payload.d.ts +14 -0
  49. package/dist/ui/payload.d.ts.map +1 -1
  50. package/dist/ui/payload.js +23 -0
  51. package/dist/ui/payload.js.map +1 -1
  52. package/package.json +24 -17
  53. package/src/branding.ts +3 -3
  54. package/src/capabilities.ts +4 -2
  55. package/src/diagnostics.ts +99 -39
  56. package/src/generated/resources.ts +15 -3
  57. package/src/generated/ui-bundle.ts +2 -2
  58. package/src/http.ts +28 -0
  59. package/src/index.ts +7 -14
  60. package/src/options.ts +79 -0
  61. package/src/prompts.ts +7 -7
  62. package/src/reference-cheatsheet.ts +27 -2
  63. package/src/schema-vocab.ts +9 -0
  64. package/src/schemas.ts +2 -0
  65. package/src/server.ts +200 -60
  66. package/src/ui/entry.ts +6 -11
  67. package/src/ui/payload.ts +26 -0
package/src/http.ts ADDED
@@ -0,0 +1,28 @@
1
+ // Streamable HTTP request handler shared by `npx @nowline/mcp --port` and
2
+ // `nowline --mcp --port`.
3
+ //
4
+ // The HTTP transport is stateless (no session IDs), so every request gets its
5
+ // own McpServer and transport. Sharing one pair across requests could deliver
6
+ // one HTTP client's messages to another, and since
7
+ // @modelcontextprotocol/server 2.3.0 the SDK throws on reuse.
8
+
9
+ import type { IncomingMessage, ServerResponse } from 'node:http';
10
+ import { NodeStreamableHTTPServerTransport } from '@modelcontextprotocol/node';
11
+ import type { McpServer } from '@modelcontextprotocol/server';
12
+
13
+ export function createMcpHttpHandler(
14
+ createServer: () => McpServer,
15
+ ): (req: IncomingMessage, res: ServerResponse) => Promise<void> {
16
+ return async (req, res) => {
17
+ const server = createServer();
18
+ const transport = new NodeStreamableHTTPServerTransport({
19
+ sessionIdGenerator: undefined,
20
+ });
21
+ res.on('close', () => {
22
+ void transport.close();
23
+ void server.close();
24
+ });
25
+ await server.connect(transport);
26
+ await transport.handleRequest(req, res);
27
+ };
28
+ }
package/src/index.ts CHANGED
@@ -15,8 +15,8 @@ import { readFileSync } from 'node:fs';
15
15
  import { createServer } from 'node:http';
16
16
  import { dirname, resolve } from 'node:path';
17
17
  import { fileURLToPath } from 'node:url';
18
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
19
- import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
18
+ import { StdioServerTransport } from '@modelcontextprotocol/server/stdio';
19
+ import { createMcpHttpHandler } from './http.js';
20
20
  import { expandRootPath } from './root-path.js';
21
21
  import { createMcpServer } from './server.js';
22
22
 
@@ -96,22 +96,15 @@ if (help) {
96
96
  // (optional field left unset) collapses to undefined → cwd fallback, inline.
97
97
  const expandedRoot = expandRootPath(root);
98
98
 
99
- const server = createMcpServer({
99
+ const serverOptions = {
100
100
  allowedRoot: expandedRoot,
101
101
  rootConfigured: expandedRoot !== undefined,
102
102
  version: PKG_VERSION,
103
- });
103
+ };
104
104
 
105
105
  if (port !== undefined) {
106
- // Streamable HTTP transport — stateless (no session management).
107
- const transport = new StreamableHTTPServerTransport({
108
- sessionIdGenerator: undefined,
109
- });
110
- await server.connect(transport);
111
-
112
- const httpServer = createServer(async (req, res) => {
113
- await transport.handleRequest(req, res);
114
- });
106
+ // Streamable HTTP transport — stateless, one server per request.
107
+ const httpServer = createServer(createMcpHttpHandler(() => createMcpServer(serverOptions)));
115
108
 
116
109
  httpServer.listen(port, '127.0.0.1', () => {
117
110
  process.stderr.write(
@@ -127,5 +120,5 @@ if (port !== undefined) {
127
120
  } else {
128
121
  // Default: stdio transport.
129
122
  const transport = new StdioServerTransport();
130
- await server.connect(transport);
123
+ await createMcpServer(serverOptions).connect(transport);
131
124
  }
package/src/options.ts ADDED
@@ -0,0 +1,79 @@
1
+ // How an MCP tool call's arguments become render options: the shared schema
2
+ // from `@nowline/config`, over one `argument` layer (specs/surface-options.md
3
+ // §3.2). MCP keeps its own wording for a bad value.
4
+
5
+ import {
6
+ type OptionError,
7
+ type OptionLayer,
8
+ resolveSurfaceOptions,
9
+ type SurfaceOptions,
10
+ } from '@nowline/config';
11
+ import { type NormalizedZone, normalizeZone, type ThemeName, TimezoneError } from '@nowline/layout';
12
+ import { InputRequiredError } from './diagnostics.js';
13
+
14
+ /** The option arguments a tool takes; each tool offers the subset it needs. */
15
+ export interface McpOptionArguments {
16
+ theme?: string;
17
+ width?: number;
18
+ now?: string;
19
+ /** Zone for the default now-line date; accepts what the CLI's `--timezone` does. */
20
+ timezone?: string;
21
+ nonWorking?: string;
22
+ pageSize?: string;
23
+ }
24
+
25
+ export interface McpResolvedOptions {
26
+ options: SurfaceOptions;
27
+ /** The theme argument, when the caller passed one; the preview picks its own otherwise. */
28
+ theme?: ThemeName;
29
+ }
30
+
31
+ /**
32
+ * Resolve a tool's option arguments. Throws `InputRequiredError` (the tool's
33
+ * input-error result) for the first bad value. An omitted `now` draws the
34
+ * now-line at today's civil date in the `timezone` argument's zone, or the
35
+ * server's local zone without one, as on every other surface. `clock` is a
36
+ * test seam.
37
+ */
38
+ export function resolveMcpOptions(
39
+ args: McpOptionArguments,
40
+ clock?: () => Date,
41
+ ): McpResolvedOptions {
42
+ const zone = readZone(args.timezone);
43
+ const layer: OptionLayer = {
44
+ source: (key) => ({ kind: 'argument', name: key }),
45
+ values: {
46
+ theme: args.theme,
47
+ width: args.width,
48
+ now: args.now,
49
+ nonWorking: args.nonWorking,
50
+ pageSize: args.pageSize,
51
+ },
52
+ };
53
+ const { options, errors } = resolveSurfaceOptions([layer], { theme: 'light' }, { zone, clock });
54
+ const [first] = errors;
55
+ if (first) throw new InputRequiredError(describeOptionError(first));
56
+ return { options, theme: args.theme === undefined ? undefined : options.theme };
57
+ }
58
+
59
+ // `--timezone`'s forms, through the same parser; the parser's text names the CLI.
60
+ function readZone(raw: string | undefined): NormalizedZone | undefined {
61
+ if (raw === undefined) return undefined;
62
+ try {
63
+ return normalizeZone(raw);
64
+ } catch (err) {
65
+ if (!(err instanceof TimezoneError)) throw err;
66
+ throw new InputRequiredError(`Invalid timezone: ${err.message.replace(/^nowline: /, '')}`);
67
+ }
68
+ }
69
+
70
+ function describeOptionError(error: OptionError): string {
71
+ const raw = String(error.raw);
72
+ if (error.key === 'theme' && error.expected) {
73
+ return `Unknown theme "${raw}". Expected one of: ${error.expected.join(', ')}.`;
74
+ }
75
+ if (error.expected) {
76
+ return `Invalid ${error.key} "${raw}". Expected one of: ${error.expected.join(', ')}.`;
77
+ }
78
+ return `Invalid ${error.key} "${raw}": ${error.detail}`;
79
+ }
package/src/prompts.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  // MCP prompt registrations for @nowline/mcp.
2
2
  // Wired into the server via registerPrompts(server).
3
3
 
4
- import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
4
+ import type { McpServer } from '@modelcontextprotocol/server';
5
5
  import { z } from 'zod';
6
6
 
7
7
  export function registerPrompts(server: McpServer): void {
@@ -13,13 +13,13 @@ export function registerPrompts(server: McpServer): void {
13
13
  title: 'Create Roadmap from Description',
14
14
  description:
15
15
  'Generate a new .nowline roadmap from a plain-English description. Composes the DSL reference and example files so the LLM has full context.',
16
- argsSchema: {
16
+ argsSchema: z.object({
17
17
  description: z
18
18
  .string()
19
19
  .describe(
20
20
  'Plain-English description of the roadmap you want to create (teams, timeline, key milestones, etc.).',
21
21
  ),
22
- },
22
+ }),
23
23
  },
24
24
  ({ description }) => ({
25
25
  messages: [
@@ -64,7 +64,7 @@ export function registerPrompts(server: McpServer): void {
64
64
  title: 'Fix Roadmap Diagnostics',
65
65
  description:
66
66
  'Fix validation errors in a .nowline file. Describe the validate→fix→re-validate loop keyed on NL.E#### diagnostic codes.',
67
- argsSchema: {
67
+ argsSchema: z.object({
68
68
  source: z
69
69
  .string()
70
70
  .describe('The .nowline source text that contains validation errors.'),
@@ -74,7 +74,7 @@ export function registerPrompts(server: McpServer): void {
74
74
  .describe(
75
75
  'JSON array of diagnostic objects from the validate tool. Omit to let the LLM call validate itself.',
76
76
  ),
77
- },
77
+ }),
78
78
  },
79
79
  ({ source, diagnostics }) => {
80
80
  const diagSection = diagnostics
@@ -113,7 +113,7 @@ export function registerPrompts(server: McpServer): void {
113
113
  title: 'Convert to Nowline',
114
114
  description:
115
115
  'Convert a gantt/timeline from another format (Mermaid gantt, MS Project, Excel, Google Sheets, CSV) into Nowline DSL. Uses the conversion guide resource for format-specific rules.',
116
- argsSchema: {
116
+ argsSchema: z.object({
117
117
  source: z
118
118
  .string()
119
119
  .describe('The source content to convert (paste the raw text/CSV/XML here).'),
@@ -130,7 +130,7 @@ export function registerPrompts(server: McpServer): void {
130
130
  .describe(
131
131
  'Source format hint. Use "auto" (default) to let the LLM detect the format.',
132
132
  ),
133
- },
133
+ }),
134
134
  },
135
135
  ({ source, from }) => {
136
136
  const formatHint =
@@ -19,13 +19,13 @@ swimlane <id> "<title>"
19
19
  item <id> "<title>" duration:3w
20
20
  \`\`\`
21
21
 
22
- Entity types: \`swimlane\`, \`group\`, \`parallel\`, \`item\`, \`milestone\`, \`anchor\`.
22
+ Entity types: \`swimlane\`, \`group\`, \`parallel\`, \`item\`, \`wave\`, \`milestone\`, \`anchor\`.
23
23
 
24
24
  Items need \`duration:\` or \`size:\`. Use 2-space indentation under parents.
25
25
 
26
26
  ## Item properties
27
27
 
28
- \`duration:\` (or \`size:\`), \`status:\`, \`remaining:\`, \`owner:\`, \`after:\`, \`before:\`, \`labels:\`, \`link:\`, \`style:\`.
28
+ \`duration:\` (or \`size:\`), \`status:\`, \`remaining:\`, \`owner:\`, \`wave:\`, \`after:\`, \`before:\`, \`labels:\`, \`link:\`, \`style:\`.
29
29
 
30
30
  There is NO \`progress:\` or \`color:\` key. Show completion with \`status:\` + \`remaining:\`; set visuals with a \`style:\` reference (see full reference).
31
31
 
@@ -39,6 +39,31 @@ item api "API redesign" duration:4w status:in-progress remaining:40%
39
39
 
40
40
  \`remaining:\` is the work *left*, written as a percent (\`40%\`) or an effort literal (\`1w\`). So an item that is 60% complete is \`remaining:40%\`. Omit \`remaining:\` for a fully open or fully done bar.
41
41
 
42
+ ## Waves
43
+
44
+ \`\`\`
45
+ wave build "Build"
46
+ wave launch "Launch"
47
+ swimlane api "API"
48
+ item core "Core" duration:3w wave:build
49
+ group wave:launch
50
+ item docs "Docs" duration:1w
51
+ milestone ship "Ship" after:launch
52
+ \`\`\`
53
+
54
+ A wave is a strict barrier across every swimlane: no item in the next wave starts until every item in this one ends. Items without \`wave:\` are background work, never held back, drawn hatched.
55
+
56
+ ## Working calendar
57
+
58
+ \`\`\`
59
+ config
60
+
61
+ non-working thanksgiving "Thanksgiving" start:2026-11-26 end:2026-11-27
62
+ working crunch "Crunch Saturday" date:2026-01-10
63
+ \`\`\`
64
+
65
+ \`non-working\` takes \`date:\`, \`start:\` with \`end:\`, or \`every:\` (weekday names, \`every:[sat, sun]\`); \`working\` gives days back. Durations count working days only. \`default roadmap non-working:hide\` (the default) collapses closed days to a seam; \`show\` draws them as bands. A dated declaration with a title is labelled in the marker row: a closure as \`Thanksgiving · 2d\` (working days removed), a \`working\` one as \`Crunch Saturday · +1d\` (days added, no cut line). Titles on \`every:\` recurrences are not drawn.
66
+
42
67
  ## Config / includes (before roadmap)
43
68
 
44
69
  \`config\` blocks, \`include "path.nowline"\` — see full reference for ordering rules.
@@ -31,9 +31,17 @@ export const SCHEMA_VOCABULARY = {
31
31
  'label',
32
32
  'size',
33
33
  'status',
34
+ 'wave',
34
35
  'milestone',
35
36
  'footnote',
36
37
  ],
38
+ // Keywords of the `config` section. `non-working` and `working` declare
39
+ // calendar days (specs/working-calendar.md); the others configure the
40
+ // chart's scale, calendar, styles, symbols and per-entity defaults.
41
+ configKeywords: ['scale', 'calendar', 'style', 'symbol', 'default', 'non-working', 'working'],
42
+ // Properties of a `non-working` / `working` declaration: one day (`date`),
43
+ // a range (`start`, `end`), or a recurring weekday (`every`).
44
+ calendarDeclarationKeys: ['date', 'start', 'end', 'every'],
37
45
  // Properties valid on `item` declarations: item-specific keys plus the
38
46
  // universal properties (`labels`, `link`, `style`, `description`).
39
47
  // Visual treatment is a single `style:` reference — raw color/style
@@ -41,6 +49,7 @@ export const SCHEMA_VOCABULARY = {
41
49
  itemPropertyKeys: [
42
50
  'status',
43
51
  'owner',
52
+ 'wave',
44
53
  'after',
45
54
  'before',
46
55
  'size',
package/src/schemas.ts CHANGED
@@ -105,4 +105,6 @@ export const SchemaOutputSchema = z.object({
105
105
  directiveKeys: z.array(z.string()),
106
106
  entityTypes: z.array(z.string()),
107
107
  itemPropertyKeys: z.array(z.string()),
108
+ configKeywords: z.array(z.string()),
109
+ calendarDeclarationKeys: z.array(z.string()),
108
110
  });