@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 +21 -12
- 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/bin/nebutra-mcp.js +0 -5
- 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
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
import type { MCPContext, ToolExecutionResult } from "../types";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* MCP Middleware for rate limiting, audit logging, and access control
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
export interface MCPMiddlewareContext extends MCPContext {
|
|
8
|
-
toolName: string;
|
|
9
|
-
arguments: Record<string, unknown>;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export type MCPMiddlewareNext = () => Promise<ToolExecutionResult>;
|
|
13
|
-
|
|
14
|
-
export type MCPMiddleware = (
|
|
15
|
-
ctx: MCPMiddlewareContext,
|
|
16
|
-
next: MCPMiddlewareNext,
|
|
17
|
-
) => Promise<ToolExecutionResult>;
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Rate limiting middleware for MCP calls
|
|
21
|
-
*/
|
|
22
|
-
export function createRateLimitMiddleware(options: {
|
|
23
|
-
maxRequests: number;
|
|
24
|
-
windowMs: number;
|
|
25
|
-
}): MCPMiddleware {
|
|
26
|
-
const requests = new Map<string, { count: number; resetAt: number }>();
|
|
27
|
-
|
|
28
|
-
return async (ctx, next) => {
|
|
29
|
-
const key = `${ctx.tenantId || "anon"}:${ctx.userId || "anon"}`;
|
|
30
|
-
const now = Date.now();
|
|
31
|
-
|
|
32
|
-
let record = requests.get(key);
|
|
33
|
-
if (!record || now > record.resetAt) {
|
|
34
|
-
record = { count: 0, resetAt: now + options.windowMs };
|
|
35
|
-
requests.set(key, record);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
record.count++;
|
|
39
|
-
|
|
40
|
-
if (record.count > options.maxRequests) {
|
|
41
|
-
return {
|
|
42
|
-
success: false,
|
|
43
|
-
error: `MCP rate limit exceeded. Max ${options.maxRequests} requests per ${options.windowMs / 1000}s`,
|
|
44
|
-
duration: 0,
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return next();
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Audit logging middleware
|
|
54
|
-
*/
|
|
55
|
-
export function createAuditMiddleware(options: {
|
|
56
|
-
onLog: (entry: AuditLogEntry) => void | Promise<void>;
|
|
57
|
-
}): MCPMiddleware {
|
|
58
|
-
return async (ctx, next) => {
|
|
59
|
-
const result = await next();
|
|
60
|
-
|
|
61
|
-
const entry: AuditLogEntry = {
|
|
62
|
-
timestamp: new Date().toISOString(),
|
|
63
|
-
requestId: ctx.requestId,
|
|
64
|
-
toolName: ctx.toolName,
|
|
65
|
-
arguments: ctx.arguments,
|
|
66
|
-
success: result.success,
|
|
67
|
-
duration: result.duration,
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
if (ctx.tenantId) entry.tenantId = ctx.tenantId;
|
|
71
|
-
if (ctx.userId) entry.userId = ctx.userId;
|
|
72
|
-
if (result.error) entry.error = result.error;
|
|
73
|
-
|
|
74
|
-
// Don't block on audit logging
|
|
75
|
-
Promise.resolve(options.onLog(entry)).catch(console.error);
|
|
76
|
-
|
|
77
|
-
return result;
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export interface AuditLogEntry {
|
|
82
|
-
timestamp: string;
|
|
83
|
-
requestId: string;
|
|
84
|
-
tenantId?: string;
|
|
85
|
-
userId?: string;
|
|
86
|
-
toolName: string;
|
|
87
|
-
arguments: Record<string, unknown>;
|
|
88
|
-
success: boolean;
|
|
89
|
-
duration: number;
|
|
90
|
-
error?: string;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Tool access control middleware
|
|
95
|
-
*/
|
|
96
|
-
export function createAccessControlMiddleware(options: {
|
|
97
|
-
blockedTools?: string[];
|
|
98
|
-
allowedTools?: string[];
|
|
99
|
-
}): MCPMiddleware {
|
|
100
|
-
return async (ctx, next) => {
|
|
101
|
-
// Check blocked list
|
|
102
|
-
if (options.blockedTools?.includes(ctx.toolName)) {
|
|
103
|
-
return {
|
|
104
|
-
success: false,
|
|
105
|
-
error: `Tool ${ctx.toolName} is blocked`,
|
|
106
|
-
duration: 0,
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// Check allowed list (if specified)
|
|
111
|
-
if (
|
|
112
|
-
options.allowedTools &&
|
|
113
|
-
options.allowedTools.length > 0 &&
|
|
114
|
-
!options.allowedTools.includes(ctx.toolName)
|
|
115
|
-
) {
|
|
116
|
-
return {
|
|
117
|
-
success: false,
|
|
118
|
-
error: `Tool ${ctx.toolName} is not in allowed list`,
|
|
119
|
-
duration: 0,
|
|
120
|
-
};
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
return next();
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Compose multiple middlewares
|
|
129
|
-
*/
|
|
130
|
-
export function composeMCPMiddleware(middlewares: MCPMiddleware[]): MCPMiddleware {
|
|
131
|
-
return async (ctx, finalNext) => {
|
|
132
|
-
let index = -1;
|
|
133
|
-
|
|
134
|
-
const dispatch = async (i: number): Promise<ToolExecutionResult> => {
|
|
135
|
-
if (i <= index) {
|
|
136
|
-
throw new Error("next() called multiple times");
|
|
137
|
-
}
|
|
138
|
-
index = i;
|
|
139
|
-
|
|
140
|
-
const middleware = middlewares[i];
|
|
141
|
-
if (!middleware) {
|
|
142
|
-
return finalNext();
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
return middleware(ctx, () => dispatch(i + 1));
|
|
146
|
-
};
|
|
147
|
-
|
|
148
|
-
return dispatch(0);
|
|
149
|
-
};
|
|
150
|
-
}
|
package/src/registry/index.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { MCPServerRegistry, serverRegistry } from "./serverRegistry";
|
|
@@ -1,165 +0,0 @@
|
|
|
1
|
-
import type { MCPContext, MCPServerConfig, ToolDefinition } from "../types";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Registry for MCP servers and their tools
|
|
5
|
-
* Handles server registration, discovery, and access control
|
|
6
|
-
*/
|
|
7
|
-
export class MCPServerRegistry {
|
|
8
|
-
private servers: Map<string, MCPServerConfig> = new Map();
|
|
9
|
-
private toolIndex: Map<string, string> = new Map(); // tool name -> server id
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Register an MCP server
|
|
13
|
-
*/
|
|
14
|
-
register(config: MCPServerConfig): void {
|
|
15
|
-
this.servers.set(config.id, config);
|
|
16
|
-
|
|
17
|
-
// Index tools for quick lookup
|
|
18
|
-
for (const tool of config.tools) {
|
|
19
|
-
const qualifiedName = `${config.id}:${tool.name}`;
|
|
20
|
-
this.toolIndex.set(qualifiedName, config.id);
|
|
21
|
-
// Also allow short name if unique
|
|
22
|
-
if (!this.toolIndex.has(tool.name)) {
|
|
23
|
-
this.toolIndex.set(tool.name, config.id);
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Unregister an MCP server
|
|
30
|
-
*/
|
|
31
|
-
unregister(serverId: string): boolean {
|
|
32
|
-
const server = this.servers.get(serverId);
|
|
33
|
-
if (!server) return false;
|
|
34
|
-
|
|
35
|
-
// Remove tool index entries
|
|
36
|
-
for (const tool of server.tools) {
|
|
37
|
-
const qualifiedName = `${serverId}:${tool.name}`;
|
|
38
|
-
this.toolIndex.delete(qualifiedName);
|
|
39
|
-
// Only remove short name if it points to this server
|
|
40
|
-
if (this.toolIndex.get(tool.name) === serverId) {
|
|
41
|
-
this.toolIndex.delete(tool.name);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
this.servers.delete(serverId);
|
|
46
|
-
return true;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Get server by ID
|
|
51
|
-
*/
|
|
52
|
-
getServer(serverId: string): MCPServerConfig | undefined {
|
|
53
|
-
return this.servers.get(serverId);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Find server by tool name
|
|
58
|
-
*/
|
|
59
|
-
findServerByTool(toolName: string): MCPServerConfig | undefined {
|
|
60
|
-
const serverId = this.toolIndex.get(toolName);
|
|
61
|
-
if (!serverId) return undefined;
|
|
62
|
-
return this.servers.get(serverId);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Get all registered servers
|
|
67
|
-
*/
|
|
68
|
-
getAllServers(): MCPServerConfig[] {
|
|
69
|
-
return Array.from(this.servers.values());
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Get all available tools
|
|
74
|
-
*/
|
|
75
|
-
getAllTools(): Array<ToolDefinition & { serverId: string }> {
|
|
76
|
-
const tools: Array<ToolDefinition & { serverId: string }> = [];
|
|
77
|
-
for (const server of this.servers.values()) {
|
|
78
|
-
for (const tool of server.tools) {
|
|
79
|
-
tools.push({ ...tool, serverId: server.id });
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
return tools;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Check if a context has access to a server
|
|
87
|
-
*/
|
|
88
|
-
canAccess(serverId: string, context: MCPContext): boolean {
|
|
89
|
-
const server = this.servers.get(serverId);
|
|
90
|
-
if (!server) return false;
|
|
91
|
-
|
|
92
|
-
// Check plan restrictions
|
|
93
|
-
if (server.allowedPlans && server.allowedPlans.length > 0) {
|
|
94
|
-
if (!context.plan || !server.allowedPlans.includes(context.plan)) {
|
|
95
|
-
return false;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// Check tenant restrictions
|
|
100
|
-
if (server.allowedTenants && server.allowedTenants.length > 0) {
|
|
101
|
-
if (!context.tenantId || !server.allowedTenants.includes(context.tenantId)) {
|
|
102
|
-
return false;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
return true;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Check whether a context can execute a specific tool. Tool-level policy
|
|
111
|
-
* narrows the server policy and supports tenant, plan, and permission gates.
|
|
112
|
-
*/
|
|
113
|
-
canAccessTool(toolName: string, context: MCPContext): boolean {
|
|
114
|
-
const server = this.findServerByTool(toolName);
|
|
115
|
-
if (!server || !this.canAccess(server.id, context)) return false;
|
|
116
|
-
|
|
117
|
-
const tool = this.findTool(server, toolName);
|
|
118
|
-
if (!tool) return false;
|
|
119
|
-
|
|
120
|
-
if (tool.allowedPlans && tool.allowedPlans.length > 0) {
|
|
121
|
-
if (!context.plan || !tool.allowedPlans.includes(context.plan)) return false;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
if (tool.allowedTenants && tool.allowedTenants.length > 0) {
|
|
125
|
-
if (!context.tenantId || !tool.allowedTenants.includes(context.tenantId)) return false;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
if (tool.requiredPermissions && tool.requiredPermissions.length > 0) {
|
|
129
|
-
const permissions = new Set(context.permissions ?? []);
|
|
130
|
-
if (!tool.requiredPermissions.every((permission) => permissions.has(permission))) {
|
|
131
|
-
return false;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
return true;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* Get tools accessible by a context
|
|
140
|
-
*/
|
|
141
|
-
getAccessibleTools(context: MCPContext): Array<ToolDefinition & { serverId: string }> {
|
|
142
|
-
const tools: Array<ToolDefinition & { serverId: string }> = [];
|
|
143
|
-
|
|
144
|
-
for (const server of this.servers.values()) {
|
|
145
|
-
if (this.canAccess(server.id, context)) {
|
|
146
|
-
for (const tool of server.tools) {
|
|
147
|
-
if (this.canAccessTool(`${server.id}:${tool.name}`, context)) {
|
|
148
|
-
tools.push({ ...tool, serverId: server.id });
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
return tools;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
private findTool(server: MCPServerConfig, toolName: string): ToolDefinition | undefined {
|
|
158
|
-
return server.tools.find(
|
|
159
|
-
(tool) => tool.name === toolName || `${server.id}:${tool.name}` === toolName,
|
|
160
|
-
);
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// Global registry instance
|
|
165
|
-
export const serverRegistry = new MCPServerRegistry();
|
|
@@ -1,211 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import fs from "node:fs";
|
|
4
|
-
import path from "node:path";
|
|
5
|
-
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
6
|
-
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
7
|
-
import {
|
|
8
|
-
CallToolRequestSchema,
|
|
9
|
-
ListResourcesRequestSchema,
|
|
10
|
-
ListToolsRequestSchema,
|
|
11
|
-
ReadResourceRequestSchema,
|
|
12
|
-
} from "@modelcontextprotocol/sdk/types.js";
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Nebutra MCP Context Server
|
|
16
|
-
* Exposes core project files and state to Cursor / Windsurf allowing AI Agents
|
|
17
|
-
* to instantly understand the Nebutra-Sailor application structure and routing logic.
|
|
18
|
-
*/
|
|
19
|
-
class NebutraContextServer {
|
|
20
|
-
private server: Server;
|
|
21
|
-
private projectRoot: string;
|
|
22
|
-
|
|
23
|
-
constructor(projectRoot?: string) {
|
|
24
|
-
this.projectRoot = projectRoot || process.cwd();
|
|
25
|
-
|
|
26
|
-
this.server = new Server(
|
|
27
|
-
{
|
|
28
|
-
name: "@nebutra/context-server",
|
|
29
|
-
version: "0.1.0",
|
|
30
|
-
},
|
|
31
|
-
{
|
|
32
|
-
capabilities: {
|
|
33
|
-
resources: {},
|
|
34
|
-
tools: {},
|
|
35
|
-
},
|
|
36
|
-
},
|
|
37
|
-
);
|
|
38
|
-
|
|
39
|
-
this.setupResourceHandlers();
|
|
40
|
-
this.setupToolHandlers();
|
|
41
|
-
|
|
42
|
-
this.server.onerror = (_error) => {};
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
private setupResourceHandlers() {
|
|
46
|
-
const handlers = createContextServerHandlers(this.projectRoot);
|
|
47
|
-
|
|
48
|
-
this.server.setRequestHandler(ListResourcesRequestSchema, async () => handlers.listResources());
|
|
49
|
-
|
|
50
|
-
this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
51
|
-
return handlers.readResource(request.params);
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
private setupToolHandlers() {
|
|
56
|
-
const handlers = createContextServerHandlers(this.projectRoot);
|
|
57
|
-
|
|
58
|
-
this.server.setRequestHandler(ListToolsRequestSchema, async () => handlers.listTools());
|
|
59
|
-
|
|
60
|
-
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
61
|
-
return handlers.callTool(request.params);
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
async run() {
|
|
66
|
-
const transport = new StdioServerTransport();
|
|
67
|
-
await this.server.connect(transport);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const RESOURCE_MAP = {
|
|
72
|
-
"file:///nebutra.config.json": {
|
|
73
|
-
relativePath: "nebutra.config.json",
|
|
74
|
-
name: "Nebutra Configuration",
|
|
75
|
-
mimeType: "application/json",
|
|
76
|
-
description:
|
|
77
|
-
"Global configurations for the Nebutra template including enabled features (auth, payments, i18n, db).",
|
|
78
|
-
},
|
|
79
|
-
"file:///prisma/schema.prisma": {
|
|
80
|
-
relativePath: "packages/platform/db/prisma/schema.prisma",
|
|
81
|
-
name: "Database Schema Overview",
|
|
82
|
-
mimeType: "text/plain",
|
|
83
|
-
description: "The core Prisma schema defining the database tables and relationships.",
|
|
84
|
-
},
|
|
85
|
-
} as const;
|
|
86
|
-
|
|
87
|
-
export function createContextServerHandlers(projectRoot: string) {
|
|
88
|
-
return {
|
|
89
|
-
async listResources() {
|
|
90
|
-
return {
|
|
91
|
-
resources: Object.entries(RESOURCE_MAP).map(([uri, resource]) => ({
|
|
92
|
-
uri,
|
|
93
|
-
name: resource.name,
|
|
94
|
-
mimeType: resource.mimeType,
|
|
95
|
-
description: resource.description,
|
|
96
|
-
})),
|
|
97
|
-
};
|
|
98
|
-
},
|
|
99
|
-
|
|
100
|
-
async readResource({ uri }: { uri: string }) {
|
|
101
|
-
const resource = RESOURCE_MAP[uri as keyof typeof RESOURCE_MAP];
|
|
102
|
-
if (!resource) {
|
|
103
|
-
throw new Error(`Resource not found: ${uri}`);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const filePath = path.join(projectRoot, resource.relativePath);
|
|
107
|
-
try {
|
|
108
|
-
const content = fs.readFileSync(filePath, "utf-8");
|
|
109
|
-
return {
|
|
110
|
-
contents: [
|
|
111
|
-
{
|
|
112
|
-
uri,
|
|
113
|
-
mimeType: resource.mimeType,
|
|
114
|
-
text: content,
|
|
115
|
-
},
|
|
116
|
-
],
|
|
117
|
-
};
|
|
118
|
-
} catch {
|
|
119
|
-
throw new Error(`Failed to read the requested resource file at ${filePath}`);
|
|
120
|
-
}
|
|
121
|
-
},
|
|
122
|
-
|
|
123
|
-
async listTools() {
|
|
124
|
-
return {
|
|
125
|
-
tools: [
|
|
126
|
-
{
|
|
127
|
-
name: "get_project_structure",
|
|
128
|
-
description: "Returns a bounded app/package routing tree for the current project.",
|
|
129
|
-
inputSchema: {
|
|
130
|
-
type: "object",
|
|
131
|
-
properties: {},
|
|
132
|
-
required: [],
|
|
133
|
-
},
|
|
134
|
-
},
|
|
135
|
-
],
|
|
136
|
-
};
|
|
137
|
-
},
|
|
138
|
-
|
|
139
|
-
async callTool({ name }: { name: string; arguments?: Record<string, unknown> | undefined }) {
|
|
140
|
-
if (name !== "get_project_structure") {
|
|
141
|
-
throw new Error(`Unknown tool: ${name}`);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
try {
|
|
145
|
-
return {
|
|
146
|
-
content: [
|
|
147
|
-
{
|
|
148
|
-
type: "text",
|
|
149
|
-
text: getProjectStructure(projectRoot),
|
|
150
|
-
},
|
|
151
|
-
],
|
|
152
|
-
};
|
|
153
|
-
} catch (error) {
|
|
154
|
-
return {
|
|
155
|
-
content: [
|
|
156
|
-
{
|
|
157
|
-
type: "text",
|
|
158
|
-
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
|
|
159
|
-
},
|
|
160
|
-
],
|
|
161
|
-
isError: true,
|
|
162
|
-
};
|
|
163
|
-
}
|
|
164
|
-
},
|
|
165
|
-
};
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
function getProjectStructure(projectRoot: string): string {
|
|
169
|
-
const roots = ["apps", "packages", "backends"].filter((segment) =>
|
|
170
|
-
fs.existsSync(path.join(projectRoot, segment)),
|
|
171
|
-
);
|
|
172
|
-
if (roots.length === 0) return "No Nebutra workspace roots found.";
|
|
173
|
-
|
|
174
|
-
const entries: string[] = [];
|
|
175
|
-
for (const root of roots) {
|
|
176
|
-
walkProjectTree(path.join(projectRoot, root), root, entries, 4);
|
|
177
|
-
}
|
|
178
|
-
return entries.length > 0 ? entries.join("\n") : "No project entries found.";
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
function walkProjectTree(
|
|
182
|
-
absoluteDir: string,
|
|
183
|
-
relativeDir: string,
|
|
184
|
-
entries: string[],
|
|
185
|
-
depthRemaining: number,
|
|
186
|
-
): void {
|
|
187
|
-
if (depthRemaining < 0 || entries.length >= 200) return;
|
|
188
|
-
|
|
189
|
-
entries.push(relativeDir);
|
|
190
|
-
|
|
191
|
-
for (const dirent of fs.readdirSync(absoluteDir, { withFileTypes: true })) {
|
|
192
|
-
if (!dirent.isDirectory()) continue;
|
|
193
|
-
if (dirent.name === "node_modules" || dirent.name.startsWith(".")) continue;
|
|
194
|
-
walkProjectTree(
|
|
195
|
-
path.join(absoluteDir, dirent.name),
|
|
196
|
-
path.join(relativeDir, dirent.name),
|
|
197
|
-
entries,
|
|
198
|
-
depthRemaining - 1,
|
|
199
|
-
);
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// Export a factory or run directly if invoked as script
|
|
204
|
-
export const startContextServer = () => {
|
|
205
|
-
const server = new NebutraContextServer();
|
|
206
|
-
server.run().catch(console.error);
|
|
207
|
-
};
|
|
208
|
-
|
|
209
|
-
if (process.argv[1]?.endsWith("contextServer.js") || process.argv[1]?.endsWith("nebutra-mcp")) {
|
|
210
|
-
startContextServer();
|
|
211
|
-
}
|
package/src/server/index.ts
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import { serverRegistry } from "../registry/serverRegistry";
|
|
2
|
-
import type { MCPServerConfig } from "../types";
|
|
3
|
-
import { createUiAgentMcpServer } from "./uiAgentServer";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Register internal Nebutra services as MCP servers
|
|
7
|
-
* These wrap our existing microservices with MCP-compatible interfaces
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
export const INTERNAL_SERVERS: MCPServerConfig[] = [
|
|
11
|
-
createUiAgentMcpServer(),
|
|
12
|
-
// AI Service
|
|
13
|
-
{
|
|
14
|
-
id: "nebutra-ai",
|
|
15
|
-
name: "Nebutra AI Service",
|
|
16
|
-
description: "AI-powered text generation, embedding, and translation",
|
|
17
|
-
endpoint: process.env.AI_SERVICE_URL || "http://localhost:8001",
|
|
18
|
-
transport: "http",
|
|
19
|
-
tools: [
|
|
20
|
-
{
|
|
21
|
-
name: "generate_text",
|
|
22
|
-
description: "Generate text using LLM",
|
|
23
|
-
parameters: {
|
|
24
|
-
prompt: { type: "string", description: "Input prompt", required: true },
|
|
25
|
-
max_tokens: { type: "number", description: "Max tokens to generate", default: 1000 },
|
|
26
|
-
temperature: { type: "number", description: "Sampling temperature", default: 0.7 },
|
|
27
|
-
},
|
|
28
|
-
returns: "Generated text",
|
|
29
|
-
},
|
|
30
|
-
{
|
|
31
|
-
name: "create_embedding",
|
|
32
|
-
description: "Create vector embedding for text",
|
|
33
|
-
parameters: {
|
|
34
|
-
text: { type: "string", description: "Text to embed", required: true },
|
|
35
|
-
model: {
|
|
36
|
-
type: "string",
|
|
37
|
-
description: "Embedding model",
|
|
38
|
-
default: "text-embedding-3-small",
|
|
39
|
-
},
|
|
40
|
-
},
|
|
41
|
-
returns: "Vector embedding array",
|
|
42
|
-
},
|
|
43
|
-
{
|
|
44
|
-
name: "translate",
|
|
45
|
-
description: "Translate text between languages",
|
|
46
|
-
parameters: {
|
|
47
|
-
text: { type: "string", description: "Text to translate", required: true },
|
|
48
|
-
source: { type: "string", description: "Source language code", default: "en" },
|
|
49
|
-
target: { type: "string", description: "Target language code", required: true },
|
|
50
|
-
},
|
|
51
|
-
returns: "Translated text",
|
|
52
|
-
},
|
|
53
|
-
],
|
|
54
|
-
rateLimit: { maxRequests: 100, windowMs: 60000 },
|
|
55
|
-
},
|
|
56
|
-
];
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Register all internal servers
|
|
60
|
-
*/
|
|
61
|
-
export function registerInternalServers(): void {
|
|
62
|
-
for (const server of INTERNAL_SERVERS) {
|
|
63
|
-
serverRegistry.register(server);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Get list of internal server IDs
|
|
69
|
-
*/
|
|
70
|
-
export function getInternalServerIds(): string[] {
|
|
71
|
-
return INTERNAL_SERVERS.map((s) => s.id);
|
|
72
|
-
}
|