From 0b5d4f1cd38d65b68d4d0bc89c17f151767ed84b Mon Sep 17 00:00:00 2001 From: Bryan Frimin Date: Thu, 20 Nov 2025 18:36:59 +0100 Subject: [PATCH] Migrate to mcpgen Signed-off-by: Bryan Frimin --- go.mod | 62 +++-- go.sum | 116 +++++---- pkg/server/api/mcp/v1/add_vendor.go | 61 ----- pkg/server/api/mcp/v1/list_organizations.go | 59 ----- pkg/server/api/mcp/v1/list_vendors.go | 55 ---- pkg/server/api/mcp/v1/mcpgen.yaml | 17 ++ pkg/server/api/mcp/v1/middleware.go | 2 +- pkg/server/api/mcp/v1/resolver.go | 17 ++ pkg/server/api/mcp/v1/schema.resolvers.go | 110 ++++++++ pkg/server/api/mcp/v1/server/server.go | 76 ++++++ pkg/server/api/mcp/v1/specification.yaml | 241 ++++++++++++++++++ pkg/server/api/mcp/v1/types.go | 2 +- pkg/server/api/mcp/v1/types/cursorkey.go | 10 +- pkg/server/api/mcp/v1/types/organization.go | 12 +- pkg/server/api/mcp/v1/types/types.go | 147 +++++++++-- pkg/server/api/mcp/v1/types/vendor.go | 265 +------------------- pkg/server/api/mcp/v1/v1_handler.go | 35 +-- 17 files changed, 719 insertions(+), 568 deletions(-) delete mode 100644 pkg/server/api/mcp/v1/add_vendor.go delete mode 100644 pkg/server/api/mcp/v1/list_organizations.go delete mode 100644 pkg/server/api/mcp/v1/list_vendors.go create mode 100644 pkg/server/api/mcp/v1/mcpgen.yaml create mode 100644 pkg/server/api/mcp/v1/resolver.go create mode 100644 pkg/server/api/mcp/v1/schema.resolvers.go create mode 100644 pkg/server/api/mcp/v1/server/server.go create mode 100644 pkg/server/api/mcp/v1/specification.yaml diff --git a/go.mod b/go.mod index d913d7c20..78e88d0ee 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module go.probo.inc/probo -go 1.25 +go 1.25.3 // https://github.com/jaytaylor/html2text/issues/67 replace github.com/olekukonko/tablewriter => github.com/olekukonko/tablewriter v0.0.5 @@ -16,25 +16,25 @@ require ( github.com/crewjam/saml v0.5.1 github.com/go-chi/chi/v5 v5.2.3 github.com/go-chi/cors v1.2.2 - github.com/google/jsonschema-go v0.3.0 - github.com/jackc/pgx/v5 v5.7.6 + github.com/jackc/pgx/v5 v5.7.5 github.com/jhillyerd/enmime v1.3.0 github.com/modelcontextprotocol/go-sdk v1.1.0 - github.com/openai/openai-go v1.12.0 - github.com/pdfcpu/pdfcpu v0.11.1 - github.com/prometheus/client_golang v1.23.2 + github.com/openai/openai-go v1.8.2 + github.com/pdfcpu/pdfcpu v0.11.0 + github.com/prometheus/client_golang v1.22.0 github.com/stretchr/testify v1.11.1 github.com/vektah/gqlparser/v2 v2.5.31 - github.com/xuri/excelize/v2 v2.10.0 - github.com/yuin/goldmark v1.7.13 + github.com/xuri/excelize/v2 v2.9.1 + github.com/yuin/goldmark v1.7.12 go.gearno.de/crypto/uuid v0.1.0 go.gearno.de/kit v0.0.0-20250930204623-af70ea5798d9 go.gearno.de/x/ref v0.0.0-20240502200927-d74926fcb14c - go.opentelemetry.io/otel v1.38.0 - go.opentelemetry.io/otel/trace v1.38.0 - golang.org/x/crypto v0.45.0 - golang.org/x/image v0.33.0 - golang.org/x/sync v0.18.0 + go.opentelemetry.io/otel v1.37.0 + go.opentelemetry.io/otel/trace v1.37.0 + go.probo.inc/mcpgen v0.0.0-20251120125859-04a3b05f0c89 + golang.org/x/crypto v0.43.0 + golang.org/x/image v0.27.0 + golang.org/x/sync v0.17.0 ) require ( @@ -65,6 +65,7 @@ require ( github.com/gobwas/pool v0.2.1 // indirect github.com/gobwas/ws v1.4.0 // indirect github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f // indirect + github.com/google/jsonschema-go v0.3.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/websocket v1.5.3 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect @@ -72,6 +73,7 @@ require ( github.com/hhrutter/lzw v1.0.0 // indirect github.com/hhrutter/pkcs7 v0.2.0 // indirect github.com/hhrutter/tiff v1.0.2 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect @@ -90,6 +92,8 @@ require ( github.com/richardlehane/msoleps v1.0.4 // indirect github.com/russellhaering/goxmldsig v1.5.0 // indirect github.com/sosodev/duration v1.3.1 // indirect + github.com/spf13/cobra v1.10.1 // indirect + github.com/spf13/pflag v1.0.9 // indirect github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect github.com/tidwall/gjson v1.18.0 // indirect github.com/tidwall/match v1.2.0 // indirect @@ -101,24 +105,28 @@ require ( github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9 // indirect github.com/yosida95/uritemplate/v3 v3.0.2 // indirect go.gearno.de/x/panicf v0.1.1 // indirect - go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect - go.opentelemetry.io/otel/metric v1.38.0 // indirect - go.opentelemetry.io/otel/sdk v1.38.0 // indirect - go.opentelemetry.io/proto/otlp v1.9.0 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0 // indirect + go.opentelemetry.io/otel/metric v1.37.0 // indirect + go.opentelemetry.io/otel/sdk v1.37.0 // indirect + go.opentelemetry.io/proto/otlp v1.7.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect - golang.org/x/mod v0.29.0 // indirect - golang.org/x/net v0.47.0 // indirect - golang.org/x/oauth2 v0.33.0 // indirect - golang.org/x/sys v0.38.0 // indirect - golang.org/x/text v0.31.0 // indirect + golang.org/x/mod v0.30.0 // indirect + golang.org/x/net v0.46.0 // indirect + golang.org/x/oauth2 v0.32.0 // indirect + golang.org/x/sys v0.37.0 // indirect + golang.org/x/text v0.30.0 // indirect golang.org/x/tools v0.38.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba // indirect - google.golang.org/grpc v1.77.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250929231259-57b25ae835d4 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250929231259-57b25ae835d4 // indirect + google.golang.org/grpc v1.75.1 // indirect google.golang.org/protobuf v1.36.10 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect sigs.k8s.io/yaml v1.6.0 // indirect ) + +tool go.probo.inc/mcpgen + +replace go.probo.inc/mcpgen => /Users/gearnode/Developer/probo/mcpgen diff --git a/go.sum b/go.sum index 907638aab..46fe7e76c 100644 --- a/go.sum +++ b/go.sum @@ -56,6 +56,7 @@ github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfa github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA= github.com/clipperhouse/uax29/v2 v2.3.0 h1:SNdx9DVUqMoBuBoW3iLOj4FQv3dN5mDtuqwuhIGpJy4= github.com/clipperhouse/uax29/v2 v2.3.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/crewjam/saml v0.5.1 h1:g+mfp0CrLuLRZCK793PgJcZeg5dS/0CDwoeAX2zcwNI= github.com/crewjam/saml v0.5.1/go.mod h1:r0fDkmFe5URDgPrmtH0IYokva6fac3AUdstiPhyEolQ= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -108,12 +109,14 @@ github.com/hhrutter/pkcs7 v0.2.0 h1:i4HN2XMbGQpZRnKBLsUwO3dSckzgX142TNqY/KfXg+I= github.com/hhrutter/pkcs7 v0.2.0/go.mod h1:aEzKz0+ZAlz7YaEMY47jDHL14hVWD6iXt0AgqgAvWgE= github.com/hhrutter/tiff v1.0.2 h1:7H3FQQpKu/i5WaSChoD1nnJbGx4MxU5TlNqqpxw55z8= github.com/hhrutter/tiff v1.0.2/go.mod h1:pcOeuK5loFUE7Y/WnzGw20YxUdnqjY1P0Jlcieb/cCw= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.7.6 h1:rWQc5FwZSPX58r1OQmkuaNicxdmExaEz5A2DO2hUuTk= -github.com/jackc/pgx/v5 v5.7.6/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M= +github.com/jackc/pgx/v5 v5.7.5 h1:JHGfMnQY+IEtGM63d+NGMjoRpysB2JBwDr5fsngwmJs= +github.com/jackc/pgx/v5 v5.7.5/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056 h1:iCHtR9CQyktQ5+f3dMVZfwD2KWJUgm7M0gdL9NGr8KA= @@ -143,18 +146,18 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= -github.com/openai/openai-go v1.12.0 h1:NBQCnXzqOTv5wsgNC36PrFEiskGfO5wccfCWDo9S1U0= -github.com/openai/openai-go v1.12.0/go.mod h1:g461MYGXEXBVdV5SaR/5tNzNbSfwTBBefwc+LlDCK0Y= +github.com/openai/openai-go v1.8.2 h1:UqSkJ1vCOPUpz9Ka5tS0324EJFEuOvMc+lA/EarJWP8= +github.com/openai/openai-go v1.8.2/go.mod h1:g461MYGXEXBVdV5SaR/5tNzNbSfwTBBefwc+LlDCK0Y= github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde h1:x0TT0RDC7UhAVbbWWBzr41ElhJx5tXPWkIHA2HWPRuw= github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0= -github.com/pdfcpu/pdfcpu v0.11.1 h1:htHBSkGH5jMKWC6e0sihBFbcKZ8vG1M67c8/dJxhjas= -github.com/pdfcpu/pdfcpu v0.11.1/go.mod h1:pP3aGga7pRvwFWAm9WwFvo+V68DfANi9kxSQYioNYcw= +github.com/pdfcpu/pdfcpu v0.11.0 h1:mL18Y3hSHzSezmnrzA21TqlayBOXuAx7BUzzZyroLGM= +github.com/pdfcpu/pdfcpu v0.11.0/go.mod h1:F1ca4GIVFdPtmgvIdvXAycAm88noyNxZwzr9CpTy+Mw= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= -github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= +github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= +github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= github.com/prometheus/common v0.67.3 h1:shd26MlnwTw5jksTDhC7rTQIteBxy+ZZDr3t7F2xN2Q= @@ -166,14 +169,19 @@ github.com/richardlehane/mscfb v1.0.4/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7 github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg= github.com/richardlehane/msoleps v1.0.4 h1:WuESlvhX3gH2IHcd8UqyCuFY5yiq/GR/yqaSM/9/g00= github.com/richardlehane/msoleps v1.0.4/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/russellhaering/goxmldsig v1.5.0 h1:AU2UkkYIUOTyZRbe08XMThaOCelArgvNfYapcmSjBNw= github.com/russellhaering/goxmldsig v1.5.0/go.mod h1:x98CjQNFJcWfMxeOrMnMKg70lvDP6tE0nTaeUnjXDmk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= github.com/sosodev/duration v1.3.1 h1:qtHBDMQ6lvMQsL15g4aopM4HEfOaYuhWBw3NPTtlqq4= github.com/sosodev/duration v1.3.1/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERAikUR6SDg= +github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= +github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= +github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf h1:pvbZ0lM0XWPBqUKqFU8cmavspvIl9nulOYwdy6IFRRo= github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02nZ62WenDCkgHFerpIOmW0iT7GKmXM= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -203,14 +211,14 @@ github.com/vektah/gqlparser/v2 v2.5.31 h1:YhWGA1mfTjID7qJhd1+Vxhpk5HTgydrGU9IgkW github.com/vektah/gqlparser/v2 v2.5.31/go.mod h1:c1I28gSOVNzlfc4WuDlqU7voQnsqI6OG2amkBAFmgts= github.com/xuri/efp v0.0.1 h1:fws5Rv3myXyYni8uwj2qKjVaRP30PdjeYe2Y6FDsCL8= github.com/xuri/efp v0.0.1/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI= -github.com/xuri/excelize/v2 v2.10.0 h1:8aKsP7JD39iKLc6dH5Tw3dgV3sPRh8uRVXu/fMstfW4= -github.com/xuri/excelize/v2 v2.10.0/go.mod h1:SC5TzhQkaOsTWpANfm+7bJCldzcnU/jrhqkTi/iBHBU= +github.com/xuri/excelize/v2 v2.9.1 h1:VdSGk+rraGmgLHGFaGG9/9IWu1nj4ufjJ7uwMDtj8Qw= +github.com/xuri/excelize/v2 v2.9.1/go.mod h1:x7L6pKz2dvo9ejrRuD8Lnl98z4JLt0TGAwjhW+EiP8s= github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9 h1:+C0TIdyyYmzadGaL/HBLbf3WdLgC29pgyhTjAT/0nuE= github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ= github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4= github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4= -github.com/yuin/goldmark v1.7.13 h1:GPddIs617DnBLFFVJFgpo1aBfe/4xcvMc3SB5t/D0pA= -github.com/yuin/goldmark v1.7.13/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= +github.com/yuin/goldmark v1.7.12 h1:YwGP/rrea2/CnCtUHgjuolG/PnMxdQtPMO5PvaE2/nY= +github.com/yuin/goldmark v1.7.12/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= go.gearno.de/crypto/uuid v0.1.0 h1:94BYg7GYItJ6yYZ1GJayb3VYhI9/FjxuR1nFaduR4hE= go.gearno.de/crypto/uuid v0.1.0/go.mod h1:fnIIvKO9QnsyLO3ZJLJT3r8KZv/p0FOeT5eZKilYWXg= go.gearno.de/kit v0.0.0-20250930204623-af70ea5798d9 h1:WjCgzngF4+4I4J/gDDPPrPdc/cJnHDkqXdHP7Woa2iw= @@ -219,57 +227,57 @@ go.gearno.de/x/panicf v0.1.1 h1:E3Cr9NB8Ry2EsvEG/1eHr7kplP3tEjTf5d56dTX64VQ= go.gearno.de/x/panicf v0.1.1/go.mod h1:VnB8oF0UefMZcYeD4v+Wk4U5Z1uza7PHLlhT2CbNEbU= go.gearno.de/x/ref v0.0.0-20240502200927-d74926fcb14c h1:b8Wwr2owaB6g38tptSk5tekXFSE7RpXsUpaoCO8tQDM= go.gearno.de/x/ref v0.0.0-20240502200927-d74926fcb14c/go.mod h1:k3GtgnI5X9dl8FlqaNYkCkil7/iACQdFOromU/H4u6I= -go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= -go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= -go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4= -go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= -go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= -go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= -go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= -go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= -go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= -go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= -go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= -go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= -go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 h1:Ahq7pZmv87yiyn3jeFz/LekZmPLLdKejuO3NcK9MssM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0/go.mod h1:MJTqhM0im3mRLw1i8uGHnCvUEeS7VwRyxlLC78PA18M= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0 h1:bDMKF3RUSxshZ5OjOTi8rsHGaPKsAt76FaqgvIUySLc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0/go.mod h1:dDT67G/IkA46Mr2l9Uj7HsQVwsjASyV9SjGofsiUZDA= +go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= +go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= +go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= +go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= +go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= +go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= +go.opentelemetry.io/proto/otlp v1.7.0 h1:jX1VolD6nHuFzOYso2E73H85i92Mv8JQYk0K9vz09os= +go.opentelemetry.io/proto/otlp v1.7.0/go.mod h1:fSKjH6YJ7HDlwzltzyMj036AJ3ejJLCgCSHGj4efDDo= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= -golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= -golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= -golang.org/x/image v0.33.0 h1:LXRZRnv1+zGd5XBUVRFmYEphyyKJjQjCRiOuAP3sZfQ= -golang.org/x/image v0.33.0/go.mod h1:DD3OsTYT9chzuzTQt+zMcOlBHgfoKQb1gry8p76Y1sc= -golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= -golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= -golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= -golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= -golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo= -golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= -golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= -golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= +golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= +golang.org/x/image v0.27.0 h1:C8gA4oWU/tKkdCfYT6T2u4faJu3MeNS5O8UPWlPF61w= +golang.org/x/image v0.27.0/go.mod h1:xbdrClrAUway1MUTEZDq9mz/UpRwYAkFFNUslZtcB+g= +golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk= +golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc= +golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= +golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= +golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY= +golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= -golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= -golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= +golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= +golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba h1:B14OtaXuMaCQsl2deSvNkyPKIzq3BjfxQp8d00QyWx4= -google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:G5IanEx8/PgI9w6CFcYQf7jMtHQhZruvfM1i3qOqk5U= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba h1:UKgtfRM7Yh93Sya0Fo8ZzhDP4qBckrrxEr2oF5UIVb8= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= -google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= -google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= +google.golang.org/genproto/googleapis/api v0.0.0-20250929231259-57b25ae835d4 h1:8XJ4pajGwOlasW+L13MnEGA8W4115jJySQtVfS2/IBU= +google.golang.org/genproto/googleapis/api v0.0.0-20250929231259-57b25ae835d4/go.mod h1:NnuHhy+bxcg30o7FnVAZbXsPHUDQ9qKWAQKCD7VxFtk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250929231259-57b25ae835d4 h1:i8QOKZfYg6AbGVZzUAY3LrNWCKF8O6zFisU9Wl9RER4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250929231259-57b25ae835d4/go.mod h1:HSkG/KdJWusxU1F6CNrwNDjBMgisKxGnc5dAZfT0mjQ= +google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI= +google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/pkg/server/api/mcp/v1/add_vendor.go b/pkg/server/api/mcp/v1/add_vendor.go deleted file mode 100644 index 6b18cdb1e..000000000 --- a/pkg/server/api/mcp/v1/add_vendor.go +++ /dev/null @@ -1,61 +0,0 @@ -package v1 - -import ( - "context" - "fmt" - - "github.com/modelcontextprotocol/go-sdk/mcp" - "go.probo.inc/probo/pkg/probo" - "go.probo.inc/probo/pkg/server/api/mcp/v1/types" -) - -var ( - AddVendorTool = &mcp.Tool{ - Name: "addVendor", - Title: "Add Vendor", - Description: "Add a new vendor to the organization", - Annotations: &mcp.ToolAnnotations{ReadOnlyHint: false}, - InputSchema: types.AddVendorInputSchema, - OutputSchema: types.AddVendorOutputSchema, - } -) - -func (r *resolver) AddVendor( - ctx context.Context, - req *mcp.CallToolRequest, - args types.AddVendorInput, -) (*mcp.CallToolResult, types.AddVendorOutput, error) { - tenantID := args.OrganizationID.TenantID() - svc := r.ProboService(ctx, tenantID) - - vendor, err := svc.Vendors.Create( - ctx, - probo.CreateVendorRequest{ - OrganizationID: args.OrganizationID, - Name: args.Name, - Description: args.Description, - HeadquarterAddress: args.HeadquarterAddress, - LegalName: args.LegalName, - WebsiteURL: args.WebsiteURL, - Category: args.Category, - PrivacyPolicyURL: args.PrivacyPolicyURL, - ServiceLevelAgreementURL: args.ServiceLevelAgreementURL, - DataProcessingAgreementURL: args.DataProcessingAgreementURL, - BusinessAssociateAgreementURL: args.BusinessAssociateAgreementURL, - SubprocessorsListURL: args.SubprocessorsListURL, - Certifications: args.Certifications, - Countries: args.Countries, - SecurityPageURL: args.SecurityPageURL, - TrustPageURL: args.TrustPageURL, - TermsOfServiceURL: args.TermsOfServiceURL, - StatusPageURL: args.StatusPageURL, - BusinessOwnerID: args.BusinessOwnerID, - SecurityOwnerID: args.SecurityOwnerID, - }, - ) - if err != nil { - return nil, types.AddVendorOutput{}, fmt.Errorf("failed to create vendor: %w", err) - } - - return nil, types.NewAddVendorOutput(vendor), nil -} diff --git a/pkg/server/api/mcp/v1/list_organizations.go b/pkg/server/api/mcp/v1/list_organizations.go deleted file mode 100644 index babe3d44e..000000000 --- a/pkg/server/api/mcp/v1/list_organizations.go +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (c) 2025 Probo Inc . -// -// Permission to use, copy, modify, and/or distribute this software for any -// purpose with or without fee is hereby granted, provided that the above -// copyright notice and this permission notice appear in all copies. -// -// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH -// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, -// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM -// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR -// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -// PERFORMANCE OF THIS SOFTWARE. - -package v1 - -import ( - "context" - "fmt" - - "github.com/google/jsonschema-go/jsonschema" - "github.com/modelcontextprotocol/go-sdk/mcp" - "go.probo.inc/probo/pkg/server/api/mcp/v1/types" -) - -var ( - ListOrganizationsTool = &mcp.Tool{ - Name: "listOrganizations", - Title: "List Organizations", - Description: "List all organizations the user has access to", - Annotations: &mcp.ToolAnnotations{ReadOnlyHint: true}, - InputSchema: &jsonschema.Schema{ - Type: "object", - Properties: map[string]*jsonschema.Schema{}, - }, - } -) - -func (r *resolver) ListOrganizations( - ctx context.Context, - req *mcp.CallToolRequest, - _ types.ListOrganizationsInput, -) (*mcp.CallToolResult, types.ListOrganizationsOutput, error) { - mcpCtx := MCPContextFromContext(ctx) - organizations, err := r.authzSvc.GetAllUserOrganizations(ctx, mcpCtx.UserID) - if err != nil { - return nil, types.ListOrganizationsOutput{}, fmt.Errorf("failed to list organizations: %w", err) - } - - result := types.ListOrganizationsOutput{ - Organizations: make([]types.Organization, 0, len(organizations)), - } - - for _, org := range organizations { - result.Organizations = append(result.Organizations, types.NewOrganization(org)) - } - - return nil, result, nil -} diff --git a/pkg/server/api/mcp/v1/list_vendors.go b/pkg/server/api/mcp/v1/list_vendors.go deleted file mode 100644 index ec899bec1..000000000 --- a/pkg/server/api/mcp/v1/list_vendors.go +++ /dev/null @@ -1,55 +0,0 @@ -package v1 - -import ( - "context" - "fmt" - - "github.com/modelcontextprotocol/go-sdk/mcp" - "go.probo.inc/probo/pkg/coredata" - "go.probo.inc/probo/pkg/page" - "go.probo.inc/probo/pkg/server/api/mcp/v1/types" -) - -var ( - ListVendorsTool = &mcp.Tool{ - Name: "listVendors", - Title: "List Vendors", - Description: "List all vendors for the organization", - Annotations: &mcp.ToolAnnotations{ReadOnlyHint: true}, - InputSchema: types.ListVendorsInputSchema, - OutputSchema: types.ListVendorsOutputSchema, - } -) - -func (r *resolver) ListVendors( - ctx context.Context, - req *mcp.CallToolRequest, - args types.ListVendorsInput, -) (*mcp.CallToolResult, types.ListVendorsOutput, error) { - prb := r.ProboService(ctx, args.OrganizationID.TenantID()) - - pageOrderBy := page.OrderBy[coredata.VendorOrderField]{ - Field: coredata.VendorOrderFieldCreatedAt, - Direction: page.OrderDirectionDesc, - } - if args.OrderBy != nil { - pageOrderBy = page.OrderBy[coredata.VendorOrderField]{ - Field: args.OrderBy.Field, - Direction: page.OrderDirectionDesc, - } - } - - cursor := types.NewCursor(args.Size, args.Cursor, pageOrderBy) - - var vendorFilter = coredata.NewVendorFilter(nil, nil) - if args.Filter != nil { - vendorFilter = coredata.NewVendorFilter(&args.Filter.SnapshotID, nil) - } - - page, err := prb.Vendors.ListForOrganizationID(ctx, args.OrganizationID, cursor, vendorFilter) - if err != nil { - panic(fmt.Errorf("cannot list organization vendors: %w", err)) - } - - return nil, types.NewListVendorsOutput(page), nil -} diff --git a/pkg/server/api/mcp/v1/mcpgen.yaml b/pkg/server/api/mcp/v1/mcpgen.yaml new file mode 100644 index 000000000..72548edbf --- /dev/null +++ b/pkg/server/api/mcp/v1/mcpgen.yaml @@ -0,0 +1,17 @@ +spec: specification.yaml + +output: "." + +resolver: + package: mcp_v1 + filename: v1_resolver.go + type: Resolver + preserve: true + +server: + package: server + filename: server/server.go + +model: + package: types + filename: types/types.go diff --git a/pkg/server/api/mcp/v1/middleware.go b/pkg/server/api/mcp/v1/middleware.go index ee25f5b34..ab0dc8282 100644 --- a/pkg/server/api/mcp/v1/middleware.go +++ b/pkg/server/api/mcp/v1/middleware.go @@ -12,7 +12,7 @@ // OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -package v1 +package mcp_v1 import ( "net/http" diff --git a/pkg/server/api/mcp/v1/resolver.go b/pkg/server/api/mcp/v1/resolver.go new file mode 100644 index 000000000..47fe388e0 --- /dev/null +++ b/pkg/server/api/mcp/v1/resolver.go @@ -0,0 +1,17 @@ +//go:generate go run go.probo.inc/mcpgen generate + +package mcp_v1 + +import ( + "go.gearno.de/kit/log" + "go.probo.inc/probo/pkg/auth" + "go.probo.inc/probo/pkg/authz" + "go.probo.inc/probo/pkg/probo" +) + +type Resolver struct { + proboSvc *probo.Service + authSvc *auth.Service + authzSvc *authz.Service + logger *log.Logger +} diff --git a/pkg/server/api/mcp/v1/schema.resolvers.go b/pkg/server/api/mcp/v1/schema.resolvers.go new file mode 100644 index 000000000..6e7596850 --- /dev/null +++ b/pkg/server/api/mcp/v1/schema.resolvers.go @@ -0,0 +1,110 @@ +package mcp_v1 + +// This file will be automatically regenerated based on the schema, any resolver implementations +// will be copied through when generating and any unknown code will be moved to the end. +// Code generated by mcpgen. DO NOT EDIT. + +import ( + "context" + "fmt" + + "github.com/modelcontextprotocol/go-sdk/mcp" + "go.probo.inc/probo/pkg/coredata" + "go.probo.inc/probo/pkg/page" + "go.probo.inc/probo/pkg/probo" + "go.probo.inc/probo/pkg/server/api/mcp/v1/types" +) + +// ListOrganizationsTool handles the listOrganizations tool +// List all organizations the user has access to +func (r *Resolver) ListOrganizationsTool(ctx context.Context, req *mcp.CallToolRequest, input *types.ListOrganizationsInput) (*mcp.CallToolResult, types.ListOrganizationsOutput, error) { + mcpCtx := MCPContextFromContext(ctx) + organizations, err := r.authzSvc.GetAllUserOrganizations(ctx, mcpCtx.UserID) + if err != nil { + return nil, types.ListOrganizationsOutput{}, fmt.Errorf("failed to list organizations: %w", err) + } + + result := types.ListOrganizationsOutput{ + Organizations: make([]*types.Organization, 0, len(organizations)), + } + + for _, org := range organizations { + result.Organizations = append(result.Organizations, types.NewOrganization(org)) + } + + return nil, result, nil +} + +// ListVendorsTool handles the listVendors tool +// List all vendors for the organization +func (r *Resolver) ListVendorsTool(ctx context.Context, req *mcp.CallToolRequest, input *types.ListVendorsInput) (*mcp.CallToolResult, types.ListVendorsOutput, error) { + prb := r.ProboService(ctx, input.OrganizationID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.VendorOrderField]{ + Field: coredata.VendorOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + if input.OrderBy != nil { + pageOrderBy = page.OrderBy[coredata.VendorOrderField]{ + Field: input.OrderBy.Field, + Direction: input.OrderBy.Direction, + } + } + + cursor := types.NewCursor(input.Size, input.Cursor, pageOrderBy) + + var vendorFilter = coredata.NewVendorFilter(nil, nil) + if input.Filter != nil { + vendorFilter = coredata.NewVendorFilter(&input.Filter.SnapshotID, nil) + } + + page, err := prb.Vendors.ListForOrganizationID(ctx, input.OrganizationID, cursor, vendorFilter) + if err != nil { + panic(fmt.Errorf("cannot list organization vendors: %w", err)) + } + + return nil, types.NewListVendorsOutput(page), nil +} + +// AddVendorTool handles the addVendor tool +// Add a new vendor to the organization +func (r *Resolver) AddVendorTool(ctx context.Context, req *mcp.CallToolRequest, input *types.AddVendorInput) (*mcp.CallToolResult, types.AddVendorOutput, error) { + svc := r.ProboService(ctx, input.OrganizationID.TenantID()) + + vendor, err := svc.Vendors.Create( + ctx, + probo.CreateVendorRequest{ + OrganizationID: input.OrganizationID, + Name: input.Name, + Description: input.Description, + // HeadquarterAddress: input.HeadquarterAddress, + // LegalName: input.LegalName, + // WebsiteURL: input.WebsiteURL, + // Category: input.Category, + // PrivacyPolicyURL: input.PrivacyPolicyURL, + // ServiceLevelAgreementURL: input.ServiceLevelAgreementURL, + // DataProcessingAgreementURL: input.DataProcessingAgreementURL, + // BusinessAssociateAgreementURL: input.BusinessAssociateAgreementURL, + // SubprocessorsListURL: input.SubprocessorsListURL, + // Certifications: input.Certifications, + // Countries: input.Countries, + // SecurityPageURL: input.SecurityPageURL, + // TrustPageURL: input.TrustPageURL, + // TermsOfServiceURL: input.TermsOfServiceURL, + // StatusPageURL: input.StatusPageURL, + // BusinessOwnerID: input.BusinessOwnerID, + // SecurityOwnerID: input.SecurityOwnerID, + }, + ) + if err != nil { + return nil, types.AddVendorOutput{}, fmt.Errorf("failed to create vendor: %w", err) + } + + return nil, types.NewAddVendorOutput(vendor), nil +} + +// UpdateVendorTool handles the updateVendor tool +// Update an existing vendor +func (r *Resolver) UpdateVendorTool(ctx context.Context, req *mcp.CallToolRequest, input *types.UpdateVendorInput) (*mcp.CallToolResult, types.UpdateVendorOutput, error) { + return nil, types.UpdateVendorOutput{}, fmt.Errorf("updateVendor not implemented") +} diff --git a/pkg/server/api/mcp/v1/server/server.go b/pkg/server/api/mcp/v1/server/server.go new file mode 100644 index 000000000..9bd835751 --- /dev/null +++ b/pkg/server/api/mcp/v1/server/server.go @@ -0,0 +1,76 @@ +// Code generated by mcpgen. DO NOT EDIT. + +package server + +import ( + "context" + "github.com/modelcontextprotocol/go-sdk/mcp" + "go.probo.inc/probo/pkg/server/api/mcp/v1/types" +) + +// ResolverInterface defines the interface that must be implemented by the parent resolver +type ResolverInterface interface { + ListOrganizationsTool(ctx context.Context, req *mcp.CallToolRequest, input *types.ListOrganizationsInput) (*mcp.CallToolResult, types.ListOrganizationsOutput, error) + ListVendorsTool(ctx context.Context, req *mcp.CallToolRequest, input *types.ListVendorsInput) (*mcp.CallToolResult, types.ListVendorsOutput, error) + AddVendorTool(ctx context.Context, req *mcp.CallToolRequest, input *types.AddVendorInput) (*mcp.CallToolResult, types.AddVendorOutput, error) + UpdateVendorTool(ctx context.Context, req *mcp.CallToolRequest, input *types.UpdateVendorInput) (*mcp.CallToolResult, types.UpdateVendorOutput, error) +} + +// New creates a new MCP server instance with all handlers registered. +// Returns a fully configured *mcp.Server ready to be used with any transport. +func New(resolver ResolverInterface) *mcp.Server { + server := mcp.NewServer( + &mcp.Implementation{ + Name: "Probo MCP Server", + Version: "1.0.0", + }, + nil, + ) + + registerToolHandlers(server, resolver) + + return server +} + +func registerToolHandlers(server *mcp.Server, resolver ResolverInterface) { + mcp.AddTool( + server, + &mcp.Tool{ + Name: "listOrganizations", + Description: "List all organizations the user has access to", + InputSchema: types.ListOrganizationsToolInputSchema, + OutputSchema: types.ListOrganizationsToolOutputSchema, + }, + resolver.ListOrganizationsTool, + ) + mcp.AddTool( + server, + &mcp.Tool{ + Name: "listVendors", + Description: "List all vendors for the organization", + InputSchema: types.ListVendorsToolInputSchema, + OutputSchema: types.ListVendorsToolOutputSchema, + }, + resolver.ListVendorsTool, + ) + mcp.AddTool( + server, + &mcp.Tool{ + Name: "addVendor", + Description: "Add a new vendor to the organization", + InputSchema: types.AddVendorToolInputSchema, + OutputSchema: types.AddVendorToolOutputSchema, + }, + resolver.AddVendorTool, + ) + mcp.AddTool( + server, + &mcp.Tool{ + Name: "updateVendor", + Description: "Update an existing vendor", + InputSchema: types.UpdateVendorToolInputSchema, + OutputSchema: types.UpdateVendorToolOutputSchema, + }, + resolver.UpdateVendorTool, + ) +} diff --git a/pkg/server/api/mcp/v1/specification.yaml b/pkg/server/api/mcp/v1/specification.yaml new file mode 100644 index 000000000..32e5c6227 --- /dev/null +++ b/pkg/server/api/mcp/v1/specification.yaml @@ -0,0 +1,241 @@ +info: + title: Probo MCP Server + version: 1.0.0 + description: Probo MCP Server API + +components: + schemas: + OrderDirection: + type: string + enum: + - asc + - desc + go.probo.inc/mcpgen/type: go.probo.inc/probo/pkg/page.OrderDirection + + VendorOrderField: + type: string + enum: + - created_at + - updated_at + - name + go.probo.inc/mcpgen/type: go.probo.inc/probo/pkg/coredata.VendorOrderField + + VendorOrderBy: + type: object + required: + - field + - direction + properties: + field: + $ref: "#/components/schemas/VendorOrderField" + description: Vendor order field + direction: + $ref: "#/components/schemas/OrderDirection" + description: Vendor order direction + GID: + type: string + format: string + go.probo.inc/mcpgen/type: go.probo.inc/probo/pkg/gid.GID + + CursorKey: + type: string + format: string + go.probo.inc/mcpgen/type: go.probo.inc/probo/pkg/page.CursorKey + + ListOrganizationsInput: + type: object + properties: + organization_id: + $ref: "#/components/schemas/GID" + description: Organization ID + + ListOrganizationsOutput: + type: object + properties: + organizations: + type: array + items: + $ref: "#/components/schemas/Organization" + + Organization: + type: object + required: + - id + - name + - description + - created_at + - updated_at + properties: + id: + $ref: "#/components/schemas/GID" + description: Organization ID + name: + type: string + description: Organization name + description: + type: + - string + - "null" + description: Organization description + created_at: + type: string + format: date-time + description: Creation timestamp + updated_at: + type: string + format: date-time + description: Update timestamp + + ListVendorsInput: + type: object + required: + - organization_id + properties: + organization_id: + $ref: "#/components/schemas/GID" + description: Organization ID + order_by: + $ref: "#/components/schemas/VendorOrderBy" + description: Vendor order by + size: + type: integer + description: Page size + cursor: + $ref: "#/components/schemas/CursorKey" + description: Page cursor + filter: + type: object + properties: + snapshot_id: + $ref: "#/components/schemas/GID" + description: Snapshot ID + + ListVendorsOutput: + type: object + required: + - vendors + properties: + next_cursor: + $ref: "#/components/schemas/CursorKey" + description: Next cursor + vendors: + type: array + items: + $ref: "#/components/schemas/Vendor" + + Vendor: + type: object + required: + - id + - name + - organization_id + - created_at + - updated_at + properties: + id: + $ref: "#/components/schemas/GID" + description: Vendor ID + name: + type: string + description: Vendor name + organization_id: + $ref: "#/components/schemas/GID" + description: Organization ID + description: + type: + - string + - "null" + description: Vendor description + created_at: + type: string + format: date-time + description: Creation timestamp + updated_at: + type: string + format: date-time + description: Update timestamp + + AddVendorInput: + type: object + required: + - organization_id + - name + properties: + organization_id: + $ref: "#/components/schemas/GID" + description: Organization ID + name: + type: string + description: Vendor name + description: + type: string + description: Vendor description + created_at: + type: string + format: date-time + description: Creation timestamp + updated_at: + type: string + format: date-time + description: Update timestamp + + AddVendorOutput: + type: object + required: + - vendor + properties: + vendor: + $ref: "#/components/schemas/Vendor" + + UpdateVendorInput: + type: object + required: + - id + properties: + id: + $ref: "#/components/schemas/GID" + description: Vendor ID + name: + type: string + description: Vendor name + description: + type: string + description: Vendor description + + UpdateVendorOutput: + type: object + required: + - vendor + properties: + vendor: + $ref: "#/components/schemas/Vendor" + +tools: + - name: listOrganizations + description: List all organizations the user has access to + readonly: true + inputSchema: + $ref: "#/components/schemas/ListOrganizationsInput" + outputSchema: + $ref: "#/components/schemas/ListOrganizationsOutput" + - name: listVendors + description: List all vendors for the organization + readonly: true + inputSchema: + $ref: "#/components/schemas/ListVendorsInput" + outputSchema: + $ref: "#/components/schemas/ListVendorsOutput" + - name: addVendor + description: Add a new vendor to the organization + readonly: false + inputSchema: + $ref: "#/components/schemas/AddVendorInput" + outputSchema: + $ref: "#/components/schemas/AddVendorOutput" + - name: updateVendor + description: Update an existing vendor + readonly: false + inputSchema: + $ref: "#/components/schemas/UpdateVendorInput" + outputSchema: + $ref: "#/components/schemas/UpdateVendorOutput" diff --git a/pkg/server/api/mcp/v1/types.go b/pkg/server/api/mcp/v1/types.go index 9567b814b..dba30a666 100644 --- a/pkg/server/api/mcp/v1/types.go +++ b/pkg/server/api/mcp/v1/types.go @@ -12,7 +12,7 @@ // OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -package v1 +package mcp_v1 import ( "context" diff --git a/pkg/server/api/mcp/v1/types/cursorkey.go b/pkg/server/api/mcp/v1/types/cursorkey.go index d5a218c01..ce7fd01ae 100644 --- a/pkg/server/api/mcp/v1/types/cursorkey.go +++ b/pkg/server/api/mcp/v1/types/cursorkey.go @@ -15,14 +15,20 @@ package types import ( + "go.gearno.de/x/ref" "go.probo.inc/probo/pkg/page" "go.probo.inc/probo/pkg/server/gqlutils/types/cursor" ) func NewCursor[O page.OrderField]( - first *int, + first *int64, after *page.CursorKey, orderBy page.OrderBy[O], ) *page.Cursor[O] { - return cursor.NewCursor(first, after, nil, nil, orderBy) + firstValue := ref.Ref(100) + if first != nil { + firstValue = ref.Ref(int(*first)) + } + + return cursor.NewCursor(firstValue, after, nil, nil, orderBy) } diff --git a/pkg/server/api/mcp/v1/types/organization.go b/pkg/server/api/mcp/v1/types/organization.go index b18e86fe3..979e0a845 100644 --- a/pkg/server/api/mcp/v1/types/organization.go +++ b/pkg/server/api/mcp/v1/types/organization.go @@ -14,8 +14,14 @@ package types -type ListOrganizationsInput struct{} +import "go.probo.inc/probo/pkg/coredata" -type ListOrganizationsOutput struct { - Organizations []Organization `json:"organizations" jsonschema:"list of organizations the user has access to"` +func NewOrganization(o *coredata.Organization) *Organization { + return &Organization{ + ID: o.ID, + Name: o.Name, + Description: o.Description, + CreatedAt: o.CreatedAt, + UpdatedAt: o.UpdatedAt, + } } diff --git a/pkg/server/api/mcp/v1/types/types.go b/pkg/server/api/mcp/v1/types/types.go index f9de9006a..5213f2603 100644 --- a/pkg/server/api/mcp/v1/types/types.go +++ b/pkg/server/api/mcp/v1/types/types.go @@ -1,31 +1,132 @@ -// Copyright (c) 2025 Probo Inc . -// -// Permission to use, copy, modify, and/or distribute this software for any -// purpose with or without fee is hereby granted, provided that the above -// copyright notice and this permission notice appear in all copies. -// -// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH -// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, -// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM -// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR -// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -// PERFORMANCE OF THIS SOFTWARE. +// Code generated by mcpgen. DO NOT EDIT. package types -import "go.probo.inc/probo/pkg/coredata" +import ( + "go.probo.inc/mcpgen/mcp" + "go.probo.inc/probo/pkg/coredata" + "go.probo.inc/probo/pkg/gid" + "go.probo.inc/probo/pkg/page" + "time" +) +// Tool input schemas +var ( + AddVendorToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["organization_id","name"],"properties":{"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"description":{"type":"string","description":"Vendor description"},"name":{"type":"string","description":"Vendor name"},"organization_id":{"type":"string","format":"string"},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"}}}`) + AddVendorToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["vendor"],"properties":{"vendor":{"type":"object","required":["id","name","organization_id","created_at","updated_at"],"properties":{"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"description":{"description":"Vendor description"},"id":{"type":"string","format":"string"},"name":{"type":"string","description":"Vendor name"},"organization_id":{"type":"string","format":"string"},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"}}}}}`) + ListOrganizationsToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","properties":{"organization_id":{"type":"string","format":"string"}}}`) + ListOrganizationsToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","properties":{"organizations":{"type":"array","items":{"type":"object","required":["id","name","description","created_at","updated_at"],"properties":{"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"description":{"description":"Organization description"},"id":{"type":"string","format":"string"},"name":{"type":"string","description":"Organization name"},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"}}}}}}`) + ListVendorsToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["organization_id"],"properties":{"cursor":{"type":"string","format":"string"},"filter":{"type":"object","properties":{"snapshot_id":{"type":"string","format":"string"}}},"order_by":{"type":"object","required":["field","direction"],"properties":{"direction":{"type":"string","enum":["asc","desc"]},"field":{"type":"string","enum":["created_at","updated_at","name"]}}},"organization_id":{"type":"string","format":"string"},"size":{"type":"integer","description":"Page size"}}}`) + ListVendorsToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["vendors"],"properties":{"next_cursor":{"type":"string","format":"string"},"vendors":{"type":"array","items":{"type":"object","required":["id","name","organization_id","created_at","updated_at"],"properties":{"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"description":{"description":"Vendor description"},"id":{"type":"string","format":"string"},"name":{"type":"string","description":"Vendor name"},"organization_id":{"type":"string","format":"string"},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"}}}}}}`) + UpdateVendorToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["id"],"properties":{"description":{"type":"string","description":"Vendor description"},"id":{"type":"string","format":"string"},"name":{"type":"string","description":"Vendor name"}}}`) + UpdateVendorToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["vendor"],"properties":{"vendor":{"type":"object","required":["id","name","organization_id","created_at","updated_at"],"properties":{"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"description":{"description":"Vendor description"},"id":{"type":"string","format":"string"},"name":{"type":"string","description":"Vendor name"},"organization_id":{"type":"string","format":"string"},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"}}}}}`) +) + +// AddVendorInput represents the schema +type AddVendorInput struct { + // Creation timestamp + CreatedAt *time.Time `json:"created_at,omitempty"` + // Vendor description + Description *string `json:"description,omitempty"` + // Vendor name + Name string `json:"name"` + // Organization ID + OrganizationID gid.GID `json:"organization_id"` + // Update timestamp + UpdatedAt *time.Time `json:"updated_at,omitempty"` +} + +// AddVendorOutput represents the schema +type AddVendorOutput struct { + Vendor *Vendor `json:"vendor"` +} + +// ListOrganizationsInput represents the schema +type ListOrganizationsInput struct { + // Organization ID + OrganizationID *gid.GID `json:"organization_id,omitempty"` +} + +// ListOrganizationsOutput represents the schema +type ListOrganizationsOutput struct { + Organizations []*Organization `json:"organizations,omitempty"` +} + +// ListVendorsInput represents the schema +type ListVendorsInput struct { + // Page cursor + Cursor *page.CursorKey `json:"cursor,omitempty"` + Filter *ListVendorsInputFilter `json:"filter,omitempty"` + // Vendor order by + OrderBy *VendorOrderBy `json:"order_by,omitempty"` + // Organization ID + OrganizationID gid.GID `json:"organization_id"` + // Page size + Size *int64 `json:"size,omitempty"` +} + +// ListVendorsOutput represents the schema +type ListVendorsOutput struct { + // Next cursor + NextCursor *page.CursorKey `json:"next_cursor,omitempty"` + Vendors []*Vendor `json:"vendors"` +} + +// Organization represents the schema type Organization struct { - Name string `json:"name" jsonschema:"the organization name"` - ID string `json:"id" jsonschema:"the organization ID"` - TenantID string `json:"tenantID" jsonschema:"the tenant ID this organization belongs to"` + // Creation timestamp + CreatedAt time.Time `json:"created_at"` + // Organization description + Description *string `json:"description"` + // Organization ID + ID gid.GID `json:"id"` + // Organization name + Name string `json:"name"` + // Update timestamp + UpdatedAt time.Time `json:"updated_at"` } -func NewOrganization(o *coredata.Organization) Organization { - return Organization{ - Name: o.Name, - ID: o.ID.String(), - TenantID: o.ID.TenantID().String(), - } +// UpdateVendorInput represents the schema +type UpdateVendorInput struct { + // Vendor description + Description *string `json:"description,omitempty"` + // Vendor ID + ID gid.GID `json:"id"` + // Vendor name + Name *string `json:"name,omitempty"` +} + +// UpdateVendorOutput represents the schema +type UpdateVendorOutput struct { + Vendor *Vendor `json:"vendor"` +} + +// Vendor represents the schema +type Vendor struct { + // Creation timestamp + CreatedAt time.Time `json:"created_at"` + // Vendor description + Description *string `json:"description,omitempty"` + // Vendor ID + ID gid.GID `json:"id"` + // Vendor name + Name string `json:"name"` + // Organization ID + OrganizationID gid.GID `json:"organization_id"` + // Update timestamp + UpdatedAt time.Time `json:"updated_at"` +} + +// VendorOrderBy represents the schema +type VendorOrderBy struct { + // Vendor order direction + Direction page.OrderDirection `json:"direction"` + // Vendor order field + Field coredata.VendorOrderField `json:"field"` +} + +// ListVendorsInputFilter represents the schema +type ListVendorsInputFilter struct { + // Snapshot ID + SnapshotID *gid.GID `json:"snapshot_id,omitempty"` } diff --git a/pkg/server/api/mcp/v1/types/vendor.go b/pkg/server/api/mcp/v1/types/vendor.go index 9d5daa3b2..8426e51d2 100644 --- a/pkg/server/api/mcp/v1/types/vendor.go +++ b/pkg/server/api/mcp/v1/types/vendor.go @@ -15,273 +15,30 @@ package types import ( - "time" - - "github.com/google/jsonschema-go/jsonschema" "go.probo.inc/probo/pkg/coredata" - "go.probo.inc/probo/pkg/gid" "go.probo.inc/probo/pkg/page" ) -type ( - VendorOrderBy OrderBy[coredata.VendorOrderField] - - VendorFilter struct { - SnapshotID *gid.GID `json:"snapshot_id"` - } - - ListVendorsInput struct { - OrganizationID gid.GID `json:"organization_id"` - Filter *VendorFilter `json:"filter"` - OrderBy *VendorOrderBy `json:"order_field"` - Cursor *page.CursorKey `json:"cursor"` - Size *int `json:"size"` - } - - ListVendorsOutput struct { - NextCursor *string `json:"next_cursor"` - Vendors []Vendor `json:"vendors"` - } - - AddVendorInput struct { - OrganizationID gid.GID `json:"organization_id"` - Name string `json:"name"` - Description *string `json:"description"` - HeadquarterAddress *string `json:"headquarter_address"` - LegalName *string `json:"legal_name"` - WebsiteURL *string `json:"website_url"` - Category *coredata.VendorCategory `json:"category"` - PrivacyPolicyURL *string `json:"privacy_policy_url"` - ServiceLevelAgreementURL *string `json:"service_level_agreement_url"` - DataProcessingAgreementURL *string `json:"data_processing_agreement_url"` - BusinessAssociateAgreementURL *string `json:"business_associate_agreement_url"` - SubprocessorsListURL *string `json:"subprocessors_list_url"` - Certifications []string `json:"certifications"` - Countries []coredata.CountryCode `json:"countries"` - SecurityPageURL *string `json:"security_page_url"` - TrustPageURL *string `json:"trust_page_url"` - TermsOfServiceURL *string `json:"terms_of_service_url"` - StatusPageURL *string `json:"status_page_url"` - BusinessOwnerID *gid.GID `json:"business_owner_id"` - SecurityOwnerID *gid.GID `json:"security_owner_id"` - } - - AddVendorOutput struct { - Vendor Vendor `json:"vendor" jsonschema:"the created vendor"` - } - - Vendor struct { - ID gid.GID `json:"id"` - OrganizationID gid.GID `json:"organization_id"` - Name string `json:"name"` - Description *string `json:"description"` - Category coredata.VendorCategory `json:"category"` - HeadquarterAddress *string `json:"headquarter_address"` - LegalName *string `json:"legal_name"` - WebsiteURL *string `json:"website_url"` - PrivacyPolicyURL *string `json:"privacy_policy_url"` - ServiceLevelAgreementURL *string `json:"service_level_agreement_url"` - DataProcessingAgreementURL *string `json:"data_processing_agreement_url"` - BusinessAssociateAgreementURL *string `json:"business_associate_agreement_url"` - SubprocessorsListURL *string `json:"subprocessors_list_url"` - Certifications []string `json:"certifications"` - Countries []coredata.CountryCode `json:"countries"` - BusinessOwnerID *gid.GID `json:"business_owner_id,omitempty"` - SecurityOwnerID *gid.GID `json:"security_owner_id,omitempty"` - StatusPageURL *string `json:"status_page_url,omitempty"` - TermsOfServiceURL *string `json:"terms_of_service_url,omitempty"` - SecurityPageURL *string `json:"security_page_url,omitempty"` - TrustPageURL *string `json:"trust_page_url,omitempty"` - ShowOnTrustCenter bool `json:"show_on_trust_center,omitempty"` - SnapshotID *gid.GID `json:"snapshot_id,omitempty"` - SourceID *gid.GID `json:"source_id,omitempty"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` - } -) - -var ( - ListVendorsInputSchema = &jsonschema.Schema{ - Type: "object", - Required: []string{"organization_id"}, - Properties: map[string]*jsonschema.Schema{ - "organization_id": {Type: "string"}, - "filter": { - Type: "object", - Properties: map[string]*jsonschema.Schema{ - "snapshot_id": {Type: "string"}, - }, - }, - "order_field": { - Types: []string{"object", "null"}, - Properties: map[string]*jsonschema.Schema{ - "field": {Type: "string", Enum: []any{"CREATED_AT", "UPDATED_AT", "NAME"}}, - "direction": OrderByDirectionSchema, - }, - }, - "cursor": {Types: []string{"string", "null"}}, - "size": {Types: []string{"integer", "null"}}, - }, - } - - OrderByDirectionSchema = &jsonschema.Schema{ - Type: "string", - Enum: []any{"ASC", "DESC"}, - } - - NullableStringSchema = &jsonschema.Schema{ - Types: []string{"string", "null"}, - } - - VendorCategorySchema = &jsonschema.Schema{ - Type: "string", - Enum: []any{ - coredata.VendorCategoryAnalytics.String(), - coredata.VendorCategoryCloudMonitoring.String(), - coredata.VendorCategoryCloudProvider.String(), - coredata.VendorCategoryCollaboration.String(), - coredata.VendorCategoryCustomerSupport.String(), - coredata.VendorCategoryDataStorageAndProcessing.String(), - coredata.VendorCategoryDocumentManagement.String(), - coredata.VendorCategoryEmployeeManagement.String(), - coredata.VendorCategoryEngineering.String(), - coredata.VendorCategoryFinance.String(), - coredata.VendorCategoryIdentityProvider.String(), - coredata.VendorCategoryIT.String(), - coredata.VendorCategoryMarketing.String(), - coredata.VendorCategoryOfficeOperations.String(), - coredata.VendorCategoryOther.String(), - coredata.VendorCategoryPasswordManagement.String(), - coredata.VendorCategoryProductAndDesign.String(), - coredata.VendorCategoryProfessionalServices.String(), - coredata.VendorCategoryRecruiting.String(), - coredata.VendorCategorySales.String(), - coredata.VendorCategorySecurity.String(), - coredata.VendorCategoryVersionControl.String(), - }, - } - - VendorSchema = &jsonschema.Schema{ - Type: "object", - Properties: map[string]*jsonschema.Schema{ - "id": {Type: "string"}, - "name": {Type: "string"}, - "organization_id": {Type: "string"}, - "description": NullableStringSchema, - "category": VendorCategorySchema, - "headquarter_address": NullableStringSchema, - "legal_name": NullableStringSchema, - "website_url": NullableStringSchema, - "privacy_policy_url": NullableStringSchema, - "service_level_agreement_url": NullableStringSchema, - "data_processing_agreement_url": NullableStringSchema, - "business_associate_agreement_url": NullableStringSchema, - "subprocessors_list_url": NullableStringSchema, - "certifications": {Types: []string{"array", "null"}, Items: &jsonschema.Schema{Type: "string"}}, - "countries": {Types: []string{"array", "null"}, Items: &jsonschema.Schema{Type: "string", Enum: []any{"US", "CA", "GB", "DE", "FR", "IT", "ES", "NL", "BE", "CH", "AT", "SE", "NO", "DK", "FI", "EE", "LT", "LV", "PL", "CZ", "SK", "HU", "RO", "BG", "HR", "SI", "ME", "AL", "MK", "BA", "XK", "XA", "XZ"}}}, - "business_owner_id": NullableStringSchema, - "security_owner_id": NullableStringSchema, - "status_page_url": NullableStringSchema, - "terms_of_service_url": NullableStringSchema, - "security_page_url": NullableStringSchema, - "trust_page_url": NullableStringSchema, - "show_on_trust_center": {Type: "boolean"}, - "snapshot_id": NullableStringSchema, - "source_id": NullableStringSchema, - "created_at": {Type: "string"}, - "updated_at": {Type: "string"}, - }, - } - - ListVendorsOutputSchema = &jsonschema.Schema{ - Type: "object", - Properties: map[string]*jsonschema.Schema{ - "next_cursor": {Types: []string{"string", "null"}}, - "vendors": { - Type: "array", - Items: VendorSchema, - }, - }, - } - - AddVendorInputSchema = &jsonschema.Schema{ - Type: "object", - Required: []string{"organization_id", "name"}, - Properties: map[string]*jsonschema.Schema{ - "organization_id": {Type: "string"}, - "name": {Type: "string"}, - "description": NullableStringSchema, - "headquarter_address": NullableStringSchema, - "legal_name": NullableStringSchema, - "website_url": NullableStringSchema, - "category": VendorCategorySchema, - "privacy_policy_url": NullableStringSchema, - "service_level_agreement_url": NullableStringSchema, - "data_processing_agreement_url": NullableStringSchema, - "business_associate_agreement_url": NullableStringSchema, - "subprocessors_list_url": NullableStringSchema, - "certifications": {Types: []string{"array", "null"}, Items: &jsonschema.Schema{Type: "string"}}, - "countries": {Types: []string{"array", "null"}, Items: &jsonschema.Schema{Type: "string", Enum: []any{"US", "CA", "GB", "DE", "FR", "IT", "ES", "NL", "BE", "CH", "AT", "SE", "NO", "DK", "FI", "EE", "LT", "LV", "PL", "CZ", "SK", "HU", "RO", "BG", "HR", "SI", "ME", "AL", "MK", "BA", "XK", "XA", "XZ"}}}, - "business_owner_id": NullableStringSchema, - "security_owner_id": NullableStringSchema, - "status_page_url": NullableStringSchema, - "terms_of_service_url": NullableStringSchema, - "security_page_url": NullableStringSchema, - "trust_page_url": NullableStringSchema, - "show_on_trust_center": {Type: "boolean"}, - "snapshot_id": NullableStringSchema, - "source_id": NullableStringSchema, - }, - } - - AddVendorOutputSchema = &jsonschema.Schema{ - Type: "object", - Properties: map[string]*jsonschema.Schema{ - "vendor": VendorSchema, - }, - } -) - -func NewVendor(v *coredata.Vendor) Vendor { - return Vendor{ - Name: v.Name, - ID: v.ID, - OrganizationID: v.OrganizationID, - Description: v.Description, - Category: v.Category, - HeadquarterAddress: v.HeadquarterAddress, - LegalName: v.LegalName, - WebsiteURL: v.WebsiteURL, - PrivacyPolicyURL: v.PrivacyPolicyURL, - ServiceLevelAgreementURL: v.ServiceLevelAgreementURL, - DataProcessingAgreementURL: v.DataProcessingAgreementURL, - BusinessAssociateAgreementURL: v.BusinessAssociateAgreementURL, - SubprocessorsListURL: v.SubprocessorsListURL, - Certifications: v.Certifications, - Countries: v.Countries, - BusinessOwnerID: v.BusinessOwnerID, - SecurityOwnerID: v.SecurityOwnerID, - StatusPageURL: v.StatusPageURL, - TermsOfServiceURL: v.TermsOfServiceURL, - SecurityPageURL: v.SecurityPageURL, - TrustPageURL: v.TrustPageURL, - ShowOnTrustCenter: v.ShowOnTrustCenter, - SnapshotID: v.SnapshotID, - SourceID: v.SourceID, - CreatedAt: v.CreatedAt, - UpdatedAt: v.UpdatedAt, +func NewVendor(v *coredata.Vendor) *Vendor { + return &Vendor{ + Name: v.Name, + ID: v.ID, + OrganizationID: v.OrganizationID, + Description: v.Description, + CreatedAt: v.CreatedAt, + UpdatedAt: v.UpdatedAt, } } func NewListVendorsOutput(vendorPage *page.Page[*coredata.Vendor, coredata.VendorOrderField]) ListVendorsOutput { - vendors := make([]Vendor, 0, len(vendorPage.Data)) + vendors := make([]*Vendor, 0, len(vendorPage.Data)) for _, v := range vendorPage.Data { vendors = append(vendors, NewVendor(v)) } - var nextCursor *string + var nextCursor *page.CursorKey if len(vendorPage.Data) > 0 { - cursorKey := vendorPage.Data[len(vendorPage.Data)-1].CursorKey(vendorPage.Cursor.OrderBy.Field).String() + cursorKey := vendorPage.Data[len(vendorPage.Data)-1].CursorKey(vendorPage.Cursor.OrderBy.Field) nextCursor = &cursorKey } diff --git a/pkg/server/api/mcp/v1/v1_handler.go b/pkg/server/api/mcp/v1/v1_handler.go index 0a6e39198..9941c2541 100644 --- a/pkg/server/api/mcp/v1/v1_handler.go +++ b/pkg/server/api/mcp/v1/v1_handler.go @@ -1,4 +1,4 @@ -package v1 +package mcp_v1 import ( "context" @@ -13,19 +13,10 @@ import ( "go.probo.inc/probo/pkg/authz" "go.probo.inc/probo/pkg/gid" "go.probo.inc/probo/pkg/probo" - "go.probo.inc/probo/pkg/server/api/mcp/mcputils" + "go.probo.inc/probo/pkg/server/api/mcp/v1/server" ) -type ( - resolver struct { - proboSvc *probo.Service - authSvc *auth.Service - authzSvc *authz.Service - logger *log.Logger - } -) - -func (r *resolver) ProboService(ctx context.Context, tenantID gid.TenantID) *probo.TenantService { +func (r *Resolver) ProboService(ctx context.Context, tenantID gid.TenantID) *probo.TenantService { validateTenantAccess(ctx, tenantID) return r.proboSvc.WithTenant(tenantID) } @@ -52,30 +43,18 @@ func NewMux(logger *log.Logger, proboSvc *probo.Service, authSvc *auth.Service, log.String("version", cfg.Version), log.String("request_timeout", cfg.RequestTimeout.String()), ) + // server.AddReceivingMiddleware(mcputils.LoggingMiddleware(logger)) - server := mcp.NewServer( - &mcp.Implementation{ - Name: "probo", - Title: "Probo", - Version: cfg.Version, - }, - &mcp.ServerOptions{}, - ) - - server.AddReceivingMiddleware(mcputils.LoggingMiddleware(logger)) - - resolver := &resolver{ + resolver := &Resolver{ proboSvc: proboSvc, authSvc: authSvc, authzSvc: authzSvc, logger: logger, } - mcp.AddTool(server, ListOrganizationsTool, resolver.ListOrganizations) - mcp.AddTool(server, ListVendorsTool, resolver.ListVendors) - mcp.AddTool(server, AddVendorTool, resolver.AddVendor) + mcpServer := server.New(resolver) - getServer := func(r *http.Request) *mcp.Server { return server } + getServer := func(r *http.Request) *mcp.Server { return mcpServer } eventStore := mcp.NewMemoryEventStore(nil) handler := mcp.NewStreamableHTTPHandler(