@@ -4,6 +4,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
4
4
import { z } from "zod" ;
5
5
import fetch from 'node-fetch' ;
6
6
import winston from 'winston' ;
7
+ import readline from 'readline' ;
7
8
const VERSION = "0.0.1" ; // Version of the MCP server
8
9
// Configure winston logger
9
10
const logger = winston . createLogger ( {
@@ -15,10 +16,24 @@ const logger = winston.createLogger({
15
16
]
16
17
} ) ;
17
18
const 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 || "" ;
19
20
if ( ! SOCKET_API_KEY ) {
20
21
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 ;
22
37
}
23
38
const SOCKET_HEADERS = {
24
39
"user-agent" : `socket-mcp/${ VERSION } ` ,
@@ -36,40 +51,45 @@ const server = new McpServer({
36
51
tools : { } ,
37
52
} ,
38
53
} ) ;
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
+ } ) ;
55
75
try {
56
- // Make a POST request to the Socket API
76
+ // Make a POST request to the Socket API with all packages
57
77
const response = await fetch ( SOCKET_API_URL , {
58
78
method : 'POST' ,
59
79
headers : SOCKET_HEADERS ,
60
- body : JSON . stringify ( { components : [ { purl } ] } )
80
+ body : JSON . stringify ( { components } )
61
81
} ) ;
62
82
const responseText = await response . text ( ) ;
63
83
if ( response . status !== 200 ) {
64
- const errorMsg = `Error processing ${ purl } : [${ response . status } ] ${ responseText } ` ;
84
+ const errorMsg = `Error processing packages : [${ response . status } ] ${ responseText } ` ;
65
85
logger . error ( errorMsg ) ;
66
86
return {
67
87
content : [ { type : "text" , text : errorMsg } ] ,
68
88
isError : false
69
89
} ;
70
90
}
71
91
else if ( ! responseText . trim ( ) ) {
72
- const errorMsg = `${ purl } was not found.` ;
92
+ const errorMsg = `No packages were found.` ;
73
93
logger . error ( errorMsg ) ;
74
94
return {
75
95
content : [ { type : "text" , text : errorMsg } ] ,
@@ -78,67 +98,72 @@ server.tool("depscore", "Get the dependency score of a package with the `depscor
78
98
}
79
99
try {
80
100
// Handle NDJSON (multiple JSON objects, one per line)
81
- let jsonData ;
101
+ let results = [ ] ;
82
102
if ( ( response . headers . get ( 'content-type' ) || '' ) . includes ( 'x-ndjson' ) ) {
83
103
const jsonLines = responseText . split ( '\n' )
84
104
. filter ( line => line . trim ( ) )
85
105
. map ( line => JSON . parse ( line ) ) ;
86
106
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` ;
88
108
return {
89
109
content : [ { type : "text" , text : errorMsg } ] ,
90
110
isError : true
91
111
} ;
92
112
}
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
+ }
112
128
}
113
129
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
+ }
122
139
}
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
+ } ;
123
150
}
124
151
catch ( e ) {
125
152
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 } ` ;
127
154
logger . error ( errorMsg ) ;
128
- const llmResponse = `Package ${ purl } not found.` ;
129
155
return {
130
- content : [ { type : "text" , text : llmResponse } ] ,
156
+ content : [ { type : "text" , text : "Error parsing response from Socket API" } ] ,
131
157
isError : true
132
158
} ;
133
159
}
134
160
}
135
161
catch ( e ) {
136
162
const error = e ;
137
- const errorMsg = `Error processing ${ purl } : ${ error . message } ` ;
163
+ const errorMsg = `Error processing packages : ${ error . message } ` ;
138
164
logger . error ( errorMsg ) ;
139
- const llmResponse = `Package ${ purl } not found.` ;
140
165
return {
141
- content : [ { type : "text" , text : llmResponse } ] ,
166
+ content : [ { type : "text" , text : "Error connecting to Socket API" } ] ,
142
167
isError : true
143
168
} ;
144
169
}
0 commit comments