scrybe-cli 0.2.0__tar.gz → 0.4.0__tar.gz

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 (60) hide show
  1. {scrybe_cli-0.2.0 → scrybe_cli-0.4.0}/Cargo.lock +24 -21
  2. {scrybe_cli-0.2.0 → scrybe_cli-0.4.0}/Cargo.toml +15 -9
  3. {scrybe_cli-0.2.0 → scrybe_cli-0.4.0}/PKG-INFO +1 -1
  4. {scrybe_cli-0.2.0 → scrybe_cli-0.4.0}/pyproject.toml +1 -1
  5. {scrybe_cli-0.2.0 → scrybe_cli-0.4.0}/scrybe-cli/Cargo.toml +1 -1
  6. scrybe_cli-0.4.0/scrybe-cli/src/rpc_client.rs +27 -0
  7. {scrybe_cli-0.2.0 → scrybe_cli-0.4.0}/scrybe-core/Cargo.toml +1 -1
  8. {scrybe_cli-0.2.0 → scrybe_cli-0.4.0}/scrybe-mcp-server/Cargo.toml +7 -1
  9. {scrybe_cli-0.2.0 → scrybe_cli-0.4.0}/scrybe-mcp-server/pyproject.toml +1 -1
  10. {scrybe_cli-0.2.0 → scrybe_cli-0.4.0}/scrybe-mcp-server/src/server.rs +49 -10
  11. scrybe_cli-0.4.0/scrybe-mcp-server/src/tools.rs +1234 -0
  12. scrybe_cli-0.4.0/scrybe-mcp-server/tests/docx_packaging.rs +76 -0
  13. scrybe_cli-0.4.0/scrybe-mcp-server/tests/live_app_open.rs +86 -0
  14. {scrybe_cli-0.2.0 → scrybe_cli-0.4.0}/scrybe-mermaid/Cargo.toml +2 -2
  15. {scrybe_cli-0.2.0 → scrybe_cli-0.4.0}/scrybe-mermaid/pyproject.toml +1 -1
  16. {scrybe_cli-0.2.0 → scrybe_cli-0.4.0}/scrybe-render/Cargo.toml +1 -1
  17. {scrybe_cli-0.2.0 → scrybe_cli-0.4.0}/scrybe-rpc/Cargo.toml +1 -1
  18. scrybe_cli-0.2.0/scrybe-cli/src/rpc_client.rs → scrybe_cli-0.4.0/scrybe-rpc/src/client.rs +38 -13
  19. {scrybe_cli-0.2.0 → scrybe_cli-0.4.0}/scrybe-rpc/src/lib.rs +4 -0
  20. scrybe_cli-0.2.0/scrybe-mcp-server/src/tools.rs +0 -703
  21. {scrybe_cli-0.2.0 → scrybe_cli-0.4.0}/README.md +0 -0
  22. {scrybe_cli-0.2.0 → scrybe_cli-0.4.0}/python/scrybe_cli/__init__.py +0 -0
  23. {scrybe_cli-0.2.0 → scrybe_cli-0.4.0}/python/scrybe_cli/py.typed +0 -0
  24. {scrybe_cli-0.2.0 → scrybe_cli-0.4.0}/scrybe-cli/README.md +0 -0
  25. {scrybe_cli-0.2.0 → scrybe_cli-0.4.0}/scrybe-cli/src/lib.rs +0 -0
  26. {scrybe_cli-0.2.0 → scrybe_cli-0.4.0}/scrybe-cli/src/lint.rs +0 -0
  27. {scrybe_cli-0.2.0 → scrybe_cli-0.4.0}/scrybe-cli/src/main.rs +0 -0
  28. {scrybe_cli-0.2.0 → scrybe_cli-0.4.0}/scrybe-cli/src/wrap.rs +0 -0
  29. {scrybe_cli-0.2.0 → scrybe_cli-0.4.0}/scrybe-cli/tests/rpc_socket.rs +0 -0
  30. {scrybe_cli-0.2.0 → scrybe_cli-0.4.0}/scrybe-core/README.md +0 -0
  31. {scrybe_cli-0.2.0 → scrybe_cli-0.4.0}/scrybe-core/src/ast.rs +0 -0
  32. {scrybe_cli-0.2.0 → scrybe_cli-0.4.0}/scrybe-core/src/change.rs +0 -0
  33. {scrybe_cli-0.2.0 → scrybe_cli-0.4.0}/scrybe-core/src/content.rs +0 -0
  34. {scrybe_cli-0.2.0 → scrybe_cli-0.4.0}/scrybe-core/src/document.rs +0 -0
  35. {scrybe_cli-0.2.0 → scrybe_cli-0.4.0}/scrybe-core/src/error.rs +0 -0
  36. {scrybe_cli-0.2.0 → scrybe_cli-0.4.0}/scrybe-core/src/lib.rs +0 -0
  37. {scrybe_cli-0.2.0 → scrybe_cli-0.4.0}/scrybe-core/src/plugin.rs +0 -0
  38. {scrybe_cli-0.2.0 → scrybe_cli-0.4.0}/scrybe-core/src/workspace.rs +0 -0
  39. {scrybe_cli-0.2.0 → scrybe_cli-0.4.0}/scrybe-mcp-server/README.md +0 -0
  40. {scrybe_cli-0.2.0 → scrybe_cli-0.4.0}/scrybe-mcp-server/python/scrybe_mcp_server/__init__.py +0 -0
  41. {scrybe_cli-0.2.0 → scrybe_cli-0.4.0}/scrybe-mcp-server/python/scrybe_mcp_server/py.typed +0 -0
  42. {scrybe_cli-0.2.0 → scrybe_cli-0.4.0}/scrybe-mcp-server/src/lib.rs +0 -0
  43. {scrybe_cli-0.2.0 → scrybe_cli-0.4.0}/scrybe-mcp-server/src/main.rs +0 -0
  44. {scrybe_cli-0.2.0 → scrybe_cli-0.4.0}/scrybe-mermaid/README.md +0 -0
  45. {scrybe_cli-0.2.0 → scrybe_cli-0.4.0}/scrybe-mermaid/python/scrybe_mermaid/__init__.py +0 -0
  46. {scrybe_cli-0.2.0 → scrybe_cli-0.4.0}/scrybe-mermaid/python/scrybe_mermaid/py.typed +0 -0
  47. {scrybe_cli-0.2.0 → scrybe_cli-0.4.0}/scrybe-mermaid/src/codec.rs +0 -0
  48. {scrybe_cli-0.2.0 → scrybe_cli-0.4.0}/scrybe-mermaid/src/error.rs +0 -0
  49. {scrybe_cli-0.2.0 → scrybe_cli-0.4.0}/scrybe-mermaid/src/lib.rs +0 -0
  50. {scrybe_cli-0.2.0 → scrybe_cli-0.4.0}/scrybe-mermaid/tests/test_python_quickstart.py +0 -0
  51. {scrybe_cli-0.2.0 → scrybe_cli-0.4.0}/scrybe-render/README.md +0 -0
  52. {scrybe_cli-0.2.0 → scrybe_cli-0.4.0}/scrybe-render/src/html.rs +0 -0
  53. {scrybe_cli-0.2.0 → scrybe_cli-0.4.0}/scrybe-render/src/lib.rs +0 -0
  54. {scrybe_cli-0.2.0 → scrybe_cli-0.4.0}/scrybe-render/src/math.rs +0 -0
  55. {scrybe_cli-0.2.0 → scrybe_cli-0.4.0}/scrybe-render/src/mermaid.rs +0 -0
  56. {scrybe_cli-0.2.0 → scrybe_cli-0.4.0}/scrybe-render/src/theme.rs +0 -0
  57. {scrybe_cli-0.2.0 → scrybe_cli-0.4.0}/scrybe-render/src/themes/dark.css +0 -0
  58. {scrybe_cli-0.2.0 → scrybe_cli-0.4.0}/scrybe-render/src/themes/default.css +0 -0
  59. {scrybe_cli-0.2.0 → scrybe_cli-0.4.0}/scrybe-render/src/themes/solarized.css +0 -0
  60. {scrybe_cli-0.2.0 → scrybe_cli-0.4.0}/scrybe-rpc/README.md +0 -0
@@ -3169,9 +3169,9 @@ checksum = "007d8adb5ddab6f8e3f491ac63566a7d5002cc7ed73901f72057943fa71ae1ae"
3169
3169
 
3170
3170
  [[package]]
3171
3171
  name = "pyo3"
3172
- version = "0.28.3"
3172
+ version = "0.29.0"
3173
3173
  source = "registry+https://github.com/rust-lang/crates.io-index"
3174
- checksum = "91fd8e38a3b50ed1167fb981cd6fd60147e091784c427b8f7183a7ee32c31c12"
3174
+ checksum = "cd274650b21d4bfc26a0a47587962c1edb425f69287324355cd040c3ea66071c"
3175
3175
  dependencies = [
3176
3176
  "libc",
3177
3177
  "once_cell",
@@ -3183,18 +3183,18 @@ dependencies = [
3183
3183
 
3184
3184
  [[package]]
3185
3185
  name = "pyo3-build-config"
3186
- version = "0.28.3"
3186
+ version = "0.29.0"
3187
3187
  source = "registry+https://github.com/rust-lang/crates.io-index"
3188
- checksum = "e368e7ddfdeb98c9bca7f8383be1648fd84ab466bf2bc015e94008db6d35611e"
3188
+ checksum = "c5e2a7d2f0d013342f295c048ad19237add5154a55b1c5a254c0ec93d4109078"
3189
3189
  dependencies = [
3190
3190
  "target-lexicon 0.13.5",
3191
3191
  ]
3192
3192
 
3193
3193
  [[package]]
3194
3194
  name = "pyo3-ffi"
3195
- version = "0.28.3"
3195
+ version = "0.29.0"
3196
3196
  source = "registry+https://github.com/rust-lang/crates.io-index"
3197
- checksum = "7f29e10af80b1f7ccaf7f69eace800a03ecd13e883acfacc1e5d0988605f651e"
3197
+ checksum = "ca85c467da1bbc8d866eea5deff9cf29ea5f7785054a17da36e65bda9c05845b"
3198
3198
  dependencies = [
3199
3199
  "libc",
3200
3200
  "pyo3-build-config",
@@ -3202,9 +3202,9 @@ dependencies = [
3202
3202
 
3203
3203
  [[package]]
3204
3204
  name = "pyo3-macros"
3205
- version = "0.28.3"
3205
+ version = "0.29.0"
3206
3206
  source = "registry+https://github.com/rust-lang/crates.io-index"
3207
- checksum = "df6e520eff47c45997d2fc7dd8214b25dd1310918bbb2642156ef66a67f29813"
3207
+ checksum = "9ac53762fd065daa3194dd09337a38bd793a188100fd1a9304c4ab312d901771"
3208
3208
  dependencies = [
3209
3209
  "proc-macro2",
3210
3210
  "pyo3-macros-backend",
@@ -3214,13 +3214,12 @@ dependencies = [
3214
3214
 
3215
3215
  [[package]]
3216
3216
  name = "pyo3-macros-backend"
3217
- version = "0.28.3"
3217
+ version = "0.29.0"
3218
3218
  source = "registry+https://github.com/rust-lang/crates.io-index"
3219
- checksum = "c4cdc218d835738f81c2338f822078af45b4afdf8b2e33cbb5916f108b813acb"
3219
+ checksum = "4ca3a1557399783172dc5bf39cfca835157732532cba56b71d2292161e53b362"
3220
3220
  dependencies = [
3221
3221
  "heck 0.5.0",
3222
3222
  "proc-macro2",
3223
- "pyo3-build-config",
3224
3223
  "quote",
3225
3224
  "syn 2.0.117",
3226
3225
  ]
@@ -3525,7 +3524,7 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
3525
3524
 
3526
3525
  [[package]]
3527
3526
  name = "scrybe-app"
3528
- version = "0.2.0"
3527
+ version = "0.4.0"
3529
3528
  dependencies = [
3530
3529
  "anyhow",
3531
3530
  "dirs 5.0.1",
@@ -3543,6 +3542,7 @@ dependencies = [
3543
3542
  "tauri-plugin-dialog",
3544
3543
  "tauri-plugin-shell",
3545
3544
  "tauri-plugin-single-instance",
3545
+ "tempfile",
3546
3546
  "thiserror 2.0.18",
3547
3547
  "tokio",
3548
3548
  "tracing",
@@ -3551,7 +3551,7 @@ dependencies = [
3551
3551
 
3552
3552
  [[package]]
3553
3553
  name = "scrybe-cli"
3554
- version = "0.2.0"
3554
+ version = "0.4.0"
3555
3555
  dependencies = [
3556
3556
  "anyhow",
3557
3557
  "clap",
@@ -3570,7 +3570,7 @@ dependencies = [
3570
3570
 
3571
3571
  [[package]]
3572
3572
  name = "scrybe-core"
3573
- version = "0.2.0"
3573
+ version = "0.4.0"
3574
3574
  dependencies = [
3575
3575
  "anyhow",
3576
3576
  "blake3",
@@ -3587,7 +3587,7 @@ dependencies = [
3587
3587
 
3588
3588
  [[package]]
3589
3589
  name = "scrybe-mcp-client"
3590
- version = "0.2.0"
3590
+ version = "0.4.0"
3591
3591
  dependencies = [
3592
3592
  "anyhow",
3593
3593
  "dirs 5.0.1",
@@ -3602,24 +3602,27 @@ dependencies = [
3602
3602
 
3603
3603
  [[package]]
3604
3604
  name = "scrybe-mcp-server"
3605
- version = "0.2.0"
3605
+ version = "0.4.0"
3606
3606
  dependencies = [
3607
3607
  "anyhow",
3608
3608
  "clap",
3609
3609
  "scrybe-core",
3610
3610
  "scrybe-mermaid",
3611
3611
  "scrybe-render",
3612
+ "scrybe-rpc",
3612
3613
  "serde",
3613
3614
  "serde_json",
3615
+ "tempfile",
3614
3616
  "thiserror 2.0.18",
3615
3617
  "tokio",
3616
3618
  "tracing",
3617
3619
  "tracing-subscriber",
3620
+ "which",
3618
3621
  ]
3619
3622
 
3620
3623
  [[package]]
3621
3624
  name = "scrybe-mermaid"
3622
- version = "0.2.0"
3625
+ version = "0.4.0"
3623
3626
  dependencies = [
3624
3627
  "anyhow",
3625
3628
  "base64 0.22.1",
@@ -3635,7 +3638,7 @@ dependencies = [
3635
3638
 
3636
3639
  [[package]]
3637
3640
  name = "scrybe-py"
3638
- version = "0.2.0"
3641
+ version = "0.4.0"
3639
3642
  dependencies = [
3640
3643
  "anyhow",
3641
3644
  "pyo3",
@@ -3649,7 +3652,7 @@ dependencies = [
3649
3652
 
3650
3653
  [[package]]
3651
3654
  name = "scrybe-render"
3652
- version = "0.2.0"
3655
+ version = "0.4.0"
3653
3656
  dependencies = [
3654
3657
  "anyhow",
3655
3658
  "pulldown-cmark",
@@ -3662,7 +3665,7 @@ dependencies = [
3662
3665
 
3663
3666
  [[package]]
3664
3667
  name = "scrybe-rpc"
3665
- version = "0.2.0"
3668
+ version = "0.4.0"
3666
3669
  dependencies = [
3667
3670
  "serde",
3668
3671
  "serde_json",
@@ -3671,7 +3674,7 @@ dependencies = [
3671
3674
 
3672
3675
  [[package]]
3673
3676
  name = "scrybe-vcs"
3674
- version = "0.2.0"
3677
+ version = "0.4.0"
3675
3678
  dependencies = [
3676
3679
  "anyhow",
3677
3680
  "chrono",
@@ -2,6 +2,12 @@
2
2
  resolver = "2"
3
3
  members = ["scrybe-core", "scrybe-render", "scrybe-mcp-server", "scrybe-mermaid", "scrybe-cli", "scrybe-rpc"]
4
4
 
5
+ # Shared package metadata. Every member inherits the version via
6
+ # `version.workspace = true`, so a release bumps all crates in lock-step
7
+ # from one place. (See RELEASE.md; issue #128.)
8
+ [workspace.package]
9
+ version = "0.4.0"
10
+
5
11
  # Workspace-level dependency pins keep all crates in lock-step.
6
12
  [workspace.dependencies]
7
13
  # Core utilities
@@ -43,7 +49,7 @@ png = "0.17"
43
49
  base64 = "0.22"
44
50
 
45
51
  # PyO3
46
- pyo3 = { version = "0.28", features = ["auto-initialize"] }
52
+ pyo3 = { version = "0.29", features = ["auto-initialize"] }
47
53
 
48
54
  # Tauri 2
49
55
  tauri = { version = "2", features = ["protocol-asset"] }
@@ -52,14 +58,14 @@ tauri-build = { version = "2", features = [] }
52
58
  # Internal crates — version pinned (=) so cargo publish accepts the
53
59
  # tarballs (path-deps without versions are rejected at publish time).
54
60
  # Workspace lock-step: every release bumps all members together.
55
- scrybe-core = { path = "scrybe-core", version = "=0.2.0" }
56
- scrybe-render = { path = "scrybe-render", version = "=0.2.0" }
57
- scrybe-mcp-client = { path = "scrybe-mcp-client", version = "=0.2.0" }
58
- scrybe-mcp-server = { path = "scrybe-mcp-server", version = "=0.2.0" }
59
- scrybe-mermaid = { path = "scrybe-mermaid", version = "=0.2.0" }
60
- scrybe-vcs = { path = "scrybe-vcs", version = "=0.2.0" }
61
- scrybe-py = { path = "scrybe-py", version = "=0.2.0" }
62
- scrybe-rpc = { path = "scrybe-rpc", version = "=0.2.0" }
61
+ scrybe-core = { path = "scrybe-core", version = "=0.4.0" }
62
+ scrybe-render = { path = "scrybe-render", version = "=0.4.0" }
63
+ scrybe-mcp-client = { path = "scrybe-mcp-client", version = "=0.4.0" }
64
+ scrybe-mcp-server = { path = "scrybe-mcp-server", version = "=0.4.0" }
65
+ scrybe-mermaid = { path = "scrybe-mermaid", version = "=0.4.0" }
66
+ scrybe-vcs = { path = "scrybe-vcs", version = "=0.4.0" }
67
+ scrybe-py = { path = "scrybe-py", version = "=0.4.0" }
68
+ scrybe-rpc = { path = "scrybe-rpc", version = "=0.4.0" }
63
69
 
64
70
  [workspace.lints.clippy]
65
71
  explicit_iter_loop = "warn"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scrybe-cli
3
- Version: 0.2.0
3
+ Version: 0.4.0
4
4
  Summary: Scrybe CLI — headless Markdown render/lint/mermaid tool (binary wheel)
5
5
  Home-Page: https://github.com/hartsock/scrybe
6
6
  License: Apache-2.0
@@ -4,7 +4,7 @@ build-backend = "maturin"
4
4
 
5
5
  [project]
6
6
  name = "scrybe-cli"
7
- version = "0.2.0"
7
+ version = "0.4.0"
8
8
  description = "Scrybe CLI — headless Markdown render/lint/mermaid tool (binary wheel)"
9
9
  license = {text = "Apache-2.0"}
10
10
  readme = "README.md"
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "scrybe-cli"
3
- version = "0.2.0"
3
+ version.workspace = true
4
4
  edition = "2021"
5
5
  description = "Scrybe CLI — headless render/lint/mermaid/panel command-line tool"
6
6
  license = "Apache-2.0"
@@ -0,0 +1,27 @@
1
+ //! Thin JSON-RPC client to talk to the running Scrybe GUI.
2
+ //!
3
+ //! The implementation now lives in [`scrybe_rpc::client`] so the CLI and the
4
+ //! MCP server dial the live app through **one** shared dialer (two divergent
5
+ //! clients is exactly the split `scrybe-rpc` exists to prevent). This module
6
+ //! re-exports it to keep the existing `rpc_client::*` call sites unchanged.
7
+
8
+ pub use scrybe_rpc::client::{is_live, send, send_to, socket_path, try_connect, try_connect_at};
9
+
10
+ #[cfg(test)]
11
+ mod tests {
12
+ use super::*;
13
+
14
+ #[test]
15
+ fn socket_path_reexported() {
16
+ // The re-export links and resolves to the shared implementation.
17
+ assert!(!socket_path().as_os_str().is_empty());
18
+ }
19
+
20
+ #[cfg(unix)]
21
+ #[test]
22
+ fn send_to_errors_when_no_server() {
23
+ let path = std::path::PathBuf::from("/tmp/scrybe-nonexistent-sock-cli-reexport-test");
24
+ let err = send_to(&path, "open", serde_json::json!({"path": "/tmp/foo.md"})).unwrap_err();
25
+ assert!(err.contains("no Scrybe running"));
26
+ }
27
+ }
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "scrybe-core"
3
- version = "0.2.0"
3
+ version.workspace = true
4
4
  edition = "2021"
5
5
  description = "Scrybe core — AST, Document, ContentAddressable, Plugin trait, Workspace"
6
6
  license = "Apache-2.0"
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "scrybe-mcp-server"
3
- version = "0.2.0"
3
+ version.workspace = true
4
4
  edition = "2021"
5
5
  description = "Scrybe MCP server — inbound MCP tools: open/read/section/edit/diff/find/embed/lint"
6
6
  license = "Apache-2.0"
@@ -18,6 +18,7 @@ path = "src/main.rs"
18
18
  scrybe-core.workspace = true
19
19
  scrybe-render.workspace = true
20
20
  scrybe-mermaid.workspace = true
21
+ scrybe-rpc.workspace = true
21
22
  anyhow.workspace = true
22
23
  thiserror.workspace = true
23
24
  serde.workspace = true
@@ -26,6 +27,11 @@ tracing.workspace = true
26
27
  tracing-subscriber.workspace = true
27
28
  tokio.workspace = true
28
29
  clap.workspace = true
30
+ which.workspace = true
31
+
32
+ [dev-dependencies]
33
+ tempfile = "3"
34
+ serde_json.workspace = true
29
35
 
30
36
  [lints]
31
37
  workspace = true
@@ -4,7 +4,7 @@ build-backend = "maturin"
4
4
 
5
5
  [project]
6
6
  name = "scrybe-mcp-server"
7
- version = "0.2.0"
7
+ version = "0.4.0"
8
8
  description = "Scrybe MCP server — standalone inbound MCP server (binary wheel)"
9
9
  license = {text = "Apache-2.0"}
10
10
  readme = "README.md"
@@ -89,16 +89,29 @@ impl McpServer {
89
89
  let name = params["name"].as_str()?;
90
90
  let args = params.get("arguments").unwrap_or(&Value::Null);
91
91
  let content = self.registry.call_tool(name, args);
92
+ // A tool result carrying an `error` field is a failed call; set
93
+ // `isError` so agents can tell success from failure structurally
94
+ // instead of parsing the text payload.
95
+ let is_error = content.get("error").is_some();
92
96
  serde_json::json!({
93
- "content": [{"type": "text", "text": content.to_string()}]
97
+ "content": [{"type": "text", "text": content.to_string()}],
98
+ "isError": is_error,
94
99
  })
95
100
  }
96
- other => serde_json::json!({
97
- "error": {
98
- "code": -32601,
99
- "message": format!("method not found: {other}")
100
- }
101
- }),
101
+ other => {
102
+ // Unknown method → a real top-level JSON-RPC error object,
103
+ // sibling to `id` (never nested under `result`).
104
+ return id.map(|i| {
105
+ serde_json::json!({
106
+ "jsonrpc": "2.0",
107
+ "id": i,
108
+ "error": {
109
+ "code": -32601,
110
+ "message": format!("method not found: {other}")
111
+ }
112
+ })
113
+ });
114
+ }
102
115
  };
103
116
 
104
117
  id.map(|i| {
@@ -207,16 +220,42 @@ mod tests {
207
220
  }
208
221
 
209
222
  #[test]
210
- fn test_unknown_method_with_id_returns_error_result() {
223
+ fn test_unknown_method_returns_top_level_error() {
211
224
  let mut s = server();
212
225
  let req = json!({"jsonrpc": "2.0", "id": 7, "method": "nonexistent"});
213
226
  let resp = s.handle(&req).unwrap();
227
+ // JSON-RPC error MUST be top-level, not nested under `result`.
228
+ assert_eq!(resp["error"]["code"], -32601);
214
229
  assert!(
215
- resp["result"]["error"].is_object() || resp.get("error").is_some(),
216
- "expected error in response: {resp}"
230
+ resp.get("result").is_none(),
231
+ "error response must not carry a result: {resp}"
217
232
  );
218
233
  }
219
234
 
235
+ #[test]
236
+ fn test_tools_call_failure_sets_is_error() {
237
+ // `read` with an unknown id returns an error payload → isError: true.
238
+ let mut s = server();
239
+ let req = json!({
240
+ "jsonrpc": "2.0", "id": 9, "method": "tools/call",
241
+ "params": {"name": "read", "arguments": {"id": "bogus-id"}}
242
+ });
243
+ let resp = s.handle(&req).unwrap();
244
+ assert_eq!(resp["result"]["isError"], true);
245
+ }
246
+
247
+ #[test]
248
+ fn test_tools_call_success_is_not_error() {
249
+ // A successful render carries no error field → isError: false.
250
+ let mut s = server();
251
+ let req = json!({
252
+ "jsonrpc": "2.0", "id": 10, "method": "tools/call",
253
+ "params": {"name": "render", "arguments": {"source": "# Hi"}}
254
+ });
255
+ let resp = s.handle(&req).unwrap();
256
+ assert_eq!(resp["result"]["isError"], false);
257
+ }
258
+
220
259
  #[test]
221
260
  fn test_jsonrpc_version_in_response() {
222
261
  let mut s = server();