@nebutra/mcp 0.1.3 → 0.1.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nebutra/mcp",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -20,13 +20,14 @@
20
20
  "surface": "tool-protocol",
21
21
  "summary": "MCP server primitives (Model Context Protocol)"
22
22
  },
23
- "main": "./src/index.ts",
24
- "types": "./src/index.ts",
23
+ "main": "./dist/index.js",
24
+ "types": "./dist/index.d.ts",
25
25
  "exports": {
26
- ".": "./src/index.ts",
27
- "./client": "./src/client/index.ts",
28
- "./server": "./src/server/index.ts",
29
- "./middleware": "./src/middleware/index.ts"
26
+ ".": {
27
+ "types": "./dist/index.d.ts",
28
+ "import": "./dist/index.js",
29
+ "default": "./dist/index.js"
30
+ }
30
31
  },
31
32
  "bin": {
32
33
  "nebutra-mcp": "./dist/server/contextServer.js",
@@ -35,14 +36,16 @@
35
36
  "dependencies": {
36
37
  "@modelcontextprotocol/sdk": "1.29.0",
37
38
  "zod": "^4.3.6",
38
- "@nebutra/capability-kit": "0.2.2",
39
+ "@nebutra/capability-kit": "0.2.4",
39
40
  "@nebutra/errors": "0.1.2",
40
- "@nebutra/event-log": "0.1.2",
41
- "@nebutra/ui": "0.2.2",
42
- "@nebutra/trace-store": "0.1.2"
41
+ "@nebutra/event-log": "0.1.4",
42
+ "@nebutra/trace-store": "0.1.4",
43
+ "@nebutra/ui": "0.2.2"
43
44
  },
44
45
  "devDependencies": {
45
- "typescript": "^5.9.3"
46
+ "tsup": "^8.5.1",
47
+ "typescript": "^5.9.3",
48
+ "vitest": "^4.1.4"
46
49
  },
47
50
  "homepage": "https://github.com/Nebutra/Nebutra-Sailor/tree/main/packages/ai/mcp#readme",
48
51
  "repository": {
@@ -56,6 +59,12 @@
56
59
  "publishConfig": {
57
60
  "access": "public"
58
61
  },
62
+ "files": [
63
+ "dist",
64
+ "README.md",
65
+ "LICENSE",
66
+ "CHANGELOG.md"
67
+ ],
59
68
  "scripts": {
60
69
  "build": "tsup",
61
70
  "dev": "tsup --watch",
@@ -1,22 +0,0 @@
1
-
2
- > @nebutra/mcp@0.1.3 build /Users/tseka_luk/Documents/Nebutra-SaaS-Lab/Nebutra-Sailor/packages/ai/mcp
3
- > tsup
4
-
5
- CLI Building entry: src/cli.ts, src/index.ts, src/server/contextServer.ts
6
- CLI Using tsconfig: tsconfig.json
7
- CLI tsup v8.5.1
8
- CLI Using tsup config: /Users/tseka_luk/Documents/Nebutra-SaaS-Lab/Nebutra-Sailor/packages/ai/mcp/tsup.config.ts
9
- CLI Target: node18
10
- CLI Cleaning output folder
11
- ESM Build start
12
- ESM dist/index.js 883.00 B
13
- ESM dist/chunk-YMTQFRV2.js 5.25 KB
14
- ESM dist/cli.js 1.47 KB
15
- ESM dist/chunk-25TNKUCR.js 28.60 KB
16
- ESM dist/server/contextServer.js 176.00 B
17
- ESM ⚡️ Build success in 24ms
18
- DTS Build start
19
- DTS ⚡️ Build success in 22181ms
20
- DTS dist/cli.d.ts 13.00 B
21
- DTS dist/server/contextServer.d.ts 1.43 KB
22
- DTS dist/index.d.ts 12.19 KB
@@ -1,14 +0,0 @@
1
-
2
- > @nebutra/mcp@0.1.2 test /Users/tseka_luk/Documents/Nebutra-SaaS-Lab/Nebutra-Sailor/packages/ai/mcp
3
- > vitest run
4
-
5
-
6
- RUN v4.1.4 /Users/tseka_luk/Documents/Nebutra-SaaS-Lab/Nebutra-Sailor/packages/ai/mcp
7
-
8
- ✓ src/__tests__/registry-and-context.test.ts (4 tests) 68ms
9
-
10
- Test Files 1 passed (1)
11
- Tests 4 passed (4)
12
- Start at 09:40:36
13
- Duration 1.41s (transform 252ms, setup 0ms, import 1.06s, tests 68ms, environment 0ms)
14
-
@@ -1,4 +0,0 @@
1
-
2
- > @nebutra/mcp@0.1.3 typecheck /Users/tseka_luk/Documents/Nebutra-SaaS-Lab/Nebutra-Sailor/packages/ai/mcp
3
- > tsc --noEmit
4
-
package/AGENTS.md DELETED
@@ -1,62 +0,0 @@
1
- # AGENTS.md — packages/mcp
2
-
3
- Execution contract for Nebutra's Model Context Protocol package.
4
-
5
- ## Scope
6
-
7
- Applies to everything under `packages/ai/mcp/`.
8
-
9
- This package owns checked-in MCP request and server types, the client runtime,
10
- middleware composition, in-process server registry, and the internal-server
11
- catalog exposed through MCP. It is an integration boundary and is still `wip`,
12
- not a production-complete tool platform.
13
-
14
- ## Source Of Truth
15
-
16
- - Public package surface and subpath exports: `package.json`, `src/index.ts`
17
- - Canonical MCP types, tool definitions, request/response shapes, and server
18
- config contract: `src/types.ts`
19
- - Client runtime and shared singleton: `src/client/mcpClient.ts`,
20
- `src/client/index.ts`
21
- - Middleware composition, access control, audit, and rate-limit behavior:
22
- `src/middleware/mcpMiddleware.ts`, `src/middleware/index.ts`
23
- - Server registry and registration semantics:
24
- `src/registry/serverRegistry.ts`, `src/registry/index.ts`
25
- - Checked-in internal server catalog and registration entrypoint:
26
- `src/server/internalServers.ts`, `src/server/index.ts`
27
- - Context server runtime entrypoint: `src/server/contextServer.ts`
28
-
29
- Treat `README.md` as descriptive only. If package behavior and docs disagree,
30
- update the source files above instead of preserving stale examples.
31
-
32
- ## Contract Boundaries
33
-
34
- - Keep `package.json` exports, `src/index.ts`, and the `bin` contract aligned.
35
- Public changes here affect client, middleware, and server consumers at once.
36
- - Treat `src/types.ts` as the canonical contract for tool definitions, request
37
- envelopes, execution results, and server registration. Tightening those
38
- shapes is a compatibility change.
39
- - Preserve the split between outbound client runtime, middleware pipeline, and
40
- registry/server concerns. Client transport logic belongs in `src/client/`,
41
- middleware semantics in `src/middleware/`, and checked-in server catalogs in
42
- `src/server/` plus `src/registry/`.
43
- - Keep internal server declarations centralized in `src/server/internalServers.ts`.
44
- Do not scatter internal MCP server metadata through apps or docs.
45
- - Respect the package's current `wip` status. The context server binary and
46
- plan-based access control are not complete; do not document or code against
47
- stronger production guarantees than the package actually provides.
48
-
49
- ## Generated And Derived Files
50
-
51
- - `dist/` is derived build output from `tsup`.
52
- - The prepared `dist/server/contextServer.js` binary artifact is generated and
53
- may be a placeholder stub before a full build.
54
- - Do not hand-edit emitted build output or generated CLI artifacts. Update the
55
- source files above and rebuild instead.
56
-
57
- ## Validation
58
-
59
- - Type or middleware/registry/client contract changes:
60
- `pnpm --filter @nebutra/mcp typecheck`
61
- - Server entrypoint, subpath export, or bin changes:
62
- `pnpm --filter @nebutra/mcp build`
@@ -1,5 +0,0 @@
1
- #!/usr/bin/env node
2
- import { startContextServer } from "../src/server/contextServer.js";
3
-
4
- // Starts the stdio MCP server for Cursor / Windsurf.
5
- startContextServer();
@@ -1,14 +0,0 @@
1
- import { McpHost } from "../src/index";
2
-
3
- const host = new McpHost();
4
-
5
- host.connectLocal({
6
- id: "read-only",
7
- name: "Read Only",
8
- description: "Read-only example tools",
9
- manifest: { name: "read-only", version: "1.0.0", scopes: ["project:read"] },
10
- tools: [{ name: "ping", description: "Return pong" }],
11
- handlers: { ping: async () => "pong" },
12
- });
13
-
14
- process.stdout.write(`${JSON.stringify(await host.doctor(), null, 2)}\n`);
@@ -1,30 +0,0 @@
1
- import { InMemoryToolConsentStore, McpHost } from "../src/index";
2
-
3
- const consent = new InMemoryToolConsentStore();
4
- const host = new McpHost({ consent });
5
-
6
- host.connectLocal({
7
- id: "notes",
8
- name: "Notes",
9
- description: "Tenant-scoped note tools",
10
- manifest: { name: "notes", version: "1.0.0", scopes: ["notes:write"] },
11
- tools: [{ name: "create_note", description: "Create a note" }],
12
- handlers: {
13
- create_note: async (args) => ({ id: "note_1", title: args.title }),
14
- },
15
- });
16
-
17
- await consent.grant({
18
- tenantId: "demo",
19
- serverId: "notes",
20
- toolName: "create_note",
21
- scopes: ["notes:write"],
22
- });
23
-
24
- const result = await host.callTool(
25
- "notes:create_note",
26
- { title: "Layer 1" },
27
- { requestId: "example_1", tenantId: "demo" },
28
- );
29
-
30
- process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
@@ -1,5 +0,0 @@
1
- import { readToolDebug } from "../src/index";
2
-
3
- const entries = await readToolDebug(5);
4
-
5
- process.stdout.write(`${JSON.stringify(entries, null, 2)}\n`);
@@ -1,295 +0,0 @@
1
- import fs from "node:fs";
2
- import os from "node:os";
3
- import path from "node:path";
4
- import { fileURLToPath } from "node:url";
5
- import { afterEach, describe, expect, it } from "vitest";
6
- import { MCPClient } from "../client/mcpClient";
7
- import { InMemoryToolConsentStore } from "../consent";
8
- import { readToolDebug } from "../debug";
9
- import { McpHost } from "../host";
10
- import { MCPServerRegistry } from "../registry/serverRegistry";
11
- import { createContextServerHandlers } from "../server/contextServer";
12
- import { createUiAgentMcpServer } from "../server/uiAgentServer";
13
-
14
- const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../../../..");
15
-
16
- describe("tool protocol host", () => {
17
- it("requires manifest and per-tool consent before executing a connected server", async () => {
18
- const consent = new InMemoryToolConsentStore();
19
- const host = new McpHost({ consent });
20
- const traces: string[] = [];
21
-
22
- expect(() =>
23
- host.connectLocal({
24
- id: "unsafe",
25
- name: "Unsafe",
26
- description: "Missing manifest",
27
- tools: [],
28
- handlers: {},
29
- }),
30
- ).toThrow(/manifest/i);
31
-
32
- host.connectLocal({
33
- id: "notes",
34
- name: "Notes",
35
- description: "Tenant-scoped notes tools",
36
- manifest: {
37
- name: "notes",
38
- version: "1.0.0",
39
- scopes: ["notes:write"],
40
- },
41
- tools: [
42
- {
43
- name: "create_note",
44
- description: "Create a note",
45
- parameters: {},
46
- },
47
- ],
48
- handlers: {
49
- create_note: async (args, context) => {
50
- traces.push(`${context.tenantId}:${args.title}`);
51
- return { id: "note_1" };
52
- },
53
- },
54
- });
55
-
56
- await expect(
57
- host.callTool("notes:create_note", { title: "Layer 1" }, { requestId: "r1" }),
58
- ).resolves.toMatchObject({
59
- success: false,
60
- error: expect.stringContaining("tenant"),
61
- });
62
-
63
- const context = { requestId: "r2", tenantId: "tenant_a", userId: "user_1" };
64
- await expect(
65
- host.callTool("notes:create_note", { title: "Layer 1" }, context),
66
- ).resolves.toMatchObject({
67
- success: false,
68
- error: expect.stringContaining("consent"),
69
- });
70
-
71
- await consent.grant({
72
- tenantId: "tenant_a",
73
- userId: "user_1",
74
- serverId: "notes",
75
- toolName: "create_note",
76
- scopes: ["notes:write"],
77
- });
78
-
79
- await expect(
80
- host.callTool("notes:create_note", { title: "Layer 1" }, context),
81
- ).resolves.toMatchObject({
82
- success: true,
83
- result: { id: "note_1" },
84
- });
85
- expect(traces).toEqual(["tenant_a:Layer 1"]);
86
- });
87
-
88
- it("writes protocol debug entries for inspected calls", async () => {
89
- const consent = new InMemoryToolConsentStore();
90
- const host = new McpHost({ consent });
91
- host.connectLocal({
92
- id: "echo",
93
- name: "Echo",
94
- description: "Echo tools",
95
- manifest: { name: "echo", version: "1.0.0", scopes: ["echo:run"] },
96
- tools: [{ name: "say", description: "Say a value" }],
97
- handlers: {
98
- say: async (args) => args,
99
- },
100
- });
101
- await consent.grant({
102
- tenantId: "tenant_b",
103
- serverId: "echo",
104
- toolName: "say",
105
- scopes: ["echo:run"],
106
- });
107
-
108
- await host.callTool("echo:say", { text: "ok" }, { requestId: "r3", tenantId: "tenant_b" });
109
-
110
- await expect(readToolDebug(1)).resolves.toEqual([
111
- expect.objectContaining({ type: "tool_call", serverId: "echo", toolName: "say", ok: true }),
112
- ]);
113
- });
114
- });
115
-
116
- describe("MCP tool registry access control", () => {
117
- it("executes registered local tools only when tenant and plan policies pass", async () => {
118
- const registry = new MCPServerRegistry();
119
- registry.register({
120
- id: "tenant-tools",
121
- name: "Tenant tools",
122
- description: "Tenant-scoped local tools",
123
- endpoint: "local://tenant-tools",
124
- transport: "local",
125
- allowedTenants: ["tenant_allowed"],
126
- tools: [
127
- {
128
- name: "summarize_context",
129
- description: "Summarize tenant context",
130
- parameters: {},
131
- },
132
- {
133
- name: "sync_plan_data",
134
- description: "Sync plan-gated data",
135
- parameters: {},
136
- allowedPlans: ["PRO", "ENTERPRISE"],
137
- },
138
- ],
139
- handlers: {
140
- summarize_context: async (args, context) => ({
141
- args,
142
- tenantId: context.tenantId,
143
- plan: context.plan,
144
- }),
145
- sync_plan_data: async () => ({ synced: true }),
146
- },
147
- });
148
-
149
- const client = new MCPClient(registry);
150
- const freeContext = {
151
- requestId: "req_1",
152
- tenantId: "tenant_allowed",
153
- plan: "FREE",
154
- };
155
-
156
- await expect(
157
- client.executeTool("summarize_context", { topic: "billing" }, freeContext),
158
- ).resolves.toMatchObject({
159
- success: true,
160
- result: { args: { topic: "billing" }, tenantId: "tenant_allowed", plan: "FREE" },
161
- });
162
-
163
- expect(client.listTools(freeContext).map((tool) => tool.name)).toEqual(["summarize_context"]);
164
-
165
- await expect(client.executeTool("sync_plan_data", {}, freeContext)).resolves.toMatchObject({
166
- success: false,
167
- error: "Access denied to tool: sync_plan_data",
168
- });
169
-
170
- await expect(
171
- client.executeTool(
172
- "sync_plan_data",
173
- {},
174
- { requestId: "req_2", tenantId: "tenant_allowed", plan: "PRO" },
175
- ),
176
- ).resolves.toMatchObject({
177
- success: true,
178
- result: { synced: true },
179
- });
180
-
181
- await expect(
182
- client.executeTool(
183
- "summarize_context",
184
- {},
185
- { requestId: "req_3", tenantId: "tenant_blocked", plan: "ENTERPRISE" },
186
- ),
187
- ).resolves.toMatchObject({
188
- success: false,
189
- error: "Access denied to tool: summarize_context",
190
- });
191
- });
192
- });
193
-
194
- describe("Nebutra UI agent MCP tools", () => {
195
- it("searches, validates, and returns migration hints from the generated UI contract", async () => {
196
- const registry = new MCPServerRegistry();
197
- registry.register(createUiAgentMcpServer());
198
- const client = new MCPClient(registry);
199
- const context = { requestId: "ui_1" };
200
-
201
- expect(client.listTools(context).map((tool) => tool.name)).toEqual(
202
- expect.arrayContaining([
203
- "nebutra_ui_search_components",
204
- "nebutra_ui_get_component",
205
- "nebutra_ui_validate_component",
206
- "nebutra_ui_get_migration_hints",
207
- ]),
208
- );
209
-
210
- await expect(
211
- client.executeTool(
212
- "nebutra_ui_search_components",
213
- { query: "button", limit: 1, root: repoRoot },
214
- context,
215
- ),
216
- ).resolves.toMatchObject({
217
- success: true,
218
- result: {
219
- items: [expect.objectContaining({ name: "button" })],
220
- },
221
- });
222
-
223
- await expect(
224
- client.executeTool(
225
- "nebutra_ui_validate_component",
226
- { name: "button", root: repoRoot },
227
- context,
228
- ),
229
- ).resolves.toMatchObject({
230
- success: true,
231
- result: { name: "button", valid: true },
232
- });
233
-
234
- await expect(
235
- client.executeTool(
236
- "nebutra_ui_get_migration_hints",
237
- { name: "button", root: repoRoot },
238
- context,
239
- ),
240
- ).resolves.toMatchObject({
241
- success: true,
242
- result: {
243
- component: "button",
244
- dryRun: true,
245
- hints: expect.arrayContaining([expect.stringContaining("breaking")]),
246
- },
247
- });
248
- });
249
- });
250
-
251
- describe("context server handlers", () => {
252
- const tempRoots: string[] = [];
253
-
254
- afterEach(() => {
255
- for (const root of tempRoots.splice(0)) {
256
- fs.rmSync(root, { recursive: true, force: true });
257
- }
258
- fs.rmSync(path.join(process.cwd(), ".nebutra"), { recursive: true, force: true });
259
- });
260
-
261
- it("lists project resources and returns non-placeholder project structure", async () => {
262
- const projectRoot = fs.mkdtempSync(path.join(os.tmpdir(), "nebutra-mcp-"));
263
- tempRoots.push(projectRoot);
264
- fs.mkdirSync(path.join(projectRoot, "apps", "web", "src", "app", "dashboard"), {
265
- recursive: true,
266
- });
267
- fs.mkdirSync(path.join(projectRoot, "packages", "platform", "db", "prisma"), {
268
- recursive: true,
269
- });
270
- fs.writeFileSync(path.join(projectRoot, "nebutra.config.json"), '{"features":["mcp"]}');
271
- fs.writeFileSync(
272
- path.join(projectRoot, "packages", "platform", "db", "prisma", "schema.prisma"),
273
- "model User { id String @id }",
274
- );
275
-
276
- const handlers = createContextServerHandlers(projectRoot);
277
- const resources = await handlers.listResources();
278
- expect(resources.resources.map((resource) => resource.uri)).toContain(
279
- "file:///nebutra.config.json",
280
- );
281
-
282
- const structure = await handlers.callTool({
283
- name: "get_project_structure",
284
- arguments: {},
285
- });
286
- expect(structure.content[0]?.text).toContain("apps/web/src/app/dashboard");
287
- expect(structure.content[0]?.text).not.toContain("Apps dir not found");
288
-
289
- await expect(
290
- handlers.readResource({ uri: "file:///prisma/schema.prisma" }),
291
- ).resolves.toMatchObject({
292
- contents: [{ text: "model User { id String @id }" }],
293
- });
294
- });
295
- });
package/src/cli.ts DELETED
@@ -1,47 +0,0 @@
1
- import { McpHost, readToolDebug } from "./index";
2
- import { createContextServerHandlers } from "./server/contextServer";
3
-
4
- const command = process.argv[2] ?? "doctor";
5
- const host = new McpHost();
6
- const contextHandlers = createContextServerHandlers(process.cwd());
7
-
8
- host.connectLocal({
9
- id: "nebutra-context",
10
- name: "Nebutra Context",
11
- description: "Read-only project context tools",
12
- manifest: {
13
- name: "nebutra-context",
14
- version: "1.0.0",
15
- scopes: ["project:read"],
16
- },
17
- tools: [
18
- {
19
- name: "get_project_structure",
20
- description: "Returns a bounded app/package routing tree for the current project.",
21
- parameters: {},
22
- },
23
- ],
24
- handlers: {
25
- get_project_structure: async () => contextHandlers.callTool({ name: "get_project_structure" }),
26
- },
27
- });
28
-
29
- if (command === "doctor") {
30
- process.stdout.write(
31
- `${JSON.stringify({ capability: "tool-protocol", ...(await host.doctor()) }, null, 2)}\n`,
32
- );
33
- } else if (command === "debug") {
34
- const id = process.argv[3];
35
- const entries = await readToolDebug(id ? 50 : 10);
36
- process.stdout.write(
37
- `${JSON.stringify({ capability: "tool-protocol", id, entries }, null, 2)}\n`,
38
- );
39
- } else if (command === "inspect") {
40
- const serverId = process.argv[3] ?? "nebutra-context";
41
- process.stdout.write(
42
- `${JSON.stringify({ capability: "tool-protocol", server: host.inspect(serverId) }, null, 2)}\n`,
43
- );
44
- } else {
45
- process.stderr.write(`Unknown tool-protocol command: ${command}\n`);
46
- process.exitCode = 1;
47
- }
@@ -1 +0,0 @@
1
- export { MCPClient, mcpClient } from "./mcpClient";