@rsconcept/rstool-mcp 0.1.5 → 0.3.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 CHANGED
@@ -44,12 +44,17 @@ Restart Cursor; the tools appear under the **rstool** server. Pin a version if y
44
44
  "mcpServers": {
45
45
  "rstool": {
46
46
  "command": "npx",
47
- "args": ["-y", "@rsconcept/rstool-mcp@0.1.0"]
47
+ "args": ["-y", "@rsconcept/rstool-mcp@0.2.0"],
48
+ "env": {
49
+ "RSTOOL_PERSISTENCE_DIR": "C:/path/to/rstool-sessions"
50
+ }
48
51
  }
49
52
  }
50
53
  }
51
54
  ```
52
55
 
56
+ Set `RSTOOL_PERSISTENCE_DIR` to keep sessions across MCP server restarts (optional).
57
+
53
58
  ## Configure in Claude Desktop
54
59
 
55
60
  Edit `claude_desktop_config.json` (`~/Library/Application Support/Claude/` on macOS, `%APPDATA%\Claude\` on Windows):
@@ -67,35 +72,35 @@ Edit `claude_desktop_config.json` (`~/Library/Application Support/Claude/` on ma
67
72
 
68
73
  ## Exposed tools
69
74
 
70
- | MCP tool | Wraps | Notes |
71
- |----------|-------|-------|
72
- | `ping` | — | Liveness + `contractVersion`. |
73
- | `list_methods` | — | Enumerate available tools. |
74
- | `create_session` | `RSToolAgent.createSession` | Optional `initial` seed. |
75
- | `add_or_update_constituenta` | `addOrUpdateConstituenta` | Single upsert. |
76
- | `analyze_expression` | `analyzeExpression` | Parser + semantic. |
77
- | `get_form_state` | `getFormState` | Current `SessionState`. |
78
- | `list_diagnostics` | `listDiagnostics` | Filterable. |
79
- | `commit_step` | `commitStep` | Records a `SessionRevision`. |
80
- | `export_session` | `exportSession` | JSON string. |
81
- | `import_session` | `importSession` | Restore from JSON. |
82
- | `set_constituenta_value` | `setConstituentaValue` | Bind interpretable. |
83
- | `set_constituenta_values` | `setConstituentaValues` | Batch bindings. |
84
- | `clear_constituenta_values` | `clearConstituentaValues` | Reset values. |
85
- | `get_model_state` | `getModelState` | Values + statuses. |
86
- | `evaluate_expression` | `evaluateExpression` | Scratch evaluation. |
87
- | `evaluate_constituenta` | `evaluateConstituenta` | Stored derived constituent. |
88
- | `recalculate_model` | `recalculateModel` | Full recompute. |
89
-
90
- Input schemas are intentionally permissive (`additionalProperties: true`) where the rstool input is structurally rich (e.g. `ConstituentaDraft`, `SessionState`); the wrapped `RSToolAgent` validates inputs at runtime and returns deterministic error responses, surfaced through `isError: true` on the MCP response.
75
+ | MCP tool | Wraps | Notes |
76
+ | --------------------- | ------------------- | -------------------------------------------- |
77
+ | `ping` | — | Liveness + `contractVersion`. |
78
+ | `list_methods` | — | Enumerate available tools. |
79
+ | `ensure_session` | `ensureSession` | Return active session or create one. |
80
+ | `create_session` | `createSession` | Optional `initial` seed. |
81
+ | `set_current_session` | `setCurrentSession` | Switch active session. |
82
+ | `apply_schema_patch` | `applySchemaPatch` | Primary schema edit path. |
83
+ | `get_session_state` | `getSessionState` | `detail=summary` (default) or `full`. |
84
+ | `analyze_expression` | `analyzeExpression` | Scratch analysis. |
85
+ | `list_diagnostics` | `listDiagnostics` | Optional `constituentId`. |
86
+ | `commit_step` | `commitStep` | Records a revision. |
87
+ | `export_session` | `exportSession` | JSON string. |
88
+ | `export_portal` | `exportPortal` | `kind=schema\|model`, `format=json\|object`. |
89
+ | `import_data` | `importData` | `kind=auto` by default. |
90
+ | `set_model_values` | `setModelValues` | `set[]` and/or `clear[]`. |
91
+ | `get_model_state` | `getModelState` | Values + statuses. |
92
+ | `evaluate` | `evaluate` | Scratch or stored constituent. |
93
+ | `recalculate_model` | `recalculateModel` | Full recompute. |
94
+
95
+ Session tools accept optional `sessionId` (flat params, no `input` wrapper). Prefer `apply_schema_patch` for schema edits.
91
96
 
92
97
  ## Programmatic use
93
98
 
94
99
  If you want to build a custom transport (HTTP, in-process) instead of stdio:
95
100
 
96
101
  ```ts
97
- import { StreamableHttpServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
98
- import { buildRSToolMcpServer } from '@rsconcept/rstool-mcp';
102
+ import { StreamableHttpServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
103
+ import { buildRSToolMcpServer } from "@rsconcept/rstool-mcp";
99
104
 
100
105
  const server = buildRSToolMcpServer();
101
106
  const transport = new StreamableHttpServerTransport();
@@ -105,11 +110,15 @@ await server.connect(transport);
105
110
  Or share a single `RSToolAgent` between multiple servers / requests:
106
111
 
107
112
  ```ts
108
- import { RSToolAgent } from '@rsconcept/rstool';
109
- import { buildRSToolMcpServer } from '@rsconcept/rstool-mcp';
113
+ import { RSToolAgent } from "@rsconcept/rstool";
114
+ import { buildRSToolMcpServer } from "@rsconcept/rstool-mcp";
110
115
 
111
116
  const tool = new RSToolAgent();
112
- const server = buildRSToolMcpServer({ tool, name: 'my-rstool-mcp', version: '0.2.0' });
117
+ const server = buildRSToolMcpServer({
118
+ tool,
119
+ name: "my-rstool-mcp",
120
+ version: "0.2.0",
121
+ });
113
122
  ```
114
123
 
115
124
  ## Companion docs
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { t as buildRSToolMcpServer } from "../server-CzaNBfgR.js";
2
+ import { t as buildRSToolMcpServer } from "../server-CWm4FZFL.js";
3
3
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
4
  //#region src/bin/server.ts
5
5
  async function main() {
package/dist/index.d.ts CHANGED
@@ -5,6 +5,8 @@ import { RSToolAgent } from "@rsconcept/rstool";
5
5
  interface BuildRSToolMcpServerOptions {
6
6
  /** Pre-built RSToolAgent. Defaults to a fresh in-memory instance. */
7
7
  tool?: RSToolAgent;
8
+ /** Persist sessions to this directory (also reads RSTOOL_PERSISTENCE_DIR when set). */
9
+ persistenceDir?: string;
8
10
  /** Server name advertised to MCP clients. */
9
11
  name?: string;
10
12
  /** Server version advertised to MCP clients. */
@@ -33,7 +35,7 @@ interface ToolDefinition {
33
35
  name: string;
34
36
  description: string;
35
37
  inputSchema: {
36
- type: 'object';
38
+ type: "object";
37
39
  properties: Record<string, unknown>;
38
40
  required?: string[];
39
41
  additionalProperties?: boolean;
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- import { n as TOOL_DEFINITIONS, t as buildRSToolMcpServer } from "./server-CzaNBfgR.js";
1
+ import { n as TOOL_DEFINITIONS, t as buildRSToolMcpServer } from "./server-CWm4FZFL.js";
2
2
  export { TOOL_DEFINITIONS, buildRSToolMcpServer };
@@ -0,0 +1,362 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
3
+ import { RSToolAgent } from "@rsconcept/rstool";
4
+ //#region src/tools.ts
5
+ const sessionId = {
6
+ type: "string",
7
+ description: "Session id. Omit to use the current active session."
8
+ };
9
+ const agentPatchSchema = {
10
+ type: "object",
11
+ description: "Agent-friendly constituent patch. id and cstType are optional; cstType is inferred from alias prefixes X/C/S/D/A/F/P.",
12
+ properties: {
13
+ id: { type: "number" },
14
+ alias: { type: "string" },
15
+ cstType: { type: "string" },
16
+ definitionFormal: { type: "string" },
17
+ term: { type: "string" },
18
+ definitionText: { type: "string" },
19
+ convention: { type: "string" }
20
+ },
21
+ required: ["alias"]
22
+ };
23
+ function optionalSessionId(args) {
24
+ const value = args.sessionId;
25
+ return typeof value === "string" && value.length > 0 ? value : void 0;
26
+ }
27
+ function omitSessionId(args) {
28
+ const { sessionId: _sessionId, ...rest } = args;
29
+ return rest;
30
+ }
31
+ const TOOL_DEFINITIONS = [
32
+ {
33
+ name: "ping",
34
+ description: "Liveness check; returns {pong: true} and the active rstool contract version.",
35
+ inputSchema: {
36
+ type: "object",
37
+ properties: {},
38
+ additionalProperties: false
39
+ },
40
+ invoke: (tool) => ({
41
+ pong: true,
42
+ contractVersion: tool.contractVersion
43
+ })
44
+ },
45
+ {
46
+ name: "list_methods",
47
+ description: "List all rstool methods exposed as MCP tools.",
48
+ inputSchema: {
49
+ type: "object",
50
+ properties: {},
51
+ additionalProperties: false
52
+ },
53
+ invoke: () => TOOL_DEFINITIONS.map((definition) => definition.name)
54
+ },
55
+ {
56
+ name: "ensure_session",
57
+ description: "Return the current active session, or create one if none exists. Optional initial seed is used only when creating.",
58
+ inputSchema: {
59
+ type: "object",
60
+ properties: { initial: {
61
+ type: "object",
62
+ properties: {
63
+ alias: { type: "string" },
64
+ title: { type: "string" },
65
+ comment: { type: "string" }
66
+ },
67
+ additionalProperties: true
68
+ } },
69
+ additionalProperties: false
70
+ },
71
+ invoke: (tool, args) => tool.ensureSession(args.initial)
72
+ },
73
+ {
74
+ name: "create_session",
75
+ description: "Create a fresh in-memory rstool session and set it as the current active session.",
76
+ inputSchema: {
77
+ type: "object",
78
+ properties: { initial: {
79
+ type: "object",
80
+ description: "Optional partial SessionState seed (alias, title, comment).",
81
+ properties: {
82
+ alias: { type: "string" },
83
+ title: { type: "string" },
84
+ comment: { type: "string" }
85
+ },
86
+ additionalProperties: false
87
+ } },
88
+ additionalProperties: false
89
+ },
90
+ invoke: (tool, args) => tool.createSession(args.initial)
91
+ },
92
+ {
93
+ name: "set_current_session",
94
+ description: "Set the active session by id.",
95
+ inputSchema: {
96
+ type: "object",
97
+ properties: { sessionId },
98
+ required: ["sessionId"]
99
+ },
100
+ invoke: (tool, args) => tool.setCurrentSession(String(args.sessionId))
101
+ },
102
+ {
103
+ name: "apply_schema_patch",
104
+ description: "Preferred schema edit path. Batch patch with inferred ids and cstType, dependency ordering, and compact summary.",
105
+ inputSchema: {
106
+ type: "object",
107
+ properties: {
108
+ sessionId,
109
+ initial: {
110
+ type: "object",
111
+ properties: {
112
+ alias: { type: "string" },
113
+ title: { type: "string" },
114
+ comment: { type: "string" }
115
+ },
116
+ additionalProperties: true
117
+ },
118
+ items: {
119
+ type: "array",
120
+ items: agentPatchSchema
121
+ },
122
+ mode: {
123
+ type: "string",
124
+ enum: ["atomic", "best_effort"]
125
+ },
126
+ commitMessage: { type: "string" }
127
+ },
128
+ required: ["items"]
129
+ },
130
+ invoke: (tool, args) => tool.applySchemaPatch(omitSessionId(args), optionalSessionId(args))
131
+ },
132
+ {
133
+ name: "get_session_state",
134
+ description: "Return session state. detail=summary (default): compact metadata, aliases, diagnostics. detail=full: complete SessionState clone.",
135
+ inputSchema: {
136
+ type: "object",
137
+ properties: {
138
+ sessionId,
139
+ detail: {
140
+ type: "string",
141
+ enum: ["summary", "full"]
142
+ }
143
+ }
144
+ },
145
+ invoke: (tool, args) => tool.getSessionState(args.detail ?? "summary", optionalSessionId(args))
146
+ },
147
+ {
148
+ name: "analyze_expression",
149
+ description: "Parse and type-check a scratch expression without saving it. Does not record diagnostics unless recordDiagnostics=true.",
150
+ inputSchema: {
151
+ type: "object",
152
+ properties: {
153
+ sessionId,
154
+ expression: { type: "string" },
155
+ cstType: { type: "string" },
156
+ recordDiagnostics: { type: "boolean" }
157
+ },
158
+ required: ["expression", "cstType"]
159
+ },
160
+ invoke: (tool, args) => tool.analyzeExpression(omitSessionId(args), optionalSessionId(args))
161
+ },
162
+ {
163
+ name: "list_diagnostics",
164
+ description: "List active diagnostics for the session (one record set per constituent, not a historical log).",
165
+ inputSchema: {
166
+ type: "object",
167
+ properties: {
168
+ sessionId,
169
+ constituentId: { type: "number" }
170
+ }
171
+ },
172
+ invoke: (tool, args) => {
173
+ const constituentId = args.constituentId;
174
+ const filters = typeof constituentId === "number" ? { constituentId } : void 0;
175
+ return tool.listDiagnostics(filters, optionalSessionId(args));
176
+ }
177
+ },
178
+ {
179
+ name: "commit_step",
180
+ description: "Record a session revision with an optional human-readable message.",
181
+ inputSchema: {
182
+ type: "object",
183
+ properties: {
184
+ sessionId,
185
+ message: { type: "string" }
186
+ }
187
+ },
188
+ invoke: (tool, args) => tool.commitStep(args.message, optionalSessionId(args))
189
+ },
190
+ {
191
+ name: "export_session",
192
+ description: "Serialize the session to a JSON string suitable for import_data with kind=session.",
193
+ inputSchema: {
194
+ type: "object",
195
+ properties: { sessionId }
196
+ },
197
+ invoke: (tool, args) => tool.exportSession(optionalSessionId(args))
198
+ },
199
+ {
200
+ name: "export_portal",
201
+ description: "Export Portal Load-from-JSON payload. kind=schema|model; format=json (default string) or object.",
202
+ inputSchema: {
203
+ type: "object",
204
+ properties: {
205
+ sessionId,
206
+ kind: {
207
+ type: "string",
208
+ enum: ["schema", "model"]
209
+ },
210
+ format: {
211
+ type: "string",
212
+ enum: ["json", "object"]
213
+ }
214
+ },
215
+ required: ["kind"]
216
+ },
217
+ invoke: (tool, args) => tool.exportPortal(omitSessionId(args), optionalSessionId(args))
218
+ },
219
+ {
220
+ name: "import_data",
221
+ description: "Import a session from export_session JSON, Portal schema JSON, or GET /api/rsforms/:id/details. kind=auto (default) detects the shape.",
222
+ inputSchema: {
223
+ type: "object",
224
+ properties: {
225
+ payload: { description: "JSON string or parsed object (session export, Portal schema, or rsform details)." },
226
+ kind: {
227
+ type: "string",
228
+ enum: [
229
+ "auto",
230
+ "session",
231
+ "portal-schema",
232
+ "portal-details"
233
+ ]
234
+ }
235
+ },
236
+ required: ["payload"]
237
+ },
238
+ invoke: (tool, args) => tool.importData(args.payload, args.kind)
239
+ },
240
+ {
241
+ name: "set_model_values",
242
+ description: "Set and/or clear interpretable model bindings. Pass set[] for bindings and clear[] for constituent ids to reset.",
243
+ inputSchema: {
244
+ type: "object",
245
+ properties: {
246
+ sessionId,
247
+ set: {
248
+ type: "array",
249
+ items: {
250
+ type: "object",
251
+ properties: {
252
+ target: { type: "number" },
253
+ type: { type: "string" },
254
+ value: {}
255
+ },
256
+ required: ["target", "value"]
257
+ }
258
+ },
259
+ clear: {
260
+ type: "array",
261
+ items: { type: "number" }
262
+ }
263
+ }
264
+ },
265
+ invoke: (tool, args) => tool.setModelValues(omitSessionId(args), optionalSessionId(args))
266
+ },
267
+ {
268
+ name: "get_model_state",
269
+ description: "Return the SessionModelState.",
270
+ inputSchema: {
271
+ type: "object",
272
+ properties: { sessionId }
273
+ },
274
+ invoke: (tool, args) => tool.getModelState(optionalSessionId(args))
275
+ },
276
+ {
277
+ name: "evaluate",
278
+ description: "Evaluate a scratch expression (expression + cstType) or a stored constituent (constituentId).",
279
+ inputSchema: {
280
+ type: "object",
281
+ properties: {
282
+ sessionId,
283
+ expression: { type: "string" },
284
+ cstType: { type: "string" },
285
+ constituentId: { type: "number" }
286
+ }
287
+ },
288
+ invoke: (tool, args) => tool.evaluate(omitSessionId(args), optionalSessionId(args))
289
+ },
290
+ {
291
+ name: "recalculate_model",
292
+ description: "Recompute every inferrable constituent.",
293
+ inputSchema: {
294
+ type: "object",
295
+ properties: { sessionId }
296
+ },
297
+ invoke: (tool, args) => tool.recalculateModel(optionalSessionId(args))
298
+ }
299
+ ];
300
+ //#endregion
301
+ //#region src/server.ts
302
+ /**
303
+ * Build an MCP `McpServer` instance that exposes the `@rsconcept/rstool` contract.
304
+ *
305
+ * The returned server is not yet connected to a transport. Wrap it with a
306
+ * `StdioServerTransport` (for local subprocess hosts like Cursor and Claude Desktop) or
307
+ * an HTTP transport, then call `server.connect(transport)`.
308
+ *
309
+ * @example
310
+ * ```ts
311
+ * import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
312
+ * import { buildRSToolMcpServer } from '@rsconcept/rstool-mcp';
313
+ *
314
+ * const server = buildRSToolMcpServer();
315
+ * await server.connect(new StdioServerTransport());
316
+ * ```
317
+ */
318
+ function buildRSToolMcpServer(options = {}) {
319
+ const persistenceDir = options.persistenceDir ?? process.env.RSTOOL_PERSISTENCE_DIR;
320
+ const tool = options.tool ?? new RSToolAgent(persistenceDir ? { persistenceDir } : void 0);
321
+ const server = new McpServer({
322
+ name: options.name ?? "rstool-mcp",
323
+ version: options.version ?? "0.1.0"
324
+ }, { capabilities: { tools: {} } });
325
+ const definitionByName = new Map(TOOL_DEFINITIONS.map((definition) => [definition.name, definition]));
326
+ server.server.setRequestHandler(ListToolsRequestSchema, () => Promise.resolve({ tools: TOOL_DEFINITIONS.map((definition) => ({
327
+ name: definition.name,
328
+ description: definition.description,
329
+ inputSchema: definition.inputSchema
330
+ })) }));
331
+ server.server.setRequestHandler(CallToolRequestSchema, async (request) => {
332
+ const definition = definitionByName.get(request.params.name);
333
+ if (!definition) return {
334
+ isError: true,
335
+ content: [{
336
+ type: "text",
337
+ text: `Unknown rstool tool: ${request.params.name}`
338
+ }]
339
+ };
340
+ try {
341
+ const result = await definition.invoke(tool, request.params.arguments ?? {});
342
+ return { content: [{
343
+ type: "text",
344
+ text: typeof result === "string" ? result : JSON.stringify(result, null, 2)
345
+ }] };
346
+ } catch (error) {
347
+ const message = error instanceof Error ? error.message : String(error);
348
+ return {
349
+ isError: true,
350
+ content: [{
351
+ type: "text",
352
+ text: `rstool error in ${definition.name}: ${message}`
353
+ }]
354
+ };
355
+ }
356
+ });
357
+ return server;
358
+ }
359
+ //#endregion
360
+ export { TOOL_DEFINITIONS as n, buildRSToolMcpServer as t };
361
+
362
+ //# sourceMappingURL=server-CWm4FZFL.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server-CWm4FZFL.js","names":[],"sources":["../src/tools.ts","../src/server.ts"],"sourcesContent":["/**\n * MCP tool definitions for the @rsconcept/rstool contract.\n */\n\nimport { type RSToolAgent } from \"@rsconcept/rstool\";\n\nexport interface ToolDefinition {\n name: string;\n description: string;\n inputSchema: {\n type: \"object\";\n properties: Record<string, unknown>;\n required?: string[];\n additionalProperties?: boolean;\n };\n invoke: (\n tool: RSToolAgent,\n args: Record<string, unknown>,\n ) => unknown | Promise<unknown>;\n}\n\nconst sessionId = {\n type: \"string\",\n description: \"Session id. Omit to use the current active session.\",\n};\n\nconst agentPatchSchema = {\n type: \"object\",\n description:\n \"Agent-friendly constituent patch. id and cstType are optional; cstType is inferred from alias prefixes X/C/S/D/A/F/P.\",\n properties: {\n id: { type: \"number\" },\n alias: { type: \"string\" },\n cstType: { type: \"string\" },\n definitionFormal: { type: \"string\" },\n term: { type: \"string\" },\n definitionText: { type: \"string\" },\n convention: { type: \"string\" },\n },\n required: [\"alias\"],\n};\n\nfunction optionalSessionId(args: Record<string, unknown>): string | undefined {\n const value = args.sessionId;\n return typeof value === \"string\" && value.length > 0 ? value : undefined;\n}\n\nfunction omitSessionId(args: Record<string, unknown>): Record<string, unknown> {\n const { sessionId: _sessionId, ...rest } = args;\n return rest;\n}\n\nexport const TOOL_DEFINITIONS: ToolDefinition[] = [\n {\n name: \"ping\",\n description:\n \"Liveness check; returns {pong: true} and the active rstool contract version.\",\n inputSchema: {\n type: \"object\",\n properties: {},\n additionalProperties: false,\n },\n invoke: (tool) => ({ pong: true, contractVersion: tool.contractVersion }),\n },\n {\n name: \"list_methods\",\n description: \"List all rstool methods exposed as MCP tools.\",\n inputSchema: {\n type: \"object\",\n properties: {},\n additionalProperties: false,\n },\n invoke: () => TOOL_DEFINITIONS.map((definition) => definition.name),\n },\n {\n name: \"ensure_session\",\n description:\n \"Return the current active session, or create one if none exists. Optional initial seed is used only when creating.\",\n inputSchema: {\n type: \"object\",\n properties: {\n initial: {\n type: \"object\",\n properties: {\n alias: { type: \"string\" },\n title: { type: \"string\" },\n comment: { type: \"string\" },\n },\n additionalProperties: true,\n },\n },\n additionalProperties: false,\n },\n invoke: (tool, args) => tool.ensureSession(args.initial as never),\n },\n {\n name: \"create_session\",\n description:\n \"Create a fresh in-memory rstool session and set it as the current active session.\",\n inputSchema: {\n type: \"object\",\n properties: {\n initial: {\n type: \"object\",\n description:\n \"Optional partial SessionState seed (alias, title, comment).\",\n properties: {\n alias: { type: \"string\" },\n title: { type: \"string\" },\n comment: { type: \"string\" },\n },\n additionalProperties: false,\n },\n },\n additionalProperties: false,\n },\n invoke: (tool, args) => tool.createSession(args.initial as never),\n },\n {\n name: \"set_current_session\",\n description: \"Set the active session by id.\",\n inputSchema: {\n type: \"object\",\n properties: { sessionId },\n required: [\"sessionId\"],\n },\n invoke: (tool, args) => tool.setCurrentSession(String(args.sessionId)),\n },\n {\n name: \"apply_schema_patch\",\n description:\n \"Preferred schema edit path. Batch patch with inferred ids and cstType, dependency ordering, and compact summary.\",\n inputSchema: {\n type: \"object\",\n properties: {\n sessionId,\n initial: {\n type: \"object\",\n properties: {\n alias: { type: \"string\" },\n title: { type: \"string\" },\n comment: { type: \"string\" },\n },\n additionalProperties: true,\n },\n items: { type: \"array\", items: agentPatchSchema },\n mode: { type: \"string\", enum: [\"atomic\", \"best_effort\"] },\n commitMessage: { type: \"string\" },\n },\n required: [\"items\"],\n },\n invoke: (tool, args) =>\n tool.applySchemaPatch(\n omitSessionId(args) as never,\n optionalSessionId(args),\n ),\n },\n {\n name: \"get_session_state\",\n description:\n \"Return session state. detail=summary (default): compact metadata, aliases, diagnostics. detail=full: complete SessionState clone.\",\n inputSchema: {\n type: \"object\",\n properties: {\n sessionId,\n detail: { type: \"string\", enum: [\"summary\", \"full\"] },\n },\n },\n invoke: (tool, args) =>\n tool.getSessionState(\n (args.detail as \"summary\" | \"full\" | undefined) ?? \"summary\",\n optionalSessionId(args),\n ),\n },\n {\n name: \"analyze_expression\",\n description:\n \"Parse and type-check a scratch expression without saving it. Does not record diagnostics unless recordDiagnostics=true.\",\n inputSchema: {\n type: \"object\",\n properties: {\n sessionId,\n expression: { type: \"string\" },\n cstType: { type: \"string\" },\n recordDiagnostics: { type: \"boolean\" },\n },\n required: [\"expression\", \"cstType\"],\n },\n invoke: (tool, args) =>\n tool.analyzeExpression(\n omitSessionId(args) as never,\n optionalSessionId(args),\n ),\n },\n {\n name: \"list_diagnostics\",\n description:\n \"List active diagnostics for the session (one record set per constituent, not a historical log).\",\n inputSchema: {\n type: \"object\",\n properties: {\n sessionId,\n constituentId: { type: \"number\" },\n },\n },\n invoke: (tool, args) => {\n const constituentId = args.constituentId;\n const filters =\n typeof constituentId === \"number\" ? { constituentId } : undefined;\n return tool.listDiagnostics(filters, optionalSessionId(args));\n },\n },\n {\n name: \"commit_step\",\n description:\n \"Record a session revision with an optional human-readable message.\",\n inputSchema: {\n type: \"object\",\n properties: {\n sessionId,\n message: { type: \"string\" },\n },\n },\n invoke: (tool, args) =>\n tool.commitStep(\n args.message as string | undefined,\n optionalSessionId(args),\n ),\n },\n {\n name: \"export_session\",\n description:\n \"Serialize the session to a JSON string suitable for import_data with kind=session.\",\n inputSchema: {\n type: \"object\",\n properties: { sessionId },\n },\n invoke: (tool, args) => tool.exportSession(optionalSessionId(args)),\n },\n {\n name: \"export_portal\",\n description:\n \"Export Portal Load-from-JSON payload. kind=schema|model; format=json (default string) or object.\",\n inputSchema: {\n type: \"object\",\n properties: {\n sessionId,\n kind: { type: \"string\", enum: [\"schema\", \"model\"] },\n format: { type: \"string\", enum: [\"json\", \"object\"] },\n },\n required: [\"kind\"],\n },\n invoke: (tool, args) =>\n tool.exportPortal(omitSessionId(args) as never, optionalSessionId(args)),\n },\n {\n name: \"import_data\",\n description:\n \"Import a session from export_session JSON, Portal schema JSON, or GET /api/rsforms/:id/details. kind=auto (default) detects the shape.\",\n inputSchema: {\n type: \"object\",\n properties: {\n payload: {\n description:\n \"JSON string or parsed object (session export, Portal schema, or rsform details).\",\n },\n kind: {\n type: \"string\",\n enum: [\"auto\", \"session\", \"portal-schema\", \"portal-details\"],\n },\n },\n required: [\"payload\"],\n },\n invoke: (tool, args) =>\n tool.importData(args.payload as string | object, args.kind as never),\n },\n {\n name: \"set_model_values\",\n description:\n \"Set and/or clear interpretable model bindings. Pass set[] for bindings and clear[] for constituent ids to reset.\",\n inputSchema: {\n type: \"object\",\n properties: {\n sessionId,\n set: {\n type: \"array\",\n items: {\n type: \"object\",\n properties: {\n target: { type: \"number\" },\n type: { type: \"string\" },\n value: {},\n },\n required: [\"target\", \"value\"],\n },\n },\n clear: { type: \"array\", items: { type: \"number\" } },\n },\n },\n invoke: (tool, args) =>\n tool.setModelValues(\n omitSessionId(args) as never,\n optionalSessionId(args),\n ),\n },\n {\n name: \"get_model_state\",\n description: \"Return the SessionModelState.\",\n inputSchema: {\n type: \"object\",\n properties: { sessionId },\n },\n invoke: (tool, args) => tool.getModelState(optionalSessionId(args)),\n },\n {\n name: \"evaluate\",\n description:\n \"Evaluate a scratch expression (expression + cstType) or a stored constituent (constituentId).\",\n inputSchema: {\n type: \"object\",\n properties: {\n sessionId,\n expression: { type: \"string\" },\n cstType: { type: \"string\" },\n constituentId: { type: \"number\" },\n },\n },\n invoke: (tool, args) =>\n tool.evaluate(omitSessionId(args) as never, optionalSessionId(args)),\n },\n {\n name: \"recalculate_model\",\n description: \"Recompute every inferrable constituent.\",\n inputSchema: {\n type: \"object\",\n properties: { sessionId },\n },\n invoke: (tool, args) => tool.recalculateModel(optionalSessionId(args)),\n },\n];\n","import { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport {\n CallToolRequestSchema,\n ListToolsRequestSchema,\n} from \"@modelcontextprotocol/sdk/types.js\";\n\nimport { RSToolAgent } from \"@rsconcept/rstool\";\n\nimport { TOOL_DEFINITIONS, type ToolDefinition } from \"./tools\";\n\nexport interface BuildRSToolMcpServerOptions {\n /** Pre-built RSToolAgent. Defaults to a fresh in-memory instance. */\n tool?: RSToolAgent;\n /** Persist sessions to this directory (also reads RSTOOL_PERSISTENCE_DIR when set). */\n persistenceDir?: string;\n /** Server name advertised to MCP clients. */\n name?: string;\n /** Server version advertised to MCP clients. */\n version?: string;\n}\n\n/**\n * Build an MCP `McpServer` instance that exposes the `@rsconcept/rstool` contract.\n *\n * The returned server is not yet connected to a transport. Wrap it with a\n * `StdioServerTransport` (for local subprocess hosts like Cursor and Claude Desktop) or\n * an HTTP transport, then call `server.connect(transport)`.\n *\n * @example\n * ```ts\n * import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\n * import { buildRSToolMcpServer } from '@rsconcept/rstool-mcp';\n *\n * const server = buildRSToolMcpServer();\n * await server.connect(new StdioServerTransport());\n * ```\n */\nexport function buildRSToolMcpServer(\n options: BuildRSToolMcpServerOptions = {},\n): McpServer {\n const persistenceDir =\n options.persistenceDir ?? process.env.RSTOOL_PERSISTENCE_DIR;\n const tool =\n options.tool ??\n new RSToolAgent(persistenceDir ? { persistenceDir } : undefined);\n const server = new McpServer(\n {\n name: options.name ?? \"rstool-mcp\",\n version: options.version ?? \"0.1.0\",\n },\n {\n capabilities: {\n tools: {},\n },\n },\n );\n\n const definitionByName = new Map<string, ToolDefinition>(\n TOOL_DEFINITIONS.map((definition) => [definition.name, definition]),\n );\n\n server.server.setRequestHandler(ListToolsRequestSchema, () =>\n Promise.resolve({\n tools: TOOL_DEFINITIONS.map((definition) => ({\n name: definition.name,\n description: definition.description,\n inputSchema: definition.inputSchema,\n })),\n }),\n );\n\n server.server.setRequestHandler(CallToolRequestSchema, async (request) => {\n const definition = definitionByName.get(request.params.name);\n if (!definition) {\n return {\n isError: true,\n content: [\n {\n type: \"text\" as const,\n text: `Unknown rstool tool: ${request.params.name}`,\n },\n ],\n };\n }\n\n try {\n const result = await definition.invoke(\n tool,\n request.params.arguments ?? {},\n );\n const text =\n typeof result === \"string\" ? result : JSON.stringify(result, null, 2);\n return {\n content: [\n {\n type: \"text\" as const,\n text,\n },\n ],\n };\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n return {\n isError: true,\n content: [\n {\n type: \"text\" as const,\n text: `rstool error in ${definition.name}: ${message}`,\n },\n ],\n };\n }\n });\n\n return server;\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rsconcept/rstool-mcp",
3
- "version": "0.1.5",
3
+ "version": "0.3.0",
4
4
  "description": "Model Context Protocol (MCP) adapter that exposes the @rsconcept/rstool RSToolAgent contract over MCP stdio. Works with Cursor, Claude Desktop, and any MCP-capable host.",
5
5
  "license": "MIT",
6
6
  "author": "IRBorisov",
@@ -49,7 +49,7 @@
49
49
  },
50
50
  "dependencies": {
51
51
  "@modelcontextprotocol/sdk": "^1.29.0",
52
- "@rsconcept/rstool": "^0.8.0"
52
+ "@rsconcept/rstool": "^1.0.0"
53
53
  },
54
54
  "devDependencies": {
55
55
  "@types/node": "^25.9.4",
@@ -1,37 +1,37 @@
1
- import { describe, expect, it } from 'vitest';
1
+ import { describe, expect, it } from "vitest";
2
2
 
3
- import { buildRSToolMcpServer } from './server';
4
- import { TOOL_DEFINITIONS } from './tools';
3
+ import { buildRSToolMcpServer } from "./server";
4
+ import { TOOL_DEFINITIONS } from "./tools";
5
5
 
6
- describe('buildRSToolMcpServer', () => {
7
- it('exposes the full rstool contract as MCP tools', () => {
6
+ describe("buildRSToolMcpServer", () => {
7
+ it("exposes the rstool v2 contract as MCP tools", () => {
8
8
  const server = buildRSToolMcpServer();
9
9
  expect(server).toBeDefined();
10
- expect(TOOL_DEFINITIONS.map(t => t.name)).toEqual([
11
- 'ping',
12
- 'list_methods',
13
- 'create_session',
14
- 'add_or_update_constituenta',
15
- 'analyze_expression',
16
- 'get_form_state',
17
- 'list_diagnostics',
18
- 'commit_step',
19
- 'export_session',
20
- 'import_session',
21
- 'set_constituenta_value',
22
- 'set_constituenta_values',
23
- 'clear_constituenta_values',
24
- 'get_model_state',
25
- 'evaluate_expression',
26
- 'evaluate_constituenta',
27
- 'recalculate_model'
10
+ expect(TOOL_DEFINITIONS.map((t) => t.name)).toEqual([
11
+ "ping",
12
+ "list_methods",
13
+ "ensure_session",
14
+ "create_session",
15
+ "set_current_session",
16
+ "apply_schema_patch",
17
+ "get_session_state",
18
+ "analyze_expression",
19
+ "list_diagnostics",
20
+ "commit_step",
21
+ "export_session",
22
+ "export_portal",
23
+ "import_data",
24
+ "set_model_values",
25
+ "get_model_state",
26
+ "evaluate",
27
+ "recalculate_model",
28
28
  ]);
29
29
  });
30
30
 
31
- it('each tool has a permissive but well-formed input schema', () => {
31
+ it("each tool has a permissive but well-formed input schema", () => {
32
32
  for (const definition of TOOL_DEFINITIONS) {
33
- expect(definition.inputSchema.type).toBe('object');
34
- expect(typeof definition.description).toBe('string');
33
+ expect(definition.inputSchema.type).toBe("object");
34
+ expect(typeof definition.description).toBe("string");
35
35
  expect(definition.description.length).toBeGreaterThan(0);
36
36
  }
37
37
  });