The company's MCP servers on a web page
/admin/mcp, for administrators only, since a server's credential reaches whatever it guards: each server's state, who has been granted its tools, its rate, the headers it is sent by name with the sealed ones marked, and its address; adding one with a bearer token or another header kept in the store; testing it with its credential and listing its tools; granting them to a team, project, org, person or key, written into that level's limits beside its other grants and its rate and budget; revoking; changing; replacing the credential; turning off and on; removing; and how a person points an agent at the gateway's /mcp with a key of their own. English and Turkish. The secret's field is named so the audit line redacts it. The admin API's server merge and test are package functions now, adminapi.MCPServerFrom and adminapi.TestMCPServer, shared by both. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
98c45ed28b
commit
03d3bcfd2d
14 files changed
+914
-4
No files matched your search
@@ -150,11 +150,18 @@ func (a *API) registerMCP(mux Mux) {
|
||||
// mcpServerFrom is the server a request makes, over the one of that name if
|
||||
// there is one: its secrets kept in the store, and named by reference.
|
||||
func (a *API) mcpServerFrom(ctx context.Context, name string, req MCPServerRequest, by string) (store.MCPServer, error) {
|
||||
return MCPServerFrom(ctx, a.mcp, name, req, by)
|
||||
}
|
||||
|
||||
// MCPServerFrom is the server a request makes, over the one of that name in
|
||||
// st if there is one: its secrets kept in the store, sealed, and named by
|
||||
// reference. Not stored; the caller puts it. For the API and the admin page.
|
||||
func MCPServerFrom(ctx context.Context, st MCPStore, name string, req MCPServerRequest, by string) (store.MCPServer, error) {
|
||||
if !store.ValidMCPServerName(name) {
|
||||
return store.MCPServer{}, errors.New("a server's name is lowercase letters, digits, - and _, starting with a letter: " +
|
||||
"its tools are NAME.tool")
|
||||
}
|
||||
m, err := a.mcp.MCPServer(ctx, name)
|
||||
m, err := st.MCPServer(ctx, name)
|
||||
switch {
|
||||
case errors.Is(err, store.ErrNotFound):
|
||||
m = store.MCPServer{Name: name, Enabled: true, CreatedBy: by}
|
||||
@@ -184,7 +191,7 @@ func (a *API) mcpServerFrom(ctx context.Context, name string, req MCPServerReque
|
||||
return m, errors.New("a secret header needs a name and a value")
|
||||
}
|
||||
key := "mcp/" + name + "/" + header
|
||||
if err := a.mcp.SetClusterSecret(ctx, key, value); err != nil {
|
||||
if err := st.SetClusterSecret(ctx, key, value); err != nil {
|
||||
return m, err
|
||||
}
|
||||
m.HeaderRefs[header] = "store:" + key
|
||||
@@ -208,6 +215,13 @@ func (a *API) mcpServerFrom(ctx context.Context, name string, req MCPServerReque
|
||||
// testMCPServer asks a server for its tools, with its credentials, as /mcp
|
||||
// would.
|
||||
func (a *API) testMCPServer(ctx context.Context, m store.MCPServer) MCPTestView {
|
||||
return TestMCPServer(ctx, m, http.HandlerFunc(a.MCP))
|
||||
}
|
||||
|
||||
// TestMCPServer asks a server for its tools, with its credentials, as /mcp
|
||||
// would; local answers for this cluster's own (AdminMCPAddress), and
|
||||
// without it that one is not asked.
|
||||
func TestMCPServer(ctx context.Context, m store.MCPServer, local http.Handler) MCPTestView {
|
||||
ctx, cancel := context.WithTimeout(ctx, 15*time.Second)
|
||||
defer cancel()
|
||||
c := &mcpclient.Client{URL: m.URL, Header: func(ctx context.Context) (http.Header, error) {
|
||||
@@ -225,7 +239,10 @@ func (a *API) testMCPServer(ctx context.Context, m store.MCPServer) MCPTestView
|
||||
return h, nil
|
||||
}}
|
||||
if m.URL == AdminMCPAddress {
|
||||
c.URL, c.HTTP = InProcessURL, &http.Client{Transport: InProcess(http.HandlerFunc(a.MCP))}
|
||||
if local == nil {
|
||||
return MCPTestView{Error: "this cluster's own tools are answered only by a control plane"}
|
||||
}
|
||||
c.URL, c.HTTP = InProcessURL, &http.Client{Transport: InProcess(local)}
|
||||
}
|
||||
tools, err := c.Tools(ctx)
|
||||
if err != nil {
|
||||
|
||||
Reference in new issue
Block a user