@nocoo/base-mcp 0.1.0-alpha.1

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 (66) hide show
  1. package/README.md +194 -0
  2. package/dist/auth/index.d.ts +5 -0
  3. package/dist/auth/index.d.ts.map +1 -0
  4. package/dist/auth/index.js +6 -0
  5. package/dist/auth/index.js.map +1 -0
  6. package/dist/auth/oauth-metadata.d.ts +31 -0
  7. package/dist/auth/oauth-metadata.d.ts.map +1 -0
  8. package/dist/auth/oauth-metadata.js +33 -0
  9. package/dist/auth/oauth-metadata.js.map +1 -0
  10. package/dist/auth/origin.d.ts +32 -0
  11. package/dist/auth/origin.d.ts.map +1 -0
  12. package/dist/auth/origin.js +66 -0
  13. package/dist/auth/origin.js.map +1 -0
  14. package/dist/auth/pkce.d.ts +40 -0
  15. package/dist/auth/pkce.d.ts.map +1 -0
  16. package/dist/auth/pkce.js +78 -0
  17. package/dist/auth/pkce.js.map +1 -0
  18. package/dist/auth/token.d.ts +79 -0
  19. package/dist/auth/token.d.ts.map +1 -0
  20. package/dist/auth/token.js +108 -0
  21. package/dist/auth/token.js.map +1 -0
  22. package/dist/framework/handlers.d.ts +14 -0
  23. package/dist/framework/handlers.d.ts.map +1 -0
  24. package/dist/framework/handlers.js +110 -0
  25. package/dist/framework/handlers.js.map +1 -0
  26. package/dist/framework/index.d.ts +7 -0
  27. package/dist/framework/index.d.ts.map +1 -0
  28. package/dist/framework/index.js +7 -0
  29. package/dist/framework/index.js.map +1 -0
  30. package/dist/framework/projection.d.ts +10 -0
  31. package/dist/framework/projection.d.ts.map +1 -0
  32. package/dist/framework/projection.js +21 -0
  33. package/dist/framework/projection.js.map +1 -0
  34. package/dist/framework/register.d.ts +16 -0
  35. package/dist/framework/register.d.ts.map +1 -0
  36. package/dist/framework/register.js +70 -0
  37. package/dist/framework/register.js.map +1 -0
  38. package/dist/framework/resolve.d.ts +39 -0
  39. package/dist/framework/resolve.d.ts.map +1 -0
  40. package/dist/framework/resolve.js +46 -0
  41. package/dist/framework/resolve.js.map +1 -0
  42. package/dist/framework/response.d.ts +6 -0
  43. package/dist/framework/response.d.ts.map +1 -0
  44. package/dist/framework/response.js +17 -0
  45. package/dist/framework/response.js.map +1 -0
  46. package/dist/framework/types.d.ts +85 -0
  47. package/dist/framework/types.d.ts.map +1 -0
  48. package/dist/framework/types.js +5 -0
  49. package/dist/framework/types.js.map +1 -0
  50. package/dist/index.d.ts +4 -0
  51. package/dist/index.d.ts.map +1 -0
  52. package/dist/index.js +6 -0
  53. package/dist/index.js.map +1 -0
  54. package/dist/server/create-server.d.ts +24 -0
  55. package/dist/server/create-server.d.ts.map +1 -0
  56. package/dist/server/create-server.js +24 -0
  57. package/dist/server/create-server.js.map +1 -0
  58. package/dist/testing/index.d.ts +2 -0
  59. package/dist/testing/index.d.ts.map +1 -0
  60. package/dist/testing/index.js +3 -0
  61. package/dist/testing/index.js.map +1 -0
  62. package/dist/testing/utils.d.ts +55 -0
  63. package/dist/testing/utils.d.ts.map +1 -0
  64. package/dist/testing/utils.js +66 -0
  65. package/dist/testing/utils.js.map +1 -0
  66. package/package.json +72 -0
@@ -0,0 +1,108 @@
1
+ // ---------------------------------------------------------------------------
2
+ // OAuth Token Management Utilities
3
+ // ---------------------------------------------------------------------------
4
+ /**
5
+ * Generate a secure random token.
6
+ *
7
+ * @param length - Number of random bytes (default 32, produces 64 hex chars)
8
+ * @returns Hex-encoded random token
9
+ */
10
+ export function generateToken(length = 32) {
11
+ const bytes = new Uint8Array(length);
12
+ crypto.getRandomValues(bytes);
13
+ return Array.from(bytes)
14
+ .map((b) => b.toString(16).padStart(2, "0"))
15
+ .join("");
16
+ }
17
+ /**
18
+ * Hash a token using SHA-256.
19
+ *
20
+ * Tokens should be stored as hashes, never in plaintext.
21
+ *
22
+ * @param token - The plaintext token
23
+ * @returns SHA-256 hash as hex string
24
+ */
25
+ export async function hashToken(token) {
26
+ const encoded = new TextEncoder().encode(token);
27
+ const buffer = await crypto.subtle.digest("SHA-256", encoded);
28
+ return Array.from(new Uint8Array(buffer))
29
+ .map((b) => b.toString(16).padStart(2, "0"))
30
+ .join("");
31
+ }
32
+ /**
33
+ * Generate a token preview (first 8 characters).
34
+ *
35
+ * Used for logging and display without exposing the full token.
36
+ *
37
+ * @param token - The plaintext token
38
+ * @returns First 8 characters
39
+ */
40
+ export function tokenPreview(token) {
41
+ return token.slice(0, 8);
42
+ }
43
+ /**
44
+ * Extract bearer token from Authorization header.
45
+ *
46
+ * @param authHeader - The Authorization header value
47
+ * @returns The token, or null if invalid
48
+ */
49
+ export function extractBearerToken(authHeader) {
50
+ if (!authHeader)
51
+ return null;
52
+ const match = authHeader.match(/^Bearer\s+(.+)$/i);
53
+ return match ? match[1] : null;
54
+ }
55
+ /**
56
+ * Validate an MCP bearer token.
57
+ *
58
+ * @param store - Token storage implementation
59
+ * @param authHeader - The Authorization header value
60
+ * @returns Validation result
61
+ */
62
+ export async function validateMcpToken(store, authHeader) {
63
+ const token = extractBearerToken(authHeader);
64
+ if (!token) {
65
+ return {
66
+ valid: false,
67
+ error: "Missing or invalid Authorization header",
68
+ status: 401,
69
+ };
70
+ }
71
+ const hash = await hashToken(token);
72
+ const record = await store.findByHash(hash);
73
+ if (!record) {
74
+ return {
75
+ valid: false,
76
+ error: "Invalid token",
77
+ status: 401,
78
+ };
79
+ }
80
+ if (record.revoked) {
81
+ return {
82
+ valid: false,
83
+ error: "Token has been revoked",
84
+ status: 401,
85
+ };
86
+ }
87
+ if (record.expires_at && new Date(record.expires_at) < new Date()) {
88
+ return {
89
+ valid: false,
90
+ error: "Token has expired",
91
+ status: 401,
92
+ };
93
+ }
94
+ // Update last used timestamp (fire-and-forget)
95
+ store.updateLastUsed(record.id).catch(() => {
96
+ // Ignore errors updating last_used_at
97
+ });
98
+ return {
99
+ valid: true,
100
+ token: {
101
+ id: record.id,
102
+ user_id: record.user_id,
103
+ client_id: record.client_id,
104
+ scope: record.scope,
105
+ },
106
+ };
107
+ }
108
+ //# sourceMappingURL=token.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token.js","sourceRoot":"","sources":["../../src/auth/token.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
2
+ import type { EntityConfig, EntityContext } from "./types.js";
3
+ import type { IdOrSlug } from "./resolve.js";
4
+ export interface CrudHandlers<TRepos> {
5
+ handleList: (ctx: EntityContext<TRepos>, args: Record<string, unknown>) => Promise<CallToolResult>;
6
+ handleGet: (ctx: EntityContext<TRepos>, args: IdOrSlug) => Promise<CallToolResult>;
7
+ handleCreate: (ctx: EntityContext<TRepos>, args: Record<string, unknown>) => Promise<CallToolResult>;
8
+ handleUpdate: (ctx: EntityContext<TRepos>, args: IdOrSlug & Record<string, unknown>) => Promise<CallToolResult>;
9
+ handleDelete: (ctx: EntityContext<TRepos>, args: IdOrSlug) => Promise<CallToolResult>;
10
+ }
11
+ export declare function createCrudHandlers<T extends {
12
+ id: string;
13
+ }, TRepos = unknown>(config: EntityConfig<T, TRepos>): CrudHandlers<TRepos>;
14
+ //# sourceMappingURL=handlers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handlers.d.ts","sourceRoot":"","sources":["../../src/framework/handlers.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AACzE,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC9D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAK7C,MAAM,WAAW,YAAY,CAAC,MAAM;IAClC,UAAU,EAAE,CACV,GAAG,EAAE,aAAa,CAAC,MAAM,CAAC,EAC1B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC1B,OAAO,CAAC,cAAc,CAAC,CAAC;IAC7B,SAAS,EAAE,CACT,GAAG,EAAE,aAAa,CAAC,MAAM,CAAC,EAC1B,IAAI,EAAE,QAAQ,KACX,OAAO,CAAC,cAAc,CAAC,CAAC;IAC7B,YAAY,EAAE,CACZ,GAAG,EAAE,aAAa,CAAC,MAAM,CAAC,EAC1B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC1B,OAAO,CAAC,cAAc,CAAC,CAAC;IAC7B,YAAY,EAAE,CACZ,GAAG,EAAE,aAAa,CAAC,MAAM,CAAC,EAC1B,IAAI,EAAE,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KACrC,OAAO,CAAC,cAAc,CAAC,CAAC;IAC7B,YAAY,EAAE,CACZ,GAAG,EAAE,aAAa,CAAC,MAAM,CAAC,EAC1B,IAAI,EAAE,QAAQ,KACX,OAAO,CAAC,cAAc,CAAC,CAAC;CAC9B;AAED,wBAAgB,kBAAkB,CAAC,CAAC,SAAS;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,EAAE,MAAM,GAAG,OAAO,EAC3E,MAAM,EAAE,YAAY,CAAC,CAAC,EAAE,MAAM,CAAC,GAC9B,YAAY,CAAC,MAAM,CAAC,CA0JtB"}
@@ -0,0 +1,110 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Entity-Driven MCP Framework — Generic CRUD Handler Factory
3
+ //
4
+ // Given an EntityConfig, produces 5 typed handler functions:
5
+ // handleList, handleGet, handleCreate, handleUpdate, handleDelete
6
+ // ---------------------------------------------------------------------------
7
+ import { resolveEntity, isResolveError } from "./resolve.js";
8
+ import { ok, error } from "./response.js";
9
+ import { projectFields } from "./projection.js";
10
+ export function createCrudHandlers(config) {
11
+ const { dataLayer, hooks, projection } = config;
12
+ const displayName = config.display;
13
+ // Helper: resolve with entity-specific not-found message
14
+ async function resolve(ctx, args) {
15
+ return resolveEntity(args, (id) => dataLayer.getById(ctx, id), dataLayer.getBySlug
16
+ ? (slug) => dataLayer.getBySlug(ctx, slug)
17
+ : async () => null, displayName);
18
+ }
19
+ // ---- list ----
20
+ async function handleList(ctx, args) {
21
+ const result = await dataLayer.list(ctx, args);
22
+ // Apply projection if configured
23
+ const items = projection
24
+ ? result.map((item) => projectFields(item, projection, args.include))
25
+ : result;
26
+ return ok(items);
27
+ }
28
+ // ---- get ----
29
+ async function handleGet(ctx, args) {
30
+ const resolved = await resolve(ctx, args);
31
+ if (isResolveError(resolved))
32
+ return error(resolved.error);
33
+ return ok(resolved);
34
+ }
35
+ // ---- create ----
36
+ async function handleCreate(ctx, args) {
37
+ if (!dataLayer.create) {
38
+ return error(`${displayName} does not support creation`);
39
+ }
40
+ // Apply beforeCreate hook if defined
41
+ const input = hooks?.beforeCreate
42
+ ? await hooks.beforeCreate(ctx, args)
43
+ : args;
44
+ const entity = await dataLayer.create(ctx, input);
45
+ // Apply afterCreate hook (best-effort)
46
+ if (hooks?.afterCreate) {
47
+ try {
48
+ await hooks.afterCreate(ctx, entity);
49
+ }
50
+ catch (err) {
51
+ console.error(`[MCP] ${displayName} afterCreate hook failed (best-effort):`, err);
52
+ }
53
+ }
54
+ return ok(entity);
55
+ }
56
+ // ---- update ----
57
+ async function handleUpdate(ctx, args) {
58
+ if (!dataLayer.update) {
59
+ return error(`${displayName} does not support updates`);
60
+ }
61
+ const resolved = await resolve(ctx, args);
62
+ if (isResolveError(resolved))
63
+ return error(resolved.error);
64
+ // Strip MCP identifier fields before any hook sees the args
65
+ const { id: _id, slug: _slug, ...businessFields } = args;
66
+ // Apply beforeUpdate hook if defined
67
+ const input = hooks?.beforeUpdate
68
+ ? await hooks.beforeUpdate(ctx, resolved.id, businessFields, resolved)
69
+ : businessFields;
70
+ const updated = await dataLayer.update(ctx, resolved.id, input);
71
+ if (!updated)
72
+ return error(`${displayName} not found: ${resolved.id}`);
73
+ // Apply afterUpdate hook (best-effort)
74
+ if (hooks?.afterUpdate) {
75
+ try {
76
+ await hooks.afterUpdate(ctx, resolved.id, businessFields, updated);
77
+ }
78
+ catch (err) {
79
+ console.error(`[MCP] ${displayName} afterUpdate hook failed (best-effort):`, err);
80
+ }
81
+ }
82
+ return ok(updated);
83
+ }
84
+ // ---- delete ----
85
+ async function handleDelete(ctx, args) {
86
+ if (!dataLayer.delete) {
87
+ return error(`${displayName} does not support deletion`);
88
+ }
89
+ const resolved = await resolve(ctx, args);
90
+ if (isResolveError(resolved))
91
+ return error(resolved.error);
92
+ // Apply beforeDelete hook if defined
93
+ if (hooks?.beforeDelete) {
94
+ await hooks.beforeDelete(ctx, resolved.id, resolved);
95
+ }
96
+ await dataLayer.delete(ctx, resolved.id);
97
+ // Apply afterDelete hook (best-effort)
98
+ if (hooks?.afterDelete) {
99
+ try {
100
+ await hooks.afterDelete(ctx, resolved.id);
101
+ }
102
+ catch (err) {
103
+ console.error(`[MCP] ${displayName} afterDelete hook failed (best-effort):`, err);
104
+ }
105
+ }
106
+ return ok({ deleted: true });
107
+ }
108
+ return { handleList, handleGet, handleCreate, handleUpdate, handleDelete };
109
+ }
110
+ //# sourceMappingURL=handlers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handlers.js","sourceRoot":"","sources":["../../src/framework/handlers.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ export { ok, error } from "./response.js";
2
+ export { projectFields } from "./projection.js";
3
+ export { validateIdOrSlug, resolveEntity, isResolveError, type IdOrSlug, type ResolveResult, } from "./resolve.js";
4
+ export { createCrudHandlers, type CrudHandlers } from "./handlers.js";
5
+ export { registerEntityTools, registerCustomTool } from "./register.js";
6
+ export type { ProjectionConfig, EntityContext, DataLayer, EntityHooks, EntitySchemas, EntityDescriptions, EntityConfig, ToolHandler, CustomToolConfig, } from "./types.js";
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/framework/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EACL,gBAAgB,EAChB,aAAa,EACb,cAAc,EACd,KAAK,QAAQ,EACb,KAAK,aAAa,GACnB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,kBAAkB,EAAE,KAAK,YAAY,EAAE,MAAM,eAAe,CAAC;AACtE,OAAO,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACxE,YAAY,EACV,gBAAgB,EAChB,aAAa,EACb,SAAS,EACT,WAAW,EACX,aAAa,EACb,kBAAkB,EAClB,YAAY,EACZ,WAAW,EACX,gBAAgB,GACjB,MAAM,YAAY,CAAC"}
@@ -0,0 +1,7 @@
1
+ // Framework exports
2
+ export { ok, error } from "./response.js";
3
+ export { projectFields } from "./projection.js";
4
+ export { validateIdOrSlug, resolveEntity, isResolveError, } from "./resolve.js";
5
+ export { createCrudHandlers } from "./handlers.js";
6
+ export { registerEntityTools, registerCustomTool } from "./register.js";
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/framework/index.ts"],"names":[],"mappings":"AAAA,oBAAoB;AACpB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EACL,gBAAgB,EAChB,aAAa,EACb,cAAc,GAGf,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,kBAAkB,EAAqB,MAAM,eAAe,CAAC;AACtE,OAAO,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC"}
@@ -0,0 +1,10 @@
1
+ import type { ProjectionConfig } from "./types.js";
2
+ /**
3
+ * Remove omitted fields from a record, unless included via named groups.
4
+ *
5
+ * - Default: omit all fields in `config.omit`
6
+ * - If `include` contains a group name, restore those fields
7
+ * - If `include` contains `"full"`, return the record unchanged
8
+ */
9
+ export declare function projectFields<T extends Record<string, unknown>>(record: T, config: ProjectionConfig, include?: string[]): Record<string, unknown>;
10
+ //# sourceMappingURL=projection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"projection.d.ts","sourceRoot":"","sources":["../../src/framework/projection.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEnD;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7D,MAAM,EAAE,CAAC,EACT,MAAM,EAAE,gBAAgB,EACxB,OAAO,CAAC,EAAE,MAAM,EAAE,GACjB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAezB"}
@@ -0,0 +1,21 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Entity-Driven MCP Framework — Field Projection Engine
3
+ // ---------------------------------------------------------------------------
4
+ /**
5
+ * Remove omitted fields from a record, unless included via named groups.
6
+ *
7
+ * - Default: omit all fields in `config.omit`
8
+ * - If `include` contains a group name, restore those fields
9
+ * - If `include` contains `"full"`, return the record unchanged
10
+ */
11
+ export function projectFields(record, config, include) {
12
+ // "full" bypass — return everything
13
+ if (include?.includes("full"))
14
+ return { ...record };
15
+ // Resolve which fields should be restored from omit list
16
+ const included = new Set((include ?? []).flatMap((key) => config.groups[key] ?? []));
17
+ // Build omit set (fields to drop minus included overrides)
18
+ const omitSet = new Set(config.omit.filter((k) => !included.has(k)));
19
+ return Object.fromEntries(Object.entries(record).filter(([key]) => !omitSet.has(key)));
20
+ }
21
+ //# sourceMappingURL=projection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"projection.js","sourceRoot":"","sources":["../../src/framework/projection.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,wDAAwD;AACxD,8EAA8E;AAI9E;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAC3B,MAAS,EACT,MAAwB,EACxB,OAAkB;IAElB,oCAAoC;IACpC,IAAI,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC;QAAE,OAAO,EAAE,GAAG,MAAM,EAAE,CAAC;IAEpD,yDAAyD;IACzD,MAAM,QAAQ,GAAG,IAAI,GAAG,CACtB,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAC3D,CAAC;IAEF,2DAA2D;IAC3D,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAErE,OAAO,MAAM,CAAC,WAAW,CACvB,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAC5D,CAAC;AACJ,CAAC"}
@@ -0,0 +1,16 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { EntityConfig, EntityContext, CustomToolConfig } from "./types.js";
3
+ /**
4
+ * Register CRUD tools for an entity on an MCP server.
5
+ *
6
+ * Generates 5 tools: list_*, get_*, create_*, update_*, delete_*
7
+ * where * is the entity's plural name.
8
+ */
9
+ export declare function registerEntityTools<T extends {
10
+ id: string;
11
+ }, TRepos = unknown>(server: McpServer, config: EntityConfig<T, TRepos>, ctx: EntityContext<TRepos>): void;
12
+ /**
13
+ * Register a custom tool on an MCP server.
14
+ */
15
+ export declare function registerCustomTool<TRepos = unknown>(server: McpServer, tool: CustomToolConfig<TRepos>, ctx: EntityContext<TRepos>): void;
16
+ //# sourceMappingURL=register.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"register.d.ts","sourceRoot":"","sources":["../../src/framework/register.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEzE,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAGhF;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,SAAS;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,EAAE,MAAM,GAAG,OAAO,EAC5E,MAAM,EAAE,SAAS,EACjB,MAAM,EAAE,YAAY,CAAC,CAAC,EAAE,MAAM,CAAC,EAC/B,GAAG,EAAE,aAAa,CAAC,MAAM,CAAC,GACzB,IAAI,CAyEN;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,GAAG,OAAO,EACjD,MAAM,EAAE,SAAS,EACjB,IAAI,EAAE,gBAAgB,CAAC,MAAM,CAAC,EAC9B,GAAG,EAAE,aAAa,CAAC,MAAM,CAAC,GACzB,IAAI,CAQN"}
@@ -0,0 +1,70 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Entity-Driven MCP Framework — Tool Registration Engine
3
+ //
4
+ // Reads an EntityConfig and registers all CRUD tools + extra tools on an
5
+ // McpServer instance. This is the bridge between the framework and the SDK.
6
+ // ---------------------------------------------------------------------------
7
+ import { z } from "zod";
8
+ import { createCrudHandlers } from "./handlers.js";
9
+ /**
10
+ * Register CRUD tools for an entity on an MCP server.
11
+ *
12
+ * Generates 5 tools: list_*, get_*, create_*, update_*, delete_*
13
+ * where * is the entity's plural name.
14
+ */
15
+ export function registerEntityTools(server, config, ctx) {
16
+ const plural = config.plural ?? config.name + "s";
17
+ const handlers = createCrudHandlers(config);
18
+ // ---- list ----
19
+ server.tool(`list_${plural}`, config.descriptions?.list ?? `List all ${plural}.`, {
20
+ ...(config.schemas?.list ?? {}),
21
+ ...(config.projection
22
+ ? {
23
+ include: z
24
+ .array(z.string())
25
+ .optional()
26
+ .describe("Opt-in fields excluded by default. Use 'full' for all fields."),
27
+ }
28
+ : {}),
29
+ },
30
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
31
+ ((args) => handlers.handleList(ctx, args)));
32
+ // ---- get ----
33
+ server.tool(`get_${config.name}`, config.descriptions?.get ??
34
+ `Get a single ${config.display} by id or slug (exactly one required).`, { id: z.string().optional(), slug: z.string().optional() },
35
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
36
+ ((args) => handlers.handleGet(ctx, args)));
37
+ // ---- create (only if dataLayer.create exists) ----
38
+ if (config.dataLayer.create) {
39
+ server.tool(`create_${config.name}`, config.descriptions?.create ?? `Create a new ${config.display}.`, config.schemas?.create ?? {},
40
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
41
+ ((args) => handlers.handleCreate(ctx, args)));
42
+ }
43
+ // ---- update (only if dataLayer.update exists) ----
44
+ if (config.dataLayer.update) {
45
+ server.tool(`update_${config.name}`, config.descriptions?.update ??
46
+ `Update an existing ${config.display} by id or slug (exactly one required).`, {
47
+ id: z.string().optional(),
48
+ slug: z.string().optional(),
49
+ ...(config.schemas?.update ?? {}),
50
+ },
51
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
52
+ ((args) => handlers.handleUpdate(ctx, args)));
53
+ }
54
+ // ---- delete (only if dataLayer.delete exists) ----
55
+ if (config.dataLayer.delete) {
56
+ server.tool(`delete_${config.name}`, config.descriptions?.delete ??
57
+ `Delete a ${config.display} by id or slug (exactly one required). Irreversible.`, { id: z.string().optional(), slug: z.string().optional() },
58
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
59
+ ((args) => handlers.handleDelete(ctx, args)));
60
+ }
61
+ }
62
+ /**
63
+ * Register a custom tool on an MCP server.
64
+ */
65
+ export function registerCustomTool(server, tool, ctx) {
66
+ server.tool(tool.name, tool.description, tool.schema.shape,
67
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
68
+ ((args) => tool.handler(ctx, args)));
69
+ }
70
+ //# sourceMappingURL=register.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"register.js","sourceRoot":"","sources":["../../src/framework/register.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ export type IdOrSlug = {
2
+ id?: string;
3
+ slug?: string;
4
+ };
5
+ export type ResolveResult = {
6
+ type: "id";
7
+ value: string;
8
+ } | {
9
+ type: "slug";
10
+ value: string;
11
+ } | {
12
+ error: string;
13
+ };
14
+ /**
15
+ * Validate that exactly one of id or slug is provided.
16
+ * Both → error. Neither → error.
17
+ */
18
+ export declare function validateIdOrSlug(args: IdOrSlug): ResolveResult;
19
+ /**
20
+ * Resolve an entity by id or slug. Returns the entity or an error object.
21
+ * Decoupled from specific entities — takes lookup functions as parameters.
22
+ *
23
+ * @param args - Object with id or slug
24
+ * @param getById - Function to lookup by id
25
+ * @param getBySlug - Function to lookup by slug
26
+ * @param displayName - Human-readable name for error messages
27
+ */
28
+ export declare function resolveEntity<T>(args: IdOrSlug, getById: (id: string) => Promise<T | null>, getBySlug: (slug: string) => Promise<T | null>, displayName?: string): Promise<T | {
29
+ error: string;
30
+ }>;
31
+ /**
32
+ * Type guard to check if a resolve result is an error.
33
+ */
34
+ export declare function isResolveError<T>(result: T | {
35
+ error: string;
36
+ }): result is {
37
+ error: string;
38
+ };
39
+ //# sourceMappingURL=resolve.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../../src/framework/resolve.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,QAAQ,GAAG;IAAE,EAAE,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAEtD,MAAM,MAAM,aAAa,GACrB;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAC7B;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAC/B;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAEtB;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,QAAQ,GAAG,aAAa,CAM9D;AAED;;;;;;;;GAQG;AACH,wBAAsB,aAAa,CAAC,CAAC,EACnC,IAAI,EAAE,QAAQ,EACd,OAAO,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,EAC1C,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,EAC9C,WAAW,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,CAAC,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAUhC;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAC9B,MAAM,EAAE,CAAC,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,GAC5B,MAAM,IAAI;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAO7B"}
@@ -0,0 +1,46 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Entity-Driven MCP Framework — ID/Slug Resolution
3
+ // ---------------------------------------------------------------------------
4
+ /**
5
+ * Validate that exactly one of id or slug is provided.
6
+ * Both → error. Neither → error.
7
+ */
8
+ export function validateIdOrSlug(args) {
9
+ if (args.id && args.slug)
10
+ return { error: "Provide either id or slug, not both." };
11
+ if (args.id)
12
+ return { type: "id", value: args.id };
13
+ if (args.slug)
14
+ return { type: "slug", value: args.slug };
15
+ return { error: "Either id or slug is required." };
16
+ }
17
+ /**
18
+ * Resolve an entity by id or slug. Returns the entity or an error object.
19
+ * Decoupled from specific entities — takes lookup functions as parameters.
20
+ *
21
+ * @param args - Object with id or slug
22
+ * @param getById - Function to lookup by id
23
+ * @param getBySlug - Function to lookup by slug
24
+ * @param displayName - Human-readable name for error messages
25
+ */
26
+ export async function resolveEntity(args, getById, getBySlug, displayName) {
27
+ const v = validateIdOrSlug(args);
28
+ if ("error" in v)
29
+ return v;
30
+ const entity = v.type === "id" ? await getById(v.value) : await getBySlug(v.value);
31
+ if (!entity) {
32
+ const label = displayName ?? "Entity";
33
+ return { error: `${label} not found: ${v.value}` };
34
+ }
35
+ return entity;
36
+ }
37
+ /**
38
+ * Type guard to check if a resolve result is an error.
39
+ */
40
+ export function isResolveError(result) {
41
+ return (typeof result === "object" &&
42
+ result !== null &&
43
+ "error" in result &&
44
+ typeof result.error === "string");
45
+ }
46
+ //# sourceMappingURL=resolve.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve.js","sourceRoot":"","sources":["../../src/framework/resolve.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,mDAAmD;AACnD,8EAA8E;AAS9E;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAc;IAC7C,IAAI,IAAI,CAAC,EAAE,IAAI,IAAI,CAAC,IAAI;QACtB,OAAO,EAAE,KAAK,EAAE,sCAAsC,EAAE,CAAC;IAC3D,IAAI,IAAI,CAAC,EAAE;QAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC;IACnD,IAAI,IAAI,CAAC,IAAI;QAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC;IACzD,OAAO,EAAE,KAAK,EAAE,gCAAgC,EAAE,CAAC;AACrD,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,IAAc,EACd,OAA0C,EAC1C,SAA8C,EAC9C,WAAoB;IAEpB,MAAM,CAAC,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC;IACjC,IAAI,OAAO,IAAI,CAAC;QAAE,OAAO,CAAC,CAAC;IAC3B,MAAM,MAAM,GACV,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;IACtE,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,KAAK,GAAG,WAAW,IAAI,QAAQ,CAAC;QACtC,OAAO,EAAE,KAAK,EAAE,GAAG,KAAK,eAAe,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC;IACrD,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,cAAc,CAC5B,MAA6B;IAE7B,OAAO,CACL,OAAO,MAAM,KAAK,QAAQ;QAC1B,MAAM,KAAK,IAAI;QACf,OAAO,IAAI,MAAM;QACjB,OAAQ,MAA6B,CAAC,KAAK,KAAK,QAAQ,CACzD,CAAC;AACJ,CAAC"}
@@ -0,0 +1,6 @@
1
+ import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
2
+ /** Build a successful MCP tool response with JSON-serialized data. */
3
+ export declare function ok(data: unknown): CallToolResult;
4
+ /** Build an error MCP tool response. */
5
+ export declare function error(message: string): CallToolResult;
6
+ //# sourceMappingURL=response.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"response.d.ts","sourceRoot":"","sources":["../../src/framework/response.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AAEzE,sEAAsE;AACtE,wBAAgB,EAAE,CAAC,IAAI,EAAE,OAAO,GAAG,cAAc,CAIhD;AAED,wCAAwC;AACxC,wBAAgB,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,CAKrD"}
@@ -0,0 +1,17 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Entity-Driven MCP Framework — MCP Response Builders
3
+ // ---------------------------------------------------------------------------
4
+ /** Build a successful MCP tool response with JSON-serialized data. */
5
+ export function ok(data) {
6
+ return {
7
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
8
+ };
9
+ }
10
+ /** Build an error MCP tool response. */
11
+ export function error(message) {
12
+ return {
13
+ content: [{ type: "text", text: message }],
14
+ isError: true,
15
+ };
16
+ }
17
+ //# sourceMappingURL=response.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"response.js","sourceRoot":"","sources":["../../src/framework/response.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,sDAAsD;AACtD,8EAA8E;AAI9E,sEAAsE;AACtE,MAAM,UAAU,EAAE,CAAC,IAAa;IAC9B,OAAO;QACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;KACjE,CAAC;AACJ,CAAC;AAED,wCAAwC;AACxC,MAAM,UAAU,KAAK,CAAC,OAAe;IACnC,OAAO;QACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;QAC1C,OAAO,EAAE,IAAI;KACd,CAAC;AACJ,CAAC"}
@@ -0,0 +1,85 @@
1
+ import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
2
+ import type { ZodTypeAny, z } from "zod";
3
+ export interface ProjectionConfig {
4
+ /** Fields to omit from list responses by default. */
5
+ omit: string[];
6
+ /** Named groups of omitted fields. Key = group name, value = field names. */
7
+ groups: Record<string, string[]>;
8
+ }
9
+ /**
10
+ * Context passed to data layer functions.
11
+ * Generic to allow different repo structures per project.
12
+ */
13
+ export interface EntityContext<TRepos = unknown> {
14
+ repos: TRepos;
15
+ }
16
+ /**
17
+ * Data layer interface for an entity.
18
+ * All methods receive the context and return promises.
19
+ */
20
+ export interface DataLayer<T, TRepos = unknown> {
21
+ list: (ctx: EntityContext<TRepos>, opts: Record<string, unknown>) => Promise<T[]>;
22
+ getById: (ctx: EntityContext<TRepos>, id: string) => Promise<T | null>;
23
+ getBySlug?: (ctx: EntityContext<TRepos>, slug: string) => Promise<T | null>;
24
+ create?: (ctx: EntityContext<TRepos>, input: Record<string, unknown>) => Promise<T>;
25
+ update?: (ctx: EntityContext<TRepos>, id: string, input: Record<string, unknown>) => Promise<T | null>;
26
+ delete?: (ctx: EntityContext<TRepos>, id: string) => Promise<boolean>;
27
+ }
28
+ /**
29
+ * Lifecycle hooks for entity operations.
30
+ */
31
+ export interface EntityHooks<T, TRepos = unknown> {
32
+ beforeCreate?: (ctx: EntityContext<TRepos>, input: Record<string, unknown>) => Promise<Record<string, unknown>>;
33
+ afterCreate?: (ctx: EntityContext<TRepos>, result: T) => Promise<void>;
34
+ beforeUpdate?: (ctx: EntityContext<TRepos>, id: string, input: Record<string, unknown>, existing: T) => Promise<Record<string, unknown>>;
35
+ afterUpdate?: (ctx: EntityContext<TRepos>, id: string, input: Record<string, unknown>, result: T) => Promise<void>;
36
+ beforeDelete?: (ctx: EntityContext<TRepos>, id: string, existing: T) => Promise<void>;
37
+ afterDelete?: (ctx: EntityContext<TRepos>, id: string) => Promise<void>;
38
+ }
39
+ /**
40
+ * Schema definitions for entity operations.
41
+ */
42
+ export interface EntitySchemas {
43
+ list?: Record<string, ZodTypeAny>;
44
+ create?: Record<string, ZodTypeAny>;
45
+ update?: Record<string, ZodTypeAny>;
46
+ }
47
+ /**
48
+ * Description text for generated tools.
49
+ */
50
+ export interface EntityDescriptions {
51
+ list?: string;
52
+ get?: string;
53
+ create?: string;
54
+ update?: string;
55
+ delete?: string;
56
+ }
57
+ /**
58
+ * Full entity configuration.
59
+ */
60
+ export interface EntityConfig<T, TRepos = unknown> {
61
+ /** Singular name (e.g., "product") */
62
+ name: string;
63
+ /** Human-readable display name (e.g., "理财产品") */
64
+ display: string;
65
+ /** Plural name for list operations (e.g., "products") */
66
+ plural: string;
67
+ /** Data access layer */
68
+ dataLayer: DataLayer<T, TRepos>;
69
+ /** Zod schemas for input validation */
70
+ schemas?: EntitySchemas;
71
+ /** Tool descriptions for MCP */
72
+ descriptions?: EntityDescriptions;
73
+ /** Field projection configuration */
74
+ projection?: ProjectionConfig;
75
+ /** Lifecycle hooks */
76
+ hooks?: EntityHooks<T, TRepos>;
77
+ }
78
+ export type ToolHandler<TRepos = unknown> = (ctx: EntityContext<TRepos>, args: Record<string, unknown>) => Promise<CallToolResult>;
79
+ export interface CustomToolConfig<TRepos = unknown> {
80
+ name: string;
81
+ description: string;
82
+ schema: z.ZodObject<Record<string, ZodTypeAny>>;
83
+ handler: ToolHandler<TRepos>;
84
+ }
85
+ //# sourceMappingURL=types.d.ts.map