feat: single-billing edge path for MCP + open public signup
- Public signup no longer requires a caller bearer (POST /auth/signup) - create_user remains admin-only (POST /auth/users) - Optional VERAE_MCP_EDGE_URL bills global stamps via zap.verae.ai - Clearer public auth errors and instructions for Grok/clients
This commit is contained in:
parent
369f9210bd
commit
d49c51c349
3 changed files with 236 additions and 25 deletions
|
|
@ -6,6 +6,7 @@ import (
|
|||
"strings"
|
||||
|
||||
"git.georgelambert.org/marchon/verae-mcp-timestamping-server-go/internal/authctx"
|
||||
"git.georgelambert.org/marchon/verae-mcp-timestamping-server-go/internal/edgebill"
|
||||
"git.georgelambert.org/marchon/verae-mcp-timestamping-server-go/internal/ledger"
|
||||
"git.georgelambert.org/marchon/verae-mcp-timestamping-server-go/internal/veraetime"
|
||||
"github.com/modelcontextprotocol/go-sdk/mcp"
|
||||
|
|
@ -21,6 +22,8 @@ type Deps struct {
|
|||
// never limited (DEC-660).
|
||||
Public bool
|
||||
Checker CredentialChecker
|
||||
// Edge bills global stamps through zap.verae.ai when set (VERAE_MCP_EDGE_URL).
|
||||
Edge *edgebill.Client
|
||||
}
|
||||
|
||||
func (d *Deps) resolveBearer(ctx context.Context, req *mcp.CallToolRequest) (string, error) {
|
||||
|
|
@ -42,13 +45,33 @@ func (d *Deps) stamp(ctx context.Context, req *mcp.CallToolRequest, in StampArgs
|
|||
}
|
||||
switch scope {
|
||||
case "global":
|
||||
if d.Global == nil {
|
||||
return nil, ReceiptsOut{}, fmt.Errorf("global VeraeTime client not configured")
|
||||
}
|
||||
tok, err := d.writeBearer(ctx, req)
|
||||
if err != nil {
|
||||
return nil, ReceiptsOut{}, err
|
||||
}
|
||||
// Single commercial meter: when Edge is configured, bill via zap.verae.ai.
|
||||
if d.Edge != nil {
|
||||
if len(in.Metadata) > 0 || in.Restamp {
|
||||
return nil, ReceiptsOut{}, fmt.Errorf("metadata/restamp on edge-billed stamps is not supported yet; omit metadata or unset VERAE_MCP_EDGE_URL")
|
||||
}
|
||||
er, err := d.Edge.Stamp(ctx, in.SHA256, tok)
|
||||
if err != nil {
|
||||
return nil, ReceiptsOut{}, err
|
||||
}
|
||||
dto := ReceiptDTO{
|
||||
ReceiptID: er.ReceiptID,
|
||||
Hash: er.Hash,
|
||||
Scope: "global",
|
||||
CreatedAt: er.CreatedAt,
|
||||
Merkle: er.Merkle,
|
||||
CubeID: in.CubeID,
|
||||
OrgID: in.OrgID,
|
||||
}
|
||||
return nil, ReceiptsOut{Receipts: []ReceiptDTO{dto}}, nil
|
||||
}
|
||||
if d.Global == nil {
|
||||
return nil, ReceiptsOut{}, fmt.Errorf("global VeraeTime client not configured")
|
||||
}
|
||||
rec, err := d.Global.StampWithMeta(ctx, in.SHA256, tok, veraetime.StampMeta{
|
||||
Metadata: in.Metadata,
|
||||
Restamp: in.Restamp,
|
||||
|
|
@ -265,9 +288,9 @@ func (d *Deps) signup(ctx context.Context, req *mcp.CallToolRequest, in AuthUser
|
|||
if d.Global == nil {
|
||||
return nil, CreateUserOut{}, fmt.Errorf("global VeraeTime client not configured")
|
||||
}
|
||||
if _, err := d.writeBearer(ctx, req); d.Public && err != nil {
|
||||
return nil, CreateUserOut{}, err
|
||||
}
|
||||
// Public self-serve: POST /auth/signup needs no caller bearer (DEC-654 write lockdown
|
||||
// does not apply — this creates the credential the caller will use next).
|
||||
// Admin-provisioned users use create_user + admin bearer → POST /auth/users.
|
||||
res, err := d.Global.Signup(ctx, in.Username, in.Password)
|
||||
if err != nil {
|
||||
return nil, CreateUserOut{}, err
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@ package tools
|
|||
|
||||
// DEC-654 (D): on the public HTTP server (mcp.verae.ai) every write needs the caller's own credential - a VeraeTime
|
||||
// JWT (access_token from login) or a Verae API key - checked at api.veraetime.net - and never falls back to this
|
||||
// host's stored service login. Writes: stamp (any scope; global stamps are billed to the caller's account), signup,
|
||||
// create_user, session, stamp_batch; login (it would replace the host credential) is refused. Read-only verify and
|
||||
// hash_report stay anonymous for the public pages (receipt links, explorer); only those use the host login, and they
|
||||
// are never rate limited (DEC-660, George 2026-10-10: "never limit").
|
||||
// host's stored service login. Writes that need a caller bearer: stamp (any scope), create_user, session,
|
||||
// stamp_batch. Public signup is open (POST /auth/signup, no bearer) so new customers can register, then stamp
|
||||
// with their own JWT. login is refused on the public server (it would replace the host credential).
|
||||
// Read-only verify and hash_report stay anonymous; they may use the host login and are never rate limited (DEC-660).
|
||||
//
|
||||
// AI clients (Grok, Claude, Cursor) must send Authorization: Bearer on every tools/call. Grok Build:
|
||||
// bearer_token_file = "~/.verae/mcp-bearer" # raw JWT only, not auth.properties
|
||||
|
|
|
|||
|
|
@ -3,11 +3,12 @@ package tools
|
|||
import "github.com/modelcontextprotocol/go-sdk/mcp"
|
||||
|
||||
const instructions = "Digests-only timestamping. " +
|
||||
"Public HTTP (mcp.verae.ai / mcp.veraetime.net): every write needs Authorization: Bearer " +
|
||||
"<VeraeTime JWT or Verae API key> on each tools/call — no host-login fallback. " +
|
||||
"Grok Build: set bearer_token_file to a file that holds only the raw token (e.g. ~/.verae/mcp-bearer from access_token), " +
|
||||
"or run this binary with -transport stdio so the laptop ~/.verae/auth.properties host login is used. " +
|
||||
"Do not paste tokens into chat. Stdio/trusted-local HTTP may use the host login when no caller bearer is sent."
|
||||
"Public HTTP (mcp.verae.ai / mcp.veraetime.net): stamp writes need Authorization: Bearer " +
|
||||
"<xVerae- key or VeraeTime JWT> — no host-login fallback. " +
|
||||
"When VERAE_MCP_EDGE_URL is set, global stamps meter on zap.verae.ai (same billing as PrintIt/Zapier). " +
|
||||
"New accounts: tool signup (POST /auth/signup) or portal createUser; admin create_user for operators. " +
|
||||
"login is disabled on the public HTTP server. " +
|
||||
"Grok Build: bearer_token_file with raw xVerae- or JWT, or -transport stdio. Do not paste tokens into chat."
|
||||
|
||||
// NewServer builds an MCP server with the full Verae timestamping tool set.
|
||||
func NewServer(d *Deps) *mcp.Server {
|
||||
|
|
@ -43,19 +44,23 @@ func Register(s *mcp.Server, d *Deps) {
|
|||
Description: "JSON-array history of receipts for one SHA-256 (Carfax).",
|
||||
}, d.hashReport)
|
||||
mcp.AddTool(s, &mcp.Tool{
|
||||
Name: "signup",
|
||||
Description: "Create a VeraeTime user account. Role is user. Does not replace the stored JWT.",
|
||||
Name: "signup",
|
||||
Description: "Self-serve create a VeraeTime user (POST /auth/signup, role=user). " +
|
||||
"No Authorization required on the public server. Does not return a JWT — " +
|
||||
"use a client login or portal to obtain a bearer for stamp.",
|
||||
}, d.signup)
|
||||
mcp.AddTool(s, &mcp.Tool{
|
||||
Name: "login",
|
||||
Description: "Log in to api.veraetime.net. Stores the JWT on this host and does not return it.",
|
||||
Description: "Log in to api.veraetime.net. Stores the JWT on this host and does not return it. Disabled on the public HTTP server.",
|
||||
}, d.login)
|
||||
mcp.AddTool(s, &mcp.Tool{
|
||||
Name: "session",
|
||||
Description: "Validate the stored VeraeTime JWT. Returns valid, username, role, and expires_at.",
|
||||
Description: "Validate the caller's VeraeTime JWT (Authorization: Bearer). Returns valid, username, role, and expires_at.",
|
||||
}, d.session)
|
||||
mcp.AddTool(s, &mcp.Tool{
|
||||
Name: "create_user",
|
||||
Description: "Create a VeraeTime user. The stored JWT must belong to an admin.",
|
||||
Name: "create_user",
|
||||
Description: "Admin create a VeraeTime user (POST /auth/users / Swagger Auth/createUser). " +
|
||||
"Requires Authorization: Bearer of an admin. Used by operators and edge provision; " +
|
||||
"self-serve MCP clients should use signup.",
|
||||
}, d.createUser)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue