effect 4.1.0 → 4.1.1
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 +30 -0
- package/THIRD_PARTY_NOTICES.md +62 -0
- package/dist/BigDecimal.d.ts +139 -7
- package/dist/BigDecimal.d.ts.map +1 -1
- package/dist/BigDecimal.js +83 -14
- package/dist/BigDecimal.js.map +1 -1
- package/dist/Equal.d.ts +2 -2
- package/dist/Equal.d.ts.map +1 -1
- package/dist/Equal.js +3 -3
- package/dist/Equal.js.map +1 -1
- package/dist/Hash.d.ts +3 -2
- package/dist/Hash.d.ts.map +1 -1
- package/dist/Hash.js +5 -4
- package/dist/Hash.js.map +1 -1
- package/dist/Iterable.d.ts.map +1 -1
- package/dist/Iterable.js +11 -8
- package/dist/Iterable.js.map +1 -1
- package/dist/Path.d.ts.map +1 -1
- package/dist/Path.js +5 -1
- package/dist/Path.js.map +1 -1
- package/dist/Pool.d.ts.map +1 -1
- package/dist/Pool.js +2 -1
- package/dist/Pool.js.map +1 -1
- package/dist/Schema.d.ts +2 -1
- package/dist/Schema.d.ts.map +1 -1
- package/dist/Schema.js +2 -1
- package/dist/Schema.js.map +1 -1
- package/dist/SchemaRepresentation.d.ts +4 -2
- package/dist/SchemaRepresentation.d.ts.map +1 -1
- package/dist/SchemaRepresentation.js +4 -2
- package/dist/SchemaRepresentation.js.map +1 -1
- package/dist/SchemaTransformation.d.ts +2 -2
- package/dist/SchemaTransformation.js +2 -2
- package/dist/ai/McpClient.d.ts +171 -19
- package/dist/ai/McpClient.d.ts.map +1 -1
- package/dist/ai/McpClient.js +277 -66
- package/dist/ai/McpClient.js.map +1 -1
- package/dist/ai/McpSchema.d.ts +108 -0
- package/dist/ai/McpSchema.d.ts.map +1 -1
- package/dist/ai/McpSchema.js +99 -0
- package/dist/ai/McpSchema.js.map +1 -1
- package/dist/ai/McpServer.d.ts +120 -6
- package/dist/ai/McpServer.d.ts.map +1 -1
- package/dist/ai/McpServer.js +215 -34
- package/dist/ai/McpServer.js.map +1 -1
- package/dist/ai/Prompt.d.ts.map +1 -1
- package/dist/ai/Prompt.js +20 -11
- package/dist/ai/Prompt.js.map +1 -1
- package/dist/ai/Tool.d.ts.map +1 -1
- package/dist/ai/Tool.js +5 -7
- package/dist/ai/Tool.js.map +1 -1
- package/dist/ai/internal/mcpCore.js +72 -3
- package/dist/ai/internal/mcpCore.js.map +1 -1
- package/dist/ai/internal/mcpProtocol/v2024_11_05.js +3 -1
- package/dist/ai/internal/mcpProtocol/v2024_11_05.js.map +1 -1
- package/dist/ai/internal/mcpProtocol/v2025_03_26.js +3 -1
- package/dist/ai/internal/mcpProtocol/v2025_03_26.js.map +1 -1
- package/dist/ai/internal/mcpProtocol/v2025_06_18.js +3 -1
- package/dist/ai/internal/mcpProtocol/v2025_06_18.js.map +1 -1
- package/dist/ai/internal/mcpProtocol/v2025_11_25.js +3 -1
- package/dist/ai/internal/mcpProtocol/v2025_11_25.js.map +1 -1
- package/dist/ai/internal/mcpProtocol/v2026_07_28.js +42 -7
- package/dist/ai/internal/mcpProtocol/v2026_07_28.js.map +1 -1
- package/dist/ai/internal/mcpProtocol.js +12 -0
- package/dist/ai/internal/mcpProtocol.js.map +1 -1
- package/dist/ai/internal/mcpRuntime.js +3 -3
- package/dist/ai/internal/mcpRuntime.js.map +1 -1
- package/dist/ai/internal/mcpSkills.d.ts +2 -0
- package/dist/ai/internal/mcpSkills.d.ts.map +1 -0
- package/dist/ai/internal/mcpSkills.js +428 -0
- package/dist/ai/internal/mcpSkills.js.map +1 -0
- package/dist/cluster/ClusterWorkflowEngine.d.ts.map +1 -1
- package/dist/cluster/ClusterWorkflowEngine.js +15 -5
- package/dist/cluster/ClusterWorkflowEngine.js.map +1 -1
- package/dist/cluster/Reply.d.ts +1 -1
- package/dist/cluster/Reply.d.ts.map +1 -1
- package/dist/cluster/Reply.js.map +1 -1
- package/dist/cluster/Sharding.d.ts.map +1 -1
- package/dist/cluster/Sharding.js +4 -1
- package/dist/cluster/Sharding.js.map +1 -1
- package/dist/cluster/internal/entityManager.js +15 -7
- package/dist/cluster/internal/entityManager.js.map +1 -1
- package/dist/crypto/Aead.d.ts +2 -1
- package/dist/crypto/Aead.d.ts.map +1 -1
- package/dist/crypto/Aead.js +2 -2
- package/dist/crypto/Aead.js.map +1 -1
- package/dist/crypto/Cipher.d.ts +2 -1
- package/dist/crypto/Cipher.d.ts.map +1 -1
- package/dist/crypto/Cipher.js +2 -2
- package/dist/crypto/Cipher.js.map +1 -1
- package/dist/crypto/KeyAgreement.d.ts +3 -2
- package/dist/crypto/KeyAgreement.d.ts.map +1 -1
- package/dist/crypto/KeyAgreement.js +2 -2
- package/dist/crypto/KeyAgreement.js.map +1 -1
- package/dist/crypto/KeyFormat.d.ts +33 -1
- package/dist/crypto/KeyFormat.d.ts.map +1 -1
- package/dist/crypto/KeyFormat.js.map +1 -1
- package/dist/crypto/KeyWrap.d.ts +4 -3
- package/dist/crypto/KeyWrap.d.ts.map +1 -1
- package/dist/crypto/KeyWrap.js +2 -2
- package/dist/crypto/KeyWrap.js.map +1 -1
- package/dist/crypto/Mac.d.ts +2 -1
- package/dist/crypto/Mac.d.ts.map +1 -1
- package/dist/crypto/Mac.js +2 -2
- package/dist/crypto/Mac.js.map +1 -1
- package/dist/crypto/Signature.d.ts +3 -2
- package/dist/crypto/Signature.d.ts.map +1 -1
- package/dist/crypto/Signature.js +2 -2
- package/dist/crypto/Signature.js.map +1 -1
- package/dist/crypto/WebCrypto.d.ts +19 -0
- package/dist/crypto/WebCrypto.d.ts.map +1 -1
- package/dist/crypto/WebCrypto.js.map +1 -1
- package/dist/crypto/internal/asymmetric.js.map +1 -1
- package/dist/crypto/internal/key.js +3 -2
- package/dist/crypto/internal/key.js.map +1 -1
- package/dist/encoding/Base64Url.d.ts.map +1 -1
- package/dist/encoding/Base64Url.js +26 -3
- package/dist/encoding/Base64Url.js.map +1 -1
- package/dist/encoding/Ndjson.d.ts +5 -4
- package/dist/encoding/Ndjson.d.ts.map +1 -1
- package/dist/encoding/Ndjson.js +6 -5
- package/dist/encoding/Ndjson.js.map +1 -1
- package/dist/encoding/Sse.d.ts +3 -1
- package/dist/encoding/Sse.d.ts.map +1 -1
- package/dist/encoding/Sse.js +5 -3
- package/dist/encoding/Sse.js.map +1 -1
- package/dist/eventlog/EventLog.d.ts +7 -0
- package/dist/eventlog/EventLog.d.ts.map +1 -1
- package/dist/eventlog/EventLog.js +7 -0
- package/dist/eventlog/EventLog.js.map +1 -1
- package/dist/eventlog/EventLogServer.d.ts +7 -1
- package/dist/eventlog/EventLogServer.d.ts.map +1 -1
- package/dist/eventlog/EventLogServer.js +27 -12
- package/dist/eventlog/EventLogServer.js.map +1 -1
- package/dist/eventlog/EventLogServerEncrypted.d.ts +10 -0
- package/dist/eventlog/EventLogServerEncrypted.d.ts.map +1 -1
- package/dist/eventlog/EventLogServerEncrypted.js.map +1 -1
- package/dist/eventlog/EventLogServerUnencrypted.d.ts +17 -0
- package/dist/eventlog/EventLogServerUnencrypted.d.ts.map +1 -1
- package/dist/eventlog/EventLogServerUnencrypted.js +14 -2
- package/dist/eventlog/EventLogServerUnencrypted.js.map +1 -1
- package/dist/eventlog/EventLogSessionAuth.d.ts +21 -0
- package/dist/eventlog/EventLogSessionAuth.d.ts.map +1 -1
- package/dist/eventlog/EventLogSessionAuth.js +22 -0
- package/dist/eventlog/EventLogSessionAuth.js.map +1 -1
- package/dist/graphql/GraphQLClient.d.ts +3 -1
- package/dist/graphql/GraphQLClient.d.ts.map +1 -1
- package/dist/graphql/GraphQLClient.js.map +1 -1
- package/dist/graphql/GraphQLClientError.d.ts +14 -9
- package/dist/graphql/GraphQLClientError.d.ts.map +1 -1
- package/dist/graphql/GraphQLClientError.js +14 -21
- package/dist/graphql/GraphQLClientError.js.map +1 -1
- package/dist/graphql/GraphQLProtocol.d.ts +12 -3
- package/dist/graphql/GraphQLProtocol.d.ts.map +1 -1
- package/dist/graphql/GraphQLProtocol.js +4 -1
- package/dist/graphql/GraphQLProtocol.js.map +1 -1
- package/dist/http/HttpClient.d.ts +189 -7
- package/dist/http/HttpClient.d.ts.map +1 -1
- package/dist/http/HttpClient.js +75 -2
- package/dist/http/HttpClient.js.map +1 -1
- package/dist/http/HttpServerRequest.d.ts.map +1 -1
- package/dist/http/HttpServerRequest.js +38 -22
- package/dist/http/HttpServerRequest.js.map +1 -1
- package/dist/http/internal/mimeTypes.d.ts +4 -1
- package/dist/http/internal/mimeTypes.d.ts.map +1 -1
- package/dist/http/internal/mimeTypes.js +4 -1
- package/dist/http/internal/mimeTypes.js.map +1 -1
- package/dist/http-api/HttpApiSchema.js +1 -1
- package/dist/http-api/HttpApiSchema.js.map +1 -1
- package/dist/internal/dateTime.js +49 -24
- package/dist/internal/dateTime.js.map +1 -1
- package/dist/internal/effect.js +10 -0
- package/dist/internal/effect.js.map +1 -1
- package/dist/internal/glob.js +234 -56
- package/dist/internal/glob.js.map +1 -1
- package/dist/internal/graphql.js +21 -7
- package/dist/internal/graphql.js.map +1 -1
- package/dist/internal/graphqlHeaders.d.ts +7 -0
- package/dist/internal/graphqlHeaders.d.ts.map +1 -1
- package/dist/internal/graphqlHeaders.js +11 -0
- package/dist/internal/graphqlHeaders.js.map +1 -1
- package/dist/internal/request.js +29 -42
- package/dist/internal/request.js.map +1 -1
- package/dist/internal/schema/fromJsonSchemaDocument.js +7 -8
- package/dist/internal/schema/fromJsonSchemaDocument.js.map +1 -1
- package/dist/internal/version.js +1 -1
- package/dist/jose/JoseError.d.ts +285 -0
- package/dist/jose/JoseError.d.ts.map +1 -0
- package/dist/jose/JoseError.js +269 -0
- package/dist/jose/JoseError.js.map +1 -0
- package/dist/jose/Jwe.d.ts +128 -0
- package/dist/jose/Jwe.d.ts.map +1 -0
- package/dist/jose/Jwe.js +134 -0
- package/dist/jose/Jwe.js.map +1 -0
- package/dist/jose/Jwk.d.ts +597 -0
- package/dist/jose/Jwk.d.ts.map +1 -0
- package/dist/jose/Jwk.js +503 -0
- package/dist/jose/Jwk.js.map +1 -0
- package/dist/jose/Jwks.d.ts +236 -0
- package/dist/jose/Jwks.d.ts.map +1 -0
- package/dist/jose/Jwks.js +306 -0
- package/dist/jose/Jwks.js.map +1 -0
- package/dist/jose/Jws.d.ts +177 -0
- package/dist/jose/Jws.d.ts.map +1 -0
- package/dist/jose/Jws.js +183 -0
- package/dist/jose/Jws.js.map +1 -0
- package/dist/jose/Jwt.d.ts +270 -0
- package/dist/jose/Jwt.d.ts.map +1 -0
- package/dist/jose/Jwt.js +293 -0
- package/dist/jose/Jwt.js.map +1 -0
- package/dist/jose/index.d.ts +51 -0
- package/dist/jose/index.d.ts.map +1 -0
- package/dist/jose/index.js +52 -0
- package/dist/jose/index.js.map +1 -0
- package/dist/jose/internal/compact.d.ts +2 -0
- package/dist/jose/internal/compact.d.ts.map +1 -0
- package/dist/jose/internal/compact.js +179 -0
- package/dist/jose/internal/compact.js.map +1 -0
- package/dist/net/Host.d.ts.map +1 -1
- package/dist/net/Host.js +5 -10
- package/dist/net/Host.js.map +1 -1
- package/dist/observability/OtlpSerialization.d.ts +1 -1
- package/dist/observability/OtlpSerialization.d.ts.map +1 -1
- package/dist/observability/OtlpSerialization.js +10 -4
- package/dist/observability/OtlpSerialization.js.map +1 -1
- package/dist/observability/internal/otlpProtobuf.js +3 -3
- package/dist/observability/internal/otlpProtobuf.js.map +1 -1
- package/dist/observability/internal/protobuf.js +6 -0
- package/dist/observability/internal/protobuf.js.map +1 -1
- package/dist/persistence/CircuitBreaker.d.ts +98 -38
- package/dist/persistence/CircuitBreaker.d.ts.map +1 -1
- package/dist/persistence/CircuitBreaker.js +67 -29
- package/dist/persistence/CircuitBreaker.js.map +1 -1
- package/dist/persistence/RateLimiter.d.ts +8 -0
- package/dist/persistence/RateLimiter.d.ts.map +1 -1
- package/dist/persistence/RateLimiter.js +63 -12
- package/dist/persistence/RateLimiter.js.map +1 -1
- package/dist/persistence/index.d.ts +1 -1
- package/dist/persistence/index.js +1 -1
- package/dist/reactivity/AtomRegistry.js +17 -7
- package/dist/reactivity/AtomRegistry.js.map +1 -1
- package/dist/rpc/Rpc.d.ts +1 -1
- package/dist/rpc/Rpc.d.ts.map +1 -1
- package/dist/rpc/Rpc.js.map +1 -1
- package/dist/rpc/RpcClient.d.ts +14 -2
- package/dist/rpc/RpcClient.d.ts.map +1 -1
- package/dist/rpc/RpcClient.js +38 -21
- package/dist/rpc/RpcClient.js.map +1 -1
- package/dist/rpc/RpcMessage.d.ts +3 -2
- package/dist/rpc/RpcMessage.d.ts.map +1 -1
- package/dist/rpc/RpcMessage.js.map +1 -1
- package/dist/sql/Migrator.d.ts +9 -1
- package/dist/sql/Migrator.d.ts.map +1 -1
- package/dist/sql/Migrator.js +18 -6
- package/dist/sql/Migrator.js.map +1 -1
- package/dist/sql/SqlClient.d.ts +38 -3
- package/dist/sql/SqlClient.d.ts.map +1 -1
- package/dist/sql/SqlClient.js +4 -3
- package/dist/sql/SqlClient.js.map +1 -1
- package/dist/sql/Statement.d.ts +41 -3
- package/dist/sql/Statement.d.ts.map +1 -1
- package/dist/sql/Statement.js +24 -20
- package/dist/sql/Statement.js.map +1 -1
- package/dist/workflow/Workflow.d.ts +4 -0
- package/dist/workflow/Workflow.d.ts.map +1 -1
- package/dist/workflow/Workflow.js.map +1 -1
- package/dist/workflow/WorkflowEngine.d.ts.map +1 -1
- package/dist/workflow/WorkflowEngine.js +5 -0
- package/dist/workflow/WorkflowEngine.js.map +1 -1
- package/package.json +5 -1
- package/src/BigDecimal.ts +170 -16
- package/src/Equal.ts +3 -3
- package/src/Hash.ts +5 -4
- package/src/Iterable.ts +8 -5
- package/src/Path.ts +6 -1
- package/src/Pool.ts +2 -1
- package/src/Schema.ts +3 -2
- package/src/SchemaRepresentation.ts +4 -2
- package/src/SchemaTransformation.ts +2 -2
- package/src/ai/McpClient.ts +488 -91
- package/src/ai/McpSchema.ts +119 -0
- package/src/ai/McpServer.ts +261 -38
- package/src/ai/Prompt.ts +16 -11
- package/src/ai/Tool.ts +5 -10
- package/src/ai/internal/mcpCore.ts +165 -12
- package/src/ai/internal/mcpProtocol/v2024_11_05.ts +3 -1
- package/src/ai/internal/mcpProtocol/v2025_03_26.ts +3 -1
- package/src/ai/internal/mcpProtocol/v2025_06_18.ts +3 -1
- package/src/ai/internal/mcpProtocol/v2025_11_25.ts +3 -1
- package/src/ai/internal/mcpProtocol/v2026_07_28.ts +41 -7
- package/src/ai/internal/mcpProtocol.ts +15 -0
- package/src/ai/internal/mcpRuntime.ts +6 -3
- package/src/ai/internal/mcpSkills.ts +480 -0
- package/src/cluster/ClusterWorkflowEngine.ts +22 -8
- package/src/cluster/Reply.ts +1 -1
- package/src/cluster/Sharding.ts +4 -1
- package/src/cluster/internal/entityManager.ts +24 -10
- package/src/crypto/Aead.ts +3 -3
- package/src/crypto/Cipher.ts +3 -3
- package/src/crypto/KeyAgreement.ts +4 -4
- package/src/crypto/KeyFormat.ts +30 -1
- package/src/crypto/KeyWrap.ts +6 -6
- package/src/crypto/Mac.ts +3 -3
- package/src/crypto/Signature.ts +4 -4
- package/src/crypto/WebCrypto.ts +20 -0
- package/src/crypto/internal/asymmetric.ts +2 -1
- package/src/crypto/internal/key.ts +12 -5
- package/src/encoding/Base64Url.ts +27 -3
- package/src/encoding/Ndjson.ts +6 -5
- package/src/encoding/Sse.ts +5 -3
- package/src/eventlog/EventLog.ts +7 -0
- package/src/eventlog/EventLogServer.ts +31 -12
- package/src/eventlog/EventLogServerEncrypted.ts +5 -0
- package/src/eventlog/EventLogServerUnencrypted.ts +19 -2
- package/src/eventlog/EventLogSessionAuth.ts +26 -0
- package/src/graphql/GraphQLClient.ts +3 -1
- package/src/graphql/GraphQLClientError.ts +15 -22
- package/src/graphql/GraphQLProtocol.ts +12 -3
- package/src/http/HttpClient.ts +210 -11
- package/src/http/HttpServerRequest.ts +61 -23
- package/src/http/internal/mimeTypes.ts +4 -1
- package/src/http-api/HttpApiSchema.ts +1 -1
- package/src/internal/dateTime.ts +54 -24
- package/src/internal/effect.ts +6 -0
- package/src/internal/glob.ts +279 -63
- package/src/internal/graphql.ts +27 -9
- package/src/internal/graphqlHeaders.ts +13 -0
- package/src/internal/request.ts +51 -58
- package/src/internal/schema/fromJsonSchemaDocument.ts +11 -10
- package/src/internal/version.ts +1 -1
- package/src/jose/JoseError.ts +329 -0
- package/src/jose/Jwe.ts +196 -0
- package/src/jose/Jwk.ts +777 -0
- package/src/jose/Jwks.ts +418 -0
- package/src/jose/Jws.ts +312 -0
- package/src/jose/Jwt.ts +493 -0
- package/src/jose/index.ts +58 -0
- package/src/jose/internal/compact.ts +195 -0
- package/src/net/Host.ts +5 -10
- package/src/observability/OtlpSerialization.ts +13 -5
- package/src/observability/internal/otlpProtobuf.ts +6 -6
- package/src/observability/internal/protobuf.ts +10 -0
- package/src/persistence/CircuitBreaker.ts +113 -48
- package/src/persistence/RateLimiter.ts +84 -15
- package/src/persistence/index.ts +1 -1
- package/src/reactivity/AtomRegistry.ts +19 -8
- package/src/rpc/Rpc.ts +3 -1
- package/src/rpc/RpcClient.ts +44 -24
- package/src/rpc/RpcMessage.ts +3 -2
- package/src/sql/Migrator.ts +35 -18
- package/src/sql/SqlClient.ts +43 -8
- package/src/sql/Statement.ts +52 -22
- package/src/workflow/Workflow.ts +4 -0
- package/src/workflow/WorkflowEngine.ts +5 -0
package/src/ai/McpClient.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Connects to MCP servers with scoped Effect clients.
|
|
3
3
|
*
|
|
4
|
-
* Clients discover tools, prompts and
|
|
5
|
-
* their transport. `callTool` returns tool-reported errors as results; supplying a schema
|
|
4
|
+
* Clients discover tools, prompts, resources and Agent Skills using the protocol revision
|
|
5
|
+
* selected by their transport. `callTool` returns tool-reported errors as results; supplying a schema
|
|
6
6
|
* turns them into a `ToolError` reason that retains the original result. Transport and
|
|
7
7
|
* protocol failures fail the Effect. Interactive server requests, subscriptions, OAuth
|
|
8
8
|
* and retries are not included.
|
|
@@ -13,11 +13,13 @@
|
|
|
13
13
|
import * as ByteSize from "../ByteSize.ts"
|
|
14
14
|
import type * as Cause from "../Cause.ts"
|
|
15
15
|
import * as Context from "../Context.ts"
|
|
16
|
+
import * as Digest from "../crypto/Digest.ts"
|
|
16
17
|
import * as Deferred from "../Deferred.ts"
|
|
17
18
|
import * as Duration from "../Duration.ts"
|
|
18
19
|
import * as Effect from "../Effect.ts"
|
|
19
20
|
import * as Base64 from "../encoding/Base64.ts"
|
|
20
21
|
import * as Sse from "../encoding/Sse.ts"
|
|
22
|
+
import * as Equal from "../Equal.ts"
|
|
21
23
|
import * as HttpClient from "../http/HttpClient.ts"
|
|
22
24
|
import * as HttpClientRequest from "../http/HttpClientRequest.ts"
|
|
23
25
|
import type * as HttpClientResponse from "../http/HttpClientResponse.ts"
|
|
@@ -32,6 +34,7 @@ import * as Schema from "../Schema.ts"
|
|
|
32
34
|
import type * as Scope from "../Scope.ts"
|
|
33
35
|
import * as Stream from "../Stream.ts"
|
|
34
36
|
import { BASE64_SENTINEL_PREFIX, BASE64_SENTINEL_SUFFIX } from "./internal/mcpProtocol.ts"
|
|
37
|
+
import * as McpSkills from "./internal/mcpSkills.ts"
|
|
35
38
|
import {
|
|
36
39
|
ClosedError,
|
|
37
40
|
ConfigurationError,
|
|
@@ -49,6 +52,7 @@ import {
|
|
|
49
52
|
} from "./McpClientError.ts"
|
|
50
53
|
import type * as McpProtocol from "./McpProtocol.ts"
|
|
51
54
|
import * as McpSchema from "./McpSchema.ts"
|
|
55
|
+
import * as Prompt from "./Prompt.ts"
|
|
52
56
|
import * as Tool from "./Tool.ts"
|
|
53
57
|
import * as Toolkit from "./Toolkit.ts"
|
|
54
58
|
|
|
@@ -84,18 +88,33 @@ export const McpClient: Context.Service<McpClient, McpClient> = Context.Service<
|
|
|
84
88
|
export interface McpClient {
|
|
85
89
|
readonly [TypeId]: typeof TypeId
|
|
86
90
|
readonly protocolVersion: ProtocolVersion
|
|
91
|
+
/** Features advertised by the remote server. */
|
|
92
|
+
readonly capabilities: McpSchema.ServerCapabilities
|
|
87
93
|
readonly serverInfo: McpSchema.Implementation | undefined
|
|
88
94
|
readonly instructions?: string | undefined
|
|
89
95
|
/**
|
|
90
|
-
* Returns every tool page as one snapshot
|
|
91
|
-
* `timeout`
|
|
96
|
+
* Returns every tool page as one snapshot within the client's discovery bounds.
|
|
97
|
+
* `timeout` and `limits` (nonnegative integers) override those bounds, and each
|
|
98
|
+
* page stays within the transport's message size limit.
|
|
92
99
|
*/
|
|
93
100
|
readonly listTools: (
|
|
94
|
-
options?: {
|
|
101
|
+
options?: {
|
|
102
|
+
readonly timeout?: Duration.Input | undefined
|
|
103
|
+
readonly limits?: {
|
|
104
|
+
readonly maxPages?: number | undefined
|
|
105
|
+
readonly maxItems?: number | undefined
|
|
106
|
+
} | undefined
|
|
107
|
+
}
|
|
95
108
|
) => Effect.Effect<ReadonlyArray<McpSchema.Tool>, McpClientError>
|
|
96
109
|
/** Returns every prompt page as one snapshot with the same bounds as `listTools`. */
|
|
97
110
|
readonly listPrompts: (
|
|
98
|
-
options?: {
|
|
111
|
+
options?: {
|
|
112
|
+
readonly timeout?: Duration.Input | undefined
|
|
113
|
+
readonly limits?: {
|
|
114
|
+
readonly maxPages?: number | undefined
|
|
115
|
+
readonly maxItems?: number | undefined
|
|
116
|
+
} | undefined
|
|
117
|
+
}
|
|
99
118
|
) => Effect.Effect<ReadonlyArray<McpSchema.Prompt>, McpClientError>
|
|
100
119
|
/** Gets the messages for a discovered prompt. Unbounded unless `timeout` is supplied. */
|
|
101
120
|
readonly getPrompt: (
|
|
@@ -107,13 +126,71 @@ export interface McpClient {
|
|
|
107
126
|
) => Effect.Effect<McpSchema.GetPromptResult, McpClientError>
|
|
108
127
|
/** Returns every resource page as one snapshot with the same bounds as `listTools`. */
|
|
109
128
|
readonly listResources: (
|
|
110
|
-
options?: {
|
|
129
|
+
options?: {
|
|
130
|
+
readonly timeout?: Duration.Input | undefined
|
|
131
|
+
readonly limits?: {
|
|
132
|
+
readonly maxPages?: number | undefined
|
|
133
|
+
readonly maxItems?: number | undefined
|
|
134
|
+
} | undefined
|
|
135
|
+
}
|
|
111
136
|
) => Effect.Effect<ReadonlyArray<McpSchema.Resource>, McpClientError>
|
|
112
137
|
/** Reads the complete contents of a resource URI. Unbounded unless `timeout` is supplied. */
|
|
113
138
|
readonly readResource: (
|
|
114
139
|
params: { readonly uri: string },
|
|
115
140
|
options?: { readonly timeout?: Duration.Input | undefined }
|
|
116
141
|
) => Effect.Effect<McpSchema.ReadResourceResult, McpClientError>
|
|
142
|
+
/**
|
|
143
|
+
* Returns every skill page as one snapshot with the same bounds as `listTools`, or none
|
|
144
|
+
* when the server does not declare the Skills extension. Fails on an invalid entry, unless
|
|
145
|
+
* `onInvalidSkill` is supplied: invalid entries are then left out and each is passed to it.
|
|
146
|
+
* Requires `extensions: { skills: true }`, as do the other skill methods.
|
|
147
|
+
*/
|
|
148
|
+
readonly listSkills: (
|
|
149
|
+
options?: {
|
|
150
|
+
readonly timeout?: Duration.Input | undefined
|
|
151
|
+
readonly limits?: {
|
|
152
|
+
readonly maxPages?: number | undefined
|
|
153
|
+
readonly maxItems?: number | undefined
|
|
154
|
+
} | undefined
|
|
155
|
+
readonly onInvalidSkill?:
|
|
156
|
+
| ((invalid: { readonly entry: unknown; readonly error: McpClientError }) => Effect.Effect<void>)
|
|
157
|
+
| undefined
|
|
158
|
+
}
|
|
159
|
+
) => Effect.Effect<ReadonlyArray<McpSchema.Skill>, McpClientError>
|
|
160
|
+
/**
|
|
161
|
+
* Gets a skill by the URI of its `SKILL.md`, whether or not `listSkills` returns it.
|
|
162
|
+
* Unbounded unless `timeout` is supplied.
|
|
163
|
+
*/
|
|
164
|
+
readonly getSkill: (
|
|
165
|
+
params: { readonly uri: string },
|
|
166
|
+
options?: { readonly timeout?: Duration.Input | undefined }
|
|
167
|
+
) => Effect.Effect<McpSchema.Skill, McpClientError>
|
|
168
|
+
/**
|
|
169
|
+
* Lists the direct children of a directory in a skill namespace, with the same bounds as
|
|
170
|
+
* `listTools`. Requires the server's `directoryRead` setting.
|
|
171
|
+
*/
|
|
172
|
+
readonly readResourceDirectory: (
|
|
173
|
+
params: { readonly uri: string },
|
|
174
|
+
options?: {
|
|
175
|
+
readonly timeout?: Duration.Input | undefined
|
|
176
|
+
readonly limits?: {
|
|
177
|
+
readonly maxPages?: number | undefined
|
|
178
|
+
readonly maxItems?: number | undefined
|
|
179
|
+
} | undefined
|
|
180
|
+
}
|
|
181
|
+
) => Effect.Effect<ReadonlyArray<McpSchema.Resource>, McpClientError>
|
|
182
|
+
/**
|
|
183
|
+
* Reads a file of a skill and fails with a `ValidationError` unless its bytes match the
|
|
184
|
+
* SHA-256 digest and size listed in the skill's manifest, and, for `SKILL.md`, unless its
|
|
185
|
+
* YAML frontmatter equals the listed frontmatter. A skill whose `resources` is `"dynamic"`
|
|
186
|
+
* has no digests: any file inside its directory can be read, and `SKILL.md` is still
|
|
187
|
+
* checked. Files outside the manifest or directory are never requested. Unbounded unless
|
|
188
|
+
* `timeout` is supplied.
|
|
189
|
+
*/
|
|
190
|
+
readonly readSkillResource: (
|
|
191
|
+
params: { readonly skill: McpSchema.Skill; readonly uri: string },
|
|
192
|
+
options?: { readonly timeout?: Duration.Input | undefined }
|
|
193
|
+
) => Effect.Effect<McpSchema.ReadResourceResult, McpClientError>
|
|
117
194
|
/**
|
|
118
195
|
* Calls a discovered tool, optionally decoding with a schema. Errors retain the full result.
|
|
119
196
|
* Unbounded unless `timeout` is supplied.
|
|
@@ -166,9 +243,20 @@ export const isMcpClient = (value: unknown): value is McpClient => Predicate.has
|
|
|
166
243
|
*
|
|
167
244
|
* `name`, `version` and the optional `title`, `description`, `websiteUrl` and
|
|
168
245
|
* `icons` identify the client to the server. `timeout` defaults to 60 seconds
|
|
169
|
-
* and bounds initialization and each complete `listTools`, `listPrompts
|
|
170
|
-
* `listResources
|
|
171
|
-
*
|
|
246
|
+
* and bounds initialization and each complete `listTools`, `listPrompts`,
|
|
247
|
+
* `listResources`, `listSkills` or `readResourceDirectory` scan.
|
|
248
|
+
* `discoveryLimits` sets the default scan limits of 100 pages and 10,000
|
|
249
|
+
* items. `callTool`, `getPrompt`, `readResource`, `getSkill` and
|
|
250
|
+
* `readSkillResource` have no deadline unless their own `timeout` option is
|
|
251
|
+
* supplied.
|
|
252
|
+
*
|
|
253
|
+
* `extensions` selects the protocol extensions the client declares. With
|
|
254
|
+
* `skills: true` it declares the Skills extension (SEP-2640) and enables
|
|
255
|
+
* `listSkills`, `getSkill`, `readResourceDirectory` and `readSkillResource`;
|
|
256
|
+
* without it they fail with a `ConfigurationError`. They run only against a
|
|
257
|
+
* server that declares the extension too, and skill entries are checked
|
|
258
|
+
* against its rules. `readSkillResource` checks digests with SHA-256 on
|
|
259
|
+
* `crypto.subtle`.
|
|
172
260
|
* Schema decoding requires the schema's services from the caller.
|
|
173
261
|
*
|
|
174
262
|
* @stability experimental
|
|
@@ -183,6 +271,13 @@ export const make: (options: {
|
|
|
183
271
|
readonly websiteUrl?: string | undefined
|
|
184
272
|
readonly icons?: ReadonlyArray<McpSchema.Icon> | undefined
|
|
185
273
|
readonly timeout?: Duration.Input | undefined
|
|
274
|
+
readonly discoveryLimits?: {
|
|
275
|
+
readonly maxPages?: number | undefined
|
|
276
|
+
readonly maxItems?: number | undefined
|
|
277
|
+
} | undefined
|
|
278
|
+
readonly extensions?: {
|
|
279
|
+
readonly skills?: boolean | undefined
|
|
280
|
+
} | undefined
|
|
186
281
|
}) => Effect.Effect<
|
|
187
282
|
McpClient,
|
|
188
283
|
McpClientError,
|
|
@@ -197,6 +292,7 @@ export const make: (options: {
|
|
|
197
292
|
const version = protocol.protocolVersion
|
|
198
293
|
const modern = version === "2026-07-28"
|
|
199
294
|
const timeout = options.timeout ?? DEFAULT_TIMEOUT
|
|
295
|
+
const discoveryLimits = yield* resolveDiscoveryLimits(options.discoveryLimits, DEFAULT_DISCOVERY_LIMITS)
|
|
200
296
|
const closed = yield* Deferred.make<never, McpClientError>()
|
|
201
297
|
yield* Effect.addFinalizer(() =>
|
|
202
298
|
Deferred.fail(closed, fail(new ClosedError({ description: "MCP client scope closed" })))
|
|
@@ -214,10 +310,12 @@ export const make: (options: {
|
|
|
214
310
|
|
|
215
311
|
// Modern requests carry the client identity in every request.
|
|
216
312
|
// https://modelcontextprotocol.io/specification/2026-07-28/basic/index
|
|
313
|
+
const skillsEnabled = options.extensions?.skills === true
|
|
314
|
+
const clientCapabilities = skillsEnabled ? { extensions: { [McpSkills.EXTENSION_ID]: {} } } : {}
|
|
217
315
|
const meta = {
|
|
218
316
|
"io.modelcontextprotocol/protocolVersion": version,
|
|
219
317
|
"io.modelcontextprotocol/clientInfo": clientInfo,
|
|
220
|
-
"io.modelcontextprotocol/clientCapabilities":
|
|
318
|
+
"io.modelcontextprotocol/clientCapabilities": clientCapabilities
|
|
221
319
|
}
|
|
222
320
|
|
|
223
321
|
const request = Effect.fnUntraced(function*<A>(
|
|
@@ -284,7 +382,7 @@ export const make: (options: {
|
|
|
284
382
|
if (!modern) {
|
|
285
383
|
return yield* request<InitializeResult>("initialize", {
|
|
286
384
|
protocolVersion: version,
|
|
287
|
-
capabilities:
|
|
385
|
+
capabilities: clientCapabilities,
|
|
288
386
|
clientInfo
|
|
289
387
|
})
|
|
290
388
|
}
|
|
@@ -334,30 +432,160 @@ export const make: (options: {
|
|
|
334
432
|
? Effect.fail(fail(new UnsupportedError({ description: `Server does not advertise ${kind}` })))
|
|
335
433
|
: request<A>(method, params, tool)
|
|
336
434
|
|
|
435
|
+
const paginate = Effect.fnUntraced(function*<K extends string, A>(
|
|
436
|
+
label: string,
|
|
437
|
+
method: string,
|
|
438
|
+
key: K,
|
|
439
|
+
params: Record<string, unknown>,
|
|
440
|
+
limits: typeof DEFAULT_DISCOVERY_LIMITS
|
|
441
|
+
) {
|
|
442
|
+
const items: Array<A> = []
|
|
443
|
+
const cursors = new Set<string>()
|
|
444
|
+
let cursor: string | undefined
|
|
445
|
+
for (let page = 0; page < limits.maxPages; page++) {
|
|
446
|
+
const result = yield* request<ListResult<K, A>>(method, cursor === undefined ? params : { ...params, cursor })
|
|
447
|
+
if (items.length + result[key].length > limits.maxItems) {
|
|
448
|
+
return yield* limitError(`MCP ${label} discovery exceeded its item limit`)
|
|
449
|
+
}
|
|
450
|
+
for (const item of result[key]) items.push(item)
|
|
451
|
+
if (result.nextCursor === undefined) return items
|
|
452
|
+
if (cursors.has(result.nextCursor)) {
|
|
453
|
+
return yield* limitError(`MCP ${label} discovery repeated a cursor`)
|
|
454
|
+
}
|
|
455
|
+
cursors.add(cursor = result.nextCursor)
|
|
456
|
+
}
|
|
457
|
+
return yield* limitError(`MCP ${label} discovery exceeded its page limit`)
|
|
458
|
+
})
|
|
459
|
+
|
|
337
460
|
const list = <K extends Capability, A>(kind: K) =>
|
|
338
461
|
Effect.fnUntraced(
|
|
339
|
-
function*(
|
|
462
|
+
function*(callOptions?: DiscoveryOptions) {
|
|
463
|
+
const limits = yield* resolveDiscoveryLimits(callOptions?.limits, discoveryLimits)
|
|
340
464
|
if (capabilities[kind] === undefined) return []
|
|
341
|
-
|
|
342
|
-
const cursors = new Set<string>()
|
|
343
|
-
let cursor: string | undefined
|
|
344
|
-
for (let page = 0; page < MAX_DISCOVERY_PAGES; page++) {
|
|
345
|
-
const result = yield* request<ListResult<K, A>>(`${kind}/list`, cursor === undefined ? {} : { cursor })
|
|
346
|
-
if (items.length + result[kind].length > MAX_DISCOVERY_ITEMS) {
|
|
347
|
-
return yield* limitError(`MCP ${kind} discovery exceeded its item limit`)
|
|
348
|
-
}
|
|
349
|
-
items.push(...result[kind])
|
|
350
|
-
if (result.nextCursor === undefined) return items
|
|
351
|
-
if (cursors.has(result.nextCursor)) {
|
|
352
|
-
return yield* limitError(`MCP ${kind} discovery repeated a cursor`)
|
|
353
|
-
}
|
|
354
|
-
cursors.add(cursor = result.nextCursor)
|
|
355
|
-
}
|
|
356
|
-
return yield* limitError(`MCP ${kind} discovery exceeded its page limit`)
|
|
465
|
+
return yield* paginate<K, A>(kind, `${kind}/list`, kind, {}, limits)
|
|
357
466
|
},
|
|
358
467
|
(effect, callOptions) => withDeadline(effect, `${kind} discovery`, callOptions?.timeout ?? timeout)
|
|
359
468
|
)
|
|
360
469
|
|
|
470
|
+
// Hosts must not call the extension's methods unless the server declares it,
|
|
471
|
+
// and the extension requires the resources capability.
|
|
472
|
+
const skillsSettings = capabilities.extensions?.[McpSkills.EXTENSION_ID]
|
|
473
|
+
const servesSkills = isRecord(skillsSettings) && capabilities.resources !== undefined
|
|
474
|
+
const servesDirectories = servesSkills && isRecord(skillsSettings) && skillsSettings.directoryRead === true
|
|
475
|
+
const digest = yield* Effect.provide(Digest.Digest, Digest.layerWebCrypto)
|
|
476
|
+
|
|
477
|
+
const invalid = (description: string) => Effect.fail(fail(new ValidationError({ description })))
|
|
478
|
+
const unsupported = (description: string) => Effect.fail(fail(new UnsupportedError({ description })))
|
|
479
|
+
const requireSkillsOption = skillsEnabled ? Effect.void : Effect.fail(
|
|
480
|
+
fail(new ConfigurationError({ description: "MCP skills methods require extensions: { skills: true }" }))
|
|
481
|
+
)
|
|
482
|
+
|
|
483
|
+
const decodeSkill = Effect.fnUntraced(function*(entry: unknown) {
|
|
484
|
+
const skill = yield* decodeUnknown(McpSchema.Skill, entry) as Effect.Effect<McpSchema.Skill, McpClientError>
|
|
485
|
+
const problem = McpSkills.entryProblem(skill)
|
|
486
|
+
return problem === undefined ? skill : yield* invalid(`Invalid MCP skill ${skill.uri}: ${problem}`)
|
|
487
|
+
})
|
|
488
|
+
|
|
489
|
+
const listSkills = Effect.fnUntraced(
|
|
490
|
+
function*(options?: Parameters<McpClient["listSkills"]>[0]) {
|
|
491
|
+
yield* requireSkillsOption
|
|
492
|
+
const limits = yield* resolveDiscoveryLimits(options?.limits, discoveryLimits)
|
|
493
|
+
if (!servesSkills) return []
|
|
494
|
+
const entries = yield* paginate<"skills", unknown>("skills", "skills/list", "skills", {}, limits)
|
|
495
|
+
// A catalog that changes between pages can list a skill twice; the first entry wins.
|
|
496
|
+
const skills = new Map<string, McpSchema.Skill>()
|
|
497
|
+
for (const entry of entries) {
|
|
498
|
+
const decoded = yield* Effect.result(decodeSkill(entry))
|
|
499
|
+
if (decoded._tag === "Failure") {
|
|
500
|
+
if (options?.onInvalidSkill === undefined) return yield* Effect.fail(decoded.failure)
|
|
501
|
+
yield* options.onInvalidSkill({ entry, error: decoded.failure })
|
|
502
|
+
continue
|
|
503
|
+
}
|
|
504
|
+
if (!skills.has(decoded.success.uri)) skills.set(decoded.success.uri, decoded.success)
|
|
505
|
+
}
|
|
506
|
+
return Array.from(skills.values())
|
|
507
|
+
},
|
|
508
|
+
(effect, callOptions) => withDeadline(effect, "skills discovery", callOptions?.timeout ?? timeout)
|
|
509
|
+
)
|
|
510
|
+
|
|
511
|
+
const getSkill = Effect.fnUntraced(
|
|
512
|
+
function*(params: { readonly uri: string }, _options?: CallOptions) {
|
|
513
|
+
yield* requireSkillsOption
|
|
514
|
+
if (!servesSkills) return yield* unsupported("Server does not declare the MCP Skills extension")
|
|
515
|
+
const result = yield* request<{ readonly skill: unknown }>("skills/get", { uri: params.uri })
|
|
516
|
+
const skill = yield* decodeSkill(result.skill)
|
|
517
|
+
return skill.uri === params.uri
|
|
518
|
+
? skill
|
|
519
|
+
: yield* invalid(`MCP server returned skill ${skill.uri} for ${params.uri}`)
|
|
520
|
+
},
|
|
521
|
+
(effect, _params, callOptions) => withDeadline(effect, "skills/get", callOptions?.timeout)
|
|
522
|
+
)
|
|
523
|
+
|
|
524
|
+
const readResourceDirectory = Effect.fnUntraced(
|
|
525
|
+
function*(params: { readonly uri: string }, options?: DiscoveryOptions) {
|
|
526
|
+
yield* requireSkillsOption
|
|
527
|
+
const limits = yield* resolveDiscoveryLimits(options?.limits, discoveryLimits)
|
|
528
|
+
if (!servesDirectories) {
|
|
529
|
+
return yield* unsupported("Server does not declare directory reads for the MCP Skills extension")
|
|
530
|
+
}
|
|
531
|
+
const entries = yield* paginate<"resources", unknown>(
|
|
532
|
+
"directory",
|
|
533
|
+
"resources/directory/read",
|
|
534
|
+
"resources",
|
|
535
|
+
{ uri: params.uri },
|
|
536
|
+
limits
|
|
537
|
+
)
|
|
538
|
+
// A directory that changes between pages can list a child twice; the first entry wins.
|
|
539
|
+
const children = new Map<string, McpSchema.Resource>()
|
|
540
|
+
for (const entry of entries) {
|
|
541
|
+
const child = yield* decodeUnknown(McpSchema.Resource, entry) as Effect.Effect<
|
|
542
|
+
McpSchema.Resource,
|
|
543
|
+
McpClientError
|
|
544
|
+
>
|
|
545
|
+
if (!McpSkills.isDirectChild(params.uri, child.uri)) {
|
|
546
|
+
return yield* invalid(`${child.uri} is not a child of the MCP directory ${params.uri}`)
|
|
547
|
+
}
|
|
548
|
+
if (!children.has(child.uri)) children.set(child.uri, child)
|
|
549
|
+
}
|
|
550
|
+
return Array.from(children.values())
|
|
551
|
+
},
|
|
552
|
+
(effect, _params, callOptions) => withDeadline(effect, "directory discovery", callOptions?.timeout ?? timeout)
|
|
553
|
+
)
|
|
554
|
+
|
|
555
|
+
const readSkillResource = Effect.fnUntraced(
|
|
556
|
+
function*(params: { readonly skill: McpSchema.Skill; readonly uri: string }, _options?: CallOptions) {
|
|
557
|
+
yield* requireSkillsOption
|
|
558
|
+
const { skill, uri } = params
|
|
559
|
+
// A file missing from the manifest is a change to the skill, not a file to
|
|
560
|
+
// trust. A dynamic skill has no manifest, so any file in its directory is read.
|
|
561
|
+
const manifest = skill.resources === "dynamic" ? undefined : skill.resources
|
|
562
|
+
const listed = manifest?.find((resource) => resource.uri === uri)
|
|
563
|
+
if (manifest === undefined ? !McpSkills.isSkillFile(skill.uri, uri) : listed === undefined) {
|
|
564
|
+
return yield* invalid(`${uri} is not a file of the MCP skill ${skill.uri}`)
|
|
565
|
+
}
|
|
566
|
+
const result = yield* call<McpSchema.ReadResourceResult>("resources", "resources/read", { uri })
|
|
567
|
+
const [content, ...rest] = result.contents
|
|
568
|
+
if (content === undefined || rest.length > 0 || content.uri !== uri) {
|
|
569
|
+
return yield* invalid(`MCP resources/read returned other contents than ${uri}`)
|
|
570
|
+
}
|
|
571
|
+
const bytes = "text" in content ? encoder.encode(content.text) : content.blob
|
|
572
|
+
if (listed !== undefined) {
|
|
573
|
+
const actual = `sha256:${yield* digest.digestHex(Digest.sha256, bytes)}`
|
|
574
|
+
if (bytes.byteLength !== listed.size || actual !== listed.digest) {
|
|
575
|
+
return yield* invalid(`${uri} does not match the digest and size listed by the MCP skill ${skill.uri}`)
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
if (uri !== skill.uri) return result
|
|
579
|
+
// Verified bytes can still carry other frontmatter than the listing, and
|
|
580
|
+
// hosts must not load a skill whose two descriptions differ.
|
|
581
|
+
const frontmatter = McpSkills.parseFrontmatter("text" in content ? content.text : decoder.decode(bytes))
|
|
582
|
+
return frontmatter !== undefined && Equal.equals(frontmatter, skill.frontmatter)
|
|
583
|
+
? result
|
|
584
|
+
: yield* invalid(`The frontmatter of ${uri} differs from the frontmatter listed for the MCP skill`)
|
|
585
|
+
},
|
|
586
|
+
(effect, _params, callOptions) => withDeadline(effect, "resources/read", callOptions?.timeout)
|
|
587
|
+
)
|
|
588
|
+
|
|
361
589
|
const listTools = list<"tools", McpSchema.Tool>("tools")
|
|
362
590
|
|
|
363
591
|
const callTool = Effect.fnUntraced(
|
|
@@ -391,6 +619,7 @@ export const make: (options: {
|
|
|
391
619
|
return {
|
|
392
620
|
[TypeId]: TypeId,
|
|
393
621
|
protocolVersion: version,
|
|
622
|
+
capabilities,
|
|
394
623
|
serverInfo,
|
|
395
624
|
instructions,
|
|
396
625
|
listTools: (callOptions) =>
|
|
@@ -412,6 +641,10 @@ export const make: (options: {
|
|
|
412
641
|
"resources/read",
|
|
413
642
|
callOptions?.timeout
|
|
414
643
|
),
|
|
644
|
+
listSkills,
|
|
645
|
+
getSkill,
|
|
646
|
+
readResourceDirectory,
|
|
647
|
+
readSkillResource,
|
|
415
648
|
callTool: callTool as McpClient["callTool"]
|
|
416
649
|
}
|
|
417
650
|
})
|
|
@@ -421,75 +654,159 @@ export const make: (options: {
|
|
|
421
654
|
// =============================================================================
|
|
422
655
|
|
|
423
656
|
/**
|
|
424
|
-
*
|
|
657
|
+
* Annotation containing the remote descriptor used to bind an MCP tool.
|
|
658
|
+
*
|
|
659
|
+
* **Details**
|
|
660
|
+
*
|
|
661
|
+
* The descriptor is server-provided and should be treated as untrusted.
|
|
662
|
+
*
|
|
663
|
+
* @stability experimental
|
|
664
|
+
* @category services
|
|
665
|
+
* @since 4.1.1
|
|
666
|
+
*/
|
|
667
|
+
export class ToolDescriptor
|
|
668
|
+
extends Context.Service<ToolDescriptor, McpSchema.Tool>()("effect/ai/McpClient/ToolDescriptor")
|
|
669
|
+
{}
|
|
670
|
+
|
|
671
|
+
/**
|
|
672
|
+
* Native tool bound to a remote MCP descriptor with configurable result and failure modes.
|
|
673
|
+
*
|
|
674
|
+
* @stability experimental
|
|
675
|
+
* @category models
|
|
676
|
+
* @since 4.1.1
|
|
677
|
+
*/
|
|
678
|
+
export type RemoteTool<
|
|
679
|
+
ResultMode extends "text" | "complete" = "text",
|
|
680
|
+
FailureMode extends Tool.FailureMode = "error",
|
|
681
|
+
Requirements = never
|
|
682
|
+
> = FailureMode extends Tool.FailureMode ? Tool.Tool<string, {
|
|
683
|
+
readonly parameters: typeof Schema.Unknown
|
|
684
|
+
readonly success: {
|
|
685
|
+
readonly text: typeof Schema.String
|
|
686
|
+
readonly complete: typeof McpSchema.CallToolResult
|
|
687
|
+
}[ResultMode]
|
|
688
|
+
readonly failure: typeof McpClientError
|
|
689
|
+
readonly failureMode: FailureMode
|
|
690
|
+
}, Requirements>
|
|
691
|
+
: never
|
|
692
|
+
|
|
693
|
+
/**
|
|
694
|
+
* Binds remote tools using the standard Toolkit implementation.
|
|
425
695
|
*
|
|
426
696
|
* **Details**
|
|
427
697
|
*
|
|
428
|
-
* Pass
|
|
429
|
-
*
|
|
430
|
-
*
|
|
431
|
-
* or
|
|
432
|
-
*
|
|
698
|
+
* Pass a prefix such as `"reports__"` or an options object. Tools are discovered
|
|
699
|
+
* unless an approved `tools` snapshot is supplied. Remote JSON Schemas are
|
|
700
|
+
* advertised as-is, and model tool names must be 1–64 ASCII letters, digits,
|
|
701
|
+
* underscores or hyphens. Build a new toolkit to pick up remote changes.
|
|
702
|
+
*
|
|
703
|
+
* By default results are text and failures fail the Effect.
|
|
704
|
+
* `resultMode: "complete"` returns the full `CallToolResult` and sends its
|
|
705
|
+
* content to the model as text and file parts. `failureMode: "return"` returns
|
|
706
|
+
* typed failures, including transport errors, to the model; `ToolError` keeps
|
|
707
|
+
* the failed result. Type arguments that select a non-default mode require the
|
|
708
|
+
* matching option.
|
|
709
|
+
*
|
|
710
|
+
* `timeout` bounds each tool call. `trustAnnotations` copies server hints into
|
|
711
|
+
* tool annotations but never sets approval. `transformTool` receives each
|
|
712
|
+
* annotated tool before binding and must return a tool derived from it, e.g.
|
|
713
|
+
* with `setNeedsApproval`, `annotate` or `addDependency`, keeping its name,
|
|
714
|
+
* schemas and failure mode.
|
|
433
715
|
*
|
|
434
716
|
* @stability experimental
|
|
435
717
|
* @category constructors
|
|
436
718
|
* @since 4.0.6
|
|
437
719
|
*/
|
|
438
|
-
export
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
> = []
|
|
461
|
-
const handlers: Record<string, (params: unknown) => Effect.Effect<string, McpClientError>> = Object.create(null)
|
|
462
|
-
|
|
463
|
-
for (const descriptor of descriptors) {
|
|
464
|
-
const name = `${prefix}${descriptor.name}`
|
|
465
|
-
if (!/^[a-zA-Z0-9_-]{1,64}$/.test(name) || Object.hasOwn(handlers, name)) {
|
|
466
|
-
return yield* fail(new ConfigurationError({ description: `Invalid or duplicate MCP model tool name: ${name}` }))
|
|
720
|
+
export function toolkit(
|
|
721
|
+
client: McpClient,
|
|
722
|
+
prefix?: string
|
|
723
|
+
): Effect.Effect<Toolkit.WithHandler<Record<string, RemoteTool>>, McpClientError>
|
|
724
|
+
export function toolkit<
|
|
725
|
+
ResultMode extends "text" | "complete" = "text",
|
|
726
|
+
FailureMode extends Tool.FailureMode = "error",
|
|
727
|
+
Requirements = never
|
|
728
|
+
>(
|
|
729
|
+
client: McpClient,
|
|
730
|
+
options:
|
|
731
|
+
& {
|
|
732
|
+
readonly prefix?: string | undefined
|
|
733
|
+
readonly tools?: ReadonlyArray<McpSchema.Tool> | undefined
|
|
734
|
+
readonly trustAnnotations?: boolean | undefined
|
|
735
|
+
readonly timeout?: Duration.Input | undefined
|
|
736
|
+
readonly transformTool?:
|
|
737
|
+
| ((
|
|
738
|
+
tool: RemoteTool<ResultMode, FailureMode>,
|
|
739
|
+
descriptor: McpSchema.Tool
|
|
740
|
+
) => RemoteTool<ResultMode, FailureMode, Requirements>)
|
|
741
|
+
| undefined
|
|
467
742
|
}
|
|
468
|
-
|
|
469
|
-
|
|
743
|
+
// A mode may be omitted only when its type is exactly the default.
|
|
744
|
+
& ([ResultMode] extends ["text"] ? { readonly resultMode?: "text" | undefined }
|
|
745
|
+
: { readonly resultMode: ResultMode })
|
|
746
|
+
& ([FailureMode] extends ["error"] ? { readonly failureMode?: "error" | undefined }
|
|
747
|
+
: { readonly failureMode: FailureMode })
|
|
748
|
+
): Effect.Effect<
|
|
749
|
+
Toolkit.WithHandler<Record<string, RemoteTool<ResultMode, FailureMode, Requirements>>>,
|
|
750
|
+
McpClientError
|
|
751
|
+
>
|
|
752
|
+
export function toolkit(
|
|
753
|
+
client: McpClient,
|
|
754
|
+
options: string | ToolkitConfig = ""
|
|
755
|
+
): Effect.Effect<Toolkit.WithHandler<any>, McpClientError> {
|
|
756
|
+
return Effect.gen(function*() {
|
|
757
|
+
const config: ToolkitConfig = typeof options === "string" ? { prefix: options } : options
|
|
758
|
+
const descriptors = config.tools ?? (yield* client.listTools())
|
|
759
|
+
const complete = config.resultMode === "complete"
|
|
760
|
+
const success = complete ? McpSchema.CallToolResult : Schema.String
|
|
761
|
+
const failureMode = config.failureMode ?? "error"
|
|
762
|
+
const tools: Array<Tool.Any> = []
|
|
763
|
+
const handlers: Record<
|
|
764
|
+
string,
|
|
765
|
+
(params: unknown) => Effect.Effect<string | McpSchema.CallToolResult, McpClientError>
|
|
766
|
+
> = Object.create(null)
|
|
767
|
+
|
|
768
|
+
for (const descriptor of descriptors) {
|
|
769
|
+
const name = `${config.prefix ?? ""}${descriptor.name}`
|
|
770
|
+
if (!/^[a-zA-Z0-9_-]{1,64}$/.test(name) || Object.hasOwn(handlers, name)) {
|
|
771
|
+
return yield* fail(new ConfigurationError({ description: `Invalid or duplicate MCP model tool name: ${name}` }))
|
|
772
|
+
}
|
|
773
|
+
const spec = {
|
|
470
774
|
parameters: descriptor.inputSchema,
|
|
471
775
|
description: descriptor.description,
|
|
472
|
-
success
|
|
473
|
-
failure: McpClientError
|
|
474
|
-
|
|
475
|
-
)
|
|
476
|
-
handlers[name] = Effect.fnUntraced(function*(params: unknown) {
|
|
477
|
-
const args = yield* decodeToolArguments(params).pipe(
|
|
478
|
-
Effect.mapError((cause) =>
|
|
479
|
-
fail(new ValidationError({ description: "MCP tool arguments must be a JSON object", cause }))
|
|
480
|
-
)
|
|
481
|
-
)
|
|
482
|
-
const result = yield* client.callTool({ tool: descriptor, arguments: args })
|
|
483
|
-
if (result.isError === true) {
|
|
484
|
-
return yield* fail(new ToolError({ result }))
|
|
776
|
+
success,
|
|
777
|
+
failure: McpClientError,
|
|
778
|
+
toModelOutput: complete ? toModelOutput : undefined
|
|
485
779
|
}
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
780
|
+
// The identical branches narrow `failureMode` to a single `RemoteTool` member.
|
|
781
|
+
let tool: RemoteTool<"text" | "complete", Tool.FailureMode> = (
|
|
782
|
+
failureMode === "return"
|
|
783
|
+
? Tool.dynamic(name, { ...spec, failureMode })
|
|
784
|
+
: Tool.dynamic(name, { ...spec, failureMode })
|
|
785
|
+
).annotate(Tool.Strict, false).annotate(ToolDescriptor, descriptor)
|
|
786
|
+
if (config.trustAnnotations && descriptor.annotations !== undefined) {
|
|
787
|
+
tool = tool.annotate(Tool.Readonly, descriptor.annotations.readOnlyHint)
|
|
788
|
+
.annotate(Tool.Destructive, descriptor.annotations.destructiveHint)
|
|
789
|
+
.annotate(Tool.Idempotent, descriptor.annotations.idempotentHint)
|
|
790
|
+
.annotate(Tool.OpenWorld, descriptor.annotations.openWorldHint)
|
|
791
|
+
if (descriptor.annotations.title !== undefined) tool = tool.annotate(Tool.Title, descriptor.annotations.title)
|
|
792
|
+
}
|
|
793
|
+
tools.push(config.transformTool === undefined ? tool : config.transformTool(tool, descriptor))
|
|
794
|
+
handlers[name] = Effect.fnUntraced(function*(params: unknown) {
|
|
795
|
+
const args = yield* decodeToolArguments(params).pipe(
|
|
796
|
+
Effect.mapError((cause) =>
|
|
797
|
+
fail(new ValidationError({ description: "MCP tool arguments must be a JSON object", cause }))
|
|
798
|
+
)
|
|
799
|
+
)
|
|
800
|
+
const result = yield* client.callTool({ tool: descriptor, arguments: args }, { timeout: config.timeout })
|
|
801
|
+
if (result.isError === true) return yield* fail(new ToolError({ result }))
|
|
802
|
+
return complete ? result : yield* toText(result)
|
|
803
|
+
})
|
|
804
|
+
}
|
|
489
805
|
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
})
|
|
806
|
+
const toolkit = Toolkit.make(...tools)
|
|
807
|
+
return yield* toolkit.pipe(Effect.provideContext(yield* toolkit.toHandlers(handlers)))
|
|
808
|
+
});
|
|
809
|
+
}
|
|
493
810
|
|
|
494
811
|
// =============================================================================
|
|
495
812
|
// Transports
|
|
@@ -687,6 +1004,13 @@ export const layerStdio: (options: {
|
|
|
687
1004
|
readonly websiteUrl?: string | undefined
|
|
688
1005
|
readonly icons?: ReadonlyArray<McpSchema.Icon> | undefined
|
|
689
1006
|
readonly timeout?: Duration.Input | undefined
|
|
1007
|
+
readonly discoveryLimits?: {
|
|
1008
|
+
readonly maxPages?: number | undefined
|
|
1009
|
+
readonly maxItems?: number | undefined
|
|
1010
|
+
} | undefined
|
|
1011
|
+
readonly extensions?: {
|
|
1012
|
+
readonly skills?: boolean | undefined
|
|
1013
|
+
} | undefined
|
|
690
1014
|
readonly command: ChildProcess.Command
|
|
691
1015
|
readonly protocol: McpProtocol.ProtocolAdapter<ProtocolVersion>
|
|
692
1016
|
readonly maxMessageBytes?: ByteSize.Input | undefined
|
|
@@ -921,6 +1245,13 @@ export const layerHttp: (options: {
|
|
|
921
1245
|
readonly websiteUrl?: string | undefined
|
|
922
1246
|
readonly icons?: ReadonlyArray<McpSchema.Icon> | undefined
|
|
923
1247
|
readonly timeout?: Duration.Input | undefined
|
|
1248
|
+
readonly discoveryLimits?: {
|
|
1249
|
+
readonly maxPages?: number | undefined
|
|
1250
|
+
readonly maxItems?: number | undefined
|
|
1251
|
+
} | undefined
|
|
1252
|
+
readonly extensions?: {
|
|
1253
|
+
readonly skills?: boolean | undefined
|
|
1254
|
+
} | undefined
|
|
924
1255
|
readonly url: string | URL
|
|
925
1256
|
readonly protocol: McpProtocol.ProtocolAdapter<ProtocolVersion>
|
|
926
1257
|
readonly headers?: Readonly<Record<string, string>> | undefined
|
|
@@ -940,8 +1271,7 @@ const TransportTypeId = "~effect/ai/McpClient/Transport" as const
|
|
|
940
1271
|
const DEFAULT_MAX_MESSAGE_BYTES = ByteSize.mebibytes(16)
|
|
941
1272
|
const DEFAULT_TIMEOUT = Duration.seconds(60)
|
|
942
1273
|
const CLOSE_TIMEOUT = Duration.seconds(5)
|
|
943
|
-
const
|
|
944
|
-
const MAX_DISCOVERY_ITEMS = 10_000
|
|
1274
|
+
const DEFAULT_DISCOVERY_LIMITS = { maxPages: 100, maxItems: 10_000 }
|
|
945
1275
|
const HEADER_TOKEN = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/
|
|
946
1276
|
const encoder = new TextEncoder()
|
|
947
1277
|
const decoder = new TextDecoder()
|
|
@@ -952,6 +1282,10 @@ type Options = Parameters<typeof make>[0]
|
|
|
952
1282
|
|
|
953
1283
|
type Capability = "tools" | "prompts" | "resources"
|
|
954
1284
|
|
|
1285
|
+
type DiscoveryOptions = NonNullable<Parameters<McpClient["listTools"]>[0]>
|
|
1286
|
+
|
|
1287
|
+
type DiscoveryLimits = DiscoveryOptions["limits"]
|
|
1288
|
+
|
|
955
1289
|
interface CallOptions {
|
|
956
1290
|
readonly timeout?: Duration.Input | undefined
|
|
957
1291
|
}
|
|
@@ -969,6 +1303,19 @@ type ListResult<K extends string, A> = Record<K, ReadonlyArray<A>> & { readonly
|
|
|
969
1303
|
const fail = (reason: McpClientErrorReason) => new McpClientError({ reason })
|
|
970
1304
|
const limitError = (description: string) => fail(new LimitError({ description }))
|
|
971
1305
|
|
|
1306
|
+
const resolveDiscoveryLimits = (
|
|
1307
|
+
limits: DiscoveryLimits,
|
|
1308
|
+
defaults: typeof DEFAULT_DISCOVERY_LIMITS
|
|
1309
|
+
): Effect.Effect<typeof DEFAULT_DISCOVERY_LIMITS, McpClientError> => {
|
|
1310
|
+
const maxPages = limits?.maxPages ?? defaults.maxPages
|
|
1311
|
+
const maxItems = limits?.maxItems ?? defaults.maxItems
|
|
1312
|
+
return Number.isSafeInteger(maxPages) && maxPages >= 0 && Number.isSafeInteger(maxItems) && maxItems >= 0
|
|
1313
|
+
? Effect.succeed({ maxPages, maxItems })
|
|
1314
|
+
: Effect.fail(
|
|
1315
|
+
fail(new ConfigurationError({ description: "MCP discovery limits must be nonnegative safe integers" }))
|
|
1316
|
+
)
|
|
1317
|
+
}
|
|
1318
|
+
|
|
972
1319
|
const validationError = (cause: Schema.SchemaError) =>
|
|
973
1320
|
fail(new ValidationError({ description: "Invalid MCP payload", cause }))
|
|
974
1321
|
|
|
@@ -1035,7 +1382,8 @@ const isRequest = (message: McpSchema.JsonRpcMessage): message is McpSchema.Json
|
|
|
1035
1382
|
|
|
1036
1383
|
const isResponse = (message: McpSchema.JsonRpcMessage): message is McpSchema.JsonRpcResponse => !("method" in message)
|
|
1037
1384
|
|
|
1038
|
-
// The client advertises no
|
|
1385
|
+
// The client advertises no capability that lets a server send it requests, so it answers pings and
|
|
1386
|
+
// declines every other server request.
|
|
1039
1387
|
const replyToServer = (request: McpSchema.JsonRpcRequest): McpSchema.JsonRpcResponse =>
|
|
1040
1388
|
request.method === "ping"
|
|
1041
1389
|
? { jsonrpc: "2.0", id: request.id, result: {} }
|
|
@@ -1175,6 +1523,57 @@ const routingHeaders = Effect.fnUntraced(function*(message: McpSchema.JsonRpcMes
|
|
|
1175
1523
|
// Internal: Toolkit
|
|
1176
1524
|
// =============================================================================
|
|
1177
1525
|
|
|
1526
|
+
interface ToolkitConfig {
|
|
1527
|
+
readonly prefix?: string | undefined
|
|
1528
|
+
readonly tools?: ReadonlyArray<McpSchema.Tool> | undefined
|
|
1529
|
+
readonly resultMode?: "text" | "complete" | undefined
|
|
1530
|
+
readonly failureMode?: Tool.FailureMode | undefined
|
|
1531
|
+
readonly trustAnnotations?: boolean | undefined
|
|
1532
|
+
readonly timeout?: Duration.Input | undefined
|
|
1533
|
+
readonly transformTool?:
|
|
1534
|
+
| ((tool: RemoteTool<"text" | "complete", Tool.FailureMode>, descriptor: McpSchema.Tool) => Tool.Any)
|
|
1535
|
+
| undefined
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1538
|
+
const toModelOutput = (result: McpSchema.CallToolResult): Array<Prompt.ToolModelOutputPart> => {
|
|
1539
|
+
const parts: Array<Prompt.ToolModelOutputPart> = []
|
|
1540
|
+
for (const block of result.content) {
|
|
1541
|
+
switch (block.type) {
|
|
1542
|
+
case "text": {
|
|
1543
|
+
parts.push(Prompt.textPart({ text: block.text }))
|
|
1544
|
+
break
|
|
1545
|
+
}
|
|
1546
|
+
case "image":
|
|
1547
|
+
case "audio": {
|
|
1548
|
+
parts.push(Prompt.filePart({ mediaType: block.mimeType, data: block.data }))
|
|
1549
|
+
break
|
|
1550
|
+
}
|
|
1551
|
+
case "resource_link": {
|
|
1552
|
+
parts.push(Prompt.textPart({ text: resourceLinkText(block) }))
|
|
1553
|
+
break
|
|
1554
|
+
}
|
|
1555
|
+
case "resource": {
|
|
1556
|
+
parts.push(
|
|
1557
|
+
"text" in block.resource
|
|
1558
|
+
? Prompt.textPart({ text: block.resource.text })
|
|
1559
|
+
: Prompt.filePart({
|
|
1560
|
+
mediaType: block.resource.mimeType ?? "application/octet-stream",
|
|
1561
|
+
data: block.resource.blob
|
|
1562
|
+
})
|
|
1563
|
+
)
|
|
1564
|
+
break
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1568
|
+
if (parts.length === 0 && result.structuredContent !== undefined) {
|
|
1569
|
+
parts.push(Prompt.textPart({ text: JSON.stringify(result.structuredContent) }))
|
|
1570
|
+
}
|
|
1571
|
+
return parts
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
const resourceLinkText = (link: McpSchema.ResourceLink) =>
|
|
1575
|
+
`${link.title ?? link.name}: ${link.uri}${link.description === undefined ? "" : `\n${link.description}`}`
|
|
1576
|
+
|
|
1178
1577
|
const toText = (result: McpSchema.CallToolResult): Effect.Effect<string, McpClientError> => {
|
|
1179
1578
|
const unsupported = (content: string) =>
|
|
1180
1579
|
Effect.fail(
|
|
@@ -1188,9 +1587,7 @@ const toText = (result: McpSchema.CallToolResult): Effect.Effect<string, McpClie
|
|
|
1188
1587
|
break
|
|
1189
1588
|
}
|
|
1190
1589
|
case "resource_link": {
|
|
1191
|
-
parts.push(
|
|
1192
|
-
`${block.title ?? block.name}: ${block.uri}${block.description === undefined ? "" : `\n${block.description}`}`
|
|
1193
|
-
)
|
|
1590
|
+
parts.push(resourceLinkText(block))
|
|
1194
1591
|
break
|
|
1195
1592
|
}
|
|
1196
1593
|
case "resource": {
|