Skip to content

Commit 10283e0

Browse files
committed
enhance connection handling
1 parent 9b056b2 commit 10283e0

File tree

10 files changed

+405
-76
lines changed

10 files changed

+405
-76
lines changed

README.md

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ The easiest way to try the toolbox is to run the server with both HTTP and
6565
STDIO transports enabled:
6666

6767

68-
```bash
68+
```bash cd
6969
go run ./cmd/mcp-sqlkit -a :5000
7070
```
7171

@@ -185,16 +185,15 @@ The toolbox registers the following MCP tools (see `mcp/tool.go`).
185185
| `dbQuery` | Execute a SQL query and return the result set | `db/query.Input` |
186186
| `dbExec` | Execute DML/DDL and return rows affected | `db/exec.Input` |
187187
| `dbListConnections` | List connectors visible to the caller | `db/connector.ListInput` |
188-
| `dbAddConnection` | Register a new connector | `db/connector.ConnectionInput` |
189-
| `dbUpdateConnection` | Update an existing connector (upsert) | `db/connector.ConnectionInput` |
188+
| `dbSetConnection` | Create or update a connector (upsert) | `db/connector.ConnectionInput` |
190189
| `dbListTables` | List tables for a given catalog/schema | `db/meta.ListTablesInput` |
191190
| `dbListColumns` | List columns for a given table | `db/meta.ListColumnsInput` |
192191

193192

194193
### Example – query a MySQL database
195194

196-
1. Register a connector (either via `dbAddConnection` or through the browser
197-
secret-elicitation flow described later). With `dbAddConnection` you pass only
195+
1. Register a connector (either via `dbSetConnection` or through the browser
196+
secret-elicitation flow described later). With `dbSetConnection` you pass only
198197
**non-secret** fields:
199198

200199
```jsonc
@@ -317,8 +316,8 @@ sequenceDiagram
317316
participant UI as "Browser (UI)"
318317
participant SS as "Secret Store"
319318
320-
U->>C: dbAddConnection (missing secret)
321-
C->>S: rpc dbAddConnection
319+
U->>C: dbSetConnection (missing secret)
320+
C->>S: rpc dbSetConnection
322321
S-->>C: Elicit request (flowURI to secret page)
323322
note over S: entry stored in-memory
324323
C-->>U: open browser to flowURI
@@ -344,8 +343,8 @@ sequenceDiagram
344343
participant OP as "OAuth Provider"
345344
participant SS as "Secret Store"
346345
347-
U->>C: dbAddConnection (BigQuery etc.)
348-
C->>S: rpc dbAddConnection
346+
U->>C: dbSetConnection (BigQuery etc.)
347+
C->>S: rpc dbSetConnection
349348
S-->>C: Elicit request (flowURI)
350349
C-->>U: open browser to flowURI
351350
U->>UI: navigate

db/connector/add.go

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,21 @@ package connector
33
import (
44
"context"
55
"fmt"
6-
"github.com/viant/jsonrpc"
76
"net/url"
87
"reflect"
98
"time"
109

10+
"github.com/viant/jsonrpc"
11+
12+
"os"
13+
"path/filepath"
14+
"strings"
15+
1116
"github.com/google/uuid"
1217
"github.com/viant/mcp-protocol/client"
1318
"github.com/viant/mcp-protocol/schema"
1419
"github.com/viant/scy"
1520
"github.com/viant/scy/cred"
16-
"os"
17-
"path/filepath"
18-
"strings"
1921
)
2022

2123
// Add registers or updates a connector in the caller's namespace. If its secret
@@ -24,27 +26,36 @@ import (
2426
// MCP Elicit protocol, a browser flow is initiated to collect the secret
2527
// value. The method never returns the secret and therefore is safe over MCP
2628
// RPC.
27-
func (s *Service) Add(ctx context.Context, connector *Connector) error {
29+
func (s *Service) Add(ctx context.Context, connector *Connector) (*AddOutput, error) {
2830
pend, err := s.GeneratePendingSecret(ctx, connector)
2931
if err != nil {
30-
return err
32+
return nil, err
3133
}
3234
pend.MCP = s.mcpClient
3335
connector.secrets = s.secrets
36+
3437
// If client can handle the Elicit protocol generate it and optionally wait.
3538
if impl, ok := s.mcpClient.(client.Operations); ok && impl.Implements(schema.MethodElicitationCreate) {
39+
elicitID := uuid.New().String()
40+
pend.ElicitID = elicitID
41+
oobURL := pend.CallbackURL
42+
if strings.Contains(oobURL, "?") {
43+
oobURL += "&elicitationId=" + url.QueryEscape(elicitID)
44+
} else {
45+
oobURL += "?elicitationId=" + url.QueryEscape(elicitID)
46+
}
3647
elicitResult, _ := impl.Elicit(ctx, &jsonrpc.TypedRequest[*schema.ElicitRequest]{
3748
Request: &schema.ElicitRequest{
3849
Params: schema.ElicitRequestParams{
39-
ElicitationId: uuid.New().String(),
50+
ElicitationId: elicitID,
4051
Message: "Open URL to provide secrets for " + connector.Name + " connector",
4152
Mode: "oob",
42-
Url: pend.CallbackURL,
53+
Url: oobURL,
4354
}}})
4455

4556
if elicitResult != nil {
4657
if elicitResult.Action != schema.ElicitResultActionAccept {
47-
return fmt.Errorf("user reject providing credentials %v", err)
58+
return nil, fmt.Errorf("user reject providing credentials %v", err)
4859
}
4960
}
5061
// Wait for secret submission up to 5 min.
@@ -60,10 +71,17 @@ func (s *Service) Add(ctx context.Context, connector *Connector) error {
6071
pend.NS.Connectors.Put(connector.Name, connector)
6172
}
6273
case <-time.After(5 * time.Minute):
74+
// Timed out – return pending state with callback URL
75+
return &AddOutput{Status: "ok", State: "PENDING_SECRET", CallbackURL: pend.CallbackURL, Connector: connector.Name}, nil
6376
case <-ctx.Done():
77+
return nil, ctx.Err()
6478
}
79+
// Secret submitted within wait window – connector activated
80+
return &AddOutput{Status: "ok", Connector: connector.Name}, nil
6581
}
66-
return nil
82+
// Client cannot handle Elicit – the connector remains pending waiting for
83+
// secret to be supplied out-of-band; return pending state with callback URL.
84+
return &AddOutput{Status: "ok", State: "PENDING_SECRET", CallbackURL: pend.CallbackURL, Connector: connector.Name}, nil
6785
}
6886

6987
func (s *Service) GeneratePendingSecret(ctx context.Context, connector *Connector) (*PendingSecret, error) {

db/connector/connector.go

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,15 @@ package connector
33
import (
44
"context"
55
"database/sql"
6-
"github.com/viant/mcp-protocol/syncmap"
7-
"github.com/viant/scy"
8-
"github.com/viant/scy/cred"
96
"reflect"
107
"strings"
118
"sync"
129
"sync/atomic"
1310
"time"
11+
12+
"github.com/viant/mcp-protocol/syncmap"
13+
"github.com/viant/scy"
14+
"github.com/viant/scy/cred"
1415
)
1516

1617
type Connectors struct {
@@ -25,7 +26,7 @@ type Connector struct {
2526
Name string `json:"name" yaml:"name"`
2627
DSN string `json:"dsn" yaml:"dsn"`
2728
Driver string `json:"driver" yaml:"driver"`
28-
Secrets *scy.Resource `json:"secrets,omitempty" yaml:"secrets,omitempty"`
29+
Secrets *scy.Resource `json:"secrets,omitempty" yaml:"secrets,omitempty" internal:"true"`
2930
db *sql.DB `internal:"true"`
3031
mux sync.RWMutex `internal:"true"`
3132
initialized uint32 `internal:"true"`
@@ -55,6 +56,12 @@ func (c *Connector) ExpandDSN(ctx context.Context) (string, error) {
5556
}
5657

5758
func (c *Connector) Close() error {
59+
c.mux.Lock()
60+
defer c.mux.Unlock()
61+
if c.db != nil {
62+
_ = c.db.Close()
63+
c.db = nil
64+
}
5865
atomic.StoreUint32(&c.initialized, 0)
5966
return nil
6067
}

db/connector/elicitation.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,5 +138,8 @@ func (s *Service) requestConnectorElicit(ctx context.Context, impl client.Operat
138138
Driver: metaInput.Driver,
139139
DSN: metaInput.Expand(metaConfig.DSN),
140140
}
141-
return conn.Name, s.Add(ctx, conn)
141+
if _, err := s.Add(ctx, conn); err != nil {
142+
return "", err
143+
}
144+
return conn.Name, nil
142145
}

db/connector/pending.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ type PendingSecret struct {
2323
CredType reflect.Type
2424
MCP client.Operations
2525
OAuth2Config *oauth2.Config
26+
ElicitID string
2627
done chan struct{}
2728
}
2829

0 commit comments

Comments
 (0)