@nebutra/mcp 0.1.3 → 2.0.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/CHANGELOG.md +11 -0
- package/bin/nebutra-mcp.js +26 -3
- package/bin/nebutra-tool-protocol.js +27 -0
- package/dist/{chunk-25TNKUCR.js → chunk-7A2G4AGA.js} +0 -81
- package/dist/chunk-EMIF5K3P.js +83 -0
- package/dist/cli.js +2 -1
- package/dist/index-BD_aLZKz.d.ts +158 -0
- package/dist/index.d.ts +4 -157
- package/dist/index.js +7 -5
- package/dist/middleware/index.d.ts +2 -0
- package/dist/middleware/index.js +12 -0
- package/dist/server/contextServer.js +0 -0
- package/package.json +38 -15
- package/.turbo/turbo-build.log +0 -22
- package/.turbo/turbo-test.log +0 -14
- package/.turbo/turbo-typecheck.log +0 -4
- package/AGENTS.md +0 -62
- package/examples/doctor.ts +0 -14
- package/examples/local-consent.ts +0 -30
- package/examples/read-debug.ts +0 -5
- package/src/__tests__/registry-and-context.test.ts +0 -295
- package/src/cli.ts +0 -47
- package/src/client/index.ts +0 -1
- package/src/client/mcpClient.ts +0 -206
- package/src/consent.ts +0 -46
- package/src/debug.ts +0 -32
- package/src/host.ts +0 -314
- package/src/index.ts +0 -30
- package/src/middleware/index.ts +0 -10
- package/src/middleware/mcpMiddleware.ts +0 -150
- package/src/registry/index.ts +0 -1
- package/src/registry/serverRegistry.ts +0 -165
- package/src/server/contextServer.ts +0 -211
- package/src/server/index.ts +0 -6
- package/src/server/internalServers.ts +0 -72
- package/src/server/uiAgentServer.ts +0 -167
- package/src/types.ts +0 -135
- package/tsconfig.json +0 -9
- package/tsconfig.tsbuildinfo +0 -1
- package/tsup.config.ts +0 -17
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nebutra/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"nebutra": {
|
|
8
|
+
"graph": "runtime",
|
|
8
9
|
"status": "wip",
|
|
9
10
|
"productionReady": false,
|
|
10
11
|
"requires": [
|
|
@@ -20,29 +21,36 @@
|
|
|
20
21
|
"surface": "tool-protocol",
|
|
21
22
|
"summary": "MCP server primitives (Model Context Protocol)"
|
|
22
23
|
},
|
|
23
|
-
"main": "./
|
|
24
|
-
"types": "./
|
|
24
|
+
"main": "./dist/index.js",
|
|
25
|
+
"types": "./dist/index.d.ts",
|
|
25
26
|
"exports": {
|
|
26
|
-
".":
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
".": {
|
|
28
|
+
"types": "./dist/index.d.ts",
|
|
29
|
+
"import": "./dist/index.js",
|
|
30
|
+
"default": "./dist/index.js"
|
|
31
|
+
},
|
|
32
|
+
"./middleware": {
|
|
33
|
+
"types": "./dist/middleware/index.d.ts",
|
|
34
|
+
"import": "./dist/middleware/index.js",
|
|
35
|
+
"default": "./dist/middleware/index.js"
|
|
36
|
+
}
|
|
30
37
|
},
|
|
31
38
|
"bin": {
|
|
32
|
-
"nebutra-mcp": "./
|
|
33
|
-
"nebutra-tool-protocol": "./
|
|
39
|
+
"nebutra-mcp": "./bin/nebutra-mcp.js",
|
|
40
|
+
"nebutra-tool-protocol": "./bin/nebutra-tool-protocol.js"
|
|
34
41
|
},
|
|
35
42
|
"dependencies": {
|
|
36
43
|
"@modelcontextprotocol/sdk": "1.29.0",
|
|
37
44
|
"zod": "^4.3.6",
|
|
38
|
-
"@nebutra/capability-kit": "0.
|
|
39
|
-
"@nebutra/errors": "0.
|
|
40
|
-
"@nebutra/event-log": "0.
|
|
41
|
-
"@nebutra/
|
|
42
|
-
"@nebutra/trace-store": "0.1.2"
|
|
45
|
+
"@nebutra/capability-kit": "2.0.0",
|
|
46
|
+
"@nebutra/errors": "2.0.0",
|
|
47
|
+
"@nebutra/event-log": "2.0.0",
|
|
48
|
+
"@nebutra/trace-store": "2.0.0"
|
|
43
49
|
},
|
|
44
50
|
"devDependencies": {
|
|
45
|
-
"
|
|
51
|
+
"tsup": "^8.5.1",
|
|
52
|
+
"typescript": "^5.9.3",
|
|
53
|
+
"vitest": "^4.1.4"
|
|
46
54
|
},
|
|
47
55
|
"homepage": "https://github.com/Nebutra/Nebutra-Sailor/tree/main/packages/ai/mcp#readme",
|
|
48
56
|
"repository": {
|
|
@@ -56,6 +64,21 @@
|
|
|
56
64
|
"publishConfig": {
|
|
57
65
|
"access": "public"
|
|
58
66
|
},
|
|
67
|
+
"files": [
|
|
68
|
+
"bin",
|
|
69
|
+
"dist",
|
|
70
|
+
"README.md",
|
|
71
|
+
"LICENSE",
|
|
72
|
+
"CHANGELOG.md"
|
|
73
|
+
],
|
|
74
|
+
"peerDependencies": {
|
|
75
|
+
"@nebutra/ui": "2.0.0"
|
|
76
|
+
},
|
|
77
|
+
"peerDependenciesMeta": {
|
|
78
|
+
"@nebutra/ui": {
|
|
79
|
+
"optional": true
|
|
80
|
+
}
|
|
81
|
+
},
|
|
59
82
|
"scripts": {
|
|
60
83
|
"build": "tsup",
|
|
61
84
|
"dev": "tsup --watch",
|
package/.turbo/turbo-build.log
DELETED
|
@@ -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
|
package/.turbo/turbo-test.log
DELETED
|
@@ -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
|
-
|
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`
|
package/examples/doctor.ts
DELETED
|
@@ -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`);
|
package/examples/read-debug.ts
DELETED
|
@@ -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
|
-
}
|
package/src/client/index.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { MCPClient, mcpClient } from "./mcpClient";
|