@homeflare/alchemy 0.43.0 → 0.45.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 +10 -1
- package/dist/caddy/index.js +35 -1
- package/dist/caddy/index.js.map +3 -3
- package/dist/launchd/index.js +35 -1
- package/dist/launchd/index.js.map +3 -3
- package/dist/linux/index.js +35 -1
- package/dist/linux/index.js.map +3 -3
- package/dist/litellm/access-group-form.d.ts +26 -0
- package/dist/litellm/access-group-form.d.ts.map +1 -0
- package/dist/litellm/access-group-operations.d.ts +37 -0
- package/dist/litellm/access-group-operations.d.ts.map +1 -0
- package/dist/litellm/access-group-types.d.ts +49 -0
- package/dist/litellm/access-group-types.d.ts.map +1 -0
- package/dist/litellm/access-group.d.ts +55 -0
- package/dist/litellm/access-group.d.ts.map +1 -0
- package/dist/litellm/credential-errors.d.ts +131 -0
- package/dist/litellm/credential-errors.d.ts.map +1 -0
- package/dist/litellm/credential-form.d.ts +79 -0
- package/dist/litellm/credential-form.d.ts.map +1 -0
- package/dist/litellm/credential-operations.d.ts +84 -0
- package/dist/litellm/credential-operations.d.ts.map +1 -0
- package/dist/litellm/credential-rewrite.d.ts +6 -0
- package/dist/litellm/credential-rewrite.d.ts.map +1 -0
- package/dist/litellm/credential-types.d.ts +87 -0
- package/dist/litellm/credential-types.d.ts.map +1 -0
- package/dist/litellm/credential-values.d.ts +53 -0
- package/dist/litellm/credential-values.d.ts.map +1 -0
- package/dist/litellm/credential.d.ts +83 -0
- package/dist/litellm/credential.d.ts.map +1 -0
- package/dist/litellm/fake-access-group-litellm.d.ts +34 -0
- package/dist/litellm/fake-access-group-litellm.d.ts.map +1 -0
- package/dist/litellm/fake-credential-litellm.d.ts +54 -0
- package/dist/litellm/fake-credential-litellm.d.ts.map +1 -0
- package/dist/litellm/fake-keys.d.ts +53 -0
- package/dist/litellm/fake-keys.d.ts.map +1 -0
- package/dist/litellm/fake-litellm.d.ts +4 -1
- package/dist/litellm/fake-litellm.d.ts.map +1 -1
- package/dist/litellm/fake-mcp-litellm.d.ts +67 -0
- package/dist/litellm/fake-mcp-litellm.d.ts.map +1 -0
- package/dist/litellm/fake-model-litellm-post-update.d.ts +49 -0
- package/dist/litellm/fake-model-litellm-post-update.d.ts.map +1 -0
- package/dist/litellm/fake-model-litellm.d.ts +48 -0
- package/dist/litellm/fake-model-litellm.d.ts.map +1 -0
- package/dist/litellm/fake-policy-attachment-litellm.d.ts +33 -0
- package/dist/litellm/fake-policy-attachment-litellm.d.ts.map +1 -0
- package/dist/litellm/fake-policy-litellm.d.ts +32 -0
- package/dist/litellm/fake-policy-litellm.d.ts.map +1 -0
- package/dist/litellm/fake-registry-base.d.ts +42 -0
- package/dist/litellm/fake-registry-base.d.ts.map +1 -0
- package/dist/litellm/fake-stack.d.ts +8 -1
- package/dist/litellm/fake-stack.d.ts.map +1 -1
- package/dist/litellm/fake-team-litellm.d.ts +47 -0
- package/dist/litellm/fake-team-litellm.d.ts.map +1 -0
- package/dist/litellm/fake-tool-litellm.d.ts +28 -0
- package/dist/litellm/fake-tool-litellm.d.ts.map +1 -0
- package/dist/litellm/fake-toolset-litellm.d.ts +29 -0
- package/dist/litellm/fake-toolset-litellm.d.ts.map +1 -0
- package/dist/litellm/index.d.ts +18 -0
- package/dist/litellm/index.d.ts.map +1 -1
- package/dist/litellm/index.js +3078 -2
- package/dist/litellm/index.js.map +65 -4
- package/dist/litellm/key-errors.d.ts +169 -0
- package/dist/litellm/key-errors.d.ts.map +1 -0
- package/dist/litellm/key-form.d.ts +119 -0
- package/dist/litellm/key-form.d.ts.map +1 -0
- package/dist/litellm/key-harness.d.ts +41 -0
- package/dist/litellm/key-harness.d.ts.map +1 -0
- package/dist/litellm/key-metadata-errors.d.ts +30 -0
- package/dist/litellm/key-metadata-errors.d.ts.map +1 -0
- package/dist/litellm/key-metadata.d.ts +22 -0
- package/dist/litellm/key-metadata.d.ts.map +1 -0
- package/dist/litellm/key-operations.d.ts +63 -0
- package/dist/litellm/key-operations.d.ts.map +1 -0
- package/dist/litellm/key-secret.d.ts +33 -0
- package/dist/litellm/key-secret.d.ts.map +1 -0
- package/dist/litellm/key.d.ts +78 -0
- package/dist/litellm/key.d.ts.map +1 -0
- package/dist/litellm/mcp-server-credential.d.ts +50 -0
- package/dist/litellm/mcp-server-credential.d.ts.map +1 -0
- package/dist/litellm/mcp-server-errors.d.ts +101 -0
- package/dist/litellm/mcp-server-errors.d.ts.map +1 -0
- package/dist/litellm/mcp-server-form.d.ts +56 -0
- package/dist/litellm/mcp-server-form.d.ts.map +1 -0
- package/dist/litellm/mcp-server-locate.d.ts +26 -0
- package/dist/litellm/mcp-server-locate.d.ts.map +1 -0
- package/dist/litellm/mcp-server-operations.d.ts +63 -0
- package/dist/litellm/mcp-server-operations.d.ts.map +1 -0
- package/dist/litellm/mcp-server-shadow.d.ts +24 -0
- package/dist/litellm/mcp-server-shadow.d.ts.map +1 -0
- package/dist/litellm/mcp-server-types.d.ts +118 -0
- package/dist/litellm/mcp-server-types.d.ts.map +1 -0
- package/dist/litellm/mcp-server-url.d.ts +11 -0
- package/dist/litellm/mcp-server-url.d.ts.map +1 -0
- package/dist/litellm/mcp-server.d.ts +95 -0
- package/dist/litellm/mcp-server.d.ts.map +1 -0
- package/dist/litellm/model-claim.d.ts +24 -0
- package/dist/litellm/model-claim.d.ts.map +1 -0
- package/dist/litellm/model-credential.d.ts +12 -0
- package/dist/litellm/model-credential.d.ts.map +1 -0
- package/dist/litellm/model-errors.d.ts +119 -0
- package/dist/litellm/model-errors.d.ts.map +1 -0
- package/dist/litellm/model-form.d.ts +78 -0
- package/dist/litellm/model-form.d.ts.map +1 -0
- package/dist/litellm/model-locate.d.ts +13 -0
- package/dist/litellm/model-locate.d.ts.map +1 -0
- package/dist/litellm/model-operations.d.ts +59 -0
- package/dist/litellm/model-operations.d.ts.map +1 -0
- package/dist/litellm/model-types.d.ts +91 -0
- package/dist/litellm/model-types.d.ts.map +1 -0
- package/dist/litellm/model.d.ts +88 -0
- package/dist/litellm/model.d.ts.map +1 -0
- package/dist/litellm/policy-attachment-form.d.ts +19 -0
- package/dist/litellm/policy-attachment-form.d.ts.map +1 -0
- package/dist/litellm/policy-attachment-operations.d.ts +31 -0
- package/dist/litellm/policy-attachment-operations.d.ts.map +1 -0
- package/dist/litellm/policy-attachment-types.d.ts +48 -0
- package/dist/litellm/policy-attachment-types.d.ts.map +1 -0
- package/dist/litellm/policy-attachment.d.ts +61 -0
- package/dist/litellm/policy-attachment.d.ts.map +1 -0
- package/dist/litellm/policy-form.d.ts +22 -0
- package/dist/litellm/policy-form.d.ts.map +1 -0
- package/dist/litellm/policy-operations.d.ts +42 -0
- package/dist/litellm/policy-operations.d.ts.map +1 -0
- package/dist/litellm/policy-types.d.ts +57 -0
- package/dist/litellm/policy-types.d.ts.map +1 -0
- package/dist/litellm/policy.d.ts +65 -0
- package/dist/litellm/policy.d.ts.map +1 -0
- package/dist/litellm/providers.d.ts +1 -1
- package/dist/litellm/providers.d.ts.map +1 -1
- package/dist/litellm/registry-errors.d.ts +62 -0
- package/dist/litellm/registry-errors.d.ts.map +1 -0
- package/dist/litellm/registry-support.d.ts +42 -0
- package/dist/litellm/registry-support.d.ts.map +1 -0
- package/dist/litellm/team-form.d.ts +26 -0
- package/dist/litellm/team-form.d.ts.map +1 -0
- package/dist/litellm/team-members.d.ts +10 -0
- package/dist/litellm/team-members.d.ts.map +1 -0
- package/dist/litellm/team-operations.d.ts +38 -0
- package/dist/litellm/team-operations.d.ts.map +1 -0
- package/dist/litellm/team-permission.d.ts +35 -0
- package/dist/litellm/team-permission.d.ts.map +1 -0
- package/dist/litellm/team-types.d.ts +101 -0
- package/dist/litellm/team-types.d.ts.map +1 -0
- package/dist/litellm/team.d.ts +67 -0
- package/dist/litellm/team.d.ts.map +1 -0
- package/dist/litellm/tool-policy-form.d.ts +21 -0
- package/dist/litellm/tool-policy-form.d.ts.map +1 -0
- package/dist/litellm/tool-policy-operations.d.ts +30 -0
- package/dist/litellm/tool-policy-operations.d.ts.map +1 -0
- package/dist/litellm/tool-policy-types.d.ts +46 -0
- package/dist/litellm/tool-policy-types.d.ts.map +1 -0
- package/dist/litellm/tool-policy.d.ts +62 -0
- package/dist/litellm/tool-policy.d.ts.map +1 -0
- package/dist/litellm/toolset-form.d.ts +20 -0
- package/dist/litellm/toolset-form.d.ts.map +1 -0
- package/dist/litellm/toolset-operations.d.ts +39 -0
- package/dist/litellm/toolset-operations.d.ts.map +1 -0
- package/dist/litellm/toolset-types.d.ts +46 -0
- package/dist/litellm/toolset-types.d.ts.map +1 -0
- package/dist/litellm/toolset.d.ts +55 -0
- package/dist/litellm/toolset.d.ts.map +1 -0
- package/dist/openbao/index.js +35 -1
- package/dist/openbao/index.js.map +3 -3
- package/dist/ownership/rows.d.ts +11 -0
- package/dist/ownership/rows.d.ts.map +1 -1
- package/dist/postgres/connection.d.ts +14 -2
- package/dist/postgres/connection.d.ts.map +1 -1
- package/dist/postgres/database-sql.d.ts +27 -8
- package/dist/postgres/database-sql.d.ts.map +1 -1
- package/dist/postgres/fake-grants-parse.d.ts +37 -0
- package/dist/postgres/fake-grants-parse.d.ts.map +1 -0
- package/dist/postgres/fake-grants-read.d.ts +61 -0
- package/dist/postgres/fake-grants-read.d.ts.map +1 -0
- package/dist/postgres/fake-grants-sql.d.ts +46 -0
- package/dist/postgres/fake-grants-sql.d.ts.map +1 -0
- package/dist/postgres/fake-grants-write.d.ts +29 -0
- package/dist/postgres/fake-grants-write.d.ts.map +1 -0
- package/dist/postgres/fake-role-sql.d.ts +41 -0
- package/dist/postgres/fake-role-sql.d.ts.map +1 -0
- package/dist/postgres/fake-schema-sql.d.ts +42 -0
- package/dist/postgres/fake-schema-sql.d.ts.map +1 -0
- package/dist/postgres/fake-sql-parse.d.ts +23 -0
- package/dist/postgres/fake-sql-parse.d.ts.map +1 -0
- package/dist/postgres/fake-sql-quote.d.ts +12 -0
- package/dist/postgres/fake-sql-quote.d.ts.map +1 -0
- package/dist/postgres/fake-sql.d.ts +42 -0
- package/dist/postgres/fake-sql.d.ts.map +1 -1
- package/dist/postgres/grants-attrs.d.ts +115 -0
- package/dist/postgres/grants-attrs.d.ts.map +1 -0
- package/dist/postgres/grants-declare.d.ts +88 -0
- package/dist/postgres/grants-declare.d.ts.map +1 -0
- package/dist/postgres/grants-diff.d.ts +25 -0
- package/dist/postgres/grants-diff.d.ts.map +1 -0
- package/dist/postgres/grants-errors.d.ts +137 -0
- package/dist/postgres/grants-errors.d.ts.map +1 -0
- package/dist/postgres/grants-existence.d.ts +14 -0
- package/dist/postgres/grants-existence.d.ts.map +1 -0
- package/dist/postgres/grants-ops.d.ts +65 -0
- package/dist/postgres/grants-ops.d.ts.map +1 -0
- package/dist/postgres/grants-ownership.d.ts +18 -0
- package/dist/postgres/grants-ownership.d.ts.map +1 -0
- package/dist/postgres/grants-plan-restore.d.ts +22 -0
- package/dist/postgres/grants-plan-restore.d.ts.map +1 -0
- package/dist/postgres/grants-plan.d.ts +65 -0
- package/dist/postgres/grants-plan.d.ts.map +1 -0
- package/dist/postgres/grants-read-defaults.d.ts +17 -0
- package/dist/postgres/grants-read-defaults.d.ts.map +1 -0
- package/dist/postgres/grants-read.d.ts +77 -0
- package/dist/postgres/grants-read.d.ts.map +1 -0
- package/dist/postgres/grants-refuse.d.ts +25 -0
- package/dist/postgres/grants-refuse.d.ts.map +1 -0
- package/dist/postgres/grants-sql.d.ts +42 -0
- package/dist/postgres/grants-sql.d.ts.map +1 -0
- package/dist/postgres/grants-words.d.ts +29 -0
- package/dist/postgres/grants-words.d.ts.map +1 -0
- package/dist/postgres/grants.d.ts +37 -0
- package/dist/postgres/grants.d.ts.map +1 -0
- package/dist/postgres/index.d.ts +31 -6
- package/dist/postgres/index.d.ts.map +1 -1
- package/dist/postgres/index.js +1998 -37
- package/dist/postgres/index.js.map +46 -7
- package/dist/postgres/installed-classifier.d.ts +13 -0
- package/dist/postgres/installed-classifier.d.ts.map +1 -0
- package/dist/postgres/providers.d.ts +16 -8
- package/dist/postgres/providers.d.ts.map +1 -1
- package/dist/postgres/psql-executor.d.ts +4 -0
- package/dist/postgres/psql-executor.d.ts.map +1 -1
- package/dist/postgres/role-attrs.d.ts +112 -0
- package/dist/postgres/role-attrs.d.ts.map +1 -0
- package/dist/postgres/role-diff.d.ts +22 -0
- package/dist/postgres/role-diff.d.ts.map +1 -0
- package/dist/postgres/role-errors.d.ts +132 -0
- package/dist/postgres/role-errors.d.ts.map +1 -0
- package/dist/postgres/role-membership-sql.d.ts +78 -0
- package/dist/postgres/role-membership-sql.d.ts.map +1 -0
- package/dist/postgres/role-provider.d.ts +11 -0
- package/dist/postgres/role-provider.d.ts.map +1 -0
- package/dist/postgres/role-scram.d.ts +8 -0
- package/dist/postgres/role-scram.d.ts.map +1 -0
- package/dist/postgres/role-secrets.d.ts +38 -0
- package/dist/postgres/role-secrets.d.ts.map +1 -0
- package/dist/postgres/role-sql.d.ts +68 -0
- package/dist/postgres/role-sql.d.ts.map +1 -0
- package/dist/postgres/role.d.ts +57 -0
- package/dist/postgres/role.d.ts.map +1 -0
- package/dist/postgres/schema-assert.d.ts +50 -0
- package/dist/postgres/schema-assert.d.ts.map +1 -0
- package/dist/postgres/schema-attrs.d.ts +51 -0
- package/dist/postgres/schema-attrs.d.ts.map +1 -0
- package/dist/postgres/schema-delete.d.ts +28 -0
- package/dist/postgres/schema-delete.d.ts.map +1 -0
- package/dist/postgres/schema-diff.d.ts +14 -0
- package/dist/postgres/schema-diff.d.ts.map +1 -0
- package/dist/postgres/schema-errors.d.ts +139 -0
- package/dist/postgres/schema-errors.d.ts.map +1 -0
- package/dist/postgres/schema-identity.d.ts +8 -0
- package/dist/postgres/schema-identity.d.ts.map +1 -0
- package/dist/postgres/schema-reconcile.d.ts +12 -0
- package/dist/postgres/schema-reconcile.d.ts.map +1 -0
- package/dist/postgres/schema-sql.d.ts +45 -0
- package/dist/postgres/schema-sql.d.ts.map +1 -0
- package/dist/postgres/schema-test-kit.d.ts +67 -0
- package/dist/postgres/schema-test-kit.d.ts.map +1 -0
- package/dist/postgres/schema.d.ts +54 -0
- package/dist/postgres/schema.d.ts.map +1 -0
- package/dist/proxmox/index.js +35 -1
- package/dist/proxmox/index.js.map +3 -3
- package/dist/release/fake-engine.d.ts +10 -0
- package/dist/release/fake-engine.d.ts.map +1 -1
- package/dist/release/index.js +35 -1
- package/dist/release/index.js.map +3 -3
- package/dist/valkey/acl-attrs.d.ts +94 -0
- package/dist/valkey/acl-attrs.d.ts.map +1 -0
- package/dist/valkey/acl-compare.d.ts +28 -0
- package/dist/valkey/acl-compare.d.ts.map +1 -0
- package/dist/valkey/acl-connection.d.ts +6 -0
- package/dist/valkey/acl-connection.d.ts.map +1 -0
- package/dist/valkey/acl-form.d.ts +62 -0
- package/dist/valkey/acl-form.d.ts.map +1 -0
- package/dist/valkey/acl-managed.d.ts +4 -0
- package/dist/valkey/acl-managed.d.ts.map +1 -0
- package/dist/valkey/acl-ops.d.ts +37 -0
- package/dist/valkey/acl-ops.d.ts.map +1 -0
- package/dist/valkey/acl-profiles.d.ts +53 -0
- package/dist/valkey/acl-profiles.d.ts.map +1 -0
- package/dist/valkey/acl-reconcile.d.ts +26 -0
- package/dist/valkey/acl-reconcile.d.ts.map +1 -0
- package/dist/valkey/acl.d.ts +24 -0
- package/dist/valkey/acl.d.ts.map +1 -0
- package/dist/valkey/connection-service.d.ts +22 -0
- package/dist/valkey/connection-service.d.ts.map +1 -0
- package/dist/valkey/connection.d.ts +59 -0
- package/dist/valkey/connection.d.ts.map +1 -0
- package/dist/valkey/errors.d.ts +165 -0
- package/dist/valkey/errors.d.ts.map +1 -0
- package/dist/valkey/fake-acl.d.ts +29 -0
- package/dist/valkey/fake-acl.d.ts.map +1 -0
- package/dist/valkey/fake-valkey.d.ts +35 -0
- package/dist/valkey/fake-valkey.d.ts.map +1 -0
- package/dist/valkey/handler-test-fixture.d.ts +20 -0
- package/dist/valkey/handler-test-fixture.d.ts.map +1 -0
- package/dist/valkey/index.d.ts +18 -0
- package/dist/valkey/index.d.ts.map +1 -0
- package/dist/valkey/index.js +964 -0
- package/dist/valkey/index.js.map +27 -0
- package/dist/valkey/instance-attrs.d.ts +46 -0
- package/dist/valkey/instance-attrs.d.ts.map +1 -0
- package/dist/valkey/instance-form.d.ts +22 -0
- package/dist/valkey/instance-form.d.ts.map +1 -0
- package/dist/valkey/instance.d.ts +48 -0
- package/dist/valkey/instance.d.ts.map +1 -0
- package/dist/valkey/provider-socket-fixture.d.ts +8 -0
- package/dist/valkey/provider-socket-fixture.d.ts.map +1 -0
- package/dist/valkey/providers.d.ts +16 -0
- package/dist/valkey/providers.d.ts.map +1 -0
- package/dist/valkey/resp.d.ts +38 -0
- package/dist/valkey/resp.d.ts.map +1 -0
- package/dist/valkey/transport-error.d.ts +36 -0
- package/dist/valkey/transport-error.d.ts.map +1 -0
- package/dist/valkey/transport.d.ts +50 -0
- package/dist/valkey/transport.d.ts.map +1 -0
- package/docs/litellm-access-group.md +105 -0
- package/docs/litellm-credential.md +60 -0
- package/docs/litellm-key-metadata.md +56 -0
- package/docs/litellm-key.md +194 -0
- package/docs/litellm-mcp.md +187 -0
- package/docs/litellm-model.md +111 -0
- package/docs/litellm-policy-attachment.md +86 -0
- package/docs/litellm-policy.md +103 -0
- package/docs/litellm-team.md +117 -0
- package/docs/litellm-tool-policy.md +90 -0
- package/docs/litellm-toolset.md +92 -0
- package/docs/litellm.md +16 -9
- package/docs/peers.md +15 -6
- package/docs/postgres-grants-example.md +55 -0
- package/docs/postgres-grants.md +157 -0
- package/docs/postgres-role.md +126 -0
- package/docs/postgres-schema.md +139 -0
- package/docs/postgres.md +20 -3
- package/docs/valkey-transport.md +29 -0
- package/docs/valkey.md +187 -0
- package/package.json +6 -2
- package/src/ceph/ceph-argv.test.ts +1 -1
- package/src/launchd/sudo-allowlist.test.ts +2 -2
- package/src/linux/sudo-allowlist.test.ts +3 -3
- package/src/linux/sudo-runner.test.ts +2 -2
- package/src/litellm/access-group-form.ts +105 -0
- package/src/litellm/access-group-operations.ts +92 -0
- package/src/litellm/access-group-types.ts +49 -0
- package/src/litellm/access-group.test.ts +197 -0
- package/src/litellm/access-group.ts +151 -0
- package/src/litellm/credential-errors.ts +183 -0
- package/src/litellm/credential-form.test.ts +151 -0
- package/src/litellm/credential-form.ts +211 -0
- package/src/litellm/credential-guards.test.ts +241 -0
- package/src/litellm/credential-operations.ts +208 -0
- package/src/litellm/credential-recovery.test.ts +93 -0
- package/src/litellm/credential-rewrite.test.ts +164 -0
- package/src/litellm/credential-rewrite.ts +50 -0
- package/src/litellm/credential-types.ts +117 -0
- package/src/litellm/credential-values.ts +94 -0
- package/src/litellm/credential.test.ts +218 -0
- package/src/litellm/credential.ts +227 -0
- package/src/litellm/fake-access-group-litellm.ts +127 -0
- package/src/litellm/fake-credential-litellm.ts +216 -0
- package/src/litellm/fake-keys.ts +190 -0
- package/src/litellm/fake-litellm.ts +19 -9
- package/src/litellm/fake-mcp-litellm.ts +214 -0
- package/src/litellm/fake-model-litellm-post-update.ts +103 -0
- package/src/litellm/fake-model-litellm.ts +233 -0
- package/src/litellm/fake-policy-attachment-litellm.ts +97 -0
- package/src/litellm/fake-policy-litellm.ts +186 -0
- package/src/litellm/fake-registry-base.ts +69 -0
- package/src/litellm/fake-stack.ts +17 -1
- package/src/litellm/fake-team-litellm.ts +229 -0
- package/src/litellm/fake-tool-litellm.ts +88 -0
- package/src/litellm/fake-toolset-litellm.ts +110 -0
- package/src/litellm/index.ts +140 -0
- package/src/litellm/key-delete.test.ts +212 -0
- package/src/litellm/key-errors.ts +241 -0
- package/src/litellm/key-form.ts +200 -0
- package/src/litellm/key-harness.ts +121 -0
- package/src/litellm/key-metadata-errors.ts +49 -0
- package/src/litellm/key-metadata.test.ts +156 -0
- package/src/litellm/key-metadata.ts +74 -0
- package/src/litellm/key-operations.ts +184 -0
- package/src/litellm/key-rotation.test.ts +110 -0
- package/src/litellm/key-scope.test.ts +135 -0
- package/src/litellm/key-secret.test.ts +248 -0
- package/src/litellm/key-secret.ts +137 -0
- package/src/litellm/key-transport.test.ts +166 -0
- package/src/litellm/key.test.ts +231 -0
- package/src/litellm/key.ts +213 -0
- package/src/litellm/mcp-server-adopt.test.ts +86 -0
- package/src/litellm/mcp-server-alias.test.ts +184 -0
- package/src/litellm/mcp-server-credential.ts +85 -0
- package/src/litellm/mcp-server-errors.ts +141 -0
- package/src/litellm/mcp-server-exports.test.ts +15 -0
- package/src/litellm/mcp-server-form.test.ts +241 -0
- package/src/litellm/mcp-server-form.ts +233 -0
- package/src/litellm/mcp-server-handlers.test.ts +150 -0
- package/src/litellm/mcp-server-locate.ts +66 -0
- package/src/litellm/mcp-server-operations.ts +111 -0
- package/src/litellm/mcp-server-registry.test.ts +146 -0
- package/src/litellm/mcp-server-retype.test.ts +114 -0
- package/src/litellm/mcp-server-shadow.test.ts +91 -0
- package/src/litellm/mcp-server-shadow.ts +45 -0
- package/src/litellm/mcp-server-tools.test.ts +85 -0
- package/src/litellm/mcp-server-types.ts +143 -0
- package/src/litellm/mcp-server-url.ts +66 -0
- package/src/litellm/mcp-server.test.ts +213 -0
- package/src/litellm/mcp-server.ts +225 -0
- package/src/litellm/model-claim.ts +69 -0
- package/src/litellm/model-credential.ts +45 -0
- package/src/litellm/model-errors.ts +168 -0
- package/src/litellm/model-foreign-row.test.ts +173 -0
- package/src/litellm/model-form.test.ts +228 -0
- package/src/litellm/model-form.ts +223 -0
- package/src/litellm/model-locate.test.ts +128 -0
- package/src/litellm/model-locate.ts +67 -0
- package/src/litellm/model-operations.test.ts +93 -0
- package/src/litellm/model-operations.ts +146 -0
- package/src/litellm/model-params-preserve.test.ts +119 -0
- package/src/litellm/model-recovery.test.ts +57 -0
- package/src/litellm/model-types.ts +112 -0
- package/src/litellm/model.test.ts +222 -0
- package/src/litellm/model.ts +237 -0
- package/src/litellm/policy-attachment-form.ts +103 -0
- package/src/litellm/policy-attachment-operations.ts +74 -0
- package/src/litellm/policy-attachment-types.ts +48 -0
- package/src/litellm/policy-attachment.test.ts +198 -0
- package/src/litellm/policy-attachment.ts +178 -0
- package/src/litellm/policy-form.ts +122 -0
- package/src/litellm/policy-operations.ts +112 -0
- package/src/litellm/policy-types.ts +58 -0
- package/src/litellm/policy.test.ts +214 -0
- package/src/litellm/policy.ts +146 -0
- package/src/litellm/providers.ts +24 -3
- package/src/litellm/registry-errors.ts +88 -0
- package/src/litellm/registry-exports.test.ts +66 -0
- package/src/litellm/registry-support.ts +109 -0
- package/src/litellm/team-form.ts +140 -0
- package/src/litellm/team-members.test.ts +150 -0
- package/src/litellm/team-members.ts +58 -0
- package/src/litellm/team-operations.ts +90 -0
- package/src/litellm/team-permission.ts +159 -0
- package/src/litellm/team-read.test.ts +89 -0
- package/src/litellm/team-types.ts +106 -0
- package/src/litellm/team.test.ts +206 -0
- package/src/litellm/team.ts +159 -0
- package/src/litellm/tool-policy-form.ts +94 -0
- package/src/litellm/tool-policy-operations.ts +58 -0
- package/src/litellm/tool-policy-types.ts +48 -0
- package/src/litellm/tool-policy.test.ts +172 -0
- package/src/litellm/tool-policy.ts +125 -0
- package/src/litellm/toolset-form.ts +104 -0
- package/src/litellm/toolset-operations.ts +90 -0
- package/src/litellm/toolset-types.ts +47 -0
- package/src/litellm/toolset.test.ts +198 -0
- package/src/litellm/toolset.ts +151 -0
- package/src/ownership/ownership.test.ts +33 -1
- package/src/ownership/rows.ts +53 -0
- package/src/postgres/connection.ts +42 -7
- package/src/postgres/database-bigint-serialization.test.ts +1 -0
- package/src/postgres/database-refusals.test.ts +2 -2
- package/src/postgres/database-sql.ts +36 -9
- package/src/postgres/fake-grants-parse.ts +189 -0
- package/src/postgres/fake-grants-read.ts +250 -0
- package/src/postgres/fake-grants-sql.ts +207 -0
- package/src/postgres/fake-grants-write.ts +90 -0
- package/src/postgres/fake-role-sql.ts +240 -0
- package/src/postgres/fake-schema-semantics.test.ts +36 -0
- package/src/postgres/fake-schema-sql.ts +146 -0
- package/src/postgres/fake-sql-parse.ts +86 -0
- package/src/postgres/fake-sql-quote.ts +36 -0
- package/src/postgres/fake-sql.ts +166 -46
- package/src/postgres/fixtures/pg-REL_18_6-acl.h.txt +147 -0
- package/src/postgres/fixtures/pg-REL_18_6-aclexplode-func.txt +31 -0
- package/src/postgres/fixtures/pg-REL_18_6-alter-default-privileges-synopsis.txt +82 -0
- package/src/postgres/fixtures/pg-REL_18_6-alter_role.sgml.txt +44 -0
- package/src/postgres/fixtures/pg-REL_18_6-create_role.sgml.txt +26 -0
- package/src/postgres/fixtures/pg-REL_18_6-grant-synopsis.txt +95 -0
- package/src/postgres/fixtures/pg-REL_18_6-pg_attribute.h.txt +24 -0
- package/src/postgres/fixtures/pg-REL_18_6-pg_auth_members.h.txt +62 -0
- package/src/postgres/fixtures/pg-REL_18_6-pg_authid.h.txt +70 -0
- package/src/postgres/fixtures/pg-REL_18_6-pg_class.h.txt +33 -0
- package/src/postgres/fixtures/pg-REL_18_6-pg_default_acl.h.txt +81 -0
- package/src/postgres/fixtures/pg-REL_18_6-pg_namespace.h.txt +28 -0
- package/src/postgres/fixtures/pg-REL_18_6-revoke-synopsis.txt +124 -0
- package/src/postgres/grants-acl.test.ts +61 -0
- package/src/postgres/grants-attrs.ts +185 -0
- package/src/postgres/grants-database.test.ts +158 -0
- package/src/postgres/grants-declare.ts +172 -0
- package/src/postgres/grants-delete-proof.test.ts +154 -0
- package/src/postgres/grants-diff.ts +101 -0
- package/src/postgres/grants-errors.ts +215 -0
- package/src/postgres/grants-existence-guards.test.ts +113 -0
- package/src/postgres/grants-existence.ts +101 -0
- package/src/postgres/grants-lifecycle-guards.test.ts +208 -0
- package/src/postgres/grants-lifecycle-repair.test.ts +208 -0
- package/src/postgres/grants-lifecycle.test.ts +181 -0
- package/src/postgres/grants-ops.ts +229 -0
- package/src/postgres/grants-owner-revoke.test.ts +121 -0
- package/src/postgres/grants-ownership.ts +49 -0
- package/src/postgres/grants-plan-collateral.test.ts +202 -0
- package/src/postgres/grants-plan-diff.test.ts +204 -0
- package/src/postgres/grants-plan-restore.ts +43 -0
- package/src/postgres/grants-plan-revocations.test.ts +69 -0
- package/src/postgres/grants-plan.test.ts +148 -0
- package/src/postgres/grants-plan.ts +245 -0
- package/src/postgres/grants-provenance.test.ts +134 -0
- package/src/postgres/grants-psql-lifecycle.test.ts +105 -0
- package/src/postgres/grants-read-absent.test.ts +91 -0
- package/src/postgres/grants-read-defaults.ts +51 -0
- package/src/postgres/grants-read.ts +250 -0
- package/src/postgres/grants-refusals.test.ts +136 -0
- package/src/postgres/grants-refuse.ts +119 -0
- package/src/postgres/grants-relkind-wire.test.ts +111 -0
- package/src/postgres/grants-repair-plan.test.ts +201 -0
- package/src/postgres/grants-sql.test.ts +128 -0
- package/src/postgres/grants-sql.ts +144 -0
- package/src/postgres/grants-words.ts +34 -0
- package/src/postgres/grants.ts +110 -0
- package/src/postgres/index.ts +129 -5
- package/src/postgres/installed-classifier.ts +36 -0
- package/src/postgres/providers.ts +33 -9
- package/src/postgres/psql-executor-escaping.test.ts +67 -0
- package/src/postgres/psql-executor.test.ts +63 -2
- package/src/postgres/psql-executor.ts +74 -34
- package/src/postgres/role-adopt.test.ts +98 -0
- package/src/postgres/role-attrs.ts +137 -0
- package/src/postgres/role-diff.ts +93 -0
- package/src/postgres/role-errors.ts +206 -0
- package/src/postgres/role-guard.test.ts +60 -0
- package/src/postgres/role-identity.test.ts +197 -0
- package/src/postgres/role-lifecycle.test.ts +228 -0
- package/src/postgres/role-membership-options.test.ts +73 -0
- package/src/postgres/role-membership-repair.test.ts +153 -0
- package/src/postgres/role-membership-sql.ts +223 -0
- package/src/postgres/role-password-ascii.test.ts +53 -0
- package/src/postgres/role-password-transports.test.ts +123 -0
- package/src/postgres/role-password.test.ts +158 -0
- package/src/postgres/role-privilege.test.ts +107 -0
- package/src/postgres/role-provenance.test.ts +200 -0
- package/src/postgres/role-provider.ts +105 -0
- package/src/postgres/role-refusals.test.ts +225 -0
- package/src/postgres/role-scram.test.ts +38 -0
- package/src/postgres/role-scram.ts +40 -0
- package/src/postgres/role-secrets.ts +61 -0
- package/src/postgres/role-sql.ts +213 -0
- package/src/postgres/role-valid-until.test.ts +68 -0
- package/src/postgres/role.ts +226 -0
- package/src/postgres/schema-assert.ts +139 -0
- package/src/postgres/schema-attrs.ts +71 -0
- package/src/postgres/schema-delete-proofs.test.ts +192 -0
- package/src/postgres/schema-delete.ts +96 -0
- package/src/postgres/schema-diff.ts +44 -0
- package/src/postgres/schema-drop-2bp01.test.ts +146 -0
- package/src/postgres/schema-errors.ts +209 -0
- package/src/postgres/schema-handlers.test.ts +193 -0
- package/src/postgres/schema-identity.ts +39 -0
- package/src/postgres/schema-lifecycle.test.ts +246 -0
- package/src/postgres/schema-race.test.ts +70 -0
- package/src/postgres/schema-reconcile.ts +100 -0
- package/src/postgres/schema-refusals.test.ts +144 -0
- package/src/postgres/schema-socket.test.ts +162 -0
- package/src/postgres/schema-sql.ts +130 -0
- package/src/postgres/schema-takeover.test.ts +149 -0
- package/src/postgres/schema-test-kit.ts +85 -0
- package/src/postgres/schema.ts +130 -0
- package/src/release/binary-alias.test.ts +3 -3
- package/src/release/binary-claims.test.ts +3 -3
- package/src/release/binary-directory-umask.test.ts +65 -0
- package/src/release/fake-engine.ts +15 -0
- package/src/valkey/acl-attrs.ts +98 -0
- package/src/valkey/acl-compare.ts +57 -0
- package/src/valkey/acl-connection.ts +13 -0
- package/src/valkey/acl-delete.test.ts +127 -0
- package/src/valkey/acl-form.ts +239 -0
- package/src/valkey/acl-managed.ts +13 -0
- package/src/valkey/acl-ops.ts +122 -0
- package/src/valkey/acl-ownership.test.ts +118 -0
- package/src/valkey/acl-prefix.test.ts +38 -0
- package/src/valkey/acl-profiles.ts +81 -0
- package/src/valkey/acl-reconcile.ts +180 -0
- package/src/valkey/acl-reserved.test.ts +69 -0
- package/src/valkey/acl-revocation.test.ts +94 -0
- package/src/valkey/acl-seat-prefix.test.ts +111 -0
- package/src/valkey/acl-validation.test.ts +54 -0
- package/src/valkey/acl.test.ts +215 -0
- package/src/valkey/acl.ts +169 -0
- package/src/valkey/connection-service.ts +46 -0
- package/src/valkey/connection.ts +180 -0
- package/src/valkey/errors.ts +239 -0
- package/src/valkey/fake-acl.ts +128 -0
- package/src/valkey/fake-valkey.ts +224 -0
- package/src/valkey/handler-test-fixture.ts +43 -0
- package/src/valkey/handlers.test.ts +205 -0
- package/src/valkey/index.ts +54 -0
- package/src/valkey/instance-attrs.ts +48 -0
- package/src/valkey/instance-form.ts +104 -0
- package/src/valkey/instance.test.ts +108 -0
- package/src/valkey/instance.ts +160 -0
- package/src/valkey/provider-socket-fixture.ts +71 -0
- package/src/valkey/providers.test.ts +129 -0
- package/src/valkey/providers.ts +21 -0
- package/src/valkey/resp.ts +212 -0
- package/src/valkey/transport-bounds.test.ts +150 -0
- package/src/valkey/transport-error.ts +37 -0
- package/src/valkey/transport.test.ts +158 -0
- package/src/valkey/transport.ts +109 -0
- package/src/valkey/upstream-bounds.test.ts +14 -0
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `LiteLLM.MCPServer`: an update must not rewrite the alias, and the names LiteLLM refuses are
|
|
3
|
+
* refused first.
|
|
4
|
+
*
|
|
5
|
+
* ⛔ MEASURED 2026-09-29 by running the live 1.103.0 container's own `validate_and_normalize_mcp_server_payload`
|
|
6
|
+
* and `_prepare_mcp_server_data` on edit bodies (no database, no network): a body with a `server_name`
|
|
7
|
+
* and no `alias` comes out with `alias = normalize(server_name)` and the alias IS written; a body with
|
|
8
|
+
* neither sends none. The alias is the tool prefix, so an update that sent the name alone renamed
|
|
9
|
+
* every tool of a row with a custom alias, and the plan showed `adopted` or an update with no alias.
|
|
10
|
+
* The fake models exactly that rule (`fake-mcp-litellm.ts`), and the first test pins the fake to it.
|
|
11
|
+
* ★ EVERY VALUE IS `FAKE-*` or an RFC 2606 host.
|
|
12
|
+
*/
|
|
13
|
+
import { describe, expect, test } from 'bun:test';
|
|
14
|
+
import * as Effect from 'effect/Effect';
|
|
15
|
+
import { FAKE_BASE } from './fake-litellm.ts';
|
|
16
|
+
import { type FakeMcpLitellm, serverRow, startFakeMcpLitellm } from './fake-mcp-litellm.ts';
|
|
17
|
+
import { fakeStack } from './fake-stack.ts';
|
|
18
|
+
import { LiteLLMMCPServer } from './mcp-server.ts';
|
|
19
|
+
import { createBody, firstProblem, updateBody } from './mcp-server-form.ts';
|
|
20
|
+
import type { McpServerAttributes, McpServerProps } from './mcp-server-types.ts';
|
|
21
|
+
|
|
22
|
+
const KEY = 'sk-test-master';
|
|
23
|
+
|
|
24
|
+
const stack = (fake: FakeMcpLitellm) => fakeStack({ apiKey: KEY, baseUrl: FAKE_BASE }, fake.fetch);
|
|
25
|
+
const declare = (props: McpServerProps) => Effect.asVoid(LiteLLMMCPServer('Web', props));
|
|
26
|
+
|
|
27
|
+
const base: McpServerProps = {
|
|
28
|
+
authType: 'none',
|
|
29
|
+
serverName: 'FAKE_web',
|
|
30
|
+
transport: 'http',
|
|
31
|
+
url: 'https://web.example.com/mcp',
|
|
32
|
+
};
|
|
33
|
+
/** A row a person aliased: its tools are `search-*`, and it is open to every key. */
|
|
34
|
+
const aliased = serverRow({
|
|
35
|
+
alias: 'search',
|
|
36
|
+
allow_all_keys: true,
|
|
37
|
+
server_id: 'FAKE-web-id',
|
|
38
|
+
server_name: 'FAKE_web',
|
|
39
|
+
transport: 'http',
|
|
40
|
+
url: 'https://web.example.com/mcp',
|
|
41
|
+
});
|
|
42
|
+
const live = (over: Partial<McpServerAttributes> = {}): McpServerAttributes => ({
|
|
43
|
+
alias: 'search',
|
|
44
|
+
allowAllKeys: false,
|
|
45
|
+
allowedTools: [],
|
|
46
|
+
authType: 'none',
|
|
47
|
+
credentialSeal: '',
|
|
48
|
+
description: null,
|
|
49
|
+
mcpAccessGroups: [],
|
|
50
|
+
serverId: 'FAKE-web-id',
|
|
51
|
+
serverName: 'FAKE_web',
|
|
52
|
+
transport: 'http',
|
|
53
|
+
url: 'https://web.example.com/mcp',
|
|
54
|
+
...over,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const put = (fake: FakeMcpLitellm, body: Record<string, unknown>) =>
|
|
58
|
+
fake.fetch(`${FAKE_BASE}/v1/mcp/server`, {
|
|
59
|
+
body: JSON.stringify(body),
|
|
60
|
+
headers: { authorization: `Bearer ${KEY}`, 'content-type': 'application/json' },
|
|
61
|
+
method: 'PUT',
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('the fake models the measured rule: a name with no alias rewrites the alias, neither leaves it', async () => {
|
|
65
|
+
const fake = startFakeMcpLitellm({ masterKey: KEY, seed: [aliased] });
|
|
66
|
+
await put(fake, { allow_all_keys: false, server_id: 'FAKE-web-id' });
|
|
67
|
+
expect(fake.servers()[0]).toMatchObject({ alias: 'search' });
|
|
68
|
+
await put(fake, { server_id: 'FAKE-web-id', server_name: 'FAKE_web' });
|
|
69
|
+
expect(fake.servers()[0]).toMatchObject({ alias: 'FAKE_web' });
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
describe('the update body', () => {
|
|
73
|
+
test('sends no server_name and no alias when the name is unchanged and no alias is declared', () => {
|
|
74
|
+
const body = updateBody(base, 'FAKE-web-id', undefined, false, live());
|
|
75
|
+
expect(body).not.toHaveProperty('server_name');
|
|
76
|
+
expect(body).not.toHaveProperty('alias');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test('a rename re-sends the live alias, so the tool prefix survives', () => {
|
|
80
|
+
const body = updateBody(
|
|
81
|
+
{ ...base, serverName: 'FAKE_web2' },
|
|
82
|
+
'FAKE-web-id',
|
|
83
|
+
undefined,
|
|
84
|
+
false,
|
|
85
|
+
live(),
|
|
86
|
+
);
|
|
87
|
+
expect(body).toMatchObject({ alias: 'search', server_name: 'FAKE_web2' });
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test('a rename of a row with no alias sends the name alone: LiteLLM then defaults the alias', () => {
|
|
91
|
+
const body = updateBody(
|
|
92
|
+
{ ...base, serverName: 'FAKE_web2' },
|
|
93
|
+
'FAKE-web-id',
|
|
94
|
+
undefined,
|
|
95
|
+
false,
|
|
96
|
+
live({ alias: null }),
|
|
97
|
+
);
|
|
98
|
+
expect(body).toHaveProperty('server_name', 'FAKE_web2');
|
|
99
|
+
expect(body).not.toHaveProperty('alias');
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test('a declared alias is sent, and wins over the live one', () => {
|
|
103
|
+
const body = updateBody({ ...base, alias: 'docs' }, 'FAKE-web-id', undefined, false, live());
|
|
104
|
+
expect(body).toHaveProperty('alias', 'docs');
|
|
105
|
+
expect(body).not.toHaveProperty('server_name');
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test('a create sends the name, and an alias only when declared', () => {
|
|
109
|
+
expect(createBody(base, 'FAKE-web-id', undefined)).not.toHaveProperty('alias');
|
|
110
|
+
expect(createBody({ ...base, alias: 'docs' }, 'FAKE-web-id', undefined)).toHaveProperty(
|
|
111
|
+
'alias',
|
|
112
|
+
'docs',
|
|
113
|
+
);
|
|
114
|
+
expect(createBody(base, 'FAKE-web-id', undefined)).toHaveProperty('server_name', 'FAKE_web');
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
describe('through the engine', () => {
|
|
119
|
+
test('adopting an aliased row and correcting its grant leaves the alias alone', async () => {
|
|
120
|
+
const fake = startFakeMcpLitellm({ masterKey: KEY, seed: [aliased] });
|
|
121
|
+
// ⚠️ the plan says `adopted` even though reconcile writes (allow_all_keys true -> false)
|
|
122
|
+
expect(await stack(fake).deploy(declare(base), { adopt: true })).toEqual({ Web: 'adopted' });
|
|
123
|
+
const sent = fake.bodies();
|
|
124
|
+
expect(sent).toHaveLength(1);
|
|
125
|
+
expect(sent[0]).not.toHaveProperty('alias');
|
|
126
|
+
expect(sent[0]).not.toHaveProperty('server_name');
|
|
127
|
+
expect(fake.servers()[0]).toMatchObject({ alias: 'search', allow_all_keys: false });
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test('a rename of an owned row keeps its custom alias, and the next deploy is a no-op', async () => {
|
|
131
|
+
const fake = startFakeMcpLitellm({ masterKey: KEY, seed: [aliased] });
|
|
132
|
+
const engine = stack(fake);
|
|
133
|
+
const pinned: McpServerProps = { ...base, serverId: 'FAKE-web-id' };
|
|
134
|
+
await engine.deploy(declare(pinned), { adopt: true });
|
|
135
|
+
expect(await engine.deploy(declare({ ...pinned, serverName: 'FAKE_web2' }))).toEqual({
|
|
136
|
+
Web: 'update',
|
|
137
|
+
});
|
|
138
|
+
expect(fake.servers()[0]).toMatchObject({ alias: 'search', server_name: 'FAKE_web2' });
|
|
139
|
+
expect(await engine.deploy(declare({ ...pinned, serverName: 'FAKE_web2' }))).toEqual({
|
|
140
|
+
Web: 'noop',
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
describe('the names LiteLLM would refuse or rewrite', () => {
|
|
146
|
+
test.each([
|
|
147
|
+
['a hyphen in serverName', { ...base, serverName: 'FAKE-web' }, 'serverName'],
|
|
148
|
+
['a hyphen in the alias', { ...base, alias: 'my-search' }, 'alias'],
|
|
149
|
+
['a space in the alias', { ...base, alias: 'my search' }, 'space'],
|
|
150
|
+
['a blank alias', { ...base, alias: ' ' }, 'alias'],
|
|
151
|
+
['a slash in serverName', { ...base, serverName: 'FAKE/web' }, 'serverName'],
|
|
152
|
+
['a colon in serverName', { ...base, serverName: 'FAKE:web' }, 'serverName'],
|
|
153
|
+
['an at-sign in serverName', { ...base, serverName: 'FAKE@web' }, 'serverName'],
|
|
154
|
+
['a slash in the alias', { ...base, alias: 'my/search' }, 'alias'],
|
|
155
|
+
['a colon in the alias', { ...base, alias: 'my:search' }, 'alias'],
|
|
156
|
+
['an at-sign in the alias', { ...base, alias: 'my@search' }, 'alias'],
|
|
157
|
+
])('%s is refused before any request', (_label, props, mention) => {
|
|
158
|
+
expect(firstProblem(props)).toContain(mention);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test('a space in serverName is refused: LiteLLM 1.103 rejects it at apply, it is not stored as written', () => {
|
|
162
|
+
const problem = firstProblem({ ...base, serverName: 'FAKE web' });
|
|
163
|
+
expect(problem).toContain('serverName');
|
|
164
|
+
expect(problem).toContain('space');
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test('a serverName or alias longer than 128 characters is refused', () => {
|
|
168
|
+
const over = 'A'.repeat(129);
|
|
169
|
+
expect(firstProblem({ ...base, serverName: over })).toContain('128');
|
|
170
|
+
expect(firstProblem({ ...base, alias: over })).toContain('128');
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
test('a name of letters, digits, "." and "_" up to 128 characters passes', () => {
|
|
174
|
+
expect(
|
|
175
|
+
firstProblem({ ...base, serverName: 'A'.repeat(128), alias: 'FAKE.web_1' }),
|
|
176
|
+
).toBeUndefined();
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test('a declared blank description is refused: LiteLLM never copies it into mcp_info', () => {
|
|
180
|
+
expect(firstProblem({ ...base, description: '' })).toContain('description');
|
|
181
|
+
expect(firstProblem({ ...base, description: ' ' })).toContain('description');
|
|
182
|
+
expect(firstProblem({ ...base, description: 'FAKE notes' })).toBeUndefined();
|
|
183
|
+
});
|
|
184
|
+
});
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The credential of a `LiteLLM.MCPServer`: resolved from the deploying process's environment,
|
|
3
|
+
* held as `Redacted`, sealed, and compared by seal.
|
|
4
|
+
*
|
|
5
|
+
* ★ `Redacted` IS THE IN-MEMORY WRAPPER, NOT THE STORAGE FORMAT. Inside this process the value is a
|
|
6
|
+
* `Redacted<string>`, so a stray `console.log`, `JSON.stringify` or error dump prints
|
|
7
|
+
* `<redacted>`. It is unwrapped in exactly one place: the wire body (`mcp-server-form.ts`). It is
|
|
8
|
+
* never a prop or an attribute — see mcp-server-types.ts for why `Redacted` cannot be either.
|
|
9
|
+
* ⚠️ LITELLM DOES NOT HAND A CREDENTIAL BACK TO COMPARE WITH (UNMEASURED at 1.103.0; the resource
|
|
10
|
+
* never relies on it). So what was last written is remembered as a SEAL, and a plan asks "is the
|
|
11
|
+
* value in my environment the one I wrote". Four answers:
|
|
12
|
+
* - `none` — no credential is declared, nothing to compare.
|
|
13
|
+
* - `unknown` — the variable is unset here, so a plan cannot tell. Never drift: a plan-only
|
|
14
|
+
* environment must not report every credentialed server as changed. (A write that
|
|
15
|
+
* CHANGES `auth_type` is the exception: it demands the value, `requireCredential`.)
|
|
16
|
+
* - `stale` — the variable is set and there is no seal (an adopted row, or one written outside
|
|
17
|
+
* this stack) or the seal disagrees. The declaration is authoritative, so it is written.
|
|
18
|
+
* - `match` — the seal was made from this value.
|
|
19
|
+
* ⚠️ A SEAL OF A GUESSABLE SECRET IS STILL GUESSABLE (secrets/write-only.ts). Use a random token.
|
|
20
|
+
*/
|
|
21
|
+
import * as Effect from 'effect/Effect';
|
|
22
|
+
import * as Redacted from 'effect/Redacted';
|
|
23
|
+
import { type Environment, resolveAll, seal, sealMatches } from '../secrets/write-only.ts';
|
|
24
|
+
import { LitellmMcpServerCredentialEnvUnsetError } from './mcp-server-errors.ts';
|
|
25
|
+
import { type McpServerProps, isStaticAuthType } from './mcp-server-types.ts';
|
|
26
|
+
|
|
27
|
+
/** What the environment holds for the declared credential. `value` is undefined when unset or empty. */
|
|
28
|
+
export interface ResolvedCredential {
|
|
29
|
+
/** The variable's NAME, for a message. `undefined` when no credential is declared. */
|
|
30
|
+
readonly variable: string | undefined;
|
|
31
|
+
readonly value: Redacted.Redacted<string> | undefined;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export type CredentialState = 'none' | 'unknown' | 'stale' | 'match';
|
|
35
|
+
|
|
36
|
+
/** ⛔ Reads the environment at CALL time, never at module load (docs/credentials.md). */
|
|
37
|
+
export const resolveCredential = (
|
|
38
|
+
props: McpServerProps,
|
|
39
|
+
env: Environment = process.env,
|
|
40
|
+
): ResolvedCredential => {
|
|
41
|
+
if (props.authValue === undefined) return { value: undefined, variable: undefined };
|
|
42
|
+
const { values } = resolveAll({ authValue: props.authValue }, env);
|
|
43
|
+
const raw = values['authValue'];
|
|
44
|
+
return {
|
|
45
|
+
value: raw === undefined ? undefined : Redacted.make(raw),
|
|
46
|
+
variable: props.authValue.fromEnv,
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const sealable = (value: Redacted.Redacted<string>) => ({ authValue: Redacted.value(value) });
|
|
51
|
+
|
|
52
|
+
/** `scrypt:<salt>:<digest>` of a credential, for `credentialSeal`. ⛔ Never the value. */
|
|
53
|
+
export const sealCredential = (value: Redacted.Redacted<string>): string => seal(sealable(value));
|
|
54
|
+
|
|
55
|
+
export const credentialState = (
|
|
56
|
+
props: McpServerProps,
|
|
57
|
+
sealed: string,
|
|
58
|
+
resolved: ResolvedCredential = resolveCredential(props),
|
|
59
|
+
): CredentialState => {
|
|
60
|
+
if (props.authValue === undefined || !isStaticAuthType(props.authType)) return 'none';
|
|
61
|
+
if (resolved.value === undefined) return 'unknown';
|
|
62
|
+
if (sealed === '') return 'stale';
|
|
63
|
+
return sealMatches(sealed, sealable(resolved.value)) ? 'match' : 'stale';
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Fails naming the variable when this write has to send a static credential and the deploying
|
|
68
|
+
* process does not hold it. Passes for a type that carries none.
|
|
69
|
+
*
|
|
70
|
+
* ⛔ A WRITE THAT CHANGES `auth_type` MUST SEND THE CREDENTIAL, exactly like a create. Measured on
|
|
71
|
+
* the live 1.103.0 container (`mcp_server/db.py` lines 1013-1014, `_credential_auth_class`): an
|
|
72
|
+
* edit whose auth class differs from the stored one and that sends no `credentials` key WIPES the
|
|
73
|
+
* stored credential, and each static type is its own class. Sent without the credential, the row
|
|
74
|
+
* would end with none while the seal still matched the old value, so a later deploy with the
|
|
75
|
+
* variable set would be a no-op and the server would stay unauthenticated until the secret rotated.
|
|
76
|
+
*/
|
|
77
|
+
export const requireCredential = (props: McpServerProps, credential: ResolvedCredential) =>
|
|
78
|
+
isStaticAuthType(props.authType) && credential.value === undefined
|
|
79
|
+
? Effect.fail(
|
|
80
|
+
new LitellmMcpServerCredentialEnvUnsetError({
|
|
81
|
+
serverName: props.serverName,
|
|
82
|
+
variable: credential.variable ?? '(undeclared)',
|
|
83
|
+
}),
|
|
84
|
+
)
|
|
85
|
+
: Effect.void;
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The typed refusals `LiteLLM.MCPServer` raises on top of `@distilled.cloud/litellm`'s own SDK errors.
|
|
3
|
+
*
|
|
4
|
+
* ⛔ NO MESSAGE HERE MAY CARRY A CREDENTIAL. The only things named are the server's name, ids the
|
|
5
|
+
* proxy already lists, field names, and the NAME of an environment variable — never a value, and
|
|
6
|
+
* never a URL (a URL can hold a token; `mcp-server-form.ts` refuses those and redacts the read).
|
|
7
|
+
*/
|
|
8
|
+
import type * as mcp from '@distilled.cloud/litellm/mcp_management';
|
|
9
|
+
import * as Data from 'effect/Data';
|
|
10
|
+
|
|
11
|
+
/** A declaration the resource refuses before any request: a combination nobody meant. */
|
|
12
|
+
export class LitellmMcpServerInvalidError extends Data.TaggedError('LitellmMcpServerInvalidError')<{
|
|
13
|
+
readonly serverName: string;
|
|
14
|
+
readonly problem: string;
|
|
15
|
+
}> {
|
|
16
|
+
override get message(): string {
|
|
17
|
+
return `LiteLLM.MCPServer "${this.serverName}": ${this.problem}.`;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* A write must send the credential and the deploying process does not have it. An empty variable
|
|
23
|
+
* counts as unset (secrets/write-only.ts: a failed renderer writes `NAME=`, and sending `''` would
|
|
24
|
+
* store an empty credential and report a successful deploy).
|
|
25
|
+
*/
|
|
26
|
+
export class LitellmMcpServerCredentialEnvUnsetError extends Data.TaggedError(
|
|
27
|
+
'LitellmMcpServerCredentialEnvUnsetError',
|
|
28
|
+
)<{
|
|
29
|
+
readonly serverName: string;
|
|
30
|
+
readonly variable: string;
|
|
31
|
+
}> {
|
|
32
|
+
override get message(): string {
|
|
33
|
+
return (
|
|
34
|
+
`LiteLLM.MCPServer "${this.serverName}": this write must send its credential, and ` +
|
|
35
|
+
`${this.variable} is unset or empty in the deploying environment. Export it and deploy again.`
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* More than one live row carries the declared `server_name`, so adopt-by-name cannot say which one
|
|
42
|
+
* is meant. LiteLLM does not make the name unique (unmeasured on 1.103.0). Declare `serverId` to
|
|
43
|
+
* pick one — nothing is adopted, updated or deleted on a guess.
|
|
44
|
+
*/
|
|
45
|
+
export class LitellmMcpServerAmbiguousNameError extends Data.TaggedError(
|
|
46
|
+
'LitellmMcpServerAmbiguousNameError',
|
|
47
|
+
)<{
|
|
48
|
+
readonly serverName: string;
|
|
49
|
+
readonly serverIds: readonly string[];
|
|
50
|
+
}> {
|
|
51
|
+
override get message(): string {
|
|
52
|
+
return (
|
|
53
|
+
`LiteLLM.MCPServer "${this.serverName}": ${this.serverIds.length} live rows carry that ` +
|
|
54
|
+
`server_name (${this.serverIds.join(', ')}). Declare \`serverId\` to choose one.`
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** `GET /v1/mcp/server` answered something that is not a list of servers. */
|
|
60
|
+
export class LitellmMcpServerUnreadableError extends Data.TaggedError(
|
|
61
|
+
'LitellmMcpServerUnreadableError',
|
|
62
|
+
)<{
|
|
63
|
+
readonly reason: string;
|
|
64
|
+
}> {
|
|
65
|
+
override get message(): string {
|
|
66
|
+
return `LiteLLM.MCPServer: the server list is unreadable (${this.reason}).`;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* A create or edit returned no error, yet the row is absent on the read back: neither in the list
|
|
72
|
+
* NOR in a database-backed read by id (the list alone is the registry, which can lag a commit).
|
|
73
|
+
*/
|
|
74
|
+
export class LitellmMcpServerAbsentAfterWriteError extends Data.TaggedError(
|
|
75
|
+
'LitellmMcpServerAbsentAfterWriteError',
|
|
76
|
+
)<{
|
|
77
|
+
readonly serverName: string;
|
|
78
|
+
readonly serverId: string;
|
|
79
|
+
}> {
|
|
80
|
+
override get message(): string {
|
|
81
|
+
return (
|
|
82
|
+
`LiteLLM.MCPServer "${this.serverName}": the write of ${this.serverId} returned no error ` +
|
|
83
|
+
'but the row is absent from the list and from the database read.'
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* The write returned no error but the row still differs from the declaration. The edit route may
|
|
90
|
+
* merge with `exclude_none` or a truthiness check (an empty list or a `false` never lands); the
|
|
91
|
+
* read back says so instead of reporting a converged row. Unmeasured on 1.103.0.
|
|
92
|
+
*/
|
|
93
|
+
export class LitellmMcpServerNotConvergedError extends Data.TaggedError(
|
|
94
|
+
'LitellmMcpServerNotConvergedError',
|
|
95
|
+
)<{
|
|
96
|
+
readonly serverName: string;
|
|
97
|
+
readonly serverId: string;
|
|
98
|
+
readonly fields: readonly string[];
|
|
99
|
+
}> {
|
|
100
|
+
override get message(): string {
|
|
101
|
+
return (
|
|
102
|
+
`LiteLLM.MCPServer "${this.serverName}": after the write, ${this.serverId} still differs ` +
|
|
103
|
+
`from the declaration in ${this.fields.join(', ')}.`
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* The declared `description` cannot be applied: the live row's `mcp_info` has a `description` key
|
|
110
|
+
* (even a null one), and LiteLLM answers that in preference to the column, which is all this
|
|
111
|
+
* resource writes (`mcp_info` is not modelled). Raised BEFORE any write, so the row is untouched.
|
|
112
|
+
* Nothing here names the text itself: a description is free text a person wrote.
|
|
113
|
+
*/
|
|
114
|
+
export class LitellmMcpServerDescriptionShadowedError extends Data.TaggedError(
|
|
115
|
+
'LitellmMcpServerDescriptionShadowedError',
|
|
116
|
+
)<{
|
|
117
|
+
readonly serverName: string;
|
|
118
|
+
readonly serverId: string;
|
|
119
|
+
}> {
|
|
120
|
+
override get message(): string {
|
|
121
|
+
return (
|
|
122
|
+
`LiteLLM.MCPServer "${this.serverName}": ${this.serverId} has a \`description\` in \`mcp_info\`, ` +
|
|
123
|
+
'which LiteLLM shows instead of the column this resource writes, and it differs from the ' +
|
|
124
|
+
'declaration. Edit it in LiteLLM, or remove `description` from the declaration.'
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export type McpServerError =
|
|
130
|
+
| mcp.AddMcpServerV1McpServerPostError
|
|
131
|
+
| mcp.EditMcpServerV1McpServerPutError
|
|
132
|
+
| mcp.FetchAllMcpServersV1McpServerGetError
|
|
133
|
+
| mcp.FetchMcpServerV1McpServerServerIdGetError
|
|
134
|
+
| mcp.RemoveMcpServerV1McpServerServerIdDeleteError
|
|
135
|
+
| LitellmMcpServerInvalidError
|
|
136
|
+
| LitellmMcpServerCredentialEnvUnsetError
|
|
137
|
+
| LitellmMcpServerAmbiguousNameError
|
|
138
|
+
| LitellmMcpServerUnreadableError
|
|
139
|
+
| LitellmMcpServerAbsentAfterWriteError
|
|
140
|
+
| LitellmMcpServerNotConvergedError
|
|
141
|
+
| LitellmMcpServerDescriptionShadowedError;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The public barrel re-exports every typed refusal, including the one a shadowed description raises.
|
|
3
|
+
* A missing name here is a runtime import failure: the class exists in mcp-server-errors.ts either way.
|
|
4
|
+
*/
|
|
5
|
+
import { expect, test } from 'bun:test';
|
|
6
|
+
import { LitellmMcpServerDescriptionShadowedError } from './index.ts';
|
|
7
|
+
|
|
8
|
+
test('LitellmMcpServerDescriptionShadowedError is on the litellm barrel', () => {
|
|
9
|
+
const error = new LitellmMcpServerDescriptionShadowedError({
|
|
10
|
+
serverId: 'FAKE-id',
|
|
11
|
+
serverName: 'FAKE_web',
|
|
12
|
+
});
|
|
13
|
+
expect(error._tag).toBe('LitellmMcpServerDescriptionShadowedError');
|
|
14
|
+
expect(error.message).toContain('FAKE_web');
|
|
15
|
+
});
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The pure half of `LiteLLM.MCPServer`: what is refused, what a row is read as, what differs, and
|
|
3
|
+
* what each write body carries. No server, no engine.
|
|
4
|
+
*
|
|
5
|
+
* ★ EVERY VALUE IS `FAKE-*` or an RFC 2606 host.
|
|
6
|
+
*/
|
|
7
|
+
import { describe, expect, test } from 'bun:test';
|
|
8
|
+
import * as Redacted from 'effect/Redacted';
|
|
9
|
+
import {
|
|
10
|
+
createBody,
|
|
11
|
+
differing,
|
|
12
|
+
firstProblem,
|
|
13
|
+
toAttributes,
|
|
14
|
+
updateBody,
|
|
15
|
+
} from './mcp-server-form.ts';
|
|
16
|
+
import { redactUrl } from './mcp-server-url.ts';
|
|
17
|
+
import type { McpServerAttributes, McpServerProps } from './mcp-server-types.ts';
|
|
18
|
+
|
|
19
|
+
const base: McpServerProps = {
|
|
20
|
+
authType: 'none',
|
|
21
|
+
serverName: 'FAKE_docs',
|
|
22
|
+
transport: 'http',
|
|
23
|
+
url: 'https://mcp.example.com/mcp',
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const attributes = (over: Partial<McpServerAttributes> = {}): McpServerAttributes => ({
|
|
27
|
+
alias: null,
|
|
28
|
+
allowAllKeys: false,
|
|
29
|
+
allowedTools: [],
|
|
30
|
+
authType: 'none',
|
|
31
|
+
credentialSeal: '',
|
|
32
|
+
description: null,
|
|
33
|
+
mcpAccessGroups: [],
|
|
34
|
+
serverId: 'FAKE-id',
|
|
35
|
+
serverName: 'FAKE_docs',
|
|
36
|
+
transport: 'http',
|
|
37
|
+
url: 'https://mcp.example.com/mcp',
|
|
38
|
+
...over,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe('what is refused before any request', () => {
|
|
42
|
+
test('an ordinary declaration passes', () => {
|
|
43
|
+
expect(firstProblem(base)).toBeUndefined();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test.each([
|
|
47
|
+
['a blank name', { ...base, serverName: ' ' }, 'serverName'],
|
|
48
|
+
['a padded name', { ...base, serverName: ' FAKE_docs' }, 'serverName'],
|
|
49
|
+
['stdio', { ...base, transport: 'stdio' as never }, 'stdio'],
|
|
50
|
+
[
|
|
51
|
+
'an auth type that needs credential fields not modelled',
|
|
52
|
+
{ ...base, authType: 'aws_sigv4' as never },
|
|
53
|
+
'authType',
|
|
54
|
+
],
|
|
55
|
+
['a static type without a credential', { ...base, authType: 'api_key' as const }, 'authValue'],
|
|
56
|
+
[
|
|
57
|
+
'a credential on oauth2',
|
|
58
|
+
{ ...base, authType: 'oauth2' as const, authValue: { fromEnv: 'FAKE_VAR' } },
|
|
59
|
+
'authValue',
|
|
60
|
+
],
|
|
61
|
+
['a blank tool name', { ...base, allowedTools: ['FAKE_tool', ''] }, 'allowedTools'],
|
|
62
|
+
['a scheme that is not http', { ...base, url: 'ftp://mcp.example.com/mcp' }, 'http'],
|
|
63
|
+
['a bare host', { ...base, url: 'mcp.example.com' }, 'url'],
|
|
64
|
+
[
|
|
65
|
+
'userinfo in the url',
|
|
66
|
+
{ ...base, url: 'https://FAKE-user:FAKE-pass@mcp.example.com/mcp' },
|
|
67
|
+
'userinfo',
|
|
68
|
+
],
|
|
69
|
+
['a fragment', { ...base, url: 'https://mcp.example.com/mcp#frag' }, 'fragment'],
|
|
70
|
+
])('%s', (_label, props, mention) => {
|
|
71
|
+
expect(firstProblem(props)).toContain(mention);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test('a secret-looking query parameter is refused by NAME and the value is never quoted', () => {
|
|
75
|
+
const problem = firstProblem({
|
|
76
|
+
...base,
|
|
77
|
+
url: 'https://mcp.example.com/mcp?api_key=FAKE-secret',
|
|
78
|
+
});
|
|
79
|
+
expect(problem).toContain('api_key');
|
|
80
|
+
expect(problem).not.toContain('FAKE-secret');
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test('a harmless query parameter is allowed', () => {
|
|
84
|
+
expect(firstProblem({ ...base, url: 'https://mcp.example.com/mcp?region=eu' })).toBeUndefined();
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
describe('redactUrl', () => {
|
|
89
|
+
test('a url that needs nothing comes back byte for byte, bare origin included', () => {
|
|
90
|
+
expect(redactUrl('https://mcp.example.com')).toBe('https://mcp.example.com');
|
|
91
|
+
expect(redactUrl('https://mcp.example.com/mcp?region=eu')).toBe(
|
|
92
|
+
'https://mcp.example.com/mcp?region=eu',
|
|
93
|
+
);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test('userinfo and secret-looking query values are replaced', () => {
|
|
97
|
+
const out = redactUrl(
|
|
98
|
+
'https://FAKE-user:FAKE-pass@mcp.example.com/mcp?token=FAKE-t&keep=1&token=FAKE-u',
|
|
99
|
+
);
|
|
100
|
+
expect(out).not.toContain('FAKE-');
|
|
101
|
+
expect(out).toContain('keep=1');
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test('a string that is not a url is returned as it is', () => {
|
|
105
|
+
expect(redactUrl('not a url')).toBe('not a url');
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
describe('the read shape', () => {
|
|
110
|
+
test('copies no credential and no header, whatever the row carries', () => {
|
|
111
|
+
const row = {
|
|
112
|
+
credentials: { auth_value: 'FAKE-live-secret', client_secret: 'FAKE-client-secret' },
|
|
113
|
+
env: { FAKE_ENV: 'FAKE-env-secret' },
|
|
114
|
+
env_vars: [{ name: 'FAKE_ENV', scope: 'global' as const }],
|
|
115
|
+
server_id: 'FAKE-id',
|
|
116
|
+
static_headers: { Authorization: 'FAKE-header-secret' },
|
|
117
|
+
transport: 'sse' as const,
|
|
118
|
+
url: 'https://mcp.example.com/sse?token=FAKE-url-secret',
|
|
119
|
+
};
|
|
120
|
+
const text = JSON.stringify(toAttributes(row));
|
|
121
|
+
for (const secret of [
|
|
122
|
+
'FAKE-live-secret',
|
|
123
|
+
'FAKE-client-secret',
|
|
124
|
+
'FAKE-env-secret',
|
|
125
|
+
'FAKE-header-secret',
|
|
126
|
+
'FAKE-url-secret',
|
|
127
|
+
]) {
|
|
128
|
+
expect(text).not.toContain(secret);
|
|
129
|
+
}
|
|
130
|
+
expect(toAttributes(row).credentialSeal).toBe('');
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test('a row with no auth type reads as none, and absent lists read as empty', () => {
|
|
134
|
+
const live = toAttributes({ server_id: 'FAKE-id', transport: 'http', auth_type: null });
|
|
135
|
+
expect(live).toMatchObject({
|
|
136
|
+
allowAllKeys: false,
|
|
137
|
+
allowedTools: [],
|
|
138
|
+
authType: 'none',
|
|
139
|
+
mcpAccessGroups: [],
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
describe('what differs', () => {
|
|
145
|
+
test('nothing, for a live row that matches', () => {
|
|
146
|
+
expect(differing(attributes(), base)).toEqual([]);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test('key and group grants are always compared, against a closed default', () => {
|
|
150
|
+
const open = attributes({ allowAllKeys: true, mcpAccessGroups: ['FAKE_g'] });
|
|
151
|
+
expect(differing(open, base)).toEqual(['allow_all_keys', 'mcp_access_groups']);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test('a tool list is compared only when declared: an undeclared one is never drift', () => {
|
|
155
|
+
const whitelisted = attributes({ allowedTools: ['FAKE_a'] });
|
|
156
|
+
expect(differing(whitelisted, base)).toEqual([]);
|
|
157
|
+
// ⚠️ a declared `[]` is a real difference from a whitelist: it asks for "no restriction"
|
|
158
|
+
expect(differing(whitelisted, { ...base, allowedTools: [] })).toEqual(['allowed_tools']);
|
|
159
|
+
expect(differing(attributes(), { ...base, allowedTools: ['FAKE_a'] })).toEqual([
|
|
160
|
+
'allowed_tools',
|
|
161
|
+
]);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test('lists compare as sets: order and repeats do not matter', () => {
|
|
165
|
+
const live = attributes({ allowedTools: ['FAKE_b', 'FAKE_a'] });
|
|
166
|
+
expect(differing(live, { ...base, allowedTools: ['FAKE_a', 'FAKE_b'] })).toEqual([]);
|
|
167
|
+
expect(differing(live, { ...base, allowedTools: ['FAKE_a'] })).toEqual(['allowed_tools']);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
test('alias and description are compared only when declared', () => {
|
|
171
|
+
const live = attributes({ alias: 'FAKE_alias', description: 'a person wrote this' });
|
|
172
|
+
expect(differing(live, base)).toEqual([]);
|
|
173
|
+
expect(differing(live, { ...base, alias: 'other', description: 'other' })).toEqual([
|
|
174
|
+
'alias',
|
|
175
|
+
'description',
|
|
176
|
+
]);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test('a live url that carried a token differs from the clean declaration', () => {
|
|
180
|
+
const live = attributes({ url: redactUrl('https://mcp.example.com/mcp?token=FAKE-t') });
|
|
181
|
+
expect(differing(live, base)).toEqual(['url']);
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
describe('the write bodies', () => {
|
|
186
|
+
const token = Redacted.make('FAKE-token');
|
|
187
|
+
|
|
188
|
+
test('a create carries the id, the managed set and the credential, and no undeclared field', () => {
|
|
189
|
+
const body = createBody(
|
|
190
|
+
{ ...base, authType: 'bearer_token', authValue: { fromEnv: 'FAKE_VAR' } },
|
|
191
|
+
'FAKE-id',
|
|
192
|
+
token,
|
|
193
|
+
);
|
|
194
|
+
expect(body).toEqual({
|
|
195
|
+
allow_all_keys: false,
|
|
196
|
+
auth_type: 'bearer_token',
|
|
197
|
+
credentials: { auth_value: 'FAKE-token' },
|
|
198
|
+
mcp_access_groups: [],
|
|
199
|
+
server_id: 'FAKE-id',
|
|
200
|
+
server_name: 'FAKE_docs',
|
|
201
|
+
transport: 'http',
|
|
202
|
+
url: 'https://mcp.example.com/mcp',
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
test('the declaration names the variable, never the value', () => {
|
|
207
|
+
expect(JSON.stringify({ ...base, authValue: { fromEnv: 'FAKE_VAR' } })).not.toContain(
|
|
208
|
+
'FAKE-token',
|
|
209
|
+
);
|
|
210
|
+
expect(JSON.stringify(token)).not.toContain('FAKE-token');
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
test('allowed_tools is sent only when declared, and a declared empty list is sent as it is', () => {
|
|
214
|
+
for (const body of [
|
|
215
|
+
createBody(base, 'FAKE-id', undefined),
|
|
216
|
+
updateBody(base, 'FAKE-id', undefined, false, attributes()),
|
|
217
|
+
]) {
|
|
218
|
+
expect(body).not.toHaveProperty('allowed_tools');
|
|
219
|
+
}
|
|
220
|
+
expect(
|
|
221
|
+
updateBody({ ...base, allowedTools: [] }, 'FAKE-id', undefined, false, attributes()),
|
|
222
|
+
).toHaveProperty('allowed_tools', []);
|
|
223
|
+
expect(createBody({ ...base, allowedTools: ['FAKE_a'] }, 'FAKE-id', undefined)).toHaveProperty(
|
|
224
|
+
'allowed_tools',
|
|
225
|
+
['FAKE_a'],
|
|
226
|
+
);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
test('an update with no credential sends no credentials key, so the stored one is left', () => {
|
|
230
|
+
expect(updateBody(base, 'FAKE-id', undefined, false, attributes())).not.toHaveProperty(
|
|
231
|
+
'credentials',
|
|
232
|
+
);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
test('leaving a static type sends an explicit null', () => {
|
|
236
|
+
expect(updateBody(base, 'FAKE-id', undefined, true, attributes())).toHaveProperty(
|
|
237
|
+
'credentials',
|
|
238
|
+
null,
|
|
239
|
+
);
|
|
240
|
+
});
|
|
241
|
+
});
|