@gmickel/gno 1.41.0 → 1.43.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.
@@ -0,0 +1,112 @@
1
+ /**
2
+ * MCP tool profiles.
3
+ *
4
+ * A profile decides which tools a server advertises. `full` is today's whole
5
+ * registry; `core` is the small read set the retrieval playbook already steers
6
+ * agents to, plus an exact write allowlist behind `--enable-write`. The write
7
+ * gate is orthogonal: a profile only ever narrows the set the gate exposes.
8
+ *
9
+ * @module src/mcp/tool-profile
10
+ */
11
+
12
+ import type {
13
+ Icon,
14
+ McpServer,
15
+ StandardSchemaWithJSON,
16
+ ToolAnnotations,
17
+ ToolCallback,
18
+ } from "@modelcontextprotocol/server";
19
+
20
+ export const MCP_TOOL_PROFILES = ["core", "full"] as const;
21
+
22
+ export type McpToolProfile = (typeof MCP_TOOL_PROFILES)[number];
23
+
24
+ export const DEFAULT_MCP_TOOL_PROFILE: McpToolProfile = "full";
25
+
26
+ /** Read tools the core profile advertises without `--enable-write`. */
27
+ export const MCP_CORE_READ_TOOL_NAMES: ReadonlySet<string> = new Set([
28
+ "gno_query",
29
+ "gno_search",
30
+ "gno_get",
31
+ "gno_multi_get",
32
+ "gno_context",
33
+ "gno_changes",
34
+ "gno_recall",
35
+ ]);
36
+
37
+ /**
38
+ * Write tools the core profile adds with `--enable-write`.
39
+ *
40
+ * `gno_job_status` is deliberately absent: `gno_capture` and `gno_remember`
41
+ * complete synchronously (neither starts a JobManager job), so nothing the
42
+ * core profile exposes ever returns a job ID to poll.
43
+ */
44
+ export const MCP_CORE_WRITE_TOOL_NAMES: ReadonlySet<string> = new Set([
45
+ "gno_capture",
46
+ "gno_remember",
47
+ ]);
48
+
49
+ export function isMcpToolProfile(value: unknown): value is McpToolProfile {
50
+ return (
51
+ typeof value === "string" &&
52
+ (MCP_TOOL_PROFILES as readonly string[]).includes(value)
53
+ );
54
+ }
55
+
56
+ /** Parse a CLI/config value; `undefined` means "not given" (caller applies precedence). */
57
+ export function parseMcpToolProfile(
58
+ value: unknown
59
+ ): McpToolProfile | undefined {
60
+ if (value === undefined) return undefined;
61
+ if (isMcpToolProfile(value)) return value;
62
+ throw new Error(
63
+ `Invalid tool profile: ${JSON.stringify(value)}. Must be one of: ${MCP_TOOL_PROFILES.join(", ")}.`
64
+ );
65
+ }
66
+
67
+ /** Tool names a profile advertises; `null` means every registered tool. */
68
+ export function mcpToolProfileAllowlist(
69
+ profile: McpToolProfile
70
+ ): ReadonlySet<string> | null {
71
+ if (profile === "full") return null;
72
+ return new Set([...MCP_CORE_READ_TOOL_NAMES, ...MCP_CORE_WRITE_TOOL_NAMES]);
73
+ }
74
+
75
+ /**
76
+ * Registration through a profile. Mirrors `McpServer.registerTool`'s
77
+ * schema-object overload minus the return value: a tool outside the profile
78
+ * is never registered, so there is no `RegisteredTool` to hand back, and
79
+ * `registerTools` never reads one.
80
+ */
81
+ export type ProfileToolRegistrar = <
82
+ OutputArgs extends StandardSchemaWithJSON,
83
+ InputArgs extends StandardSchemaWithJSON | undefined = undefined,
84
+ >(
85
+ name: string,
86
+ config: {
87
+ title?: string;
88
+ description?: string;
89
+ inputSchema?: InputArgs;
90
+ outputSchema?: OutputArgs;
91
+ annotations?: ToolAnnotations;
92
+ icons?: Icon[];
93
+ _meta?: Record<string, unknown>;
94
+ },
95
+ cb: ToolCallback<InputArgs>
96
+ ) => void;
97
+
98
+ /**
99
+ * Wrap `server.registerTool` so tools outside the profile are never
100
+ * registered. `full` forwards every call unchanged, so registration order
101
+ * and wire bytes stay identical to an unprofiled server.
102
+ */
103
+ export function createProfileToolRegistrar(
104
+ server: McpServer,
105
+ profile: McpToolProfile
106
+ ): ProfileToolRegistrar {
107
+ const allowlist = mcpToolProfileAllowlist(profile);
108
+ return (name, config, cb) => {
109
+ if (allowlist !== null && !allowlist.has(name)) return;
110
+ server.registerTool(name, config, cb);
111
+ };
112
+ }