@@ -4,6 +4,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
44import { z } from "zod" ;
55import fetch from 'node-fetch' ;
66import winston from 'winston' ;
7+ import readline from 'readline' ;
78const VERSION = "0.0.1" ; // Version of the MCP server
89// Configure winston logger
910const logger = winston . createLogger ( {
@@ -15,10 +16,24 @@ const logger = winston.createLogger({
1516 ]
1617} ) ;
1718const SOCKET_API_URL = "https://api.socket.dev/v0/purl?alerts=false&compact=false&fixable=false&licenseattrib=false&licensedetails=false" ;
18- const SOCKET_API_KEY = process . env . SOCKET_API_KEY || "" ;
19+ let SOCKET_API_KEY = process . env . SOCKET_API_KEY || "" ;
1920if ( ! SOCKET_API_KEY ) {
2021 logger . error ( "SOCKET_API_KEY environment variable is not set" ) ;
21- process . exit ( 1 ) ;
22+ const rl = readline . createInterface ( {
23+ input : process . stdin ,
24+ output : process . stderr
25+ } ) ;
26+ const apiKey = await new Promise ( ( resolve ) => {
27+ rl . question ( 'Please enter your Socket API key: ' , ( answer ) => {
28+ rl . close ( ) ;
29+ resolve ( answer ) ;
30+ } ) ;
31+ } ) ;
32+ if ( ! apiKey ) {
33+ logger . error ( "No API key provided" ) ;
34+ process . exit ( 1 ) ;
35+ }
36+ SOCKET_API_KEY = apiKey ;
2237}
2338const SOCKET_HEADERS = {
2439 "user-agent" : `socket-mcp/${ VERSION } ` ,
@@ -36,40 +51,45 @@ const server = new McpServer({
3651 tools : { } ,
3752 } ,
3853} ) ;
39- server . tool ( "depscore" , "Get the dependency score of a package with the `depscore` tool from Socket. Use 'unknown' for version if not known. Use this tool to scan dependencies for their quality and security on existing code or when code is generated. Stop generating code and ask the user how to proceed when any of the scores are low. When checking dependencies, make sure to also check the imports in the code, not just the manifest files (pyproject.toml, package.json, etc)." , {
40- ecosystem : z . string ( ) . describe ( "The package ecosystem (e.g., npm, pypi)" ) . default ( "npm" ) ,
41- depname : z . string ( ) . describe ( "The name of the dependency" ) ,
42- version : z . string ( ) . describe ( "The version of the dependency, use 'unknown' if not known" ) . default ( "unknown" ) ,
43- } , async ( { ecosystem, depname, version } ) => {
44- logger . info ( `Received request for ${ depname } (${ version } ) in ${ ecosystem } ecosystem` ) ;
45- // cleanup version
46- let purl ;
47- const cleanedVersion = version . replace ( / [ \^ ~ ] / g, '' ) ; // Remove ^ and ~ from version
48- if ( cleanedVersion === "1.0.0" || cleanedVersion === "unknown" || ! cleanedVersion ) {
49- purl = `pkg:${ ecosystem } /${ depname } ` ;
50- }
51- else {
52- logger . info ( `Using version ${ cleanedVersion } for ${ depname } ` ) ;
53- purl = `pkg:${ ecosystem } /${ depname } @${ cleanedVersion } ` ;
54- }
54+ server . tool ( "depscore" , "Get the dependency score of packages with the `depscore` tool from Socket. Use 'unknown' for version if not known. Use this tool to scan dependencies for their quality and security on existing code or when code is generated. Stop generating code and ask the user how to proceed when any of the scores are low. When checking dependencies, make sure to also check the imports in the code, not just the manifest files (pyproject.toml, package.json, etc)." , {
55+ packages : z . array ( z . object ( {
56+ ecosystem : z . string ( ) . describe ( "The package ecosystem (e.g., npm, pypi)" ) . default ( "npm" ) ,
57+ depname : z . string ( ) . describe ( "The name of the dependency" ) ,
58+ version : z . string ( ) . describe ( "The version of the dependency, use 'unknown' if not known" ) . default ( "unknown" ) ,
59+ } ) ) . describe ( "Array of packages to check" ) ,
60+ } , async ( { packages } ) => {
61+ logger . info ( `Received request for ${ packages . length } packages` ) ;
62+ // Build components array for the API request
63+ const components = packages . map ( pkg => {
64+ const cleanedVersion = pkg . version . replace ( / [ \^ ~ ] / g, '' ) ; // Remove ^ and ~ from version
65+ let purl ;
66+ if ( cleanedVersion === "1.0.0" || cleanedVersion === "unknown" || ! cleanedVersion ) {
67+ purl = `pkg:${ pkg . ecosystem } /${ pkg . depname } ` ;
68+ }
69+ else {
70+ logger . info ( `Using version ${ cleanedVersion } for ${ pkg . depname } ` ) ;
71+ purl = `pkg:${ pkg . ecosystem } /${ pkg . depname } @${ cleanedVersion } ` ;
72+ }
73+ return { purl } ;
74+ } ) ;
5575 try {
56- // Make a POST request to the Socket API
76+ // Make a POST request to the Socket API with all packages
5777 const response = await fetch ( SOCKET_API_URL , {
5878 method : 'POST' ,
5979 headers : SOCKET_HEADERS ,
60- body : JSON . stringify ( { components : [ { purl } ] } )
80+ body : JSON . stringify ( { components } )
6181 } ) ;
6282 const responseText = await response . text ( ) ;
6383 if ( response . status !== 200 ) {
64- const errorMsg = `Error processing ${ purl } : [${ response . status } ] ${ responseText } ` ;
84+ const errorMsg = `Error processing packages : [${ response . status } ] ${ responseText } ` ;
6585 logger . error ( errorMsg ) ;
6686 return {
6787 content : [ { type : "text" , text : errorMsg } ] ,
6888 isError : false
6989 } ;
7090 }
7191 else if ( ! responseText . trim ( ) ) {
72- const errorMsg = `${ purl } was not found.` ;
92+ const errorMsg = `No packages were found.` ;
7393 logger . error ( errorMsg ) ;
7494 return {
7595 content : [ { type : "text" , text : errorMsg } ] ,
@@ -78,67 +98,72 @@ server.tool("depscore", "Get the dependency score of a package with the `depscor
7898 }
7999 try {
80100 // Handle NDJSON (multiple JSON objects, one per line)
81- let jsonData ;
101+ let results = [ ] ;
82102 if ( ( response . headers . get ( 'content-type' ) || '' ) . includes ( 'x-ndjson' ) ) {
83103 const jsonLines = responseText . split ( '\n' )
84104 . filter ( line => line . trim ( ) )
85105 . map ( line => JSON . parse ( line ) ) ;
86106 if ( ! jsonLines . length ) {
87- const errorMsg = `No valid JSON objects found in NDJSON response for ${ purl } ` ;
107+ const errorMsg = `No valid JSON objects found in NDJSON response` ;
88108 return {
89109 content : [ { type : "text" , text : errorMsg } ] ,
90110 isError : true
91111 } ;
92112 }
93- jsonData = jsonLines [ 0 ] ;
94- }
95- else {
96- jsonData = JSON . parse ( responseText ) ;
97- }
98- if ( jsonData . score && jsonData . score . overall !== undefined ) {
99- // Unroll the jsonData.score object into key-value pairs
100- const scoreEntries = Object . entries ( jsonData . score )
101- . filter ( ( [ key ] ) => key !== "overall" && key !== "uuid" )
102- . map ( ( [ key , value ] ) => `${ key } : ${ value } ` )
103- . join ( ', ' ) ;
104- return {
105- content : [
106- {
107- type : "text" ,
108- text : `Dependency scores for ${ purl } : ${ scoreEntries } `
109- }
110- ]
111- } ;
113+ // Process each result
114+ for ( const jsonData of jsonLines ) {
115+ if ( jsonData . score && jsonData . score . overall !== undefined ) {
116+ const scoreEntries = Object . entries ( jsonData . score )
117+ . filter ( ( [ key ] ) => key !== "overall" && key !== "uuid" )
118+ . map ( ( [ key , value ] ) => `${ key } : ${ value } ` )
119+ . join ( ', ' ) ;
120+ const packageName = jsonData . name || 'unknown' ;
121+ results . push ( `${ packageName } : ${ scoreEntries } ` ) ;
122+ }
123+ else {
124+ const packageName = jsonData . name || 'unknown' ;
125+ results . push ( `${ packageName } : No score found` ) ;
126+ }
127+ }
112128 }
113129 else {
114- return {
115- content : [
116- {
117- type : "text" ,
118- text : `No score found for ${ purl } `
119- }
120- ]
121- } ;
130+ const jsonData = JSON . parse ( responseText ) ;
131+ if ( jsonData . score && jsonData . score . overall !== undefined ) {
132+ const scoreEntries = Object . entries ( jsonData . score )
133+ . filter ( ( [ key ] ) => key !== "overall" && key !== "uuid" )
134+ . map ( ( [ key , value ] ) => `${ key } : ${ value } ` )
135+ . join ( ', ' ) ;
136+ const packageName = jsonData . package ?. name || 'unknown' ;
137+ results . push ( `${ packageName } : ${ scoreEntries } ` ) ;
138+ }
122139 }
140+ return {
141+ content : [
142+ {
143+ type : "text" ,
144+ text : results . length > 0
145+ ? `Dependency scores:\n${ results . join ( '\n' ) } `
146+ : "No scores found for the provided packages"
147+ }
148+ ]
149+ } ;
123150 }
124151 catch ( e ) {
125152 const error = e ;
126- const errorMsg = `JSON parsing error for ${ purl } : ${ error . message } -- Response: ${ responseText } ` ;
153+ const errorMsg = `JSON parsing error: ${ error . message } -- Response: ${ responseText } ` ;
127154 logger . error ( errorMsg ) ;
128- const llmResponse = `Package ${ purl } not found.` ;
129155 return {
130- content : [ { type : "text" , text : llmResponse } ] ,
156+ content : [ { type : "text" , text : "Error parsing response from Socket API" } ] ,
131157 isError : true
132158 } ;
133159 }
134160 }
135161 catch ( e ) {
136162 const error = e ;
137- const errorMsg = `Error processing ${ purl } : ${ error . message } ` ;
163+ const errorMsg = `Error processing packages : ${ error . message } ` ;
138164 logger . error ( errorMsg ) ;
139- const llmResponse = `Package ${ purl } not found.` ;
140165 return {
141- content : [ { type : "text" , text : llmResponse } ] ,
166+ content : [ { type : "text" , text : "Error connecting to Socket API" } ] ,
142167 isError : true
143168 } ;
144169 }
0 commit comments