@needmoretruth/nmts-cli 0.17.2 → 0.34.2
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/AGENTS.md +125 -517
- package/CHANGELOG.md +104 -0
- package/README.ko.md +377 -469
- package/README.md +394 -474
- package/dist/account-proof.d.ts +29 -0
- package/dist/account-proof.js +6 -6
- package/dist/account.d.ts +18 -0
- package/dist/account.js +5 -5
- package/dist/agent-host.d.ts +71 -0
- package/dist/agent-host.js +169 -0
- package/dist/api-advice.d.ts +2 -0
- package/dist/api-advice.js +210 -0
- package/dist/api-key.d.ts +79 -0
- package/dist/api-key.js +7 -7
- package/dist/api-refusal.d.ts +14 -0
- package/dist/api-refusal.js +8 -0
- package/dist/api.d.ts +114 -0
- package/dist/api.js +59 -162
- package/dist/args.d.ts +160 -0
- package/dist/args.js +118 -30
- package/dist/artifact-about.d.ts +49 -0
- package/dist/autonomy.d.ts +39 -0
- package/dist/autonomy.js +110 -32
- package/dist/code-access.d.ts +29 -0
- package/dist/code-access.js +3 -3
- package/dist/code-vault.d.ts +38 -0
- package/dist/code-vault.js +8 -8
- package/dist/collision.d.ts +67 -0
- package/dist/collision.js +1 -1
- package/dist/commands/accept-terms.d.ts +11 -0
- package/dist/commands/accept-terms.js +106 -0
- package/dist/commands/balance.d.ts +7 -0
- package/dist/commands/balance.js +54 -1
- package/dist/commands/create-code-file.d.ts +24 -0
- package/dist/commands/create-code-file.js +93 -0
- package/dist/commands/create-link.d.ts +10 -0
- package/dist/commands/create-link.js +232 -0
- package/dist/commands/create.d.ts +20 -0
- package/dist/commands/create.js +43 -102
- package/dist/commands/credits.d.ts +9 -0
- package/dist/commands/credits.js +110 -0
- package/dist/commands/delete-account.d.ts +11 -0
- package/dist/commands/delete-account.js +63 -0
- package/dist/commands/deposit.d.ts +7 -0
- package/dist/commands/deposit.js +85 -0
- package/dist/commands/devices.d.ts +12 -0
- package/dist/commands/devices.js +144 -0
- package/dist/commands/documents.d.ts +35 -0
- package/dist/commands/documents.js +212 -0
- package/dist/commands/env.d.ts +5 -0
- package/dist/commands/env.js +10 -2
- package/dist/commands/erase.d.ts +13 -0
- package/dist/commands/erase.js +193 -0
- package/dist/commands/expiring.d.ts +19 -0
- package/dist/commands/extend.d.ts +33 -0
- package/dist/commands/extend.js +54 -15
- package/dist/commands/get.d.ts +18 -0
- package/dist/commands/key-manage.d.ts +11 -0
- package/dist/commands/key-manage.js +162 -0
- package/dist/commands/key.d.ts +28 -0
- package/dist/commands/key.js +249 -0
- package/dist/commands/kit.d.ts +11 -0
- package/dist/commands/kit.js +7 -7
- package/dist/commands/listfile.d.ts +13 -0
- package/dist/commands/listfile.js +12 -5
- package/dist/commands/login.d.ts +18 -0
- package/dist/commands/login.js +19 -19
- package/dist/commands/logout.d.ts +1 -0
- package/dist/commands/logout.js +1 -1
- package/dist/commands/losses.d.ts +12 -0
- package/dist/commands/losses.js +180 -0
- package/dist/commands/ls.d.ts +23 -0
- package/dist/commands/marks-dispatch.d.ts +2 -0
- package/dist/commands/marks-dispatch.js +51 -0
- package/dist/commands/marks.d.ts +46 -0
- package/dist/commands/marks.js +80 -0
- package/dist/commands/mcp.d.ts +24 -0
- package/dist/commands/mcp.js +70 -5
- package/dist/commands/mode.d.ts +8 -0
- package/dist/commands/mode.js +60 -24
- package/dist/commands/on-collision.d.ts +6 -0
- package/dist/commands/organise.d.ts +46 -0
- package/dist/commands/padding.d.ts +7 -0
- package/dist/commands/padding.js +114 -0
- package/dist/commands/public-code.d.ts +9 -0
- package/dist/commands/public-code.js +8 -8
- package/dist/commands/pull.d.ts +27 -0
- package/dist/commands/push-wallet.d.ts +19 -0
- package/dist/commands/push-wallet.js +69 -0
- package/dist/commands/push.d.ts +47 -0
- package/dist/commands/push.js +37 -7
- package/dist/commands/put-wallet.d.ts +77 -0
- package/dist/commands/put-wallet.js +316 -0
- package/dist/commands/put.d.ts +56 -0
- package/dist/commands/put.js +53 -6
- package/dist/commands/rebuild.d.ts +18 -0
- package/dist/commands/rebuild.js +108 -10
- package/dist/commands/receive.d.ts +24 -0
- package/dist/commands/recovery-dispatch.d.ts +2 -0
- package/dist/commands/recovery-dispatch.js +34 -0
- package/dist/commands/recovery-list.d.ts +11 -0
- package/dist/commands/recovery-list.js +6 -6
- package/dist/commands/recovery.d.ts +25 -0
- package/dist/commands/recovery.js +2 -2
- package/dist/commands/rollback.d.ts +9 -0
- package/dist/commands/rollback.js +118 -0
- package/dist/commands/s3.d.ts +23 -0
- package/dist/commands/s3.js +6 -6
- package/dist/commands/settings.d.ts +5 -0
- package/dist/commands/settings.js +21 -6
- package/dist/commands/share-dispatch.d.ts +2 -0
- package/dist/commands/share-dispatch.js +46 -0
- package/dist/commands/share.d.ts +14 -0
- package/dist/commands/share.js +13 -3
- package/dist/commands/shares-sent.d.ts +18 -0
- package/dist/commands/shares-sent.js +95 -0
- package/dist/commands/support.d.ts +24 -0
- package/dist/commands/support.js +321 -0
- package/dist/commands/sweep.d.ts +11 -0
- package/dist/commands/tip.d.ts +11 -0
- package/dist/commands/tip.js +86 -0
- package/dist/commands/trash.d.ts +17 -0
- package/dist/commands/trash.js +1 -1
- package/dist/commands/trial.d.ts +7 -0
- package/dist/commands/trial.js +16 -18
- package/dist/commands/unlock.d.ts +16 -0
- package/dist/commands/unlock.js +159 -0
- package/dist/commands/update.d.ts +42 -0
- package/dist/commands/update.js +1 -1
- package/dist/commands/usage.d.ts +8 -0
- package/dist/commands/verify.d.ts +18 -0
- package/dist/commands/verify.js +10 -6
- package/dist/commands/wallet-activity.d.ts +26 -0
- package/dist/commands/wallet-activity.js +117 -0
- package/dist/commands/wallet-donate.d.ts +26 -0
- package/dist/commands/wallet-donate.js +163 -0
- package/dist/commands/wallet-hall.d.ts +35 -0
- package/dist/commands/wallet-hall.js +214 -0
- package/dist/commands/wallet-send.d.ts +22 -0
- package/dist/commands/wallet-send.js +153 -0
- package/dist/commands/wallet-storage-ops.d.ts +24 -0
- package/dist/commands/wallet-storage-ops.js +215 -0
- package/dist/commands/wallet-storage.d.ts +18 -0
- package/dist/commands/wallet-storage.js +95 -0
- package/dist/commands/wallet-swap.d.ts +32 -0
- package/dist/commands/wallet-swap.js +287 -0
- package/dist/commands/wallet.d.ts +36 -0
- package/dist/commands/wallet.js +57 -15
- package/dist/commands/whoami.d.ts +10 -0
- package/dist/commands/whoami.js +24 -6
- package/dist/consent.d.ts +140 -0
- package/dist/consent.js +89 -30
- package/dist/credentials.d.ts +146 -0
- package/dist/credentials.js +10 -10
- package/dist/crypto-surface.d.ts +197 -0
- package/dist/crypto.d.ts +81 -0
- package/dist/crypto.js +13 -0
- package/dist/deposit.d.ts +25 -0
- package/dist/deposit.js +78 -0
- package/dist/download-part.d.ts +40 -0
- package/dist/download-sink.d.ts +76 -0
- package/dist/download.d.ts +79 -0
- package/dist/download.js +3 -3
- package/dist/drive-paths.d.ts +73 -0
- package/dist/environment.d.ts +62 -0
- package/dist/environment.js +29 -9
- package/dist/errors.d.ts +22 -0
- package/dist/errors.js +3 -3
- package/dist/exit.d.ts +44 -0
- package/dist/expiry.d.ts +58 -0
- package/dist/extend-budget.d.ts +35 -0
- package/dist/extend-budget.js +81 -0
- package/dist/extend-chain.d.ts +43 -0
- package/dist/extend-chain.js +40 -3
- package/dist/extend-plan.d.ts +148 -0
- package/dist/extend-plan.js +1 -1
- package/dist/gate.d.ts +18 -0
- package/dist/gate.js +100 -0
- package/dist/guards.d.ts +2 -0
- package/dist/help.d.ts +20 -0
- package/dist/help.js +199 -33
- package/dist/human-check.d.ts +23 -0
- package/dist/index.d.ts +42 -0
- package/dist/index.js +49 -0
- package/dist/item-trash.d.ts +1 -0
- package/dist/kit-file.d.ts +56 -0
- package/dist/kit-file.js +10 -10
- package/dist/list-file.d.ts +88 -0
- package/dist/list-file.js +90 -7
- package/dist/list-view-find.d.ts +15 -0
- package/dist/list-view-order.d.ts +32 -0
- package/dist/main.d.ts +12 -0
- package/dist/main.js +112 -113
- package/dist/manifest-chunk-cache.d.ts +14 -0
- package/dist/manifest-chunk-cache.js +103 -0
- package/dist/manifest-chunk-flow.d.ts +81 -0
- package/dist/manifest-chunk-flow.js +307 -0
- package/dist/manifest-create.d.ts +23 -0
- package/dist/manifest-create.js +26 -30
- package/dist/manifest-write.d.ts +161 -0
- package/dist/manifest-write.js +42 -33
- package/dist/manifest.d.ts +82 -0
- package/dist/manifest.js +69 -21
- package/dist/mark-render.d.ts +26 -0
- package/dist/mcp-args.d.ts +17 -0
- package/dist/mcp-ask.d.ts +43 -0
- package/dist/mcp-ask.js +100 -0
- package/dist/mcp-tools/context.d.ts +46 -0
- package/dist/mcp-tools/credits.d.ts +3 -0
- package/dist/mcp-tools/credits.js +61 -0
- package/dist/mcp-tools/files.d.ts +3 -0
- package/dist/mcp-tools/files.js +34 -0
- package/dist/mcp-tools/organise.d.ts +3 -0
- package/dist/mcp-tools/organise.js +82 -2
- package/dist/mcp-tools/reads.d.ts +3 -0
- package/dist/mcp-tools/reads.js +132 -4
- package/dist/mcp-tools/share.d.ts +3 -0
- package/dist/mcp-tools/share.js +26 -7
- package/dist/mcp-tools/support.d.ts +3 -0
- package/dist/mcp-tools/support.js +140 -0
- package/dist/mcp-tools/tiers.d.ts +31 -0
- package/dist/mcp-tools/tiers.js +147 -0
- package/dist/mcp.d.ts +80 -0
- package/dist/mcp.js +110 -9
- package/dist/net-retry.d.ts +38 -0
- package/dist/network.d.ts +10 -0
- package/dist/notice.d.ts +7 -0
- package/dist/notice.js +7 -7
- package/dist/product.d.ts +29 -0
- package/dist/product.js +1 -1
- package/dist/progress.d.ts +37 -0
- package/dist/prompt.d.ts +31 -0
- package/dist/prompt.js +1 -1
- package/dist/rebuild-key-check.d.ts +26 -0
- package/dist/rebuild-key-check.js +90 -0
- package/dist/rebuild.d.ts +87 -0
- package/dist/rebuild.js +26 -4
- package/dist/recovery-assemble.d.ts +40 -0
- package/dist/recovery-assemble.js +1 -1
- package/dist/recovery-build.d.ts +40 -0
- package/dist/recovery-map-file.d.ts +53 -0
- package/dist/recovery-map-file.js +4 -4
- package/dist/recovery-map.d.ts +197 -0
- package/dist/recovery-release.d.ts +104 -0
- package/dist/recovery-seq.d.ts +10 -0
- package/dist/recovery-source.d.ts +50 -0
- package/dist/redact.d.ts +70 -0
- package/dist/redact.js +212 -0
- package/dist/registration.d.ts +30 -0
- package/dist/registration.js +5 -5
- package/dist/risk.d.ts +380 -0
- package/dist/risk.js +206 -0
- package/dist/run-log.d.ts +78 -0
- package/dist/run-log.js +301 -0
- package/dist/s3/listing.d.ts +47 -0
- package/dist/s3/multipart.d.ts +21 -0
- package/dist/s3/response-sink.d.ts +8 -0
- package/dist/s3/same-file.d.ts +54 -0
- package/dist/s3/server.d.ts +58 -0
- package/dist/s3/server.js +2 -2
- package/dist/s3/sigv4.d.ts +46 -0
- package/dist/s3/sigv4.js +1 -1
- package/dist/s3/staging.d.ts +10 -0
- package/dist/s3/xml.d.ts +28 -0
- package/dist/safe-path.d.ts +41 -0
- package/dist/seal.d.ts +120 -0
- package/dist/secret-reader.d.ts +28 -0
- package/dist/server.d.ts +9 -0
- package/dist/session.d.ts +32 -0
- package/dist/session.js +1 -1
- package/dist/setup-questions.d.ts +7 -0
- package/dist/share.d.ts +108 -0
- package/dist/share.js +1 -1
- package/dist/shared/lib/crypto/size-padding.d.ts +55 -0
- package/dist/shared/lib/crypto/size-padding.js +5 -0
- package/dist/shared/lib/drive/manifest-chunks.d.ts +95 -0
- package/dist/shared/lib/drive/manifest-chunks.js +252 -0
- package/dist/shared/lib/drive/manifest-codec.d.ts +214 -0
- package/dist/shared/lib/drive/manifest-codec.js +7 -7
- package/dist/shared/lib/drive/manifest-index.d.ts +98 -0
- package/dist/shared/lib/drive/manifest-ops.d.ts +167 -0
- package/dist/shared/lib/drive/manifest-ops.js +9 -4
- package/dist/shared/lib/drive/manifest-pack.d.ts +65 -0
- package/dist/shared/lib/drive/manifest-pack.js +287 -0
- package/dist/shared/lib/drive/manifest-settings.d.ts +113 -0
- package/dist/shared/lib/drive/manifest-settings.js +87 -6
- package/dist/shared/lib/drive/name-conflict.d.ts +76 -0
- package/dist/shared/lib/drive/rebuild-verify.d.ts +73 -0
- package/dist/shared/lib/drive/rebuild-verify.js +112 -0
- package/dist/shared/lib/drive/unique-name.d.ts +9 -0
- package/dist/shared/lib/drive/zstd.d.ts +27 -0
- package/dist/shared/lib/drive/zstd.js +74 -0
- package/dist/shared/lib/extend/epochs.d.ts +205 -0
- package/dist/shared/lib/net/retry-budget.d.ts +79 -0
- package/dist/shared/lib/share/shared-file-info.d.ts +21 -0
- package/dist/shared/lib/storage-control/chain.d.ts +57 -0
- package/dist/shared/lib/storage-control/chain.js +82 -0
- package/dist/shared/lib/storage-control/plan.d.ts +40 -0
- package/dist/shared/lib/storage-control/plan.js +72 -0
- package/dist/shared/lib/storage-control/reuse.d.ts +72 -0
- package/dist/shared/lib/storage-control/reuse.js +95 -0
- package/dist/shared/lib/storage-network.d.ts +25 -0
- package/dist/shared/lib/upload/part-plan.d.ts +24 -0
- package/dist/shared/lib/wallet/activity.d.ts +138 -0
- package/dist/shared/lib/wallet/activity.js +208 -0
- package/dist/shared/lib/wallet/send-rules.d.ts +68 -0
- package/dist/shared/lib/wallet/send-rules.js +116 -0
- package/dist/shared/lib/wallet/swap-rules.d.ts +115 -0
- package/dist/shared/lib/wallet/swap-rules.js +199 -0
- package/dist/shared/lib/wallet/tip.d.ts +14 -0
- package/dist/shared/lib/wallet/tip.js +34 -0
- package/dist/shared/lib/wallet/venue-ids.d.ts +23 -0
- package/dist/shared/lib/wallet/venue-ids.js +53 -0
- package/dist/standing-tip.d.ts +24 -0
- package/dist/standing-tip.js +52 -0
- package/dist/stdout.d.ts +38 -0
- package/dist/storage-control-chain.d.ts +38 -0
- package/dist/storage-control-chain.js +78 -0
- package/dist/sui-rpc.d.ts +15 -0
- package/dist/sui-rpc.js +59 -0
- package/dist/support-attach.d.ts +24 -0
- package/dist/support-attach.js +72 -0
- package/dist/support-copy.d.ts +42 -0
- package/dist/support-copy.js +109 -0
- package/dist/support-wire.d.ts +49 -0
- package/dist/support-wire.js +194 -0
- package/dist/trash-sweep.d.ts +55 -0
- package/dist/units.d.ts +2 -0
- package/dist/update-check.d.ts +50 -0
- package/dist/update-source.d.ts +63 -0
- package/dist/upload-api.d.ts +3 -0
- package/dist/upload-file.d.ts +97 -0
- package/dist/upload-file.js +3 -2
- package/dist/upload-price.d.ts +50 -0
- package/dist/upload-steps.d.ts +26 -0
- package/dist/upload-steps.js +29 -9
- package/dist/upload-store.d.ts +182 -0
- package/dist/upload-wallet-chain.d.ts +54 -0
- package/dist/upload-wallet-chain.js +203 -0
- package/dist/upload-wallet-plan.d.ts +128 -0
- package/dist/upload-wallet-plan.js +184 -0
- package/dist/upload-wallet.d.ts +24 -0
- package/dist/upload-wallet.js +208 -0
- package/dist/upload-wire.d.ts +244 -0
- package/dist/upload.d.ts +18 -0
- package/dist/upload.js +10 -0
- package/dist/usage-report.d.ts +35 -0
- package/dist/wallet-activity-chain.d.ts +5 -0
- package/dist/wallet-activity-chain.js +31 -0
- package/dist/wallet-chain.d.ts +19 -0
- package/dist/wallet-chain.js +2 -2
- package/dist/wallet-grant.d.ts +50 -0
- package/dist/wallet-grant.js +218 -0
- package/dist/wallet-send-chain.d.ts +24 -0
- package/dist/wallet-send-chain.js +49 -0
- package/dist/wallet-sign.d.ts +133 -0
- package/dist/wallet-sign.js +317 -0
- package/dist/wallet-storage-chain.d.ts +3 -0
- package/dist/wallet-storage-chain.js +20 -0
- package/dist/wallet-swap-chain.d.ts +52 -0
- package/dist/wallet-swap-chain.js +186 -0
- package/dist/wallet-swap-quote.d.ts +22 -0
- package/dist/wallet-swap-quote.js +126 -0
- package/dist/wallet.d.ts +135 -0
- package/dist/wallet.js +5 -5
- package/dist/walrus-write.d.ts +56 -0
- package/dist/walrus-write.js +5 -4
- package/dist/walrus.d.ts +86 -0
- package/dist/walrus.js +48 -10
- package/dist/zstd-node.d.ts +11 -0
- package/dist/zstd-node.js +71 -0
- package/docs/commands/accept-terms.md +15 -0
- package/docs/commands/create.md +17 -0
- package/docs/commands/credits.md +34 -0
- package/docs/commands/delete-account.md +14 -0
- package/docs/commands/deposit.md +27 -0
- package/docs/commands/devices.md +9 -0
- package/docs/commands/documents.md +10 -0
- package/docs/commands/env.md +21 -0
- package/docs/commands/extend.md +27 -0
- package/docs/commands/folders.md +10 -0
- package/docs/commands/get.md +14 -0
- package/docs/commands/key.md +14 -0
- package/docs/commands/login.md +23 -0
- package/docs/commands/logout.md +11 -0
- package/docs/commands/losses.md +13 -0
- package/docs/commands/ls.md +16 -0
- package/docs/commands/marks.md +9 -0
- package/docs/commands/mcp.md +33 -0
- package/docs/commands/mode.md +29 -0
- package/docs/commands/on-collision.md +13 -0
- package/docs/commands/padding.md +13 -0
- package/docs/commands/public-code.md +9 -0
- package/docs/commands/pull.md +9 -0
- package/docs/commands/push.md +12 -0
- package/docs/commands/put.md +36 -0
- package/docs/commands/rebuild.md +13 -0
- package/docs/commands/recovery.md +18 -0
- package/docs/commands/s3.md +17 -0
- package/docs/commands/share.md +15 -0
- package/docs/commands/support.md +16 -0
- package/docs/commands/tip.md +21 -0
- package/docs/commands/trash.md +25 -0
- package/docs/commands/trial.md +12 -0
- package/docs/commands/unlock.md +25 -0
- package/docs/commands/update.md +14 -0
- package/docs/commands/usage.md +14 -0
- package/docs/commands/verify.md +18 -0
- package/docs/commands/wallet.md +67 -0
- package/docs/commands/whoami.md +12 -0
- package/package.json +67 -8
- package/server.json +28 -0
- package/vendor/nmts-crypto/nmts_crypto_wasm.d.ts +23 -2
- package/vendor/nmts-crypto/nmts_crypto_wasm.js +44 -2
- package/vendor/nmts-crypto/nmts_crypto_wasm_bg.wasm +0 -0
- package/vendor/nmts-crypto/nmts_crypto_wasm_bg.wasm.d.ts +1 -0
- package/vendor/nmts-crypto/package.json +1 -1
- package/dist/commands/consent.js +0 -77
- package/dist/extend-sign.js +0 -130
package/dist/mcp-tools/share.js
CHANGED
|
@@ -11,6 +11,23 @@
|
|
|
11
11
|
// person or a program typed a grant, so this is a rule rather than a mechanism, and saying
|
|
12
12
|
// otherwise would be claiming a protection that is not there.
|
|
13
13
|
//
|
|
14
|
+
// ⛔ AND ON THIS SURFACE THE PERSON IS ASKED EVERY TIME. Once per
|
|
15
|
+
// machine is the right shape for a capability and the wrong one for this act: the machine
|
|
16
|
+
// agreed that sharing may happen, and no recorded agreement can say that THIS address is the
|
|
17
|
+
// one that was meant. Every other tool here acts on the account's own files; this one hands a
|
|
18
|
+
// key to somebody else, and the undo does not reach what has already been fetched. So the
|
|
19
|
+
// question goes to the client, over `elicitation/create`, once per call.
|
|
20
|
+
//
|
|
21
|
+
// ⛔ NO WAY TO ASK MEANS NO SHARE. A client that did not declare elicitation gets a refusal that
|
|
22
|
+
// names the terminal command instead. Falling back to the recorded agreement would let the
|
|
23
|
+
// least capable client decide the rule for all of them.
|
|
24
|
+
//
|
|
25
|
+
// ⛔ A MODE THAT WAS TURNED ON IS THE ANSWER ALREADY GIVEN. With `auto` or `skip-permissions` set,
|
|
26
|
+
// the person has said in writing that an agent may decide, and asking anyway would be ignoring
|
|
27
|
+
// them — the modes exist for the unattended case, which is exactly the case where nobody is
|
|
28
|
+
// there to answer. The mode was typed with a flag that spells out the risk, and every run that
|
|
29
|
+
// uses one announces it.
|
|
30
|
+
//
|
|
14
31
|
// ⛔ THE RECIPIENT'S PUBLIC CODE IS NOT CHECKED AGAINST A PERSON. A share sent to a well-formed
|
|
15
32
|
// code that belongs to somebody else is sent, and is not recallable. Confirm it with whoever
|
|
16
33
|
// gave it to you, out of band, before calling this.
|
|
@@ -25,24 +42,26 @@ export function shareTools(ctx) {
|
|
|
25
42
|
"recipient already fetched, and a public code typed wrongly is a share sent to whoever " +
|
|
26
43
|
"holds that code. The first share on a machine stops and asks the person to agree — show " +
|
|
27
44
|
"them what it says rather than agreeing for them. Nothing is uploaded and nothing is " +
|
|
28
|
-
"charged; the recipient pays nothing either."
|
|
45
|
+
"charged; the recipient pays nothing either. Every call puts the file and the code in " +
|
|
46
|
+
"front of the person to confirm, unless they have turned on a mode that says an agent " +
|
|
47
|
+
"may decide.",
|
|
29
48
|
inputSchema: {
|
|
30
49
|
type: "object",
|
|
31
50
|
properties: {
|
|
32
51
|
path: { type: "string", description: "The file to share, as nmts_list prints it." },
|
|
33
52
|
public_code: {
|
|
34
53
|
type: "string",
|
|
35
|
-
description: "The recipient's PUBLIC CODE, given to you by them. Not their
|
|
54
|
+
description: "The recipient's PUBLIC CODE, given to you by them. Not their NMTS key.",
|
|
36
55
|
},
|
|
37
56
|
},
|
|
38
57
|
required: ["path", "public_code"],
|
|
39
58
|
additionalProperties: false,
|
|
40
59
|
},
|
|
41
|
-
run: (args) =>
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
write,
|
|
45
|
-
}
|
|
60
|
+
run: async (args) => {
|
|
61
|
+
const path = needString(args, "path");
|
|
62
|
+
const code = needString(args, "public_code");
|
|
63
|
+
return say((write) => share(path, code, { ...common(ctx), json: true, write }));
|
|
64
|
+
},
|
|
46
65
|
},
|
|
47
66
|
{
|
|
48
67
|
name: "nmts_unshare",
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
// Writing to the person who builds NMTS, offered to an agent as tools.
|
|
2
|
+
//
|
|
3
|
+
// ⛔ WHY THIS IS ON THIS SURFACE AT ALL, when credentials, consent and the human check are not.
|
|
4
|
+
// Those are the person's to give. A report is the opposite: it is the one thing an agent that
|
|
5
|
+
// met a defect can do about it, and it is better placed than the person it works for to say
|
|
6
|
+
// what was run and what came back. The owner asked for it to be here (2026-09-03).
|
|
7
|
+
//
|
|
8
|
+
// ⛔ AND IT IS THE ONE TOOL HERE THAT SENDS WORDS SOMEWHERE. So the description says, in the
|
|
9
|
+
// words the command prints, what is stripped before anything leaves and what must not be
|
|
10
|
+
// typed. A model reads the description before it composes the message, which is the only
|
|
11
|
+
// moment where saying it changes what gets written.
|
|
12
|
+
//
|
|
13
|
+
// ⛔ IT DOES NOT ASK OVER ELICITATION, AND `nmts_share` DOES. The difference is what is at stake:
|
|
14
|
+
// a share hands somebody else the key to a file and cannot be recalled, and a report is words
|
|
15
|
+
// in an inbox. What replaces the question is that the tool answers with EXACTLY what it sent —
|
|
16
|
+
// the category, the message and the attachment, after redaction — so the preview is in the
|
|
17
|
+
// transcript whether or not anybody was watching.
|
|
18
|
+
import { support } from "../commands/support.js";
|
|
19
|
+
import { ATTACH_LOG_TEXT, categoryCodes, SUPPORT_SHORT } from "../support-copy.js";
|
|
20
|
+
import { MAX_ATTACHED_RUNS } from "../run-log.js";
|
|
21
|
+
import { common, needString, say } from "./context.js";
|
|
22
|
+
const NO_ARGS = { type: "object", properties: {}, additionalProperties: false };
|
|
23
|
+
/** The two sentences every message is written under, as one line for a description. */
|
|
24
|
+
const CARE = SUPPORT_SHORT.join(" ");
|
|
25
|
+
/** What `--attach-log` does, as one line for a description. */
|
|
26
|
+
const LOG = ATTACH_LOG_TEXT.join(" ");
|
|
27
|
+
/** Values a caller wants replaced on top of the rules. Declared the same way on both writers. */
|
|
28
|
+
const OMIT_ARG = {
|
|
29
|
+
type: "array",
|
|
30
|
+
items: { type: "string" },
|
|
31
|
+
description: "Values that must not travel — a file name, a folder path. Every occurrence in the message " +
|
|
32
|
+
"and in the attached log becomes [omitted]. At least 3 characters each.",
|
|
33
|
+
};
|
|
34
|
+
export function supportTools(ctx) {
|
|
35
|
+
return [
|
|
36
|
+
{
|
|
37
|
+
name: "nmts_support_send",
|
|
38
|
+
description: "File a report with the developer of NMTS: a bug, a confusing message, an idea, a " +
|
|
39
|
+
`question. ${CARE} The NMTS key, API key, passphrase, tokens and key material are ` +
|
|
40
|
+
"replaced by labels on this machine before anything is sent, and file contents are never " +
|
|
41
|
+
`read at all; file names and the account's public code may go. ${LOG} The answer to this ` +
|
|
42
|
+
"call is exactly what was sent, so it can be checked afterwards. One report per problem.",
|
|
43
|
+
inputSchema: {
|
|
44
|
+
type: "object",
|
|
45
|
+
properties: {
|
|
46
|
+
category: {
|
|
47
|
+
type: "string",
|
|
48
|
+
enum: categoryCodes(),
|
|
49
|
+
description: "What the report is about.",
|
|
50
|
+
},
|
|
51
|
+
subcategory: {
|
|
52
|
+
type: "string",
|
|
53
|
+
description: "Which part of that category, when one fits. Optional everywhere.",
|
|
54
|
+
},
|
|
55
|
+
message: {
|
|
56
|
+
type: "string",
|
|
57
|
+
description: "The report. A good one has four parts: the command that was run, what was " +
|
|
58
|
+
"expected, what happened, and anything that narrows it down. 1 to 4000 characters.",
|
|
59
|
+
},
|
|
60
|
+
attach_log_runs: {
|
|
61
|
+
type: "integer",
|
|
62
|
+
description: `How many of the CLI's last runs to attach. 0 to ${MAX_ATTACHED_RUNS}; 0 attaches none.`,
|
|
63
|
+
},
|
|
64
|
+
omit: OMIT_ARG,
|
|
65
|
+
},
|
|
66
|
+
required: ["category", "message"],
|
|
67
|
+
additionalProperties: false,
|
|
68
|
+
},
|
|
69
|
+
// ⚠ NOT `json: true`, unlike every read on this surface. The machine-readable answer is the
|
|
70
|
+
// ticket alone; what this tool has to hand back is what it SENT.
|
|
71
|
+
run: (args) => say((write) => support("send", [], {
|
|
72
|
+
...common(ctx),
|
|
73
|
+
write,
|
|
74
|
+
yes: true,
|
|
75
|
+
category: needString(args, "category"),
|
|
76
|
+
...(typeof args["subcategory"] === "string" ? { sub: args["subcategory"] } : {}),
|
|
77
|
+
message: needString(args, "message"),
|
|
78
|
+
...(runsAsked(args["attach_log_runs"])),
|
|
79
|
+
omit: stringsOf(args["omit"]),
|
|
80
|
+
})),
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
name: "nmts_support_list",
|
|
84
|
+
description: "The reports this account has filed from the command line, with their codes, what each " +
|
|
85
|
+
"is about, whether it has been answered, and how many threads hold something unread. " +
|
|
86
|
+
"Read-only; costs nothing.",
|
|
87
|
+
inputSchema: NO_ARGS,
|
|
88
|
+
run: () => say((write) => support("list", [], { ...common(ctx), json: true, write })),
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
name: "nmts_support_show",
|
|
92
|
+
description: "One report and everything said in it, oldest first, using a code from nmts_support_list. " +
|
|
93
|
+
"Reading it marks the thread as seen.",
|
|
94
|
+
inputSchema: {
|
|
95
|
+
type: "object",
|
|
96
|
+
properties: { code: { type: "string", description: "The report's code, from nmts_support_list." } },
|
|
97
|
+
required: ["code"],
|
|
98
|
+
additionalProperties: false,
|
|
99
|
+
},
|
|
100
|
+
run: (args) => say((write) => support("show", [needString(args, "code")], { ...common(ctx), json: true, write })),
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
name: "nmts_support_reply",
|
|
104
|
+
description: `Answer in a report's own thread, using a code from nmts_support_list. ${CARE} The same ` +
|
|
105
|
+
"redaction runs over a reply as over the first message. At most 8000 characters.",
|
|
106
|
+
inputSchema: {
|
|
107
|
+
type: "object",
|
|
108
|
+
properties: {
|
|
109
|
+
code: { type: "string", description: "The report's code, from nmts_support_list." },
|
|
110
|
+
message: { type: "string", description: "What to add to the thread." },
|
|
111
|
+
omit: OMIT_ARG,
|
|
112
|
+
},
|
|
113
|
+
required: ["code", "message"],
|
|
114
|
+
additionalProperties: false,
|
|
115
|
+
},
|
|
116
|
+
run: (args) => say((write) => support("reply", [needString(args, "code")], {
|
|
117
|
+
...common(ctx),
|
|
118
|
+
write,
|
|
119
|
+
yes: true,
|
|
120
|
+
message: needString(args, "message"),
|
|
121
|
+
omit: stringsOf(args["omit"]),
|
|
122
|
+
})),
|
|
123
|
+
},
|
|
124
|
+
];
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* How many runs to attach, in the shape the command takes.
|
|
128
|
+
*
|
|
129
|
+
* ⛔ THE DEFAULT IS NONE. On the command line `--attach-log` is typed on purpose; here it is a
|
|
130
|
+
* field a model fills in, and attaching a machine's recent history to every report because
|
|
131
|
+
* nobody said not to is the wrong default for the side that cannot see what is in it.
|
|
132
|
+
*/
|
|
133
|
+
function runsAsked(value) {
|
|
134
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value <= 0)
|
|
135
|
+
return {};
|
|
136
|
+
return { attachLog: String(value) };
|
|
137
|
+
}
|
|
138
|
+
function stringsOf(value) {
|
|
139
|
+
return Array.isArray(value) ? value.filter((v) => typeof v === "string") : [];
|
|
140
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type Autonomy } from "../autonomy.ts";
|
|
2
|
+
import type { Asker } from "../mcp-ask.ts";
|
|
3
|
+
import type { ToolDefinition } from "../mcp.ts";
|
|
4
|
+
import { type ActId, type Tier } from "../risk.ts";
|
|
5
|
+
interface ToolTier {
|
|
6
|
+
/** The act, or how to read it off the arguments when one tool both reads and sets. */
|
|
7
|
+
act: ActId | ((args: Record<string, unknown>) => ActId);
|
|
8
|
+
/** Changes nothing anywhere: the client may call it freely. */
|
|
9
|
+
readOnly?: true;
|
|
10
|
+
/** The question put to the person, when the generic one would not name what is at stake. */
|
|
11
|
+
question?: (args: Record<string, unknown>) => string;
|
|
12
|
+
}
|
|
13
|
+
export declare const TOOL_TIERS: Readonly<Record<string, ToolTier>>;
|
|
14
|
+
/** What `tools/list` says about a tool, in the words the MCP specification gives those hints. */
|
|
15
|
+
export declare function annotationsOf(name: string): Record<string, boolean>;
|
|
16
|
+
/** The sentence put in front of a tool's description, so the model knows the tier before calling. */
|
|
17
|
+
export declare function tierLine(tier: Tier): string;
|
|
18
|
+
/** The refusal for a client that cannot be asked, and the way round it. */
|
|
19
|
+
export declare function cannotAsk(what: string): string;
|
|
20
|
+
/** The refusal after the question was put and not agreed to. */
|
|
21
|
+
export declare const SAID_NO = "Refused: the person did not confirm it. Nothing was sent and nothing changed.";
|
|
22
|
+
/**
|
|
23
|
+
* Run the tier gate for one tool call. Returns the refusal to hand back, or `null` to go ahead.
|
|
24
|
+
*
|
|
25
|
+
* ⛔ THE MODE AND THE ASKER ARE ARGUMENTS, NOT THINGS THIS READS, so every branch is tested for
|
|
26
|
+
* the answer it gives rather than for the machine the test runs on.
|
|
27
|
+
*/
|
|
28
|
+
export declare function passTool(name: string, args: Record<string, unknown>, mode: Autonomy, ask: Asker, now?: Date): Promise<string | null>;
|
|
29
|
+
/** Wrap the served tools: tier in the description, hints in the listing, the gate before each run. */
|
|
30
|
+
export declare function withTiers(tools: readonly ToolDefinition[], asker: () => Asker): ToolDefinition[];
|
|
31
|
+
export {};
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
// Every MCP tool carries the tier of the act it performs, and the same gate the command line runs.
|
|
2
|
+
//
|
|
3
|
+
// ⛔ ONE TABLE, HELD AGAINST THE SURFACE BY A TEST. A tool with no row here is a tool nobody
|
|
4
|
+
// decided about, so the test compares this table with the tools actually served, both ways.
|
|
5
|
+
//
|
|
6
|
+
// ⛔ THE GATE RUNS BEFORE THE TOOL, IN THE TRANSPORT'S WRAPPER, so a tool cannot forget it. What
|
|
7
|
+
// it does is what `gate.ts` does for a command: a locked act is refused until a person runs
|
|
8
|
+
// `nmts unlock <key>` at a terminal; an act that needs the person's yes in this mode is put in
|
|
9
|
+
// front of them over MCP elicitation, and refused when the client has no way to ask.
|
|
10
|
+
import { currentMode } from "../autonomy.js";
|
|
11
|
+
import { unlocked } from "../gate.js";
|
|
12
|
+
import { BINARY_NAME } from "../product.js";
|
|
13
|
+
import { ACTS } from "../risk.js";
|
|
14
|
+
const READ = { act: "ls", readOnly: true };
|
|
15
|
+
export const TOOL_TIERS = {
|
|
16
|
+
nmts_whoami: { act: "whoami", readOnly: true },
|
|
17
|
+
nmts_list: READ,
|
|
18
|
+
nmts_usage: { act: "usage", readOnly: true },
|
|
19
|
+
nmts_expiring: { act: "expiring", readOnly: true },
|
|
20
|
+
nmts_losses: { act: "losses", readOnly: true },
|
|
21
|
+
nmts_loss_recheck: { act: "losses", readOnly: true },
|
|
22
|
+
nmts_balance: { act: "balance", readOnly: true },
|
|
23
|
+
nmts_wallet_activity: { act: "wallet", readOnly: true },
|
|
24
|
+
nmts_wallet_storage: { act: "wallet", readOnly: true },
|
|
25
|
+
nmts_devices: { act: "devices", readOnly: true },
|
|
26
|
+
nmts_public_code: { act: "public-code", readOnly: true },
|
|
27
|
+
nmts_shares: { act: "shares", readOnly: true },
|
|
28
|
+
nmts_shares_sent: { act: "shares", readOnly: true },
|
|
29
|
+
nmts_notices: { act: "notices", readOnly: true },
|
|
30
|
+
nmts_notice: { act: "notices", readOnly: true },
|
|
31
|
+
nmts_terms: { act: "terms", readOnly: true },
|
|
32
|
+
nmts_privacy: { act: "privacy", readOnly: true },
|
|
33
|
+
nmts_get: { act: "get" },
|
|
34
|
+
nmts_pull: { act: "pull" },
|
|
35
|
+
nmts_receive: { act: "receive" },
|
|
36
|
+
nmts_put: { act: "put" },
|
|
37
|
+
nmts_push: { act: "push" },
|
|
38
|
+
nmts_mkdir: { act: "mkdir" },
|
|
39
|
+
nmts_move: { act: "mv" },
|
|
40
|
+
nmts_rename: { act: "rename" },
|
|
41
|
+
nmts_mark: { act: "star" },
|
|
42
|
+
nmts_label_rename: { act: "label" },
|
|
43
|
+
nmts_unlabel_all: { act: "unlabel" },
|
|
44
|
+
nmts_padding: { act: (args) => (typeof args["mode"] === "string" ? "padding.set" : "padding") },
|
|
45
|
+
nmts_deposit: { act: (args) => (typeof args["credits"] === "number" ? "deposit.set" : "deposit") },
|
|
46
|
+
nmts_credits_transfer: {
|
|
47
|
+
act: "credits.transfer",
|
|
48
|
+
question: (args) => `Move ${String(args["credits"])} credits from this account to ${String(args["to"])}?\n\n` +
|
|
49
|
+
"Both are accounts of yours — the server refuses any other recipient. Nothing is spent by " +
|
|
50
|
+
"moving them and the same move the other way puts them back, but the credits keep the date " +
|
|
51
|
+
"they were already going to lapse on: moving them does not renew them.",
|
|
52
|
+
},
|
|
53
|
+
nmts_trash: { act: "rm" },
|
|
54
|
+
nmts_restore: { act: "restore" },
|
|
55
|
+
nmts_share: {
|
|
56
|
+
act: "share",
|
|
57
|
+
question: (args) => `Share "${String(args["path"])}" with the NMTS account whose public code is ${String(args["public_code"])}?\n\n` +
|
|
58
|
+
"Whoever holds that code can then download the file. Withdrawing the share afterwards stops " +
|
|
59
|
+
"further downloads and cannot reach a copy already fetched. The code is not checked against " +
|
|
60
|
+
"a person — if it is the wrong one, the file goes to whoever holds it.",
|
|
61
|
+
},
|
|
62
|
+
nmts_unshare: { act: "unshare" },
|
|
63
|
+
nmts_support_send: { act: "support.send" },
|
|
64
|
+
nmts_support_list: { act: "support", readOnly: true },
|
|
65
|
+
nmts_support_show: { act: "support", readOnly: true },
|
|
66
|
+
nmts_support_reply: { act: "support.send" },
|
|
67
|
+
};
|
|
68
|
+
/** What `tools/list` says about a tool, in the words the MCP specification gives those hints. */
|
|
69
|
+
export function annotationsOf(name) {
|
|
70
|
+
const t = TOOL_TIERS[name];
|
|
71
|
+
const tier = t === undefined || typeof t.act === "function" ? "low" : ACTS[t.act].tier;
|
|
72
|
+
return {
|
|
73
|
+
readOnlyHint: t?.readOnly === true,
|
|
74
|
+
destructiveHint: tier === "high" || tier === "ultra-high",
|
|
75
|
+
idempotentHint: t?.readOnly === true,
|
|
76
|
+
openWorldHint: false,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
/** The sentence put in front of a tool's description, so the model knows the tier before calling. */
|
|
80
|
+
export function tierLine(tier) {
|
|
81
|
+
switch (tier) {
|
|
82
|
+
case "none":
|
|
83
|
+
return "";
|
|
84
|
+
case "low":
|
|
85
|
+
return "Tier low: asked in the default mode, runs unasked in the auto modes. ";
|
|
86
|
+
case "medium":
|
|
87
|
+
return "Tier medium: asked in the default mode; in an auto mode it is your judgement whether this is what the person wants. ";
|
|
88
|
+
case "high":
|
|
89
|
+
return `Tier high: locked until a person runs \`${BINARY_NAME} unlock\` at a terminal, and asked every time in every mode but skip-permissions. `;
|
|
90
|
+
case "ultra-high":
|
|
91
|
+
return "Tier ultra-high: a person's act, in every mode. ";
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/** The refusal for a client that cannot be asked, and the way round it. */
|
|
95
|
+
export function cannotAsk(what) {
|
|
96
|
+
return (`Refused: ${what} needs the person's yes in this mode, and this client cannot put a question in ` +
|
|
97
|
+
`front of them. Ask them in your own words; once they agree, run the same thing with \`${BINARY_NAME}\` ` +
|
|
98
|
+
`at the command line with --yes, or use a client that supports MCP elicitation. Nothing was done.`);
|
|
99
|
+
}
|
|
100
|
+
/** The refusal after the question was put and not agreed to. */
|
|
101
|
+
export const SAID_NO = "Refused: the person did not confirm it. Nothing was sent and nothing changed.";
|
|
102
|
+
/**
|
|
103
|
+
* Run the tier gate for one tool call. Returns the refusal to hand back, or `null` to go ahead.
|
|
104
|
+
*
|
|
105
|
+
* ⛔ THE MODE AND THE ASKER ARE ARGUMENTS, NOT THINGS THIS READS, so every branch is tested for
|
|
106
|
+
* the answer it gives rather than for the machine the test runs on.
|
|
107
|
+
*/
|
|
108
|
+
export async function passTool(name, args, mode, ask, now = new Date()) {
|
|
109
|
+
const t = TOOL_TIERS[name];
|
|
110
|
+
if (t === undefined)
|
|
111
|
+
return `Refused: ${name} has no tier. Nothing was done.`;
|
|
112
|
+
const act = typeof t.act === "function" ? t.act(args) : t.act;
|
|
113
|
+
const a = ACTS[act];
|
|
114
|
+
if (a.tier === "none" || args["dry_run"] === true)
|
|
115
|
+
return null;
|
|
116
|
+
if (mode === "skip-permissions")
|
|
117
|
+
return null;
|
|
118
|
+
if (a.tier === "ultra-high")
|
|
119
|
+
return `Refused: ${a.what ?? act} is a person's act, in every mode. Nothing was done.`;
|
|
120
|
+
const what = a.what ?? `${name} does this.`;
|
|
121
|
+
if ("lock" in a && !(await unlocked(a.lock, now))) {
|
|
122
|
+
return (`Refused: ${what} It is locked on this machine. A person unlocks it at a terminal, once: ` +
|
|
123
|
+
`\`${BINARY_NAME} unlock ${a.lock}\`. Do not run that yourself. Nothing was done.`);
|
|
124
|
+
}
|
|
125
|
+
const needsYes = a.tier === "high" ? !("standing" in a) : mode === "default";
|
|
126
|
+
if (!needsYes)
|
|
127
|
+
return null;
|
|
128
|
+
if (ask === null)
|
|
129
|
+
return cannotAsk(what);
|
|
130
|
+
const outcome = await ask(t.question?.(args) ?? `${what} Go ahead?`);
|
|
131
|
+
if (outcome === "unreachable")
|
|
132
|
+
return cannotAsk(what);
|
|
133
|
+
return outcome === "yes" ? null : SAID_NO;
|
|
134
|
+
}
|
|
135
|
+
/** Wrap the served tools: tier in the description, hints in the listing, the gate before each run. */
|
|
136
|
+
export function withTiers(tools, asker) {
|
|
137
|
+
return tools.map((tool) => {
|
|
138
|
+
const t = TOOL_TIERS[tool.name];
|
|
139
|
+
const tier = t === undefined ? "low" : typeof t.act === "function" ? ACTS[t.act({})].tier : ACTS[t.act].tier;
|
|
140
|
+
return {
|
|
141
|
+
...tool,
|
|
142
|
+
description: `${tierLine(tier)}${tool.description}`,
|
|
143
|
+
annotations: annotationsOf(tool.name),
|
|
144
|
+
run: async (args) => (await passTool(tool.name, args, currentMode(), asker())) ?? (await tool.run(args)),
|
|
145
|
+
};
|
|
146
|
+
});
|
|
147
|
+
}
|
package/dist/mcp.d.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { type Asker } from "./mcp-ask.ts";
|
|
2
|
+
import { hermesFromParent, type HostSighting } from "./agent-host.ts";
|
|
3
|
+
/** JSON-RPC 2.0, the subset MCP uses. `id` absent means a notification: no answer is sent. */
|
|
4
|
+
export interface Request {
|
|
5
|
+
jsonrpc: "2.0";
|
|
6
|
+
id?: string | number;
|
|
7
|
+
method: string;
|
|
8
|
+
params?: Record<string, unknown>;
|
|
9
|
+
}
|
|
10
|
+
export interface ToolDefinition {
|
|
11
|
+
name: string;
|
|
12
|
+
description: string;
|
|
13
|
+
inputSchema: Record<string, unknown>;
|
|
14
|
+
/** The MCP hints (read-only, destructive, …); listed when present. */
|
|
15
|
+
annotations?: Record<string, boolean>;
|
|
16
|
+
/** Returns the text the model reads. Throwing produces a tool error, not a dead session. */
|
|
17
|
+
run(args: Record<string, unknown>): Promise<string>;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Protocol versions this server knows how to speak.
|
|
21
|
+
*
|
|
22
|
+
* ⛔ Ordered newest first. If the client asks for one of these it gets that one back; if it asks
|
|
23
|
+
* for anything else it gets the newest we know, which is what the specification says to do —
|
|
24
|
+
* guessing that an unknown version is compatible is how a session half-works.
|
|
25
|
+
*/
|
|
26
|
+
export declare const PROTOCOL_VERSIONS: readonly ["2025-06-18", "2025-03-26", "2024-11-05"];
|
|
27
|
+
export interface ServerInfo {
|
|
28
|
+
name: string;
|
|
29
|
+
version: string;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Answer one request. Returns the response object, or `null` for a notification.
|
|
33
|
+
*
|
|
34
|
+
* Pure apart from the tools it is handed, so the whole protocol is testable without pipes.
|
|
35
|
+
*/
|
|
36
|
+
export declare function handle(request: unknown, tools: readonly ToolDefinition[], info: ServerInfo): Promise<Record<string, unknown> | null>;
|
|
37
|
+
/** What `initialize` said about the client at the other end. */
|
|
38
|
+
export interface ClientSighting {
|
|
39
|
+
/** The name the client gave, verbatim, or `null` if it gave none. */
|
|
40
|
+
name: string | null;
|
|
41
|
+
/** The agent host that name belongs to, or `null` when the name belongs to no host we know. */
|
|
42
|
+
host: HostSighting | null;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Who is on the other end, read out of an `initialize` request. `null` for anything else.
|
|
46
|
+
*
|
|
47
|
+
* ⛔ THE NAME IS KEPT EVEN WHEN IT MATCHES NOTHING. A client calling itself something we have never
|
|
48
|
+
* heard of is a fact somebody can act on; turning it into `null` throws away the only evidence
|
|
49
|
+
* that would let a person work out what is talking to them.
|
|
50
|
+
*
|
|
51
|
+
* ⚠ Hermes is the one host that sends no name of its own — the Python SDK's default arrives
|
|
52
|
+
* instead — so it is looked for in the shape of the parent process, and only where that can be
|
|
53
|
+
* read. On every other platform Hermes is simply not recognised.
|
|
54
|
+
*/
|
|
55
|
+
export declare function clientOf(request: unknown, readParent?: typeof hermesFromParent): ClientSighting | null;
|
|
56
|
+
/**
|
|
57
|
+
* What the client said it can do, read out of the same `initialize` request.
|
|
58
|
+
*
|
|
59
|
+
* ⛔ SEPARATE FROM `clientOf` ON PURPOSE. Who is connected and what it can do are different
|
|
60
|
+
* questions with different consumers, and the first one is deliberately kept usable even when
|
|
61
|
+
* the client names itself nothing.
|
|
62
|
+
*/
|
|
63
|
+
export declare function capabilitiesOf(request: unknown): unknown;
|
|
64
|
+
export interface ServeOptions {
|
|
65
|
+
input: NodeJS.ReadableStream;
|
|
66
|
+
/** Where protocol messages go. NOTHING else may write here. */
|
|
67
|
+
output: (line: string) => void;
|
|
68
|
+
tools: readonly ToolDefinition[];
|
|
69
|
+
info: ServerInfo;
|
|
70
|
+
/** Called once per `initialize`, with whatever the client said about itself. */
|
|
71
|
+
onClient?: (client: ClientSighting) => void;
|
|
72
|
+
/**
|
|
73
|
+
* Called once per `initialize`, with the way to ask this client a question — or `null` when it
|
|
74
|
+
* declared no way to ask. ⛔ It arrives here rather than in `ToolContext` because it cannot exist
|
|
75
|
+
* until the client has spoken, and the tools are built before that.
|
|
76
|
+
*/
|
|
77
|
+
onAsker?: (asker: Asker) => void;
|
|
78
|
+
}
|
|
79
|
+
/** Read newline-delimited JSON-RPC from `input` until it ends, answering on `output`. */
|
|
80
|
+
export declare function serve(options: ServeOptions): Promise<void>;
|
package/dist/mcp.js
CHANGED
|
@@ -15,7 +15,9 @@
|
|
|
15
15
|
// anywhere else on disk, and it cannot make or revoke a key — those need a person at a browser.
|
|
16
16
|
import { createInterface } from "node:readline";
|
|
17
17
|
import { checkArgs } from "./mcp-args.js";
|
|
18
|
+
import { askerFor } from "./mcp-ask.js";
|
|
18
19
|
import { renderError } from "./errors.js";
|
|
20
|
+
import { hermesFromParent, hostFromClientInfo } from "./agent-host.js";
|
|
19
21
|
/**
|
|
20
22
|
* Protocol versions this server knows how to speak.
|
|
21
23
|
*
|
|
@@ -34,6 +36,19 @@ function isRequest(value) {
|
|
|
34
36
|
const v = value;
|
|
35
37
|
return v["jsonrpc"] === "2.0" && typeof v["method"] === "string";
|
|
36
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* A JSON-RPC answer to something WE sent. The client only sends these once this server has started
|
|
41
|
+
* asking questions of its own (see `mcp-ask.ts`), and before that they were reported as malformed
|
|
42
|
+
* requests — which put an error on the wire for a message that was perfectly well formed.
|
|
43
|
+
*/
|
|
44
|
+
function isResponse(value) {
|
|
45
|
+
if (typeof value !== "object" || value === null)
|
|
46
|
+
return false;
|
|
47
|
+
const v = value;
|
|
48
|
+
if (v["jsonrpc"] !== "2.0" || typeof v["method"] === "string")
|
|
49
|
+
return false;
|
|
50
|
+
return typeof v["id"] === "string" && ("result" in v || "error" in v);
|
|
51
|
+
}
|
|
37
52
|
/**
|
|
38
53
|
* Answer one request. Returns the response object, or `null` for a notification.
|
|
39
54
|
*
|
|
@@ -48,6 +63,10 @@ export async function handle(request, tools, info) {
|
|
|
48
63
|
const fail = (code, message) => ({ jsonrpc: "2.0", id, error: { code, message } });
|
|
49
64
|
switch (request.method) {
|
|
50
65
|
case "initialize": {
|
|
66
|
+
// ⛔ `clientInfo` IS NOT READ HERE, ON PURPOSE. It changes nothing about the answer, and this
|
|
67
|
+
// function is pure so that the whole protocol can be tested without pipes. Who connected
|
|
68
|
+
// is a fact about the SESSION, so `serve` reads it out of the same request — see
|
|
69
|
+
// `clientOf` below.
|
|
51
70
|
const asked = request.params?.["protocolVersion"];
|
|
52
71
|
const version = typeof asked === "string" && PROTOCOL_VERSIONS.includes(asked)
|
|
53
72
|
? asked
|
|
@@ -62,7 +81,12 @@ export async function handle(request, tools, info) {
|
|
|
62
81
|
return reply({});
|
|
63
82
|
case "tools/list":
|
|
64
83
|
return reply({
|
|
65
|
-
tools: tools.map((t) => ({
|
|
84
|
+
tools: tools.map((t) => ({
|
|
85
|
+
name: t.name,
|
|
86
|
+
description: t.description,
|
|
87
|
+
inputSchema: t.inputSchema,
|
|
88
|
+
...(t.annotations === undefined ? {} : { annotations: t.annotations }),
|
|
89
|
+
})),
|
|
66
90
|
});
|
|
67
91
|
case "tools/call": {
|
|
68
92
|
const name = request.params?.["name"];
|
|
@@ -113,9 +137,55 @@ export async function handle(request, tools, info) {
|
|
|
113
137
|
return fail(METHOD_NOT_FOUND, `unknown method ${request.method}`);
|
|
114
138
|
}
|
|
115
139
|
}
|
|
140
|
+
/**
|
|
141
|
+
* Who is on the other end, read out of an `initialize` request. `null` for anything else.
|
|
142
|
+
*
|
|
143
|
+
* ⛔ THE NAME IS KEPT EVEN WHEN IT MATCHES NOTHING. A client calling itself something we have never
|
|
144
|
+
* heard of is a fact somebody can act on; turning it into `null` throws away the only evidence
|
|
145
|
+
* that would let a person work out what is talking to them.
|
|
146
|
+
*
|
|
147
|
+
* ⚠ Hermes is the one host that sends no name of its own — the Python SDK's default arrives
|
|
148
|
+
* instead — so it is looked for in the shape of the parent process, and only where that can be
|
|
149
|
+
* read. On every other platform Hermes is simply not recognised.
|
|
150
|
+
*/
|
|
151
|
+
export function clientOf(request, readParent = hermesFromParent) {
|
|
152
|
+
if (!isRequest(request) || request.method !== "initialize")
|
|
153
|
+
return null;
|
|
154
|
+
const raw = request.params?.["clientInfo"];
|
|
155
|
+
const info = typeof raw === "object" && raw !== null && !Array.isArray(raw) ? raw : undefined;
|
|
156
|
+
const name = typeof info?.name === "string" && info.name !== "" ? info.name : null;
|
|
157
|
+
return { name, host: hostFromClientInfo(info) ?? readParent() };
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* What the client said it can do, read out of the same `initialize` request.
|
|
161
|
+
*
|
|
162
|
+
* ⛔ SEPARATE FROM `clientOf` ON PURPOSE. Who is connected and what it can do are different
|
|
163
|
+
* questions with different consumers, and the first one is deliberately kept usable even when
|
|
164
|
+
* the client names itself nothing.
|
|
165
|
+
*/
|
|
166
|
+
export function capabilitiesOf(request) {
|
|
167
|
+
if (!isRequest(request) || request.method !== "initialize")
|
|
168
|
+
return null;
|
|
169
|
+
return request.params?.["capabilities"] ?? null;
|
|
170
|
+
}
|
|
116
171
|
/** Read newline-delimited JSON-RPC from `input` until it ends, answering on `output`. */
|
|
117
172
|
export async function serve(options) {
|
|
118
173
|
const lines = createInterface({ input: options.input, crlfDelay: Infinity });
|
|
174
|
+
// ⛔ IDS WE SEND ARE IN OUR OWN NAMESPACE. JSON-RPC scopes ids per direction, so a client
|
|
175
|
+
// numbering its requests 1, 2, 3 says nothing about ours. The prefix makes a stray answer
|
|
176
|
+
// obviously ours to whoever is reading a transcript.
|
|
177
|
+
let asked = 0;
|
|
178
|
+
const waiting = new Map();
|
|
179
|
+
/** Tool calls still in flight. See the note in the loop for why they are not awaited there. */
|
|
180
|
+
const running = new Set();
|
|
181
|
+
const send = (method, params) => {
|
|
182
|
+
asked += 1;
|
|
183
|
+
const id = `nmts-${asked}`;
|
|
184
|
+
return new Promise((resolve) => {
|
|
185
|
+
waiting.set(id, resolve);
|
|
186
|
+
options.output(JSON.stringify({ jsonrpc: "2.0", id, method, params }));
|
|
187
|
+
});
|
|
188
|
+
};
|
|
119
189
|
for await (const line of lines) {
|
|
120
190
|
if (line.trim() === "")
|
|
121
191
|
continue;
|
|
@@ -127,16 +197,47 @@ export async function serve(options) {
|
|
|
127
197
|
options.output(JSON.stringify({ jsonrpc: "2.0", id: null, error: { code: PARSE_ERROR, message: "invalid JSON" } }));
|
|
128
198
|
continue;
|
|
129
199
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
200
|
+
// An answer to one of ours is routed and never handled as a request.
|
|
201
|
+
if (isResponse(parsed)) {
|
|
202
|
+
const resolve = waiting.get(parsed.id);
|
|
203
|
+
if (resolve !== undefined) {
|
|
204
|
+
waiting.delete(parsed.id);
|
|
205
|
+
// ⛔ An `error` answer resolves with `undefined` rather than throwing. The caller reads
|
|
206
|
+
// "not an accept" out of that, which is the same no it gives for every other shape it
|
|
207
|
+
// cannot read — one refusal path instead of two.
|
|
208
|
+
resolve("result" in parsed ? parsed.result : undefined);
|
|
209
|
+
}
|
|
210
|
+
continue;
|
|
133
211
|
}
|
|
134
|
-
|
|
212
|
+
const client = clientOf(parsed);
|
|
213
|
+
if (client !== null) {
|
|
214
|
+
options.onClient?.(client);
|
|
215
|
+
options.onAsker?.(askerFor(capabilitiesOf(parsed), send));
|
|
216
|
+
}
|
|
217
|
+
// ⛔ THE LOOP DOES NOT WAIT FOR THE TOOL, AND THAT IS NOT AN OPTIMISATION. This server now
|
|
218
|
+
// asks the client questions from inside a tool call (`mcp-ask.ts`), and the answer arrives
|
|
219
|
+
// as another line on this same pipe. Awaiting the tool here would mean the loop cannot read
|
|
220
|
+
// the answer until the tool finishes, and the tool cannot finish until the answer is read —
|
|
221
|
+
// a deadlock that no test of the pieces would find, because each piece is correct.
|
|
222
|
+
//
|
|
223
|
+
// ⛔ REPLIES MAY THEREFORE COME BACK OUT OF ORDER, WHICH JSON-RPC ALLOWS: every reply carries
|
|
224
|
+
// the id it answers. Nothing here shares state between calls, so two in flight cannot see
|
|
225
|
+
// each other.
|
|
226
|
+
const job = handle(parsed, options.tools, options.info)
|
|
227
|
+
.catch((error) => {
|
|
135
228
|
// The handler itself falling over must not end the session either.
|
|
136
229
|
const message = error instanceof Error ? error.message : "the server failed";
|
|
137
|
-
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
|
|
230
|
+
return { jsonrpc: "2.0", id: null, error: { code: INTERNAL_ERROR, message } };
|
|
231
|
+
})
|
|
232
|
+
.then((response) => {
|
|
233
|
+
if (response !== null)
|
|
234
|
+
options.output(JSON.stringify(response));
|
|
235
|
+
});
|
|
236
|
+
running.add(job);
|
|
237
|
+
void job.finally(() => running.delete(job));
|
|
141
238
|
}
|
|
239
|
+
// ⛔ THE INPUT ENDING IS NOT THE SESSION ENDING. A client that closes the pipe with a tool still
|
|
240
|
+
// running gets its answer written before this returns; dropping it would lose work already
|
|
241
|
+
// done, and in the paid tools that means work already charged for.
|
|
242
|
+
await Promise.all(running);
|
|
142
243
|
}
|