@galaxy-stack/orbit-mcp 0.1.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/README.md ADDED
@@ -0,0 +1,9 @@
1
+ # @galaxy-stack/orbit-mcp
2
+
3
+ Orbit framework package. See the [framework documentation](https://galaxy-orbit-framework.vercel.app).
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ bun add @galaxy-stack/orbit-mcp
9
+ ```
package/dist/cli.js ADDED
@@ -0,0 +1,623 @@
1
+ // @bun
2
+ // src/knowledge.ts
3
+ var KNOWLEDGE = [
4
+ {
5
+ id: "module-pattern",
6
+ title: "Module pattern",
7
+ summary: "Organize features into@Module classes with imports/providers/controllers/exports.",
8
+ content: `Every Orbit feature lives in a class decorated with @Module(). The module wires up its own controllers, providers, and child modules:
9
+
10
+ \`\`\`ts
11
+ import { Module } from '@galaxy-stack/orbit-core';
12
+ import { UserController } from './user.controller';
13
+ import { UserService } from './user.service';
14
+
15
+ @Module({
16
+ imports: [DatabaseModule.forRoot({ filename: 'app.db' })],
17
+ controllers: [UserController],
18
+ providers: [UserService],
19
+ exports: [UserService],
20
+ })
21
+ export class UserModule {}
22
+ \`\`\`
23
+
24
+ Rules:
25
+ - providers list everything the module instantiates (services, repositories).
26
+ - exports must list providers that other modules may inject.
27
+ - imports list other modules (or dynamic modules such as CacheModule.forRoot(...)).
28
+ - The root AppModule imports every feature module and nothing else.`
29
+ },
30
+ {
31
+ id: "controller-pattern",
32
+ title: "Controller and route decorators",
33
+ summary: "@Controller for path prefix; @Get/@Post/@Put/@Patch/@Delete for routes; @Body/@Param/@Query for inputs.",
34
+ content: `Controllers declare REST routes. Method argument decorators extract request data:
35
+
36
+ \`\`\`ts
37
+ import { Controller, Get, Post, Body, Param, Query, HttpCode } from '@galaxy-stack/orbit-core';
38
+
39
+ @Controller('users')
40
+ export class UserController {
41
+ constructor(private readonly users: UserService) {}
42
+
43
+ @Get()
44
+ list(@Query('page') page = '1') {
45
+ return this.users.list(Number(page));
46
+ }
47
+
48
+ @Get(':id')
49
+ find(@Param('id') id: string) {
50
+ return this.users.find(id);
51
+ }
52
+
53
+ @Post()
54
+ @HttpCode(201)
55
+ create(@Body() body: CreateUserDto) {
56
+ return this.users.create(body);
57
+ }
58
+ }
59
+ \`\`\`
60
+
61
+ Return values are serialized to JSON automatically. Throwing HttpException subclasses sets the status code.`
62
+ },
63
+ {
64
+ id: "di-pattern",
65
+ title: "Dependency injection",
66
+ summary: "@Injectable classes are resolved by constructor; three scopes: singleton (default), request, transient.",
67
+ content: `Mark providers with @Injectable() and inject them through constructor parameters:
68
+
69
+ \`\`\`ts
70
+ @Injectable({ scope: Scope.REQUEST }) // optional scoping
71
+ export class UserService {
72
+ constructor(private readonly db: DatabaseService) {}
73
+ }
74
+ \`\`\`
75
+
76
+ Scope semantics:
77
+ - singleton (default): one instance per application.
78
+ - request: one instance per HTTP request.
79
+ - transient: a new instance per injection site.
80
+
81
+ Register providers on a module; import that module elsewhere to gain access to its exported providers.`
82
+ },
83
+ {
84
+ id: "graphql-pattern",
85
+ title: "GraphQL resolvers and security",
86
+ summary: "@Resolver/@Query/@Mutation build the schema; GraphQLModule.forRoot({ security }) enables depth/complexity/alias/introspection guards.",
87
+ content: `Define resolvers with class decorators; Orbit generates the executable schema:
88
+
89
+ \`\`\`ts
90
+ import { Resolver, Query, Mutation, Args } from '@galaxy-stack/orbit-graphql';
91
+
92
+ @Resolver()
93
+ export class UserResolver {
94
+ @Query(() => [User])
95
+ users() { return this.userService.list(); }
96
+
97
+ @Mutation(() => User)
98
+ createUser(@Args('input') input: CreateUserInput) {
99
+ return this.userService.create(input);
100
+ }
101
+ }
102
+ \`\`\`
103
+
104
+ Production hardening (built into GraphQLModule):
105
+
106
+ \`\`\`ts
107
+ GraphQLModule.forRoot({
108
+ introspection: process.env.NODE_ENV !== 'production',
109
+ security: { maxDepth: 10, maxComplexity: 1000, maxAliases: 30 },
110
+ })
111
+ \`\`\`
112
+
113
+ Rules run during validation before any resolver executes: depthLimit, complexityLimit (list fan-out multiplier), aliasLimit, blockIntrospection.`
114
+ },
115
+ {
116
+ id: "security-checklist",
117
+ title: "Security checklist",
118
+ summary: "Helmet headers, CSRF, rate limiting, validation, GraphQL query limits, JWT auth.",
119
+ content: `Minimum hardening for an Orbit backend:
120
+
121
+ 1. SecurityModule.forRoot({ helmet: true, csrf: true }) \u2014 sets OWASP-recommended headers (HSTS, nosniff, frameguard, COOP/CORP) and double-submit CSRF.
122
+ 2. ThrottlerModule \u2014 per-route or global rate limiting.
123
+ 3. ValidationPipe with Zod schemas on every @Body input.
124
+ 4. GraphQLModule: introspection off in production + security limits (depth/complexity/aliases).
125
+ 5. AuthModule JWT guards on protected controllers: @UseGuards(JwtAuthGuard).
126
+ 6. Never log secrets; Logger redacts by default.
127
+ 7. Sanitize any stored HTML (Sanitizer from orbit-security strips script/style).`
128
+ },
129
+ {
130
+ id: "database-pattern",
131
+ title: "Database and repository",
132
+ summary: "DatabaseModule (Drizzle + bun:sqlite) with Repository pattern and transactions.",
133
+ content: `Register the database once, then use repositories per feature:
134
+
135
+ \`\`\`ts
136
+ DatabaseModule.forRoot({ filename: 'app.db' })
137
+
138
+ @Injectable()
139
+ export class UserRepository extends Repository<User> {
140
+ constructor(db: DatabaseService) { super(db, usersTable); }
141
+ }
142
+ \`\`\`
143
+
144
+ - bun:sqlite driver by default \u2014 no external database needed for local development.
145
+ - Transactions: await this.db.transaction(async tx => { ... }).
146
+ - Migrations live in drizzle/ directory; run via CLI.`
147
+ },
148
+ {
149
+ id: "testing-pattern",
150
+ title: "Testing",
151
+ summary: "OrbitTestFactory boots an isolated app; bun:test for unit, e2e via handle(Request).",
152
+ content: `Unit-test providers directly; integration-test through the HTTP surface:
153
+
154
+ \`\`\`ts
155
+ import { describe, test, expect } from 'bun:test';
156
+
157
+ describe('UserService', () => {
158
+ test('creates a user', () => {
159
+ const service = new UserService(new InMemoryDb());
160
+ expect(service.create({ name: 'a' }).id).toBeDefined();
161
+ });
162
+ });
163
+ \`\`\`
164
+
165
+ E2E: build the module, get the handler, pass a Request:
166
+
167
+ \`\`\`ts
168
+ const module = await OrbitTestFactory.create(AppModule).compile();
169
+ const app = module.get(OrbitApplication);
170
+ const res = await app.handle(new Request('http://localhost/users'));
171
+ expect(res.status).toBe(200);
172
+ \`\`\``
173
+ },
174
+ {
175
+ id: "package-map",
176
+ title: "Package map",
177
+ summary: "All @galaxy-stack/orbit-* packages and what they provide.",
178
+ content: `Core: orbit-core (DI, modules, controllers, pipeline), orbit-common (shared utils), orbit-platform-bun (Bun.serve adapter), orbit-config (@galaxy-stack/orbit-config env/config loader), orbit-validation (Zod pipe).
179
+
180
+ Data: orbit-database (Drizzle + bun:sqlite), orbit-cache (in-memory/Redis cache manager).
181
+
182
+ API: orbit-graphql (schema builder + security rules), orbit-graphql-federation (Apollo Federation gateway + subgraphs), orbit-swagger (OpenAPI UI), orbit-websockets (gateway + pubsub).
183
+
184
+ Microservices: orbit-microservices core plus transports orbit-microservices-{tcp,redis,nats,rmq,kafka,grpc}.
185
+
186
+ Quality: orbit-auth (JWT), orbit-security (helmet/CSRF/API-key/sanitizer), orbit-throttler, orbit-terminus (health), orbit-schedule (cron), orbit-logger, orbit-telemetry (OTel), orbit-observability (metrics/tracing).
187
+
188
+ Tooling: orbit-cli (scaffold), orbit-testing, orbit-devtools (dashboard), orbit-mcp (AI guidance server), orbit-docs, vscode-snippets.`
189
+ },
190
+ {
191
+ id: "microservices-pattern",
192
+ title: "Microservices transports",
193
+ summary: "ClientProxy for RPC/events across 6 transports; server decorators expose handlers.",
194
+ content: `Server side:
195
+
196
+ \`\`\`ts
197
+ @MessageHandler('user.created')
198
+ handleUserCreated(payload: any) { ... }
199
+
200
+ @EventHandler('audit.*')
201
+ handleAudit(pattern: string, payload: any) { ... }
202
+ \`\`\`
203
+
204
+ Client side:
205
+
206
+ \`\`\`ts
207
+ constructor(@Inject('USER_CLIENT') private client: ClientProxy) {}
208
+ send = this.client.send('user.get', { id: 1 }); // RPC (observable)
209
+ emit = this.client.emit('user.created', data); // fire-and-forget
210
+ \`\`\`
211
+
212
+ Transports: TCP (zero deps), Redis, NATS, RabbitMQ, Kafka, gRPC \u2014 each in its own @galaxy-stack/orbit-microservices-* package.`
213
+ }
214
+ ];
215
+
216
+ // src/tools.ts
217
+ var TOOLS = [
218
+ {
219
+ name: "orbit_knowledge_topics",
220
+ description: "List all available Orbit framework knowledge topics with summaries.",
221
+ inputSchema: { type: "object", properties: {} }
222
+ },
223
+ {
224
+ name: "orbit_knowledge_read",
225
+ description: "Read one Orbit knowledge topic by id (use orbit_knowledge_topics first).",
226
+ inputSchema: {
227
+ type: "object",
228
+ properties: {
229
+ id: { type: "string", description: "Topic id from orbit_knowledge_topics" }
230
+ },
231
+ required: ["id"]
232
+ }
233
+ },
234
+ {
235
+ name: "orbit_scaffold_module",
236
+ description: "Generate a complete Orbit feature module (module, controller, service, optional Zod DTO and tests) as copy-paste-ready code.",
237
+ inputSchema: {
238
+ type: "object",
239
+ properties: {
240
+ name: { type: "string", description: 'Feature name in kebab-case, e.g. "user"' },
241
+ withDatabase: { type: "boolean", description: "Include a Drizzle table + repository (bun:sqlite)" },
242
+ withTests: { type: "boolean", description: "Include unit + e2e test files" },
243
+ withSwagger: { type: "boolean", description: "Include OpenAPI decorators" }
244
+ },
245
+ required: ["name"]
246
+ }
247
+ },
248
+ {
249
+ name: "orbit_scaffold_graphql",
250
+ description: "Generate a GraphQL feature: resolver, object types, input types, and module wiring with security limits.",
251
+ inputSchema: {
252
+ type: "object",
253
+ properties: {
254
+ name: { type: "string", description: "Feature name in kebab-case" },
255
+ withLoaders: { type: "boolean", description: "Include DataLoader wiring" }
256
+ },
257
+ required: ["name"]
258
+ }
259
+ },
260
+ {
261
+ name: "orbit_security_review",
262
+ description: "Run a static checklist against pasted source code and report missing security hardening with concrete fixes.",
263
+ inputSchema: {
264
+ type: "object",
265
+ properties: {
266
+ code: { type: "string", description: "Source code to review" },
267
+ context: { type: "string", enum: ["rest", "graphql", "both"], description: "Surface to review" }
268
+ },
269
+ required: ["code"]
270
+ }
271
+ }
272
+ ];
273
+ function pascal(name) {
274
+ return name.split(/[-_]/).map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join("");
275
+ }
276
+ function executeTool(name, args) {
277
+ const text = (t) => ({ content: [{ type: "text", text: t }] });
278
+ switch (name) {
279
+ case "orbit_knowledge_topics":
280
+ return text(KNOWLEDGE.map((k) => `- **${k.id}** \u2014 ${k.title}: ${k.summary}`).join(`
281
+ `));
282
+ case "orbit_knowledge_read": {
283
+ const entry = KNOWLEDGE.find((k) => k.id === args.id);
284
+ if (!entry)
285
+ return text(`Unknown topic "${args.id}". Use orbit_knowledge_topics to list ids.`);
286
+ return text(`# ${entry.title}
287
+
288
+ ${entry.content}`);
289
+ }
290
+ case "orbit_scaffold_module": {
291
+ const name2 = String(args.name || "feature");
292
+ const kebab = name2.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
293
+ const cls = pascal(name2);
294
+ const files = [];
295
+ files.push(`// src/${kebab}/${kebab}.module.ts
296
+ import { Module } from '@galaxy-stack/orbit-core';
297
+ import { ${cls}Controller } from './${kebab}.controller';
298
+ import { ${cls}Service } from './${kebab}.service';
299
+
300
+ @Module({
301
+ controllers: [${cls}Controller],
302
+ providers: [${cls}Service],
303
+ exports: [${cls}Service],
304
+ })
305
+ export class ${cls}Module {}`);
306
+ files.push(`// src/${kebab}/${kebab}.service.ts
307
+ import { Injectable } from '@galaxy-stack/orbit-core';
308
+
309
+ export interface Create${cls}Input {
310
+ name: string;
311
+ }
312
+
313
+ @Injectable()
314
+ export class ${cls}Service {
315
+ private readonly items = new Map<string, { id: string; name: string }>();
316
+
317
+ list() { return [...this.items.values()]; }
318
+
319
+ find(id: string) { return this.items.get(id); }
320
+
321
+ create(input: Create${cls}Input) {
322
+ const item = { id: crypto.randomUUID(), name: input.name };
323
+ this.items.set(item.id, item);
324
+ return item;
325
+ }
326
+ }`);
327
+ files.push(`// src/${kebab}/${kebab}.controller.ts
328
+ import { Controller, Get, Post, Body, Param, NotFoundError } from '@galaxy-stack/orbit-core';
329
+ import { ${cls}Service } from './${kebab}.service';
330
+
331
+ @Controller('${kebab}')
332
+ export class ${cls}Controller {
333
+ constructor(private readonly service: ${cls}Service) {}
334
+
335
+ @Get()
336
+ list() { return this.service.list(); }
337
+
338
+ @Get(':id')
339
+ find(@Param('id') id: string) {
340
+ const item = this.service.find(id);
341
+ if (!item) throw new NotFoundError('${cls} not found');
342
+ return item;
343
+ }
344
+
345
+ @Post()
346
+ @HttpCode(201)
347
+ create(@Body() body: { name: string }) { return this.service.create(body); }
348
+ }`);
349
+ if (args.withTests) {
350
+ files.push(`// src/${kebab}/${kebab}.service.test.ts
351
+ import { describe, test, expect } from 'bun:test';
352
+ import { ${cls}Service } from './${kebab}.service';
353
+
354
+ describe('${cls}Service', () => {
355
+ test('creates and finds items', () => {
356
+ const service = new ${cls}Service();
357
+ const created = service.create({ name: 'demo' });
358
+ expect(service.find(created.id)?.name).toBe('demo');
359
+ });
360
+ });`);
361
+ }
362
+ return text(files.join(`
363
+
364
+ `));
365
+ }
366
+ case "orbit_scaffold_graphql": {
367
+ const name2 = String(args.name || "feature");
368
+ const kebab = name2.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
369
+ const cls = pascal(name2);
370
+ const loader = args.withLoaders ? `
371
+ @ResolveField(() => Author)
372
+ author(@Parent() parent: ${cls}, @Loader('authorLoader') loader: DataLoader) {
373
+ return loader.load(parent.authorId);
374
+ }` : "";
375
+ return text(`// src/${kebab}/${kebab}.resolver.ts
376
+ import { Resolver, Query, Mutation, Args${args.withLoaders ? ", ResolveField, Parent, Loader" : ""} } from '@galaxy-stack/orbit-graphql';
377
+ ${args.withLoaders ? `import { DataLoader } from '@galaxy-stack/orbit-graphql';
378
+ ` : ""}
379
+ @Resolver(() => ${cls})
380
+ export class ${cls}Resolver {
381
+ constructor(private readonly service: ${cls}Service) {}
382
+
383
+ @Query(() => [${cls}])
384
+ ${kebab}() { return this.service.list(); }
385
+
386
+ @Mutation(() => ${cls})
387
+ create${cls}(@Args('input') input: Create${cls}Input) {
388
+ return this.service.create(input);
389
+ }${loader}
390
+ }
391
+
392
+ // Module wiring with production security limits:
393
+ // GraphQLModule.forRoot({
394
+ // introspection: process.env.NODE_ENV !== 'production',
395
+ // security: { maxDepth: 10, maxComplexity: 1000, maxAliases: 30 },
396
+ // resolvers: [${cls}Resolver],
397
+ // })`);
398
+ }
399
+ case "orbit_security_review": {
400
+ const code = String(args.code || "");
401
+ const surface = args.context || "both";
402
+ const findings = [];
403
+ const has = (re) => re.test(code);
404
+ if (surface !== "graphql" && has(/@(Post|Put|Patch|Delete)\(/)) {
405
+ if (!has(/ValidationPipe|Zod|schema\.parse|@Is/))
406
+ findings.push("[HIGH] Mutation endpoints lack input validation. Add ValidationPipe with a Zod schema: @UsePipes(new ValidationPipe(schema)) on the handler or module.");
407
+ if (!has(/UseGuards|AuthGuard|jwt|session/i))
408
+ findings.push("[HIGH] State-changing endpoint without an auth guard. Add @UseGuards(JwtAuthGuard) or equivalent.");
409
+ if (!has(/Throttle|RateLimit|throttler/i))
410
+ findings.push("[MEDIUM] No rate limiting on writes. Enable ThrottlerModule globally or @Throttle() per route.");
411
+ }
412
+ if (surface !== "rest" && has(/GraphQLModule|@Resolver\(/)) {
413
+ if (has(/introspection:\s*true/))
414
+ findings.push("[HIGH] introspection: true hardcoded \u2014 expose it only outside production.");
415
+ if (!has(/security:\s*{|maxDepth|maxComplexity/))
416
+ findings.push("[HIGH] GraphQLModule without security limits. Add security: { maxDepth: 10, maxComplexity: 1000, maxAliases: 30 }.");
417
+ if (has(/playground:\s*true/))
418
+ findings.push("[MEDIUM] Playground enabled \u2014 disable in production.");
419
+ }
420
+ if (has(/password|secret|token|api[_-]?key/i)) {
421
+ if (has(/console\.log|Logger\.(log|info|debug)\(.*(password|secret|token|api[_-]?key)/i))
422
+ findings.push("[CRITICAL] Potential secret logging detected. Redact secrets before logging.");
423
+ if (!has(/process\.env|ConfigService/) && has(/(password|secret|apiKey|api_key)\s*[:=]\s*['"][^'"]{6,}/))
424
+ findings.push("[CRITICAL] Hardcoded secret detected. Move to environment variables via ConfigModule.");
425
+ }
426
+ if (has(/innerHTML\s*=|dangerouslySetInnerHTML/) && !has(/sanitize|Sanitizer|DOMPurify/))
427
+ findings.push("[HIGH] Unsanitized HTML sink. Sanitize with orbit-security Sanitizer before rendering.");
428
+ if (has(/SecurityModule|helmet/i)) {} else if (surface !== "graphql") {
429
+ findings.push("[MEDIUM] No SecurityModule/helmet usage detected. Enable SecurityModule.forRoot({ helmet: true }).");
430
+ }
431
+ if (findings.length === 0)
432
+ return text("No security findings detected by the checklist. This is not a substitute for penetration testing.");
433
+ return text(findings.map((f, i) => `${i + 1}. ${f}`).join(`
434
+ `));
435
+ }
436
+ default:
437
+ return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true };
438
+ }
439
+ }
440
+
441
+ // src/server.ts
442
+ var PROTOCOL_VERSION = "2025-03-26";
443
+ var SERVER_INFO = {
444
+ name: "@galaxy-stack/orbit-mcp",
445
+ version: "0.1.0"
446
+ };
447
+ var PROMPTS = [
448
+ {
449
+ name: "build_orbit_feature",
450
+ description: "Design and implement a new Orbit feature module end-to-end (module, controller, service, validation, tests).",
451
+ arguments: [
452
+ { name: "feature", description: 'Feature name, e.g. "orders"', required: true },
453
+ { name: "storage", description: "Storage choice: memory | sqlite | none", required: false }
454
+ ]
455
+ },
456
+ {
457
+ name: "harden_graphql_api",
458
+ description: "Audit and harden an Orbit GraphQL API: introspection, depth/complexity/alias limits, auth guards.",
459
+ arguments: [
460
+ { name: "code", description: "Current GraphQL module code", required: true }
461
+ ]
462
+ },
463
+ {
464
+ name: "migrate_from_nestjs",
465
+ description: "Map NestJS concepts/decorators to Orbit equivalents and produce a migration plan.",
466
+ arguments: [
467
+ { name: "nest_code", description: "NestJS source to migrate", required: true }
468
+ ]
469
+ }
470
+ ];
471
+ function promptText(name, args) {
472
+ switch (name) {
473
+ case "build_orbit_feature":
474
+ return `Build an Orbit feature module named "${args.feature}"${args.storage ? ` using ${args.storage} storage` : ""}.
475
+
476
+ Requirements:
477
+ 1. Create module, controller, service (orbit_scaffold_module tool can draft the skeleton).
478
+ 2. Validate all inputs with a Zod schema and ValidationPipe.
479
+ 3. Add auth guards to mutating routes.
480
+ 4. Wire the module into AppModule.
481
+ 5. Include unit tests (bun:test) and an e2e test through app.handle(Request).
482
+ 6. Run bun test to verify.`;
483
+ case "harden_graphql_api":
484
+ return `Harden this Orbit GraphQL module:
485
+
486
+ ${args.code}
487
+
488
+ Use the orbit_security_review tool (context: graphql) for the checklist, then apply fixes: disable introspection outside production, add security { maxDepth, maxComplexity, maxAliases }, guard protected resolvers, and disable the playground in production.`;
489
+ case "migrate_from_nestjs":
490
+ return `Map this NestJS code to Orbit:
491
+
492
+ ${args.nest_code}
493
+
494
+ Concept mapping: @nestjs/common decorators -> @galaxy-stack/orbit-core; class-validator DTOs -> Zod schemas + orbit-validation; @nestjs/graphql -> orbit-graphql; Bull queues -> (roadmap) orbit-queue; EventEmitter2 -> (roadmap) orbit-event-bus. Read the orbit://knowledge/* resources for detailed patterns. Produce: 1) mapping table, 2) migrated files, 3) test plan.`;
495
+ default:
496
+ return `Unknown prompt: ${name}`;
497
+ }
498
+ }
499
+ function ok(id, result) {
500
+ return { jsonrpc: "2.0", id, result };
501
+ }
502
+ function err(id, code, message) {
503
+ return { jsonrpc: "2.0", id, error: { code, message } };
504
+ }
505
+ function handleRequest(req) {
506
+ const { id = null, method, params = {} } = reqBell(req);
507
+ switch (method) {
508
+ case "initialize":
509
+ return ok(id, {
510
+ protocolVersion: PROTOCOL_VERSION,
511
+ capabilities: {
512
+ tools: {},
513
+ resources: {},
514
+ prompts: {}
515
+ },
516
+ serverInfo: SERVER_INFO
517
+ });
518
+ case "ping":
519
+ return ok(id, {});
520
+ case "tools/list":
521
+ return ok(id, { tools: TOOLS });
522
+ case "tools/call": {
523
+ const { name, arguments: args } = params;
524
+ try {
525
+ const result = executeTool(name, args || {});
526
+ return ok(id, result);
527
+ } catch (e) {
528
+ return ok(id, {
529
+ content: [{ type: "text", text: `Tool error: ${e.message}` }],
530
+ isError: true
531
+ });
532
+ }
533
+ }
534
+ case "resources/list":
535
+ return ok(id, {
536
+ resources: KNOWLEDGE.map((k) => ({
537
+ uri: `orbit://knowledge/${k.id}`,
538
+ name: k.title,
539
+ description: k.summary,
540
+ mimeType: "text/markdown"
541
+ }))
542
+ });
543
+ case "resources/read": {
544
+ const uri = params.uri || "";
545
+ const idPart = uri.replace("orbit://knowledge/", "");
546
+ const entry = KNOWLEDGE.find((k) => k.id === idPart);
547
+ if (!entry)
548
+ return err(id, -32602, `Unknown resource: ${uri}`);
549
+ return ok(id, {
550
+ contents: [{
551
+ uri,
552
+ mimeType: "text/markdown",
553
+ text: `# ${entry.title}
554
+
555
+ ${entry.content}`
556
+ }]
557
+ });
558
+ }
559
+ case "prompts/list":
560
+ return ok(id, { prompts: PROMPTS });
561
+ case "prompts/get": {
562
+ const prompt = PROMPTS.find((p) => p.name === params.name);
563
+ if (!prompt)
564
+ return err(id, -32602, `Unknown prompt: ${params.name}`);
565
+ return ok(id, {
566
+ messages: [{
567
+ role: "user",
568
+ content: {
569
+ type: "text",
570
+ text: promptText(params.name, params.arguments || {})
571
+ }
572
+ }]
573
+ });
574
+ }
575
+ default:
576
+ return err(id, -32601, `Method not found: ${method}`);
577
+ }
578
+ }
579
+ function reqBell(req) {
580
+ return req;
581
+ }
582
+ async function serveStdio(input = process.stdin, output = process.stdout) {
583
+ let buffer = "";
584
+ const out = output;
585
+ for await (const chunk of input) {
586
+ buffer += typeof chunk === "string" ? chunk : new TextDecoder().decode(chunk);
587
+ let newlineIndex;
588
+ while ((newlineIndex = buffer.indexOf(`
589
+ `)) !== -1) {
590
+ const line = buffer.slice(0, newlineIndex).trim();
591
+ buffer = buffer.slice(newlineIndex + 1);
592
+ if (!line)
593
+ continue;
594
+ let response;
595
+ try {
596
+ const req = JSON.parse(line);
597
+ if (req.jsonrpc !== "2.0" || typeof req.method !== "string") {
598
+ response = err(req.id ?? null, -32600, "Invalid Request");
599
+ } else if (req.id === undefined) {
600
+ continue;
601
+ } else {
602
+ response = handleRequest(req);
603
+ }
604
+ } catch {
605
+ response = err(null, -32700, "Parse error");
606
+ }
607
+ out.write(JSON.stringify(response) + `
608
+ `);
609
+ }
610
+ }
611
+ }
612
+ if (import.meta.main) {
613
+ serveStdio().catch((e) => {
614
+ console.error("orbit-mcp fatal:", e);
615
+ process.exit(1);
616
+ });
617
+ }
618
+ export {
619
+ serveStdio,
620
+ handleRequest,
621
+ SERVER_INFO,
622
+ PROTOCOL_VERSION
623
+ };