connectors 0.1.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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +35 -0
- data/CONNECTORS_FRAMEWORK.md +799 -0
- data/CONTRIBUTING.md +60 -0
- data/MCP_CLIENT.md +168 -0
- data/MIT-LICENSE +20 -0
- data/README.md +146 -0
- data/app/connectors/clickup/connector.rb +13 -0
- data/app/connectors/gmail/api.rb +114 -0
- data/app/connectors/gmail/connector.rb +921 -0
- data/app/connectors/gmail/mime_builder.rb +261 -0
- data/app/connectors/gmail/mime_parser.rb +106 -0
- data/app/connectors/gmail/polling.rb +154 -0
- data/app/connectors/remote_mcp/connector.rb +18 -0
- data/app/connectors/resend/connector.rb +218 -0
- data/app/controllers/concerns/connectors/grant_access.rb +43 -0
- data/app/controllers/connectors/actions_controller.rb +66 -0
- data/app/controllers/connectors/application_controller.rb +5 -0
- data/app/controllers/connectors/credentials_controller.rb +245 -0
- data/app/controllers/connectors/grants_controller.rb +123 -0
- data/app/controllers/connectors/mcp_controller.rb +88 -0
- data/app/controllers/connectors/oauth_controller.rb +134 -0
- data/app/controllers/connectors/types_controller.rb +144 -0
- data/app/controllers/connectors/webhooks_controller.rb +105 -0
- data/app/jobs/connectors/application_job.rb +4 -0
- data/app/jobs/connectors/deliver_webhook_job.rb +27 -0
- data/app/jobs/connectors/poll_job.rb +49 -0
- data/app/models/connectors/application_record.rb +5 -0
- data/app/models/connectors/credential_share.rb +31 -0
- data/app/models/connectors/grant.rb +103 -0
- data/app/models/connectors/mcp_authorization.rb +6 -0
- data/app/models/connectors/mcp_interaction.rb +6 -0
- data/app/models/connectors/poll_state.rb +17 -0
- data/app/models/connectors/webhook_event.rb +19 -0
- data/config/routes.rb +68 -0
- data/db/migrate/20260518210324_create_connectors_grants.rb +49 -0
- data/db/migrate/20260518214609_create_connectors_webhook_events.rb +35 -0
- data/db/migrate/20260521140000_create_connectors_credential_shares.rb +26 -0
- data/db/migrate/20260922120000_create_connectors_poll_states.rb +12 -0
- data/db/migrate/20260922130000_create_connectors_mcp_transactions.rb +18 -0
- data/docs/adding-connectors.md +92 -0
- data/docs/architecture.md +71 -0
- data/docs/releasing.md +60 -0
- data/lib/connectors/action.rb +90 -0
- data/lib/connectors/action_builder.rb +125 -0
- data/lib/connectors/action_runner.rb +99 -0
- data/lib/connectors/auth/scheme/api_key.rb +51 -0
- data/lib/connectors/auth/scheme/oauth2.rb +23 -0
- data/lib/connectors/auth/scheme.rb +35 -0
- data/lib/connectors/auth.rb +4 -0
- data/lib/connectors/auth_injection.rb +65 -0
- data/lib/connectors/client_builder.rb +87 -0
- data/lib/connectors/configuration.rb +138 -0
- data/lib/connectors/connector.rb +565 -0
- data/lib/connectors/credential_schema.rb +219 -0
- data/lib/connectors/credential_tester.rb +85 -0
- data/lib/connectors/credential_type_registry.rb +162 -0
- data/lib/connectors/credential_types/http_auth.rb +171 -0
- data/lib/connectors/engine.rb +123 -0
- data/lib/connectors/errors.rb +88 -0
- data/lib/connectors/grant_policy.rb +13 -0
- data/lib/connectors/mcp/access.rb +50 -0
- data/lib/connectors/mcp/authorization.rb +177 -0
- data/lib/connectors/mcp/authorization_context.rb +25 -0
- data/lib/connectors/mcp/authorization_discovery.rb +42 -0
- data/lib/connectors/mcp/cancellation.rb +36 -0
- data/lib/connectors/mcp/client.rb +137 -0
- data/lib/connectors/mcp/connection_config.rb +48 -0
- data/lib/connectors/mcp/http.rb +108 -0
- data/lib/connectors/mcp/interaction.rb +82 -0
- data/lib/connectors/mcp/pending_transaction.rb +26 -0
- data/lib/connectors/mcp/protocol/2026-07-28.json +3963 -0
- data/lib/connectors/mcp/protocol/LICENSE +216 -0
- data/lib/connectors/mcp/protocol/README.md +8 -0
- data/lib/connectors/mcp/protocol_schema.rb +30 -0
- data/lib/connectors/mcp/schema.rb +45 -0
- data/lib/connectors/mcp/settings.rb +27 -0
- data/lib/connectors/mcp/token_endpoint.rb +48 -0
- data/lib/connectors/mcp/transport.rb +105 -0
- data/lib/connectors/mcp.rb +69 -0
- data/lib/connectors/middleware/authenticate_generic.rb +79 -0
- data/lib/connectors/middleware/auto_refresh.rb +71 -0
- data/lib/connectors/middleware/error_normalization.rb +45 -0
- data/lib/connectors/middleware/grant_status.rb +19 -0
- data/lib/connectors/middleware/pre_authentication.rb +54 -0
- data/lib/connectors/middleware/rate_limit.rb +40 -0
- data/lib/connectors/oauth/authorize_url.rb +78 -0
- data/lib/connectors/oauth/client_authentication.rb +24 -0
- data/lib/connectors/oauth/client_credentials.rb +43 -0
- data/lib/connectors/oauth/grant_writer.rb +73 -0
- data/lib/connectors/oauth/pkce.rb +32 -0
- data/lib/connectors/oauth/revoke.rb +72 -0
- data/lib/connectors/oauth/state.rb +41 -0
- data/lib/connectors/oauth/token_exchange.rb +69 -0
- data/lib/connectors/oauth/token_response.rb +40 -0
- data/lib/connectors/oauth.rb +4 -0
- data/lib/connectors/oauth1.rb +151 -0
- data/lib/connectors/permission_check.rb +33 -0
- data/lib/connectors/poll_runner.rb +50 -0
- data/lib/connectors/pre_authentication_helpers.rb +76 -0
- data/lib/connectors/registry.rb +38 -0
- data/lib/connectors/version.rb +3 -0
- data/lib/connectors/webhook_context.rb +62 -0
- data/lib/connectors/webhook_lifecycle.rb +69 -0
- data/lib/connectors/webhook_methods.rb +48 -0
- data/lib/connectors/webhooks/verifier.rb +31 -0
- data/lib/connectors/webhooks.rb +5 -0
- data/lib/connectors.rb +50 -0
- data/lib/tasks/connectors_mcp.rake +9 -0
- data/lib/tasks/connectors_tasks.rake +4 -0
- data/openapi.yaml +1099 -0
- metadata +263 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
module Connectors
|
|
2
|
+
# Host-app integration surface. The engine never references host constants
|
|
3
|
+
# like `User` directly — it goes through this block, which the host wires up
|
|
4
|
+
# in an initializer:
|
|
5
|
+
#
|
|
6
|
+
# # config/initializers/connectors.rb
|
|
7
|
+
# Connectors.configure do |c|
|
|
8
|
+
# c.owner_class_name = "User"
|
|
9
|
+
# c.current_owner_resolver = ->(controller) { controller.current_user }
|
|
10
|
+
# c.host_base_url = ENV.fetch("APP_BASE_URL", "http://localhost:3000")
|
|
11
|
+
# c.oauth_credentials = {
|
|
12
|
+
# slack: { client_id: ENV["SLACK_CLIENT_ID"], client_secret: ENV["SLACK_CLIENT_SECRET"] },
|
|
13
|
+
# linear: { client_id: ENV["LINEAR_CLIENT_ID"], client_secret: ENV["LINEAR_CLIENT_SECRET"] }
|
|
14
|
+
# }
|
|
15
|
+
# c.on_webhook = ->(event) { Inbox::IngestJob.perform_later(event.id) }
|
|
16
|
+
# end
|
|
17
|
+
class Configuration
|
|
18
|
+
attr_accessor :owner_class_name,
|
|
19
|
+
:current_owner_resolver,
|
|
20
|
+
:host_base_url,
|
|
21
|
+
:app_callback_url,
|
|
22
|
+
:on_webhook,
|
|
23
|
+
:principal_resolver,
|
|
24
|
+
:secrets_resolver,
|
|
25
|
+
:secrets_managed_fields_for
|
|
26
|
+
|
|
27
|
+
attr_reader :oauth_credentials
|
|
28
|
+
|
|
29
|
+
def mcp
|
|
30
|
+
@mcp ||= MCP::Settings.new
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def resolved_mcp_callback_url
|
|
34
|
+
mcp.callback_url || "#{host_base_url.to_s.chomp('/')}#{Connectors::Engine.mount_path}/mcp/oauth/callback"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def initialize
|
|
38
|
+
@oauth_credentials = {}
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def oauth_credentials=(map)
|
|
42
|
+
@oauth_credentials = (map || {}).transform_keys(&:to_sym)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def owner_class
|
|
46
|
+
raise Error.new("Connectors.configuration.owner_class_name is not set") if owner_class_name.nil?
|
|
47
|
+
owner_class_name.constantize
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def oauth_credentials_for(connector_key)
|
|
51
|
+
@oauth_credentials.fetch(connector_key.to_sym) do
|
|
52
|
+
raise Error.new("no oauth_credentials configured for #{connector_key.inspect}")
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def resolve_owner(controller)
|
|
57
|
+
raise Error.new("Connectors.configuration.current_owner_resolver is not set") if current_owner_resolver.nil?
|
|
58
|
+
current_owner_resolver.call(controller)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# The host wires in a block that, given the controller (or any context
|
|
62
|
+
# with `current_user`/etc.), returns an array of `[principal_type,
|
|
63
|
+
# principal_id]` pairs representing every identity the requester has
|
|
64
|
+
# for sharing-visibility purposes.
|
|
65
|
+
#
|
|
66
|
+
# Default: just the owner — `[["<owner_class_name>", owner.id]]`. EE
|
|
67
|
+
# hosts override to include teams / projects:
|
|
68
|
+
#
|
|
69
|
+
# c.principal_resolver = ->(ctrl) {
|
|
70
|
+
# [
|
|
71
|
+
# ["User", ctrl.current_user.id],
|
|
72
|
+
# *ctrl.current_user.team_ids.map { |id| ["Team", id] },
|
|
73
|
+
# ]
|
|
74
|
+
# }
|
|
75
|
+
# n8n parity: external secrets EE module
|
|
76
|
+
# (`external-secrets.controller.ee.ts`). Host wires a block that takes a
|
|
77
|
+
# Grant (one whose `is_managed?` is true) and returns the hash to merge
|
|
78
|
+
# into the credentials at request time. Returned values overwrite the
|
|
79
|
+
# DB-stored values for the lifetime of that Grant instance.
|
|
80
|
+
#
|
|
81
|
+
# c.secrets_resolver = ->(grant) {
|
|
82
|
+
# vault_client.read("kv/data/connectors/#{grant.connector_key}/#{grant.external_ref}")
|
|
83
|
+
# }
|
|
84
|
+
def resolve_secrets(grant)
|
|
85
|
+
return {} if secrets_resolver.nil?
|
|
86
|
+
secrets_resolver.call(grant).to_h.transform_keys(&:to_s)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Per-connector list of field names sourced from the vault — surfaces
|
|
90
|
+
# in `GET /connectors/types/:name` as `__overwritten_properties` so the
|
|
91
|
+
# editor can hide / lock those fields. n8n parity:
|
|
92
|
+
# `frontend.service.ts:681-705` — same shape, just emitted server-side
|
|
93
|
+
# instead of recomputed in the editor.
|
|
94
|
+
def managed_fields_for(connector_key)
|
|
95
|
+
return [] if secrets_managed_fields_for.nil?
|
|
96
|
+
Array(secrets_managed_fields_for.call(connector_key)).map(&:to_s)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def resolve_principals(controller)
|
|
100
|
+
return principal_resolver.call(controller) if principal_resolver
|
|
101
|
+
owner = resolve_owner(controller)
|
|
102
|
+
return [] if owner.nil?
|
|
103
|
+
[ [ owner.class.name, owner.id ] ]
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# The URL the OAuth provider redirects back to after consent. Defaults
|
|
107
|
+
# to the engine's own callback path (n8n-style: provider → backend →
|
|
108
|
+
# render HTML → close popup). For split frontend/backend setups
|
|
109
|
+
# (Activepieces-style: provider → frontend → POST exchange → backend),
|
|
110
|
+
# set this to the FRONTEND's callback page, e.g.
|
|
111
|
+
# `"http://localhost:3001/oauth/callback"`.
|
|
112
|
+
#
|
|
113
|
+
# Either way it must MATCH a value registered in the provider's app
|
|
114
|
+
# console; if it doesn't, Google/Slack/etc. respond with
|
|
115
|
+
# `redirect_uri_mismatch` before consent even renders.
|
|
116
|
+
def resolved_app_callback_url(connector_key)
|
|
117
|
+
return app_callback_url if app_callback_url.present?
|
|
118
|
+
base = host_base_url or
|
|
119
|
+
raise Error.new("Connectors.configuration.host_base_url is not set")
|
|
120
|
+
"#{base.chomp('/')}#{Connectors::Engine.mount_path}/#{connector_key}/callback"
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
class << self
|
|
125
|
+
def configure
|
|
126
|
+
yield configuration
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def configuration
|
|
130
|
+
@configuration ||= Configuration.new
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# For tests — reset between cases.
|
|
134
|
+
def reset_configuration!
|
|
135
|
+
@configuration = Configuration.new
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|